Skip to content

Commit a9c7e7f

Browse files
committed
Merge pull request #130 from pelson/hash_it
Added a checksum test to the linter.
2 parents 3889643 + b692a04 commit a9c7e7f

2 files changed

Lines changed: 70 additions & 29 deletions

File tree

conda_smithy/lint_recipe.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
import ruamel.yaml
66

77

8-
EXPECTED_SECTION_ORDER = ['package', 'source', 'build', 'requirements', 'test', 'app', 'about', 'extra']
8+
EXPECTED_SECTION_ORDER = ['package', 'source', 'build', 'requirements',
9+
'test', 'app', 'about', 'extra']
10+
11+
REQUIREMENTS_ORDER = ['build', 'run']
912

1013

1114
class NullUndefined(jinja2.Undefined):
@@ -17,32 +20,38 @@ def lintify(meta, recipe_dir=None):
1720
lints = []
1821
major_sections = list(meta.keys())
1922

20-
# If the recipe_dir exists (no guarantee within this function) , we can find the
21-
# meta.yaml within it.
23+
# If the recipe_dir exists (no guarantee within this function) , we can
24+
# find the meta.yaml within it.
2225
meta_fname = os.path.join(recipe_dir or '', 'meta.yaml')
2326

2427
# 1: Top level meta.yaml keys should have a specific order.
25-
section_order_sorted = sorted(major_sections, key=EXPECTED_SECTION_ORDER.index)
28+
section_order_sorted = sorted(major_sections,
29+
key=EXPECTED_SECTION_ORDER.index)
2630
if major_sections != section_order_sorted:
27-
lints.append('The top level meta keys are in an unexpected order. Expecting {}.'.format(section_order_sorted))
31+
lints.append('The top level meta keys are in an unexpected order. '
32+
'Expecting {}.'.format(section_order_sorted))
2833

2934
# 2: The about section should have a home, license and summary.
3035
for about_item in ['home', 'license', 'summary']:
3136
about_section = meta.get('about', {}) or {}
3237
# if the section doesn't exist, or is just empty, lint it.
3338
if not about_section.get(about_item, ''):
34-
lints.append('The {} item is expected in the about section.'.format(about_item))
39+
lints.append('The {} item is expected in the about section.'
40+
''.format(about_item))
3541

3642
# 3: The recipe should have some maintainers.
3743
extra_section = meta.get('extra', {}) or {}
3844
if not extra_section.get('recipe-maintainers', []):
39-
lints.append('The recipe could do with some maintainers listed in the "extra/recipe-maintainers" section.')
45+
lints.append('The recipe could do with some maintainers listed in '
46+
'the "extra/recipe-maintainers" section.')
4047

4148
# 4: The recipe should have some tests.
4249
if 'test' not in major_sections:
43-
test_files = ['run_test.py', 'run_test.sh', 'run_test.bat', 'run_test.pl']
50+
test_files = ['run_test.py', 'run_test.sh', 'run_test.bat',
51+
'run_test.pl']
4452
a_test_file_exists = (recipe_dir is not None and
45-
any(os.path.exists(os.path.join(recipe_dir, test_file))
53+
any(os.path.exists(os.path.join(recipe_dir,
54+
test_file))
4655
for test_file in test_files))
4756
if not a_test_file_exists:
4857
lints.append('The recipe must have some tests.')
@@ -62,8 +71,8 @@ def lintify(meta, recipe_dir=None):
6271
if not good_selectors_pat.match(selector_line):
6372
bad_selectors.append(selector_line)
6473
if bad_selectors:
65-
lints.append('Selectors are suggested to take a " # [<selector>]" '
66-
'form.')
74+
lints.append('Selectors are suggested to take a '
75+
'" # [<selector>]" form.')
6776

6877
# 7: The build section should have a build number.
6978
build_section = meta.get('build', {}) or {}
@@ -73,17 +82,25 @@ def lintify(meta, recipe_dir=None):
7382

7483
# 8: The build section should be before the run section in requirements.
7584
requirements_section = meta.get('requirements', {}) or {}
76-
EXPECTED_REQUIREMENTS_ORDER = ['build', 'run']
77-
requirements_order_sorted = sorted(requirements_section, key=EXPECTED_REQUIREMENTS_ORDER.index)
85+
requirements_order_sorted = sorted(requirements_section,
86+
key=REQUIREMENTS_ORDER.index)
7887
if requirements_section.keys() != requirements_order_sorted:
79-
lints.append('The `requirements/build` section should be defined before '
80-
'the `requirements/run` section.')
88+
lints.append('The `requirements/build` section should be defined '
89+
'before the `requirements/run` section.')
90+
91+
# 9: Files downloaded should have a hash.
92+
source_section = meta.get('source', {}) or {}
93+
if ('url' in source_section and
94+
not ({'sha1', 'sha256', 'md5'} & set(source_section.keys()))):
95+
lints.append('When defining a source/url please add a sha256, sha1 '
96+
'or md5 checksum (sha256 preferably).')
8197

8298
return lints
8399

84100

85101
def selector_lines(lines):
86-
# Using the same pattern defined in conda-build (metadata.py), we identify selectors.
102+
# Using the same pattern defined in conda-build (metadata.py),
103+
# we identify selectors.
87104
sel_pat = re.compile(r'(.+?)\s*(#.*)?\[(.+)\](?(2).*)$')
88105

89106
for line in lines:
@@ -93,7 +110,7 @@ def selector_lines(lines):
93110
continue
94111
m = sel_pat.match(line)
95112
if m:
96-
cond = m.group(3)
113+
m.group(3)
97114
yield line
98115

99116

conda_smithy/tests/test_lint_recipe.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ def test_bad_order(self):
2424
['build', {}],
2525
['source', {}]])
2626
lints = linter.lintify(meta)
27-
expected_message = "The top level meta keys are in an unexpected order. Expecting ['package', 'source', 'build']."
28-
self.assertIn(expected_message, lints)
27+
expected_msg = ("The top level meta keys are in an unexpected "
28+
"order. Expecting ['package', 'source', 'build'].")
29+
self.assertIn(expected_msg, lints)
2930

