def test_run_noop(self, context: Context, state: State, tf_name: str, tf_version: str) -> None: """Test not removing scipy from a TensorFlow stack.""" scipy_package_version = PackageVersion( name="scipy", version="==1.2.2", develop=False, index=Source("https://pypi.org/simple"), ) tf_package_version = PackageVersion( name=tf_name, version=f"=={tf_version}", develop=False, index=Source("https://pypi.org/simple"), ) assert "tensorflow" not in state.resolved_dependencies state.resolved_dependencies[ "tensorflow"] = tf_package_version.to_tuple() context.register_package_version(tf_package_version) context.dependents["scipy"] = { scipy_package_version.to_tuple(): {(tf_package_version.to_tuple(), "rhel", "8", "3.6")} } unit = TensorFlowRemoveSciPyStep() with TensorFlowRemoveSciPyStep.assigned_context(context): assert unit.run(state, scipy_package_version) is None
def test_cve_sieve_allow_cve(self, context: Context) -> None: """Make sure a CVE filtering allows allow-cve configuration.""" context.graph.should_receive("get_python_cve_records_all").with_args( package_name="flask", package_version="0.12.0").and_return([self._FLASK_CVE]) pv = PackageVersion( name="flask", version="==0.12.0", index=Source("https://pypi.org/simple"), develop=False, ) assert not context.stack_info context.recommendation_type = RecommendationType.SECURITY context.labels["allow-cve"] = self._FLASK_CVE["cve_id"] with self.UNIT_TESTED.assigned_context(context): unit = self.UNIT_TESTED() unit.pre_run() result = list(unit.run((p for p in (pv, )))) assert result == [pv] assert context.stack_info == []
def test_run_dev(self, context: Context) -> None: """Test discarding user's lock file if development dependencies are not present in lock but provided.""" pipfile = str(self.data_dir / "projects" / "dev" / "Pipfile") pipfile_lock = str(self.data_dir / "projects" / "dev" / "Pipfile.lock") project = Project.from_files(pipfile_path=pipfile, pipfile_lock_path=pipfile_lock) assert project.pipfile.dev_packages.packages assert project.pipfile_lock.dev_packages.packages # Remove packages. project.pipfile_lock.dev_packages.packages.clear() context.cli_parameters["dev"] = True context.project = project unit = self.UNIT_TESTED() with unit.assigned_context(context): unit.run() assert project.pipfile.dev_packages.packages assert not project.pipfile_lock, "Lock file was not removed from the input" assert len(context.stack_info) == 1 assert self.verify_justification_schema(context.stack_info)
def test_register_package_version_existing( self, context: Context, package_version: PackageVersion) -> None: """Test registering an existing package version to context.""" assert context.register_package_version(package_version) is False assert context.get_package_version( package_version.to_tuple()) is package_version assert context.register_package_version(package_version) is True
def test_run(self, state: State, context: Context) -> None: """Test running this wrap.""" state = State() assert not state.justification state.resolved_dependencies["tensorflow"] = ("tensorflow", "2.3.0", "https://pypi.org/simple") unit = self.UNIT_TESTED() tf_package_version = PackageVersion( name="tensorflow", version="==2.3.0", index=Source("https://pypi.org/simple"), develop=False, ) context.register_package_version(tf_package_version) with unit.assigned_context(context): unit.run(state) assert len(state.justification) == 1 assert set( state.justification[0].keys()) == {"type", "message", "link"} assert state.justification[0][ "link"], "Empty link to justification document provided" assert state.justification[0]["type"] == "WARNING" assert ( state.justification[0]["message"] == "TensorFlow in version <=2.4 is slow when tf.keras.layers.Embedding is used" )
def test_register_package_tuple_existing( self, context: Context, package_tuple: Tuple[str, str, str]) -> None: """Check registering an existing package tuple does not instantiate a new one.""" with pytest.raises(NotFound): context.get_package_version(package_tuple) extras = ["postgresql"] package_version_registered = context.register_package_tuple( package_tuple, develop=True, extras=extras, os_name="fedora", os_version="31", python_version="3.7") assert package_version_registered is not None package_version_another = context.register_package_tuple( package_tuple, develop=True, extras=extras, os_name="fedora", os_version="31", python_version="3.7") assert package_version_registered is package_version_another, "Different instances returned"
def test_register_package_tuple_new( self, context: Context, package_tuple: Tuple[str, str, str]) -> None: """Test registering a new package tuple to the context.""" with pytest.raises(NotFound): context.get_package_version(package_tuple) extras = ["postgresql"] assert (context.register_package_tuple(package_tuple, develop=True, extras=extras, os_name="rhel", os_version="8.1", python_version="3.6") is not None) package_version = context.get_package_version(package_tuple) assert package_version.name == "selinon" assert package_version.version == "==1.0.0" assert package_version.develop is True assert package_version.index is not None assert package_version.index.url == "https://pypi.org/simple" assert package_version.markers is None assert package_version.extras == extras
def test_tf_21(self, context: Context, tf_version: str, h5py_version: str) -> None: """Test blocking resolution of h5py with TensorFlow==2.1 or TensorFlow==2.3.1.""" tf_package_version = PackageVersion( name="tensorflow", version=f"=={tf_version}", develop=False, index=Source("https://pypi.org/simple"), ) h5py_package_version = PackageVersion( name="h5py", version=f"=={h5py_version}", develop=False, index=Source("https://pypi.org/simple"), ) state = State() state.resolved_dependencies["tensorflow"] = tf_package_version.to_tuple() context.register_package_version(tf_package_version) assert not context.stack_info with self.UNIT_TESTED.assigned_context(context): unit = self.UNIT_TESTED() unit.pre_run() assert unit._message_logged is False with pytest.raises(NotAcceptable): unit.run(state, h5py_package_version) assert unit._message_logged is True assert context.stack_info assert self.verify_justification_schema(context.stack_info)
def test_run_not_acceptable(self, context: Context, tf_name: str, tf_version: str, gast_version: str) -> None: """Test not acceptable TensorFlow<=1.14 with gast>0.2.2.""" gast_package_version = PackageVersion( name="gast", version=f"=={gast_version}", develop=False, index=Source("https://pypi.org/simple"), ) tf_package_version = PackageVersion( name=tf_name, version=f"=={tf_version}", develop=False, index=Source("https://pypi.org/simple"), ) state = State() state.add_resolved_dependency(tf_package_version.to_tuple()) context.register_package_version(tf_package_version) unit = TensorFlow114GastStep() with unit.assigned_context(context): assert unit._message_logged is False with pytest.raises(NotAcceptable): unit.run(state, gast_package_version) assert unit._message_logged is True
def test_run_noop(self, context: Context, tf_name: str, tf_version: str, gast_version: str) -> None: """Test no operation performed when not invalid combination is seen.""" gast_package_version = PackageVersion( name="gast", version=f"=={gast_version}", develop=False, index=Source("https://pypi.org/simple"), ) tf_package_version = PackageVersion( name=tf_name, version=f"=={tf_version}", develop=False, index=Source("https://pypi.org/simple"), ) state = State() state.add_resolved_dependency(tf_package_version.to_tuple()) context.register_package_version(tf_package_version) unit = TensorFlow114GastStep() with unit.assigned_context(context): assert unit._message_logged is False assert unit.run(state, gast_package_version) is None assert unit._message_logged is False
def test_not_solved_without_error(self, context: Context) -> None: """Test a not found package is not accepted by sieve.""" package_version, project = self._get_case() (GraphDatabase.should_receive("has_python_solver_error").with_args( package_version.name, package_version.locked_version, package_version.index.url, os_name=None, os_version=None, python_version=None, ).and_return(True).once()) context.graph = GraphDatabase() context.project = flexmock( runtime_environment=RuntimeEnvironment.from_dict({})) assert not context.stack_info, "No stack info should be provided before test run" sieve = SolvedSieve() sieve.pre_run() with SolvedSieve.assigned_context(context): assert list(sieve.run(p for p in [package_version])) == [] assert context.stack_info, "No stack info provided by the pipeline unit" assert self.verify_justification_schema(context.stack_info) is True
def test_run_not_acceptable(self, context: Context, tf_name: str, tf_version: str, np_version: str) -> None: """Test resolutions that are not acceptable.""" package_version = PackageVersion( name="numpy", version=f"=={np_version}", develop=False, index=Source("https://pypi.org/simple"), ) tf_package_version = PackageVersion( name=tf_name, version=f"=={tf_version}", develop=False, index=Source("https://pypi.org/simple"), ) state = State() state.add_resolved_dependency(tf_package_version.to_tuple()) context.register_package_version(tf_package_version) unit = TensorFlow22NumPyStep() unit.pre_run() with unit.assigned_context(context): assert unit._message_logged is False with pytest.raises(NotAcceptable): unit.run(state, package_version) assert unit._message_logged is True
def test_get_package_version(self, context: Context, package_version: PackageVersion) -> None: """Test getting registering and getting a package version.""" with pytest.raises(NotFound): context.get_package_version(package_version.to_tuple()) assert context.register_package_version(package_version) is False assert context.get_package_version( package_version.to_tuple()) is package_version
def test_run_develop(self, context: Context, state: State, develop: bool) -> None: """Test running this pipeline unit resulting in a justification addition when "not" index url is used.""" prescription_str = f""" name: GHReleaseNotes type: wrap.GHReleaseNotes should_include: adviser_pipeline: true match: state: resolved_dependencies: name: thoth-solver develop: {'true' if develop else 'false'} run: release_notes: organization: thoth-station repository: solver tag_version_prefix: v """ units = list(self._instantiate_gh_release_notes_wrap(prescription_str)) assert len( units ) == 1, "Multiple units created, expected only one based on prescription" unit = units[0] state.resolved_dependencies.clear() state.justification.clear() package_version = PackageVersion( name="thoth-solver", version="==0.5.0", index=Source("https://pypi.org/simple"), develop=develop, ) state.add_resolved_dependency(package_version.to_tuple()) context.register_package_version(package_version) unit.pre_run() with unit.assigned_context(context): # Run twice to verify the justification is added just once. assert unit.run(state) is None assert unit.run(state) is None self.verify_justification_schema(state.justification) assert set(tuple(i.items()) for i in state.justification) == { ( ("type", "INFO"), ("message", "Release notes for package 'thoth-solver'"), ("link", "https://github.com/thoth-station/solver/releases/tag/v0.5.0" ), ("package_name", "thoth-solver"), ), }
def test_is_adviser(self) -> None: """Test checking if the given context is an adviser context.""" context = Context( project=None, graph=None, library_usage=None, limit=None, count=None, beam=None, recommendation_type=RecommendationType.LATEST, decision_type=None, ) assert context.is_adviser() is True assert context.is_dependency_monkey() is False
def test_run_develop_state_match(self, context: Context, state: State, develop: bool, state_develop: bool) -> None: """Test not running the prescription if develop flag is set also on the state match.""" prescription_str = f""" name: StepUnit type: step should_include: times: 1 adviser_pipeline: true match: package_version: name: numpy develop: {'true' if develop else 'false'} state: resolved_dependencies: - name: pytest develop: {'true' if state_develop else 'false'} run: score: 0.5 """ prescription = yaml.safe_load(prescription_str) PRESCRIPTION_STEP_SCHEMA(prescription) StepPrescription.set_prescription(prescription) package_version = PackageVersion( name="numpy", version="==1.19.1", index=Source("https://pypi.org/simple"), develop=develop, ) state_package_version = PackageVersion( name="pytest", version="==6.2.4", index=Source("https://pypi.org/simple"), develop=state_develop, ) state.add_resolved_dependency(state_package_version.to_tuple()) context.register_package_version(state_package_version) unit = StepPrescription() unit.pre_run() with unit.assigned_context(context): result = unit.run(state, package_version) assert isinstance(result, tuple) assert result[0] == 0.5 assert result[1] is None
def test_development_status( self, development_status_classifiers: List[str], recommendation_type: RecommendationType, justification: List[Dict[str, str]], state: State, context: Context, ) -> None: """Test adding justifications for development status.""" package_name = "thoth-common" package_version = "0.26.0" index_url = "https://pypi.org/simple" context.graph.should_receive("get_python_package_version_trove_classifiers_all").with_args( package_name=package_name, package_version=package_version, index_url=index_url, os_name=context.project.runtime_environment.operating_system.name, os_version=context.project.runtime_environment.operating_system.version, python_version=context.project.runtime_environment.python_version, ).and_return(development_status_classifiers).once() context.recommendation_type = recommendation_type state.justification.clear() unit = self.UNIT_TESTED() unit.pre_run() state.resolved_dependencies.clear() state.add_resolved_dependency((package_name, package_version, index_url)) with unit.assigned_context(context): unit.run(state) assert state.justification == justification
def test_pre_releases_disallowed_removal(self, context: Context, package_name: str, package_version: str) -> None: """Test no removals if pre-releases are allowed.""" pv = PackageVersion( name=package_name, version=f"=={package_version}", index=Source("https://pypi.org/simple"), develop=False, ) project = Project.from_strings(self._CASE_GLOBAL_DISALLOWED_PIPFILE) context.project = project sieve = self.UNIT_TESTED() sieve.update_configuration({ "package_name": None, "allow_prereleases": project.pipfile.thoth.allow_prereleases }) assert not context.stack_info with self.UNIT_TESTED.assigned_context(context): assert list(sieve.run(p for p in [pv])) == [] assert len(context.stack_info) == 1 assert self.verify_justification_schema(context.stack_info)
def test_no_rule(self, context: Context) -> None: """Test if no rule is configured for the given package.""" package_version = PackageVersion( name="flask", version="==1.1.2", index=Source("https://pypi.org/simple"), develop=False) (GraphDatabase.should_receive( "get_python_package_version_solver_rules_all").with_args( "flask", "1.1.2", "https://pypi.org/simple", ).and_return([])) (GraphDatabase.should_receive( "get_python_package_version_solver_rules_all").with_args( "flask", "1.1.2", ).and_return([])) context.graph = GraphDatabase() assert not context.stack_info, "No stack info should be provided before test run" sieve = self.UNIT_TESTED() sieve.pre_run() with self.UNIT_TESTED.assigned_context(context): assert list(sieve.run( p for p in [package_version])) == [package_version] assert not context.stack_info, "No stack info should be provided by the pipeline unit"
def test_remove_pre_releases_disallowed_noop(self, context: Context, package_name: str, package_version: str) -> None: """Test NOT removing dependencies based on pre-release configuration.""" pv = PackageVersion( name=package_name, version=f"=={package_version}", index=Source("https://pypi.org/simple"), develop=False, ) project = Project.from_strings(self._CASE_GLOBAL_DISALLOWED_PIPFILE) context.project = project sieve = self.UNIT_TESTED() sieve.update_configuration({ "package_name": None, "allow_prereleases": project.pipfile.thoth.allow_prereleases }) assert not context.stack_info with self.UNIT_TESTED.assigned_context(context): assert list(sieve.run(p for p in [pv])) == [pv] assert not context.stack_info, "No stack information should be provided"
def test_unknown_symbol(self, context: Context) -> None: """Test no sieving is done if an unknown symbol is spotted.""" context.library_usage = { # run_functions_eagerly available since 2.3.0. "report": { "tensorflow": ["tensorflow.SomeUnknownSymbol"] } } source = Source("https://pypi.org/simple") pv_0 = PackageVersion( name="tensorflow", version="==2.2.0", develop=False, index=source, ) pv_1 = PackageVersion( name="tensorflow", version="==2.3.0", develop=False, index=source, ) pv_2 = PackageVersion( name="tensorflow", version="==2.4.0", develop=False, index=source, ) unit = self.UNIT_TESTED() with unit.assigned_context(context): unit.pre_run() result = list(unit.run((pv for pv in (pv_0, pv_1, pv_2)))) assert len(result) == 3 assert result == [pv_0, pv_1, pv_2]
def test_policy_size_shrink(self, context: Context) -> None: """Test limiting policy size over runs.""" # The main difference with the similar TD test is in reward signal propagated. predictor = MCTS() predictor._policy = { ("numpy", "2.0.0", "https://pypi.org/simple"): [1.0, 100], ("tensorflow", "2.0.0", "https://thoth-station.ninja/simple"): [3.0, 100], } rewarded = list(predictor._policy.keys())[0] # numpy state = flexmock(score=0.5) state.should_receive( "iter_resolved_dependencies").with_args().and_return([rewarded ]).once() # No shrink as we are in this iteration. context.iteration = 3 * mcts_module._MCTS_POLICY_SIZE_CHECK_ITERATION old_policy_size = mcts_module._MCTS_POLICY_SIZE with predictor.assigned_context(context): try: mcts_module._MCTS_POLICY_SIZE = 1 predictor.set_reward_signal(state, rewarded, math.inf) finally: mcts_module._MCTS_POLICY_SIZE = old_policy_size # the numpy entry with a value of [1.5, 101] gets removed assert predictor._policy == { ("tensorflow", "2.0.0", "https://thoth-station.ninja/simple"): [3.0, 100], } assert predictor._next_state is None
def test_run( self, context: Context, runtime_environment_dict: Dict[str, Any], expected: List[Dict[str, str]], use_constraints: bool, use_library_usage: bool, ) -> None: """Test providing information about the runtime environment.""" if use_constraints: context.project.constraints = Constraints.from_string( "flask>=1.3.0") if use_library_usage: context.library_usage = {"flask": ["flask.App"]} context.project.runtime_environment = RuntimeEnvironment.from_dict( runtime_environment_dict) assert not context.stack_info unit = self.UNIT_TESTED() with unit.assigned_context(context): assert unit.run() is None assert context.stack_info == expected assert self.verify_justification_schema(context.stack_info)
def test_multiple_calls(self, context: Context) -> None: """Test multiple calls of resolver invalidate internal caches.""" context.library_usage = { # run_functions_eagerly available since 2.3.0. "report": { "tensorflow": [ "tensorflow.v2.__version__", "tensorflow.raw_ops.LoadDataset" ] } } source = Source("https://pypi.org/simple") pv_1 = PackageVersion( name="tensorflow", version="==2.2.0", develop=False, index=source, ) pv_2 = PackageVersion( name="tensorflow", version="==2.3.0", develop=False, index=source, ) unit = self.UNIT_TESTED() with unit.assigned_context(context): unit.pre_run() result = list(unit.run((pv for pv in (pv_1, pv_2)))) assert len(result) == 1 assert result == [pv_2] # Now without symbols introduced in 2.3.0 on the same unit instance. context.library_usage = { # run_functions_eagerly available since 2.3.0. "report": { "tensorflow": ["tensorflow.v2.__version__"] } } with unit.assigned_context(context): unit.pre_run() result = list(unit.run((pv for pv in (pv_1, pv_2)))) assert len(result) == 2 assert result == [pv_1, pv_2]
def test_run_match_develop(self, context: Context, state: State, develop: bool) -> None: """Test running this pipeline unit based on develop matching.""" prescription_str = f""" name: StrideUnit type: stride should_include: times: 1 adviser_pipeline: true match: state: resolved_dependencies: - name: flask develop: {'true' if develop else 'false'} run: stack_info: - type: INFO message: This message will be shown link: https://thoth-station.ninja """ prescription = yaml.safe_load(prescription_str) PRESCRIPTION_STRIDE_SCHEMA(prescription) StridePrescription.set_prescription(prescription) package_version = PackageVersion( name="flask", version="==2.0.1", index=Source("https://pypi.org/simple"), develop=develop, ) state.add_resolved_dependency(package_version.to_tuple()) context.register_package_version(package_version) assert not context.stack_info unit = StridePrescription() unit.pre_run() with unit.assigned_context(context): assert unit.run(state) is None # Run one more time to verify the stack info is added just once. assert unit.run(state) is None assert context.stack_info == [ {"type": "INFO", "message": "This message will be shown", "link": "https://thoth-station.ninja"} ]
def test_run_develop(self, context: Context, state: State, develop: bool) -> None: """Test running this pipeline unit based on matching develop flag.""" prescription_str = f""" name: WrapUnit type: wrap should_include: times: 1 adviser_pipeline: true match: state: resolved_dependencies: - name: flask develop: {'true' if develop else 'false'} run: justification: - type: INFO message: This message will be shown link: https://thoth-station.ninja """ prescription = yaml.safe_load(prescription_str) PRESCRIPTION_WRAP_SCHEMA(prescription) WrapPrescription.set_prescription(prescription) package_version = PackageVersion( name="flask", version="==2.0.1", index=Source("https://pypi.org/simple"), develop=develop, ) state.add_resolved_dependency(package_version.to_tuple()) context.register_package_version(package_version) state.justification.clear() unit = WrapPrescription() unit.pre_run() with unit.assigned_context(context): assert unit.run(state) is None # Run one more time to verify justification is added only once. assert unit.run(state) is None assert state.justification == unit.run_prescription["justification"]
def test_run_develop_no_match(self, context: Context, state: State, develop: bool) -> None: """Test running this pipeline unit resulting in a justification addition when "not" index url is used.""" prescription_str = f""" name: GHReleaseNotes type: wrap.GHReleaseNotes should_include: adviser_pipeline: true match: state: resolved_dependencies: name: thoth-solver develop: {'true' if develop else 'false'} run: release_notes: organization: thoth-station repository: solver tag_version_prefix: v """ units = list(self._instantiate_gh_release_notes_wrap(prescription_str)) assert len( units ) == 1, "Multiple units created, expected only one based on prescription" unit = units[0] state.resolved_dependencies.clear() state.justification.clear() package_version = PackageVersion( name="thoth-solver", version="==0.5.0", index=Source("https://pypi.org/simple"), develop=not develop, ) state.add_resolved_dependency(package_version.to_tuple()) context.register_package_version(package_version) unit.pre_run() with unit.assigned_context(context): assert unit.run(state) is None assert not state.justification
def test_run_version(self, context: Context, state: State, version_expected: str, version_provided: str, include: bool) -> None: """Test running this pipeline unit resulting in a justification addition when "not" index url is used.""" prescription_str = f""" name: GHReleaseNotes type: wrap.GHReleaseNotes should_include: adviser_pipeline: true match: state: resolved_dependencies: name: requests version: '{version_expected}' run: release_notes: organization: psf repository: requests """ units = list(self._instantiate_gh_release_notes_wrap(prescription_str)) assert len( units ) == 1, "Multiple units created, expected only one based on prescription" unit = units[0] state.resolved_dependencies.clear() state.justification.clear() package_version = PackageVersion( name="requests", version=f"=={version_provided}", index=Source("https://pypi.org/simple"), develop=False, ) state.add_resolved_dependency(package_version.to_tuple()) context.register_package_version(package_version) unit.pre_run() with unit.assigned_context(context): assert unit.run(state) is None assert len(state.justification) == int(include)
def test_get_top_accepted_final_state(self, context: Context) -> None: """Test retrieval of top accepted final state.""" context.count = 10 assert context.get_top_accepted_final_state() is None state1 = State(score=0.0) assert context.register_accepted_final_state(state1) is None assert context.get_top_accepted_final_state() is state1 state2 = State(score=1.0) assert context.register_accepted_final_state(state2) is None assert context.get_top_accepted_final_state() is state2 state3 = State(score=2.0) assert context.register_accepted_final_state(state3) is None assert context.get_top_accepted_final_state() is state3 state4 = State(score=0.5) assert context.register_accepted_final_state(state4) is None assert context.get_top_accepted_final_state() is state3
def test_rhel_assign(self, context: Context) -> None: """Test remapping UBI to RHEL.""" context.project = Project.from_strings(self._CASE_PIPFILE) context.project.runtime_environment.operating_system.name = "ubi" boot = UbiBoot() with UbiBoot.assigned_context(context): boot.run() assert context.project.runtime_environment.operating_system.name == "rhel" assert context.stack_info, "No stack info provided" assert self.verify_justification_schema(context.stack_info) is True