summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Ipsum <richard.ipsum@codethink.co.uk>2014-12-05 18:28:53 +0000
committerRichard Ipsum <richard.ipsum@codethink.co.uk>2014-12-05 22:47:52 +0000
commitc3faaf5ceccc6d6ec1de9dadd8c85dd1052dcf16 (patch)
tree54fba0d5e5813743afcb7bf3b00e955894ff56cf
parent71ca610d93bee0edfb75bbda8f4f56ba961eb0eb (diff)
downloadimport-c3faaf5ceccc6d6ec1de9dadd8c85dd1052dcf16.tar.gz
Add tests for requirement conflict detection
python.find_deps does some pretty basic validation of the requirement specs, this commit adds the tests for this validation.
-rwxr-xr-xbaserockimport/exts/python_find_deps_tests.py362
1 files changed, 362 insertions, 0 deletions
diff --git a/baserockimport/exts/python_find_deps_tests.py b/baserockimport/exts/python_find_deps_tests.py
new file mode 100755
index 0000000..f7fc2dd
--- /dev/null
+++ b/baserockimport/exts/python_find_deps_tests.py
@@ -0,0 +1,362 @@
+#!/usr/bin/env python
+# Copyright (C) 2014 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import unittest
+import random
+
+import imp
+python_find_deps = imp.load_source('python_find_deps', 'python.find_deps')
+
+from pkg_resources import parse_requirements, parse_version
+
+def reverse(xs):
+ return xs[::-1]
+
+class ConflictDetectionTests(unittest.TestCase):
+
+ def setUp(self):
+ reqs = ['a == 0.1', 'a == 0.2']
+ self.test_requirements = parse_requirements(reqs)
+
+ def run_conflict_test(self, requirements, expected_conflicts):
+ names = set([r.project_name for r in requirements])
+
+ with self.assertRaises(python_find_deps.ConflictError) as cm:
+ python_find_deps.resolve_specs(requirements)
+
+ for name in names:
+ _exps = [(op, parse_version(v)) for (op, v)
+ in expected_conflicts[name]]
+
+ self.assertEqual(cm.exception.specs, _exps)
+
+ def run_conflict_test_reversed(self, requirements, expected_conflicts):
+ # First reverse conflicts to get them in the right order
+ reversed_expected_conflicts = {k: reverse(v) for (k, v)
+ in expected_conflicts.iteritems()}
+
+ self.run_conflict_test(reverse(requirements),
+ reversed_expected_conflicts)
+
+ def run_no_conflict_test(self, requirements, expected_specs):
+ print python_find_deps.resolve_specs(requirements)
+
+ names = set([r.project_name for r in requirements])
+
+ for name in names:
+ _exps = set([(op, parse_version(v)) for (op, v)
+ in expected_specs[name]])
+
+ _specs = python_find_deps.resolve_specs(requirements)[name]
+
+ self.assertEqual(_specs, _exps)
+
+ def test_eqs_eqs(self):
+ requirements = list(parse_requirements(['a == 0.1', 'a == 0.2']))
+ expected_conflicts = {'a': [('==', '0.1'), ('==', '0.2')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_eqs_nt_eq(self):
+ # == x conflicts with != x
+ requirements = list(parse_requirements(['a == 0.1', 'a != 0.1']))
+ expected_conflicts = {'a': [('==', '0.1'), ('!=', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_eqs_lt(self):
+ # == x conflicts with < y if x >= y
+ requirements = list(parse_requirements(['a == 0.2', 'a < 0.1']))
+
+ expected_conflicts = {'a': [('==', '0.2'), ('<', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a == 0.1', 'a < 0.1']))
+
+ expected_conflicts = {'a': [('==', '0.1'), ('<', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_eqs_gt(self):
+ # == x conflicts with > y if x <= y
+ requirements = list(parse_requirements(['a == 0.1', 'a > 0.1']))
+
+ expected_conflicts = {'a': [('==', '0.1'), ('>', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a == 0.1', 'a > 0.2']))
+
+ expected_conflicts = {'a': [('==', '0.1'), ('>', '0.2')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_eqs_lte(self):
+ # == x conflicts with <= y if x > y
+ requirements = list(parse_requirements(['a == 0.2', 'a <= 0.1']))
+
+ expected_conflicts = {'a': [('==', '0.2'), ('<=', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a == 0.1', 'a <= 0.1'])) # no conflict
+ expected_specs = {'a': set([('==', '0.1'), ('<=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_eq_gte(self):
+ # == x conflicts with >= y if x < y
+ requirements = list(parse_requirements(['a == 0.1', 'a >= 0.2']))
+
+ expected_conflicts = {'a': [('==', '0.1'), ('>=', '0.2')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a == 0.1', 'a >= 0.1']))
+ expected_specs = {'a': set([('==', '0.1'), ('>=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_lt_lt(self):
+ # < x < y never conflicts
+ requirements = list(parse_requirements(['a < 0.1', 'a < 0.1']))
+ expected_specs = {'a': set([('<', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a < 0.1', 'a < 0.2']))
+ expected_specs = {'a': set([('<', '0.1'), ('<', '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_lt_gt(self):
+ # < x conflicts with > y if x <= y
+ requirements = list(parse_requirements(['a < 0.1', 'a > 0.1']))
+
+ expected_conflicts = {'a': [('<', '0.1'), ('>', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a < 0.1', 'a > 0.2']))
+
+ expected_conflicts = {'a': [('<', '0.1'), ('>', '0.2')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_lt_lte(self):
+ # < x <= y never conflicts
+ requirements = list(parse_requirements(['a < 0.1', 'a <= 0.1']))
+ expected_specs = {'a': set([('<', '0.1'), ('<=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a < 0.1', 'a <= 0.2']))
+ expected_specs = {'a': set([('<', '0.1'), ('<=', '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_lt_gte(self):
+ # < x conflicts with >= y if x <= y
+ requirements = list(parse_requirements(['a < 0.1', 'a >= 0.1']))
+
+ expected_conflicts = {'a': [('<', '0.1'), ('>=', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a < 0.1', 'a >= 0.2']))
+
+ expected_conflicts = {'a': [('<', '0.1'), ('>=', '0.2')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_gt_gt(self):
+ # > x > y never conflicts
+ requirements = list(parse_requirements(['a > 0.1', 'a > 0.1']))
+ expected_specs = {'a': set([('>', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a > 0.1', 'a > 0.2']))
+ expected_specs = {'a': set([('>', '0.1'), ('>', '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_gt_lte(self):
+ # > x conflicts with <= y if x >= y
+ requirements = list(parse_requirements(['a > 0.1', 'a <= 0.1']))
+
+ expected_conflicts = {'a': [('>', '0.1'), ('<=', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ requirements = list(parse_requirements(['a > 0.2', 'a <= 0.1']))
+
+ expected_conflicts = {'a': [('>', '0.2'), ('<=', '0.1')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_gt_gte(self):
+ # > x >= y never conflicts
+ requirements = list(parse_requirements(['a > 0.1', 'a >= 0.1']))
+ expected_specs = {'a': set([('>', '0.1'), ('>=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a > 0.1', 'a >= 0.2']))
+ expected_specs = {'a': set([('>', '0.1'), ('>=', '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_lte_lte(self):
+ # <= x <= y never conflicts
+ requirements = list(parse_requirements(['a <= 0.1', 'a <= 0.1']))
+ expected_specs = {'a': set([('<=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a <= 0.1', 'a <= 0.2']))
+ expected_specs = {'a': set([('<=', '0.1'), ('<=', '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_lte_gte(self):
+ # <= x conflicts with >= y if x < y
+ # note that if x == y, then the two specs don't add any constraint
+ requirements = list(parse_requirements(['a <= 0.1', 'a >= 0.1']))
+
+ expected_specs= {'a': set([('<=', '0.1'), ('>=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a <= 0.1', 'a >= 0.2']))
+
+ expected_conflicts = {'a': [('<=', '0.1'), ('>=', '0.2')]}
+
+ self.run_conflict_test(requirements, expected_conflicts)
+ self.run_conflict_test_reversed(requirements, expected_conflicts)
+
+ def test_gte_gte(self):
+ # >= x >= y never conflicts
+ requirements = list(parse_requirements(['a >= 0.1', 'a >= 0.1']))
+ expected_specs = {'a': set([('>=', '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a >= 0.1', 'a >= 0.2']))
+ expected_specs = {'a': set([('>=', '0.1'), ('>=', '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_ne(self):
+ # != can only conflict with == (which is tested above)
+ for s in ['<', '>', '<=', '>=']:
+ requirements = list(parse_requirements(['a != 0.1', 'a %s 0.1' % s]))
+ expected_specs = {'a': set([('!=', '0.1'), ('%s' % s, '0.1')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ requirements = list(parse_requirements(['a != 0.1', 'a %s 0.2' % s]))
+ expected_specs = {'a': set([('!=', '0.1'), ('%s' % s, '0.2')])}
+
+ self.run_no_conflict_test(requirements, expected_specs)
+ self.run_no_conflict_test(reverse(requirements), expected_specs)
+
+ def test_unmatched(self):
+ # Run all permutations, fail if we get an UnmatchedException
+ # or something else we weren't expecting
+ comparitors = ['==', '!=', '<', '>', '<=', '>=']
+ vs = [('0.1', '0.1'), ('0.1', '0.2'),
+ ('%s' % random.randint(0, 100), '%s' % random.randint(0, 100))]
+
+ for (vx, vy) in vs:
+ for cmpx in comparitors:
+ for cmpy in comparitors:
+ requirements = parse_requirements(['a %s %s' % (cmpx, vx),
+ 'a %s %s' % (cmpy, vy)])
+ try:
+ python_find_deps.resolve_specs(requirements)
+ except python_find_deps.ConflictError:
+ pass
+ except python_find_deps.UnmatchedException as e:
+ self.fail('Got UnmatchedException: %s' % e)
+ except Exception as e:
+ self.fail('Got some other unexpected Exception: %s' % e)
+
+ def test_cause_unmatched(self):
+ requirements_specs = list(parse_requirements(['a == 0.1', 'a == 0.1']))
+
+ # replace our parsed specs with invalid specs
+ # specifically, specs with invalid operators
+ #
+ # note, one spec won't do, we're validating the specs logically
+ # not syntactically; we assume the specs themselves have been parsed
+ # by pkg_resources which will do the validation for us.
+ #
+ # so we need two specs to force a check for a conflict,
+ # an UnmatchedError should occur if neither of the specs
+ # contain an operator recognised by the conflict detector
+ # e.g. '===', which is undefined in a spec
+ requirements_specs[0].specs = [('===', '0.1')]
+ requirements_specs[1].specs = [('===', '0.1')]
+
+ with self.assertRaises(python_find_deps.UnmatchedError):
+ specs = python_find_deps.resolve_specs(requirements_specs)
+
+ def test_distinct_requirements_no_conflict(self):
+ requirements = list(parse_requirements(['a == 0.1', 'b == 0.1']))
+
+ specs = python_find_deps.resolve_specs(requirements)
+
+ expected_specs = {'a': set([('==', parse_version('0.1'))]),
+ 'b': set([('==', parse_version('0.1'))])}
+
+ self.assertEqual(specs, expected_specs)
+
+
+if __name__ == '__main__':
+ suite = unittest.TestLoader().loadTestsFromTestCase(ConflictDetectionTests)
+ unittest.TextTestRunner(verbosity=2).run(suite)