""" Tests for the resolver """ import os import re import sys import pytest import yaml from tests.lib import DATA_DIR, create_basic_wheel_for_package, path_to_url def generate_yaml_tests(directory): """ Generate yaml test cases from the yaml files in the given directory """ for yml_file in directory.glob("*.yml"): data = yaml.safe_load(yml_file.read_text()) assert "cases" in data, "A fixture needs cases to be used in testing" # Strip the parts of the directory to only get a name without # extension and resolver directory base_name = str(yml_file)[len(str(directory)) + 1:-4] base = data.get("base", {}) cases = data["cases"] for resolver in 'old', 'new': for i, case_template in enumerate(cases): case = base.copy() case.update(case_template) case[":name:"] = base_name if len(cases) > 1: case[":name:"] += "-" + str(i) case[":name:"] += "*" + resolver case[":resolver:"] = resolver skip = case.pop("skip", False) assert skip in [False, True, 'old', 'new'] if skip is True or skip == resolver: case = pytest.param(case, marks=pytest.mark.xfail) yield case def id_func(param): """ Give a nice parameter name to the generated function parameters """ if isinstance(param, dict) and ":name:" in param: return param[":name:"] retval = str(param) if len(retval) > 25: retval = retval[:20] + "..." + retval[-2:] return retval def convert_to_dict(string): def stripping_split(my_str, splitwith, count=None): if count is None: return [x.strip() for x in my_str.strip().split(splitwith)] else: return [x.strip() for x in my_str.strip().split(splitwith, count)] parts = stripping_split(string, ";") retval = {} retval["depends"] = [] retval["extras"] = {} retval["name"], retval["version"] = stripping_split(parts[0], " ") for part in parts[1:]: verb, args_str = stripping_split(part, " ", 1) assert verb in ["depends"], "Unknown verb {!r}".format(verb) retval[verb] = stripping_split(args_str, ",") return retval def handle_request(script, action, requirement, options, new_resolver=False): if action == 'install': args = ['install'] if new_resolver: args.append("--use-feature=2020-resolver") args.extend(["--no-index", "--find-links", path_to_url(script.scratch_path)]) elif action == 'uninstall': args = ['uninstall', '--yes'] else: raise "Did not excpet action: {!r}".format(action) if isinstance(requirement, str): args.append(requirement) elif isinstance(requirement, list): args.extend(requirement) else: raise "requirement neither str nor list {!r}".format(requirement) args.extend(options) args.append("--verbose") result = script.pip(*args, allow_stderr_error=True, allow_stderr_warning=True, allow_error=True) # Check which packages got installed state = [] for path in os.listdir(script.site_packages_path): if path.endswith(".dist-info"): name, version = ( os.path.basename(path)[:-len(".dist-info")] ).rsplit("-", 1) # TODO: information about extras. state.append(" ".join((name, version))) return {"result": result, "state": sorted(state)} def check_error(error, result): return_code = error.get('code') if return_code: assert result.returncode == return_code stderr = error.get('stderr') if not stderr: return if isinstance(stderr, str): patters = [stderr] elif isinstance(stderr, list): patters = stderr else: raise "string or list expected, found %r" % stderr for patter in patters: match = re.search(patter, result.stderr, re.I) assert match, 'regex %r not found in stderr: %r' % ( stderr, result.stderr) @pytest.mark.yaml @pytest.mark.parametrize( "case", generate_yaml_tests(DATA_DIR.parent / "yaml"), ids=id_func ) def test_yaml_based(script, case): available = case.get("available", []) requests = case.get("request", []) responses = case.get("response", []) assert len(requests) == len(responses), ( "Expected requests and responses counts to be same" ) # Create a custom index of all the packages that are supposed to be # available # XXX: This doesn't work because this isn't making an index of files. for package in available: if isinstance(package, str): package = convert_to_dict(package) assert isinstance(package, dict), "Needs to be a dictionary" create_basic_wheel_for_package(script, **package) # use scratch path for index for request, response in zip(requests, responses): for action in 'install', 'uninstall': if action in request: break else: raise "Unsupported request {!r}".format(request) # Perform the requested action effect = handle_request(script, action, request[action], request.get('options', '').split(), case[':resolver:'] == 'new') result = effect['result'] if 0: # for analyzing output easier with open(DATA_DIR.parent / "yaml" / case[':name:'].replace('*', '-'), 'w') as fo: fo.write("=== RETURNCODE = %d\n" % result.returncode) fo.write("=== STDERR ===:\n%s\n" % result.stderr) if 'state' in response: assert effect['state'] == (response['state'] or []), str(result) error = response.get('error') if error and case[":resolver:"] == 'new' and sys.platform != 'win32': # Note: we currently skip running these tests on Windows, as they # were failing due to different error codes. There should not # be a reason for not running these this check on Windows. check_error(error, result)