def test_analyze_ignore_linter(config): """Run all checkers except the ignored linter.""" FakeChecker1 = create_fake_checker( check_type=CheckType.attribute, name="name1", result="res1", text="text1", url="url1", ) FakeChecker2 = create_fake_checker( check_type=CheckType.lint, name="name2", result="res2", text="text2", url="url2", ) config.analysis.ignore.linters.append("name2") with patch("charmcraft.linters.CHECKERS", [FakeChecker1, FakeChecker2]): result = analyze(config, "somepath") res1, res2 = result assert res1.check_type == CheckType.attribute assert res1.name == "name1" assert res1.result == "res1" assert res1.text == "text1" assert res1.url == "url1" assert res2.check_type == CheckType.lint assert res2.name == "name2" assert res2.result == IGNORED assert res2.text == "" assert res2.url == "url2"
def test_analyze_run_everything(config): """Check that analyze runs all and collect the results.""" FakeChecker1 = create_fake_checker(check_type="type1", name="name1", url="url1", text="text1", result="result1") FakeChecker2 = create_fake_checker(check_type="type2", name="name2", url="url2", text="text2", result="result2") # hack the first fake checker to validate that it receives the indicated path def dir_validator(self, basedir): assert basedir == "test-buildpath" return "result1" FakeChecker1.run = dir_validator with patch("charmcraft.linters.CHECKERS", [FakeChecker1, FakeChecker2]): result = analyze(config, "test-buildpath") r1, r2 = result assert r1.check_type == "type1" assert r1.name == "name1" assert r1.url == "url1" assert r1.text == "text1" assert r1.result == "result1" assert r2.check_type == "type2" assert r2.name == "name2" assert r2.url == "url2" assert r2.text == "text2" assert r2.result == "result2"
def test_analyze_all_can_be_ignored(config): """Control that all real life checkers can be ignored.""" config.analysis.ignore.attributes.extend( c.name for c in CHECKERS if c.check_type == CheckType.attribute) config.analysis.ignore.linters.extend(c.name for c in CHECKERS if c.check_type == CheckType.lint) result = analyze(config, "somepath") assert all(r.result == IGNORED for r in result)
def build_charm(self, bases_config: BasesConfiguration) -> str: """Build the charm. :param bases_config: Bases configuration to use for build. :returns: File name of charm. :raises CraftError: on lifecycle exception. :raises RuntimeError: on unexpected lifecycle exception. """ if env.is_charmcraft_running_in_managed_mode(): work_dir = env.get_managed_environment_home_path() else: work_dir = self.buildpath emit.progress(f"Building charm in {str(work_dir)!r}") if self._special_charm_part: # all current deprecated arguments set charm plugin parameters self._handle_deprecated_cli_arguments() # add charm files to the prime filter self._set_prime_filter() # set source if empty or not declared in charm part if not self._special_charm_part.get("source"): self._special_charm_part["source"] = str(self.charmdir) # run the parts lifecycle emit.trace(f"Parts definition: {self._parts}") lifecycle = parts.PartsLifecycle( self._parts, work_dir=work_dir, project_dir=self.charmdir, project_name=self.metadata.name, ignore_local_sources=["*.charm"], ) lifecycle.run(Step.PRIME) # run linters and show the results linting_results = linters.analyze(self.config, lifecycle.prime_dir) self.show_linting_results(linting_results) create_manifest( lifecycle.prime_dir, self.config.project.started_at, bases_config, linting_results, ) zipname = self.handle_package(lifecycle.prime_dir, bases_config) emit.message(f"Created '{zipname}'.", intermediate=True) return zipname
def test_analyze_crash_lint(config): """The lint checker crashes.""" FakeChecker = create_fake_checker(check_type=CheckType.lint, name="name", text="text", url="url") def raises(*a): raise ValueError FakeChecker.run = raises with patch("charmcraft.linters.CHECKERS", [FakeChecker]): result = analyze(config, "somepath") (res, ) = result assert res.check_type == CheckType.lint assert res.name == "name" assert res.result == FATAL assert res.text == "text" assert res.url == "url"
def test_analyze_override_ignore(config): """Run all checkers even the ignored ones, if requested.""" FakeChecker1 = create_fake_checker(check_type=CheckType.attribute, name="name1", result="res1") FakeChecker2 = create_fake_checker(check_type=CheckType.lint, name="name2", result="res2") config.analysis.ignore.attributes.append("name1") config.analysis.ignore.linters.append("name2") with patch("charmcraft.linters.CHECKERS", [FakeChecker1, FakeChecker2]): result = analyze(config, "somepath", override_ignore_config=True) res1, res2 = result assert res1.check_type == CheckType.attribute assert res1.name == "name1" assert res1.result == "res1" assert res2.check_type == CheckType.lint assert res2.name == "name2" assert res2.result == "res2"
def build_charm(self, bases_config: BasesConfiguration) -> str: """Build the charm. :param bases_config: Bases configuration to use for build. :returns: File name of charm. """ logger.debug("Building charm in %r", str(self.buildpath)) self._handle_deprecated_cli_arguments() # add charm files to the prime filter self._set_prime_filter() # set source for buiding self._charm_part["source"] = str(self.charmdir) # run the parts lifecycle logger.debug("Parts definition: %s", self._parts) lifecycle = parts.PartsLifecycle( self._parts, work_dir=self.buildpath, ignore_local_sources=["*.charm"], ) lifecycle.run(Step.PRIME) # run linters and show the results linting_results = linters.analyze(self.config, lifecycle.prime_dir) self.show_linting_results(linting_results) create_manifest( lifecycle.prime_dir, self.config.project.started_at, bases_config, linting_results, ) zipname = self.handle_package(lifecycle.prime_dir, bases_config) logger.info("Created '%s'.", zipname) return zipname
def run(self, parsed_args): """Run the command.""" tmpdir = self._unzip_charm(parsed_args.filepath) # run the analyzer override_ignore_config = bool(parsed_args.force) linting_results = linters.analyze( self.config, tmpdir, override_ignore_config=override_ignore_config, ) # if format is json almost no further processing is needed if parsed_args.format == JSON_FORMAT: info = [{ "name": r.name, "result": r.result, "url": r.url, "type": r.check_type, } for r in linting_results] emit.message(json.dumps(info, indent=4)) return # group by attributes and lint outcomes (discarding ignored ones) grouped = {} for result in linting_results: if result.check_type == linters.CheckType.attribute: group_key = linters.CheckType.attribute result_info = result.result else: # linters group_key = result.result if result.result == linters.OK: result_info = "no issues found" elif result.result in (linters.FATAL, linters.IGNORED): result_info = None else: result_info = result.text grouped.setdefault(group_key, []).append((result, result_info)) # present the results titles = [ ("Attributes", linters.CheckType.attribute), ("Lint Ignored", linters.IGNORED), ("Lint Warnings", linters.WARNINGS), ("Lint Errors", linters.ERRORS), ("Lint Fatal", linters.FATAL), ("Lint OK", linters.OK), ] for title, key in titles: results = grouped.get(key) if results is not None: emit.message(f"{title}:") for result, result_info in results: if result_info: emit.message( f"- {result.name}: { result_info} ({result.url})") else: emit.message(f"- {result.name} ({result.url})") # the return code depends on the presence of different issues if linters.FATAL in grouped: retcode = 1 elif linters.ERRORS in grouped: retcode = 2 elif linters.WARNINGS in grouped: retcode = 3 else: retcode = 0 return retcode
def run(self, parsed_args): """Run the command.""" tmpdir = tempfile.mkdtemp() filepath = str(parsed_args.filepath) try: zf = zipfile.ZipFile(filepath) zf.extractall(path=tmpdir) except Exception as exc: raise CommandError( "Cannot open the indicated charm file {!r}: {!r}.".format( filepath, exc)) # run the analyzer override_ignore_config = bool(parsed_args.force) linting_results = linters.analyze( self.config, pathlib.Path(tmpdir), override_ignore_config=override_ignore_config, ) # if format is json almost no further processing is needed if parsed_args.format == JSON_FORMAT: info = [{ "name": r.name, "result": r.result, "url": r.url, "type": r.check_type, } for r in linting_results] logger.info(json.dumps(info, indent=4)) return # group by attributes and lint outcomes (discarding ignored ones) grouped = {} for result in linting_results: if result.check_type == linters.CheckType.attribute: group_key = linters.CheckType.attribute result_info = result.result else: # linters group_key = result.result if result.result == linters.OK: result_info = "no issues found" elif result.result in (linters.FATAL, linters.IGNORED): result_info = None else: result_info = result.text grouped.setdefault(group_key, []).append((result, result_info)) # present the results titles = [ ("Attributes", linters.CheckType.attribute), ("Lint Ignored", linters.IGNORED), ("Lint Warnings", linters.WARNINGS), ("Lint Errors", linters.ERRORS), ("Lint Fatal", linters.FATAL), ("Lint OK", linters.OK), ] for title, key in titles: results = grouped.get(key) if results is not None: logger.info("%s:", title) for result, result_info in results: if result_info: logger.info("- %s: %s (%s)", result.name, result_info, result.url) else: logger.info("- %s (%s)", result.name, result.url) # the return code depends on the presence of different issues if linters.FATAL in grouped: retcode = 1 elif linters.ERRORS in grouped: retcode = 2 elif linters.WARNINGS in grouped: retcode = 3 else: retcode = 0 return retcode