This directory contains test programs for the Ion compiler.
Use the test harness script:
cd tests
chmod +x test_runner.sh
./test_runner.shOr from the project root:
cd tests && ./test_runner.shOn Windows, use Git Bash, not WSL bash. WSL often cannot run ion-compiler.exe.
The harness defaults to ../target/release/ion-compiler. Rebuild it after compiler changes:
cargo build --releaseAt startup the harness precompiles runtime/ion_runtime.c once to .ion_test_runtime.o (override with RUNTIME_OBJ) and links that object for every run test and test_multifile, instead of recompiling the runtime per test.
If the release build fails with "Access is denied", stop ion-lsp (or any running ion-compiler / ion-lsp process) and retry. You can also build only the compiler:
cargo build --release --bin ion-compilerTo run against the debug compiler instead:
COMPILER=../target/debug/ion-compiler.exe ./test_runner.shEach test is an Ion source file (.ion) that should:
- Compile successfully to C
- Generate C that compiles without errors
- Produce an executable that runs and returns the expected exit code
The test runner prints pass/fail counts when it finishes. Do not rely on hardcoded totals in documentation.
test_basic.ion- Basic function and returntest_arithmetic.ion- Arithmetic operationstest_move_basic.ion- Move semanticstest_ref_valid.ion- Valid reference usagetest_double_mut_borrow_error.ion- Second&muton same variable (negative,BorrowConflict)test_mut_shared_borrow_error.ion-&mutwhile shared borrow active (negative)test_move_while_borrowed_error.ion- Move into call whilelet r = &xis active (negative)test_copy_use_while_mut_borrowed_error.ion- Copy-type read whilelet r = &mut xis active (negative)test_copy_use_while_shared_borrowed_error.ion- Copy-type read whilelet r = &xis active (negative)test_assign_while_borrowed_error.ion- Assignment whilelet r = &xis active (negative)test_mut_borrow_block_ok.ion- Mutable borrow ends withifbranch scope (exit 62)test_shared_borrow_ok.ion- Multiple&Tborrows allowed (exit 63)test_nested_shared_borrow_ok.ion- Outer shared borrow survives innerifscope (exit 64)test_field_double_mut_borrow_error.ion- Second&muton disjoint fields of same owner (negative,BorrowConflict)test_field_mut_borrow_blocks_owner_error.ion- Field read while root owner is mut-borrowed (negative)test_field_whole_mut_borrow_error.ion- Whole-owner&mutwhile field&mutis active (negative)test_field_shared_borrow_ok.ion- Multiple shared field borrows on same owner (exit 65)test_field_mut_borrow_scope_ok.ion- Field mut borrow released by innerifscope (exit 66)test_send_basic.ion- Send/Send smoke testtest_defer_basic.ion- Defer statementstest_defer_block.ion- Block-scoped defertest_scope_drop_block.ion- Automatic Vec drop at block exittest_struct_field_drop.ion- Struct and enum field drops at block exit (nested String fields, enum payload)test_struct_field_drop_vec.ion- Struct field holdingVec<int>drop at block exit (exit 46)test_struct_field_drop_box.ion- Box field drop at block exit (exit 44)test_struct_enum_empty_drop.ion- Enum drop with Empty variant only (exit 45)test_move_call_drop.ion- No double-drop when String is moved into a function calltest_move_in_loop_ok.ion- Borrowing a non-copy value across loop iterations (no move in body)test_move_in_loop_copy.ion- Fresh copy-type bindings each loop iteration (exit 30)test_scope_drop_elif.ion- Vec drop inside an else-if branchtest_channel_basic.ion- Channel operationstest_channel_contention.ion- Four producer spawns, one channel each; sum exit 73test_channel_shutdown.ion- Worker recv loop;Senderdropped insend_jobs(exit 91)test_spawn_basic.ion- Spawn statementstest_spawn_channel.ion- Cross-thread channel send/recv via spawn (channel handle drop at scope exit)test_if_basic.ion- If statements with elsetest_if_no_else.ion- If statements without elsetest_if_elif.ion- If with else-if chaintest_if_elif_no_else.ion- Else-if without a final elsetest_struct_basic.ion- Struct declarations
test_enum_basic.ion- Enum declarations and literalstest_enum_generic.ion- Generic enum typestest_match_basic.ion- Pattern matchingtest_match_pattern_bindings.ion- Pattern matching with bindingstest_match_complex.ion- Complex pattern matching scenariostest_while_basic.ion- While loopstest_break_continue.ion-breakandcontinueinwhileandforloopstest_call_basic.ion- Function callstest_string_basic.ion- String literalstest_string_from.ion- String::from() functiontest_string_new.ion- String::new() functiontest_string_push_str.ion- String::push_str() functiontest_string_push_str_owned.ion- String::push_str() with an owned String argumenttest_string_push_byte.ion- String::push_byte() functiontest_string_eq.ion- String==and!=value equality (exit 55)test_generic_types.ion- Generic type systemtest_generic_struct.ion- Generic struct typestest_box_basic.ion- Box heap allocationtest_box_ops.ion- Box operations (new, unwrap)test_vec_basic.ion- Vec dynamic arraystest_vec_new.ion- Vec::new() functiontest_vec_push_pop.ion- Vec push and pop operationstest_vec_get_set.ion- Vec get and set operationstest_vec_capacity.ion- Vec capacity managementtest_vec_i32.ion-Vec<i32>with annotatedVec::new,i32indicestest_vec_struct.ion-Vecwith struct elements, annotatedVec::new, andforiterationtest_vec_get_struct.ion-Vec::getwith struct elements containingStringtest_vec_get_multi_option.ion-match Vec::getpicksOption<T>per vector element typetest_vec_get_putback.ion- put-back scan afterVec::getmove-out preserves vector lengthtest_vec_get_ref_scan.ion- read-only scan viaVec::get_refwithout hollowing the vectortest_vec_get_ref_oob.ion-Vec::get_refon empty index, OOB, and valid indextest_vec_get_ref_set_after.ion-Vec::setafterget_refborrow endstest_vec_get_ref_string.ion-Vec::get_refwithStringelementstest_vec_get_ref_nested.ion-get_refon nestedVecin a struct fieldtest_vec_get_ref_scan_nested_vec.ion- repeatedget_refscan overVecof structs with nestedVecfields (no heap corruption)test_vm_execute.ion- VM-style interpreter loop:get_refenum dispatch, struct field+=, method calls on&mut VMfields (exit 42)test_field_assign_plus.ion- struct field assignment and+=on owned and&mutreceivers (exit 42)test_vec_enum_push_set.ion-Vecof enums: push,get_refdispatch, andsetround-trip (exit 50)test_nested_vec_generic.ion- nested genericVec<Vec<int>>parse and sum (exit 60)test_match_break_in_loop.ion-breakinsidematchwithinloopwithout stop-flag workaround (exit 3)test_str_from_literal.ion-&strparameters and string literal call-site coercion (exit 71)test_vec_string_mangle.ion-Vec<String>monomorphizes asVec_Stringin generated Ctest_vec_search_index_ok.ion-find_indexreturnsint; caller usesVec::get(exit 84)test_vec_push_mut_param.ion-Vec::pushthrough&mut Vec<T>parametertest_vec_push_nested_call.ion-Vec::pushwith nested call expression valuetest_vec_push_struct_var.ion-Vec::pushwith a struct variable (address-of lvalue)test_struct_field_move_vec.ion- move aVecout of a struct field without double-freetest_tuple_vec_int.ion- tuple(Vec<T>, int)mangling and returntest_tuple_vec_int_epilogue.ion- tuple return with loop body before epiloguereturntest_tuple_fn_lit.ion- fn literal returning a tuple;ret_valcompound inittest_call_struct_field_move.ion- struct field moved into a call argument without broken C
test_module_basic.ion- Module system and importstest_module_visibility.ion- Module visibility control (negative test)test_ffi_basic.ion- Foreign Function Interface (FFI)
test_array_basic.ion- Fixed-size arraystest_array_literal.ion- Array literalstest_array_indexing.ion- Array indexing operationstest_index_i32.ion- Array indexing withi32index variablestest_array_bounds_safe.ion- Array bounds checking with valid indices (Safety Enhancement)test_unsafe_array_indexing.ion- Unsafe array indexing without bounds checking (Safety Enhancement)test_slice_bounds_codegen.ion- Slice bounds checking in generated C (codegen grep)test_unsafe_slice_indexing.ion- Unsafe slice indexing without bounds checking (codegen grep)test_slice_bounds_panic.ion- Slice out-of-bounds panic (harness: codegen grep only; manual run below)test_slice_basic.ion- Dynamically sized slicestest_slice_indexing.ion- Slice indexing operationstest_array_to_slice_coercion.ion-&[T; N]to&[]Tat call sites (exit 10)test_array_to_slice_let.ion-&[T; N]to&[]Tin let bindings (exit 11)test_array_bounds_panic.ion- Array out-of-bounds panic (harness: codegen grep only; manual run below)test_unsafe_basic.ion- Unsafe blockstest_unsafe_extern_required.ion- Unsafe requirement for extern calls (negative test)test_multifile.ion- Multi-file compilationtest_multi_struct.ion- Multi-file module with private struct in librarytest_multi_fmt_io.ion- Multi-file link with bothfmtandiostdlib modules
test_bool_literal.ion- Boolean literals (true,false)test_bool_operations.ion- Boolean type usagetest_bool_comparison.ion- Comparison operators returningbooltest_if_bool_required.ion- Negative test:ifrequiresboolconditiontest_float_literal.ion- Floating-point literals (.5,3.,1e9, etc.)test_float_arithmetic.ion- Float arithmetic operationstest_float_promotion.ion- Float type promotion rulestest_float_comparison.ion- Float comparison operationstest_integer_types.ion- All integer type declarationstest_integer_promotion.ion- Integer type promotion rulestest_integer_signed_unsigned.ion- Signed/unsigned integer mixingtest_type_alias_basic.ion- Basic type aliasestest_type_alias_generic.ion- Generic type aliasestest_type_alias_resolution.ion- Type alias resolution in function signatures
test_method_call_basic.ion- Basic method call syntax (vec.push(),s.len())test_method_call_mut.ion- Mutable receiver method calls (vec.pop(),vec.set())test_method_call_generic.ion- Generic method calls with type inferencetest_method_call_chaining.ion- Chained method calls (vec.push().len())
test_channel_split.ion- Split Channel API (Sender<T>,Receiver<T>types)test_channel_string.ion-channel<String>send/recv; IR recv usesStringelement type (exit 3)test_channel_send_call_expr.ion-send(&tx, make())with non-lvalue operand codegen (exit 7)test_channel_send_field_call_expr.ion-send(&tx, make_pair().x)temps field of call result (exit 11)test_enum_struct_variant.ion- Struct-style enum variants with named fieldstest_for_loop.ion-for...inloop syntax with Vec iteration
test_escape_sequences.ion- Complete escape sequence support (\r,\t,\0, etc.)test_array_init.ion- Array initialization syntax ([value; count])test_bitwise_ops.ion- Bitwise operators (&,|,^,<<,>>)
test_comparison_operators.ion- Full comparison operators (<=,>=)test_type_cast.ion- Type casting withaskeywordtest_array_assignment.ion- Array element assignment (arr[i] = value)
test_hex_literals.ion- Hex integer literals (0xFF)test_bin_literals.ion- Binary integer literals (0b10101010)test_compound_assign.ion- Compound assignment (+=)test_for_compound_assign.ion-+=inside aforloop body (exit 6)test_loop_basic.ion- Infiniteloop { }withbreak
test_match_result_type.ion-matchinfers non-intresult type (boolvia-> boolhelper, exit 88)test_match_expr_rvalue.ion-matchas rvalue inletbinding (exit 91)test_match_arm_type_mismatch.ion- Mismatched arm result types (negative)test_match_arm_divergent_rvalue.ion- Diverging arm mixed with value arm in rvaluematch(negative)test_match_arm_if_else_value_rvalue.ion-if/elsevalue branches unify in rvaluematch(exit 80)test_match_arm_if_else_mixed_rvalue.ion- Mixed diverging and value paths within one rvalue arm (negative)
test_if_else_move_ok.ion- Move in diverging branch; use afterif(exit 60)test_if_else_move_error.ion- Move in fall-through branch; use afterif(negative)
-
test_fn_type_basic.ion- Store named function infn(int) -> intvariable; call through pointer (exit 77) -
test_fn_type_mismatch.ion- Function signature mismatch when coercing to fn type (negative) -
test_fn_literal_basic.ion- Capture-free fn literal stored infn(int) -> intand called (exit 12) -
test_fn_literal_callback.ion- Pass capture-free fn literal tofn(int) -> intparameter (exit 40) -
test_fn_literal_return.ion- Return capture-free fn literal from function (exit 6) -
test_fn_literal_capture_error.ion- Fn literal referencing outer binding (negative,ClosureCapture) -
test_fn_literal_ref_capture_error.ion- Fn literal referencing outer reference (negative,ClosureCapture) -
test_doc_comments.ion- Adjacent//doc comments attach to AST without affecting compile or runtime (exit 42) -
test_tuple_basic.ion- Tuple literals,.0/.1access, and destructuring (exit 81) -
test_io_print_str.ion- Safe I/O library:print_str()function -
test_io_print.ion- Safe I/O library:print()function for String -
test_io_println.ion- Safe I/O library:println()function for String
test_for_array.ion-for...inover fixed-size arraystest_for_string.ion-for...inoverString(byte iteration)test_match_guard.ion- Match arms withifguardstest_generic_field_access.ion- Field access on generic struct values
test_trait_bound_send_ok.ion- generic fn withT: Sendacceptsinttest_trait_bound_copy_ok.ion- generic fn withT: Copyacceptsinttest_trait_bound_copy_fn_ok.ion- generic fn withT: Copyaccepts a function identifiertest_trait_bound_eq_ok.ion- generic fn withT: Eqcompares intstest_trait_bound_eq_fn_ok.ion- generic fn withT: Eqcompares function pointerstest_trait_bound_send_error.ion-&intrejected forT: Sendtest_trait_bound_copy_error.ion-Stringrejected forT: Copytest_trait_bound_eq_error.ion-Vec<int>rejected forT: Eqtest_trait_bound_unknown_error.ion- unknown bound name rejected at declarationtest_io_print_int.ion-io::print_intdecimal outputtest_fmt_int_to_string.ion-fmt::int_to_stringdecimal output (0, positive, negative,int::MIN)test_fmt_println_int.ion-fmt::println_intvia stdlib merge; codegen usesio_print_intinfmt_print_inttest_fs_read.ion-fs::read_to_string_resultreadsfixtures/small.txt(exit 80)
The harness also runs ion-build (not via test_expectations.tsv):
build_hello/- minimalion.tomlproject; expects exit code 55../examples/worker_pool/-ion-buildsmoke on worker_pool example; expects exit code 0build_bad_main/- invalidmainpath; expectsmain file not foundon stderr
Set ION_BUILD to override the ion-build binary path (default ../target/release/ion-build).
test_move_error.ion- Use-after-move errorstest_move_in_loop.ion- Use-after-move when a non-copy value is moved inside a loop bodytest_move_in_loop_for.ion- Same rule forforloops (outer binding moved in body)test_move_channel_error.ion- Use-after-move on channel receiverstest_ref_return_error.ion- Reference escape errorstest_ref_return_error2.ion- Additional reference escape errorstest_channel_ref_error.ion- Non-Send channel elementstest_send_ref_error.ion- Non-Send send operationstest_spawn_ref_error.ion- Non-Send spawn capturestest_spawn_borrow_error.ion- Spawn capture while lasting borrow is active (negative)test_spawn_move_error.ion- Move errors in spawn blockstest_struct_ref_error.ion- Reference in struct fieldstest_enum_ref_error.ion- Reference in enum variantstest_match_ref_return_error.ion-match/branch returning&local (ReferenceEscape)test_vec_index_ref_error.ion- Helper almost returning&vec[i]viaVec::get(ReferenceEscape)test_vec_get_ref_return_error.ion- Returning&TfromVec::get_ref(ReferenceEscape)test_vec_get_ref_mut_error.ion-Vec::setwhileget_refborrow is active (BorrowConflict)test_nested_struct_ref_error.ion- Nested struct storing a reference field (ReferenceEscape)test_module_visibility.ion- Module visibility violationstest_unsafe_extern_required.ion- Unsafe requirement for extern callstest_if_bool_required.ion- Boolean requirement for if conditionstest_break_continue_error.ion-breakoutside of a loop (negative test)
test_array_bounds_panic.ion and test_slice_bounds_panic.ion call ion_panic and abort. The harness compiles them and greps generated C for the panic message; it does not run the binaries.
From tests/ (Git Bash):
../target/release/ion-compiler test_array_bounds_panic.ion
gcc test_array_bounds_panic.c ../runtime/ion_runtime.c -o test_array_bounds_panic \
-I. -I.. -I../runtime -lpthread -lws2_32
./test_array_bounds_panic
# Expect stderr: Ion panic: Array index out of bounds
../target/release/ion-compiler test_slice_bounds_panic.ion
gcc test_slice_bounds_panic.c ../runtime/ion_runtime.c -o test_slice_bounds_panic \
-I. -I.. -I../runtime -lpthread -lws2_32
./test_slice_bounds_panic
# Expect stderr: Ion panic: Slice index out of bounds- Create a new
test_<feature>.ionfile in this directory - Add one line to
test_expectations.tsv(tab-separated):
file kind exit error_pattern must_match must_not_match
test_myfeature.ion run 42
Kinds: run (compile+run+exit code), error (compile must fail; error_pattern greps CLI stderr), cgen (must_match / optional must_not_match on generated .c).
For error rows, a matching failure message is required; a wrong pattern is a harness failure (not a pass).
- Document the test in this README under the appropriate category
Special cases (not in the manifest):
test_multifile.ion: multi-file mode harness intest_runner.shtest_array_bounds_panic.ion/test_slice_bounds_panic.ion: codegen-only in manifest; runtime panic is manual (see below)
COMPILER: Path to the ion-compiler binary (default:../target/release/ion-compiler)ION_BUILD: Path to the ion-build binary (default:../target/release/ion-build)CC: C compiler to use (default:gcc)CFLAGS: Extra C compiler flags for generated C and the precompiled runtime (default: empty). CI uses-fsanitize=address,undefinedfor sanitizer smoke and runs the full harness with-Wall -Wextra -Werroron Linux.LDFLAGS: Extra C linker flags for generated test executables (default: empty). Pair withCFLAGSfor sanitizer runtime flags when needed.RUNTIME_OBJ: Path to the precompiled runtime object file (default:.ion_test_runtime.ointests/). Rebuilt whenruntime/ion_runtime.cis newer than the object.
Example:
COMPILER=../target/debug/ion-compiler ION_BUILD=../target/debug/ion-build CC=clang ./test_runner.sh
CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" LDFLAGS="-fsanitize=address,undefined" ./test_runner.sh