Skip to content

Commit 3467f40

Browse files
committed
bears/general: Add OutdatedDependencyBear
Closes #2445
1 parent c3ae6bc commit 3467f40

File tree

4 files changed

+135
-0
lines changed

4 files changed

+135
-0
lines changed

bear-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ munkres~=1.1.2
2323
mypy==0.590
2424
nbformat~=4.1
2525
nltk~=3.2
26+
pip-tools~=3.8.0
2627
proselint~=0.7.0
2728
pycodestyle~=2.2
2829
pydocstyle~=2.0

bear-requirements.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ pip_requirements:
5252
version: ~=4.1
5353
nltk:
5454
version: ~=3.2
55+
pip-tools:
56+
version: ~=3.8.0
5557
proselint:
5658
version: ~=0.7.0
5759
pycodestyle:
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from coalib.bears.LocalBear import LocalBear
2+
from coalib.results.Result import Result
3+
from dependency_management.requirements.PipRequirement import PipRequirement
4+
from distutils.version import LooseVersion
5+
from sarge import run, Capture
6+
7+
8+
class OutdatedDependencyBear(LocalBear):
9+
LANGUAGES = {'All'}
10+
REQUIREMENTS = {PipRequirement('pip-tools', '3.8.0')}
11+
AUTHORS = {'The coala developers'}
12+
AUTHORS_EMAILS = {'coala-devel@googlegroups.com'}
13+
LICENSE = 'AGPL-3.0'
14+
15+
def run(self, filename, file, requirement_type: str,):
16+
"""
17+
Checks for the outdated dependencies in a project.
18+
19+
:param requirement_type:
20+
One of the requirement types supported by coala's package manager.
21+
:param requirements_file:
22+
Requirements file can be specified to look for the requirements.
23+
"""
24+
requirement_types = ['pip']
25+
26+
if requirement_type not in requirement_types:
27+
raise ValueError('Currently the bear only supports {} as '
28+
'requirement_type.'
29+
.format(', '.join(
30+
_type for _type in requirement_types)))
31+
32+
message = ('The requirement {} with version {} is not '
33+
'pinned to its latest version {}.')
34+
35+
out = run('pip-compile -n --allow-unsafe {}'.format(filename),
36+
stdout=Capture())
37+
38+
data = [line for line in out.stdout.text.splitlines()
39+
if '#' not in line and line]
40+
41+
for requiremenent in data:
42+
package, version = requiremenent.split('==')
43+
pip_requirement = PipRequirement(package)
44+
latest_ver = pip_requirement.get_latest_version()
45+
line_number = [num for num, line in enumerate(file, 1)
46+
if package in line.lower()]
47+
48+
if LooseVersion(version) < LooseVersion(latest_ver):
49+
yield Result.from_values(origin=self,
50+
message=message.format(
51+
package,
52+
version,
53+
latest_ver),
54+
file=filename,
55+
line=line_number[0],
56+
end_line=line_number[0],
57+
)
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import unittest.mock
2+
import sarge
3+
from queue import Queue
4+
5+
from bears.general.OutdatedDependencyBear import OutdatedDependencyBear
6+
from coalib.testing.LocalBearTestHelper import LocalBearTestHelper
7+
from coalib.results.Result import Result
8+
from coalib.settings.Section import Section
9+
from coalib.settings.Setting import Setting
10+
11+
12+
test_file = """
13+
foo==1.0
14+
bar==2.0
15+
"""
16+
17+
18+
class OutdatedDependencyBearTest(LocalBearTestHelper):
19+
20+
def setUp(self):
21+
self.section = Section('')
22+
self.uut = OutdatedDependencyBear(self.section, Queue())
23+
24+
@unittest.mock.patch('bears.general.OutdatedDependencyBear.'
25+
'PipRequirement.get_latest_version')
26+
def test_pip_outdated_requirement(self, _mock):
27+
self.section.append(Setting('requirement_type', 'pip'))
28+
_mock.return_value = '3.0'
29+
with unittest.mock.patch('bears.general.OutdatedDependencyBear.'
30+
'run') as mock:
31+
patched = unittest.mock.Mock(spec=sarge.Pipeline)
32+
patched.stdout = unittest.mock.Mock(spec=sarge.Capture)
33+
patched.stdout.text = 'foo==1.0\nbar==2.0'
34+
mock.return_value = patched
35+
message = ('The requirement {} with version {} is not '
36+
'pinned to its latest version 3.0.')
37+
self.check_results(self.uut,
38+
test_file.splitlines(True),
39+
[Result.from_values(
40+
origin='OutdatedDependencyBear',
41+
message=message.format('foo', '1.0'),
42+
file='default',
43+
line=2, end_line=2,
44+
),
45+
Result.from_values(
46+
origin='OutdatedDependencyBear',
47+
message=message.format('bar', '2.0'),
48+
file='default',
49+
line=3, end_line=3,
50+
)],
51+
filename='default',
52+
)
53+
54+
@unittest.mock.patch('bears.general.OutdatedDependencyBear.'
55+
'PipRequirement.get_latest_version')
56+
def test_pip_latest_requirement(self, _mock):
57+
self.section.append(Setting('requirement_type', 'pip'))
58+
_mock.return_value = '1.0'
59+
with unittest.mock.patch('bears.general.OutdatedDependencyBear.'
60+
'run') as mock:
61+
patched = unittest.mock.Mock(spec=sarge.Pipeline)
62+
patched.stdout = unittest.mock.Mock(spec=sarge.Capture)
63+
patched.stdout.text = 'foo==1.0'
64+
mock.return_value = patched
65+
self.check_results(self.uut,
66+
[test_file.splitlines()[0]],
67+
[],
68+
filename='default')
69+
70+
def test_requirement_type_value_error(self):
71+
self.section.append(Setting('requirement_type', 'blabla'))
72+
error = ('ValueError: Currently the bear only supports pip as '
73+
'requirement_type.')
74+
with self.assertRaisesRegex(AssertionError, error):
75+
self.check_validity(self.uut, [], filename='default')

0 commit comments

Comments
 (0)