Repository: melexis/warnings-plugin

Issue:
Add exact number of warnings parameter
Now option is to have minimum and maximum number of warnings. If you use both parameters you can make the range, so that you get failure if number of warnings is different from what is expected. We could group this and use a `--expectedwarnings value` argument which would do this internally for us.

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index 00a7092..fa9f802 100644
--- a/README.rst
+++ b/README.rst
@@ -6,11 +6,11 @@
     :target: https://travis-ci.org/melexis/warnings-plugin
     :alt: Build status
 
-.. image:: https://badge.fury.io/py/mlx.warnings.png
+.. image:: https://badge.fury.io/py/mlx.warnings.svg
     :target: https://badge.fury.io/py/mlx.warnings
     :alt: Pypi packaged release
 
-.. image:: https://img.shields.io/badge/Documentation-published-brightgreen.png
+.. image:: https://img.shields.io/badge/Documentation-published-brightgreen.svg
     :target: https://melexis.github.io/warnings-plugin/
     :alt: Documentation
 
@@ -307,9 +307,9 @@ Other options
 -------------
 
 Since the plugin is under active development there are new Features added fast.
-Important options currently include setting maximum number of warnings or
-minimum number of warnings, that are still acceptable to return 0 (success)
-return code. Look at scripts help, for more details about the options.
+Important options currently include setting a minimum and a maximum number of warnings
+that are still acceptable to return 0 (success). Requiring an exact amount of warnings
+using a single option is also possible. Look at scripts help for more details about the options.
 
 Exclude matches with regexes
 ----------------------------
diff --git a/src/mlx/warnings.py b/src/mlx/warnings.py
index ef8c741..cbe08d0 100644
--- a/src/mlx/warnings.py
+++ b/src/mlx/warnings.py
@@ -190,10 +190,12 @@ def warnings_wrapper(args):
     group1.add_argument('-s', '--sphinx', dest='sphinx', action='store_true')
     group1.add_argument('-j', '--junit', dest='junit', action='store_true')
     group1.add_argument('-x', '--xmlrunner', dest='xmlrunner', action='store_true')
