Skip to content

Commit 352a821

Browse files
authored
Merge branch 'master' into push_to_pypi
2 parents e17b7d5 + 0aea2c8 commit 352a821

8 files changed

Lines changed: 263 additions & 5 deletions

File tree

CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ Python_add_library(_bindings
2020
src/methods.cpp src/scorep_bindings.cpp src/scorepy/events.cpp
2121
)
2222
if(Python_VERSION_MAJOR GREATER_EQUAL 3)
23-
target_sources(_bindings PRIVATE src/classes.cpp src/scorepy/cInstrumenter.cpp src/scorepy/pythonHelpers.cpp)
23+
target_sources(_bindings PRIVATE
24+
src/classes.cpp
25+
src/scorepy/cInstrumenter.cpp
26+
src/scorepy/pathUtils.cpp
27+
src/scorepy/pythonHelpers.cpp
28+
)
2429
endif()
2530
target_link_libraries(_bindings PRIVATE Scorep::Plugin)
2631
target_compile_features(_bindings PRIVATE cxx_std_11)
@@ -45,6 +50,9 @@ if(ENV{PYTHONPATH})
4550
string(PREPEND pythonPath "$ENV{PYTHONPATH}:")
4651
endif()
4752
set_tests_properties(ScorepPythonTests PROPERTIES ENVIRONMENT "PYTHONPATH=${pythonPath}")
53+
add_executable(CppTests test/test_pathUtils.cpp src/scorepy/pathUtils.cpp)
54+
target_include_directories(CppTests PRIVATE src)
55+
add_test(NAME CppTests COMMAND CppTests)
4856

4957
set(INSTALL_DIR "lib/python${Python_VERSION_MAJOR}.${Python_VERSION_MINOR}/site-packages")
5058

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@
2626
include += [src_folder]
2727
sources = ['src/methods.cpp', 'src/scorep_bindings.cpp', 'src/scorepy/events.cpp']
2828
if sys.version_info.major >= 3:
29-
sources.extend(['src/classes.cpp', 'src/scorepy/cInstrumenter.cpp', 'src/scorepy/pythonHelpers.cpp'])
29+
sources.extend([
30+
'src/classes.cpp',
31+
'src/scorepy/cInstrumenter.cpp',
32+
'src/scorepy/pythonHelpers.cpp',
33+
'src/scorepy/pathUtils.cpp',
34+
])
3035

