Skip to content

Commit b210da3

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

File tree

7 files changed

+206
-0
lines changed

7 files changed

+206
-0
lines changed

.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
2+
pbt_prototype_driver.cpp)
3+
target_link_libraries(pbt_prototype_driver PUBLIC sanitizers warnings cib)
4+
target_compile_options(pbt_prototype_driver PUBLIC -fconstexpr-steps=4000000000)
5+
6+
add_unit_test(
7+
tuple
8+
PYTEST
9+
FILES
10+
lookup.py
11+
EXTRA_ARGS
12+
-vv
13+
-n 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: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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+
return cmd.split(" ")
39+
40+
return f
41+
42+
@pytest.fixture(scope="module")
43+
def args_compilation_command(pytestconfig):
44+
compiler = pytestconfig.getoption("compiler")
45+
if compiler is None:
46+
return None
47+
48+
include_dirs = [f"-I{i}" for i in pytestconfig.getoption("includes").split(",") if i]
49+
compiler_args = [i for i in pytestconfig.getoption("compiler_args").split(",") if i]
50+
51+
def f(filename):
52+
compile_command = [
53+
compiler, temp_cpp_file_path,
54+
"-o", temp_cpp_file_path + ".out"
55+
] + compiler_args + include_args
56+
return compile_command
57+
58+
return f
59+
60+
61+
62+
@pytest.fixture(scope="module")
63+
def compile(cmake_compilation_command, args_compilation_command):
64+
cmd = cmake_compilation_command
65+
if cmd is None:
66+
cmd = args_compilation_command
67+
68+
def f(code_str):
69+
code_str += "\n"
70+
with tempfile.NamedTemporaryFile(delete=False, suffix=".cpp") as temp_cpp_file:
71+
temp_cpp_file.write(code_str.encode('utf-8'))
72+
temp_cpp_file_path = temp_cpp_file.name
73+
74+
try:
75+
compile_command = cmd(temp_cpp_file_path)
76+
result = subprocess.run(compile_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
77+
78+
if result.returncode == 0:
79+
os.chmod(temp_cpp_file_path + ".out", 0o700)
80+
return temp_cpp_file_path + ".out"
81+
else:
82+
error_message = (
83+
f"Compiler returned non-zero exit code: {result.returncode}\n"
84+
f"Compilation command: {' '.join(compile_command)}\n"
85+
f"Source code:\n{code_str}\n"
86+
f"Compiler stderr:\n{result.stderr.decode('utf-8')}\n"
87+
f"Compiler stdout:\n{result.stdout.decode('utf-8')}\n"
88+
)
89+
pytest.fail(error_message)
90+
91+
except Exception as e:
92+
pytest.fail(str(e))
93+
finally:
94+
os.remove(temp_cpp_file_path)
95+
96+
return f
97+

test/lookup/pbt/lookup.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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, max_examples=20)
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+
assert result
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)