-    group1.add_argument('-m', '--maxwarnings', type=int, required=False, default=0,
+    group1.add_argument('-m', '--maxwarnings', '--max-warnings', type=int, default=0,
                         help='Maximum amount of warnings accepted')
-    group1.add_argument('--minwarnings', type=int, required=False, default=0,
+    group1.add_argument('--minwarnings', '--min-warnings', type=int, default=0,
                         help='Minimum amount of warnings accepted')
+    group1.add_argument('--exact-warnings', type=int, default=0,
+                        help='Exact amount of warnings expected')
     group2 = parser.add_argument_group('Configuration file with options')
     group2.add_argument('--config', dest='configfile', action='store', required=False,
                         help='Config file in JSON format provides toggle of checkers and their limits')
@@ -213,8 +215,9 @@ def warnings_wrapper(args):
 
     # Read config file
     if args.configfile is not None:
-        checkersflag = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner
-        if checkersflag or (args.maxwarnings != 0) or (args.minwarnings != 0):
+        checker_flags = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner
+        warning_args = (args.maxwarnings != 0) or (args.minwarnings != 0) or (args.exact_warnings != 0)
+        if checker_flags or warning_args:
             print("Configfile cannot be provided with other arguments")
             sys.exit(2)
         warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile)
@@ -230,8 +233,15 @@ def warnings_wrapper(args):
             warnings.activate_checker_name('xmlrunner')
         if args.coverity:
             warnings.activate_checker_name('coverity')
-        warnings.set_maximum(args.maxwarnings)
-        warnings.set_minimum(args.minwarnings)
+        if args.exact_warnings:
+            if args.maxwarnings | args.minwarnings:
+                print("expected-warnings cannot be provided with maxwarnings or minwarnings")
+                sys.exit(2)
+            warnings.set_maximum(args.exact_warnings)
+            warnings.set_minimum(args.exact_warnings)
+        else:
+            warnings.set_maximum(args.maxwarnings)
+            warnings.set_minimum(args.minwarnings)
 
     if args.include_sphinx_deprecation and 'sphinx' in warnings.activated_checkers.keys():
         warnings.get_checker('sphinx').include_sphinx_deprecation()
diff --git a/src/mlx/warnings_checker.py b/src/mlx/warnings_checker.py
index d4a76c7..f044e3b 100644
--- a/src/mlx/warnings_checker.py
+++ b/src/mlx/warnings_checker.py
@@ -123,17 +123,22 @@ class WarningsChecker:
         ''' Function for checking whether the warning count is within the configured limits
 
         Returns:
-            int: 0 if the amount of warnings is within limits. the count of warnings otherwise
+            int: 0 if the amount of warnings is within limits, the count of warnings otherwise
         '''
-        if self.count > self.warn_max:
+        if self.warn_min == self.warn_max and self.count == self.warn_max:
+            print("Number of warnings ({0.count}) is exactly as expected. Well done."
+                  .format(self))
+        elif self.count > self.warn_max:
             print("Number of warnings ({0.count}) is higher than the maximum limit ({0.warn_max}). "
                   "Returning error code 1.".format(self))
             return self.count
-        if self.count < self.warn_min:
+        elif self.count < self.warn_min:
             print("Number of warnings ({0.count}) is lower than the minimum limit ({0.warn_min}). "
                   "Returning error code 1.".format(self))
             return self.count
-        print("Number of warnings ({0.count}) is between limits {0.warn_min} and {0.warn_max}. Well done.".format(self))
+        else:
+            print("Number of warnings ({0.count}) is between limits {0.warn_min} and {0.warn_max}. Well done."
+                  .format(self))
         return 0
 
     def print_when_verbose(self, message):


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 243acfe..f975e68 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -86,9 +86,33 @@ class TestIntegration(TestCase):
         self.assertEqual(self.junit_warning_cnt, retval)
 
     def test_min_but_still_ok(self):
-        retval = warnings_wrapper(['--junit', '--maxwarnings', '100', '--minwarnings', '2', 'tests/junit*.xml'])
+        retval = warnings_wrapper(['--junit', '--max-warnings', '100', '--min-warnings', '2', 'tests/junit*.xml'])
         self.assertEqual(0, retval)
 
+    def test_exact_sphinx(self):
+        retval = warnings_wrapper(['--sphinx', '--exact-warnings', '2', 'tests/sphinx_double_warning.txt'])
+        self.assertEqual(0, retval)
+
+    def test_exact_too_few(self):
+        retval = warnings_wrapper(['--sphinx', '--exact-warnings', '3', 'tests/sphinx_double_warning.txt'])
+        self.assertEqual(2, retval)
+
+    def test_exact_too_many(self):
+        retval = warnings_wrapper(['--sphinx', '--exact-warnings', '1', 'tests/sphinx_double_warning.txt'])
+        self.assertEqual(2, retval)
+
+    def test_exact_junit(self):
+        retval = warnings_wrapper(['--junit', '--exact-warnings', '3', 'tests/junit*.xml'])
+        self.assertEqual(0, retval)
+
+    def test_exact_with_min(self):
+        with self.assertRaises(SystemExit):
+            warnings_wrapper(['--junit', '--exact-warnings', '3', '--min-warnings', '3', 'tests/junit*.xml'])
+
+    def test_exact_with_max(self):
+        with self.assertRaises(SystemExit):
+            warnings_wrapper(['--junit', '--exact-warnings', '3', '--max-warnings', '3', 'tests/junit*.xml'])
+
     def test_configfile_ok(self):
         retval = warnings_wrapper(['--config', 'tests/config_example.json', 'tests/junit_single_fail.xml'])
         self.assertEqual(0, retval)