diff --git a/whereto/app.py b/whereto/app.py index fef9636..5157c88 100644 --- a/whereto/app.py +++ b/whereto/app.py @@ -27,8 +27,11 @@ from whereto import rules def process_tests(ruleset, tests): """Run the tests against the ruleset and return the results. - Results are generated as tuples containing the inputs that did not - match the expected value. The first element is the test tuple + The return value is a tuple containing a list of tuples with the + inputs that did not match the expected value, and a set containing + the line numbers of the rules that never matched an input test. + + The first element of the mismatched tuples is the test tuple (line, input, expected), and the second element is the list of any rules that did match the input pattern. @@ -36,17 +39,44 @@ def process_tests(ruleset, tests): :type ruleset: RuleSet """ + used = set() + mismatches = [] for linenum, input, code, expected in tests: matches = list(ruleset.match(input)) if len(matches) == 1: match = matches[0] if (code, expected) == match[1:]: + used.add(match[0]) continue - yield ((linenum, input, code, expected), matches) + mismatches.append( + ((linenum, input, code, expected), matches) + ) + untested = set(ruleset.all_ids) - used + return (mismatches, untested) def main(): arg_parser = argparse.ArgumentParser() + group = arg_parser.add_mutually_exclusive_group() + group.add_argument( + '--ignore-untested', + action='store_false', + dest='error_untested', + default=True, + help='ignore untested rules', + ) + group.add_argument( + '--error-untested', + action='store_true', + dest='error_untested', + help='error if there are untested rules', + ) + arg_parser.add_argument( + '-q', '--quiet', + action='store_true', + default=False, + help='run quietly', + ) arg_parser.add_argument( 'htaccess_file', help='file with rewrite rules', @@ -69,7 +99,8 @@ def main(): ] failures = 0 - for test, matches in process_tests(ruleset, tests): + mismatches, untested = process_tests(ruleset, tests) + for test, matches in mismatches: failures += 1 if not matches: print('No rule matched test on line {}: {}'.format( @@ -82,6 +113,15 @@ def main(): for match in matches: print(' {}'.format(ruleset[match[0]])) + if untested: + if not args.quiet: + print('') + for linenum in sorted(untested): + if not args.quiet: + print('Untested rule: {}'.format(ruleset[linenum])) + if args.error_untested: + failures += 1 + if failures: print('\n{} failures'.format(failures)) return 1 diff --git a/whereto/rules.py b/whereto/rules.py index 9ed3603..7a9ac16 100644 --- a/whereto/rules.py +++ b/whereto/rules.py @@ -96,6 +96,10 @@ class RuleSet(object): def __iter__(self): return iter(self._rules) + @property + def all_ids(self): + return list(self._by_num.keys()) + def match(self, path): for rule in self: m = rule.match(path) diff --git a/whereto/tests/test_app.py b/whereto/tests/test_app.py index 48920af..cf0bb87 100644 --- a/whereto/tests/test_app.py +++ b/whereto/tests/test_app.py @@ -28,11 +28,11 @@ class TestProcessTests(base.TestCase): ) def test_one_match(self): - actual = list(app.process_tests( + actual = app.process_tests( self.ruleset, [(1, '/path', '301', '/new/path')], - )) - expected = [] + ) + expected = ([], set()) self.assertEqual(expected, actual) def test_two_matches(self): @@ -40,13 +40,14 @@ class TestProcessTests(base.TestCase): 2, 'redirect', '301', '/path', '/duplicate/redirect', ) - actual = list(app.process_tests( + actual = app.process_tests( self.ruleset, [(1, '/path', '301', '/new/path')], - )) - expected = [ - ((1, '/path', '301', '/new/path'), - [(1, '301', '/new/path'), - (2, '301', '/duplicate/redirect')]) - ] + ) + expected = ( + [((1, '/path', '301', '/new/path'), + [(1, '301', '/new/path'), + (2, '301', '/duplicate/redirect')])], + {1, 2}, + ) self.assertEqual(expected, actual) diff --git a/whereto/tests/test_rules.py b/whereto/tests/test_rules.py index 1098afb..6c17fce 100644 --- a/whereto/tests/test_rules.py +++ b/whereto/tests/test_rules.py @@ -156,6 +156,18 @@ class TestRuleSet(base.TestCase): self.assertEqual(1, len(self.ruleset._rules)) self.assertIsInstance(self.ruleset._rules[0], rules.RedirectMatch) + def test_all_ids(self): + self.ruleset.add( + 1, + 'redirect', '301', '/path', '/new/path', + ) + self.assertEqual([1], self.ruleset.all_ids) + self.ruleset.add( + 2, + 'redirect', '301', '/path', '/other/path', + ) + self.assertEqual([1, 2], self.ruleset.all_ids) + def test_match_one(self): self.ruleset.add( 1,