3031
def test_missing_about_license_and_summary(self):
3132
meta = {'about': {'home': 'a URL'}}
@@ -66,7 +67,8 @@ def test_missing_about_home_empty(self):
6667
self.assertIn(expected_message, lints)
6768

6869
def test_maintainers_section(self):
69-
expected_message = 'The recipe could do with some maintainers listed in the "extra/recipe-maintainers" section.'
70+
expected_message = ('The recipe could do with some maintainers listed '
71+
'in the "extra/recipe-maintainers" section.')
7072

7173
lints = linter.lintify({'extra': {'recipe-maintainers': []}})
7274
self.assertIn(expected_message, lints)
@@ -88,7 +90,8 @@ def test_test_section(self):
8890
self.assertNotIn(expected_message, lints)
8991

9092
def test_test_section_with_recipe(self):
91-
# If we have a run_test.py file, we shouldn't need to provide other tests.
93+
# If we have a run_test.py file, we shouldn't need to provide
94+
# other tests.
9295

9396
expected_message = 'The recipe must have some tests.'
9497

@@ -102,7 +105,8 @@ def test_test_section_with_recipe(self):
102105
self.assertNotIn(expected_message, lints)
103106

104107
def test_selectors(self):
105-
expected_message = 'Selectors are suggested to take a " # [<selector>]" form.'
108+
expected_message = ('Selectors are suggested to take a '
109+
'" # [<selector>]" form.')
106110

107111
with tmp_directory() as recipe_dir:
108112
def assert_selector(selector, is_good=True):
@@ -114,11 +118,14 @@ def assert_selector(selector, is_good=True):
114118
""".format(selector))
115119
lints = linter.lintify({}, recipe_dir)
116120
if is_good:
117-
message = "Found lints when there shouldn't have been a lint for '{}'.".format(selector)
121+
message = ("Found lints when there shouldn't have been a "
122+
"lint for '{}'.".format(selector))
118123
else:
119-
message = "Expecting lints for '{}', but didn't get any.".format(selector)
124+
message = ("Expecting lints for '{}', but didn't get any."
125+
"".format(selector))
120126
self.assertEqual(not is_good,
121-
any(lint.startswith(expected_message) for lint in lints),
127+
any(lint.startswith(expected_message)
128+
for lint in lints),
122129
message)
123130

124131
assert_selector("name: foo_py3 # [py3k]")
@@ -141,8 +148,8 @@ def test_missing_build_number(self):
141148
self.assertIn(expected_message, lints)
142149

143150
def test_bad_requirements_order(self):
144-
expected_message = ("The `requirements/build` section should be defined "
145-
"before the `requirements/run` section.")
151+
expected_message = ("The `requirements/build` section should be "
152+
"defined before the `requirements/run` section.")
146153

147154
meta = {'requirements': OrderedDict([['run', 'a'],
148155
['build', 'a']])}
@@ -154,6 +161,21 @@ def test_bad_requirements_order(self):
154161
lints = linter.lintify(meta)
155162
self.assertNotIn(expected_message, lints)
156163

164+
def test_no_sha_with_dl(self):
165+
expected_message = ("When defining a source/url please add a sha256, "
166+
"sha1 or md5 checksum (sha256 preferably).")
167+
meta = {'source': {'url': None}}
168+
self.assertIn(expected_message, linter.lintify(meta))
169+
170+
meta = {'source': {'url': None, 'sha1': None}}
171+
self.assertNotIn(expected_message, linter.lintify(meta))
172+
173+
meta = {'source': {'url': None, 'sha256': None}}
174+
self.assertNotIn(expected_message, linter.lintify(meta))
175+
176+
meta = {'source': {'url': None, 'md5': None}}
177+
self.assertNotIn(expected_message, linter.lintify(meta))
178+
157179

158180
class TestCLI_recipe_lint(unittest.TestCase):
159181
def test_cli_fail(self):
@@ -165,7 +187,8 @@ def test_cli_fail(self):
165187
build: []
166188
requirements: []
167189
"""))
168-
child = subprocess.Popen(['conda-smithy', 'recipe-lint', recipe_dir],
190+
child = subprocess.Popen(['conda-smithy', 'recipe-lint',
191+
recipe_dir],
169192
stdout=subprocess.PIPE)
170193
out, _ = child.communicate()
171194
self.assertEqual(child.returncode, 1, out)
@@ -188,7 +211,8 @@ def test_cli_success(self):
188211
- a
189212
- b
190213
"""))
191-
child = subprocess.Popen(['conda-smithy', 'recipe-lint', recipe_dir],
214+
child = subprocess.Popen(['conda-smithy', 'recipe-lint',
215+
recipe_dir],
192216
stdout=subprocess.PIPE)
193217
out, _ = child.communicate()
194218
self.assertEqual(child.returncode, 0, out)

0 commit comments

Comments
 (0)