Ejemplo n.º 1
0
    def _ensure_dirty_report(self, part: pluginhandler.PluginHandler,
                             step: steps.Step) -> None:
        # If we already have a dirty report, bail
        if step in self._dirty_reports[part.name]:
            return

        # Get the dirty report from the PluginHandler. If it's dirty, we can
        # stop here
        self._dirty_reports[part.name][step] = part.get_dirty_report(step)

        # The dirty report from the PluginHandler only takes into account
        # properties specific to that part. If it's not dirty because of those,
        # we need to expand it here to also take its dependencies (if any) into
        # account
        dependencies = self.config.parts.get_dependencies(part.name,
                                                          recursive=True)

        # core20 uses Plugins V2 which does not require staging parts for pull
        # like V1 Plugins do.
        if (self.config.project._get_build_base() == "core20"
                and part._build_attributes.core22_step_dependencies()):
            prerequisite_step = step
        else:
            prerequisite_step = steps.get_dependency_prerequisite_step(step)

        changed_dependencies: List[pluginhandler.Dependency] = []
        with contextlib.suppress(errors.StepHasNotRunError):
            timestamp = part.step_timestamp(step)
            for dependency in dependencies:
                # Make sure the prerequisite step of this dependency has not
                # run more recently than (or should run _before_) this step.
                try:
                    prerequisite_timestamp = dependency.step_timestamp(
                        prerequisite_step)
                except errors.StepHasNotRunError:
                    dependency_changed = True
                else:
                    dependency_changed = timestamp < prerequisite_timestamp

                if dependency_changed or self.should_step_run(
                        dependency, prerequisite_step):
                    changed_dependencies.append(
                        pluginhandler.Dependency(part_name=dependency.name,
                                                 step=prerequisite_step))

            if changed_dependencies:
                self._dirty_reports[
                    part.name][step] = pluginhandler.DirtyReport(
                        changed_dependencies=changed_dependencies)
Ejemplo n.º 2
0
 def _fake_dirty_report(self, step):
     if step == 'pull':
         return pluginhandler.DirtyReport(set(), {'foo', 'bar'})
     return None
Ejemplo n.º 3
0
 def _fake_dirty_report(self, step):
     if step == 'build':
         return pluginhandler.DirtyReport({'foo', 'bar'}, set())
     return None
Ejemplo n.º 4
0
 def _fake_dirty_report(self, step):
     if step == 'stage':
         return pluginhandler.DirtyReport({'foo'}, {'bar'})
     return None
Ejemplo n.º 5
0
 def _fake_dirty_report(self, step):
     if self.name == 'part1' and step == 'prime':
         return pluginhandler.DirtyReport({'foo'}, {'bar'})
     return None
