Skip to content

Commit 92f736b

Browse files
committed
Add some tuple pbt tests
1 parent 4aec31e commit 92f736b

File tree

6 files changed

+338
-5
lines changed

6 files changed

+338
-5
lines changed

.github/workflows/unit_tests.yml

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ jobs:
9898
- name: Install build tools
9999
run: |
100100
${{ matrix.install }}
101-
sudo apt install -y ninja-build
101+
sudo apt install -y ninja-build python3-pip
102+
sudo pip3 install --break-system-packages pytest pytest-forked pytest-xdist hypothesis
102103
103104
- name: Restore CPM cache
104105
env:
@@ -177,7 +178,8 @@ jobs:
177178
- name: Install build tools
178179
run: |
179180
${{ matrix.install }}
180-
sudo apt install -y ninja-build
181+
sudo apt install -y ninja-build python3-pip
182+
sudo pip3 install pytest pytest-forked pytest-xdist hypothesis
181183
182184
- name: Restore CPM cache
183185
env:
@@ -292,7 +294,8 @@ jobs:
292294
- name: Install build tools
293295
run: |
294296
${{ matrix.install }}
295-
sudo apt install -y ninja-build
297+
sudo apt install -y ninja-build python3-pip
298+
sudo pip3 install --break-system-packages pytest pytest-forked pytest-xdist hypothesis
296299
297300
- name: Restore CPM cache
298301
env:
@@ -338,7 +341,8 @@ jobs:
338341

339342
- name: Install build tools
340343
run: |
341-
sudo apt update && sudo apt install -y gcc-${{env.DEFAULT_GCC_VERSION}} g++-${{env.DEFAULT_GCC_VERSION}} ninja-build valgrind
344+
sudo apt update && sudo apt install -y gcc-${{env.DEFAULT_GCC_VERSION}} g++-${{env.DEFAULT_GCC_VERSION}} ninja-build valgrind python3-pip
345+
sudo pip3 install --break-system-packages pytest pytest-forked pytest-xdist hypothesis
342346
343347
- name: Restore CPM cache
344348
env:
@@ -409,7 +413,8 @@ jobs:
409413

410414
- name: Install build tools
411415
run: |
412-
sudo apt update && sudo apt install -y clang-${{env.MULL_LLVM_VERSION}} ninja-build
416+
sudo apt update && sudo apt install -y clang-${{env.MULL_LLVM_VERSION}} ninja-build python3-pip
417+
sudo pip3 install --break-system-packages pytest pytest-forked pytest-xdist hypothesis
413418
414419
- name: Install mull
415420
env:

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@
1010
.cmake-format.yaml
1111
CMakePresets.json
1212
/toolchains
13+
__pycache__
14+
.mypy_cache
15+
.pytest_cache
16+
.hypothesis

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,4 @@ if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
6464
endif()
6565

6666
add_subdirectory(fail)
67+
add_subdirectory(pbt)

test/pbt/CMakeLists.txt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
set(COMPILER_ARGS
2+
--std=c++${CMAKE_CXX_STANDARD}
3+
-Wall
4+
-Wcast-align
5+
-Wconversion
6+
-Wdouble-promotion
7+
-Werror
8+
-Wextra
9+
-Wextra-semi
10+
-Wfatal-errors
11+
-Wformat=2
12+
-Wold-style-cast
13+
-Woverloaded-virtual
14+
-Wshadow
15+
-Wunused
16+
-Wno-missing-braces)
17+
18+
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
19+
list(
20+
APPEND
21+
COMPILER_ARGS
22+
-fbracket-depth=99999
23+
-Warray-bounds-pointer-arithmetic
24+
-Wextra-semi-stmt
25+
-Wgcc-compat
26+
-Wheader-hygiene
27+
-Widiomatic-parentheses
28+
-Wimplicit
29+
-Wnewline-eof
30+
-Wmissing-prototypes
31+
-Wno-gnu-string-literal-operator-template)
32+
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
33+
list(APPEND COMPILER_ARGS -Wduplicated-branches -Wduplicated-cond
34+
-Wlogical-op -Wuseless-cast)
35+
endif()
36+
37+
get_target_property(SANITIZER_COMPILER_ARGS sanitizers
38+
INTERFACE_COMPILE_OPTIONS)
39+
if(SANITIZER_COMPILER_ARGS AND NOT SANITIZER_COMPILER_ARGS STREQUAL
40+
"SANITIZER_COMPILER_ARGS-NOTFOUND")
41+
list(APPEND COMPILER_ARGS ${SANITIZER_COMPILER_ARGS})
42+
endif()
43+
44+
string(REPLACE ";" "," COMPILER_ARGS "${COMPILER_ARGS}")
45+
46+
get_target_property(INCLUDE_DIRS stdx INTERFACE_INCLUDE_DIRECTORIES)
47+
string(REPLACE ";" "," INCLUDE_DIRS "${INCLUDE_DIRS}")
48+
49+
if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
50+
add_unit_test(
51+
tuple
52+
PYTEST
53+
FILES
54+
tuple.py
55+
EXTRA_ARGS
56+
-vv
57+
-n2
58+
--compiler=${CMAKE_CXX_COMPILER}
59+
--compiler-args=${COMPILER_ARGS}
60+
--includes=${INCLUDE_DIRS})
61+
endif()

