Repository: sscpac/statick

Issue:
Add support for changing the issue file that is looked up
The idea is that there could be multiple mapping files (the use case here is that there are the "official" mapping files, based on the CMU-SEI published list, but we would like to add "unofficial" mappings which are not blessed by CMU-SEI). I'm thinking that the most straightforward approach is to support adding a suffix (e.g. findbugs-experimental.txt) and try to look that up if it exists. Shouldn't be too complicated.

Discussion:
denewiler: tox is passing locally for me, but there are errors on Travis. These errors look similar to some of the issues I was having with `statick_ws`, where the plugins were not getting arguments passed to them the way I would expect.
creffett: Ahh, the issue is that the perl PR hadn't been accepted when I made the MR but was there after you made your edit, so it caught the fact that one tweak I had to make in the tests hadn't been made in the perlcritic test. Easy fix.

Patch:
diff --git a/statick_tool/statick.py b/statick_tool/statick.py
index 1ca89f6..c0ab701 100644
--- a/statick_tool/statick.py
+++ b/statick_tool/statick.py
@@ -72,6 +72,8 @@ class Statick(object):
                           type=str, help="Force only the given list of tools to run")
         args.add_argument('--version', action='version',
                           version='%(prog)s {version}'.format(version=__version__))
+        args.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                          type=str, help="Suffix to use when searching for CERT mapping files")
 
         for _, plugin in list(self.discovery_plugins.items()):
             plugin.gather_args(args)
diff --git a/statick_tool/tool_plugin.py b/statick_tool/tool_plugin.py
index c8f3b76..28839e3 100644
--- a/statick_tool/tool_plugin.py
+++ b/statick_tool/tool_plugin.py
@@ -36,8 +36,16 @@ class ToolPlugin(IPlugin):
 
     def load_mapping(self):
         """Load a mapping between warnings and identifiers."""
-        file_name = "plugin_mapping/%s.txt" % (self.get_name())
+        file_name = "plugin_mapping/{}.txt".format(self.get_name())
         full_path = self.plugin_context.resources.get_file(file_name)
+        if self.plugin_context.args.mapping_file_suffix is not None:
+            # If the user specified a suffix, try to get the suffixed version of the file
+            suffixed_file_name = "plugin_mapping/{}-{}.txt".format(self.get_name(), self.plugin_context.args.mapping_file_suffix)
+            suffixed_full_path = self.plugin_context.resources.get_file(suffixed_file_name)
+            if suffixed_full_path is not None:
+                # If there actually is a file with that suffix, use it (else use the un-suffixed version)
+                full_path = suffixed_full_path
+
         if full_path is None:
             return {}
         warning_mapping = {}


Test patch:
diff --git a/tests/plugins/tool/bandit_tool_plugin/test_bandit_tool_plugin.py b/tests/plugins/tool/bandit_tool_plugin/test_bandit_tool_plugin.py
index 0724e8e..18006bc 100644
--- a/tests/plugins/tool/bandit_tool_plugin/test_bandit_tool_plugin.py
+++ b/tests/plugins/tool/bandit_tool_plugin/test_bandit_tool_plugin.py
@@ -19,6 +19,8 @@ def setup_bandit_tool_plugin():
     arg_parser.add_argument("--show-tool-output", dest="show_tool_output",
                             action="store_true", help="Show tool output")
     arg_parser.add_argument("--bandit-bin", dest="bandit_bin")
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str)
 
     resources = Resources([os.path.join(os.path.dirname(statick_tool.__file__),
                                         'plugins')])
diff --git a/tests/plugins/tool/perlcritic_tool_plugin/test_perlcritic_tool_plugin.py b/tests/plugins/tool/perlcritic_tool_plugin/test_perlcritic_tool_plugin.py
index b2dea60..6ac3733 100644
--- a/tests/plugins/tool/perlcritic_tool_plugin/test_perlcritic_tool_plugin.py
+++ b/tests/plugins/tool/perlcritic_tool_plugin/test_perlcritic_tool_plugin.py
@@ -21,6 +21,8 @@ def setup_perlcritic_tool_plugin():
     arg_parser.add_argument("--show-tool-output", dest="show_tool_output",
                             action="store_true", help="Show tool output")
     arg_parser.add_argument("--perlcritic-bin", dest="perlcritic_bin")
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str)
 
     resources = Resources([os.path.join(os.path.dirname(statick_tool.__file__),
                                         'plugins')])
