1*0006be33SJames Wright#!/usr/bin/env python3 2*0006be33SJames Wrightfrom junit_common import * 3*0006be33SJames Wright 4*0006be33SJames Wright 5*0006be33SJames Wrightdef create_argparser() -> argparse.ArgumentParser: 6*0006be33SJames Wright """Creates argument parser to read command line arguments 7*0006be33SJames Wright 8*0006be33SJames Wright Returns: 9*0006be33SJames Wright argparse.ArgumentParser: Created `ArgumentParser` 10*0006be33SJames Wright """ 11*0006be33SJames Wright parser = argparse.ArgumentParser('Test runner with JUnit and TAP output') 12*0006be33SJames Wright parser.add_argument( 13*0006be33SJames Wright '-c', 14*0006be33SJames Wright '--ceed-backends', 15*0006be33SJames Wright type=str, 16*0006be33SJames Wright nargs='*', 17*0006be33SJames Wright default=['/cpu/self'], 18*0006be33SJames Wright help='libCEED backend to use with convergence tests') 19*0006be33SJames Wright parser.add_argument( 20*0006be33SJames Wright '-m', 21*0006be33SJames Wright '--mode', 22*0006be33SJames Wright type=RunMode, 23*0006be33SJames Wright action=CaseInsensitiveEnumAction, 24*0006be33SJames Wright help='Output mode, junit or tap', 25*0006be33SJames Wright default=RunMode.JUNIT) 26*0006be33SJames Wright parser.add_argument('-n', '--nproc', type=int, default=1, help='number of MPI processes') 27*0006be33SJames Wright parser.add_argument('-o', '--output', type=Optional[Path], default=None, help='Output file to write test') 28*0006be33SJames Wright parser.add_argument('-b', '--junit-batch', type=str, default='', help='Name of JUnit batch for output file') 29*0006be33SJames Wright parser.add_argument('-np', '--pool-size', type=int, default=1, help='Number of test cases to run in parallel') 30*0006be33SJames Wright parser.add_argument('-s', '--smartredis_dir', type=str, default='', help='path to SmartSim library, if present') 31*0006be33SJames Wright parser.add_argument('--has_torch', type=bool, default=False, help='Whether to build with torch') 32*0006be33SJames Wright parser.add_argument('test', help='Test executable', nargs='?') 33*0006be33SJames Wright 34*0006be33SJames Wright return parser 35*0006be33SJames Wright 36*0006be33SJames Wright 37*0006be33SJames Wright# Necessary functions for running tests 38*0006be33SJames Wrightclass CeedSuiteSpec(SuiteSpec): 39*0006be33SJames Wright def __init__(self, has_torch: bool): 40*0006be33SJames Wright self.has_torch: bool = has_torch 41*0006be33SJames Wright 42*0006be33SJames Wright def get_source_path(self, test: str) -> Path: 43*0006be33SJames Wright """Compute path to test source file 44*0006be33SJames Wright 45*0006be33SJames Wright Args: 46*0006be33SJames Wright test (str): Name of test 47*0006be33SJames Wright 48*0006be33SJames Wright Returns: 49*0006be33SJames Wright Path: Path to source file 50*0006be33SJames Wright """ 51*0006be33SJames Wright return Path(test).with_suffix('.c') 52*0006be33SJames Wright 53*0006be33SJames Wright # get path to executable 54*0006be33SJames Wright def get_run_path(self, test: str) -> Path: 55*0006be33SJames Wright """Compute path to built test executable file 56*0006be33SJames Wright 57*0006be33SJames Wright Args: 58*0006be33SJames Wright test (str): Name of test 59*0006be33SJames Wright 60*0006be33SJames Wright Returns: 61*0006be33SJames Wright Path: Path to test executable 62*0006be33SJames Wright """ 63*0006be33SJames Wright return Path('build') / test 64*0006be33SJames Wright 65*0006be33SJames Wright def get_output_path(self, test: str, output_file: str) -> Path: 66*0006be33SJames Wright """Compute path to expected output file 67*0006be33SJames Wright 68*0006be33SJames Wright Args: 69*0006be33SJames Wright test (str): Name of test 70*0006be33SJames Wright output_file (str): File name of output file 71*0006be33SJames Wright 72*0006be33SJames Wright Returns: 73*0006be33SJames Wright Path: Path to expected output file 74*0006be33SJames Wright """ 75*0006be33SJames Wright return Path('tests') / 'output' / output_file 76*0006be33SJames Wright 77*0006be33SJames Wright def check_pre_skip(self, test: str, spec: TestSpec, resource: str, nproc: int) -> Optional[str]: 78*0006be33SJames Wright """Check if a test case should be skipped prior to running, returning the reason for skipping 79*0006be33SJames Wright 80*0006be33SJames Wright Args: 81*0006be33SJames Wright test (str): Name of test 82*0006be33SJames Wright spec (TestSpec): Test case specification 83*0006be33SJames Wright resource (str): libCEED backend 84*0006be33SJames Wright nproc (int): Number of MPI processes to use when running test case 85*0006be33SJames Wright 86*0006be33SJames Wright Returns: 87*0006be33SJames Wright Optional[str]: Skip reason, or `None` if test case should not be skipped 88*0006be33SJames Wright """ 89*0006be33SJames Wright if contains_any(resource, ['occa']) and startswith_any( 90*0006be33SJames Wright test, ['t4', 't5', 'ex', 'mfem', 'nek', 'petsc', 'fluids', 'solids']): 91*0006be33SJames Wright return 'OCCA mode not supported' 92*0006be33SJames Wright if test.startswith('t318') and contains_any(resource, ['/gpu/cuda/ref']): 93*0006be33SJames Wright return 'CUDA ref backend not supported' 94*0006be33SJames Wright if test.startswith('t506') and contains_any(resource, ['/gpu/cuda/shared']): 95*0006be33SJames Wright return 'CUDA shared backend not supported' 96*0006be33SJames Wright for condition in spec.only: 97*0006be33SJames Wright if (condition == 'cpu') and ('gpu' in resource): 98*0006be33SJames Wright return 'CPU only test with GPU backend' 99*0006be33SJames Wright if condition == 'torch' and not self.has_torch: 100*0006be33SJames Wright return 'PyTorch only test without USE_TORCH=1' 101*0006be33SJames Wright 102*0006be33SJames Wright def check_post_skip(self, test: str, spec: TestSpec, resource: str, stderr: str) -> Optional[str]: 103*0006be33SJames Wright """Check if a test case should be allowed to fail, based on its stderr output 104*0006be33SJames Wright 105*0006be33SJames Wright Args: 106*0006be33SJames Wright test (str): Name of test 107*0006be33SJames Wright spec (TestSpec): Test case specification 108*0006be33SJames Wright resource (str): libCEED backend 109*0006be33SJames Wright stderr (str): Standard error output from test case execution 110*0006be33SJames Wright 111*0006be33SJames Wright Returns: 112*0006be33SJames Wright Optional[str]: Skip reason, or `None` if unexpeced error 113*0006be33SJames Wright """ 114*0006be33SJames Wright if 'OCCA backend failed to use' in stderr: 115*0006be33SJames Wright return f'OCCA mode not supported' 116*0006be33SJames Wright elif 'Backend does not implement' in stderr: 117*0006be33SJames Wright return f'Backend does not implement' 118*0006be33SJames Wright elif 'Can only provide HOST memory for this backend' in stderr: 119*0006be33SJames Wright return f'Device memory not supported' 120*0006be33SJames Wright elif 'Can only set HOST memory for this backend' in stderr: 121*0006be33SJames Wright return f'Device memory not supported' 122*0006be33SJames Wright elif 'Test not implemented in single precision' in stderr: 123*0006be33SJames Wright return f'Test not implemented in single precision' 124*0006be33SJames Wright elif 'No SYCL devices of the requested type are available' in stderr: 125*0006be33SJames Wright return f'SYCL device type not available' 126*0006be33SJames Wright elif 'You may need to add --download-ctetgen or --download-tetgen' in stderr: 127*0006be33SJames Wright return f'Tet mesh generator not installed for {test}, {spec.name}' 128*0006be33SJames Wright return None 129*0006be33SJames Wright 130*0006be33SJames Wright def check_required_failure(self, test: str, spec: TestSpec, resource: str, stderr: str) -> Tuple[str, bool]: 131*0006be33SJames Wright """Check whether a test case is expected to fail and if it failed expectedly 132*0006be33SJames Wright 133*0006be33SJames Wright Args: 134*0006be33SJames Wright test (str): Name of test 135*0006be33SJames Wright spec (TestSpec): Test case specification 136*0006be33SJames Wright resource (str): libCEED backend 137*0006be33SJames Wright stderr (str): Standard error output from test case execution 138*0006be33SJames Wright 139*0006be33SJames Wright Returns: 140*0006be33SJames Wright tuple[str, bool]: Tuple of the expected failure string and whether it was present in `stderr` 141*0006be33SJames Wright """ 142*0006be33SJames Wright test_id: str = test[:4] 143*0006be33SJames Wright fail_str: str = '' 144*0006be33SJames Wright if test_id in ['t006', 't007']: 145*0006be33SJames Wright fail_str = 'No suitable backend:' 146*0006be33SJames Wright elif test_id in ['t008']: 147*0006be33SJames Wright fail_str = 'Available backend resources:' 148*0006be33SJames Wright elif test_id in ['t110', 't111', 't112', 't113', 't114']: 149*0006be33SJames Wright fail_str = 'Cannot grant CeedVector array access' 150*0006be33SJames Wright elif test_id in ['t115']: 151*0006be33SJames Wright fail_str = 'Cannot grant CeedVector read-only array access, the access lock is already in use' 152*0006be33SJames Wright elif test_id in ['t116']: 153*0006be33SJames Wright fail_str = 'Cannot destroy CeedVector, the writable access lock is in use' 154*0006be33SJames Wright elif test_id in ['t117']: 155*0006be33SJames Wright fail_str = 'Cannot restore CeedVector array access, access was not granted' 156*0006be33SJames Wright elif test_id in ['t118']: 157*0006be33SJames Wright fail_str = 'Cannot sync CeedVector, the access lock is already in use' 158*0006be33SJames Wright elif test_id in ['t215']: 159*0006be33SJames Wright fail_str = 'Cannot destroy CeedElemRestriction, a process has read access to the offset data' 160*0006be33SJames Wright elif test_id in ['t303']: 161*0006be33SJames Wright fail_str = 'Length of input/output vectors incompatible with basis dimensions' 162*0006be33SJames Wright elif test_id in ['t408']: 163*0006be33SJames Wright fail_str = 'CeedQFunctionContextGetData(): Cannot grant CeedQFunctionContext data access, a process has read access' 164*0006be33SJames Wright elif test_id in ['t409'] and contains_any(resource, ['memcheck']): 165*0006be33SJames Wright fail_str = 'Context data changed while accessed in read-only mode' 166*0006be33SJames Wright 167*0006be33SJames Wright return fail_str, fail_str in stderr 168*0006be33SJames Wright 169*0006be33SJames Wright def check_allowed_stdout(self, test: str) -> bool: 170*0006be33SJames Wright """Check whether a test is allowed to print console output 171*0006be33SJames Wright 172*0006be33SJames Wright Args: 173*0006be33SJames Wright test (str): Name of test 174*0006be33SJames Wright 175*0006be33SJames Wright Returns: 176*0006be33SJames Wright bool: True if the test is allowed to print console output 177*0006be33SJames Wright """ 178*0006be33SJames Wright return test[:4] in ['t003'] 179*0006be33SJames Wright 180*0006be33SJames Wright 181*0006be33SJames Wrightif __name__ == '__main__': 182*0006be33SJames Wright args = create_argparser().parse_args() 183*0006be33SJames Wright 184*0006be33SJames Wright # run tests 185*0006be33SJames Wright if 'smartsim' in args.test: 186*0006be33SJames Wright has_smartsim: bool = args.smartredis_dir and Path(args.smartredis_dir).is_dir() 187*0006be33SJames Wright test_cases = [] 188*0006be33SJames Wright 189*0006be33SJames Wright if args.mode is RunMode.TAP: 190*0006be33SJames Wright print(f'1..1') 191*0006be33SJames Wright if has_smartsim: 192*0006be33SJames Wright sys.path.insert(0, str(Path(__file__).parents[1] / "examples" / "fluids")) 193*0006be33SJames Wright from smartsim_regression_framework import SmartSimTest 194*0006be33SJames Wright 195*0006be33SJames Wright test_framework = SmartSimTest(Path(__file__).parent / 'test_dir') 196*0006be33SJames Wright test_framework.setup() 197*0006be33SJames Wright 198*0006be33SJames Wright is_new_subtest = True 199*0006be33SJames Wright subtest_ok = True 200*0006be33SJames Wright for i, backend in enumerate(args.ceed_backends): 201*0006be33SJames Wright test_cases.append(test_framework.test_junit(backend)) 202*0006be33SJames Wright if is_new_subtest and args.mode == RunMode.TAP: 203*0006be33SJames Wright is_new_subtest = False 204*0006be33SJames Wright print(f'# Subtest: {test_cases[0].category}') 205*0006be33SJames Wright print(f' 1..{len(args.ceed_backends)}') 206*0006be33SJames Wright print(test_case_output_string(test_cases[i], TestSpec("SmartSim Tests"), args.mode, backend, '', i)) 207*0006be33SJames Wright if args.mode == RunMode.TAP: 208*0006be33SJames Wright print(f'{"" if subtest_ok else "not "}ok 1 - {test_cases[0].category}') 209*0006be33SJames Wright test_framework.teardown() 210*0006be33SJames Wright elif args.mode is RunMode.TAP: 211*0006be33SJames Wright print(f'ok 1 - # SKIP SmartSim not installed') 212*0006be33SJames Wright result: TestSuite = TestSuite('SmartSim Tests', test_cases) 213*0006be33SJames Wright else: 214*0006be33SJames Wright result: TestSuite = run_tests( 215*0006be33SJames Wright args.test, 216*0006be33SJames Wright args.ceed_backends, 217*0006be33SJames Wright args.mode, 218*0006be33SJames Wright args.nproc, 219*0006be33SJames Wright CeedSuiteSpec(args.has_torch), 220*0006be33SJames Wright args.pool_size) 221*0006be33SJames Wright 222*0006be33SJames Wright # write output and check for failures 223*0006be33SJames Wright if args.mode is RunMode.JUNIT: 224*0006be33SJames Wright write_junit_xml(result, args.output, args.junit_batch) 225*0006be33SJames Wright if has_failures(result): 226*0006be33SJames Wright sys.exit(1) 227