test/pbt/conftest.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import pytest
2+
import hypothesis
3+
import subprocess
4+
import tempfile
5+
import os
6+
7+
hypothesis.settings.register_profile("ci", max_examples=500)
8+
hypothesis.settings.register_profile("fast", max_examples=10)
9+
10+
11+
def pytest_addoption(parser):
12+
parser.addoption("--includes", action="store", help="C++ include directories", required=True)
13+
parser.addoption("--compiler", action="store", help="C++ compiler", required=True)
14+
parser.addoption("--compiler-args", action="store", help="C++ compiler arguments", default="", required=False)
15+
16+
17+
@pytest.fixture(scope="module")
18+
def compiler(pytestconfig):
19+
return pytestconfig.getoption("compiler")
20+
21+
@pytest.fixture(scope="module")
22+
def include_dirs(pytestconfig):
23+
return [i for i in pytestconfig.getoption("includes").split(",") if i]
24+
25+
@pytest.fixture(scope="module")
26+
def compiler_args(pytestconfig):
27+
args = pytestconfig.getoption("compiler_args")
28+
29+
if args:
30+
return [i for i in args.split(",") if i]
31+
else:
32+
return []
33+
34+
@pytest.fixture(scope="module")
35+
def compile(compiler, compiler_args, include_dirs):
36+
include_args = [f"-I{i}" for i in include_dirs]
37+
def f(code_str):
38+
code_str += "\n"
39+
with tempfile.NamedTemporaryFile(delete=False, suffix=".cpp") as temp_cpp_file:
40+
temp_cpp_file.write(code_str.encode('utf-8'))
41+
temp_cpp_file_path = temp_cpp_file.name
42+
43+
try:
44+
compile_command = [
45+
compiler, temp_cpp_file_path,
46+
"-o", temp_cpp_file_path + ".out"
47+
] + compiler_args + include_args
48+
49+
result = subprocess.run(compile_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
50+
51+
if result.returncode == 0:
52+
return True
53+
else:
54+
error_message = (
55+
f"Compiler returned non-zero exit code: {result.returncode}\n"
56+
f"Compilation command: {' '.join(compile_command)}\n"
57+
f"Source code:\n{code_str}\n"
58+
f"Compiler stderr:\n{result.stderr.decode('utf-8')}\n"
59+
f"Compiler stdout:\n{result.stdout.decode('utf-8')}\n"
60+
)
61+
pytest.fail(error_message)
62+
63+
except Exception as e:
64+
pytest.fail(str(e))
65+
finally:
66+
os.remove(temp_cpp_file_path)
67+
if os.path.exists(temp_cpp_file_path + ".out"):
68+
os.remove(temp_cpp_file_path + ".out")
69+
70+
return f
71+

test/pbt/tuple.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
from hypothesis import strategies as st, given, settings, event, assume
2+
3+
def unpack(l):
4+
return ", ".join([str(i) for i in l])
5+
6+
small_ints = st.integers(min_value=-100, max_value=100)
7+
8+
@st.composite
9+
def tuples(draw, children):
10+
values = draw(st.lists(children))
11+
return f"stdx::make_tuple({unpack(values)})"
12+
13+
@st.composite
14+
def list_trees(draw, leaves=st.integers()):
15+
l = draw(st.recursive(leaves, lambda children: st.lists(children)))
16+
if not isinstance(l, list):
17+
l = [l]
18+
return l
19+
20+
def as_tuple_tree(value):
21+
if isinstance(value, list):
22+
values = [as_tuple_tree(v) for v in value]
23+
return f"stdx::make_tuple({unpack(values)})"
24+
else:
25+
return value
26+
27+
@st.composite
28+
def tuple_trees(draw, leaves=st.integers()):
29+
return draw(st.recursive(leaves, lambda children: tuples(children)))
30+
31+
@settings(deadline=10000)
32+
@given(tuple_trees(small_ints))
33+
def test_tuple_trees(compile, t):
34+
assert compile(f"""
35+
#include <stdx/tuple.hpp>
36+
37+
[[maybe_unused]] constexpr auto t = {t};
38+
39+
int main() {{
40+
return 0;
41+
}}
42+
""")
43+
44+
@settings(deadline=10000)
45+
@given(list_trees(small_ints))
46+
def test_tuple_size(compile, l):
47+
t = as_tuple_tree(l)
48+
assert compile(f"""
49+
#include <stdx/tuple.hpp>
50+
51+
constexpr auto t = {t};
52+
static_assert(stdx::tuple_size_v<decltype(t)> == {len(l)});
53+
static_assert(std::size(t) == {len(l)});
54+
55+
int main() {{
56+
return 0;
57+
}}
58+
""")
59+
60+
61+
@settings(deadline=10000)
62+
@given(list_trees(small_ints), st.integers())
63+
def test_get_by_index(compile, l, i):
64+
assume(len(l) > 0)
65+
t = as_tuple_tree(l)
66+
i = i % len(l)
67+
68+
expected_v = as_tuple_tree(l[i])
69+
70+
assert compile(f"""
71+
#include <stdx/tuple.hpp>
72+
73+
using namespace stdx::literals;
74+
75+
constexpr auto t = {t};
76+
constexpr auto expected = {expected_v};
77+
78+
static_assert(stdx::get<{i}>(t) == expected);
79+
static_assert(get<{i}>(t) == expected);
80+
81+
static_assert(t[{i}_idx] == expected);
82+
static_assert(t[stdx::index<{i}>] == expected);
83+
84+
// TODO: enable once #148 is fixed
85+
//static_assert(t.get(stdx::index<{i}>) == expected);
86+
//static_assert(t.get({i}_idx) == expected);
87+
88+
int main() {{
89+
return 0;
90+
}}
91+
""")
92+
93+
94+
@settings(deadline=10000)
95+
@given(st.lists(list_trees(small_ints)))
96+
def test_tuple_cat(compile, ls):
97+
ts = [as_tuple_tree(l) for l in ls]
98+
99+
flattened_ls = [i for subl in ls for i in subl]
100+
expected = as_tuple_tree(flattened_ls)
101+
102+
assert compile(f"""
103+
#include <stdx/tuple.hpp>
104+
#include <stdx/tuple_algorithms.hpp>
105+
106+
static_assert(stdx::tuple_cat({unpack(ts)}) == {expected});
107+
108+
int main() {{
109+
return 0;
110+
}}
111+
""")
112+
113+
114+
@settings(deadline=10000)
115+
@given(list_trees(small_ints), st.one_of(list_trees(small_ints), small_ints))
116+
def test_push(compile, l, elem):
117+
expected_back = as_tuple_tree(l + [elem])
118+
expected_front = as_tuple_tree([elem] + l)
119+
120+
if isinstance(elem, list):
121+
elem = as_tuple_tree(elem)
122+
else:
123+
elem = str(elem)
124+
125+
t = as_tuple_tree(l)
126+
127+
assert compile(f"""
128+
#include <stdx/tuple.hpp>
129+
#include <stdx/tuple_algorithms.hpp>
130+
131+
constexpr auto t = {t};
132+
constexpr auto elem = {elem};
133+
134+
constexpr auto expected_back = {expected_back};
135+
static_assert(stdx::tuple_push_back(t, elem) == expected_back);
136+
static_assert(stdx::tuple_snoc(t, elem) == expected_back);
137+
138+
constexpr auto expected_front = {expected_front};
139+
static_assert(stdx::tuple_push_front(elem, t) == expected_front);
140+
static_assert(stdx::tuple_cons(elem, t) == expected_front);
141+
142+
int main() {{
143+
return 0;
144+
}}
145+
""")
146+
147+
from itertools import product
148+
149+
@settings(deadline=20000)
150+
@given(st.lists(list_trees(small_ints), max_size=3))
151+
def test_cartesian_product(compile, ls):
152+
expected = as_tuple_tree([list(p) for p in product(*ls)])
153+
154+
ts = [as_tuple_tree(l) for l in ls]
155+
156+
assert compile(f"""
157+
#include <stdx/tuple.hpp>
158+
#include <stdx/tuple_algorithms.hpp>
159+
160+
static_assert(stdx::cartesian_product_copy({unpack(ts)}) == {expected});
161+
162+
int main() {{
163+
return 0;
164+
}}
165+
""")
166+
167+
from functools import reduce
168+
169+
@settings(deadline=20000)
170+
@given(st.lists(small_ints))
171+
def test_star_of(compile, l):
172+
expected_any_of = any([i > 50 for i in l])
173+
expected_all_of = all([i > 50 for i in l])
174+
expected_none_of = not expected_any_of
175+
176+
t = as_tuple_tree(l)
177+
178+
assert compile(f"""
179+
#include <stdx/tuple.hpp>
180+
#include <stdx/tuple_algorithms.hpp>
181+
182+
constexpr auto f = [](int i) {{ return i > 50; }};
183+
184+
static_assert(stdx::any_of(f, {t}) == {str(expected_any_of).lower()});
185+
static_assert(stdx::all_of(f, {t}) == {str(expected_all_of).lower()});
186+
static_assert(stdx::none_of(f, {t}) == {str(expected_none_of).lower()});
187+
188+
int main() {{
189+
return 0;
190+
}}
191+
""")

0 commit comments

Comments
 (0)