Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bears/general: Add OutdatedDependencyBear #2928

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bear-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ munkres~=1.1.2
mypy==0.590
nbformat~=4.1
nltk~=3.2
pip-tools~=3.8.0
proselint~=0.7.0
pycodestyle~=2.2
pydocstyle~=2.0
Expand Down
2 changes: 2 additions & 0 deletions bear-requirements.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pip_requirements:
version: ~=4.1
nltk:
version: ~=3.2
pip-tools:
version: ~=3.8.0
proselint:
version: ~=0.7.0
pycodestyle:
Expand Down
57 changes: 57 additions & 0 deletions bears/general/OutdatedDependencyBear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from coalib.bears.LocalBear import LocalBear
from coalib.results.Result import Result
from dependency_management.requirements.PipRequirement import PipRequirement
from distutils.version import LooseVersion
from sarge import run, Capture


class OutdatedDependencyBear(LocalBear):
LANGUAGES = {'All'}
REQUIREMENTS = {PipRequirement('pip-tools', '3.8.0')}
AUTHORS = {'The coala developers'}
AUTHORS_EMAILS = {'[email protected]'}
LICENSE = 'AGPL-3.0'

def run(self, filename, file, requirement_type: str,):
"""
Checks for the outdated dependencies in a project.

:param requirement_type:
One of the requirement types supported by coala's package manager.
:param requirements_file:
Requirements file can be specified to look for the requirements.
"""
requirement_types = ['pip']

if requirement_type not in requirement_types:
raise ValueError('Currently the bear only supports {} as '
'requirement_type.'
.format(', '.join(
_type for _type in requirement_types)))

message = ('The requirement {} with version {} is not '
'pinned to its latest version {}.')

out = run('pip-compile -n --allow-unsafe {}'.format(filename),
stdout=Capture())

data = [line for line in out.stdout.text.splitlines()
if '#' not in line and line]

for requiremenent in data:
package, version = requiremenent.split('==')
pip_requirement = PipRequirement(package)
latest_ver = pip_requirement.get_latest_version()
line_number = [num for num, line in enumerate(file, 1)
if package in line.lower()]

if LooseVersion(version) < LooseVersion(latest_ver):
yield Result.from_values(origin=self,
message=message.format(
package,
version,
latest_ver),
file=filename,
line=line_number[0],
end_line=line_number[0],
)
75 changes: 75 additions & 0 deletions tests/general/OutdatedDependencyBearTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import unittest.mock
import sarge
from queue import Queue

from bears.general.OutdatedDependencyBear import OutdatedDependencyBear
from coalib.testing.LocalBearTestHelper import LocalBearTestHelper
from coalib.results.Result import Result
from coalib.settings.Section import Section
from coalib.settings.Setting import Setting


test_file = """
foo==1.0
bar==2.0
"""


class OutdatedDependencyBearTest(LocalBearTestHelper):

def setUp(self):
self.section = Section('')
self.uut = OutdatedDependencyBear(self.section, Queue())

@unittest.mock.patch('bears.general.OutdatedDependencyBear.'
'PipRequirement.get_latest_version')
def test_pip_outdated_requirement(self, _mock):
self.section.append(Setting('requirement_type', 'pip'))
_mock.return_value = '3.0'
with unittest.mock.patch('bears.general.OutdatedDependencyBear.'
'run') as mock:
patched = unittest.mock.Mock(spec=sarge.Pipeline)
patched.stdout = unittest.mock.Mock(spec=sarge.Capture)
patched.stdout.text = 'foo==1.0\nbar==2.0'
mock.return_value = patched
message = ('The requirement {} with version {} is not '
'pinned to its latest version 3.0.')
self.check_results(self.uut,
test_file.splitlines(True),
[Result.from_values(
origin='OutdatedDependencyBear',
message=message.format('foo', '1.0'),
file='default',
line=2, end_line=2,
),
Result.from_values(
origin='OutdatedDependencyBear',
message=message.format('bar', '2.0'),
file='default',
line=3, end_line=3,
)],
filename='default',
)

@unittest.mock.patch('bears.general.OutdatedDependencyBear.'
'PipRequirement.get_latest_version')
def test_pip_latest_requirement(self, _mock):
self.section.append(Setting('requirement_type', 'pip'))
_mock.return_value = '1.0'
with unittest.mock.patch('bears.general.OutdatedDependencyBear.'
'run') as mock:
patched = unittest.mock.Mock(spec=sarge.Pipeline)
patched.stdout = unittest.mock.Mock(spec=sarge.Capture)
patched.stdout.text = 'foo==1.0'
mock.return_value = patched
self.check_results(self.uut,
[test_file.splitlines()[0]],
[],
filename='default')

def test_requirement_type_value_error(self):
self.section.append(Setting('requirement_type', 'blabla'))
error = ('ValueError: Currently the bear only supports pip as '
'requirement_type.')
with self.assertRaisesRegex(AssertionError, error):
self.check_validity(self.uut, [], filename='default')