Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pbt for lookup library #633

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@
CMakePresets.json
/toolchains
mull.yml
__pycache__
.mypy_cache
.pytest_cache
.hypothesis
11 changes: 11 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions test/lookup/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,5 @@ foreach(BENCH_ALG_NAME ${BENCH_ALG_NAMES})
QBENCH_DATASET="${BENCH_DATASET}")
endforeach()
endforeach()

add_subdirectory(pbt)
18 changes: 18 additions & 0 deletions test/lookup/pbt/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 $<$<CXX_COMPILER_ID:Clang>:-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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Add EXTRA_DEPS for the lookup headers.

98 changes: 98 additions & 0 deletions test/lookup/pbt/conftest.py
Original file line number Diff line number Diff line change
@@ -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)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: Load the correct profile corresponding to the environment variable.



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

76 changes: 76 additions & 0 deletions test/lookup/pbt/lookup.py
Original file line number Diff line number Diff line change
@@ -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<lookup::entry<{key_type}, {value_type}>, {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<true, 1>",
"lookup::pseudo_pext_lookup<true, 2>",
"lookup::pseudo_pext_lookup<true, 3>",
"lookup::pseudo_pext_lookup<true, 4>"
])

@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 <lookup/entry.hpp>
#include <lookup/input.hpp>
#include <lookup/lookup.hpp>

#include <stdx/utility.hpp>

#include <array>
#include <cstdint>

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
1 change: 1 addition & 0 deletions test/lookup/pbt/pbt_prototype_driver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int main() {}
Loading