tests: run some Python tests with CTF 1 and CTF 2 traces
authorSimon Marchi <simon.marchi@efficios.com>
Tue, 24 Oct 2023 02:12:24 +0000 (22:12 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Wed, 4 Sep 2024 19:05:14 +0000 (15:05 -0400)
The goal of this patch is to run as many Python-based tests that use CTF
traces against both CTF 1 and CTF 2.  I would like to minimize the
amount of changes and boilerplate necessary in each individual test.

I've taken the approach to add a decorator named `test_all_ctf_versions`
to the test classes, inspired by the parameterized Python package [1].
Unlike the decorators, from parameterized, our decorator is hardcoded
for what we need.  When applied to a class, it makes two copies of all
`test_*` methods of that class, suffixed with `_ctf_1` and `_ctf_2`, and
removes the original method.  The two new methods are in fact wrapped in
a small wrapper that sets the `_ctf_version` attribute on the class
(with the value 1 or 2) for the duration of the method call.

The changes necessary in the test themselves are to make the test trace
paths dependent on `self._ctf_version`, instead of hard-coded.

[1] https://pypi.org/project/parameterized/

Change-Id: Idef27183db6287630e63260395d255e68da2a4c3
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/8737
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
Tested-by: jenkins <jenkins@lttng.org>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12755

tests/data/ctf-traces/2/intersection/3eventsintersect/metadata [new file with mode: 0644]
tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_0 [new file with mode: 0644]
tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_1 [new file with mode: 0644]
tests/plugins/src.ctf.fs/query/test_query_support_info.py
tests/plugins/src.ctf.fs/query/test_query_trace_info.py
tests/utils/python/test_all_ctf_versions.py [new file with mode: 0644]

diff --git a/tests/data/ctf-traces/2/intersection/3eventsintersect/metadata b/tests/data/ctf-traces/2/intersection/3eventsintersect/metadata
new file mode 100644 (file)
index 0000000..a223ab1
--- /dev/null
@@ -0,0 +1,233 @@
+\1e{
+  "type": "preamble",
+  "uuid": [
+    122,
+    254,
+    143,
+    190,
+    121,
+    184,
+    79,
+    106,
+    187,
+    199,
+    208,
+    199,
+    130,
+    231,
+    221,
+    175
+  ],
+  "version": 2
+}
+\1e{
+  "environment": {
+    "host": "sinkpad"
+  },
+  "packet-header-field-class": {
+    "member-classes": [
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 32,
+          "roles": [
+            "packet-magic-number"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "magic"
+      },
+      {
+        "field-class": {
+          "length": 16,
+          "roles": [
+            "metadata-stream-uuid"
+          ],
+          "type": "static-length-blob"
+        },
+        "name": "uuid"
+      },
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 32,
+          "roles": [
+            "data-stream-class-id"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "stream_id"
+      }
+    ],
+    "minimum-alignment": 8,
+    "type": "structure"
+  },
+  "type": "trace-class",
+  "uid": "7afe8fbe-79b8-4f6a-bbc7-d0c782e7ddaf"
+}
+\1e{
+  "description": "This is a test clock",
+  "frequency": 1000000000,
+  "id": "test_clock",
+  "offset-from-origin": {
+    "cycles": 0,
+    "seconds": 13515309
+  },
+  "origin": "unix-epoch",
+  "precision": 10,
+  "type": "clock-class"
+}
+\1e{
+  "default-clock-class-id": "test_clock",
+  "event-record-header-field-class": {
+    "member-classes": [
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 32,
+          "roles": [
+            "event-record-class-id"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "id"
+      },
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "default-clock-timestamp"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "timestamp"
+      }
+    ],
+    "minimum-alignment": 8,
+    "type": "structure"
+  },
+  "packet-context-field-class": {
+    "member-classes": [
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "default-clock-timestamp"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "timestamp_begin"
+      },
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "packet-end-default-clock-timestamp"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "timestamp_end"
+      },
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "packet-content-length"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "content_size"
+      },
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "packet-total-length"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "packet_size"
+      },
+      {
+        "field-class": {
+          "alignment": 8,
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "discarded-event-record-counter-snapshot"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "events_discarded"
+      },
+      {
+        "field-class": {
+          "byte-order": "big-endian",
+          "length": 64,
+          "roles": [
+            "packet-sequence-number"
+          ],
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "packet_seq_num"
+      }
+    ],
+    "minimum-alignment": 8,
+    "type": "structure"
+  },
+  "type": "data-stream-class"
+}
+\1e{
+  "name": "dummy_event",
+  "payload-field-class": {
+    "member-classes": [
+      {
+        "field-class": {
+          "byte-order": "big-endian",
+          "length": 32,
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "dummy_value"
+      },
+      {
+        "field-class": {
+          "byte-order": "big-endian",
+          "length": 32,
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "tracefile_id"
+      },
+      {
+        "field-class": {
+          "byte-order": "big-endian",
+          "length": 32,
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "packet_begin"
+      },
+      {
+        "field-class": {
+          "byte-order": "big-endian",
+          "length": 32,
+          "type": "fixed-length-unsigned-integer"
+        },
+        "name": "packet_end"
+      }
+    ],
+    "type": "structure"
+  },
+  "type": "event-record-class"
+}
diff --git a/tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_0 b/tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_0
new file mode 100644 (file)
index 0000000..6a69e44
Binary files /dev/null and b/tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_0 differ
diff --git a/tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_1 b/tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_1
new file mode 100644 (file)
index 0000000..0cf1404
Binary files /dev/null and b/tests/data/ctf-traces/2/intersection/3eventsintersect/test_stream_1 differ
index 0bc0a33addccfb57f10cb5abfec50890c9a9fc30..1add093613fc9809c4ac7d4a11611e7947551668 100644 (file)
@@ -7,63 +7,69 @@ import os
 import unittest
 
 import bt2
-
-session_rotation_trace_path = os.path.join(
-    os.environ["BT_CTF_TRACES_PATH"], "1", "succeed", "session-rotation"
-)
-
-
-trace_10352_1 = os.path.join(
-    session_rotation_trace_path,
-    "a",
-    "1",
-    "ust",
-    "pid",
-    "10352",
-)
-trace_10353_1 = os.path.join(
-    session_rotation_trace_path,
-    "a",
-    "1",
-    "ust",
-    "pid",
-    "10353",
-)
-trace_10352_2 = os.path.join(
-    session_rotation_trace_path,
-    "a",
-    "2",
-    "ust",
-    "pid",
-    "10352",
-)
-trace_10353_2 = os.path.join(
-    session_rotation_trace_path,
-    "a",
-    "2",
-    "ust",
-    "pid",
-    "10353",
-)
-trace_10352_3 = os.path.join(
-    session_rotation_trace_path,
-    "3",
-    "ust",
-    "pid",
-    "10352",
-)
-trace_10353_3 = os.path.join(
-    session_rotation_trace_path,
-    "3",
-    "ust",
-    "pid",
-    "10353",
-)
+from test_all_ctf_versions import test_all_ctf_versions
 
 
+@test_all_ctf_versions
 class QuerySupportInfoTestCase(unittest.TestCase):
     def test_support_info_with_uuid(self):
         # Test that the right group is reported for each trace.
+        ctf = bt2.find_plugin("ctf")
+        fs = ctf.source_component_classes["fs"]
+
+        session_rotation_trace_path = os.path.join(
+            os.environ["BT_CTF_TRACES_PATH"],
+            str(self._ctf_version),
+            "succeed",
+            "session-rotation",
+        )
+
+        trace_10352_1 = os.path.join(
+            session_rotation_trace_path,
+            "a",
+            "1",
+            "ust",
+            "pid",
+            "10352",
+        )
+        trace_10353_1 = os.path.join(
+            session_rotation_trace_path,
+            "a",
+            "1",
+            "ust",
+            "pid",
+            "10353",
+        )
+        trace_10352_2 = os.path.join(
+            session_rotation_trace_path,
+            "a",
+            "2",
+            "ust",
+            "pid",
+            "10352",
+        )
+        trace_10353_2 = os.path.join(
+            session_rotation_trace_path,
+            "a",
+            "2",
+            "ust",
+            "pid",
+            "10353",
+        )
+        trace_10352_3 = os.path.join(
+            session_rotation_trace_path,
+            "3",
+            "ust",
+            "pid",
+            "10352",
+        )
+        trace_10353_3 = os.path.join(
+            session_rotation_trace_path,
+            "3",
+            "ust",
+            "pid",
+            "10353",
+        )
 
         def do_one_query(input, expected_group):
             qe = bt2.QueryExecutor(
@@ -73,9 +79,6 @@ class QuerySupportInfoTestCase(unittest.TestCase):
             result = qe.query()
             self.assertEqual(result["group"], expected_group)
 
-        ctf = bt2.find_plugin("ctf")
-        fs = ctf.source_component_classes["fs"]
-
         do_one_query(trace_10352_1, "21cdfa5e-9a64-490a-832c-53aca6c101ba")
         do_one_query(trace_10352_2, "21cdfa5e-9a64-490a-832c-53aca6c101ba")
         do_one_query(trace_10352_3, "21cdfa5e-9a64-490a-832c-53aca6c101ba")
index 1d688013d8f26d37fe14464f6940a589d8c8ae4f..79e5d182ede387ae69c3acf6fb40bf87cd06258f 100644 (file)
@@ -8,6 +8,7 @@ import re
 import unittest
 
 import bt2
+from test_all_ctf_versions import test_all_ctf_versions
 
 test_ctf_traces_path = os.environ["BT_CTF_TRACES_PATH"]
 
@@ -17,13 +18,21 @@ def sort_predictably(stream):
     return stream["port-name"]
 
 
+@test_all_ctf_versions
 class QueryTraceInfoClockOffsetTestCase(unittest.TestCase):
     def setUp(self):
         ctf = bt2.find_plugin("ctf")
         self._fs = ctf.source_component_classes["fs"]
 
-        self._inputs = [
-            os.path.join(test_ctf_traces_path, "1", "intersection", "3eventsintersect")
+    @property
+    def _inputs(self):
+        return [
+            os.path.join(
+                test_ctf_traces_path,
+                str(self._ctf_version),
+                "intersection",
+                "3eventsintersect",
+            )
         ]
 
     def _check(self, trace, offset):
@@ -115,6 +124,7 @@ class QueryTraceInfoClockOffsetTestCase(unittest.TestCase):
             ).query()
 
 
+@test_all_ctf_versions
 class QueryTraceInfoPortNameTestCase(unittest.TestCase):
     def setUp(self):
         ctf = bt2.find_plugin("ctf")
@@ -127,18 +137,20 @@ class QueryTraceInfoPortNameTestCase(unittest.TestCase):
             {
                 "inputs": [
                     os.path.join(
-                        test_ctf_traces_path, "1", "intersection", "3eventsintersect"
+                        test_ctf_traces_path,
+                        str(self._ctf_version),
+                        "intersection",
+                        "3eventsintersect",
                     )
                 ]
             },
         ).query()
 
-        if os.environ["BT_TESTS_OS_TYPE"] == "mingw":
-            os_stream_path = (
-                "\\tests\\data\\ctf-traces\\1\\intersection\\3eventsintersect\\"
-            )
-        else:
-            os_stream_path = "/tests/data/ctf-traces/1/intersection/3eventsintersect/"
+        os_stream_path = (
+            "\\tests\\data\\ctf-traces\\{}\\intersection\\3eventsintersect\\"
+            if os.environ["BT_TESTS_OS_TYPE"] == "mingw"
+            else "/tests/data/ctf-traces/{}/intersection/3eventsintersect/"
+        ).format(self._ctf_version)
 
         self.assertEqual(len(res), 1)
         trace = res[0]
@@ -187,6 +199,7 @@ class QueryTraceInfoPortNameTestCase(unittest.TestCase):
         )
 
 
+@test_all_ctf_versions
 class QueryTraceInfoRangeTestCase(unittest.TestCase):
     def setUp(self):
         ctf = bt2.find_plugin("ctf")
@@ -203,7 +216,12 @@ class QueryTraceInfoRangeTestCase(unittest.TestCase):
             "babeltrace.trace-infos",
             {
                 "inputs": [
-                    os.path.join(test_ctf_traces_path, "1", "succeed", "succeed1")
+                    os.path.join(
+                        test_ctf_traces_path,
+                        str(self._ctf_version),
+                        "succeed",
+                        "succeed1",
+                    )
                 ]
             },
         ).query()
@@ -224,7 +242,7 @@ class QueryTraceInfoRangeTestCase(unittest.TestCase):
                 "inputs": [
                     os.path.join(
                         test_ctf_traces_path,
-                        "1",
+                        str(self._ctf_version),
                         "succeed",
                         "lttng-tracefile-rotation",
                         "kernel",
@@ -255,17 +273,22 @@ class QueryTraceInfoRangeTestCase(unittest.TestCase):
         self.assertEqual(streams[3]["range-ns"]["end"], 1571261797582522088)
 
 
+@test_all_ctf_versions
 class QueryTraceInfoPacketTimestampQuirksTestCase(unittest.TestCase):
     def setUp(self):
         ctf = bt2.find_plugin("ctf")
         self._fs = ctf.source_component_classes["fs"]
-        self._path = os.path.join(test_ctf_traces_path, "1", "succeed")
+
+    def _trace_path(self, trace_name):
+        return os.path.join(
+            test_ctf_traces_path, str(self._ctf_version), "succeed", trace_name
+        )
 
     def _test_lttng_quirks(self, trace_name):
         res = bt2.QueryExecutor(
             self._fs,
             "babeltrace.trace-infos",
-            {"inputs": [os.path.join(self._path, trace_name)]},
+            {"inputs": [self._trace_path(trace_name)]},
         ).query()
 
         self.assertEqual(len(res), 1)
diff --git a/tests/utils/python/test_all_ctf_versions.py b/tests/utils/python/test_all_ctf_versions.py
new file mode 100644 (file)
index 0000000..cc6fdd0
--- /dev/null
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2022 EfficiOS, Inc.
+#
+
+
+# Decorator for unittest.TestCast sub-classes to run tests against CTF 1 and
+# CTF 2 versions of the same traces.
+#
+# Replaces all test_* methods with a test_*_ctf_1 and a test_*_ctf_2 variant.
+#
+# For instance, it transforms this:
+#
+#   @test_all_ctf_versions
+#   class MyTestCase(unittest.TestCase):
+#       test_something(self):
+#           pass
+#
+# into:
+#
+#   class MyTestcase(unittest.TestCase):
+#       test_something_ctf_1(self):
+#           pass
+#
+#       test_something_ctf_2(self):
+#           pass
+#
+# The test methods are wrapped such that the self._ctf_version attribute is
+# set to either 1 or 2 during the call to each method.
+def test_all_ctf_versions(cls):
+    for attr_name, attr_value in list(cls.__dict__.items()):
+        if not attr_name.startswith("test_") or not callable(attr_value):
+            continue
+
+        for ctf_version in 1, 2:
+            # Callable that wraps and replaces test methods in order to
+            # temporarily set the _ctf_version attribute on the TestCase class.
+            def set_ctf_version_wrapper_method(self, ctf_version, test_method):
+                assert not hasattr(self, "_ctf_version")
+                self._ctf_version = ctf_version
+
+                try:
+                    return test_method(self)
+                finally:
+                    assert hasattr(self, "_ctf_version")
+                    del self._ctf_version
+
+            def wrap_method(wrapper_method, ctf_version, original_method):
+                return lambda self: wrapper_method(self, ctf_version, original_method)
+
+            setattr(
+                cls,
+                "{}_ctf_{}".format(attr_name, ctf_version),
+                wrap_method(set_ctf_version_wrapper_method, ctf_version, attr_value),
+            )
+
+        delattr(cls, attr_name)
+
+    return cls
This page took 0.031968 seconds and 4 git commands to generate.