| # pylint: disable=C0301,C0103,C0111 |
| from sys import platform |
| from platform import machine |
| import os |
| import signal |
| import psutil |
| import subprocess |
| from time import monotonic |
| from typing import Dict, List, Optional |
| |
| import xml.etree.ElementTree as ET |
| import glob |
| |
| from tests_engine import TestResult, CRITICAL_TEST |
| from unstable_tests import KnownUnstableSuite |
| |
| |
| THIS_PATH = os.path.abspath(os.path.dirname(__file__)) |
| |
| |
| def install_cli_arguments(parser): |
| parser.add_argument("--skip-building", action="store_true", help="Do not build tests before run.") |
| |
| |
| class NUnitTestSuite(object): |
| |
| def __init__(self, path): |
| self.path = path |
| self.known_unstable: Optional[KnownUnstableSuite] = None |
| |
| def check(self, options, number_of_runs): # API requires this method |
| pass |
| |
| def get_output_dir(self, options, iteration_index, suite_retry_index): |
| # Unused mechanism, this exists to keep a uniform interface with |
| # robot_tests_provider.py. |
| return options.results_directory |
| |
| def prepare(self, options): |
| if not options.skip_building: |
| print("Building {0}".format(self.path)) |
| arch = 'arm' if machine() in ['aarch64', 'arm64'] else 'i386' |
| params = ['build', '--verbosity', 'quiet', '--configuration', options.configuration, '/p:NET=true', f'/p:Architecture={arch}'] |
| result = subprocess.call(['dotnet', *params, self.path]) |
| if result != 0: |
| print("Building project `{}` failed with error code: {}".format(self.path, result)) |
| return result |
| else: |
| print('Skipping the build') |
| |
| return 0 |
| |
| def _cleanup_dangling(self, process, proc_name, test_agent_name): |
| for proc in psutil.process_iter(['pid', 'name', 'cmdline']): |
| if proc_name in (proc.info['name'] or ''): |
| flat_cmdline = ' '.join(proc.info['cmdline'] or []) |
| if test_agent_name in flat_cmdline and '--pid={}'.format(process.pid) in flat_cmdline: |
| # let's kill it |
| print('KILLING A DANGLING {} test process {}'.format(test_agent_name, proc.info['pid'])) |
| os.kill(proc.info['pid'], signal.SIGTERM) |
| |
| def run(self, options, iteration_index=1, suite_retry_index=0): |
| if self.known_unstable: |
| raise Exception("NUnit tests are not yet supported in the --unstable feature.") |
| |
| # The iteration_index and suite_retry_index arguments are not implemented. |
| # They exist for the sake of a uniform interface with robot_tests_provider. |
| print('Running ' + self.path) |
| |
| project_file = os.path.split(self.path)[1] |
| output_file = os.path.join(options.results_directory, 'results-{}.xml'.format(project_file)) |
| |
| args = ['dotnet', 'test', "--no-build", "--logger", "console;verbosity=detailed", "--logger", "trx;LogFileName={}".format(output_file), '--configuration', options.configuration, self.path] |
| |
| if options.stop_on_error: |
| args.append('--stoponerror') |
| |
| where_conditions = [] |
| if options.fixture: |
| where_conditions.append(options.fixture) |
| |
| cat = 'TestCategory' |
| if options.exclude: |
| for category in options.exclude: |
| where_conditions.append('{} != {}'.format(cat, category)) |
| if options.include: |
| for category in options.include: |
| where_conditions.append('{} = {}'.format(cat, category)) |
| |
| if where_conditions: |
| args.append('--filter') |
| args.append(' & '.join('({})'.format(x) for x in where_conditions)) |
| |
| startTimestamp = monotonic() |
| args += ['--', 'NUnit.DisplayName=FullName'] |
| process = subprocess.Popen(args) |
| print('dotnet test runner PID is {}'.format(process.pid), flush=True) |
| |
| process.wait() |
| self._cleanup_dangling(process, 'dotnet', 'dotnet test') |
| |
| result = process.returncode == 0 |
| endTimestamp = monotonic() |
| print('Suite ' + self.path + (' finished successfully!' if result else ' failed!') + ' in ' + str(round(endTimestamp - startTimestamp, 2)) + ' seconds.', flush=True) |
| return TestResult(result, [output_file]) |
| |
| def cleanup(self, options): |
| pass |
| |
| def should_retry_suite(self, options, iteration_index, suite_retry_index): |
| # Unused mechanism, this exists to keep a uniform interface with |
| # robot_tests_provider.py. |
| return False |
| |
| def tests_failed_due_to_renode_crash(self) -> bool: |
| # Unused mechanism, this exists to keep a uniform interface with |
| # robot_tests_provider.py. |
| return False |
| |
| @staticmethod |
| def find_failed_tests(path, files_pattern='*.csproj.xml'): |
| test_files = glob.glob(os.path.join(path, files_pattern)) |
| ret = {CRITICAL_TEST: []} |
| for test_file in test_files: |
| tree = ET.parse(test_file) |
| root = tree.getroot() |
| |
| xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010" |
| for test in root.iter(f"{{{xmlns}}}UnitTestResult"): |
| if test.attrib['outcome'] == 'Failed': |
| ret[CRITICAL_TEST].append(test.attrib['testName']) |
| |
| if not ret[CRITICAL_TEST]: |
| return None |
| return ret |
| |
| @staticmethod |
| def find_rerun_tests(path): |
| # Unused mechanism, this exists to keep a uniform interface with |
| # robot_tests_provider.py. |
| return None |