From 6ace4dea0178f4d94de98f2a8cd13289e8a99ba1 Mon Sep 17 00:00:00 2001 From: Brandon Lucas Date: Fri, 13 Feb 2026 23:35:22 -0500 Subject: [PATCH] feat: add integration tests and lint script for examples - Add 30 integration tests that verify all examples and projects parse and type-check correctly - Add scripts/lint-examples.sh for quick pre-commit validation - Tests will catch regressions like missing `run ... with {}` syntax or broken escape sequences Run with: cargo test example_tests Or quick check: ./scripts/lint-examples.sh Co-Authored-By: Claude Opus 4.5 --- scripts/lint-examples.sh | 71 ++++++++++++++++ src/main.rs | 173 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100755 scripts/lint-examples.sh diff --git a/scripts/lint-examples.sh b/scripts/lint-examples.sh new file mode 100755 index 0000000..bca6e07 --- /dev/null +++ b/scripts/lint-examples.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# Lint all Lux example and project files +# Run this before committing to catch parse/type errors early + +set -e + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(dirname "$SCRIPT_DIR")" +LUX="${ROOT_DIR}/target/release/lux" + +# Build if needed +if [ ! -f "$LUX" ]; then + echo "Building lux..." + cd "$ROOT_DIR" && cargo build --release +fi + +FAILED=0 +PASSED=0 + +check_file() { + local file="$1" + local output + # Run the file and capture output, timeout after 10s + if output=$(timeout 10 "$LUX" "$file" 2>&1); then + echo -e "${GREEN}OK${NC}: $file" + ((PASSED++)) || true + else + # Check if it's just a runtime error vs parse/type error + if echo "$output" | grep -q "Parse error\|Type error\|Type Error\|Type Mismatch"; then + echo -e "${RED}FAIL${NC}: $file" + echo "$output" | head -20 + echo "" + ((FAILED++)) || true + else + # Runtime errors are OK for linting (file parses and type checks) + echo -e "${GREEN}OK${NC}: $file (runtime error expected)" + ((PASSED++)) || true + fi + fi +} + +echo "=== Linting Lux Examples ===" +echo "" + +for file in "$ROOT_DIR"/examples/*.lux; do + check_file "$file" +done + +echo "" +echo "=== Linting Lux Projects ===" +echo "" + +for file in "$ROOT_DIR"/projects/*/main.lux; do + check_file "$file" +done + +echo "" +echo "=== Summary ===" +echo "Passed: $PASSED" +echo "Failed: $FAILED" + +if [ $FAILED -gt 0 ]; then + exit 1 +fi + +echo "" +echo "All files pass parse and type checking!" diff --git a/src/main.rs b/src/main.rs index 71fd503..8e8ea0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2814,4 +2814,177 @@ c")"#; assert_eq!(eval(source).unwrap(), "42"); } } + + // Integration tests for example files + mod example_tests { + use super::*; + use std::fs; + use std::path::Path; + + fn check_file(path: &str) -> Result<(), String> { + let source = fs::read_to_string(path) + .map_err(|e| format!("Failed to read {}: {}", path, e))?; + + let program = Parser::parse_source(&source) + .map_err(|e| format!("Parse error in {}: {}", path, e))?; + + let mut checker = TypeChecker::new(); + checker.check_program(&program).map_err(|errors| { + format!("Type errors in {}:\n{}", path, + errors.iter().map(|e| e.to_string()).collect::>().join("\n")) + })?; + + Ok(()) + } + + #[test] + fn test_example_hello() { + check_file("examples/hello.lux").unwrap(); + } + + #[test] + fn test_example_factorial() { + check_file("examples/factorial.lux").unwrap(); + } + + #[test] + fn test_example_functional() { + check_file("examples/functional.lux").unwrap(); + } + + #[test] + fn test_example_pipelines() { + check_file("examples/pipelines.lux").unwrap(); + } + + #[test] + fn test_example_tailcall() { + check_file("examples/tailcall.lux").unwrap(); + } + + #[test] + fn test_example_effects() { + check_file("examples/effects.lux").unwrap(); + } + + #[test] + fn test_example_handlers() { + check_file("examples/handlers.lux").unwrap(); + } + + #[test] + fn test_example_builtin_effects() { + check_file("examples/builtin_effects.lux").unwrap(); + } + + #[test] + fn test_example_datatypes() { + check_file("examples/datatypes.lux").unwrap(); + } + + #[test] + fn test_example_generics() { + check_file("examples/generics.lux").unwrap(); + } + + #[test] + fn test_example_traits() { + check_file("examples/traits.lux").unwrap(); + } + + #[test] + fn test_example_interpolation() { + check_file("examples/interpolation.lux").unwrap(); + } + + #[test] + fn test_example_behavioral() { + check_file("examples/behavioral.lux").unwrap(); + } + + #[test] + fn test_example_behavioral_types() { + check_file("examples/behavioral_types.lux").unwrap(); + } + + #[test] + fn test_example_versioning() { + check_file("examples/versioning.lux").unwrap(); + } + + #[test] + fn test_example_schema_evolution() { + check_file("examples/schema_evolution.lux").unwrap(); + } + + #[test] + fn test_example_file_io() { + check_file("examples/file_io.lux").unwrap(); + } + + #[test] + fn test_example_json() { + check_file("examples/json.lux").unwrap(); + } + + #[test] + fn test_example_random() { + check_file("examples/random.lux").unwrap(); + } + + #[test] + fn test_example_statemachine() { + check_file("examples/statemachine.lux").unwrap(); + } + + #[test] + fn test_example_shell() { + check_file("examples/shell.lux").unwrap(); + } + + #[test] + fn test_example_http() { + check_file("examples/http.lux").unwrap(); + } + + #[test] + fn test_example_http_server() { + check_file("examples/http_server.lux").unwrap(); + } + + #[test] + fn test_example_jit_test() { + check_file("examples/jit_test.lux").unwrap(); + } + + #[test] + fn test_project_json_parser() { + check_file("projects/json-parser/main.lux").unwrap(); + } + + #[test] + fn test_project_markdown_converter() { + check_file("projects/markdown-converter/main.lux").unwrap(); + } + + #[test] + fn test_project_todo_app() { + check_file("projects/todo-app/main.lux").unwrap(); + } + + #[test] + fn test_project_mini_interpreter() { + check_file("projects/mini-interpreter/main.lux").unwrap(); + } + + #[test] + fn test_project_guessing_game() { + check_file("projects/guessing-game/main.lux").unwrap(); + } + + #[test] + fn test_project_rest_api() { + check_file("projects/rest-api/main.lux").unwrap(); + } + } }