| #!/usr/bin/env python |
| """ |
| Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| See https://llvm.org/LICENSE.txt for license information. |
| SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| Provides classes used by the test results reporting infrastructure |
| within the LLDB test suite. |
| |
| |
| Tests the process_control module. |
| """ |
| |
| |
| # System imports. |
| import os |
| import os.path |
| import unittest |
| import sys |
| import threading |
| |
| # Our imports. |
| from test_runner import process_control |
| |
| |
| class TestInferiorDriver(process_control.ProcessDriver): |
| |
| def __init__(self, soft_terminate_timeout=None): |
| super(TestInferiorDriver, self).__init__( |
| soft_terminate_timeout=soft_terminate_timeout) |
| self.started_event = threading.Event() |
| self.started_event.clear() |
| |
| self.completed_event = threading.Event() |
| self.completed_event.clear() |
| |
| self.was_timeout = False |
| self.returncode = None |
| self.output = None |
| |
| def write(self, content): |
| # We'll swallow this to keep tests non-noisy. |
| # Uncomment the following line if you want to see it. |
| # sys.stdout.write(content) |
| pass |
| |
| def on_process_started(self): |
| self.started_event.set() |
| |
| def on_process_exited(self, command, output, was_timeout, exit_status): |
| self.returncode = exit_status |
| self.was_timeout = was_timeout |
| self.output = output |
| self.returncode = exit_status |
| self.completed_event.set() |
| |
| |
| class ProcessControlTests(unittest.TestCase): |
| |
| @classmethod |
| def _suppress_soft_terminate(cls, command): |
| # Do the right thing for your platform here. |
| # Right now only POSIX-y systems are reporting |
| # soft terminate support, so this is set up for |
| # those. |
| helper = process_control.ProcessHelper.process_helper() |
| signals = helper.soft_terminate_signals() |
| if signals is not None: |
| for signum in helper.soft_terminate_signals(): |
| command.extend(["--ignore-signal", str(signum)]) |
| |
| @classmethod |
| def inferior_command( |
| cls, |
| ignore_soft_terminate=False, |
| options=None): |
| |
| # Base command. |
| script_name = "{}/inferior.py".format(os.path.dirname(__file__)) |
| if not os.path.exists(script_name): |
| raise Exception( |
| "test inferior python script not found: {}".format(script_name)) |
| command = ([sys.executable, script_name]) |
| |
| if ignore_soft_terminate: |
| cls._suppress_soft_terminate(command) |
| |
| # Handle options as string or list. |
| if isinstance(options, str): |
| command.extend(options.split()) |
| elif isinstance(options, list): |
| command.extend(options) |
| |
| # Return full command. |
| return command |
| |
| |
| class ProcessControlNoTimeoutTests(ProcessControlTests): |
| """Tests the process_control module.""" |
| |
| def test_run_completes(self): |
| """Test that running completes and gets expected stdout/stderr.""" |
| driver = TestInferiorDriver() |
| driver.run_command(self.inferior_command()) |
| self.assertTrue( |
| driver.completed_event.wait(5), "process failed to complete") |
| self.assertEqual(driver.returncode, 0, "return code does not match") |
| |
| def test_run_completes_with_code(self): |
| """Test that running completes and gets expected stdout/stderr.""" |
| driver = TestInferiorDriver() |
| driver.run_command(self.inferior_command(options="-r10")) |
| self.assertTrue( |
| driver.completed_event.wait(5), "process failed to complete") |
| self.assertEqual(driver.returncode, 10, "return code does not match") |
| |
| |
| class ProcessControlTimeoutTests(ProcessControlTests): |
| |
| def test_run_completes(self): |
| """Test that running completes and gets expected return code.""" |
| driver = TestInferiorDriver() |
| timeout_seconds = 5 |
| driver.run_command_with_timeout( |
| self.inferior_command(), |
| "{}s".format(timeout_seconds), |
| False) |
| self.assertTrue( |
| driver.completed_event.wait(2 * timeout_seconds), |
| "process failed to complete") |
| self.assertEqual(driver.returncode, 0) |
| |
| def _soft_terminate_works(self, with_core): |
| # Skip this test if the platform doesn't support soft ti |
| helper = process_control.ProcessHelper.process_helper() |
| if not helper.supports_soft_terminate(): |
| self.skipTest("soft terminate not supported by platform") |
| |
| driver = TestInferiorDriver() |
| timeout_seconds = 5 |
| |
| driver.run_command_with_timeout( |
| # Sleep twice as long as the timeout interval. This |
| # should force a timeout. |
| self.inferior_command( |
| options="--sleep {}".format(timeout_seconds * 2)), |
| "{}s".format(timeout_seconds), |
| with_core) |
| |
| # We should complete, albeit with a timeout. |
| self.assertTrue( |
| driver.completed_event.wait(2 * timeout_seconds), |
| "process failed to complete") |
| |
| # Ensure we received a timeout. |
| self.assertTrue(driver.was_timeout, "expected to end with a timeout") |
| |
| self.assertTrue( |
| helper.was_soft_terminate(driver.returncode, with_core), |
| ("timeout didn't return expected returncode " |
| "for soft terminate with core: {}").format(driver.returncode)) |
| |
| def test_soft_terminate_works_core(self): |
| """Driver uses soft terminate (with core request) when process times out. |
| """ |
| self._soft_terminate_works(True) |
| |
| def test_soft_terminate_works_no_core(self): |
| """Driver uses soft terminate (no core request) when process times out. |
| """ |
| self._soft_terminate_works(False) |
| |
| def test_hard_terminate_works(self): |
| """Driver falls back to hard terminate when soft terminate is ignored. |
| """ |
| |
| driver = TestInferiorDriver(soft_terminate_timeout=2.0) |
| timeout_seconds = 1 |
| |
| driver.run_command_with_timeout( |
| # Sleep much longer than the timeout interval,forcing a |
| # timeout. Do whatever is needed to have the inferior |
| # ignore soft terminate calls. |
| self.inferior_command( |
| ignore_soft_terminate=True, |
| options="--never-return"), |
| "{}s".format(timeout_seconds), |
| True) |
| |
| # We should complete, albeit with a timeout. |
| self.assertTrue( |
| driver.completed_event.wait(60), |
| "process failed to complete") |
| |
| # Ensure we received a timeout. |
| self.assertTrue(driver.was_timeout, "expected to end with a timeout") |
| |
| helper = process_control.ProcessHelper.process_helper() |
| self.assertTrue( |
| helper.was_hard_terminate(driver.returncode), |
| ("timeout didn't return expected returncode " |
| "for hard teriminate: {} ({})").format( |
| driver.returncode, |
| driver.output)) |
| |
| def test_inferior_exits_with_live_child_shared_handles(self): |
| """inferior exit detected when inferior children are live with shared |
| stdout/stderr handles. |
| """ |
| # Requires review D13362 or equivalent to be implemented. |
| self.skipTest("http://reviews.llvm.org/D13362") |
| |
| driver = TestInferiorDriver() |
| |
| # Create the inferior (I1), and instruct it to create a child (C1) |
| # that shares the stdout/stderr handles with the inferior. |
| # C1 will then loop forever. |
| driver.run_command_with_timeout( |
| self.inferior_command( |
| options="--launch-child-share-handles --return-code 3"), |
| "5s", |
| False) |
| |
| # We should complete without a timetout. I1 should end |
| # immediately after launching C1. |
| self.assertTrue( |
| driver.completed_event.wait(5), |
| "process failed to complete") |
| |
| # Ensure we didn't receive a timeout. |
| self.assertFalse( |
| driver.was_timeout, "inferior should have completed normally") |
| |
| self.assertEqual( |
| driver.returncode, 3, |
| "expected inferior process to end with expected returncode") |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |