From c1369ea5bd0f156210aa2991bf244c93c5134fc3 Mon Sep 17 00:00:00 2001 From: lyken Date: Fri, 12 Jul 2024 22:29:04 +0800 Subject: [PATCH] core/irrt: introduce irrt testing `cargo test -F test` would compile `nac3core/irrt/irrt_test.cpp` targetted to the host machine (it gets to use `std`) and run the test executable. --- flake.nix | 2 + nac3core/Cargo.toml | 3 + nac3core/build.rs | 73 ++++++++++++++++--- nac3core/irrt/irrt.cpp | 1 + nac3core/irrt/irrt/int_defs.hpp | 6 +- nac3core/irrt/irrt/utils.hpp | 8 +++ nac3core/irrt/irrt_test.cpp | 12 ++++ nac3core/irrt/test/includes.hpp | 11 +++ nac3core/irrt/test/test_core.hpp | 16 +++++ nac3core/irrt/test/util.hpp | 116 ++++++++++++++++++++++++++++++ nac3core/src/codegen/irrt/mod.rs | 2 + nac3core/src/codegen/irrt/test.rs | 26 +++++++ 12 files changed, 266 insertions(+), 10 deletions(-) create mode 100644 nac3core/irrt/irrt_test.cpp create mode 100644 nac3core/irrt/test/includes.hpp create mode 100644 nac3core/irrt/test/test_core.hpp create mode 100644 nac3core/irrt/test/util.hpp create mode 100644 nac3core/src/codegen/irrt/test.rs diff --git a/flake.nix b/flake.nix index d10bab12..e7941831 100644 --- a/flake.nix +++ b/flake.nix @@ -13,6 +13,7 @@ '' mkdir -p $out/bin ln -s ${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang $out/bin/clang-irrt + ln -s ${pkgs.llvmPackages_14.clang}/bin/clang $out/bin/clang-irrt-test ln -s ${pkgs.llvmPackages_14.llvm.out}/bin/llvm-as $out/bin/llvm-as-irrt ''; nac3artiq = pkgs.python3Packages.toPythonModule ( @@ -23,6 +24,7 @@ cargoLock = { lockFile = ./Cargo.lock; }; + cargoTestFlags = [ "--features" "test" ]; passthru.cargoLock = cargoLock; nativeBuildInputs = [ pkgs.python3 (pkgs.wrapClangMulti pkgs.llvmPackages_14.clang) llvm-tools-irrt pkgs.llvmPackages_14.llvm.out llvm-nac3 ]; buildInputs = [ pkgs.python3 llvm-nac3 ]; diff --git a/nac3core/Cargo.toml b/nac3core/Cargo.toml index 724e0c8c..6e66f440 100644 --- a/nac3core/Cargo.toml +++ b/nac3core/Cargo.toml @@ -1,3 +1,6 @@ +[features] +test = [] + [package] name = "nac3core" version = "0.1.0" diff --git a/nac3core/build.rs b/nac3core/build.rs index 9466f37d..345b042b 100644 --- a/nac3core/build.rs +++ b/nac3core/build.rs @@ -3,22 +3,32 @@ use std::{ env, fs::File, io::Write, - path::Path, + path::{Path, PathBuf}, process::{Command, Stdio}, }; -fn main() { - // Define relevant directories - let out_dir = env::var("OUT_DIR").unwrap(); - let out_dir = Path::new(&out_dir); - let irrt_dir = Path::new("irrt"); +const CMD_IRRT_CLANG: &str = "clang-irrt"; +const CMD_IRRT_CLANG_TEST: &str = "clang-irrt-test"; +const CMD_IRRT_LLVM_AS: &str = "llvm-as-irrt"; - let irrt_cpp_path = irrt_dir.join("irrt.cpp"); +fn get_out_dir() -> PathBuf { + PathBuf::from(env::var("OUT_DIR").unwrap()) +} + +fn get_irrt_dir() -> &'static Path { + Path::new("irrt") +} + +/// Compile `irrt.cpp` for use in `src/codegen` +fn compile_irrt_cpp() { + let out_dir = get_out_dir(); + let irrt_dir = get_irrt_dir(); /* * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * Compiling for WASM32 and filtering the output with regex is the closest we can get. */ + let irrt_cpp_path = irrt_dir.join("irrt.cpp"); let flags: &[&str] = &[ "--target=wasm32", "-x", @@ -46,7 +56,7 @@ fn main() { println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap()); // Compile IRRT and capture the LLVM IR output - let output = Command::new("clang-irrt") + let output = Command::new(CMD_IRRT_CLANG) .args(flags) .output() .map(|o| { @@ -94,7 +104,7 @@ fn main() { // Assemble the emitted and filtered IR to .bc // That .bc will be integrated into nac3core's codegen - let mut llvm_as = Command::new("llvm-as-irrt") + let mut llvm_as = Command::new(CMD_IRRT_LLVM_AS) .stdin(Stdio::piped()) .arg("-o") .arg(out_dir.join("irrt.bc")) @@ -103,3 +113,48 @@ fn main() { llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap(); assert!(llvm_as.wait().unwrap().success()); } + +/// Compile `irrt_test.cpp` for testing +fn compile_irrt_test_cpp() { + let out_dir = get_out_dir(); + let irrt_dir = get_irrt_dir(); + + let exe_path = out_dir.join("irrt_test.out"); // Output path of the compiled test executable + let irrt_test_cpp_path = irrt_dir.join("irrt_test.cpp"); + let flags: &[&str] = &[ + irrt_test_cpp_path.to_str().unwrap(), + "-x", + "c++", + "-I", + irrt_dir.to_str().unwrap(), + "-g", + "-fno-discard-value-names", + "-O0", + "-Wall", + "-Wextra", + "-Werror=return-type", + "-lm", // for `tgamma()`, `lgamma()` + "-o", + exe_path.to_str().unwrap(), + ]; + + Command::new(CMD_IRRT_CLANG_TEST) + .args(flags) + .output() + .map(|o| { + assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap()); + o + }) + .unwrap(); + println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap()); +} + +fn main() { + compile_irrt_cpp(); + + // https://github.com/rust-lang/cargo/issues/2549 + // `cargo test -F test` to also build `irrt_test.cpp + if cfg!(feature = "test") { + compile_irrt_test_cpp(); + } +} diff --git a/nac3core/irrt/irrt.cpp b/nac3core/irrt/irrt.cpp index c9bcc714..29ca32cb 100644 --- a/nac3core/irrt/irrt.cpp +++ b/nac3core/irrt/irrt.cpp @@ -1,3 +1,4 @@ +#define IRRT_DEFINE_TYPEDEF_INTS #include /* diff --git a/nac3core/irrt/irrt/int_defs.hpp b/nac3core/irrt/irrt/int_defs.hpp index c24f7394..823dc536 100644 --- a/nac3core/irrt/irrt/int_defs.hpp +++ b/nac3core/irrt/irrt/int_defs.hpp @@ -1,8 +1,12 @@ #pragma once +// This is made toggleable since `irrt_test.cpp` itself would include +// headers that define these typedefs +#ifdef IRRT_DEFINE_TYPEDEF_INTS using int8_t = _BitInt(8); using uint8_t = unsigned _BitInt(8); using int32_t = _BitInt(32); using uint32_t = unsigned _BitInt(32); using int64_t = _BitInt(64); -using uint64_t = unsigned _BitInt(64); \ No newline at end of file +using uint64_t = unsigned _BitInt(64); +#endif \ No newline at end of file diff --git a/nac3core/irrt/irrt/utils.hpp b/nac3core/irrt/irrt/utils.hpp index 11138b5f..5306fe17 100644 --- a/nac3core/irrt/irrt/utils.hpp +++ b/nac3core/irrt/irrt/utils.hpp @@ -10,4 +10,12 @@ template const T& min(const T& a, const T& b) { return a > b ? b : a; } + +template +bool arrays_match(int len, T* as, T* bs) { + for (int i = 0; i < len; i++) { + if (as[i] != bs[i]) return false; + } + return true; +} } // namespace diff --git a/nac3core/irrt/irrt_test.cpp b/nac3core/irrt/irrt_test.cpp new file mode 100644 index 00000000..355dc01f --- /dev/null +++ b/nac3core/irrt/irrt_test.cpp @@ -0,0 +1,12 @@ +// This file will be compiled like a real C++ program, +// and we do have the luxury to use the standard libraries. +// That is if the nix flakes do not have issues... especially on msys2... +#include +#include +#include +#include + +int main() { + test::core::run(); + return 0; +} \ No newline at end of file diff --git a/nac3core/irrt/test/includes.hpp b/nac3core/irrt/test/includes.hpp new file mode 100644 index 00000000..304b0e01 --- /dev/null +++ b/nac3core/irrt/test/includes.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include +#include +#include +#include +#include + +/* + Include this header for every test_*.cpp +*/ \ No newline at end of file diff --git a/nac3core/irrt/test/test_core.hpp b/nac3core/irrt/test/test_core.hpp new file mode 100644 index 00000000..488c6152 --- /dev/null +++ b/nac3core/irrt/test/test_core.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace test { +namespace core { +void test_int_exp() { + BEGIN_TEST(); + + assert_values_match(125, __nac3_int_exp_impl(5, 3)); + assert_values_match(3125, __nac3_int_exp_impl(5, 5)); +} + +void run() { test_int_exp(); } +} // namespace core +} // namespace test \ No newline at end of file diff --git a/nac3core/irrt/test/util.hpp b/nac3core/irrt/test/util.hpp new file mode 100644 index 00000000..33e2e45e --- /dev/null +++ b/nac3core/irrt/test/util.hpp @@ -0,0 +1,116 @@ +#pragma once + +#include +#include + +template +void print_value(const T& value); + +template <> +void print_value(const int8_t& value) { + printf("%d", value); +} + +template <> +void print_value(const int32_t& value) { + printf("%d", value); +} + +template <> +void print_value(const uint8_t& value) { + printf("%u", value); +} + +template <> +void print_value(const uint32_t& value) { + printf("%u", value); +} + +template <> +void print_value(const float& value) { + printf("%f", value); +} + +template <> +void print_value(const double& value) { + printf("%f", value); +} + +void __begin_test(const char* function_name, const char* file, int line) { + printf("######### Running %s @ %s:%d\n", function_name, file, line); +} + +#define BEGIN_TEST() __begin_test(__FUNCTION__, __FILE__, __LINE__) + +void test_fail() { + printf("[!] Test failed. Exiting with status code 1.\n"); + exit(1); +} + +template +void debug_print_array(int len, const T* as) { + printf("["); + for (int i = 0; i < len; i++) { + if (i != 0) printf(", "); + print_value(as[i]); + } + printf("]"); +} + +void print_assertion_passed(const char* file, int line) { + printf("[*] Assertion passed on %s:%d\n", file, line); +} + +void print_assertion_failed(const char* file, int line) { + printf("[!] Assertion failed on %s:%d\n", file, line); +} + +void __assert_true(const char* file, int line, bool cond) { + if (cond) { + print_assertion_passed(file, line); + } else { + print_assertion_failed(file, line); + test_fail(); + } +} + +#define assert_true(cond) __assert_true(__FILE__, __LINE__, cond) + +template +void __assert_arrays_match(const char* file, int line, int len, + const T* expected, const T* got) { + if (arrays_match(len, expected, got)) { + print_assertion_passed(file, line); + } else { + print_assertion_failed(file, line); + printf("Expect = "); + debug_print_array(len, expected); + printf("\n"); + printf(" Got = "); + debug_print_array(len, got); + printf("\n"); + test_fail(); + } +} + +#define assert_arrays_match(len, expected, got) \ + __assert_arrays_match(__FILE__, __LINE__, len, expected, got) + +template +void __assert_values_match(const char* file, int line, T expected, T got) { + if (expected == got) { + print_assertion_passed(file, line); + } else { + print_assertion_failed(file, line); + printf("Expect = "); + print_value(expected); + printf("\n"); + printf(" Got = "); + print_value(got); + printf("\n"); + test_fail(); + } +} + +#define assert_values_match(expected, got) \ + __assert_values_match(__FILE__, __LINE__, expected, got) \ No newline at end of file diff --git a/nac3core/src/codegen/irrt/mod.rs b/nac3core/src/codegen/irrt/mod.rs index 5866f7ee..002cdb37 100644 --- a/nac3core/src/codegen/irrt/mod.rs +++ b/nac3core/src/codegen/irrt/mod.rs @@ -1,5 +1,7 @@ use crate::typecheck::typedef::Type; +mod test; + use super::{ classes::{ ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue, ListValue, NDArrayValue, diff --git a/nac3core/src/codegen/irrt/test.rs b/nac3core/src/codegen/irrt/test.rs new file mode 100644 index 00000000..9a91f41f --- /dev/null +++ b/nac3core/src/codegen/irrt/test.rs @@ -0,0 +1,26 @@ +#[cfg(test)] +mod tests { + use std::{path::Path, process::Command}; + + #[test] + fn run_irrt_test() { + assert!( + cfg!(feature = "test"), + "Please do `cargo test -F test` to compile `irrt_test.out` and run test" + ); + + let irrt_test_out_path = Path::new(concat!(env!("OUT_DIR"), "/irrt_test.out")); + let output = Command::new(irrt_test_out_path.to_str().unwrap()).output().unwrap(); + + if !output.status.success() { + eprintln!("irrt_test failed with status {}:", output.status); + eprintln!("====== stdout ======"); + eprintln!("{}", String::from_utf8(output.stdout).unwrap()); + eprintln!("====== stderr ======"); + eprintln!("{}", String::from_utf8(output.stderr).unwrap()); + eprintln!("===================="); + + panic!("irrt_test failed"); + } + } +}