def test_from_dict_duplicate_name_error(self) -> None: """Test raising duplicate error if unit names clash.""" unit = { "name": "BootUnit", "type": "boot", "should_include": { "adviser_pipeline": True }, "run": { "log": { "message": "Some text printed", "type": "INFO" } }, } prescription = { "units": { "boots": [unit, dict(unit)], }, } with pytest.raises(PrescriptionDuplicateUnitNameError): Prescription.from_dict(prescription, prescription_name="thoth", prescription_release="2021.06.15")
def test_from_dict_duplicate_name_error(self) -> None: """Test raising duplicate error if unit names clash.""" unit = { "name": "BootUnit", "type": "boot", "should_include": { "adviser_pipeline": True }, "run": { "log": { "message": "Some text printed", "type": "INFO" } }, } prescription = { "apiVersion": "thoth-station.ninja/v1", "kind": "prescription", "spec": { "name": "thoth", "release": "2021.03.30", "units": { "boots": [unit, dict(unit)], }, }, } with pytest.raises(PrescriptionDuplicateUnitNameError): Prescription.from_dict(prescription)
def validate_prescription(prescription: str) -> None: """Validate the given prescription.""" _LOGGER.info("Loading prescription %r", prescription) with open(prescription, "r") as prescription_file: content = yaml.safe_load(prescription_file) Prescription.validate(content) _LOGGER.info("Prescription %r validated successfully", prescription)
def test_verify_multiple_should_include( self, builder_context: PipelineBuilderContext) -> None: """Verify multiple should_include calls do not loop endlessly.""" builder_context.recommendation_type = RecommendationType.LATEST prescription_path = str(self.data_dir / "prescriptions" / "basic.yaml") builder_context.prescription = Prescription.load(prescription_path) self.verify_multiple_should_include(builder_context)
def test_run(self, context: Context) -> None: """Test remapping UBI to RHEL.""" assert not context.stack_info prescription_path = str(self.data_dir / "prescriptions" / "basic.yaml") context.prescription = Prescription.load(prescription_path) unit = self.UNIT_TESTED() unit.pre_run() with self.UNIT_TESTED.assigned_context(context): unit.run() assert context.stack_info, "No stack info provided" assert len(context.stack_info) == 1 assert context.stack_info[0]["type"] == "INFO" assert context.stack_info[0][ "message"] == "Using prescription 'thoth' release '2020.03.18'" assert self.verify_justification_schema(context.stack_info) is True
def validate_prescription(prescriptions: str, show_unit_names: bool, output: str) -> None: """Validate the given prescription.""" _LOGGER.info("Validating prescriptions in %r", prescriptions) prescription = Prescription.validate(prescriptions) _LOGGER.info("Prescriptions %r validated successfully", prescriptions) result = { "prescriptions": [{ "name": p[0], "release": p[1] } for p in prescription.prescriptions], "count": { "boots_count": sum(1 for _ in prescription.iter_boot_units()), "pseudonyms_count": sum(1 for _ in prescription.iter_pseudonym_units()), "sieves_count": sum(1 for _ in prescription.iter_sieve_units()), "steps_count": sum(1 for _ in prescription.iter_step_units()), "strides_count": sum(1 for _ in prescription.iter_stride_units()), "wraps_count": sum(1 for _ in prescription.iter_wrap_units()), }, "count_all": sum(1 for _ in prescription.units), } if show_unit_names: result["boots"] = sorted(prescription.boots_dict.keys()) result["pseudonyms"] = sorted(prescription.pseudonyms_dict.keys()) result["sieves"] = sorted(prescription.sieves_dict.keys()) result["steps"] = sorted(prescription.steps_dict.keys()) result["strides"] = sorted(prescription.strides_dict.keys()) result["wraps"] = sorted(prescription.wraps_dict.keys()) yaml.safe_dump(result, sys.stdout) if output: _LOGGER.info("Writing validated prescriptions to %r", output) with open(output, "wb") as fp: pickle.dump(prescription, fp)
def test_load(self) -> None: """Test loading prescription.""" prescription_path = str(self.data_dir / "prescriptions" / "basic.yaml") instance = Prescription.load(prescription_path) assert instance is not None assert list(instance.boots_dict) == ["thoth.BootUnit"] assert list(instance.pseudonyms_dict) == ["thoth.PseudonymUnit"] assert list(instance.sieves_dict) == ["thoth.SieveUnit"] assert list(instance.steps_dict) == ["thoth.StepUnit"] assert list(instance.strides_dict) == ["thoth.StrideUnit"] assert list(instance.wraps_dict) == ["thoth.WrapUnit"] assert [u.get_unit_name() for u in instance.iter_boot_units()] == ["thoth.BootUnit"] assert [u.get_unit_name() for u in instance.iter_pseudonym_units() ] == ["thoth.PseudonymUnit"] assert [u.get_unit_name() for u in instance.iter_sieve_units()] == ["thoth.SieveUnit"] assert [u.get_unit_name() for u in instance.iter_step_units()] == ["thoth.StepUnit"] assert [u.get_unit_name() for u in instance.iter_stride_units()] == ["thoth.StrideUnit"] assert [u.get_unit_name() for u in instance.iter_wrap_units()] == ["thoth.WrapUnit"]
def test_from_dict_validate_error(self) -> None: """Test raising an error if schema validation fails.""" with pytest.raises(PrescriptionSchemaError): Prescription.from_dict({"foo": "bar"})
def test_should_include(self, builder_context: PipelineBuilderContext) -> None: """Test including this pipeline unit.""" prescription_path = str(self.data_dir / "prescriptions" / "basic.yaml") builder_context.prescription = Prescription.load(prescription_path) assert list(self.UNIT_TESTED.should_include(builder_context)) == [{}]
def dependency_monkey( click_ctx: click.Context, *, beam_width: int, count: int, decision_type: str, predictor: str, report_output: str, requirements: str, requirements_format: str, stack_output: str, predictor_config: Optional[str] = None, context: Optional[str] = None, dry_run: bool = False, library_usage: Optional[str] = None, limit_latest_versions: Optional[int] = None, no_pretty: bool = False, plot: Optional[str] = None, runtime_environment: str = None, seed: Optional[int] = None, pipeline: Optional[str] = None, prescription: Optional[str] = None, dev: bool = False, ): """Generate software stacks based on all valid resolutions that conform version ranges.""" parameters = locals() parameters.pop("click_ctx") if pipeline and prescription: sys.exit("Options --pipeline/--prescription are disjoint") if library_usage: if os.path.isfile(library_usage): try: with open(library_usage, "r") as f: library_usage = json.load(f) except Exception: _LOGGER.error("Failed to load library usage file %r", library_usage) raise else: library_usage = json.loads(library_usage) # Show library usage in the final report. parameters["library_usage"] = library_usage runtime_environment = RuntimeEnvironment.load(runtime_environment) parameters["runtime_environment"] = runtime_environment.to_dict() decision_type = DecisionType.by_name(decision_type) requirements_format = PythonRecommendationOutput.by_name( requirements_format) project = _instantiate_project(requirements, runtime_environment=runtime_environment) parameters["requirements"] = project.pipfile.to_dict() parameters["project"] = project.to_dict() pipeline_config = None if pipeline is None else PipelineBuilder.load( pipeline) if pipeline_config is not None: parameters["pipeline"] = pipeline_config.to_dict() prescription_instance = None if prescription: if len(prescription) == 1: # Click does not support multiple parameters when supplied via env vars. Perform split on delimiter. prescription_instance = Prescription.load( *prescription[0].split(",")) else: prescription_instance = Prescription.load(*prescription) # Use current time to make sure we have possibly reproducible runs - the seed is reported. seed = seed if seed is not None else int(time.time()) predictor_class = _get_dependency_monkey_predictor(predictor, decision_type) predictor_kwargs = _get_predictor_kwargs(predictor_config) predictor_instance = predictor_class(**predictor_kwargs, keep_history=plot is not None) _LOGGER.info( "Starting resolver using predictor %r with random seed set to %r, predictor parameters: %r", predictor_class.__name__, seed, predictor_kwargs, ) random.seed(seed) termial_random.seed(seed) resolver = Resolver.get_dependency_monkey_instance( predictor=predictor_instance, project=project, library_usage=library_usage, count=count, beam_width=beam_width, limit_latest_versions=limit_latest_versions, decision_type=decision_type, pipeline_config=pipeline_config, prescription=prescription_instance, cli_parameters=parameters, ) del prescription # No longer needed, garbage collect it. context_content = {} try: with open(context) as f: context_content = json.load(f) except (FileNotFoundError, IOError): # IOError raised if context is too large to be handled with open. context_content = json.loads(context) parameters["context"] = context_content dependency_monkey_runner = DependencyMonkey( resolver=resolver, stack_output=stack_output, context=context_content, dry_run=dry_run, decision_type=decision_type, ) print_func = _PrintFunc( partial( print_command_result, click_ctx=click_ctx, analyzer=analyzer_name, analyzer_version=analyzer_version, output=report_output, pretty=not no_pretty, )) exit_code = subprocess_run( dependency_monkey_runner, print_func, result_dict={"parameters": parameters}, plot=plot, with_devel=dev, user_stack_scoring=False, # Keep verbose output (stating pipeline units run) in dependency-monkey. verbose=True, ) click_ctx.exit(int(exit_code != 0))
def advise( click_ctx: click.Context, *, beam_width: int, count: int, limit: int, output: str, recommendation_type: str, requirements_format: str, requirements: str, predictor: str, predictor_config: Optional[str] = None, library_usage: Optional[str] = None, limit_latest_versions: Optional[int] = None, no_pretty: bool = False, plot: Optional[str] = None, requirements_locked: Optional[str] = None, runtime_environment: Optional[str] = None, seed: Optional[int] = None, pipeline: Optional[str] = None, prescription: Optional[str] = None, constraints: Optional[str] = None, user_stack_scoring: bool = True, dev: bool = False, labels: Optional[str] = None, ): """Advise package and package versions in the given stack or on solely package only.""" parameters = locals() parameters.pop("click_ctx") if pipeline and prescription: sys.exit("Options --pipeline/--prescription are disjoint") if library_usage: if os.path.isfile(library_usage): try: with open(library_usage, "r") as f: library_usage = json.load(f) except Exception: _LOGGER.error("Failed to load library usage file %r", library_usage) raise else: library_usage = json.loads(library_usage) # Show library usage in the final report. parameters["library_usage"] = library_usage labels_dict = {} if labels: if os.path.isfile(labels): try: with open(labels, "r") as f: labels_dict = json.load(f) except Exception: _LOGGER.error("Failed to load labels file %r", labels) raise else: labels_dict = json.loads(labels) # Show labels in the final report. parameters["labels"] = labels_dict runtime_environment = RuntimeEnvironment.load(runtime_environment) recommendation_type = RecommendationType.by_name(recommendation_type) _LOGGER.info("Using recommendation type %s", recommendation_type.name.lower()) requirements_format = PythonRecommendationOutput.by_name( requirements_format) project = _instantiate_project(requirements, requirements_locked, runtime_environment=runtime_environment, constraints=constraints) pipeline_config = None if pipeline: pipeline_config = PipelineBuilder.load(pipeline) parameters["project"] = project.to_dict() if pipeline_config is not None: parameters["pipeline"] = pipeline_config.to_dict() prescription_instance = None if prescription: if len(prescription) == 1: # Click does not support multiple parameters when supplied via env vars. Perform split on delimiter. prescription_instance = Prescription.load( *prescription[0].split(",")) else: prescription_instance = Prescription.load(*prescription) predictor_class, predictor_kwargs = _get_adviser_predictor( predictor, recommendation_type) predictor_kwargs = _get_predictor_kwargs( predictor_config) or predictor_kwargs predictor_instance = predictor_class(**predictor_kwargs, keep_history=plot is not None) # Use current time to make sure we have possibly reproducible runs - the seed is reported. seed = seed if seed is not None else int(time.time()) _LOGGER.info( "Starting resolver using %r predictor with random seed set to %r, predictor parameters: %r", predictor_class.__name__, seed, predictor_kwargs, ) random.seed(seed) termial_random.seed(seed) resolver = Resolver.get_adviser_instance( predictor=predictor_instance, project=project, labels=labels_dict, library_usage=library_usage, recommendation_type=recommendation_type, limit=limit, count=count, beam_width=beam_width, limit_latest_versions=limit_latest_versions, pipeline_config=pipeline_config, prescription=prescription_instance, cli_parameters=parameters, ) del prescription # No longer needed, garbage collect it. print_func = _PrintFunc( partial( print_command_result, click_ctx=click_ctx, analyzer=analyzer_name, analyzer_version=analyzer_version, output=output, pretty=not no_pretty, )) exit_code = subprocess_run( resolver, print_func, plot=plot, result_dict={"parameters": parameters}, with_devel=dev, user_stack_scoring=user_stack_scoring, verbose=click_ctx.parent.params.get("verbose", False), ) # Push metrics. if _THOTH_METRICS_PUSHGATEWAY_URL: _METRIC_INFO.labels(_THOTH_DEPLOYMENT_NAME, analyzer_version).inc() _METRIC_DATABASE_SCHEMA_SCRIPT.labels( analyzer_name, resolver.graph.get_script_alembic_version_head(), _THOTH_DEPLOYMENT_NAME).inc() try: _LOGGER.debug("Submitting metrics to Prometheus pushgateway %s", _THOTH_METRICS_PUSHGATEWAY_URL) push_to_gateway(_THOTH_METRICS_PUSHGATEWAY_URL, job="adviser", registry=prometheus_registry) except Exception: _LOGGER.exception("An error occurred when pushing metrics") click_ctx.exit(int(exit_code != 0))
def test_from_dict_validate_error(self) -> None: """Test raising an error if schema validation fails.""" with pytest.raises(PrescriptionSchemaError): Prescription.from_dict({"foo": "bar"}, prescription_name="thoth", prescription_release="2021.06.15")
def validate_prescription(prescriptions: str) -> None: """Validate the given prescription.""" _LOGGER.info("Validating prescriptions in %r", prescriptions) Prescription.validate(prescriptions) _LOGGER.info("Prescriptions %r validated successfully", prescriptions)