Skip to content

Commit

Permalink
oilgen: add to integration test framework
Browse files Browse the repository at this point in the history
TODO: Replace the references to local paths.

oilgen (the basis of Ahead Of Time compilation for OIL) has never been passed
through our large test suite and has instead had more focused testing and large
examples. When consuming DWARF information in a similar fashion to JIT OIL this
was okay, but with the new Clang AST based mechanism it means we have very
little coverage.

This change adds an oilgen test for every test case that has an oil test.
Relying on the build system to create the test target as before would make it
difficult to have failing tests, so we move the build into the integration test
runner. This involves:
1. Writing the input source to a file.
2. Consuming it with oilgen to get the implementation object file.
3. Compiling the input source and linking it with this file.
4. Running the newly created target.

This approach can give the full error message at any stage that fails and will
fail the test appropriately. The downside is the build system integration is
more difficult, as we need the correct compiler flags for the target and to use
the correct compiler. It would be very tricky to replicate this in a build
system that's not CMake, so we will likely only run these tests in open source.

Test plan:
- CI
  • Loading branch information
JakeHillion committed Jan 3, 2024
1 parent e7adc06 commit 72536b3
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 38 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ workflows:
name: test-gcc
requires:
- build-gcc
tests_regex: "OidIntegration\\..*"
tests_regex: "OidIntegration\\..*|OilgenIntegration\\..*"
exclude_regex: ".*inheritance_polymorphic.*|.*arrays_member_int0"
- coverage:
name: coverage
Expand Down Expand Up @@ -48,7 +48,7 @@ workflows:
name: test-clang
requires:
- build-clang
tests_regex: "OidIntegration\\..*"
tests_regex: "OidIntegration\\..*|OilgenIntegration\\..*"
# Tests disabled due to bad DWARF generated by the old clang compiler in CI
exclude_regex: ".*inheritance_polymorphic.*|.*arrays_member_int0|.*fbstring.*|.*std_string_*|.*multi_arg_tb_.*|.*ignored_a"