3136
cmodules.append(Extension('scorep._bindings',
3237
include_dirs=include,

src/scorepy/pathUtils.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include "pathUtils.hpp"
2+
#include <cerrno>
3+
#include <limits>
4+
#include <unistd.h>
5+
6+
namespace scorepy
7+
{
8+
static std::string getcwd()
9+
{
10+
std::string result;
11+
const char* cwd;
12+
constexpr size_t chunk_size = 512;
13+
do
14+
{
15+
const size_t new_size = result.size() + chunk_size;
16+
if (new_size > std::numeric_limits<int>::max())
17+
{
18+
cwd = nullptr;
19+
break;
20+
}
21+
result.resize(new_size);
22+
cwd = ::getcwd(&result.front(), result.size());
23+
} while (!cwd && errno == ERANGE);
24+
if (cwd)
25+
result.resize(result.find('\0', result.size() - chunk_size));
26+
else
27+
result.clear();
28+
return result;
29+
}
30+
31+
void normalize_path(std::string& path)
32+
{
33+
// 2 slashes are kept, 1 or more than 2 become 1 according to POSIX
34+
const size_t num_slashes = (path.find_first_not_of('/') == 2) ? 2 : 1;
35+
const size_t path_len = path.size();
36+
size_t cur_out = num_slashes;
37+
for (size_t i = cur_out; i != path_len + 1; ++i)
38+
{
39+
if (i == path_len || path[i] == '/')
40+
{
41+
const char prior = path[cur_out - 1];
42+
if (prior == '/') // Double slash -> ignore
43+
continue;
44+
if (prior == '.')
45+
{
46+
const char second_prior = path[cur_out - 2];
47+
if (second_prior == '/') // '/./'
48+
{
49+
--cur_out;
50+
continue;
51+
}
52+
else if (second_prior == '.' && path[cur_out - 3] == '/') // '/../'
53+
{
54+
if (cur_out < 3 + num_slashes) // already behind root slash
55+
cur_out -= 2;
56+
else
57+
{
58+
const auto prior_slash = path.rfind('/', cur_out - 4);
59+
cur_out = prior_slash + 1;
60+
}
61+
continue;
62+
}
63+
}
64+
if (i == path_len)
65+
break;
66+
}
67+
path[cur_out++] = path[i];
68+
}
69+
// Remove trailing slash
70+
if (cur_out > num_slashes && path[cur_out - 1] == '/')
71+
--cur_out;
72+
path.resize(cur_out);
73+
}
74+
75+
std::string abspath(const char* input_path)
76+
{
77+
std::string result;
78+
if (input_path[0] != '/')
79+
{
80+
result = getcwd();
81+
// On error exit
82+
if (result.empty())
83+
return result;
84+
// Prepend CWD
85+
result.append(1, '/').append(input_path);
86+
}
87+
else
88+
result = input_path;
89+
normalize_path(result);
90+
return result;
91+
}
92+
} // namespace scorepy

src/scorepy/pathUtils.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#pragma once
2+
3+
#include <string>
4+
5+
namespace scorepy
6+
{
7+
/// A//B, A/./B and A/foo/../B all become A/B
8+
/// Assumes an absolute, non-empty path
9+
void normalize_path(std::string& path);
10+
/// Makes the path absolute and normalized, see Python os.path.abspath
11+
std::string abspath(const char* input_path);
12+
} // namespace scorepy

src/scorepy/pythonHelpers.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "pythonHelpers.hpp"
2+
#include "pathUtils.hpp"
23
#include <stdlib.h>
34

45
namespace scorepy
@@ -25,8 +26,7 @@ std::string get_file_name(const PyFrameObject& frame)
2526
{
2627
return "None";
2728
}
28-
char actual_path[PATH_MAX];
29-
const char* full_file_name = PyUnicode_AsUTF8(filename);
30-
return full_file_name ? full_file_name : "ErrorPath";
29+
const std::string full_file_name = abspath(PyUnicode_AsUTF8(filename));
30+
return !full_file_name.empty() ? full_file_name : "ErrorPath";
3131
}
3232
} // namespace scorepy

test/cases/io.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import os
2+
import scorep.instrumenter
3+
4+
with scorep.instrumenter.enable("expect io"):
5+
with open("test.txt", "w") as f:
6+
f.write("test")
7+
8+
with open("test.txt", "r") as f:
9+
data = f.read()
10+
print(data)
11+
12+
os.remove("test.txt")

test/test_pathUtils.cpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#include "scorepy/pathUtils.hpp"
2+
#include <iostream>
3+
#include <stdexcept>
4+
5+
#define TEST(condition) \
6+
if (!(condition)) \
7+
throw std::runtime_error(std::string("Test ") + #condition + " failed at " + __FILE__ + ":" + \
8+
std::to_string(__LINE__))
9+
10+
int main()
11+
{
12+
using scorepy::abspath;
13+
// Multiple slashes at start collapsed to 1
14+
TEST(abspath("/abc") == "/abc");
15+
TEST(abspath("//abc") == "//abc");
16+
TEST(abspath("///abc") == "/abc");
17+
TEST(abspath("////abc") == "/abc");
18+
// Trailing slashes and multiple slashes removed
19+
TEST(abspath("/abc/") == "/abc");
20+
TEST(abspath("/abc//") == "/abc");
21+
TEST(abspath("/abc//de") == "/abc/de");
22+
TEST(abspath("/abc//de///fg") == "/abc/de/fg");
23+
TEST(abspath("/abc//de///fg/") == "/abc/de/fg");
24+
TEST(abspath("/abc//de///fg////") == "/abc/de/fg");
25+
TEST(abspath("//abc/") == "//abc");
26+
TEST(abspath("//abc//") == "//abc");
27+
TEST(abspath("//abc//de") == "//abc/de");
28+
TEST(abspath("//abc//de///fg") == "//abc/de/fg");
29+
TEST(abspath("//abc//de///fg/") == "//abc/de/fg");
30+
TEST(abspath("//abc//de///fg////") == "//abc/de/fg");
31+
// Single dots removed
32+
TEST(abspath("/./abc/./defgh/./ijkl/.") == "/abc/defgh/ijkl");
33+
TEST(abspath("/./abc././def.gh/./ijkl././.mn/.") == "/abc./def.gh/ijkl./.mn");
34+
// Going up 1 level removes prior folder
35+
TEST(abspath("/abc/..") == "/");
36+
TEST(abspath("//abc/..") == "//");
37+
TEST(abspath("///abc/..") == "/");
38+
TEST(abspath("/abc/../de") == "/de");
39+
TEST(abspath("//abc/../de") == "//de");
40+
TEST(abspath("///abc/../de") == "/de");
41+
TEST(abspath("/abc/de/../fg") == "/abc/fg");
42+
TEST(abspath("/abc/de/../../fg") == "/fg");
43+
TEST(abspath("/abc/de../../fg") == "/abc/fg");
44+
TEST(abspath("/abc../de/../fg") == "/abc../fg");
45+
// Going up from root does nothing
46+
TEST(abspath("/../ab") == "/ab");
47+
TEST(abspath("//../ab") == "//ab");
48+
TEST(abspath("///../ab") == "/ab");
49+
TEST(abspath("/abc/defgh/../../ijkl") == "/ijkl");
50+
TEST(abspath("//abc/defgh/../../ijkl") == "//ijkl");
51+
TEST(abspath("///abc/defgh/../../ijkl") == "/ijkl");
52+
}