diff --git a/tests/tool_plugin/good_config/rsc/plugin_mapping/None-experimental.txt b/tests/tool_plugin/good_config/rsc/plugin_mapping/None-experimental.txt
new file mode 100644
index 0000000..d595bf5
--- /dev/null
+++ b/tests/tool_plugin/good_config/rsc/plugin_mapping/None-experimental.txt
@@ -0,0 +1,1 @@
+b:TST2-NO
diff --git a/tests/tool_plugin/test_tool_plugin.py b/tests/tool_plugin/test_tool_plugin.py
index 10588cb..cdf2fb3 100644
--- a/tests/tool_plugin/test_tool_plugin.py
+++ b/tests/tool_plugin/test_tool_plugin.py
@@ -17,6 +17,8 @@ from statick_tool.tool_plugin import ToolPlugin
 def test_tool_plugin_load_mapping_valid():
     """Test that we can load the warnings mapping."""
     arg_parser = argparse.ArgumentParser()
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str)
     resources = Resources([os.path.join(os.path.dirname(__file__), 'good_config')])
     plugin_context = PluginContext(arg_parser.parse_args([]), resources, None)
     tp = ToolPlugin()
@@ -29,6 +31,8 @@ def test_tool_plugin_load_mapping_valid():
 def test_tool_plugin_load_mapping_invalid():
     """Test that we correctly skip invalid entries."""
     arg_parser = argparse.ArgumentParser()
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str)
     resources = Resources([os.path.join(os.path.dirname(__file__), 'bad_config')])
     plugin_context = PluginContext(arg_parser.parse_args([]), resources, None)
     tp = ToolPlugin()
@@ -40,6 +44,8 @@ def test_tool_plugin_load_mapping_invalid():
 def test_tool_plugin_load_mapping_missing():
     """Test that we return an empty dict for missing files."""
     arg_parser = argparse.ArgumentParser()
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str)
     resources = Resources([os.path.join(os.path.dirname(__file__), 'missing_config')])
     plugin_context = PluginContext(arg_parser.parse_args([]), resources, None)
     tp = ToolPlugin()
@@ -48,6 +54,34 @@ def test_tool_plugin_load_mapping_missing():
     assert not mapping
 
 
+def test_tool_plugin_load_mapping_suffixed():
+    """Test that we can load the warnings mapping with a suffix."""
+    arg_parser = argparse.ArgumentParser()
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str, default='experimental')
+    resources = Resources([os.path.join(os.path.dirname(__file__), 'good_config')])
+    plugin_context = PluginContext(arg_parser.parse_args([]), resources, None)
+    tp = ToolPlugin()
+    tp.set_plugin_context(plugin_context)
+    mapping = tp.load_mapping()
+    assert len(mapping) == 1
+    assert mapping == {'b': 'TST2-NO'}
+
+
+def test_tool_plugin_load_mapping_suffixed_fallback():
+    """Test that we fall back to the non-suffixed file if we can't find a mapping file with an appropriate suffix."""
+    arg_parser = argparse.ArgumentParser()
+    arg_parser.add_argument('--mapping-file-suffix', dest="mapping_file_suffix",
+                            type=str, default='gibberish')
+    resources = Resources([os.path.join(os.path.dirname(__file__), 'good_config')])
+    plugin_context = PluginContext(arg_parser.parse_args([]), resources, None)
+    tp = ToolPlugin()
+    tp.set_plugin_context(plugin_context)
+    mapping = tp.load_mapping()
+    assert len(mapping) == 1
+    assert mapping == {'a': 'TST1-NO'}
+
+
 def test_tool_plugin_get_user_flags_invalid_level():
     """Test that we return an empty list for invalid levels."""
     arg_parser = argparse.ArgumentParser()