feat: replace end2end test and add more test
This commit is contained in:
@@ -1,116 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Exit immediately if a command exits with a non-zero status.
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# --- Helper Function ---
|
|
||||||
run_test() {
|
|
||||||
local src_file="$1"
|
|
||||||
local expected_code="$2"
|
|
||||||
local base_name
|
|
||||||
base_name=$(basename "$src_file" .src)
|
|
||||||
local obj_file="tests/$base_name.o"
|
|
||||||
local exec_file="tests/$base_name"
|
|
||||||
|
|
||||||
echo "--- Running test: $src_file ---"
|
|
||||||
|
|
||||||
# 1. Compile the source file using our compiler
|
|
||||||
echo " [1/4] Compiling..."
|
|
||||||
cargo run --release -- "$src_file" -o "$obj_file" > /dev/null 2>&1
|
|
||||||
|
|
||||||
# 2. Link the object file with the system linker (gcc)
|
|
||||||
echo " [2/4] Linking with gcc..."
|
|
||||||
gcc "$obj_file" -o "$exec_file"
|
|
||||||
|
|
||||||
# 3. Run the executable
|
|
||||||
echo " [3/4] Running..."
|
|
||||||
set +e
|
|
||||||
./"$exec_file"
|
|
||||||
local actual_code=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 4. Check the exit code
|
|
||||||
echo " [4/4] Verifying exit code..."
|
|
||||||
if [ "$actual_code" -eq "$expected_code" ]; then
|
|
||||||
echo "SUCCESS: Exit code is $actual_code as expected."
|
|
||||||
else
|
|
||||||
echo "FAILURE: Expected exit code $expected_code, but got $actual_code."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up generated files
|
|
||||||
rm "$obj_file" "$exec_file"
|
|
||||||
echo "------------------------------------"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Helper Function ---
|
|
||||||
run_harness_test() {
|
|
||||||
local src_file="$1"
|
|
||||||
local harness_file="$2"
|
|
||||||
local expected_code="$3"
|
|
||||||
local base_name
|
|
||||||
base_name=$(basename "$src_file" .src)
|
|
||||||
local obj_file="tests/$base_name.o"
|
|
||||||
local exec_file="tests/$base_name"
|
|
||||||
|
|
||||||
echo "--- Running test: $src_file ---"
|
|
||||||
|
|
||||||
# 1. Compile the source file using our compiler
|
|
||||||
echo " [1/4] Compiling..."
|
|
||||||
cargo run --release -- "$src_file" -o "$obj_file" > /dev/null 2>&1
|
|
||||||
|
|
||||||
# 2. Link the object file with the system linker (gcc)
|
|
||||||
echo " [2/4] Linking with gcc..."
|
|
||||||
gcc "$obj_file" "$harness_file" -o "$exec_file"
|
|
||||||
|
|
||||||
# 3. Run the executable
|
|
||||||
echo " [3/4] Running..."
|
|
||||||
set +e
|
|
||||||
./"$exec_file"
|
|
||||||
local actual_code=$?
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# 4. Check the exit code
|
|
||||||
echo " [4/4] Verifying exit code..."
|
|
||||||
if [ "$actual_code" -eq "$expected_code" ]; then
|
|
||||||
echo "SUCCESS: Exit code is $actual_code as expected."
|
|
||||||
else
|
|
||||||
echo "FAILURE: Expected exit code $expected_code, but got $actual_code."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clean up generated files
|
|
||||||
rm "$obj_file" "$exec_file"
|
|
||||||
echo "------------------------------------"
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Test Cases ---
|
|
||||||
|
|
||||||
# Test a simple positive return value.
|
|
||||||
run_test "tests/return_42.src" 42
|
|
||||||
|
|
||||||
# Test a negative return value. Shell exit codes are unsigned 8-bit integers,
|
|
||||||
# so -69 (i8) wraps around to 187.
|
|
||||||
run_test "tests/return_neg_69.src" 187
|
|
||||||
|
|
||||||
# Test boolean operations.
|
|
||||||
run_harness_test "tests/booleans.src" "tests/booleans.c" 0
|
|
||||||
|
|
||||||
# Test if and else statements.
|
|
||||||
run_harness_test "tests/if-else.src" "tests/if-else.c" 0
|
|
||||||
|
|
||||||
# Test variable declarations and scoping.
|
|
||||||
run_test "tests/let_stmt.src" 30
|
|
||||||
|
|
||||||
# Test assignments and multi-assignments
|
|
||||||
run_test "tests/assign.src" 42
|
|
||||||
|
|
||||||
# Test while loops
|
|
||||||
run_test "tests/fibonacci.src" 55
|
|
||||||
|
|
||||||
# Test break and continue inside while loops
|
|
||||||
run_test "tests/while_break_continue.src" 16
|
|
||||||
|
|
||||||
echo "All end-to-end tests passed!"
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def run_test(test_file: Path, compiler_bin: Path):
|
||||||
|
with open(test_file, "r", encoding="utf-8") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
expected_code = 0
|
||||||
|
code_lines = []
|
||||||
|
harness_lines = None
|
||||||
|
current_section = None
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
trimmed = line.strip()
|
||||||
|
if trimmed.startswith("[") and trimmed.endswith("]"):
|
||||||
|
current_section = trimmed[1:-1]
|
||||||
|
if current_section == "harness":
|
||||||
|
harness_lines = []
|
||||||
|
elif current_section:
|
||||||
|
if current_section == "expected_return_code":
|
||||||
|
if trimmed:
|
||||||
|
expected_code = int(trimmed)
|
||||||
|
elif current_section == "code":
|
||||||
|
code_lines.append(line)
|
||||||
|
elif current_section == "harness":
|
||||||
|
harness_lines.append(line)
|
||||||
|
|
||||||
|
code = "".join(code_lines)
|
||||||
|
harness = "".join(harness_lines) if harness_lines is not None else None
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory(prefix=f"compiler_test_{test_file.stem}_") as temp_dir_name:
|
||||||
|
temp_dir = Path(temp_dir_name)
|
||||||
|
|
||||||
|
code_path = temp_dir / "code.src"
|
||||||
|
obj_path = temp_dir / "code.o"
|
||||||
|
exec_path = temp_dir / "exec"
|
||||||
|
|
||||||
|
code_path.write_text(code, encoding="utf-8")
|
||||||
|
|
||||||
|
# 1. Invoke the custom compiler
|
||||||
|
comp_res = subprocess.run(
|
||||||
|
[str(compiler_bin), str(code_path), "-o", str(obj_path)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if comp_res.returncode != 0:
|
||||||
|
out = comp_res.stdout.strip()
|
||||||
|
err = comp_res.stderr.strip()
|
||||||
|
error_msg = f"{out}\n{err}".strip() or "Compiler failed with no output"
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
# 2. Invoke GCC to link the object file (and compile harness if provided)
|
||||||
|
gcc_cmd = ["gcc", str(obj_path), "-o", str(exec_path)]
|
||||||
|
if harness is not None:
|
||||||
|
harness_path = temp_dir / "harness.c"
|
||||||
|
harness_path.write_text(harness, encoding="utf-8")
|
||||||
|
gcc_cmd.append(str(harness_path))
|
||||||
|
|
||||||
|
gcc_res = subprocess.run(gcc_cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if gcc_res.returncode != 0:
|
||||||
|
out = gcc_res.stdout.strip()
|
||||||
|
err = gcc_res.stderr.strip()
|
||||||
|
error_msg = f"{out}\n{err}".strip() or "GCC failed with no output"
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
# 3. Run the resulting executable
|
||||||
|
exec_res = subprocess.run([str(exec_path)], capture_output=True, text=True)
|
||||||
|
|
||||||
|
if exec_res.returncode != expected_code:
|
||||||
|
raise Exception(f"Expected return code {expected_code}, but got {exec_res.returncode}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
GREEN = '\033[92m'
|
||||||
|
RED = '\033[91m'
|
||||||
|
RESET = '\033[0m'
|
||||||
|
|
||||||
|
project_root = Path(__file__).resolve().parent
|
||||||
|
tests_dir = project_root / "tests"
|
||||||
|
compiler_bin = project_root / "target" / "debug" / "compiler"
|
||||||
|
|
||||||
|
# Automatically run cargo build to ensure the compiler is compiled and up to date
|
||||||
|
build_status = subprocess.run(["cargo", "build"], cwd=project_root)
|
||||||
|
if build_status.returncode != 0:
|
||||||
|
print("Failed to build the compiler binary.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
test_files = sorted(tests_dir.glob("*.test"))
|
||||||
|
|
||||||
|
all_passed = True
|
||||||
|
failed_tests = []
|
||||||
|
|
||||||
|
for test_file in test_files:
|
||||||
|
test_name = test_file.name
|
||||||
|
|
||||||
|
# Format the output to align results, padding with dots
|
||||||
|
msg = f"Running {test_name} "
|
||||||
|
print(f"{msg:.<50} ", end="", file=sys.stderr, flush=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_test(test_file, compiler_bin)
|
||||||
|
print(f"{GREEN}ok{RESET}", file=sys.stderr)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{RED}FAIL{RESET}", file=sys.stderr)
|
||||||
|
print(f" Reason: {e}", file=sys.stderr)
|
||||||
|
all_passed = False
|
||||||
|
failed_tests.append(test_name)
|
||||||
|
|
||||||
|
if not all_passed:
|
||||||
|
print(f"\nOne or more E2E tests failed: {failed_tests}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
42
|
||||||
|
|
||||||
|
[code]
|
||||||
fn main() -> i32 {
|
fn main() -> i32 {
|
||||||
let a = 0;
|
let a = 0;
|
||||||
let b = 0;
|
let b = 0;
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fn eq(a: i32, b: i32) -> bool { return a == b; }
|
|
||||||
fn neq(a: i32, b: i32) -> bool { return a != b; }
|
|
||||||
fn lt(a: i32, b: i32) -> bool { return a < b; }
|
|
||||||
fn lte(a: i32, b: i32) -> bool { return a <= b; }
|
|
||||||
fn gt(a: i32, b: i32) -> bool { return a > b; }
|
|
||||||
fn gte(a: i32, b: i32) -> bool { return a >= b; }
|
|
||||||
fn not_bool(a: bool) -> bool { return !a; }
|
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
0
|
||||||
|
|
||||||
|
[code]
|
||||||
|
fn eq(a: i32, b: i32) -> bool { return a == b; }
|
||||||
|
fn neq(a: i32, b: i32) -> bool { return a != b; }
|
||||||
|
fn lt(a: i32, b: i32) -> bool { return a < b; }
|
||||||
|
fn lte(a: i32, b: i32) -> bool { return a <= b; }
|
||||||
|
fn gt(a: i32, b: i32) -> bool { return a > b; }
|
||||||
|
fn gte(a: i32, b: i32) -> bool { return a >= b; }
|
||||||
|
fn not_bool(a: bool) -> bool { return !a; }
|
||||||
|
|
||||||
|
[harness]
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
extern bool eq(int a, int b);
|
extern bool eq(int a, int b);
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
15
|
||||||
|
|
||||||
|
[code]
|
||||||
|
fn main() -> i32 {
|
||||||
|
let val = 0;
|
||||||
|
|
||||||
|
if true {
|
||||||
|
val = 10;
|
||||||
|
} else {
|
||||||
|
val = 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
if false {
|
||||||
|
val = val + 100;
|
||||||
|
} else {
|
||||||
|
val = val + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
7
|
||||||
|
|
||||||
|
[code]
|
||||||
|
fn main() -> i32 {
|
||||||
|
let n = 4;
|
||||||
|
|
||||||
|
if n == 1 {
|
||||||
|
return 10;
|
||||||
|
} else if n == 2 {
|
||||||
|
return 20;
|
||||||
|
} else if n == 3 {
|
||||||
|
return 30;
|
||||||
|
} else {
|
||||||
|
return 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
42
|
||||||
|
|
||||||
|
[code]
|
||||||
|
fn main() -> i32 {
|
||||||
|
let x = 5;
|
||||||
|
let y = 10;
|
||||||
|
|
||||||
|
if x > 0 {
|
||||||
|
if y < 5 {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
100
|
||||||
|
|
||||||
|
[code]
|
||||||
|
fn main() -> i32 {
|
||||||
|
let a = 10;
|
||||||
|
|
||||||
|
if a == 10 {
|
||||||
|
a = 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
if a == 5 {
|
||||||
|
a = 0; // Should be skipped
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
55
|
||||||
|
|
||||||
|
[code]
|
||||||
fn main() -> i32 {
|
fn main() -> i32 {
|
||||||
let n = 10;
|
let n = 10;
|
||||||
let a = 0;
|
let a = 0;
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
extern int min(int a, int b);
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
if (min(12, 15)) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
fn min(a: i32, b: i32) -> i32 {
|
|
||||||
if a < b {
|
|
||||||
return a;
|
|
||||||
} else {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
0
|
||||||
|
|
||||||
|
[code]
|
||||||
|
fn min(a: i32, b: i32) -> i32 {
|
||||||
|
if a < b {
|
||||||
|
return a;
|
||||||
|
} else {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[harness]
|
||||||
|
extern int min(int a, int b);
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (min(12, 15)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
30
|
||||||
|
|
||||||
|
[code]
|
||||||
fn main() -> i32 {
|
fn main() -> i32 {
|
||||||
let a = 10;
|
let a = 10;
|
||||||
let b: i32 = 20;
|
let b: i32 = 20;
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
42
|
||||||
|
|
||||||
|
[code]
|
||||||
fn main() -> i32 {
|
fn main() -> i32 {
|
||||||
return 10 * 4 + 2;
|
return 10 * 4 + 2;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
187
|
||||||
|
|
||||||
|
[code]
|
||||||
fn main() -> i8 {
|
fn main() -> i8 {
|
||||||
return -69;
|
return -69;
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
[expected_return_code]
|
||||||
|
16
|
||||||
|
|
||||||
|
[code]
|
||||||
fn main() -> i32 {
|
fn main() -> i32 {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
Reference in New Issue
Block a user