test/test_scorep.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,3 +439,80 @@ def test_threads(scorep_env, instrumenter):
439439
assert re.search(
440440
'%s[ ]*[0-9 ]*[0-9 ]*Region: "%s"' % (event, func), std_out
441441
)
442+
443+
@pytest.mark.skipif(sys.version_info.major < 3, reason="not tested for python 2")
444+
@foreach_instrumenter
445+
def test_io(scorep_env, instrumenter):
446+
trace_path = get_trace_path(scorep_env)
447+
448+
print("start")
449+
std_out, std_err = call_with_scorep(
450+
"cases/io.py",
451+
[
452+
"--nocompiler",
453+
"--instrumenter-type=" + instrumenter,
454+
"--noinstrumenter",
455+
"--io=runtime:posix",
456+
],
457+
env=scorep_env,
458+
)
459+
460+
assert std_err == ""
461+
assert "test\n" in std_out
462+
463+
print("otf2-print")
464+
std_out, std_err = call(["otf2-print", trace_path])
465+
466+
assert std_err == ""
467+
468+
file_regex = "\\[POSIX I\\/O\\][ \\w:/]*test\\.txt"
469+
# print_regex = "STDOUT_FILENO"
470+
471+
ops = {
472+
"open": {"ENTER": "open64", "IO_CREATE_HANDLE": file_regex, "LEAVE": "open64"},
473+
"seek": {"ENTER": "lseek64", "IO_SEEK": file_regex, "LEAVE": "lseek64"},
474+
"write": {
475+
"ENTER": "write",
476+
"IO_OPERATION_BEGIN": file_regex,
477+
"IO_OPERATION_COMPLETE": file_regex,
478+
"LEAVE": "write",
479+
},
480+
"read": {
481+
"ENTER": "read",
482+
"IO_OPERATION_BEGIN": file_regex,
483+
"IO_OPERATION_COMPLETE": file_regex,
484+
"LEAVE": "read",
485+
},
486+
# for some reason there is no print in pytest
487+
# "print": {
488+
# "ENTER": "read",
489+
# "IO_OPERATION_BEGIN": print_regex,
490+
# "IO_OPERATION_COMPLETE": print_regex,
491+
# "LEAVE": "read",
492+
# },
493+
"close": {"ENTER": "close", "IO_DESTROY_HANDLE": file_regex, "LEAVE": "close"},
494+
}
495+
496+
io_trace = ""
497+
io_trace_after = ""
498+
in_expected_io = False
499+
after_expected_io = False
500+
501+
for line in std_out.split("\n"):
502+
if ("user_instrumenter:expect io" in line) and (in_expected_io is False):
503+
in_expected_io = True
504+
elif ("user_instrumenter:expect io" in line) and (in_expected_io is True):
505+
in_expected_io = False
506+
after_expected_io = True
507+
if in_expected_io:
508+
io_trace += line + "\n"
509+
if after_expected_io:
510+
io_trace_after += line + "\n"
511+
512+
for _, details in ops.items():
513+
for event, data in details.items():
514+
regex_str = '{event:}[ ]*[0-9 ]*[0-9 ]*(Region|Handle): "{data:}"'.format(
515+
event=event, data=data
516+
)
517+
print(regex_str)
518+
assert re.search(regex_str, io_trace)

0 commit comments

Comments
 (0)