Skip to content

Commit 4280b5a

Browse files
Merge pull request #751 from adamtheturtle/custom-linters
Use new custom linters
2 parents 7f2f484 + ecd079e commit 4280b5a

File tree

8 files changed

+160
-39
lines changed

8 files changed

+160
-39
lines changed

.travis.yml

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
sudo: false
23
language: python
34
python:
@@ -6,31 +7,31 @@ matrix:
67
fast_finish: true
78
env:
89
matrix:
9-
- TEST_FILENAME=''
10-
- TEST_FILENAME=test_query.py
11-
- TEST_FILENAME=test_add_target.py
12-
- TEST_FILENAME=test_authorization_header.py
13-
- TEST_FILENAME=test_database_summary.py
14-
- TEST_FILENAME=test_date_header.py::TestFormat
15-
- TEST_FILENAME=test_date_header.py::TestMissing
16-
- TEST_FILENAME=test_date_header.py::TestSkewedTime
17-
- TEST_FILENAME=test_delete_target.py
18-
- TEST_FILENAME=test_get_duplicates.py
19-
- TEST_FILENAME=test_get_target.py
20-
- TEST_FILENAME=test_invalid_given_id.py
21-
- TEST_FILENAME=test_invalid_json.py
22-
- TEST_FILENAME=test_target_list.py
23-
- TEST_FILENAME=test_target_summary.py
24-
- TEST_FILENAME=test_unexpected_json.py
25-
- TEST_FILENAME=test_update_target.py::TestActiveFlag
26-
- TEST_FILENAME=test_update_target.py::TestApplicationMetadata
27-
- TEST_FILENAME=test_update_target.py::TestImage
28-
- TEST_FILENAME=test_update_target.py::TestTargetName
29-
- TEST_FILENAME=test_update_target.py::TestUnexpectedData
30-
- TEST_FILENAME=test_update_target.py::TestUpdate
31-
- TEST_FILENAME=test_update_target.py::TestWidth
32-
- TEST_FILENAME=test_update_target.py::TestInactiveProject
33-
- TEST_FILENAME=test_usage.py
10+
- CI_PATTERN=''
11+
- CI_PATTERN=test_query.py
12+
- CI_PATTERN=test_add_target.py
13+
- CI_PATTERN=test_authorization_header.py
14+
- CI_PATTERN=test_database_summary.py
15+
- CI_PATTERN=test_date_header.py::TestFormat
16+
- CI_PATTERN=test_date_header.py::TestMissing
17+
- CI_PATTERN=test_date_header.py::TestSkewedTime
18+
- CI_PATTERN=test_delete_target.py
19+
- CI_PATTERN=test_get_duplicates.py
20+
- CI_PATTERN=test_get_target.py
21+
- CI_PATTERN=test_invalid_given_id.py
22+
- CI_PATTERN=test_invalid_json.py
23+
- CI_PATTERN=test_target_list.py
24+
- CI_PATTERN=test_target_summary.py
25+
- CI_PATTERN=test_unexpected_json.py
26+
- CI_PATTERN=test_update_target.py::TestActiveFlag
27+
- CI_PATTERN=test_update_target.py::TestApplicationMetadata
28+
- CI_PATTERN=test_update_target.py::TestImage
29+
- CI_PATTERN=test_update_target.py::TestTargetName
30+
- CI_PATTERN=test_update_target.py::TestUnexpectedData
31+
- CI_PATTERN=test_update_target.py::TestUpdate
32+
- CI_PATTERN=test_update_target.py::TestWidth
33+
- CI_PATTERN=test_update_target.py::TestInactiveProject
34+
- CI_PATTERN=test_usage.py
3435
addons:
3536
apt:
3637
packages:

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ SHELL := /bin/bash -euxo pipefail
33
.PHONY: lint
44
lint:
55
check-manifest .
6+
pytest -vvv -x ci/custom_linters.py
67
dodgy
78
flake8 .
89
isort --recursive --check-only

