Skip to content

Commit cab8974

Browse files
committed
add pbt for lookup library
1 parent 962740f commit cab8974

File tree

8 files changed

+228
-4
lines changed

8 files changed

+228
-4
lines changed

.github/workflows/unit_tests.yml

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,11 @@ jobs:
9797
- name: Install build tools
9898
run: |
9999
${{ matrix.install }}
100-
sudo apt install -y ninja-build
100+
sudo apt install -y ninja-build python3-venv python3-pip
101+
python3 -m venv ${{github.workspace}}/test_venv
102+
source ${{github.workspace}}/test_venv/bin/activate
103+
pip install -r ${{github.workspace}}/requirements.txt
104+
echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH
101105
102106
- name: Restore CPM cache
103107
env:
@@ -176,7 +180,11 @@ jobs:
176180
- name: Install build tools
177181
run: |
178182
${{ matrix.install }}
179-
sudo apt install -y ninja-build
183+
sudo apt install -y ninja-build python3-venv python3-pip
184+
python3 -m venv ${{github.workspace}}/test_venv
185+
source ${{github.workspace}}/test_venv/bin/activate
186+
pip install -r ${{github.workspace}}/requirements.txt
187+
echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH
180188
181189
- name: Restore CPM cache
182190
env:
@@ -296,7 +304,11 @@ jobs:
296304
- name: Install build tools
297305
run: |
298306
${{ matrix.install }}
299-
sudo apt install -y ninja-build
307+
sudo apt install -y ninja-build python3-venv python3-pip
308+
python3 -m venv ${{github.workspace}}/test_venv
309+
source ${{github.workspace}}/test_venv/bin/activate
310+
pip install -r ${{github.workspace}}/requirements.txt
311+
echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH
300312
301313
- name: Restore CPM cache
302314
env:
@@ -342,7 +354,11 @@ jobs:
342354

343355
- name: Install build tools
344356
run: |
345-
sudo apt update && sudo apt install -y gcc-${{env.DEFAULT_GCC_VERSION}} g++-${{env.DEFAULT_GCC_VERSION}} ninja-build valgrind
357+
sudo apt update && sudo apt install -y gcc-${{env.DEFAULT_GCC_VERSION}} g++-${{env.DEFAULT_GCC_VERSION}} ninja-build valgrind python3-venv python3-pip
358+
python3 -m venv ${{github.workspace}}/test_venv
359+
source ${{github.workspace}}/test_venv/bin/activate
360+
pip install -r ${{github.workspace}}/requirements.txt
361+
echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH
346362
347363
- name: Restore CPM cache
348364
env:

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@
1111
CMakePresets.json
1212
/toolchains
1313
mull.yml
14+
__pycache__
15+
.mypy_cache
16+
.pytest_cache
17+
.hypothesis

requirements.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
pytest==8.3.3
2+
pytest-forked==1.6.0
3+
pytest-xdist==3.6.1
4+
hypothesis==6.112.5
5+
attrs==24.2.0
6+
execnet==2.1.1
7+
pluggy==1.5.0
8+
sortedcontainers==2.4.0
9+
iniconfig==2.0.0
10+
packaging==24.1
11+
py==1.11.0

test/lookup/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,5 @@ foreach(BENCH_ALG_NAME ${BENCH_ALG_NAMES})
8484
QBENCH_DATASET="${BENCH_DATASET}")
8585
endforeach()
8686
endforeach()
87+
88+
add_subdirectory(pbt)

test/lookup/pbt/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
add_executable(pbt_prototype_driver EXCLUDE_FROM_ALL pbt_prototype_driver.cpp)
2+
target_link_libraries(pbt_prototype_driver PUBLIC sanitizers warnings cib)
3+
target_compile_options(pbt_prototype_driver PUBLIC -fconstexpr-steps=4000000000)
4+
5+
add_unit_test(
6+
tuple
7+
PYTEST
8+
FILES
9+
lookup.py
10+
EXTRA_ARGS
11+
-vv
12+
-n
13+
auto
14+
-x
15+
--compile-commands=${CMAKE_BINARY_DIR}/compile_commands.json
16+
--prototype-driver=${CMAKE_CURRENT_SOURCE_DIR}/pbt_prototype_driver.cpp)

