def test_maintainers_section(self): expected_message = ( "The recipe could do with some maintainers listed " "in the `extra/recipe-maintainers` section." ) lints, hints = linter.lintify({"extra": {"recipe-maintainers": []}}) self.assertIn(expected_message, lints) # No extra section at all. lints, hints = linter.lintify({}) self.assertIn(expected_message, lints) lints, hints = linter.lintify({"extra": {"recipe-maintainers": ["a"]}}) self.assertNotIn(expected_message, lints) expected_message = ( 'The "extra" section was expected to be a ' "dictionary, but got a list." ) lints, hints = linter.lintify({"extra": ["recipe-maintainers"]}) self.assertIn(expected_message, lints) lints, hints = linter.lintify({"extra": {"recipe-maintainers": "Luke"}}) expected_message = "Recipe maintainers should be a json list." self.assertIn(expected_message, lints)
def test_bad_subheader(self): expected_message = ( "The {} section contained an unexpected " "subsection name. {} is not a valid subsection" " name." ) meta = { "build": {"skip": "True", "script": "python setup.py install", "number": 0} } lints, hints = linter.lintify(meta) self.assertNotIn(expected_message.format("build", "ski"), lints) meta = { "build": {"ski": "True", "script": "python setup.py install", "number": 0} } lints, hints = linter.lintify(meta) self.assertIn(expected_message.format("build", "ski"), lints) meta = {"source": {"urll": "http://test"}} lints, hints = linter.lintify(meta) self.assertIn(expected_message.format("source", "urll"), lints) meta = {"source": [{"urll": "http://test"}, {"url": "https://test"}]} lints, hints = linter.lintify(meta) self.assertIn(expected_message.format("source", "urll"), lints)
def test_version(self): meta = {"package": {"name": "python", "version": "3.6.4"}} expected_message = "Package version 3.6.4 doesn't match conda spec" lints, hints = linter.lintify(meta) self.assertNotIn(expected_message, lints) meta = {"package": {"name": "python", "version": "2.0.0~alpha0"}} expected_message = "Package version 2.0.0~alpha0 doesn't match conda spec" lints, hints = linter.lintify(meta) self.assertIn(expected_message, lints)
def test_missing_build_number(self): expected_message = "The recipe must have a `build/number` section." meta = { "build": {"skip": "True", "script": "python setup.py install", "number": 0} } lints, hints = linter.lintify(meta) self.assertNotIn(expected_message, lints) meta = {"build": {"skip": "True", "script": "python setup.py install"}} lints, hints = linter.lintify(meta) self.assertIn(expected_message, lints)
def test_examples(self): msg = "Please move the recipe out of the example dir and into its " "own dir." lints, hints = linter.lintify( {"extra": {"recipe-maintainers": ["support"]}}, recipe_dir="recipes/example/", conda_forge=True, ) self.assertIn(msg, lints) lints = linter.lintify( {"extra": {"recipe-maintainers": ["support"]}}, recipe_dir="python", conda_forge=True, ) self.assertNotIn(msg, lints)
def test_test_section_with_recipe(self): # If we have a run_test.py file, we shouldn't need to provide # other tests. expected_message = "The recipe must have some tests." with tmp_directory() as recipe_dir: lints, hints = linter.lintify({}, recipe_dir) self.assertIn(expected_message, lints) with io.open(os.path.join(recipe_dir, "run_test.py"), "w") as fh: fh.write("# foo") lints, hints = linter.lintify({}, recipe_dir) self.assertNotIn(expected_message, lints)
def assert_selector(selector, is_good=True): with io.open(os.path.join(recipe_dir, "meta.yaml"), "w") as fh: fh.write( """ package: name: foo_py2 # [py2k] {} """.format( selector ) ) lints, hints = linter.lintify({}, recipe_dir) if is_good: message = ( "Found lints when there shouldn't have been a " "lint for '{}'.".format(selector) ) else: message = "Expecting lints for '{}', but didn't get any." "".format( selector ) self.assertEqual( not is_good, any(lint.startswith(expected_message) for lint in lints), message, )
def assert_jinja(jinja_var, is_good=True): with io.open(os.path.join(recipe_dir, "meta.yaml"), "w") as fh: fh.write( """ {{% set name = "nwb-extensions-smithy" %}} {} """.format( jinja_var ) ) lints, hints = linter.lintify({}, recipe_dir) if is_good: message = ( "Found lints when there shouldn't have been a " "lint for '{}'.".format(jinja_var) ) else: message = "Expecting lints for '{}', but didn't get any." "".format( jinja_var ) self.assertEqual( not is_good, any(lint.startswith(expected_message) for lint in lints), message, )
def test_single_space_pins(self): meta = { "requirements": { "build": [ "{{ compilers('c') }}", "python >=3", "pip 19", ], "host": [ "python >= 2", "libcblas 3.8.* *netlib", ], "run": [ "xonsh>1.0", "conda= 4.*", "conda-smithy<=54.*", ], } } lints, hints = linter.lintify(meta) filtered_lints = [lint for lint in lints if lint.startswith("``requirements: ")] expected_messages = [ "``requirements: host: python >= 2`` should not contain a space between " "relational operator and the version, i.e. ``python >=2``", "``requirements: run: xonsh>1.0`` must contain a space between the " "name and the pin, i.e. ``xonsh >1.0``", "``requirements: run: conda= 4.*`` must contain a space between the " "name and the pin, i.e. ``conda =4.*``", "``requirements: run: conda-smithy<=54.*`` must contain a space " "between the name and the pin, i.e. ``conda-smithy <=54.*``", ] self.assertEqual(expected_messages, filtered_lints)
def test_no_sha_with_dl(self): expected_message = ( "When defining a source/url please add a sha256, " "sha1 or md5 checksum (sha256 preferably)." ) lints, hints = linter.lintify({"source": {"url": None}}) self.assertIn(expected_message, lints) lints, hints = linter.lintify({"source": {"url": None, "sha1": None}}) self.assertNotIn(expected_message, lints) lints, hints = linter.lintify({"source": {"url": None, "sha256": None}}) self.assertNotIn(expected_message, lints, hints) meta = {"source": {"url": None, "md5": None}} self.assertNotIn(expected_message, linter.lintify(meta))
def test_missing_about_license_and_summary(self): meta = {"about": {"home": "a URL"}} lints, hints = linter.lintify(meta) expected_message = "The license item is expected in the about section." self.assertIn(expected_message, lints) expected_message = "The summary item is expected in the about section." self.assertIn(expected_message, lints)
def test_string_source(self): url = "http://mistake.com/v1.0.tar.gz" lints, hints = linter.lintify({"source": url}) msg = ( 'The "source" section was expected to be a dictionary or a ' "list, but got a {}.{}." ).format(type(url).__module__, type(url).__name__) self.assertIn(msg, lints)
def test_recipe_name(self): meta = {"package": {"name": "mp++"}} lints, hints = linter.lintify(meta) expected_message = ( "Recipe name has invalid characters. only lowercase alpha, " "numeric, underscores, hyphens and dots allowed" ) self.assertIn(expected_message, lints)
def test_bad_order(self): meta = OrderedDict([["package", {}], ["build", {}], ["source", {}]]) lints, hints = linter.lintify(meta) expected_msg = ( "The top level meta keys are in an unexpected " "order. Expecting ['package', 'source', 'build']." ) self.assertIn(expected_msg, lints)
def test_missing_about_home_empty(self): meta = {"about": {"home": "", "summary": "", "license": ""}} lints, hints = linter.lintify(meta) expected_message = "The home item is expected in the about section." self.assertIn(expected_message, lints) expected_message = "The license item is expected in the about section." self.assertIn(expected_message, lints) expected_message = "The summary item is expected in the about section." self.assertIn(expected_message, lints)
def test_bad_about_license(self): meta = { "about": { "home": "a URL", "summary": "A test summary", "license": "unknown", } } lints, hints = linter.lintify(meta) expected_message = "The recipe license cannot be unknown." self.assertIn(expected_message, lints)
def test_bad_about_license_family(self): meta = { "about": { "home": "a URL", "summary": "A test summary", "license": "BSD 3-clause", "license_family": "BSD3", } } lints, hints = linter.lintify(meta) expected = "about/license_family 'BSD3' not allowed" self.assertTrue(any(lint.startswith(expected) for lint in lints))
def test_redundant_license(self): meta = { "about": { "home": "a URL", "summary": "A test summary", "license": "MIT License", } } lints, hints = linter.lintify(meta) expected_message = ( "The recipe `license` should not include " 'the word "License".' ) self.assertIn(expected_message, lints)
def test_license_file_required(self): meta = { "about": { "home": "a URL", "summary": "A test summary", "license": "MIT", } } lints, hints = linter.lintify(meta) expected_message = ( "license_file entry is missing, but is required." ) self.assertIn(expected_message, lints)
def test_bad_requirements_order(self): expected_message = ( "The `requirements/` sections should be defined in " "the following order: build, host, run; " "instead saw: run, build." ) meta = {"requirements": OrderedDict([["run", "a"], ["build", "a"]])} lints, hints = linter.lintify(meta) self.assertIn(expected_message, lints) meta = { "requirements": OrderedDict( [["run", "a"], ["invalid", "a"], ["build", "a"]] ) } lints, hints = linter.lintify(meta) self.assertIn(expected_message, lints) meta = {"requirements": OrderedDict([["build", "a"], ["run", "a"]])} lints, hints = linter.lintify(meta) self.assertNotIn(expected_message, lints)
def test_end_empty_line(self): bad_contents = [ # No empty lines at the end of the file "extra:\n recipe-maintainers:\n - goanpeca", "extra:\r recipe-maintainers:\r - goanpeca", "extra:\r\n recipe-maintainers:\r\n - goanpeca", # Two empty lines at the end of the file "extra:\n recipe-maintainers:\n - goanpeca\n\n", "extra:\r recipe-maintainers:\r - goanpeca\r\r", "extra:\r\n recipe-maintainers:\r\n - goanpeca\r\n\r\n", # Three empty lines at the end of the file "extra:\n recipe-maintainers:\n - goanpeca\n\n\n", "extra:\r recipe-maintainers:\r - goanpeca\r\r\r", "extra:\r\n recipe-maintainers:\r\n - goanpeca\r\n\r\n\r\n", ] # Exactly one empty line at the end of the file valid_content = "extra:\n recipe-maintainers:\n - goanpeca\n" for content, lines in zip( bad_contents + [valid_content], [0, 0, 0, 2, 2, 2, 3, 3, 3, 1] ): with tmp_directory() as recipe_dir: with io.open(os.path.join(recipe_dir, "meta.yaml"), "w") as f: f.write(content) lints, hints = linter.lintify({}, recipe_dir=recipe_dir) if lines > 1: expected_message = ( "There are {} too many lines. " "There should be one empty line " "at the end of the " "file.".format(lines - 1) ) else: expected_message = ( "There are too few lines. " "There should be one empty line at" " the end of the file." ) if content == valid_content: self.assertNotIn(expected_message, lints) else: self.assertIn(expected_message, lints)
def test_test_section(self): expected_message = "The recipe must have some tests." lints, hints = linter.lintify({}) self.assertIn(expected_message, lints) lints, hints = linter.lintify({"test": {"files": "foo"}}) self.assertIn(expected_message, lints) lints, hints = linter.lintify({"test": {"imports": "sys"}}) self.assertNotIn(expected_message, lints) lints, hints = linter.lintify({"outputs": [{"name": "foo"}]}) self.assertIn(expected_message, lints) lints, hints = linter.lintify( {"outputs": [{"name": "foo", "test": {"files": "foo"}}]} ) self.assertIn(expected_message, lints) lints, hints = linter.lintify( {"outputs": [{"name": "foo", "test": {"imports": "sys"}}]} ) self.assertNotIn(expected_message, lints) lints, hints = linter.lintify( { "outputs": [ {"name": "foo", "test": {"imports": "sys"}}, {"name": "foobar", "test": {"files": "hi"}}, ] } ) self.assertNotIn(expected_message, lints) self.assertIn( "It looks like the 'foobar' output doesn't have any tests.", hints )
def test_maintainer_exists(self): lints, hints = linter.lintify( {"extra": {"recipe-maintainers": ["support"]}}, conda_forge=True ) expected_message = 'Recipe maintainer "support" does not exist' self.assertIn(expected_message, lints) lints = linter.lintify( {"extra": {"recipe-maintainers": ["isuruf"]}}, conda_forge=True ) expected_message = 'Recipe maintainer "isuruf" does not exist' self.assertNotIn(expected_message, lints) expected_message = "Feedstock with the same name exists in conda-forge" # Check that feedstock exists if staged_recipes lints = linter.lintify( {"package": {"name": "python"}}, recipe_dir="python", conda_forge=True ) self.assertIn(expected_message, lints) lints = linter.lintify( {"package": {"name": "python"}}, recipe_dir="python", conda_forge=False ) self.assertNotIn(expected_message, lints) # No lint if in a feedstock lints = linter.lintify( {"package": {"name": "python"}}, recipe_dir="recipe", conda_forge=True ) self.assertNotIn(expected_message, lints) lints = linter.lintify( {"package": {"name": "python"}}, recipe_dir="recipe", conda_forge=False ) self.assertNotIn(expected_message, lints) # Make sure there's no feedstock named python1 before proceeding gh = github.Github(os.environ["GH_TOKEN"]) cf = gh.get_user("conda-forge") try: cf.get_repo("python1-feedstock") feedstock_exists = True except github.UnknownObjectException as e: feedstock_exists = False if feedstock_exists: warnings.warn( "There's a feedstock named python1, but tests assume that there isn't" ) else: lints = linter.lintify( {"package": {"name": "python1"}}, recipe_dir="python", conda_forge=True ) self.assertNotIn(expected_message, lints) # Test bioconda recipe checking expected_message = ( "Recipe with the same name exists in bioconda: " "please discuss with @conda-forge/bioconda-recipes." ) bio = gh.get_user("bioconda").get_repo("bioconda-recipes") r = "samtools" try: bio.get_dir_contents("recipe/{}".format(r)) except github.UnknownObjectException as e: warnings.warn( "There's no bioconda recipe named {}, but tests assume that there is".format( r ) ) else: # Check that feedstock exists if staged_recipes lints = linter.lintify( {"package": {"name": r}}, recipe_dir=r, conda_forge=True ) self.assertIn(expected_message, lints) lints = linter.lintify( {"package": {"name": r}}, recipe_dir=r, conda_forge=False ) self.assertNotIn(expected_message, lints) # No lint if in a feedstock lints = linter.lintify( {"package": {"name": r}}, recipe_dir="recipe", conda_forge=True ) self.assertNotIn(expected_message, lints) lints = linter.lintify( {"package": {"name": r}}, recipe_dir="recipe", conda_forge=False ) self.assertNotIn(expected_message, lints) # No lint if the name isn't specified lints = linter.lintify({}, recipe_dir=r, conda_forge=True) self.assertNotIn(expected_message, lints) r = "this-will-never-exist" try: bio.get_dir_contents("recipes/{}".format(r)) except github.UnknownObjectException as e: lints = linter.lintify( {"package": {"name": r}}, recipe_dir=r, conda_forge=True ) self.assertNotIn(expected_message, lints) else: warnings.warn( "There's a bioconda recipe named {}, but tests assume that there isn't".format( r ) )
def test_bad_top_level(self): meta = OrderedDict([["package", {}], ["build", {}], ["sources", {}]]) lints, hints = linter.lintify(meta) expected_msg = "The top level meta key sources is unexpected" self.assertIn(expected_msg, lints)
def test_outputs(self): meta = OrderedDict([["outputs", [{"name": "asd"}]]]) lints, hints = linter.lintify(meta)