Ejemplo n.º 6
0
class SnapcraftExceptionTests(unit.TestCase):

    scenarios = (
        (
            "StrangeExceptionSimple",
            {
                "exception": StrangeExceptionSimple,
                "kwargs": {},
                "expected_brief": "something's strange, in the neighborhood",
                "expected_resolution": "who you gonna call? ghostbusters!!",
                "expected_details": "i ain't afraid of no ghosts",
                "expected_docs_url":
                "https://docs.snapcraft.io/the-snapcraft-format/8337",
                "expected_reportable": False,
            },
        ),
        (
            "StrangeExceptionWithFormatting",
            {
                "exception": StrangeExceptionWithFormatting,
                "kwargs": {
                    "neighborhood": "Times Square",
                    "ghosts": ["slimer", "puft", "vigo"],
                    "contact": "Janine Melnitz",
                },
                "expected_brief":
                "something's strange, in the neighborhood of Times Square",
                "expected_resolution": "who you gonna call? Janine Melnitz!!",
                "expected_details":
                "i ain't afraid of no ghosts: ['slimer', 'puft', 'vigo']",
                "expected_docs_url":
                "https://docs.snapcraft.io/the-snapcraft-format/8337",
                "expected_reportable": False,
            },
        ),
        (
            "MissingStateCleanError",
            {
                "exception": errors.MissingStateCleanError,
                "kwargs": {
                    "step": steps.PULL
                },
                "expected_brief": "Failed to clean for step 'pull'.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError dirty_properties",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(
                        dirty_properties=["test-property1", "test-property2"]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "The 'test-property1' and 'test-property2' part properties appear to have changed.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError dirty_project_options",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(
                        dirty_project_options=["test-option"]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "The 'test-option' project option appears to have changed.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError changed_dependencies",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(changed_dependencies=[
                        pluginhandler.Dependency(part_name="another-part",
                                                 step=steps.PULL)
                    ]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "A dependency has changed: 'another-part'\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError multiple changed_dependencies",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(changed_dependencies=[
                        pluginhandler.Dependency(part_name="another-part1",
                                                 step=steps.PULL),
                        pluginhandler.Dependency(part_name="another-part2",
                                                 step=steps.PULL),
                    ]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "Some dependencies have changed: 'another-part1' and 'another-part2'\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError previous step updated",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.STAGE,
                    "part":
                    "test-part",
                    "outdated_report":
                    pluginhandler.OutdatedReport(
                        previous_step_modified=steps.BUILD),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "The 'build' step has run more recently.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError source updated",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "outdated_report":
                    pluginhandler.OutdatedReport(source_updated=True),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details": "The source has changed on disk.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "SnapcraftEnvironmentError",
            {
                "exception": errors.SnapcraftEnvironmentError,
                "kwargs": {
                    "message": "test-message"
                },
                "expected_brief": "test-message",
                "expected_resolution": "",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "SnapcraftDataDirectoryMissingError",
            {
                "exception": errors.SnapcraftDataDirectoryMissingError,
                "kwargs": {},
                "expected_brief": "Cannot find snapcraft's data files.",
                "expected_resolution":
                "Re-install snapcraft or verify installation is correct.",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": True,
            },
        ),
        (
            "SnapcraftMissingLinkerInBaseError",
            {
                "exception": errors.SnapcraftMissingLinkerInBaseError,
                "kwargs": {
                    "base": "core18",
                    "linker_path": "/snap/core18/current/lib64/ld-linux.so.2",
                },
                "expected_brief":
                "Cannot find the linker to use for the target base 'core18'.",
                "expected_resolution":
                "Verify that the linker exists at the expected path '/snap/core18/current/lib64/ld-linux.so.2' and try again. If the linker does not exist contact the author of the base (run `snap info core18` to get information for this base).",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "SnapcraftPluginAssertionError",
            {
                "exception": errors.SnapcraftPluginAssertionError,
                "kwargs": {
                    "name": "part-name",
                    "reason": "missing important file"
                },
                "expected_brief":
                "Unable to build 'part-name': missing important file",
                "expected_resolution":
                "Ensure the part's configuration and sources are correct.",
                "expected_details": "",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "XAttributeError",
            {
                "exception": errors.XAttributeError,
                "kwargs": {
                    "path": "/tmp/foo",
                    "key": "foo",
                    "action": "read"
                },
                "expected_brief": "Unable to read extended attribute.",
                "expected_resolution":
                "Check that your filesystem supports extended attributes.",
                "expected_details":
                "Failed to read attribute 'foo' on '/tmp/foo'.",
                "expected_docs_url": None,
                "expected_reportable": True,
            },
        ),
        (
            "XAttributeTooLongError",
            {
                "exception": errors.XAttributeTooLongError,
                "kwargs": {
                    "path": "/tmp/foo",
                    "key": "foo",
                    "value": "bar"
                },
                "expected_brief":
                "Unable to write extended attribute as the key and/or value is too long.",
                "expected_resolution":
                "This issue is generally resolved by addressing/truncating the data source of the long data value. In some cases, the filesystem being used will limit the allowable size.",
                "expected_details":
                "Failed to write attribute to '/tmp/foo':\nkey='foo' value='bar'",
                "expected_docs_url": None,
                "expected_reportable": True,
            },
        ),
        (
            "SnapcraftPluginBuildError",
            {
                "exception": errors.SnapcraftPluginBuildError,
                "kwargs": {
                    "part_name": "foo"
                },
                "expected_brief": "Failed to build 'foo'.",
                "expected_resolution":
                "Check the build logs and ensure the part's configuration and sources are correct.",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
    )

    def test_snapcraft_exception_handling(self):
        exception = self.exception(**self.kwargs)
        self.assertEquals(self.expected_brief, exception.get_brief())
        self.assertEquals(self.expected_resolution, exception.get_resolution())
        self.assertEquals(self.expected_details, exception.get_details())
        self.assertEquals(self.expected_docs_url, exception.get_docs_url())
        self.assertEquals(self.expected_reportable, exception.get_reportable())
Ejemplo n.º 7
0
class ErrorFormattingTestCase(unit.TestCase):

    scenarios = (
        (
            "MissingStateCleanError",
            {
                "exception":
                errors.MissingStateCleanError,
                "kwargs": {
                    "step": steps.PULL
                },
                "expected_message":
                ("Failed to clean: "
                 "Missing state for 'pull'. "
                 "To clean the project, run `snapcraft clean`."),
            },
        ),
        (
            "StepOutdatedError dependents",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step": steps.PULL,
                    "part": "test-part",
                    "dependents": ["test-dependent"],
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'pull' step of 'test-part' is out of date:\n"
                 "The 'pull' step for 'test-part' needs to be run again, "
                 "but 'test-dependent' depends on it.\n"
                 "To continue, clean that part's 'pull' step by running "
                 "`snapcraft clean test-dependent -s pull`."),
            },
        ),
        (
            "StepOutdatedError dirty_properties",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(
                        dirty_properties=["test-property1", "test-property2"]),
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'pull' step of 'test-part' is out of date:\n"
                 "The 'test-property1' and 'test-property2' part properties "
                 "appear to have changed.\n"
                 "To continue, clean that part's 'pull' step by running "
                 "`snapcraft clean test-part -s pull`."),
            },
        ),
        (
            "StepOutdatedError dirty_project_options",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(
                        dirty_project_options=["test-option"]),
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'pull' step of 'test-part' is out of date:\n"
                 "The 'test-option' project option appears to have changed.\n"
                 "To continue, clean that part's 'pull' step by running "
                 "`snapcraft clean test-part -s pull`."),
            },
        ),
        (
            "StepOutdatedError changed_dependencies",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(changed_dependencies=[
                        pluginhandler.Dependency(part_name="another-part",
                                                 step=steps.PULL)
                    ]),
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'pull' step of 'test-part' is out of date:\n"
                 "A dependency has changed: 'another-part'\n"
                 "To continue, clean that part's "
                 "'pull' step by running "
                 "`snapcraft clean test-part -s pull`."),
            },
        ),
        (
            "StepOutdatedError multiple changed_dependencies",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(changed_dependencies=[
                        pluginhandler.Dependency(part_name="another-part1",
                                                 step=steps.PULL),
                        pluginhandler.Dependency(part_name="another-part2",
                                                 step=steps.PULL),
                    ]),
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'pull' step of 'test-part' is out of date:\n"
                 "Some dependencies have changed: 'another-part1' and "
                 "'another-part2'\n"
                 "To continue, clean that part's "
                 "'pull' step by running "
                 "`snapcraft clean test-part -s pull`."),
            },
        ),
        (
            "StepOutdatedError previous step updated",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.STAGE,
                    "part":
                    "test-part",
                    "outdated_report":
                    pluginhandler.OutdatedReport(
                        previous_step_modified=steps.BUILD),
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'stage' step of 'test-part' is out of date:\n"
                 "The 'build' step has run more recently.\n"
                 "To continue, clean that part's "
                 "'stage' step by running "
                 "`snapcraft clean test-part -s stage`."),
            },
        ),
        (
            "StepOutdatedError source updated",
            {
                "exception":
                errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "outdated_report":
                    pluginhandler.OutdatedReport(source_updated=True),
                },
                "expected_message":
                ("Failed to reuse files from previous run: "
                 "The 'pull' step of 'test-part' is out of date:\n"
                 "The source has changed on disk.\n"
                 "To continue, clean that part's "
                 "'pull' step by running "
                 "`snapcraft clean test-part -s pull`."),
            },
        ),
        (
            "SnapcraftEnvironmentError",
            {
                "exception": errors.SnapcraftEnvironmentError,
                "kwargs": {
                    "message": "test-message"
                },
                "expected_message": "test-message",
            },
        ),
        (
            "SnapcraftMissingLinkerInBaseError",
            {
                "exception":
                errors.SnapcraftMissingLinkerInBaseError,
                "kwargs": {
                    "base": "core18",
                    "linker_path": "/snap/core18/current/lib64/ld-linux.so.2",
                },
                "expected_message":
                ("Cannot find the linker to use for the target base 'core18'.\n"
                 "Please verify that the linker exists at the expected path "
                 "'/snap/core18/current/lib64/ld-linux.so.2' and try again. If "
                 "the linker does not exist contact the author of the base "
                 "(run `snap info core18` to get information for this "
                 "base)."),
            },
        ),
        (
            "PrimeFileConflictError",
            {
                "exception":
                errors.PrimeFileConflictError,
                "kwargs": {
                    "fileset": {"test-file"}
                },
                "expected_message":
                ("Failed to filter files: "
                 "The following files have been excluded by the `stage` "
                 "keyword, but included by the `prime` keyword: "
                 "{'test-file'}. "
                 "Edit the `snapcraft.yaml` to make sure that the files "
                 "included in `prime` are also included in `stage`."),
            },
        ),
        (
            "InvalidAppCommandError",
            {
                "exception":
                errors.InvalidAppCommandError,
                "kwargs": {
                    "command": "test-command",
                    "app": "test-app"
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "The specified command 'test-command' defined in the app "
                 "'test-app' does not exist or is not executable"),
            },
        ),
        (
            "InvalidContainerRemoteError",
            {
                "exception":
                errors.InvalidContainerRemoteError,
                "kwargs": {
                    "remote": "test-remote"
                },
                "expected_message":
                ("Failed to use LXD remote: "
                 "'test-remote' is not a valid name.\n"
                 "Use a LXD remote without colons, spaces and slashes in the "
                 "name.\n"),
            },
        ),
        (
            "InvalidDesktopFileError",
            {
                "exception":
                errors.InvalidDesktopFileError,
                "kwargs": {
                    "filename": "test-file",
                    "message": "test-message"
                },
                "expected_message":
                ("Failed to generate desktop file: "
                 "Invalid desktop file 'test-file': test-message."),
            },
        ),
        (
            "SnapcraftPartMissingError",
            {
                "exception":
                errors.SnapcraftPartMissingError,
                "kwargs": {
                    "part_name": "test-part"
                },
                "expected_message":
                ("Failed to get part information: "
                 "Cannot find the definition for part 'test-part'. "
                 "If it is a remote part, run `snapcraft update` "
                 "to refresh the remote parts cache. "
                 "If it is a local part, make sure that it is defined in the "
                 "`snapcraft.yaml`."),
            },
        ),
        (
            "PartNotInCacheError",
            {
                "exception":
                errors.PartNotInCacheError,
                "kwargs": {
                    "part_name": "test-part"
                },
                "expected_message":
                ("Failed to get remote part information: "
                 "Cannot find the part name 'test-part' in the cache. "
                 "If it is an existing remote part, run `snapcraft update` "
                 "and try again. If it has not been defined, consider going to "
                 "https://wiki.ubuntu.com/snapcraft/parts to add it."),
            },
        ),
        (
            "PluginError",
            {
                "exception": errors.PluginError,
                "kwargs": {
                    "message": "test-message"
                },
                "expected_message": "Failed to load plugin: test-message",
            },
        ),
        (
            "SnapcraftPartConflictError",
            {
                "exception":
                errors.SnapcraftPartConflictError,
                "kwargs": {
                    "part_name": "test-part",
                    "other_part_name": "test-other-part",
                    "conflict_files": ("test-file1", "test-file2"),
                },
                "expected_message":
                ("Failed to stage: "
                 "Parts 'test-other-part' and 'test-part' have the following "
                 "files, but with different contents:\n"
                 "    test-file1\n"
                 "    test-file2\n"
                 "\n"
                 "Snapcraft offers some capabilities to solve this by use of "
                 "the following keywords:\n"
                 "    - `filesets`\n"
                 "    - `stage`\n"
                 "    - `snap`\n"
                 "    - `organize`\n"
                 "\n"
                 "To learn more about these part keywords, run "
                 "`snapcraft help plugins`."),
            },
        ),
        (
            "MissingCommandError",
            {
                "exception":
                errors.MissingCommandError,
                "kwargs": {
                    "required_commands": ["test-command1", "test-command2"]
                },
                "expected_message":
                ("Failed to run command: "
                 "One or more packages are missing, please install:"
                 " ['test-command1', 'test-command2']"),
            },
        ),
        (
            "InvalidWikiEntryError",
            {
                "exception": errors.InvalidWikiEntryError,
                "kwargs": {
                    "error": "test-error"
                },
                "expected_message": "Invalid wiki entry: 'test-error'",
            },
        ),
        (
            "PluginOutdatedError",
            {
                "exception": errors.PluginOutdatedError,
                "kwargs": {
                    "message": "test-message"
                },
                "expected_message": "This plugin is outdated: test-message",
            },
        ),
        (
            "RequiredCommandFailure",
            {
                "exception": errors.RequiredCommandFailure,
                "kwargs": {
                    "command": "test-command"
                },
                "expected_message": "'test-command' failed.",
            },
        ),
        (
            "RequiredCommandNotFound",
            {
                "exception": errors.RequiredCommandNotFound,
                "kwargs": {
                    "cmd_list": ["test-command", "test-argument"]
                },
                "expected_message": "'test-command' not found.",
            },
        ),
        (
            "RequiredPathDoesNotExist",
            {
                "exception": errors.RequiredPathDoesNotExist,
                "kwargs": {
                    "path": "test-path"
                },
                "expected_message":
                "Required path does not exist: 'test-path'",
            },
        ),
        (
            "SnapcraftPathEntryError",
            {
                "exception":
                errors.SnapcraftPathEntryError,
                "kwargs": {
                    "value": "test-path",
                    "key": "test-key",
                    "app": "test-app"
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "The path 'test-path' set for 'test-key' in 'test-app' does "
                 "not exist. Make sure that the files are in the `prime` "
                 "directory."),
            },
        ),
        (
            "InvalidPullPropertiesError",
            {
                "exception":
                errors.InvalidPullPropertiesError,
                "kwargs": {
                    "plugin_name": "test-plugin",
                    "properties": ["test-property1", "test-property2"],
                },
                "expected_message":
                ("Failed to load plugin: "
                 "Invalid pull properties specified by 'test-plugin' plugin: "
                 "['test-property1', 'test-property2']"),
            },
        ),
        (
            "InvalidBuildPropertiesError",
            {
                "exception":
                errors.InvalidBuildPropertiesError,
                "kwargs": {
                    "plugin_name": "test-plugin",
                    "properties": ["test-property1", "test-property2"],
                },
                "expected_message":
                ("Failed to load plugin: "
                 "Invalid build properties specified by 'test-plugin' plugin: "
                 "['test-property1', 'test-property2']"),
            },
        ),
        (
            "StagePackageDownloadError",
            {
                "exception":
                errors.StagePackageDownloadError,
                "kwargs": {
                    "part_name": "test-part",
                    "message": "test-message"
                },
                "expected_message": ("Failed to fetch stage packages: "
                                     "Error downloading packages for part "
                                     "'test-part': test-message."),
            },
        ),
        (
            "InvalidContainerImageInfoError",
            {
                "exception":
                errors.InvalidContainerImageInfoError,
                "kwargs": {
                    "image_info": "test-image-info"
                },
                "expected_message":
                ("Failed to parse container image info: "
                 "SNAPCRAFT_IMAGE_INFO is not a valid JSON string: "
                 "test-image-info"),
            },
        ),
        # meta errors.
        (
            "AdoptedPartMissingError",
            {
                "exception":
                meta_errors.AdoptedPartMissingError,
                "kwargs": {
                    "part": "test-part"
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "'adopt-info' refers to a part named 'test-part', but it is "
                 "not defined in the 'snapcraft.yaml' file."),
            },
        ),
        (
            "AdoptedPartNotParsingInfo",
            {
                "exception":
                meta_errors.AdoptedPartNotParsingInfo,
                "kwargs": {
                    "part": "test-part"
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "'adopt-info' refers to part 'test-part', but that part is "
                 "lacking the 'parse-info' property."),
            },
        ),
        (
            "MissingSnapcraftYamlKeysError",
            {
                "exception":
                meta_errors.MissingSnapcraftYamlKeysError,
                "kwargs": {
                    "keys": ["test-key1", "test-key2"]
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "Missing required key(s) in snapcraft.yaml: "
                 "'test-key1' and 'test-key2'. Either specify the missing "
                 "key(s), or use 'adopt-info' to get them from a part."),
            },
        ),
        (
            "AmbiguousPassthroughKeyError",
            {
                "exception":
                meta_errors.AmbiguousPassthroughKeyError,
                "kwargs": {
                    "keys": ["key1", "key2"]
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "The following keys are specified in their regular location "
                 "as well as in passthrough: 'key1' and 'key2'. "
                 "Remove duplicate keys."),
            },
        ),
        (
            "MissingMetadataFileError",
            {
                "exception":
                errors.MissingMetadataFileError,
                "kwargs": {
                    "part_name": "test-part",
                    "path": "test/path"
                },
                "expected_message":
                ("Failed to generate snap metadata: "
                 "Part 'test-part' has a 'parse-info' referring to metadata "
                 "file 'test/path', which does not exist."),
            },
        ),
        (
            "UnhandledMetadataFileTypeError",
            {
                "exception":
                errors.UnhandledMetadataFileTypeError,
                "kwargs": {
                    "path": "test/path"
                },
                "expected_message":
                ("Failed to extract metadata from 'test/path': "
                 "This type of file is not supported for supplying "
                 "metadata."),
            },
        ),
        (
            "InvalidExtractorValueError",
            {
                "exception":
                errors.InvalidExtractorValueError,
                "kwargs": {
                    "path": "test/path",
                    "extractor_name": "extractor"
                },
                "expected_message":
                ("Failed to extract metadata from 'test/path': "
                 "Extractor 'extractor' didn't return ExtractedMetadata as "
                 "expected."),
            },
        ),
        (
            "PatcherNewerPatchelfError",
            {
                "exception":
                errors.PatcherNewerPatchelfError,
                "kwargs": {
                    "elf_file":
                    "test/path",
                    "patchelf_version":
                    "patchelf 0.9",
                    "process_exception":
                    CalledProcessError(cmd=["patchelf"], returncode=-1),
                },
                "expected_message":
                ("'test/path' cannot be patched to function properly in a "
                 "classic confined snap: patchelf failed with exit code -1.\n"
                 "'patchelf 0.9' may be too old. A newer version of patchelf "
                 "may be required.\n"
                 "Try adding the `after: [patchelf]` and a `patchelf` part "
                 "that would filter out files from prime `prime: [-*]` or "
                 "`build-snaps: [patchelf/latest/edge]` to the failing part "
                 "in your `snapcraft.yaml` to use a newer patchelf."),
            },
        ),
        (
            "PatcherGenericError",
            {
                "exception":
                errors.PatcherGenericError,
                "kwargs": {
                    "elf_file":
                    "test/path",
                    "process_exception":
                    CalledProcessError(cmd=["patchelf"], returncode=-1),
                },
                "expected_message":
                ("'test/path' cannot be patched to function properly in a "
                 "classic confined snap: patchelf failed with exit code -1"),
            },
        ),
        (
            "StagePackageMissingError",
            {
                "exception":
                errors.StagePackageMissingError,
                "kwargs": {
                    "package": "libc6"
                },
                "expected_message":
                ("'libc6' is required inside the snap for this "
                 "part to work properly.\nAdd it as a `stage-packages` "
                 "entry for this part."),
            },
        ),
        (
            "RemotePartsUpdateConnectionError",
            {
                "exception":
                errors.RemotePartsUpdateConnectionError,
                "kwargs": {
                    "requests_exception":
                    requests.exceptions.ConnectionError("I'm a naughty error")
                },
                "expected_message":
                ("Failed to update cache of remote parts: A Connection error "
                 "occurred.\nPlease try again."),
            },
        ),
        (
            "SnapcraftCommandError",
            {
                "exception":
                errors.SnapcraftCommandError,
                "kwargs": {
                    "command":
                    "pip install foo",
                    "call_error":
                    CalledProcessError(cmd=["/bin/sh"],
                                       returncode=1,
                                       output="failed"),
                },
                "expected_message":
                ("Failed to run 'pip install foo': Exited with code 1."),
            },
        ),
        (
            "SnapcraftPluginCommandError string command",
            {
                "exception":
                errors.SnapcraftPluginCommandError,
                "kwargs": {
                    "command": "make install",
                    "exit_code": -1,
                    "part_name": "make_test",
                },
                "expected_message":
                ("Failed to run 'make install' for 'make_test': "
                 "Exited with code -1.\n"
                 "Verify that the part is using the correct parameters and try "
                 "again."),
            },
        ),
        (
            "SnapcraftPluginCommandError list command",
            {
                "exception":
                errors.SnapcraftPluginCommandError,
                "kwargs": {
                    "command": ["make", "install"],
                    "exit_code": 2,
                    "part_name": "make_test",
                },
                "expected_message":
                ("Failed to run 'make install' for 'make_test': "
                 "Exited with code 2.\n"
                 "Verify that the part is using the correct parameters and try "
                 "again."),
            },
        ),
        (
            "CacheUpdateFailedError",
            {
                "exception":
                repo_errors.CacheUpdateFailedError,
                "kwargs": {
                    "errors": ""
                },
                "expected_message":
                ("Failed to update the package cache: "
                 "Some files could not be downloaded: "
                 "Check that the sources on your host are configured correctly."
                 ),
            },
        ),
        (
            "CacheUpdateFailedError",
            {
                "exception":
                repo_errors.CacheUpdateFailedError,
                "kwargs": {
                    "errors": "foo, bar"
                },
                "expected_message":
                ("Failed to update the package cache: "
                 "Some files could not be downloaded:\n\nfoo\nbar\n\n"
                 "Check that the sources on your host are configured correctly."
                 ),
            },
        ),
        (
            "StoreNetworkError generic error",
            {
                "exception":
                store_errors.StoreNetworkError,
                "kwargs": {
                    "exception":
                    requests.exceptions.ConnectionError("bad error")
                },
                "expected_message":
                ("There seems to be a network error: bad error"),
            },
        ),
        (
            "StoreNetworkError max retry error",
            {
                "exception":
                store_errors.StoreNetworkError,
                "kwargs": {
                    "exception":
                    requests.exceptions.ConnectionError(
                        urllib3.exceptions.MaxRetryError(pool="test-pool",
                                                         url="test-url"))
                },
                "expected_message":
                ("There seems to be a network error: maximum retries exceeded "
                 "trying to reach the store.\n"
                 "Check your network connection, and check the store status at "
                 "https://status.snapcraft.io/"),
            },
        ),
        (
            "SnapcraftCopyFileNotFoundError",
            {
                "exception":
                errors.SnapcraftCopyFileNotFoundError,
                "kwargs": {
                    "path": "test-path"
                },
                "expected_message":
                ("Failed to copy 'test-path': no such file or directory.\n"
                 "Check the path and try again."),
            },
        ),
        (
            "StoreServerError 500",
            {
                "exception":
                store_errors.StoreServerError,
                "kwargs": {
                    "response": _fake_error_response(500)
                },
                "expected_message":
                ("The Snap Store encountered an error while processing your "
                 "request: internal server error (code 500).\nThe operational "
                 "status of the Snap Store can be checked at "
                 "https://status.snapcraft.io/"),
            },
        ),
        (
            "StoreServerError 501",
            {
                "exception":
                store_errors.StoreServerError,
                "kwargs": {
                    "response": _fake_error_response(501)
                },
                "expected_message":
                ("The Snap Store encountered an error while processing your "
                 "request: not implemented (code 501).\nThe operational "
                 "status of the Snap Store can be checked at "
                 "https://status.snapcraft.io/"),
            },
        ),
        (
            "MountPointNotFoundError",
            {
                "exception": errors.MountPointNotFoundError,
                "kwargs": {
                    "mount_point": "test-mount-point"
                },
                "expected_message":
                ("Nothing is mounted at 'test-mount-point'"),
            },
        ),
        (
            "RootNotMountedError",
            {
                "exception": errors.RootNotMountedError,
                "kwargs": {
                    "root": "test-root"
                },
                "expected_message": ("'test-root' is not mounted"),
            },
        ),
        (
            "InvalidMountinfoFormat",
            {
                "exception": errors.InvalidMountinfoFormat,
                "kwargs": {
                    "row": [1, 2, 3]
                },
                "expected_message":
                ("Unable to parse mountinfo row: [1, 2, 3]"),
            },
        ),
        (
            "InvalidStepError",
            {
                "exception":
                errors.InvalidStepError,
                "kwargs": {
                    "step_name": "test-step-name"
                },
                "expected_message":
                ("'test-step-name' is not a valid lifecycle step"),
            },
        ),
        (
            "NoLatestStepError",
            {
                "exception":
                errors.NoLatestStepError,
                "kwargs": {
                    "part_name": "test-part-name"
                },
                "expected_message":
                ("The 'test-part-name' part hasn't run any steps"),
            },
        ),
        (
            "NoNextStepError",
            {
                "exception":
                errors.NoNextStepError,
                "kwargs": {
                    "part_name": "test-part-name"
                },
                "expected_message":
                ("The 'test-part-name' part has run through its entire lifecycle"
                 ),
            },
        ),
        (
            "StepHasNotRunError",
            {
                "exception":
                errors.StepHasNotRunError,
                "kwargs": {
                    "part_name": "test-part-name",
                    "step": steps.BUILD
                },
                "expected_message":
                ("The 'test-part-name' part has not yet run the 'build' step"),
            },
        ),
        (
            "ScriptletDuplicateFieldError",
            {
                "exception":
                errors.ScriptletDuplicateFieldError,
                "kwargs": {
                    "field": "foo",
                    "step": steps.PULL
                },
                "expected_message":
                ("Unable to set foo: it was already set in the 'pull' step."),
            },
        ),
    )

    def test_error_formatting(self):
        self.assertThat(str(self.exception(**self.kwargs)),
                        Equals(self.expected_message))