Expand Down
4 changes: 3 additions & 1 deletion oi/type_graph/ClangTypeParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,7 @@ Primitive& ClangTypeParser::enumeratePrimitive(const clang::BuiltinType& ty) {
case clang::BuiltinType::WChar_U:
return makeType<Primitive>(ty, Primitive::Kind::UInt32);

case clang::BuiltinType::Char8:
case clang::BuiltinType::Char_S:
case clang::BuiltinType::SChar:
return makeType<Primitive>(ty, Primitive::Kind::Int8);
Expand Down Expand Up @@ -380,8 +381,9 @@ Primitive& ClangTypeParser::enumeratePrimitive(const clang::BuiltinType& ty) {
case clang::BuiltinType::Float:
return makeType<Primitive>(ty, Primitive::Kind::Float32);
case clang::BuiltinType::Double:
case clang::BuiltinType::LongDouble:
return makeType<Primitive>(ty, Primitive::Kind::Float64);
case clang::BuiltinType::LongDouble:
return makeType<Primitive>(ty, Primitive::Kind::Float128);

case clang::BuiltinType::UInt128:
case clang::BuiltinType::Int128:
Expand Down
2 changes: 1 addition & 1 deletion oi/type_graph/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ class Primitive : public Type {
Float32,
Float64,
Float80, // TODO worth including?
Float128, // TODO can we generate this?
Float128,
Bool,

StubbedPointer,
Expand Down
11 changes: 9 additions & 2 deletions test/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,18 @@ target_link_libraries(integration_test_runner PRIVATE
GTest::gmock_main
Boost::headers
${Boost_LIBRARIES}
range-v3
toml
)
target_compile_definitions(integration_test_runner PRIVATE
TARGET_EXE_PATH="${CMAKE_CURRENT_BINARY_DIR}/integration_test_target"
TARGET_EXE_PATH="$<TARGET_FILE:integration_test_target>"
OID_EXE_PATH="$<TARGET_FILE:oid>"
CONFIG_FILE_PATH="${CMAKE_BINARY_DIR}/testing.oid.toml")
OILGEN_EXE_PATH="$<TARGET_FILE:oilgen>"
CONFIG_FILE_PATH="${CMAKE_BINARY_DIR}/testing.oid.toml"

CXX="${CMAKE_CXX_COMPILER}"
TARGET_INCLUDE_DIRECTORIES="$<JOIN:$<TARGET_PROPERTY:integration_test_target,INCLUDE_DIRECTORIES>,:>"
)

if (${THRIFT_FOUND})
foreach(THRIFT_TEST IN LISTS THRIFT_TESTS)
Expand All @@ -85,6 +91,7 @@ if (${THRIFT_FOUND})

add_custom_target(integration_test_thrift_sources_${THRIFT_TEST} DEPENDS ${THRIFT_TYPES_H})
add_dependencies(integration_test_target integration_test_thrift_sources_${THRIFT_TEST})
add_dependencies(integration_test_runner integration_test_thrift_sources_${THRIFT_TEST})
target_sources(integration_test_target PRIVATE ${THRIFT_DATA_CPP})
endforeach()

Expand Down
153 changes: 121 additions & 32 deletions test/integration/gen_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,41 @@ def add_headers(f, custom_headers, thrift_headers):
f.write(f'#include "{header}"\n')


def add_test_getters(f, case_name, case):
param_types = ", ".join(
f"std::remove_cvref_t<{param}>" for param in case["param_types"]
)
if "arg_types" in case:
arg_types = ", ".join(case["arg_types"])
else:
arg_types = param_types

f.write(
f"\n"
f" std::tuple<{arg_types}> get_{case_name}() {{\n"
f'{case["setup"]}\n'
f" }}\n"
)


def get_param_str(param, i):
if "]" in param:
# Array param

if ")" in param:
# "int(&)[5]" -> "int (&a0)[5]"
start, end = param.split(")")
return f"{start}a{i}){end}"

# "int[5]" -> "int a0[5]"
# "int[5][10]" -> "int a0[5][10]"
type_name, array_size = param.split("[", 1)
return f"{type_name} a{i}[{array_size}"

# Non-array param, e.g. "int&" -> "int& a0"
return f"{param} a{i}"


def add_test_setup(f, config):
ns = get_namespace(config["suite"])
# fmt: off
Expand All @@ -65,23 +100,6 @@ def add_test_setup(f, config):
)
# fmt: on

def get_param_str(param, i):
if "]" in param:
# Array param

if ")" in param:
# "int(&)[5]" -> "int (&a0)[5]"
start, end = param.split(")")
return f"{start}a{i}){end}"

# "int[5]" -> "int a0[5]"
# "int[5][10]" -> "int a0[5][10]"
type_name, array_size = param.split("[", 1)
return f"{type_name} a{i}[{array_size}"

# Non-array param, e.g. "int&" -> "int& a0"
return f"{param} a{i}"

def define_traceable_func(name, params, body):
return (
f"\n"
Expand All @@ -99,21 +117,7 @@ def define_traceable_func(name, params, body):
# target func for it
continue

# generate getter for an object of this type
param_types = ", ".join(
f"std::remove_cvref_t<{param}>" for param in case["param_types"]
)
if "arg_types" in case:
arg_types = ", ".join(case["arg_types"])
else:
arg_types = param_types

f.write(
f"\n"
f" std::tuple<{arg_types}> get_{case_name}() {{\n"
f'{case["setup"]}\n'
f" }}\n"
)
add_test_getters(f, case_name, case)

# generate oid and oil targets
params_str = ", ".join(
Expand Down Expand Up @@ -266,6 +270,7 @@ def add_tests(f, config):
for case_name, case in config["cases"].items():
add_oid_integration_test(f, config, case_name, case)
add_oil_integration_test(f, config, case_name, case)
add_oilgen_integration_test(f, config, case_name, case)


def add_oid_integration_test(f, config, case_name, case):
Expand Down Expand Up @@ -400,6 +405,90 @@ def add_oil_integration_test(f, config, case_name, case):
f.write(f"}}\n")


def add_oilgen_integration_test(f, config, case_name, case):
case_str = get_case_name(config["suite"], case_name)
exit_code = case.get("expect_oil_exit_code", 0)

if "oil_disable" in case or "target_function" in case:
return

config_prefix = case.get("config_prefix", "")
config_suffix = case.get("config_suffix", "")

f.write(
f"\n"
f"TEST_F(OilgenIntegration, {case_str}) {{\n"
f"{generate_skip(case, 'oil')}"
)

f.write(' constexpr std::string_view targetSrc = R"--(')
headers = set(config.get("includes", []))
thrift_headers = [f"thrift/annotation/gen-cpp2/{config['suite']}_types.h"] if is_thrift_test(config) else []
add_headers(f, sorted(headers), thrift_headers)

f.write(
f"\n"
f'{config.get("raw_definitions", "")}\n'
f"#pragma clang diagnostic push\n"
f'#pragma clang diagnostic ignored "-Wunused-private-field"\n'
f'{config.get("definitions", "")}\n'
f"#pragma clang diagnostic pop\n"
)
add_test_getters(f, case_name, case)

main = "int main() {\n"
main += " auto pr = oi::exporters::Json(std::cout);\n"
main += " pr.setPretty(true);\n"
main += f" auto val = get_{case_name}();\n"
for i in range(len(case["param_types"])):
main += f" auto ret{i} = oi::result::SizedResult(oi::introspect"
if "arg_types" in case:
main += f"<std::decay_t<{case['param_types'][i]}>>"
main += f"(std::get<{i}>(val)));\n"
main += f" pr.print(ret{i});\n"
main += "}\n"

f.write(main)
f.write(')--";\n')

f.write(
f' std::string configPrefix = R"--({config_prefix})--";\n'
f' std::string configSuffix = R"--({config_suffix})--";\n'
f" ba::io_context ctx;\n"
f" auto target = runOilgenTarget({{\n"
f" .ctx = ctx,\n"
f" .targetSrc = targetSrc,\n"
f" }}, std::move(configPrefix), std::move(configSuffix));\n\n"
f" ASSERT_EQ(exit_code(target), {exit_code});\n"
)

key = "expect_json"
if "expect_json_v2" in case:
key = "expect_json_v2"
if key in case:
try:
json.loads(case[key])
except json.decoder.JSONDecodeError as error:
print(
f"\x1b[31m`expect_json` value for test case {config['suite']}.{case_name} was invalid JSON: {error}\x1b[0m",
file=sys.stderr,
)
sys.exit(1)

f.write(
f"\n"
f" std::stringstream expected_json_ss;\n"
f' expected_json_ss << R"--({case[key]})--";\n'
f" auto result_json_ss = std::stringstream(stdout_);\n"
f" bpt::ptree expected_json, actual_json;\n"
f" bpt::read_json(expected_json_ss, expected_json);\n"
f" bpt::read_json(result_json_ss, actual_json);\n"
f" compare_json(expected_json, actual_json);\n"
)

f.write(f"}}\n")


def generate_skip(case, specific):
possibly_skip = ""
skip_reason = case.get("skip", False)
Expand Down
Loading

0 comments on commit 72536b3

Please sign in to comment.