Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avocado instrumented timeout handling in thread #6060

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions avocado/core/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import functools
import inspect
import logging
import multiprocessing
import multiprocessing.pool
import os
import shutil
import sys
Expand Down Expand Up @@ -654,14 +656,26 @@

def _catch_test_status(self, method):
"""Wrapper around test methods for catching and logging failures."""
try:

def set_new_event_loop_for_method(method):
asyncio.set_event_loop(asyncio.new_event_loop())

Check warning on line 661 in avocado/core/test.py

View check run for this annotation

Codecov / codecov/patch

avocado/core/test.py#L661

Added line #L661 was not covered by tests
method()
if self.__log_warn_used and self.__status not in STATUSES_NOT_OK:
raise exceptions.TestWarn(
"Test passed but there were warnings "
"during execution. Check the log for "
"details."
)

try:
pool = multiprocessing.pool.ThreadPool(1)
res = pool.apply_async(set_new_event_loop_for_method, [method])
pool.close()
try:
res.get(self.timeout)
if self.__log_warn_used and self.__status not in STATUSES_NOT_OK:
raise exceptions.TestWarn(
"Test passed but there were warnings "
"during execution. Check the log for "
"details."
)
except multiprocessing.TimeoutError:
raise exceptions.TestInterrupt("Test interrupted: Timeout reached")

except exceptions.TestBaseException as detail:
self.__status = detail.status
self.__fail_class = detail.__class__.__name__
Expand All @@ -686,6 +700,8 @@
)
for e_line in tb_info:
self.log.error(e_line)
finally:
pool.terminate()

def run_avocado(self):
"""
Expand Down
26 changes: 6 additions & 20 deletions avocado/plugins/runners/avocado_instrumented.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@

from avocado.core.exceptions import TestInterrupt
from avocado.core.nrunner.app import BaseRunnerApp
from avocado.core.nrunner.runner import (
RUNNER_RUN_CHECK_INTERVAL,
RUNNER_RUN_STATUS_INTERVAL,
BaseRunner,
)
from avocado.core.nrunner.runner import RUNNER_RUN_CHECK_INTERVAL, BaseRunner
from avocado.core.test import TestID
from avocado.core.tree import TreeNodeEnvOnly
from avocado.core.utils import loader, messages
Expand Down Expand Up @@ -138,23 +134,14 @@
)

@staticmethod
def _monitor(proc, time_started, queue):
timeout = float("inf")
next_status_time = None
def _monitor(queue):
while True:
time.sleep(RUNNER_RUN_CHECK_INTERVAL)
now = time.monotonic()
if queue.empty():
if next_status_time is None or now > next_status_time:
next_status_time = now + RUNNER_RUN_STATUS_INTERVAL
yield messages.RunningMessage.get()
if (now - time_started) > timeout:
proc.terminate()
yield messages.RunningMessage.get()

Check warning on line 141 in avocado/plugins/runners/avocado_instrumented.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/avocado_instrumented.py#L141

Added line #L141 was not covered by tests
else:
message = queue.get()
if message.get("type") == "early_state":
timeout = float(message.get("timeout") or float("inf"))
else:
if message.get("type") != "early_state":

Check warning on line 144 in avocado/plugins/runners/avocado_instrumented.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/avocado_instrumented.py#L144

Added line #L144 was not covered by tests
yield message
if message.get("status") == "finished":
break
Expand All @@ -172,13 +159,12 @@

process.start()

time_started = time.monotonic()
for message in self._monitor(process, time_started, queue):
for message in self._monitor(queue):

Check warning on line 162 in avocado/plugins/runners/avocado_instrumented.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/avocado_instrumented.py#L162

Added line #L162 was not covered by tests
yield message

except TestInterrupt:
process.terminate()
for message in self._monitor(process, time_started, queue):
for message in self._monitor(queue):

Check warning on line 167 in avocado/plugins/runners/avocado_instrumented.py

View check run for this annotation

Codecov / codecov/patch

avocado/plugins/runners/avocado_instrumented.py#L167

Added line #L167 was not covered by tests
yield message
except Exception as e:
yield messages.StderrMessage.get(traceback.format_exc())
Expand Down
2 changes: 1 addition & 1 deletion selftests/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"nrunner-requirement": 28,
"unit": 678,
"jobs": 11,
"functional-parallel": 313,
"functional-parallel": 314,
"functional-serial": 7,
"optional-plugins": 0,
"optional-plugins-golang": 2,
Expand Down
51 changes: 50 additions & 1 deletion selftests/functional/plugin/runners/avocado_instrumented.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
import json
import os

from avocado import Test
from avocado.core.exit_codes import AVOCADO_JOB_INTERRUPTED
from avocado.utils import process
from avocado.utils import process, script
from selftests.utils import AVOCADO, TestCaseTmpDir

TIMEOU_TEST_WITH_EXCEPTION = """
import time

from avocado import Test

class TimeoutTest(Test):

timeout = 3

def test(self):
try:
time.sleep(5)
except Exception:
pass

def tearDown(self):
self.log.info("TearDown")
"""


class AvocadoInstrumentedRunnerTest(TestCaseTmpDir, Test):
def test_timeout(self):
Expand All @@ -16,3 +38,30 @@ def test_timeout(self):
"examples/tests/timeouttest.py:TimeoutTest.test: INTERRUPTED: Test interrupted: Timeout reached",
result.stdout_text,
)

def test_timeout_with_exception(self):
with script.TemporaryScript(
"test_timeout.py",
TIMEOU_TEST_WITH_EXCEPTION,
"avocado_timeout_test",
) as tst:
res = process.run(
(
f"{AVOCADO} run --disable-sysinfo "
f"--job-results-dir {self.tmpdir.name} {tst} "
f"--json -"
),
ignore_status=True,
)
results = json.loads(res.stdout_text)
self.assertIn(
"Test interrupted: Timeout reached",
results["tests"][0]["fail_reason"],
)
debug_log_path = results["tests"][0]["logfile"]
self.assertTrue(os.path.exists(debug_log_path))
with open(debug_log_path, encoding="utf-8") as file:
self.assertIn(
"INFO | TearDown",
file.read(),
)
Loading