Ejemplo n.º 8
0
 def _fake_dirty_report(self, step):
     if step == steps.PULL:
         return pluginhandler.DirtyReport(
             dirty_project_options={"foo", "bar"})
     return None
Ejemplo n.º 9
0
 def _fake_dirty_report(self, step):
     if step == steps.BUILD:
         return pluginhandler.DirtyReport(
             dirty_properties={"foo", "bar"})
     return None
Ejemplo n.º 10
0
 def _fake_dirty_report(self, step):
     if step == steps.STAGE:
         return pluginhandler.DirtyReport(dirty_properties={"foo"},
                                          dirty_project_options={"bar"})
     return None
Ejemplo n.º 11
0
class SnapcraftExceptionTests(unit.TestCase):

    scenarios = (
        (
            "StrangeExceptionSimple",
            {
                "exception": StrangeExceptionSimple,
                "kwargs": {},
                "expected_brief": "something's strange, in the neighborhood",
                "expected_resolution": "who you gonna call? ghostbusters!!",
                "expected_details": "i ain't afraid of no ghosts",
                "expected_docs_url":
                "https://docs.snapcraft.io/the-snapcraft-format/8337",
                "expected_reportable": False,
            },
        ),
        (
            "StrangeExceptionWithFormatting",
            {
                "exception": StrangeExceptionWithFormatting,
                "kwargs": {
                    "neighborhood": "Times Square",
                    "ghosts": ["slimer", "puft", "vigo"],
                    "contact": "Janine Melnitz",
                },
                "expected_brief":
                "something's strange, in the neighborhood of Times Square",
                "expected_resolution": "who you gonna call? Janine Melnitz!!",
                "expected_details":
                "i ain't afraid of no ghosts: ['slimer', 'puft', 'vigo']",
                "expected_docs_url":
                "https://docs.snapcraft.io/the-snapcraft-format/8337",
                "expected_reportable": False,
            },
        ),
        (
            "MissingStateCleanError",
            {
                "exception": errors.MissingStateCleanError,
                "kwargs": {
                    "step": steps.PULL
                },
                "expected_brief": "Failed to clean for step 'pull'.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError dirty_properties",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(
                        dirty_properties=["test-property1", "test-property2"]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "The 'test-property1' and 'test-property2' part properties appear to have changed.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError dirty_project_options",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(
                        dirty_project_options=["test-option"]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "The 'test-option' project option appears to have changed.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError changed_dependencies",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(changed_dependencies=[
                        pluginhandler.Dependency(part_name="another-part",
                                                 step=steps.PULL)
                    ]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "A dependency has changed: 'another-part'\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError multiple changed_dependencies",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "dirty_report":
                    pluginhandler.DirtyReport(changed_dependencies=[
                        pluginhandler.Dependency(part_name="another-part1",
                                                 step=steps.PULL),
                        pluginhandler.Dependency(part_name="another-part2",
                                                 step=steps.PULL),
                    ]),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "Some dependencies have changed: 'another-part1' and 'another-part2'\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError previous step updated",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.STAGE,
                    "part":
                    "test-part",
                    "outdated_report":
                    pluginhandler.OutdatedReport(
                        previous_step_modified=steps.BUILD),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details":
                "The 'build' step has run more recently.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "StepOutdatedError source updated",
            {
                "exception": errors.StepOutdatedError,
                "kwargs": {
                    "step":
                    steps.PULL,
                    "part":
                    "test-part",
                    "outdated_report":
                    pluginhandler.OutdatedReport(source_updated=True),
                },
                "expected_brief": "Failed to reuse files from previous run.",
                "expected_resolution":
                "Run `snapcraft clean` and retry build.",
                "expected_details": "The source has changed on disk.\n",
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "SnapcraftEnvironmentError",
            {
                "exception": errors.SnapcraftEnvironmentError,
                "kwargs": {
                    "message": "test-message"
                },
                "expected_brief": "test-message",
                "expected_resolution": "",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
        (
            "SnapcraftDataDirectoryMissingError",
            {
                "exception": errors.SnapcraftDataDirectoryMissingError,
                "kwargs": {},
                "expected_brief": "Cannot find snapcraft's data files.",
                "expected_resolution":
                "Re-install snapcraft or verify installation is correct.",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": True,
            },
        ),
        (
            "SnapcraftMissingLinkerInBaseError",
            {
                "exception": errors.SnapcraftMissingLinkerInBaseError,
                "kwargs": {
                    "base": "core18",
                    "linker_path": "/snap/core18/current/lib64/ld-linux.so.2",
                },
                "expected_brief":
                "Cannot find the linker to use for the target base 'core18'.",
                "expected_resolution":
                "Verify that the linker exists at the expected path '/snap/core18/current/lib64/ld-linux.so.2' and try again. If the linker does not exist contact the author of the base (run `snap info core18` to get information for this base).",
                "expected_details": None,
                "expected_docs_url": None,
                "expected_reportable": False,
            },
        ),
    )

    def test_snapcraft_exception_handling(self):
        exception = self.exception(**self.kwargs)
        self.assertEquals(self.expected_brief, exception.get_brief())
        self.assertEquals(self.expected_resolution, exception.get_resolution())
        self.assertEquals(self.expected_details, exception.get_details())
        self.assertEquals(self.expected_docs_url, exception.get_docs_url())
        self.assertEquals(self.expected_reportable, exception.get_reportable())