ci/custom_linters.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
"""
2+
Custom lint tests.
3+
"""
4+
5+
import subprocess
6+
from pathlib import Path
7+
from typing import Dict, Set
8+
9+
import pytest
10+
import yaml
11+
12+
13+
def _travis_ci_patterns() -> Set[str]:
14+
"""
15+
Return the CI patterns given in the ``.travis.yml`` file.
16+
"""
17+
travis_file = Path(__file__).parent.parent / '.travis.yml'
18+
travis_contents = travis_file.read_text()
19+
travis_dict = yaml.load(travis_contents)
20+
travis_matrix = travis_dict['env']['matrix']
21+
22+
ci_patterns = set() # type: Set[str]
23+
for matrix_item in travis_matrix:
24+
key, value = matrix_item.split('=')
25+
assert key == 'CI_PATTERN'
26+
assert value not in ci_patterns
27+
# Special case for running no tests.
28+
if value != "''":
29+
ci_patterns.add(value)
30+
31+
return ci_patterns
32+
33+
34+
def _tests_from_pattern(ci_pattern: str) -> Set[str]:
35+
"""
36+
From a CI pattern, get all tests ``pytest`` would collect.
37+
"""
38+
tests = set([]) # type: Set[str]
39+
args = ['pytest', '--collect-only', ci_pattern, '-q']
40+
result = subprocess.run(args=args, stdout=subprocess.PIPE)
41+
output = result.stdout
42+
for line in output.splitlines():
43+
if line and not line.startswith(b'no tests ran in'):
44+
tests.add(line.decode())
45+
46+
return tests
47+
48+
49+
def test_ci_patterns_valid() -> None:
50+
"""
51+
All of the CI patterns in ``.travis.yml`` match at least one test in the
52+
test suite.
53+
"""
54+
ci_patterns = _travis_ci_patterns()
55+
56+
for ci_pattern in ci_patterns:
57+
pattern = 'tests/mock_vws/' + ci_pattern
58+
collect_only_result = pytest.main(['--collect-only', pattern])
59+
60+
message = '"{ci_pattern}" does not match any tests.'.format(
61+
ci_pattern=ci_pattern,
62+
)
63+
assert collect_only_result == 0, message
64+
65+
66+
def test_tests_collected_once() -> None:
67+
"""
68+
Each test in the test suite is collected exactly once.
69+
70+
This does not necessarily mean that they are run - they may be skipped.
71+
"""
72+
ci_patterns = _travis_ci_patterns()
73+
tests_to_patterns: Dict[str, Set[str]] = {}
74+
for pattern in ci_patterns:
75+
pattern = 'tests/mock_vws/' + pattern
76+
tests = _tests_from_pattern(ci_pattern=pattern)
77+
for test in tests:
78+
if test in tests_to_patterns:
79+
tests_to_patterns[test].add(pattern)
80+
else:
81+
tests_to_patterns[test] = set([pattern])
82+
83+
for test_name, patterns in tests_to_patterns.items():
84+
message = (
85+
'Test "{test_name}" will be run once for each pattern in '
86+
'{patterns}. '
87+
'Each test should be run only once.'
88+
).format(
89+
test_name=test_name,
90+
patterns=patterns,
91+
)
92+
assert len(patterns) == 1, message
93+
94+
all_tests = _tests_from_pattern(ci_pattern='tests/')
95+
assert tests_to_patterns.keys() - all_tests == set()
96+
assert all_tests - tests_to_patterns.keys() == set()
97+
98+
99+
def test_init_files() -> None:
100+
"""
101+
``__init__`` files exist where they should do.
102+
103+
If ``__init__`` files are missing, linters may not run on all files that
104+
they should run on.
105+
"""
106+
directories = (Path('src'), Path('tests'))
107+
108+
for directory in directories:
109+
files = directory.glob('**/*.py')
110+
for python_file in files:
111+
parent = python_file.parent
112+
expected_init = parent / '__init__.py'
113+
assert expected_init.exists()

ci/run_script.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@
1010
import pytest
1111

1212

