Skip to content

Commit eb0642d

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

File tree

6 files changed

+304
-5
lines changed

6 files changed

+304
-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 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 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 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 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 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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
get_target_property(STDX_INCLUDE_DIRS stdx INTERFACE_INCLUDE_DIRECTORIES)
2+
get_target_property(SANITIZER_ARGS sanitizers INTERFACE_COMPILE_OPTIONS)
3+
4+
if(SANITIZER_ARGS AND NOT SANITIZER_ARGS STREQUAL "SANITIZER_ARGS-NOTFOUND")
5+
string(REPLACE ";" "," SANITIZER_ARGS "${SANITIZER_ARGS}")
6+
set(SANITIZER_ARGS "--compiler-args ${SANITIZER_ARGS}")
7+
else()
8+
set(SANITIZER_ARGS "")
9+
endif()
10+
11+
string(REPLACE ";" "," STDX_INCLUDE_DIRS_ESCAPED "${STDX_INCLUDE_DIRS}")
12+
13+
if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 20)
14+
add_unit_test(
15+
tuple
16+
PYTEST
17+
FILES
18+
tuple.py
19+
EXTRA_ARGS
20+
-vv
21+
--compiler
22+
${CMAKE_CXX_COMPILER}
23+
${SANITIZER_ARGS}
24+
--includes
25+
${STDX_INCLUDE_DIRS_ESCAPED})
26+
endif()

test/pbt/conftest.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
with tempfile.NamedTemporaryFile(delete=False, suffix=".cpp") as temp_cpp_file:
39+
temp_cpp_file.write(code_str.encode('utf-8'))
40+
temp_cpp_file_path = temp_cpp_file.name
41+
42+
try:
43+
compile_command = [
44+
compiler, temp_cpp_file_path,
45+
"-o", temp_cpp_file_path + ".out",
46+
"--std=c++20",
47+
"-fbracket-depth=99999"
48+
] + compiler_args + include_args
49+
50+
result = subprocess.run(compile_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
51+
52+
if result.returncode == 0:
53+
return True
54+
else:
55+
error_message = (
56+
f"Compiler returned non-zero exit code: {result.returncode}\n"
57+
f"Compilation command: {' '.join(compile_command)}\n"
58+
f"Source code:\n{code_str}\n"
59+
f"Compiler stderr:\n{result.stderr.decode('utf-8')}\n"
60+
f"Compiler stdout:\n{result.stdout.decode('utf-8')}\n"
61+
)
62+
pytest.fail(error_message)
63+
64+
except Exception as e:
65+
pytest.fail(str(e))
66+
finally:
67+
os.remove(temp_cpp_file_path)
68+
if os.path.exists(temp_cpp_file_path + ".out"):
69+
os.remove(temp_cpp_file_path + ".out")
70+
71+
return f
72+

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+
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)