From 1b13fd3ce0b5cfe12dc3232013bc024705cb3af0 Mon Sep 17 00:00:00 2001 From: Luke Valenty Date: Sun, 13 Oct 2024 23:57:56 -0700 Subject: [PATCH] add pbt for lookup library --- .github/workflows/unit_tests.yml | 24 +++++- .gitignore | 4 + requirements.txt | 11 +++ test/lookup/CMakeLists.txt | 2 + test/lookup/pbt/CMakeLists.txt | 18 +++++ test/lookup/pbt/conftest.py | 98 ++++++++++++++++++++++++ test/lookup/pbt/lookup.py | 76 ++++++++++++++++++ test/lookup/pbt/pbt_prototype_driver.cpp | 1 + 8 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 requirements.txt create mode 100644 test/lookup/pbt/CMakeLists.txt create mode 100644 test/lookup/pbt/conftest.py create mode 100644 test/lookup/pbt/lookup.py create mode 100644 test/lookup/pbt/pbt_prototype_driver.cpp diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 5f1a8801..7dfd2c72 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -97,7 +97,11 @@ jobs: - name: Install build tools run: | ${{ matrix.install }} - sudo apt install -y ninja-build + sudo apt install -y ninja-build python3-venv python3-pip + python3 -m venv ${{github.workspace}}/test_venv + source ${{github.workspace}}/test_venv/bin/activate + pip install -r ${{github.workspace}}/requirements.txt + echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH - name: Restore CPM cache env: @@ -176,7 +180,11 @@ jobs: - name: Install build tools run: | ${{ matrix.install }} - sudo apt install -y ninja-build + sudo apt install -y ninja-build python3-venv python3-pip + python3 -m venv ${{github.workspace}}/test_venv + source ${{github.workspace}}/test_venv/bin/activate + pip install -r ${{github.workspace}}/requirements.txt + echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH - name: Restore CPM cache env: @@ -296,7 +304,11 @@ jobs: - name: Install build tools run: | ${{ matrix.install }} - sudo apt install -y ninja-build + sudo apt install -y ninja-build python3-venv python3-pip + python3 -m venv ${{github.workspace}}/test_venv + source ${{github.workspace}}/test_venv/bin/activate + pip install -r ${{github.workspace}}/requirements.txt + echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH - name: Restore CPM cache env: @@ -342,7 +354,11 @@ jobs: - name: Install build tools run: | - sudo apt update && sudo apt install -y gcc-${{env.DEFAULT_GCC_VERSION}} g++-${{env.DEFAULT_GCC_VERSION}} ninja-build valgrind + sudo apt update && sudo apt install -y gcc-${{env.DEFAULT_GCC_VERSION}} g++-${{env.DEFAULT_GCC_VERSION}} ninja-build valgrind python3-venv python3-pip + python3 -m venv ${{github.workspace}}/test_venv + source ${{github.workspace}}/test_venv/bin/activate + pip install -r ${{github.workspace}}/requirements.txt + echo "${{github.workspace}}/test_venv/bin" >> $GITHUB_PATH - name: Restore CPM cache env: diff --git a/.gitignore b/.gitignore index 3d1008fa..48759dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ CMakePresets.json /toolchains mull.yml +__pycache__ +.mypy_cache +.pytest_cache +.hypothesis \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..19a90b92 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +pytest==8.3.3 +pytest-forked==1.6.0 +pytest-xdist==3.6.1 +hypothesis==6.112.5 +attrs==24.2.0 +execnet==2.1.1 +pluggy==1.5.0 +sortedcontainers==2.4.0 +iniconfig==2.0.0 +packaging==24.1 +py==1.11.0 \ No newline at end of file diff --git a/test/lookup/CMakeLists.txt b/test/lookup/CMakeLists.txt index c24f1db1..d78a88ea 100644 --- a/test/lookup/CMakeLists.txt +++ b/test/lookup/CMakeLists.txt @@ -84,3 +84,5 @@ foreach(BENCH_ALG_NAME ${BENCH_ALG_NAMES}) QBENCH_DATASET="${BENCH_DATASET}") endforeach() endforeach() + +add_subdirectory(pbt) diff --git a/test/lookup/pbt/CMakeLists.txt b/test/lookup/pbt/CMakeLists.txt new file mode 100644 index 00000000..d266b8de --- /dev/null +++ b/test/lookup/pbt/CMakeLists.txt @@ -0,0 +1,18 @@ +add_executable(pbt_prototype_driver EXCLUDE_FROM_ALL pbt_prototype_driver.cpp) +target_link_libraries(pbt_prototype_driver PUBLIC sanitizers warnings cib) +target_compile_options( + pbt_prototype_driver + PUBLIC $<$:-fconstexpr-steps=4000000000>) + +add_unit_test( + lookup + PYTEST + FILES + lookup.py + EXTRA_ARGS + -vv + -n + auto + -x + --compile-commands=${CMAKE_BINARY_DIR}/compile_commands.json + --prototype-driver=${CMAKE_CURRENT_SOURCE_DIR}/pbt_prototype_driver.cpp) diff --git a/test/lookup/pbt/conftest.py b/test/lookup/pbt/conftest.py new file mode 100644 index 00000000..0f3bcc83 --- /dev/null +++ b/test/lookup/pbt/conftest.py @@ -0,0 +1,98 @@ +import pytest +import hypothesis +import json +import subprocess +import tempfile +import os +import re + +hypothesis.settings.register_profile("ci", max_examples=500) +hypothesis.settings.register_profile("fast", max_examples=10) + + + +def pytest_addoption(parser): + parser.addoption("--compiler", action="store", help="C++ compiler", default=None, required=False) + parser.addoption("--compiler-args", action="store", help="C++ compiler arguments", default="", required=False) + parser.addoption("--includes", action="store", help="C++ include directories", default="", required=False) + + parser.addoption("--compile-commands", action="store", help="cmake compiler commands", default=None, required=False) + parser.addoption("--prototype-driver", action="store", help="Prototype .cpp filename to gather compilation command from", default=None, required=False) + +@pytest.fixture(scope="module") +def cmake_compilation_command(pytestconfig): + compile_commands_filename = pytestconfig.getoption("compile_commands") + prototype_driver_filename = pytestconfig.getoption("prototype_driver") + + if compile_commands_filename is None or prototype_driver_filename is None: + return None + + def f(filename): + with open(compile_commands_filename, "r") as f: + db = json.load(f) + for obj in db: + if obj["file"] == prototype_driver_filename: + cmd = obj["command"] + cmd = cmd.replace(prototype_driver_filename, filename) + cmd = re.sub(r"-o .*?\.cpp\.o", f"-o {filename}.o", cmd) + cmd = re.sub(r" -c ", f" ", cmd) + return cmd.split(" ") + + return f + +@pytest.fixture(scope="module") +def args_compilation_command(pytestconfig): + compiler = pytestconfig.getoption("compiler") + if compiler is None: + return None + + include_dirs = [f"-I{i}" for i in pytestconfig.getoption("includes").split(",") if i] + compiler_args = [i for i in pytestconfig.getoption("compiler_args").split(",") if i] + + def f(filename): + compile_command = [ + compiler, temp_cpp_file_path, + "-o", temp_cpp_file_path + ".out" + ] + compiler_args + include_args + return compile_command + + return f + + + +@pytest.fixture(scope="module") +def compile(cmake_compilation_command, args_compilation_command): + cmd = cmake_compilation_command + if cmd is None: + cmd = args_compilation_command + + def f(code_str): + code_str += "\n" + with tempfile.NamedTemporaryFile(delete=False, suffix=".cpp") as temp_cpp_file: + temp_cpp_file.write(code_str.encode('utf-8')) + temp_cpp_file_path = temp_cpp_file.name + + try: + compile_command = cmd(temp_cpp_file_path) + result = subprocess.run(compile_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + if result.returncode == 0: + os.chmod(temp_cpp_file_path + ".o", 0o700) + return temp_cpp_file_path + ".o" + else: + error_message = ( + f"Compiler returned non-zero exit code: {result.returncode}\n" + f"Compilation command: {' '.join(compile_command)}\n" + f"Source code:\n{code_str}\n" + f"Compiler stderr:\n{result.stderr.decode('utf-8')}\n" + f"Compiler stdout:\n{result.stdout.decode('utf-8')}\n" + ) + pytest.fail(error_message) + + except Exception as e: + pytest.fail(str(e)) + finally: + os.remove(temp_cpp_file_path) + + return f + diff --git a/test/lookup/pbt/lookup.py b/test/lookup/pbt/lookup.py new file mode 100644 index 00000000..aa809275 --- /dev/null +++ b/test/lookup/pbt/lookup.py @@ -0,0 +1,76 @@ +from hypothesis import strategies as st, given, settings, event, assume +import subprocess + +def unpack(l): + return ", ".join([str(i) for i in l]) + +uint16s = st.integers(min_value = 0, max_value = (1 << 16) - 1) + + +def lookup_make(lookup_type, key_type, value_type, default, entries): + entries = [f"lookup::entry<{key_type}, {value_type}>{{{k}, {v}}}" for k, v in entries] + return f""" + {lookup_type}::make(CX_VALUE(lookup::input<{key_type}, {value_type}, {len(entries)}>{{ + {default}, + std::array, {len(entries)}>{{ {unpack(entries)} }} + }})) + """ + +@st.composite +def lookup_inputs(draw, keys=uint16s, values=uint16s, default=uint16s, min_size=0, max_size=10): + entries = draw(st.lists(st.tuples(keys, values), min_size=min_size, max_size=max_size, unique_by=lambda x: x[0])) + default = draw(default) + return (default, entries) + +pseudo_pext_lookups = st.sampled_from([ + "lookup::pseudo_pext_lookup<>", + "lookup::pseudo_pext_lookup", + "lookup::pseudo_pext_lookup", + "lookup::pseudo_pext_lookup", + "lookup::pseudo_pext_lookup" +]) + +@settings(deadline=50000) +@given( + pseudo_pext_lookups, + lookup_inputs(min_size=2, max_size=12), + st.lists(uint16s, min_size=10, max_size=1000, unique=True) +) +def test_lookup(compile, t, l, extras): + default, entries = l + + lookup_model = {k: v for k, v in entries} + lookup = lookup_make(t, "std::uint16_t", "std::uint16_t", default, entries) + + check_keys = set(lookup_model.keys()).union(set(extras)) + + static_asserts = "\n".join( + [f"static_assert(lookup[{k}] == {lookup_model.get(k, default)});" for k in check_keys] + ) + + runtime_checks = " &&\n".join( + [f"(lookup[{k}] == {lookup_model.get(k, default)})" for k in check_keys] + ) + + out = compile(f""" + #include + #include + #include + + #include + + #include + #include + + int main() {{ + [[maybe_unused]] constexpr auto lookup = {lookup}; + {static_asserts} + + bool const pass = {runtime_checks}; + + return pass ? 0 : 1; + }} + """) + + result = subprocess.run([out], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + assert result.returncode == 0 \ No newline at end of file diff --git a/test/lookup/pbt/pbt_prototype_driver.cpp b/test/lookup/pbt/pbt_prototype_driver.cpp new file mode 100644 index 00000000..237c8ce1 --- /dev/null +++ b/test/lookup/pbt/pbt_prototype_driver.cpp @@ -0,0 +1 @@ +int main() {}