13-
def run_test(test_filename: str) -> None:
13+
def run_test(ci_pattern: str) -> None:
1414
"""
1515
Run pytest with a given filename.
1616
"""
17-
path = Path('tests') / 'mock_vws' / test_filename
17+
path = Path('tests') / 'mock_vws' / ci_pattern
1818
result = pytest.main(
1919
[
2020
'-vvv',
@@ -28,8 +28,8 @@ def run_test(test_filename: str) -> None:
2828

2929

3030
if __name__ == '__main__':
31-
TEST_FILENAME = os.environ.get('TEST_FILENAME')
32-
if TEST_FILENAME:
33-
run_test(test_filename=TEST_FILENAME)
31+
CI_PATTERN = os.environ.get('CI_PATTERN')
32+
if CI_PATTERN:
33+
run_test(ci_pattern=CI_PATTERN)
3434
else:
3535
subprocess.check_call(['make', 'lint'])

dev-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ pytest==3.7.2 # Test runners
2121
timeout-decorator==0.4.0 # Decorate functions to time out.
2222
vulture==0.29
2323
yapf==0.21.0 # Automatic formatting for Python
24+
PyYAML==3.13

spelling_private_dict.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ rgb
5151
str
5252
timestamp
5353
todo
54+
travis
5455
txt
5556
unmocked
5657
url
@@ -61,3 +62,4 @@ vuforia's
6162
vwq
6263
vws
6364
xxx
65+
yml
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Common fixtures.
3+
"""

tests/mock_vws/fixtures/prepared_requests.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def _add_target(
7474
data=content,
7575
)
7676

77-
prepared_request = request.prepare()
77+
prepared_request = request.prepare() # type: ignore # type: ignore
7878

7979
return Endpoint(
8080
successful_headers_status_code=codes.CREATED,
@@ -126,7 +126,7 @@ def _delete_target(
126126
data=content,
127127
)
128128

129-
prepared_request = request.prepare()
129+
prepared_request = request.prepare() # type: ignore
130130
return Endpoint(
131131
successful_headers_status_code=codes.OK,
132132
successful_headers_result_code=ResultCodes.SUCCESS,
@@ -173,7 +173,7 @@ def _database_summary(
173173
data=content,
174174
)
175175

176-
prepared_request = request.prepare()
176+
prepared_request = request.prepare() # type: ignore
177177

178178
return Endpoint(
179179
successful_headers_status_code=codes.OK,
@@ -227,7 +227,7 @@ def _get_duplicates(
227227
data=content,
228228
)
229229

230-
prepared_request = request.prepare()
230+
prepared_request = request.prepare() # type: ignore
231231

232232
return Endpoint(
233233
successful_headers_status_code=codes.OK,
@@ -280,7 +280,7 @@ def _get_target(
280280
data=content,
281281
)
282282

283-
prepared_request = request.prepare()
283+
prepared_request = request.prepare() # type: ignore
284284

285285
return Endpoint(
286286
successful_headers_status_code=codes.OK,
@@ -326,7 +326,7 @@ def _target_list(vuforia_database_keys: VuforiaDatabaseKeys) -> Endpoint:
326326
data=content,
327327
)
328328

329-
prepared_request = request.prepare()
329+
prepared_request = request.prepare() # type: ignore
330330

331331
return Endpoint(
332332
successful_headers_status_code=codes.OK,
@@ -379,7 +379,7 @@ def _target_summary(
379379
data=content,
380380
)
381381

382-
prepared_request = request.prepare()
382+
prepared_request = request.prepare() # type: ignore
383383

384384
return Endpoint(
385385
successful_headers_status_code=codes.OK,
@@ -435,7 +435,7 @@ def _update_target(
435435
data=content,
436436
)
437437

438-
prepared_request = request.prepare()
438+
prepared_request = request.prepare() # type: ignore
439439

440440
return Endpoint(
441441
successful_headers_status_code=codes.OK,
@@ -488,7 +488,7 @@ def _query(
488488
data=content,
489489
)
490490

491-
prepared_request = request.prepare()
491+
prepared_request = request.prepare() # type: ignore
492492

493493
return Endpoint(
494494
successful_headers_status_code=codes.OK,

0 commit comments

Comments
 (0)