def __init__(self, project: project.Project) -> None: self.build_snaps: Set[str] = set() self.project = project # raw_snapcraft_yaml is read only, create a new copy snapcraft_yaml = apply_extensions(project.info.get_raw_snapcraft()) self.validator = Validator(snapcraft_yaml) self.validator.validate() snapcraft_yaml = self._expand_filesets(snapcraft_yaml) self.data = self._expand_env(snapcraft_yaml) self.data["architectures"] = _process_architectures( self.data.get("architectures"), project.deb_arch) self._ensure_no_duplicate_app_aliases() self._global_grammar_processor = grammar_processing.GlobalGrammarProcessor( properties=self.data, project=project) # XXX: Resetting snap_meta due to above mangling of data. # Convergence to operating on snap_meta will remove this requirement... project._snap_meta = Snap.from_dict(self.data) self.parts = PartsConfig(parts=self.data, project=project, validator=self.validator)
def __init__(self, project: project.Project) -> None: self.build_snaps: Set[str] = set() self.project = project # raw_snapcraft_yaml is read only, create a new copy snapcraft_yaml = apply_extensions(project.info.get_raw_snapcraft()) self.validator = Validator(snapcraft_yaml) self.validator.validate() snapcraft_yaml = self._expand_filesets(snapcraft_yaml) self.data = self._expand_env(snapcraft_yaml) self.data["architectures"] = _process_architectures( self.data.get("architectures"), project.deb_arch) self._ensure_no_duplicate_app_aliases() grammar_processor = grammar_processing.GlobalGrammarProcessor( properties=self.data, project=project) self.build_tools = grammar_processor.get_build_packages() self.build_tools |= set(project.additional_build_packages) # If version: git is used we want to add "git" to build-packages if self.data.get("version") == "git": self.build_tools.add("git") # XXX: Resetting snap_meta due to above mangling of data. # Convergence to operating on snap_meta will remove this requirement... project._snap_meta = Snap.from_dict(self.data) # Always add the base for building for non os and base snaps if project.info.base is None and project.info.type in ("app", "gadget"): raise SnapcraftEnvironmentError( "A base is required for snaps of type {!r}.".format( project.info.type)) if project.info.base is not None: # If the base is already installed by other means, skip its installation. # But, we should always add it when in a docker environment so # the creator of said docker image is aware that it is required. if common.is_process_container( ) or not repo.snaps.SnapPackage.is_snap_installed( project.info.base): self.build_snaps.add(project.info.base) self.parts = PartsConfig( parts=self.data, project=project, validator=self.validator, build_snaps=self.build_snaps, build_tools=self.build_tools, )
def __init__(self, project: project.Project) -> None: self.build_snaps = set() # type: Set[str] self.project = project # raw_snapcraft_yaml is read only, create a new copy snapcraft_yaml = apply_extensions(project.info.get_raw_snapcraft()) self.validator = Validator(snapcraft_yaml) self.validator.validate() snapcraft_yaml = self._expand_filesets(snapcraft_yaml) self.data = self._expand_env(snapcraft_yaml) self._ensure_no_duplicate_app_aliases() grammar_processor = grammar_processing.GlobalGrammarProcessor( properties=self.data, project=project) self.build_tools = grammar_processor.get_build_packages() self.build_tools |= set(project.additional_build_packages) # If version: git is used we want to add "git" to build-packages if self.data.get("version") == "git": self.build_tools.add("git") # Always add the base for building for non os and base snaps if project.info.base is not None and project.info.type not in ("base", "os"): # If the base is already installed by other means, skip its installation. # But, we should always add it when in a docker environment so # the creator of said docker image is aware that it is required. if common.is_docker_instance( ) or not repo.snaps.SnapPackage.is_snap_installed( project.info.base): self.build_snaps.add(project.info.base) elif project.info.type not in ("base", "os"): # This exception is here to help with porting issues with bases. In normal # executions, when no base is set, the legacy snapcraft will be executed. raise RuntimeError("A base is required for {!r} snaps.".format( project.info.type)) self.parts = PartsConfig( parts=self.data, project=project, validator=self.validator, build_snaps=self.build_snaps, build_tools=self.build_tools, ) self.data["architectures"] = _process_architectures( self.data.get("architectures"), project.deb_arch) conduct_environment_sanity_check(self.project, self.data, self.validator.schema)
def test_required_properties(data, key): del data[key] with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() assert f"{key!r} is a required property" in str(error.value)
def test_type_base_and_base(data): data["type"] = "base" with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() assert _BASE_TYPE_MSG in str(error.value)
def test_invalid(self, data, packages, message_contains): data["package-repositories"] = packages with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() assert message_contains in str(error.value)
def test_valid_app_daemons(self): self.data["apps"] = { "service1": {"command": "binary1 start", "daemon": "simple"}, "service2": { "command": "binary2", "stop-command": "binary2 --stop", "daemon": "simple", }, "service3": {"command": "binary3", "daemon": "forking"}, "service4": { "command": "binary4", "daemon": "simple", "restart-condition": "always", }, "service5": {"command": "binary5", "daemon": "notify"}, "service6": { "command": "binary6", "post-stop-command": "binary6 --post-stop", "daemon": "simple", }, "service7": { "command": "binary7", "reload-command": "binary7 --reload", "daemon": "simple", }, } Validator(self.data).validate()
def test_invalid_title(data, title, error_template): data["title"] = title with pytest.raises(snapcraft.yaml_utils.errors.YamlValidationError) as error: Validator(data).validate() assert _EXPECTED_ERROR_TEMPLATE[error_template].format(title) in str(error.value)
def test(self, data, architectures, message): data["architectures"] = architectures with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() assert message in str(error.value)
def test_invalid_adapter(data, adapter): data["apps"] = {"foo": {"command": "foo", "adapter": adapter}} with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = "The 'apps/foo/adapter' property does not match" assert expected_message in str(error.value)
def test_invalid_yaml_invalid_username(data): data["system-usernames"] = {"snap_user": "******"} with pytest.raises( snapcraft.yaml_utils.errors.YamlValidationError) as error: Validator(data).validate() expected_message = "The 'system-usernames' property does not match the required schema: 'snap_user' is not a valid system-username." assert expected_message in str(error.value)
def test_invalid_yaml_invalid_long_scope(data): data["system-usernames"] = {"snap_daemon": {"scope": "invalid-scope"}} with pytest.raises( snapcraft.yaml_utils.errors.YamlValidationError) as error: Validator(data).validate() expected_message = "The 'system-usernames/snap_daemon' property does not match the required schema: {'scope': 'invalid-scope'} is not valid under any of the given schemas" assert expected_message in str(error.value)
def test_required_properties(self): data = self.data.copy() del data[self.key] raised = self.assertRaises(errors.YamlValidationError, Validator(data).validate) expected_message = "'{}' is a required property".format(self.key) self.assertThat(raised.message, Equals(expected_message), message=data)
def test_invalid_command_chain(data, command_chain): data["apps"] = {"foo": {"command": "foo", "command-chain": command_chain}} with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = "The 'apps/foo/command-chain" assert expected_message in str(error.value)
def test_valid_modes(self): self.data["apps"] = { "service1": { "command": "binary1", "daemon": "simple", "stop-mode": self.mode, } } Validator(self.data).validate()
def test_valid_restart_conditions(self): self.data["apps"] = { "service1": { "command": "binary1", "daemon": "simple" } } self.data["apps"]["service1"]["restart-condition"] = self.condition Validator(self.data).validate()
def test_invalid_grade(data, grade): data["grade"] = grade with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ("The 'grade' property does not match the required " f"schema: {grade!r} is not one of ['stable', 'devel']") assert expected_message in str(error.value)
def test_valid_modes(data, mode): data["apps"] = { "service1": { "command": "binary1", "daemon": "simple", "stop-mode": mode } } Validator(data).validate()
def test(self, data, name, err): data["name"] = name with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() assert str(error.value).endswith( f"The 'name' property does not match the required schema: {name!r} is {err}" )
def test_daemon_dependency(data, option, value): data["apps"] = {"service1": {"command": "binary1", option: value}} with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() assert str(error.value).endswith( "The 'apps/service1' property does not match the required schema: " f"'daemon' is a dependency of {option!r}")
def test_invalid_compression(data, compression): data["compression"] = compression with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ( "The 'compression' property does not match the required " f"schema: {compression!r} is not one of ['lzo', 'xz']") assert expected_message in str(error.value)
def test_invalid_metadata_links(data, contact, donation, issues, source_code, website): data["contact"] = contact data["donation"] = donation data["issues"] = issues data["source-code"] = source_code data["website"] = website with pytest.raises(snapcraft.yaml_utils.errors.YamlValidationError): Validator(data).validate()
def test_invalid_names(self): data = self.data.copy() data["name"] = self.name raised = self.assertRaises(errors.YamlValidationError, Validator(data).validate) expected_message = ( "The 'name' property does not match the required schema: {!r} is {}" ).format(self.name, self.err) self.assertThat(raised.message, Equals(expected_message), message=data)
def test_daemon_missing_errors(self): self.data["apps"] = { "service1": { "command": "binary1", "stop-mode": self.mode } } self.assertRaises(errors.YamlValidationError, Validator(self.data).validate)
def test_invalid_description(data, desc): data["description"] = desc with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ( "The 'description' property does not match the required " f"schema: {desc!r} is not a valid description string") assert expected_message in str(error.value)
def test_invalid_license(data): data["license"] = 1234 with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ( "The 'license' property does not match the required schema: " "1234 is not of type 'string'") assert expected_message in str(error.value)
def test_invalid_epoch(data, epoch): data["epoch"] = epoch with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ( f"{epoch!r} is not a 'epoch' (epochs are positive integers " "followed by an optional asterisk)") assert expected_message in str(error.value)
def test_summary_too_long(self): self.data["summary"] = "a" * 80 raised = self.assertRaises( errors.YamlValidationError, Validator(self.data).validate ) expected_message = ( "The 'summary' property does not match the required schema: " "'{}' is too long (maximum length is 78)" ).format(self.data["summary"]) self.assertThat(raised.message, Equals(expected_message), message=self.data)
def test_invalid_version_length(data): data["version"] = "this.is.a.really.too.long.version" with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ( "The 'version' property does not match the required " "schema: 'this.is.a.really.too.long.version' is too long " "(maximum length is 32)") assert expected_message in str(error.value)
def test_invalid_confinement(data, confinement): data["confinement"] = confinement with pytest.raises(errors.YamlValidationError) as error: Validator(data).validate() expected_message = ( "The 'confinement' property does not match the required " f"schema: {confinement!r} is not one of ['classic', 'devmode', " "'strict']") assert expected_message in str(error.value)