def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. """ for dirname in CODE_DIR.iterdir(): if not dirname.is_dir(): continue if dirname != TESTS_DIR: config.addinivalue_line("norecursedirs", str(CODE_DIR / dirname)) # Expose the markers we use to pytest CLI config.addinivalue_line( "markers", "requires_salt_modules(*required_module_names): Skip if at least one module is not available.", ) config.addinivalue_line( "markers", "requires_salt_states(*required_state_names): Skip if at least one state module is not available.", ) config.addinivalue_line( "markers", "windows_whitelisted: Mark test as whitelisted to run under Windows" ) config.addinivalue_line( "markers", "requires_sshd_server: Mark test that require an SSH server running" ) # Make sure the test suite "knows" this is a pytest test run RUNTIME_VARS.PYTEST_SESSION = True # "Flag" the slotTest decorator if we're skipping slow tests or not os.environ["SLOW_TESTS"] = str(config.getoption("--run-slow"))
def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. """ for dirname in CODE_DIR.iterdir(): if not dirname.is_dir(): continue if dirname != TESTS_DIR: config.addinivalue_line("norecursedirs", str(CODE_DIR / dirname)) # Expose the markers we use to pytest CLI config.addinivalue_line( "markers", "requires_salt_modules(*required_module_names): Skip if at least one module is not available.", ) config.addinivalue_line( "markers", "requires_salt_states(*required_state_names): Skip if at least one state module is not available.", ) config.addinivalue_line( "markers", "windows_whitelisted: Mark test as whitelisted to run under Windows") # Make sure the test suite "knows" this is a pytest test run RUNTIME_VARS.PYTEST_SESSION = True # "Flag" the slotTest decorator if we're skipping slow tests or not os.environ["SLOW_TESTS"] = str(config.getoption("--run-slow")) # If PyTest has no logging configured, default to ERROR level levels = [logging.ERROR] logging_plugin = config.pluginmanager.get_plugin("logging-plugin") try: level = logging_plugin.log_cli_handler.level if level is not None: levels.append(level) except AttributeError: # PyTest CLI logging not configured pass try: level = logging_plugin.log_file_level if level is not None: levels.append(level) except AttributeError: # PyTest Log File logging not configured pass if logging.NOTSET in levels: # We don't want the NOTSET level on the levels levels.pop(levels.index(logging.NOTSET)) log_level = logging.getLevelName(min(levels)) log_server = LogServer(log_level=log_level) config.pluginmanager.register(log_server, "salt-saltfactories-log-server")
def groups_collection_modifyitems(config, items): group_count = config.getoption("test-group-count") group_id = config.getoption("test-group") if not group_count or not group_id: # We're not selection tests using groups, don't do any filtering return total_items = len(items) tests_in_group, deselected = get_group(items, group_count, group_id) # Replace all items in the list items[:] = tests_in_group if deselected: config.hook.pytest_deselected(items=deselected) terminal_reporter = config.pluginmanager.get_plugin("terminalreporter") terminal_reporter.write( "Running test group #{} ({} tests)\n".format(group_id, len(items)), yellow=True, )
def pytest_collection_modifyitems(config, items): # Let PyTest or other plugins handle the initial collection yield group_count = config.getoption('test-group-count') group_id = config.getoption('test-group') if not group_count or not group_id: # We're not selection tests using groups, don't do any filtering return total_items = len(items) group_size = get_group_size(total_items, group_count) tests_in_group = get_group(items, group_count, group_size, group_id) # Replace all items in the list items[:] = tests_in_group terminal_reporter = config.pluginmanager.get_plugin('terminalreporter') terminal_reporter.write('Running test group #{0} ({1} tests)\n'.format( group_id, len(items)), yellow=True)
def from_filenames_collection_modifyitems(config, items): from_filenames = config.getoption("--from-filenames") if not from_filenames: # Don't do anything return test_categories_paths = ( (TESTS_DIR / "integration").relative_to(CODE_DIR), (TESTS_DIR / "multimaster").relative_to(CODE_DIR), (TESTS_DIR / "unit").relative_to(CODE_DIR), (PYTESTS_DIR / "e2e").relative_to(CODE_DIR), (PYTESTS_DIR / "functional").relative_to(CODE_DIR), (PYTESTS_DIR / "integration").relative_to(CODE_DIR), (PYTESTS_DIR / "unit").relative_to(CODE_DIR), ) test_module_paths = set() from_filenames_listing = set() for path in [pathlib.Path(path.strip()) for path in from_filenames.split(",")]: if path.is_absolute(): # In this case, this path is considered to be a file containing a line separated list # of files to consider with salt.utils.files.fopen(str(path)) as rfh: for line in rfh: line_path = pathlib.Path(line.strip()) if not line_path.exists(): continue from_filenames_listing.add(line_path) continue from_filenames_listing.add(path) filename_map = yaml.deserialize((TESTS_DIR / "filename_map.yml").read_text()) # Let's add the match all rule for rule, matches in filename_map.items(): if rule == "*": for match in matches: test_module_paths.add(_match_to_test_file(match)) break # Let's now go through the list of files gathered for filename in from_filenames_listing: if str(filename).startswith("tests/"): # Tests in the listing don't require additional matching and will be added to the # list of tests to run test_module_paths.add(filename) continue if filename.name == "setup.py" or str(filename).startswith("salt/"): if path.name == "__init__.py": # No direct macthing continue # Now let's try a direct match between the passed file and possible test modules for test_categories_path in test_categories_paths: test_module_path = test_categories_path / "test_{}".format(path.name) if test_module_path.is_file(): test_module_paths.add(test_module_path) continue # Do we have an entry in tests/filename_map.yml for rule, matches in filename_map.items(): if rule == "*": continue elif "|" in rule: # This is regex if re.match(rule, str(filename)): for match in matches: test_module_paths.add(_match_to_test_file(match)) elif "*" in rule or "\\" in rule: # Glob matching for filerule in CODE_DIR.glob(rule): if not filerule.exists(): continue filerule = filerule.relative_to(CODE_DIR) if filerule != filename: continue for match in matches: test_module_paths.add(_match_to_test_file(match)) else: if str(filename) != rule: continue # Direct file paths as rules filerule = pathlib.Path(rule) if not filerule.exists(): continue for match in matches: test_module_paths.add(_match_to_test_file(match)) continue else: log.debug("Don't know what to do with path %s", filename) selected = [] deselected = [] for item in items: itempath = pathlib.Path(str(item.fspath)).resolve().relative_to(CODE_DIR) if itempath in test_module_paths: selected.append(item) else: deselected.append(item) items[:] = selected if deselected: config.hook.pytest_deselected(items=deselected)
def from_filenames_collection_modifyitems(config, items): from_filenames = config.getoption("--from-filenames") if not from_filenames: # Don't do anything return log.info( "Calculating test modules to run based on the paths in --from-filenames" ) from_filenames_paths = set() for path in [path.strip() for path in from_filenames.split(",")]: # Make sure that, no matter what kind of path we're passed, Windows or Posix path, # we resolve it to the platform slash separator properly_slashed_path = pathlib.Path( path.replace("\\", os.sep).replace("/", os.sep)) if not properly_slashed_path.exists(): log.info( "The path %s(%s) passed in --from-filenames does not exist", path, properly_slashed_path, ) continue if properly_slashed_path.is_absolute(): # In this case, this path is considered to be a file containing a line separated list # of files to consider with salt.utils.files.fopen(str(path)) as rfh: for line in rfh: line_path = pathlib.Path(line.strip().replace( "\\", os.sep).replace("/", os.sep)) if not line_path.exists(): log.info( "The path %s contained in %s passed in --from-filenames does not exist", line_path, properly_slashed_path, ) continue from_filenames_paths.add(line_path) continue from_filenames_paths.add(properly_slashed_path) # Let's start collecting test modules test_module_paths = set() filename_map = yaml.deserialize( (TESTS_DIR / "filename_map.yml").read_text()) # Let's add the match all rule for rule, matches in filename_map.items(): if rule == "*": for match in matches: test_module_paths.add(_match_to_test_file(match)) break # Let's now go through the list of files gathered for path in from_filenames_paths: if path.as_posix().startswith("tests/"): if path.name == "conftest.py": # This is not a test module, but consider any test_*.py files in child directories for match in path.parent.rglob("test_*.py"): test_module_paths.add(match) continue # Tests in the listing don't require additional matching and will be added to the # list of tests to run test_module_paths.add(path) continue if path.name == "setup.py" or path.as_posix().startswith("salt/"): if path.name == "__init__.py": # No direct matching continue # Let's try a direct match between the passed file and possible test modules glob_patterns = ( # salt/version.py -> # tests/unit/test_version.py # tests/pytests/unit/test_version.py "**/test_{}".format(path.name), # salt/modules/grains.py -> # tests/pytests/integration/modules/grains/tests_*.py # salt/modules/saltutil.py -> # tests/pytests/integration/modules/saltutil/test_*.py "**/{}/test_*.py".format(path.stem), # salt/modules/config.py -> # tests/unit/modules/test_config.py # tests/integration/modules/test_config.py # tests/pytests/unit/modules/test_config.py # tests/pytests/integration/modules/test_config.py "**/{}/test_{}".format(path.parent.name, path.name), ) for pattern in glob_patterns: for match in TESTS_DIR.rglob(pattern): relative_path = match.relative_to(CODE_DIR) log.info("Glob pattern %r matched '%s'", pattern, relative_path) test_module_paths.add(relative_path) # Do we have an entry in tests/filename_map.yml for rule, matches in filename_map.items(): if rule == "*": continue elif "|" in rule: # This is regex if re.match(rule, path.as_posix()): for match in matches: test_module_paths.add(_match_to_test_file(match)) elif "*" in rule or "\\" in rule: # Glob matching for filerule in CODE_DIR.glob(rule): if not filerule.exists(): continue filerule = filerule.relative_to(CODE_DIR) if filerule != path: continue for match in matches: test_module_paths.add(_match_to_test_file(match)) else: if path.as_posix() != rule: continue # Direct file paths as rules filerule = pathlib.Path(rule) if not filerule.exists(): continue for match in matches: test_module_paths.add(_match_to_test_file(match)) continue else: log.info("Don't know what to do with path %s", path) log.info( "Collected the following paths from --from-filenames processing:\n%s", "\n".join(sorted(map(str, test_module_paths))), ) selected = [] deselected = [] for item in items: itempath = pathlib.Path(str( item.fspath)).resolve().relative_to(CODE_DIR) if itempath in test_module_paths: selected.append(item) else: deselected.append(item) items[:] = selected if deselected: config.hook.pytest_deselected(items=deselected)