diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 4eb284e..870df28 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -38,7 +38,7 @@ jobs: run: | pip install --upgrade pip pip install --upgrade setuptools - pip install numpy mpi4py pytest + pip install numpy mpi4py pytest otf2 - name: Build python bindings run: pip install . diff --git a/test/test_scorep.py b/test/test_scorep.py index 228c8de..7549a26 100755 --- a/test/test_scorep.py +++ b/test/test_scorep.py @@ -8,8 +8,20 @@ import sys import numpy +import otf2 +from otf2.enums import MeasurementMode +from otf2.events import ( + Enter, + IoCreateHandle, + IoDestroyHandle, + IoOperationBegin, + IoOperationComplete, + IoSeek, + Leave, + ParameterString, +) + import utils -from utils import OTF2_Trace, OTF2_Region, OTF2_Parameter def version_tuple(v): @@ -91,11 +103,11 @@ def test_user_regions(scorep_env, instrumenter): == "hello world\nhello world\nhello world3\nhello world3\nhello world4\n" ) - trace = OTF2_Trace(trace_path) - assert OTF2_Region("user:test_region") in trace - assert OTF2_Region("user:test_region_2") in trace - assert len(trace.findall(OTF2_Region("__main__:foo3"))) == 4 - assert OTF2_Region("user:test_region_4") in trace + region_names = utils.get_region_names(trace_path) + assert "user:test_region" in region_names + assert "user:test_region_2" in region_names + assert len(utils.findall_regions(trace_path, "__main__:foo3")) == 4 + assert "user:test_region_4" in region_names @foreach_instrumenter @@ -111,9 +123,9 @@ def test_context(scorep_env, instrumenter): assert std_err == "" assert std_out == "hello world\nhello world\nhello world\n" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("user:test_region") in trace - assert OTF2_Region("__main__:foo") in trace + region_names = utils.get_region_names(trace_path) + assert "user:test_region" in region_names + assert "__main__:foo" in region_names @foreach_instrumenter @@ -129,8 +141,7 @@ def test_decorator(scorep_env, instrumenter): assert std_err == "" assert std_out == "hello world\nhello world\nhello world\n" - trace = OTF2_Trace(trace_path) - assert len(trace.findall(OTF2_Region("__main__:foo"))) == 6 + assert len(utils.findall_regions(trace_path, "__main__:foo")) == 6 def test_user_regions_no_scorep(): @@ -154,9 +165,18 @@ def test_user_rewind(scorep_env, instrumenter): assert std_err == "" assert std_out == "hello world\nhello world\n" - trace = OTF2_Trace(trace_path) - assert re.search("MEASUREMENT_ON_OFF[ ]*[0-9 ]*[0-9 ]*Mode: OFF", str(trace)) - assert re.search("MEASUREMENT_ON_OFF[ ]*[0-9 ]*[0-9 ]*Mode: ON", str(trace)) + measurement_on = False + measurement_off = False + with otf2.reader.open(trace_path) as trace: + for _, event in trace.events: + if isinstance(event, otf2.events.MeasurementOnOff): + if event.measurement_mode == MeasurementMode.ON: + measurement_on = True + elif event.measurement_mode == MeasurementMode.OFF: + measurement_off = True + + assert measurement_on + assert measurement_off @pytest.mark.parametrize("instrumenter", ALL_INSTRUMENTERS + [None]) @@ -172,10 +192,10 @@ def test_instrumentation(scorep_env, instrumenter): assert std_err == "" assert std_out == "hello world\nbaz\nbar\n" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("__main__:foo") in trace - assert OTF2_Region("instrumentation2:bar") in trace - assert OTF2_Region("instrumentation2:baz") in trace + region_names = utils.get_region_names(trace_path) + assert "__main__:foo" in region_names + assert "instrumentation2:bar" in region_names + assert "instrumentation2:baz" in region_names @foreach_instrumenter @@ -191,11 +211,11 @@ def test_user_instrumentation(scorep_env, instrumenter): assert std_err == "" assert std_out == "hello world\nbar\nhello world2\nbaz\n" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("__main__:foo") in trace - assert OTF2_Region("__main__:foo2") in trace - assert OTF2_Region("instrumentation2:bar") in trace - assert OTF2_Region("instrumentation2:baz") in trace + region_names = utils.get_region_names(trace_path) + assert "__main__:foo" in region_names + assert "__main__:foo2" in region_names + assert "instrumentation2:bar" in region_names + assert "instrumentation2:baz" in region_names @foreach_instrumenter @@ -212,9 +232,10 @@ def test_external_user_instrumentation(scorep_env, instrumenter): assert std_err == "" assert std_out == "hello world\nbaz\nbar\n" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("instrumentation2:bar") in trace - assert OTF2_Region("instrumentation2:baz") in trace + region_names = utils.get_region_names(trace_path) + assert "__main__:foo" not in region_names + assert "instrumentation2:bar" in region_names + assert "instrumentation2:baz" in region_names @foreach_instrumenter @@ -234,10 +255,20 @@ def test_error_region(scorep_env, instrumenter): ) assert std_out == "" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("error_region") in trace - assert OTF2_Parameter("leave-region", "user:test_region") in trace - assert OTF2_Parameter("leave-region", "user:test_region_2") in trace + region_names = utils.get_region_names(trace_path) + with otf2.reader.open(trace_path) as trace: + parameters = [event for _, event in trace.events if isinstance(event, ParameterString)] + assert "error_region" in region_names + assert any( + param for param in parameters + if param.parameter.name == "leave-region" + and param.string == "user:test_region" + ) + assert any( + param for param in parameters + if param.parameter.name == "leave-region" + and param.string == "user:test_region_2" + ) @requires_package("mpi4py") @@ -271,9 +302,9 @@ def test_mpi(scorep_env, instrumenter): assert "bar\n" in std_out assert "baz\n" in std_out - trace = OTF2_Trace(trace_path) - assert OTF2_Region("instrumentation2:bar") in trace - assert OTF2_Region("instrumentation2:baz") in trace + region_names = utils.get_region_names(trace_path) + assert "instrumentation2:bar" in region_names + assert "instrumentation2:baz" in region_names @foreach_instrumenter @@ -307,11 +338,10 @@ def test_classes(scorep_env, instrumenter): assert std_out == expected_std_out assert std_err == expected_std_err - trace = OTF2_Trace(trace_path) - - assert OTF2_Region("__main__:foo") in trace - assert OTF2_Region("__main__.TestClass:foo") in trace - assert OTF2_Region("__main__.TestClass2:foo") in trace + region_names = utils.get_region_names(trace_path) + assert "__main__:foo" in region_names + assert "__main__.TestClass:foo" in region_names + assert "__main__.TestClass2:foo" in region_names def test_dummy(scorep_env): @@ -344,8 +374,8 @@ def test_numpy_dot(scorep_env, instrumenter): assert std_out == "[[ 7 10]\n [15 22]]\n" assert std_err == "" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("numpy.__array_function__:dot") in trace + region_names = utils.get_region_names(trace_path) + assert "numpy.__array_function__:dot" in region_names @foreach_instrumenter @@ -365,10 +395,10 @@ def test_threads(scorep_env, instrumenter): assert "bar\n" in std_out assert "baz\n" in std_out - trace = OTF2_Trace(trace_path) - assert OTF2_Region("__main__:foo") in trace - assert OTF2_Region("instrumentation2:bar") in trace - assert OTF2_Region("instrumentation2:baz") in trace + region_names = utils.get_region_names(trace_path) + assert "__main__:foo" in region_names + assert "instrumentation2:bar" in region_names + assert "instrumentation2:baz" in region_names @pytest.mark.skipif(sys.version_info.major < 3, reason="not tested for python 2") @@ -377,7 +407,6 @@ def test_io(scorep_env, instrumenter): trace_path = get_trace_path(scorep_env) scorep_env["SCOREP_IO_POSIX"] = "true" - print("start") std_out, std_err = utils.call_with_scorep( "cases/file_io.py", [ @@ -391,26 +420,24 @@ def test_io(scorep_env, instrumenter): assert std_err == "" assert "test\n" in std_out - trace = utils.OTF2_Trace(trace_path) - - file_regex = "\\[POSIX I\\/O\\][ \\w:/]*test\\.txt" + file_regex = "[\\w:/]*test\\.txt" # print_regex = "STDOUT_FILENO" ops = { # CPython calls "int open64( const char*, int, ... )" but PyPy calls "int open( const char*, int, ... )" - "open": {"ENTER": "open(64)?", "IO_CREATE_HANDLE": file_regex, "LEAVE": "open(64)?"}, - "seek": {"ENTER": "lseek(64)?", "IO_SEEK": file_regex, "LEAVE": "lseek(64)?"}, + "open": {Enter: "open(64)?", IoCreateHandle: file_regex, Leave: "open(64)?"}, + "seek": {Enter: "lseek(64)?", IoSeek: file_regex, Leave: "lseek(64)?"}, "write": { - "ENTER": "write", - "IO_OPERATION_BEGIN": file_regex, - "IO_OPERATION_COMPLETE": file_regex, - "LEAVE": "write", + Enter: "write", + IoOperationBegin: file_regex, + IoOperationComplete: file_regex, + Leave: "write", }, "read": { - "ENTER": "read", - "IO_OPERATION_BEGIN": file_regex, - "IO_OPERATION_COMPLETE": file_regex, - "LEAVE": "read", + Enter: "read", + IoOperationBegin: file_regex, + IoOperationComplete: file_regex, + Leave: "read", }, # for some reason there is no print in pytest # "print": { @@ -419,32 +446,44 @@ def test_io(scorep_env, instrumenter): # "IO_OPERATION_COMPLETE": print_regex, # "LEAVE": "read", # }, - "close": {"ENTER": "close", "IO_DESTROY_HANDLE": file_regex, "LEAVE": "close"}, + "close": {Enter: "close", IoDestroyHandle: file_regex, Leave: "close"}, } - io_trace = "" - io_trace_after = "" + io_trace = [] + io_trace_after = [] in_expected_io = False after_expected_io = False - for line in str(trace).split("\n"): - if ("user_instrumenter:expect io" in line) and (in_expected_io is False): - in_expected_io = True - elif ("user_instrumenter:expect io" in line) and (in_expected_io is True): - in_expected_io = False - after_expected_io = True - if in_expected_io: - io_trace += line + "\n" - if after_expected_io: - io_trace_after += line + "\n" - - for _, details in ops.items(): - for event, data in details.items(): - regex_str = '{event:}[ ]*[0-9 ]*[0-9 ]*(Region|Handle): ".*{data:}.*"'.format( - event=event, data=data - ) - print(regex_str) - assert re.search(regex_str, io_trace) + with otf2.reader.open(trace_path) as trace: + for _, event in trace.events: + if isinstance(event, (Enter, Leave)) and event.region.name == "user_instrumenter:expect io": + if not in_expected_io: + in_expected_io = True + continue + else: + in_expected_io = False + after_expected_io = True + continue + + if in_expected_io: + io_trace.append(event) + elif after_expected_io: + io_trace_after.append(event) + + for _, events in ops.items(): + for event_type, pattern in events.items(): + if issubclass(event_type, (Enter, Leave)): + assert any( + isinstance(e, event_type) and re.search(pattern, e.region.canonical_name) + for e in io_trace + ) + + elif issubclass(event_type, ( + IoCreateHandle, IoSeek, IoOperationBegin, IoOperationComplete, IoDestroyHandle)): + assert any( + isinstance(e, event_type) and re.search(pattern, e.handle.file.name) + for e in io_trace + ) @foreach_instrumenter @@ -460,6 +499,6 @@ def test_force_finalize(scorep_env, instrumenter): assert std_err == "" assert std_out == "foo\nbar\n" - trace = OTF2_Trace(trace_path) - assert OTF2_Region("__main__:foo") in trace - assert OTF2_Region("__main__:bar") not in trace + region_names = utils.get_region_names(trace_path) + assert "__main__:foo" in region_names + assert "__main__:bar" not in region_names diff --git a/test/utils.py b/test/utils.py index a5bead8..bab7b57 100644 --- a/test/utils.py +++ b/test/utils.py @@ -1,6 +1,6 @@ import sys import subprocess -import re +import otf2 def call(arguments, expected_returncode=0, env=None): @@ -43,62 +43,16 @@ def call_with_scorep(file, scorep_arguments=None, expected_returncode=0, env=Non return call(arguments + [str(file)], expected_returncode=expected_returncode, env=env) -def call_otf2_print(trace_path): - trace, std_err = call(["otf2-print", str(trace_path)]) - return trace, std_err +def get_region_names(trace_path): + with otf2.reader.open(trace_path) as trace: + return [region.name for region in trace.definitions.regions] -class OTF2_Region: - def __init__(self, region): - self.region = region - - def __str__(self): - return self.region - - -class OTF2_Parameter: - def __init__(self, parameter, value): - self.parameter = parameter - self.value = value - - def __str__(self): - return "{}:{}".format(self.parameter, self.value) - - -class OTF2_Trace: - def __init__(self, trace_path): - self.path = trace_path - self.trace, self.std_err = call_otf2_print(self.path) - assert self.std_err == "" - - def __contains__(self, otf2_element): - result = [] - if isinstance(otf2_element, OTF2_Region): - for event in ("ENTER", "LEAVE"): - search_str = "{event}[ ]*[0-9 ]*[0-9 ]*Region: \"{region}\"".format( - event=event, region=otf2_element.region) - search_res = re.search(search_str, self.trace) - result.append(search_res is not None) - elif isinstance(otf2_element, OTF2_Parameter): - search_str = "PARAMETER_STRING[ ]*[0-9 ]*[0-9 ]*Parameter: \"{parameter}\" <[0-9]*>, Value: \"{value}\"" - search_str = search_str.format(parameter=otf2_element.parameter, value=otf2_element.value) - search_res = re.search(search_str, self.trace) - result.append(search_res is not None) - else: - raise NotImplementedError - return all(result) - - def findall(self, otf2_element): - result = [] - if isinstance(otf2_element, OTF2_Region): - for event in ("ENTER", "LEAVE"): - search_str = "{event}[ ]*[0-9 ]*[0-9 ]*Region: \"{region}\"".format( - event=event, region=otf2_element.region) - search_res = re.findall(search_str, self.trace) - result.extend(search_res) - else: - raise NotImplementedError - return result - - def __str__(self): - return self.trace +def findall_regions(trace_path, region_name): + with otf2.reader.open(trace_path) as trace: + return [ + (location, event) + for location, event in trace.events + if isinstance(event, (otf2.events.Enter, otf2.events.Leave)) + and event.region.name == region_name + ]