Repository: smarr/ReBench

Issue:
`ReBenchLog` gauge adapter doesn't work on case-sensitive filesystems
ReBench's custom benchmark log format is defined in [interop/rebench_log_adapter.py](https://github.com/smarr/ReBench/blob/08c736b78808a67189925fd31484647926341d8c/rebench/interop/rebench_log_adapter.py), in the class `RebenchLogAdapter`. The corresponding `benchmark_suites.${benchmark}.gauge_adapter` value is defined in [executor.py](https://github.com/smarr/ReBench/blob/08c736b78808a67189925fd31484647926341d8c/rebench/executor.py#L449):

https://github.com/smarr/ReBench/blob/08c736b78808a67189925fd31484647926341d8c/rebench/executor.py#L446-L449

So the correct value is `RebenchLog`. However, the documentation and code reference `ReBenchLog` a number of times. On my Linux-based system, that fails to find the gauge adapter. I hazard that it would work on Windows (but probably not MacOS?).

In particular, [`docs/extensions.md`](https://github.com/smarr/ReBench/blob/08c736b78808a67189925fd31484647926341d8c/docs/extensions.md) states the wrong value.

https://github.com/smarr/ReBench/blob/08c736b78808a67189925fd31484647926341d8c/docs/extensions.md#L8-L17

Comments:

Patch:
diff --git a/docs/extensions.md b/docs/extensions.md
index 2642d2d..a0a17fa 100644
--- a/docs/extensions.md
+++ b/docs/extensions.md
@@ -16,6 +16,7 @@ ReBench currently provides builtin support for the following benchmark harnesses
 - `ValidationLog`: the format used by [SOMns](https://github.com/smarr/SOMns)'s ImpactHarness
 - `Time`: a harness that uses `/usr/bin/time` automatically
 
+
 ### `PlainSecondsLog`
 
 This adapter attempts to read every line of program output as a millisecond
@@ -35,6 +36,7 @@ Implementation Notes:
 
  - Python's `float()` function is used for parsing
 
+
 ### `ReBenchLog`
 
 The ReBenchLog parser is the most commonly used and has most features.
@@ -80,6 +82,24 @@ Implementation Notes:
    the following regular expression should match
    `r"^(?:.*: )?([^\s]+): ([^:]{1,30}):\s*([0-9]+)([a-zA-Z]+)")`
 
+
+## `Time`
+
+The `Time` adapter uses Unix's `/usr/bin/time` command.
+On Linux, or more generally the platforms that support it, it will also use the
+`-f` switch of the `time` command to record the maximum resident set size,
+i.e., the maximum amount of memory the program used.
+
+Example configuration for a suite:
+
+```yaml
+    Suite:
+        gauge_adapter: Time
+        benchmarks:
+          - Bench1
+```
+
+
 ## Supporting other Benchmark Harnesses
 
 To add support for your own harness, check the `rebench.interop` module.
diff --git a/rebench/executor.py b/rebench/executor.py
index 42d0a2b..58651e9 100644
--- a/rebench/executor.py
+++ b/rebench/executor.py
@@ -24,15 +24,13 @@ from collections import deque
 from math import floor
 from multiprocessing import cpu_count
 import os
-import pkgutil
 import random
 import subprocess
-import sys
 from threading import Thread, RLock
 from time import time
 
 from . import subprocess_with_timeout as subprocess_timeout
-from .interop.adapter import ExecutionDeliveredNoResults
+from .interop.adapter import ExecutionDeliveredNoResults, instantiate_adapter
 from .ui import escape_braces
 
 
@@ -444,22 +442,7 @@ class Executor(object):
         return terminate
 
     def _get_gauge_adapter_instance(self, adapter_name):
-        adapter_name += "Adapter"
-
-        root = sys.modules['rebench.interop'].__path__
-
-        for _, name, _ in pkgutil.walk_packages(root):
-            # depending on how ReBench was executed, name might one of the two
-            try:
-                mod = __import__("rebench.interop." + name, fromlist=adapter_name)
-            except ImportError:
-                try:
-                    mod = __import__("interop." + name, fromlist=adapter_name)
-                except ImportError:
-                    mod = None
-            if mod is not None and hasattr(mod, adapter_name):
-                return getattr(mod, adapter_name)(self._include_faulty, self)
-        return None
+        return instantiate_adapter(adapter_name, self._include_faulty, self)
 
     def _generate_data_point(self, cmdline, gauge_adapter, run_id,
                              termination_check):
diff --git a/rebench/interop/adapter.py b/rebench/interop/adapter.py
index 8b54b54..c1097fa 100644
--- a/rebench/interop/adapter.py
+++ b/rebench/interop/adapter.py
@@ -18,6 +18,8 @@
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 # THE SOFTWARE.
 import re
+import pkgutil
+import sys
 
 
 class GaugeAdapter(object):
@@ -78,3 +80,24 @@ class OutputNotParseable(ExecutionDeliveredNoResults):
 
 class ResultsIndicatedAsInvalid(ExecutionDeliveredNoResults):
     pass
+
+
+def instantiate_adapter(name, include_faulty, executor):
+    adapter_name = name + "Adapter"
+    root = sys.modules['rebench.interop'].__path__
+
+    for _, module_name, _ in pkgutil.walk_packages(root):
+        # depending on how ReBench was executed, name might one of the two
+        try:
+            mod = __import__("rebench.interop." + module_name, fromlist=adapter_name)
+        except ImportError:
+            try:
+                mod = __import__("interop." + module_name, fromlist=adapter_name)
+            except ImportError:
+                mod = None
+        if mod is not None:
+            for key in dir(mod):
+                if key.lower() == adapter_name.lower():
+                    return getattr(mod, key)(include_faulty, executor)
+
+    return None
diff --git a/rebench/interop/rebench_log_adapter.py b/rebench/interop/rebench_log_adapter.py
index ef73ed1..d06e767 100644
--- a/rebench/interop/rebench_log_adapter.py
+++ b/rebench/interop/rebench_log_adapter.py
@@ -30,6 +30,8 @@ class RebenchLogAdapter(GaugeAdapter):
     """RebenchLogPerformance is the standard log parser of ReBench.
        It reads a simple log format, which includes the number of iterations of
        a benchmark and its runtime in microseconds.
+
+       Note: regular expressions are documented in /docs/extensions.md
     """
     re_log_line = re.compile(
         r"^(?:.*: )?([^\s]+)( [\w\.]+)?: iterations=([0-9]+) runtime: ([0-9]+)([mu])s")


Test patch:
diff --git a/rebench/tests/interop/adapter_test.py b/rebench/tests/interop/adapter_test.py
new file mode 100644
index 0000000..81c4567
--- /dev/null
+++ b/rebench/tests/interop/adapter_test.py
@@ -0,0 +1,22 @@
+from unittest import TestCase
+from ...interop.adapter import instantiate_adapter
+
+
+class AdapterTest(TestCase):
+    def test_load_all_known_adapters(self):
+        self.assertIsNotNone(instantiate_adapter("JMH", False, None))
+        self.assertIsNotNone(instantiate_adapter("Multivariate", False, None))
+        self.assertIsNotNone(instantiate_adapter("Perf", False, None))
+        self.assertIsNotNone(instantiate_adapter("PlainSecondsLog", False, None))
+        self.assertIsNotNone(instantiate_adapter("RebenchLog", False, None))
+        self.assertIsNotNone(instantiate_adapter("SavinaLog", False, None))
+        self.assertIsNotNone(instantiate_adapter("Test", False, None))
+        self.assertIsNotNone(instantiate_adapter("TestExecutor", False, None))
+        self.assertIsNotNone(instantiate_adapter("Time", False, None))
+        self.assertIsNotNone(instantiate_adapter("ValidationLog", False, None))
+
+    def test_case_insensitive_names(self):
+        self.assertIsNotNone(instantiate_adapter("RebenchLog", False, None))
+        self.assertIsNotNone(instantiate_adapter("ReBenchLog", False, None))
+        self.assertIsNotNone(instantiate_adapter("rebenchlog", False, None))
+        self.assertIsNotNone(instantiate_adapter("REBENCHLOG", False, None))