test/lookup/pbt/conftest.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import pytest
2+
import hypothesis
3+
import json
4+
import subprocess
5+
import tempfile
6+
import os
7+
import re
8+
9+
hypothesis.settings.register_profile("ci", max_examples=500)
10+
hypothesis.settings.register_profile("fast", max_examples=10)
11+
12+
13+
14+
def pytest_addoption(parser):
15+
parser.addoption("--compiler", action="store", help="C++ compiler", default=None, required=False)
16+
parser.addoption("--compiler-args", action="store", help="C++ compiler arguments", default="", required=False)
17+
parser.addoption("--includes", action="store", help="C++ include directories", default="", required=False)
18+
19+
parser.addoption("--compile-commands", action="store", help="cmake compiler commands", default=None, required=False)
20+
parser.addoption("--prototype-driver", action="store", help="Prototype .cpp filename to gather compilation command from", default=None, required=False)
21+
22+
@pytest.fixture(scope="module")
23+
def cmake_compilation_command(pytestconfig):
24+
compile_commands_filename = pytestconfig.getoption("compile_commands")
25+
prototype_driver_filename = pytestconfig.getoption("prototype_driver")
26+
27+
if compile_commands_filename is None or prototype_driver_filename is None:
28+
return None
29+
30+
def f(filename):
31+
with open(compile_commands_filename, "r") as f:
32+
db = json.load(f)
33+
for obj in db:
34+
if obj["file"] == prototype_driver_filename:
35+
cmd = obj["command"]
36+
cmd = cmd.replace(prototype_driver_filename, filename)
37+
cmd = re.sub(r"-o .*?\.cpp\.o", f"-o {filename}.o", cmd)
38+
cmd = re.sub(r" -c ", f" ", cmd)
39+
return cmd.split(" ")
40+
41+
return f
42+
43+
@pytest.fixture(scope="module")
44+
def args_compilation_command(pytestconfig):
45+
compiler = pytestconfig.getoption("compiler")
46+
if compiler is None:
47+
return None
48+
49+
include_dirs = [f"-I{i}" for i in pytestconfig.getoption("includes").split(",") if i]
50+
compiler_args = [i for i in pytestconfig.getoption("compiler_args").split(",") if i]
51+
52+
def f(filename):
53+
compile_command = [
54+
compiler, temp_cpp_file_path,
55+
"-o", temp_cpp_file_path + ".out"
56+
] + compiler_args + include_args
57+
return compile_command
58+
59+
return f
60+
61+
62+
63+
@pytest.fixture(scope="module")
64+
def compile(cmake_compilation_command, args_compilation_command):
65+
cmd = cmake_compilation_command
66+
if cmd is None:
67+
cmd = args_compilation_command
68+
69+
def f(code_str):
70+
code_str += "\n"
71+
with tempfile.NamedTemporaryFile(delete=False, suffix=".cpp") as temp_cpp_file:
72+
temp_cpp_file.write(code_str.encode('utf-8'))
73+
temp_cpp_file_path = temp_cpp_file.name
74+
75+
try:
76+
compile_command = cmd(temp_cpp_file_path)
77+
result = subprocess.run(compile_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
78+
79+
if result.returncode == 0:
80+
os.chmod(temp_cpp_file_path + ".o", 0o700)
81+
return temp_cpp_file_path + ".o"
82+
else:
83+
error_message = (
84+
f"Compiler returned non-zero exit code: {result.returncode}\n"
85+
f"Compilation command: {' '.join(compile_command)}\n"
86+
f"Source code:\n{code_str}\n"
87+
f"Compiler stderr:\n{result.stderr.decode('utf-8')}\n"
88+
f"Compiler stdout:\n{result.stdout.decode('utf-8')}\n"
89+
)
90+
pytest.fail(error_message)
91+
92+
except Exception as e:
93+
pytest.fail(str(e))
94+
finally:
95+
os.remove(temp_cpp_file_path)
96+
97+
return f
98+

test/lookup/pbt/lookup.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from hypothesis import strategies as st, given, settings, event, assume
2+
import subprocess
3+
4+
def unpack(l):
5+
return ", ".join([str(i) for i in l])
6+
7+
uint16s = st.integers(min_value = 0, max_value = (1 << 16) - 1)
8+
9+
10+
def lookup_make(lookup_type, key_type, value_type, default, entries):
11+
entries = [f"lookup::entry<{key_type}, {value_type}>{{{k}, {v}}}" for k, v in entries]
12+
return f"""
13+
{lookup_type}::make(CX_VALUE(lookup::input<{key_type}, {value_type}, {len(entries)}>{{
14+
{default},
15+
std::array<lookup::entry<{key_type}, {value_type}>, {len(entries)}>{{ {unpack(entries)} }}
16+
}}))
17+
"""
18+
19+
@st.composite
20+
def lookup_inputs(draw, keys=uint16s, values=uint16s, default=uint16s, min_size=0, max_size=10):
21+
entries = draw(st.lists(st.tuples(keys, values), min_size=min_size, max_size=max_size, unique_by=lambda x: x[0]))
22+
default = draw(default)
23+
return (default, entries)
24+
25+
pseudo_pext_lookups = st.sampled_from([
26+
"lookup::pseudo_pext_lookup<>",
27+
"lookup::pseudo_pext_lookup<true, 1>",
28+
"lookup::pseudo_pext_lookup<true, 2>",
29+
"lookup::pseudo_pext_lookup<true, 3>",
30+
"lookup::pseudo_pext_lookup<true, 4>"
31+
])
32+
33+
@settings(deadline=50000)
34+
@given(
35+
pseudo_pext_lookups,
36+
lookup_inputs(min_size=2, max_size=12),
37+
st.lists(uint16s, min_size=10, max_size=1000, unique=True)
38+
)
39+
def test_lookup(compile, t, l, extras):
40+
default, entries = l
41+
42+
lookup_model = {k: v for k, v in entries}
43+
lookup = lookup_make(t, "std::uint16_t", "std::uint16_t", default, entries)
44+
45+
check_keys = set(lookup_model.keys()).union(set(extras))
46+
47+
static_asserts = "\n".join(
48+
[f"static_assert(lookup[{k}] == {lookup_model.get(k, default)});" for k in check_keys]
49+
)
50+
51+
runtime_checks = " &&\n".join(
52+
[f"(lookup[{k}] == {lookup_model.get(k, default)})" for k in check_keys]
53+
)
54+
55+
out = compile(f"""
56+
#include <lookup/entry.hpp>
57+
#include <lookup/input.hpp>
58+
#include <lookup/lookup.hpp>
59+
60+
#include <stdx/utility.hpp>
61+
62+
#include <array>
63+
#include <cstdint>
64+
65+
int main() {{
66+
[[maybe_unused]] constexpr auto lookup = {lookup};
67+
{static_asserts}
68+
69+
bool const pass = {runtime_checks};
70+
71+
return pass ? 0 : 1;
72+
}}
73+
""")
74+
75+
result = subprocess.run([out], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
76+
assert result.returncode == 0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int main() {}

0 commit comments

Comments
 (0)