Skip to content

Latest commit

 

History

History
704 lines (641 loc) · 53.7 KB

File metadata and controls

704 lines (641 loc) · 53.7 KB

Ion Compiler Tests

This directory contains test programs for the Ion compiler.

Prebuilt release archives ship tests/README.md only (not the .ion files here). Run this harness from a full source checkout.

Running Tests

Use the test harness script:

cd tests
chmod +x test_runner.sh
./test_runner.sh

Or from the project root:

cd tests && ./test_runner.sh

To run a subset, pass stems. A name ending in .ion matches that file only. A bare stem is a substring (test_enum_unannotated also runs test_enum_unannotated_let.ion). Every matching manifest row runs (run, error, cgen); other rows are skipped:

cd tests && ./test_runner.sh test_enum_unannotated_let.ion
cd tests && ./test_runner.sh test_enum_unannotated
./test_runner.sh --help

On 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 --release

At 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-compiler

To run against the debug compiler instead:

COMPILER=../target/debug/ion-compiler.exe ./test_runner.sh

Test Format

Each test is an Ion source file (.ion) that should:

  1. Compile successfully to C
  2. Generate C that compiles without errors
  3. Produce an executable that runs and returns the expected exit code

Test Categories

The test runner prints pass/fail counts when it finishes. Do not rely on hardcoded totals in documentation.

Core language

  • test_basic.ion - Basic function and return
  • test_arithmetic.ion - Arithmetic operations
  • test_move_basic.ion - Move semantics
  • test_ref_valid.ion - Valid reference usage
  • test_double_mut_borrow_error.ion - Second &mut on same variable (negative, BorrowConflict)
  • test_mut_shared_borrow_error.ion - &mut while shared borrow active (negative)
  • test_move_while_borrowed_error.ion - Move into call while let r = &x is active (negative)
  • test_copy_use_while_mut_borrowed_error.ion - Copy-type read while let r = &mut x is active (negative)
  • test_copy_use_while_shared_borrowed_error.ion - Copy-type read while let r = &x is active (negative)
  • test_assign_while_borrowed_error.ion - Assignment while let r = &x is active (negative)
  • test_mut_borrow_block_ok.ion - Mutable borrow ends with if branch scope (exit 62)
  • test_shared_borrow_ok.ion - Multiple &T borrows allowed (exit 63)
  • test_nested_shared_borrow_ok.ion - Outer shared borrow survives inner if scope (exit 64)
  • test_field_double_mut_borrow_error.ion - Second &mut on 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 &mut while field &mut is 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 inner if scope (exit 66)
  • test_send_basic.ion - Send/Send smoke test
  • test_defer_basic.ion - Defer statements
  • test_defer_block.ion - Block-scoped defer
  • test_defer_before_break.ion - defer before break runs; exit 1
  • test_scope_drop_block.ion - Automatic Vec drop at block exit
  • test_vec_string_scope_drop.ion - Vec<String> scope-exit drop frees each String then the backing array (exit 0); cgen asserts element ion_string_free and ion_vec_free; Linux CI leak-sanitizer
  • test_vec_struct_string_scope_drop.ion - struct field Vec<String> drops elements then the array (exit 0)
  • test_box_string_scope_drop.ion - Box<String> drops the String then ion_box_free (exit 0); Linux CI leak-sanitizer
  • test_loop_continue_nested_if_drop.ion - nested if continue drops inner and loop-body Strings; fall-through still uses a (exit 4); Linux CI leak-sanitizer
  • test_for_continue_drop_step.ion - for continue frees a body String and still visits later elements (exit 8); Linux CI leak-sanitizer
  • test_loop_break_drop.ion - loop break drops a body String (exit 0); Linux CI leak-sanitizer
  • test_nested_loop_inner_break_drop.ion - inner break drops the inner String and leaves outer Strings usable (exit 11); Linux CI leak-sanitizer
  • test_tuple_string_scope_drop.ion - (String, String) whole-value drop frees f0 then f1 (exit 3); Linux CI leak-sanitizer
  • test_array_string_scope_drop.ion - [String; 2] whole-value drop frees path[i] in increasing index (exit 0); Linux CI leak-sanitizer
  • test_drop_order_locals_struct_defer.ion - defers then reverse-local drops; struct fields in declaration order (exit 0); Linux CI leak-sanitizer
  • test_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 holding Vec<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 call
  • test_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_move_in_loop_break_ok.ion - Move then break with no later use (exit 7)
  • test_move_in_loop_return_ok.ion - Move then return inside a loop (exit 9)
  • test_move_nested_loop_inner_break_ok.ion - Inner break after move does not exit-snapshot the outer loop (exit 3)
  • test_scope_drop_elif.ion - Vec drop inside an else-if branch
  • test_channel_basic.ion - Channel operations
  • test_channel_contention.ion - Four clone_sender producers, one receiver until None; sum exit 73
  • test_channel_shutdown.ion - Worker recv until None after send_jobs drops the sender (exit 91)
  • test_channel_drop_race.ion - 64 rounds of send, spawn recv-until-None, drop the sender on main (exit 47)
  • test_channel_send_closed.ion - Send after last receiver drop returns Closed(T) (exit 9)
  • test_channel_queued_string_drop.ion - Queued String values dropped on channel destroy (exit 0)
  • test_channel_capacity.ion - channel<int>(4) same-thread send/recv (exit 10)
  • test_try_recv.ion - try_recv Empty then try_send Sent/Full then try_recv Msg (exit 0)
  • test_select.ion - select takes a ready recv arm; default polls when empty (exit 0)
  • test_select_timeout.ion - timeout(1) fires on an empty channel (exit 0)
  • test_select_blocking_send.ion - 64 rounds of blocking select vs spawn send (exit 7)
  • test_select_timeout_neg_error.ion - literal timeout(-1) is select timeout must be >= 0
  • test_join.ion - let h = spawn { }; join(h) (exit 0)
  • test_spawn_basic.ion - Spawn statements
  • test_spawn_channel.ion - Cross-thread channel send/recv via spawn (channel handle drop at scope exit)
  • test_if_basic.ion - If statements with else
  • test_if_no_else.ion - If statements without else
  • test_if_elif.ion - If with else-if chain
  • test_if_elif_no_else.ion - Else-if without a final else
  • test_struct_basic.ion - Struct declarations

Enums, generics, and collections

  • test_enum_basic.ion - Enum declarations and literals
  • test_enum_unannotated_let.ion - unannotated let flag = Flag::On / let status = Status::Ok { ... } types as the enum, not default int (exit 7); cgen asserts Flag flag = / Status status =
  • test_enum_generic_unannotated_let.ion - unannotated let x = Option::Some(42) types as Option<int> (exit 42); cgen asserts Option_int x =
  • test_option_some_int.ion - let x: Option<int> = Option::Some(1) (exit 1)
  • test_option_some_bool.ion - let x: Option<bool> = Option::Some(true) (exit 1)
  • test_option_some_bool_int_error.ion - let x: Option<bool> = Option::Some(1) is TypeMismatch
  • test_option_some_string_int_error.ion - let x: Option<String> = Option::Some(1) is TypeMismatch in ion-compiler (not gcc)
  • test_option_none_unannotated_error.ion - let empty = Option::None then use as Option<Box<Node>> needs a type annotation (cannot infer type parameter)
  • test_option_none_struct_field.ion - Node { next: Option::None } infers T from the field (exit 2)
  • test_option_none_call_arg.ion - take(Option::None) infers T from the parameter (exit 3)
  • test_option_none_call_arg_middle.ion - take2(5, Option::None) infers T in a non-final argument (exit 5)
  • test_result_err_call_arg.ion - take(Result::Err(7)) infers T from the parameter (exit 7)
  • test_send_option_none.ion - send(&tx, Option::None) infers T from Sender<Option<int>> (exit 0)
  • test_send_result_err.ion - send(&tx, Result::Err(4)) infers T from Sender<Result<int, int>> (exit 4)
  • test_send_result_send_ok.ion - SendResult<int> crosses a thread (exit 7)
  • test_box_new_option_none.ion - Box::new(Option::None) infers from expected Box<Option<int>> (exit 0)
  • test_array_option_none.ion - [Option::None] infers T from [Option<int>; 1] (exit 2)
  • test_tuple_option_none.ion - (Option::None, 3) infers T from (Option<int>, int) (exit 3)
  • test_array_option_some.ion - [Option::Some(4)] type-checks and emits Option_int not bare (Option) (exit 4)
  • test_array_repeat_option_none.ion - [Option::None; 1] infers from [Option<int>; 1] (exit 5)
  • test_array_option_none_call_arg.ion - array literal argument infers from the parameter type (exit 6)
  • test_array_option_none_struct_field.ion - array literal field infers from the struct field type (exit 7)
  • test_array_of_tuples_option_none.ion - [(Option::None, 8)] nested array-of-tuples (exit 8)
  • test_tuple_of_arrays_option_none.ion - ([Option::None], 9) nested tuple-of-arrays (exit 9)
  • test_array_result_err.ion - [Result::Err(Option::None)] infers payload from the array element type (exit 10)
  • test_assign_option_none.ion - x = Option::None infers from the left-hand side type (exit 11)
  • test_return_array_option_none.ion - return [Option::None] infers from the function return type (exit 12)
  • test_nested_array.ion - 2-D [int; 2] local, index, and index-assign (exit 17); cgen typedef int arr_int_2[2];
  • test_nested_array_field_param_return.ion - 2-D array struct field, parameter, and return (exit 7)
  • test_nested_array_3d.ion - 3-D array local (exit 6)
  • test_box_array.ion - Box<[int; 2]> (exit 30); cgen arr_int_2*
  • test_vec_array.ion - Vec<[int; 2]> push/get (exit 7); cgen Vec_arr_int_2
  • test_enum_option_payload_none.ion - Hold::H(Option::None) emits Option_int (exit 13)
  • test_enum_option_payload_some.ion - Hold::H(Option::Some(14)) (exit 14)
  • test_generic_struct_option_none.ion - S<int> { x: Option::None } emits Option_int (exit 15)
  • test_unannotated_let_non_int.ion - unannotated let q = p / let n = w.p / let v = origin() keep struct types, not default int (exit 5); cgen asserts Point q = / Point n = / Point v =
  • test_enum_generic.ion - Generic enum types
  • test_result_custom_enum.ion - Result<int, MyError> via stdlib/result.ion (Ok and Err, exit 0)
  • test_stdlib_option.ion - option::map, and_then, unwrap_or, expect (exit 8)
  • test_stdlib_result.ion - result::map, and_then, unwrap_or, expect (exit 8)
  • test_stdlib_string.ion - contains, starts_with, ends_with, trim_ascii, split_once (exit 3)
  • test_stdlib_hash.ion - compiler Hash for int and String (exit 0)
  • test_stdlib_map.ion - HashMap grows past 16 slots, replaces a key, and accepts a non-Copy Hash key and value (exit 170)
  • test_stdlib_math.ion - abs, clamp, checked_div (exit 4)
  • test_stdlib_path.ion - file_name, parent, join (exit 5)
  • test_stdlib_env.ion - missing name and PATH (exit 1)
  • test_stdlib_time.ion - millis is non-negative (exit 1)
  • test_match_ref_generic_enum_arith.ion - Match on &Status<int> and non-generic &Flag with arithmetic on copy payloads (exit 55)
  • test_match_basic.ion - Pattern matching
  • test_match_pattern_bindings.ion - Pattern matching with bindings
  • test_match_complex.ion - Complex pattern matching scenarios
  • test_nested_match_pattern.ion - Nested constructors Outer::Wrap(Inner::B) (exit 2)
  • test_nested_match_named.ion - Named-field nested constructor Outer::Wrap { inner: Inner::B } (exit 2)
  • test_nested_match_catchall_binding.ion - Nested constructor plus catch-all binding other (exit 4); default: arms are braced so Clang accepts a declaration after the label
  • test_nested_match_unknown_error.ion - Unknown nested enum Nope is TypeMismatch
  • test_nested_match_nonexhaustive_error.ion - Inner variant B not covered
  • test_while_basic.ion - While loops with assignment
  • test_break_continue.ion - break and continue in while and for loops
  • test_match_break_in_while.ion - break inside match within while exits the loop (exit 3)
  • test_call_basic.ion - Function calls
  • test_string_basic.ion - String literals
  • test_string_from.ion - String::from() function
  • test_string_call_arg_literal.ion - string literal as String call argument
  • test_string_new.ion - String::new() function
  • test_string_push_str.ion - String::push_str() function
  • test_string_push_str_owned.ion - String::push_str() with an owned String argument
  • test_string_push_byte.ion - String::push_byte() function
  • test_string_eq.ion - String == and != value equality (exit 55)
  • test_generic_types.ion - Generic type system
  • test_generic_struct.ion - Generic struct types
  • test_generic_enum_named_vec.ion - generic named-field enum in Vec: push, get_ref, owned get (exit 42)
  • test_vec_len_mut_param.ion - Vec::len / .len() on a &mut Vec field (exit 42)
  • test_handle_arena_basic.ion - stdlib/handle.ion insert, len, contains, remove Hit then Miss (exit 42)
  • test_handle_arena_stale.ion - reused slot: stale handle is Miss / contains false (exit 42)
  • test_handle_arena_get_ref.ion - local peek via Vec::get_ref on arena.slots through &World / &mut World (exit 42)
  • test_arena_get_ref.ion - compiler builtin arena.get_ref(handle) Occupied peek (exit 0)
  • test_arena_get_ref_field.ion - Arena::get_ref on an arena field through &Holder, &mut Holder, and a nested field (exit 0)
  • test_handle_arena_copy_struct.ion - Arena<Item> with String field; remove returns owned value (exit 42)
  • test_handle_arena_escape_error.ion - returning &T from a slot peek (ReferenceEscape)
  • test_box_basic.ion - Box heap allocation
  • test_box_new_struct_let_annotated.ion - let b: Box<Node> = Box::new(Node { ... }) multi-field unwrap (exit 10); cgen asserts ion_box_free(_box)
  • test_box_new_struct_let.ion - unannotated let b = Box::new(Node { ... }) multi-field unwrap (exit 10)
  • test_box_unwrap_struct_let.ion - unannotated let n = Box::unwrap(boxed) for Box<Node> (exit 10); cgen asserts Node* _box not int* _box
  • test_box_unwrap_same_scope.ion - unwrap then more work in the same function; moved box is not freed again (exit 3)
  • test_box_unwrap_binop.ion - sum + Box::unwrap(extra) once (exit 42)
  • test_box_unwrap_binop_error.ion - second Box::unwrap after an unwrap inside + (UseAfterMove)
  • test_recursive_struct_box.ion - linked list via Option<Box<Node>> (exit 3)
  • test_recursive_struct_mutual_box.ion - mutual recursion through Box (exit 30)
  • test_recursive_struct_vec.ion - self-referential Vec<Forest> (exit 2)
  • test_recursive_struct_send_ok.ion - recursive Box node satisfies T: Send (exit 9)
  • test_recursive_struct_infinite_error.ion - bare next: Node → InfiniteSize
  • test_recursive_struct_option_infinite_error.ion - next: Option<Node> → InfiniteSize
  • test_recursive_struct_eq_error.ion - recursive Box node rejected for T: Eq
  • test_recursive_box_ref_error.ion - Box<&int> field still ReferenceEscape
  • test_box_ref_let_error.ion - let b: Box<&int> = Box::new(&x) is ReferenceEscape
  • test_box_ref_unannotated_let_error.ion - let b = Box::new(&x) is ReferenceEscape
  • test_box_ref_param_error.ion - fn take(b: Box<&int>) is ReferenceEscape
  • test_vec_ref_let_error.ion - let v: Vec<&int> = Vec::new() is ReferenceEscape
  • test_box_ops.ion - Box operations (new, unwrap); cgen asserts ion_box_free(_box) and no ion_box_free(boxed)
  • test_vec_basic.ion - Vec dynamic arrays
  • test_vec_new.ion - Vec::new() function
  • test_vec_push_pop.ion - Vec push and pop operations; cgen asserts a stack Option (_ion_pop.tag = 0) and no ion_vec_pop or slot memset
  • test_vec_get_set.ion - Vec get and set operations; cgen asserts a stack Option (_ion_get.tag = 0) and no ion_vec_get or slot memset for Vec<int>
  • test_vec_set_string.ion - Vec::set of String drops the previous element (exit 0); Linux CI leak-sanitizer
  • test_vec_capacity.ion - Vec capacity management
  • test_vec_i32.ion - Vec<i32> with annotated Vec::new, i32 indices
  • test_vec_struct.ion - Vec with struct elements, annotated Vec::new, and for iteration
  • test_vec_get_struct.ion - Vec::get with struct elements containing String; cgen asserts _ion_get.tag = 0, a hollow memset, and no ion_vec_get
  • test_vec_get_multi_option.ion - match Vec::get picks Option<T> per vector element type
  • test_vec_get_putback.ion - put-back scan after Vec::get move-out preserves vector length
  • test_vec_get_putback_named.ion - put-back via let row then Vec::set(..., row) as the last match-arm statement
  • test_vec_get_ref_scan.ion - read-only scan via Vec::get_ref without hollowing the vector
  • test_vec_get_ref_oob.ion - Vec::get_ref on empty index, OOB, and valid index
  • test_vec_get_ref_set_after.ion - Vec::set after get_ref borrow ends
  • test_vec_get_ref_string.ion - Vec::get_ref with String elements
  • test_vec_get_ref_nested.ion - get_ref on nested Vec in a struct field
  • test_vec_get_ref_scan_nested_vec.ion - repeated get_ref scan over Vec of structs with nested Vec fields (no heap corruption)
  • test_string_get.ion - String::get byte peek and None on OOB/negative
  • test_string_get_method.ion - s.get(i) method desugar for String::get
  • test_slice_get_ref_scan.ion - read-only scan via Slice::get_ref over &[]Product, loop bound from Slice::len
  • test_slice_get_ref_oob.ion - Slice::get_ref OOB/negative/None and in-range
  • test_slice_get_ref_from_array.ion - array method arr.get_ref and Slice::get_ref(&arr, i)
  • test_slice_len.ion - Slice::len / s.len() on &[]T with two array sizes; vec.len() still uses Vec::len
  • test_vm_execute.ion - VM-style interpreter loop: get_ref enum dispatch, struct field +=, method calls on &mut VM fields (exit 42)
  • test_field_assign_plus.ion - struct field assignment and += on owned and &mut receivers (exit 42)
  • test_vec_enum_push_set.ion - Vec of enums: push, get_ref dispatch, and set round-trip (exit 50)
  • test_nested_vec_generic.ion - nested generic Vec<Vec<int>> parse and sum (exit 60)
  • test_match_break_in_loop.ion - break inside match within loop without stop-flag workaround (exit 3)
  • test_str_from_literal.ion - &str parameters and string literal call-site coercion (exit 71)
  • test_vec_string_mangle.ion - Vec<String> monomorphizes as Vec_String in generated C
  • test_vec_search_index_ok.ion - find_index returns int; caller uses Vec::get (exit 84)
  • test_vec_push_mut_param.ion - Vec::push through &mut Vec<T> parameter
  • test_vec_push_nested_call.ion - Vec::push with nested call expression value
  • test_vec_push_struct_var.ion - Vec::push with a struct variable; cgen asserts ion_vec_reserve_one, a typed store of the variable, and no ion_vec_push
  • test_struct_field_move_vec.ion - move a Vec out of a struct field without double-free
  • test_tuple_vec_int.ion - tuple (Vec<T>, int) mangling, return, and t.f0 drop; Linux CI leak-sanitizer
  • test_tuple_vec_int_epilogue.ion - tuple return with loop body before epilogue return
  • test_tuple_fn_lit.ion - fn literal returning a tuple; ret_val compound init
  • test_call_struct_field_move.ion - struct field moved into a call argument without broken C

Modules and FFI

  • test_module_basic.ion - Module system and imports
  • test_module_visibility.ion - Module visibility control (negative test)
  • test_ffi_basic.ion - Foreign Function Interface (FFI)
  • test_extern_rust_error.ion - extern "Rust" (or any non-"C" ABI) is a compile error

Arrays, slices, and unsafe

  • test_array_basic.ion - Fixed-size arrays
  • test_array_literal.ion - Array literals
  • test_array_indexing.ion - Array indexing and index assignment
  • test_index_i32.ion - Array indexing with i32 index variables
  • test_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 slices
  • test_slice_indexing.ion - Slice indexing operations
  • test_array_to_slice_coercion.ion - &[T; N] to &[]T at call sites (exit 10)
  • test_array_to_slice_let.ion - &[T; N] to &[]T in let bindings (exit 11)
  • test_array_bounds_panic.ion - Array out-of-bounds panic (harness: codegen grep only; manual run below)
  • test_assign_index_oob.ion - arr[i] = bounds-check panic (codegen grep)
  • test_assign_int_cgen.ion - plain int assignment stays a single store (codegen grep)
  • test_assign_replace_cgen.ion - Box local and field replacement call ion_box_free before the store (codegen grep)
  • test_int_wrap.ion - int::MAX + 1 and int::MIN - 1 wrap
  • test_div_zero_panic.ion / test_signed_div_overflow_panic.ion / test_shift_width_panic.ion - codegen grep for panic strings
  • test_string_from_utf8.ion - ill-formed Vec<u8> is None; ASCII bytes become String
  • test_match_move_join_error.ion - match arm move joined with a fall-through arm is UseAfterMove
  • test_unsafe_basic.ion - Unsafe blocks
  • test_unsafe_extern_required.ion - Unsafe requirement for extern calls (negative test)
  • test_capability_show.ion - Capability method, generic bound, and Show::show
  • test_capability_generic_impl.ion - Generic impl Show for Wrap<T>
  • test_capability_impl_copy_error.ion - impl Copy is rejected
  • test_capability_missing_method_error.ion - Impl must supply every capability method
  • test_capability_bound_error.ion - Instantiation must satisfy T: Show
  • test_capability_ambiguous_error.ion - Two capabilities with the same method name
  • test_capability_foreign_impl_error.ion - Impl must sit in the type's module
  • test_drop_sends.ion - impl Drop runs before the sender field is dropped
  • test_field_replace_drop.ion - replacing a field drops the previous value (exit 42)
  • test_local_box_replace_drop.ion - replacing a local Box drops the previous value (exit 42)
  • test_local_struct_replace_drop.ion - replacing a struct local drops its String, Vec, and Guard (exit 42)
  • test_array_elem_replace_drop.ion - replacing an array element drops the previous Box; self-assignment does not (exit 42)
  • test_assign_self_drop.ion - b = b does not drop; scope exit drops once (exit 42)
  • test_assign_self_cgen.ion - b = b stays a plain store (codegen grep)
  • test_move_then_assign_drop.ion - reassignment after a move does not drop the moved value (exit 42)
  • test_array_move_then_assign_drop.ion - a moved-out array element is not dropped again on the next store (exit 42)
  • test_replace_rhs_move.ion - a local Box, Vec (including a loop), String, or Option replaced by a call that moved it is not dropped again (exit 0)
  • test_replace_field_move.ion - field, nested field, &mut field, partial move, and one match arm (exit 0)
  • test_replace_array_move.ion - array element update through a literal index and a variable index (exit 0)
  • test_replace_vec_set.ion - Vec::set of a fresh Box and of Vec::get on that index (exit 0)
  • test_shadow_let.ion - same-scope let reuses a name; the initializer sees the previous binding (exit 0)
  • test_ref_temp_let_error.ion - storing & of an owned temporary is ReferenceEscape (Vec, String, field, index, assignment, aggregate, match scrutinee, match result)
  • test_ref_temp_ephemeral.ion - get_ref of a temporary as a call argument, len on a temporary, a borrow of a local, and an owned match (exit 0)
  • test_shadow_borrow.ion - a later let of the same name is a different binding (exit 3)
  • test_shadow_borrow_same_error.ion - mutating the borrowed binding is BorrowConflict
  • test_array_return.ion - [int; 3] returned by value, including a nested call and a fn literal (exit 7)
  • test_array_return_string.ion - [String; 1] returned and discarded drops the element once (exit 0)
  • test_get_ref_qualified_disjoint.ion - Vec::get_ref(&vm.code, 0) then Vec::push on vm.stack (exit 7)
  • test_get_ref_qualified_same_field_error.ion - Vec::get_ref(&vm.code, 0) then Vec::push on vm.code is BorrowConflict
  • test_replace_consume_cgen.ion - a consuming local update stores the temp and does not free that local
  • test_replace_field_null_cgen.ion - a field update does not assign NULL after the store
  • test_aggregate_eq.ion - struct, enum, and array == compare fields, the active variant, and elements (exit 0)
  • test_drop_partial_move_error.ion - Drop types cannot be partially moved
  • test_drop_match_partial_move_error.ion - match cannot move a non-Copy field out of a Drop value
  • test_drop_match_rest.ion - Guard { .. } leaves the field for Drop (exit 7)
  • test_drop_receiver_error.ion - Drop::drop takes &mut Self
  • test_match_named_field_pun.ion - Event::Set { state, code } field pun
  • test_match_struct_rest.ion - .. in a struct-variant pattern
  • test_tuple_rest.ion - let (a, .., b)
  • test_match_or_pattern.ion - Done | Ready
  • test_match_at_binding.ion - p @ State::Done
  • test_match_or_bindings_error.ion - Or-pattern bindings must match
  • test_match_int_range.ion - Integer literal and inclusive range
  • test_match_bool.ion - true / false patterns
  • test_match_string_lit.ion - String literal pattern compares bytes with memcmp and does not allocate
  • test_match_struct_pattern.ion - Point { x: 1, y } and @
  • test_match_struct_string.ion - moving a String field out of a struct pattern nulls the source (exit 5)
  • test_match_value_rvalue.ion - match as a + operand yields the arm result (exit 5)
  • test_match_pattern_generic.ion - a generic call on a struct-pattern binding is monomorphized (exit 4)
  • test_match_not_exhaustive_error.ion - Integer match needs an irrefutable arm
  • test_let_refutable_error.ion - Refutable let pattern
  • test_borrow_last_use.ion - A lasting borrow ends at the last use of that binding
  • test_borrow_nested_last_use.ion - A use inside if ends the loan when the outer block does not use it again
  • test_borrow_copy_last_use.ion - A copied reference keeps the loan until the copy's last use
  • test_borrow_nested_use_error.ion - A nested use does not end an outer loan that is used again
  • test_borrow_copy_error.ion - Copying &mut does not end the loan
  • test_borrow_reborrow_error.ion - &mut a.x keeps the loan of a
  • test_borrow_tuple_error.ion - A tuple element keeps the loan
  • test_borrow_match_alias_error.ion - A match result keeps the loan
  • test_borrow_assign_error.ion - Assigning a reference keeps the loan
  • test_ref_reassign.ion - Same-scope reference reassignment ends the previous loan (exit 12)
  • test_ref_reassign_mut.ion - Same-scope &mut reassignment frees the previous owner (exit 12)
  • test_ref_reassign_outer.ion - An if that may skip the reassignment still loans the original owner (BorrowConflict)
  • test_ref_reassign_inside.ion - A shorter referent used only inside its block (exit 5)
  • test_ref_assign_shorter_error.ion - Assigning &inner to an outer reference that is used later (ReferenceEscape)
  • test_ref_assign_nested_error.ion - The use is in the enclosing block (ReferenceEscape)
  • test_ref_assign_branch_error.ion - Both if branches assign locals that die with the if (ReferenceEscape)
  • test_ref_assign_loop_error.ion - A loop body local cannot be stored in an outer reference (ReferenceEscape)
  • test_ref_assign_indirect_error.ion - r = r2 does not extend the referent (ReferenceEscape)
  • test_ref_assign_field_error.ion - r = &inner.s still borrows inner (ReferenceEscape)
  • test_ref_assign_mut_shorter_error.ion - &mut to a narrower local is ReferenceEscape
  • test_ref_assign_get_ref_drop_error.ion - get_ref copied out of the vector's block (ReferenceEscape)
  • test_ref_assign_get_ref_push_error.ion - get_ref copied into another binding still blocks Vec::push (BorrowConflict)
  • test_match_yield_local_ref_error.ion - A match arm cannot yield & of a local declared in the arm (ReferenceEscape)
  • test_borrow_index_reborrow_error.ion - &mut a[i] keeps the loan of a
  • test_borrow_enum_ref_error.ion - Option::Some keeps the loan of the reference
  • test_borrow_struct_ref_error.ion - Hold { v: a } keeps the loan of the reference
  • test_borrow_struct_ref_call.ion - touch(h.v) passes the field pointer (exit 1)
  • test_borrow_struct_fresh.ion - Hold { v: &mut s } keeps the new loan (exit 1)
  • test_borrow_struct_fresh_error.ion - Hold { v: &mut s } conflicts with a later &mut s
  • test_borrow_enum_fresh.ion - Option::Some(&mut s) keeps the new loan (exit 1)
  • test_borrow_enum_fresh_error.ion - Option::Some(&mut s) conflicts with a later &mut s
  • test_borrow_tuple_fresh.ion - (&mut s.x, 1) keeps the new loan (exit 2)
  • test_borrow_tuple_fresh_error.ion - (&mut s.x, 1) conflicts with a later &mut s.x
  • test_borrow_match_fresh.ion - a match arm that yields &mut s.x keeps the new loan (exit 1)
  • test_borrow_match_fresh_error.ion - a match arm that yields &mut s.x conflicts with a later &mut s.x
  • test_borrow_match_yield_local.ion - a match arm that yields a local tuple of &mut s.x keeps the loan (exit 2)
  • test_borrow_match_yield_local_error.ion - that local yield conflicts with a later &mut s.x
  • test_borrow_get_ref_yield_error.ion - A match that yields a get_ref payload keeps the loan
  • test_match_ref_struct.ion - match on &Pair reborrows the String field (exit 5)
  • test_match_option_ref.ion - Option<&int> binds the pointer (exit 7)
  • test_borrow_match_use_error.ion - A use inside match keeps the loan live
  • test_field_same_mut_borrow_error.ion - Two &mut of the same field
  • test_field_nested_prefix_borrow_error.ion - s.a.b conflicts with s.a.c
  • test_const_item.ion - const item, const fn, and [int; LIMIT]
  • test_const_param.ion - fn pad<const N: int>
  • test_const_assert_error.ion - const_assert failure
  • test_const_fn_error.ion - const fn cannot call a runtime function
  • test_extern_rust_error.ion - Non-"C" extern linkage (negative test)
  • test_multifile.ion - Multi-file compilation
  • test_multi_struct.ion - Multi-file module with private struct in library
  • test_multi_fmt_io.ion - Multi-file link with both fmt and io stdlib modules

Numeric types and aliases

  • test_bool_literal.ion - Boolean literals (true, false) and !
  • test_bool_operations.ion - Boolean type usage
  • test_bool_comparison.ion - Comparison operators returning bool
  • test_if_bool_required.ion - Negative test: if requires bool condition
  • test_float_literal.ion - Floating-point literals (.5, 3., 1e9, etc.)
  • test_float_arithmetic.ion - Float arithmetic operations
  • test_float_promotion.ion - Float type promotion rules
  • test_float_comparison.ion - Float comparison operations
  • test_integer_types.ion - All integer type declarations
  • test_integer_promotion.ion - Integer type promotion rules
  • test_integer_signed_unsigned.ion - Signed/unsigned integer mixing
  • test_type_alias_basic.ion - Basic type aliases
  • test_type_alias_generic.ion - Generic type aliases
  • test_type_alias_resolution.ion - Type alias resolution in function signatures

Method call syntax

  • 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_ref_param.ion - Method-call syntax on &T / &mut T parameters (out.push(), vec.get())
  • test_ref_struct_field_to_ref_vec_param.ion - Reborrowed Vec field through &Struct (and nested a.b.c) passed to a free &Vec<T> param; also method sugar on the field (exit 42)
  • test_method_call_generic.ion - Generic method calls with type inference
  • test_method_call_chaining.ion - Chained method calls (vec.push().len())

Split channels, struct variants, and for loops

  • test_channel_split.ion - Split Channel API (Sender<T>, Receiver<T> types)
  • test_channel_string.ion - channel<String> send/recv; recv yields Option<String> (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_send_option_none.ion - send(&tx, Option::None) infers from Sender<T> (exit 0); also listed under Enums
  • test_enum_struct_variant.ion - Struct-style enum variants with named fields
  • test_for_loop.ion - for...in loop syntax with Vec iteration

Literals and bitwise operators

  • 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, hex literals, wrapping shifts, and integer as narrowing

Casting, comparison, and stdlib I/O

  • test_comparison_operators.ion - Full comparison operators (<=, >=)
  • test_type_cast.ion - Type casting with as keyword
  • test_array_assignment.ion - Array element assignment (arr[i] = value)

Ergonomics (literals, compound assign, loop)

  • 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 a for loop body (exit 6)
  • test_loop_basic.ion - Infinite loop { } with break

Match expression types

  • test_match_result_type.ion - match infers non-int result type (bool via -> bool helper, exit 88)
  • test_match_expr_rvalue.ion - match as rvalue in let binding (exit 91)
  • test_match_expr_rvalue_string.ion - rvalue match whose success arm is a bare owned String binding (s;) (exit 93)
  • test_match_arm_type_mismatch.ion - Mismatched arm result types (negative)
  • test_match_arm_divergent_rvalue.ion - Diverging arm mixed with value arm in rvalue match (negative)
  • test_match_arm_if_else_value_rvalue.ion - if/else value branches unify in rvalue match (exit 80)
  • test_match_arm_if_else_mixed_rvalue.ion - Mixed diverging and value paths within one rvalue arm (negative)

Postfix try operator

  • test_try_result_ok.ion - Result success and chained ? (exit 21)
  • test_try_result_err.ion - Result error path returns Err (exit 7)
  • test_try_option.ion - Option None and Some via ? (exit 6)
  • test_try_result_payload_change.ion - Result<T, E> in a Result<U, E> function (exit 5)
  • test_try_result_string.ion - owned Result<String, int> success arm (exit 5)
  • test_try_vec_get.ion - v.get(i)? (exit 42)
  • test_try_fn_literal.ion - ? in a capture-free fn literal; Result::Ok(7) at the fn-pointer call site (exit 8)
  • test_fn_ptr_enum_lit_arg.ion - generic enum literal passed to a fn-pointer parameter (exit 7)
  • test_try_import.ion - ? on a Result returned from an imported function (exit 21)
  • test_try_on_int_error.ion - ? on int (negative)
  • test_try_in_int_fn_error.ion - Result? in fn -> int (negative)
  • test_try_option_in_result_error.ion - Option? in a Result function (negative)
  • test_try_mismatched_e_error.ion - mismatched Result error type (negative)
  • test_try_readresult_error.ion - ReadResult? in fn -> int (negative)
  • test_try_readresult.ion - ReadResult? in a function that returns ReadResult (exit 2)
  • test_try_parse.ion - ? on an enum whose only payload variant is the success case (exit 5)
  • test_try_math_error.ion - ? on a bare error enum (negative)
  • test_try_setresult_error.ion - ? on SetResult (negative)
  • test_try_spawn_error.ion - ? inside spawn (negative)
  • test_try_move_error.ion - use-after-move of a ? operand (negative)

if/else ownership merge

  • test_if_else_move_ok.ion - Move in diverging branch; use after if (exit 60)
  • test_if_else_move_error.ion - Move in fall-through branch; use after if (negative)

Function types

  • test_fn_type_basic.ion - Store named function in fn(int) -> int variable; 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 in fn(int) -> int and called (exit 12)

  • test_fn_literal_callback.ion - Pass capture-free fn literal to fn(int) -> int parameter (exit 40)

  • test_fn_literal_return.ion - Return capture-free fn literal from function (exit 6)

  • test_fn_literal_capture_error.ion - Owned capture is a closure value, not fn(int) -> int (negative)

  • test_fn_literal_ref_capture_error.ion - Fn literal referencing outer reference (negative, ClosureCapture)

  • test_move_closure.ion - Move closure of Copy captures, called twice (exit 21)

  • test_move_closure_consume_error.ion - Second call after moving a non-Copy capture (negative)

  • test_closure_assign.ion - Store of a captured int, including += and if / while / match (exit 0)

  • test_closure_assign_agg.ion - Whole struct, tuple, and enum store of a capture (exit 0)

  • test_closure_assign_drop.ion - Replacing a captured impl Drop value drops the previous one (exit 1)

  • test_closure_drop_box.ion - An uncalled closure drops its Box capture (exit 0)

  • test_closure_drop_sender.ion - An uncalled closure drops its Sender so a discarded Receiver can free the channel (exit 0)

  • test_channel_discard_receiver.ion - Both ends of an unused channel are dropped (exit 0)

  • test_array_whole_assign.ion - Whole-array store of a local (exit 17)

  • test_closure_array_assign.ion - Whole-array store of a capture (exit 17)

  • test_closure_nested.ion - Nested closure reads the outer capture (exit 4)

  • test_closure_nested_3.ion - Three-level nested capture (exit 2)

  • test_closure_nested_after_assign.ion - Nested closure reads the updated capture (exit 2)

  • test_closure_field_store.ion - Field store on a captured struct (exit 12)

  • test_closure_copy_two.ion - Two closures copy the same int (exit 13)

  • test_closure_box_once.ion - One move closure of Box (exit 1)

  • test_closure_spawn_read.ion - spawn inside a closure reads the capture (exit 0)

  • test_closure_box_twice_error.ion - Second closure moving the same Box (negative, UseAfterMove)

  • test_closure_vec_twice_error.ion - Second closure moving the same Vec (negative, UseAfterMove)

  • test_closure_unused_borrow_error.ion - Move closure while an unused & of that binding is still in scope (negative, BorrowConflict)

  • test_unwrap_after_borrow.ion - Box::unwrap after the & carrier's last use, outside a closure (exit 1)

  • test_closure_copy_after_borrow.ion - Copy capture after the & carrier's last use (exit 10)

  • test_doc_comments.ion - Adjacent // doc comments attach to AST without affecting compile or runtime (exit 42)

  • test_tuple_basic.ion - Tuple literals, .0/.1 access, and destructuring (exit 81)

  • test_nested_tuple_eq.ion - nested tuples, ==/!=, struct field tuple, generic (T, int) param (exit 0)

  • test_vec_set_result.ion - Vec::set returns SetResult::Ok / OutOfBounds (exit 0)

  • test_file_rw.ion - File::create / write / open / read round-trip (exit 0)

  • test_file_field.ion - File::write / read / close on a File field through &mut Holder (exit 0)

  • test_file_send_error.ion - channel<File>() is not Send

  • 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

Iteration, Guards, and Formatting Tests

  • test_for_array.ion - for...in over fixed-size arrays
  • test_for_string.ion - for...in over String (byte iteration)
  • test_match_guard.ion - Match arms with if guards
  • test_generic_field_access.ion - Field access on generic struct values

Trait bounds on generics

  • test_trait_bound_send_ok.ion - generic fn with T: Send accepts int
  • test_trait_bound_send_channel_ok.ion - fn wrap<T: Send> may channel<T>() with int (exit 42)
  • test_trait_bound_copy_ok.ion - generic fn with T: Copy accepts int
  • test_trait_bound_copy_fn_ok.ion - generic fn with T: Copy accepts a function identifier
  • test_trait_bound_eq_ok.ion - generic fn with T: Eq compares ints
  • test_trait_bound_eq_fn_ok.ion - generic fn with T: Eq compares function pointers
  • test_trait_bound_send_error.ion - &int rejected for T: Send
  • test_unbounded_t_send_error.ion - unbounded T is not Send; channel<T>() inside wrap<T> plus wrap(&x) is rejected
  • test_trait_bound_copy_error.ion - String rejected for T: Copy
  • test_trait_bound_eq_error.ion - Vec<int> rejected for T: Eq
  • test_trait_bound_unknown_error.ion - unknown bound name rejected at declaration
  • test_empty_file_error.ion - empty entry file rejected (MissingMain)
  • test_no_main_error.ion - helper-only entry file rejected (MissingMain)
  • test_match_stmt_no_semi.ion - statement-form match without trailing ;
  • test_lt_literal_lhs_with_call.ion - if 5 < x { f(x); } / <= parse correctly (not struct-lit)
  • test_io_print_int.ion - io::print_int decimal output
  • test_fmt_int_to_string.ion - fmt::int_to_string decimal output (0, positive, negative, int::MIN)
  • test_fmt_println_int.ion - fmt::println_int via stdlib merge; codegen uses io_print_int in fmt_print_int
  • test_fs_read.ion - fs::read_to_string_result reads fixtures/small.txt (exit 80)

ion-build smoke tests

The harness also runs ion-build (not via test_expectations.tsv):

  • build_hello/ - minimal ion.toml project; expects exit code 55
  • ../examples/worker_pool/ - ion-build smoke on worker_pool example; expects exit code 0
  • build_bad_main/ - invalid main path; expects main file not found on stderr

Set ION_BUILD to override the ion-build binary path (default ../target/release/ion-build).

Negative Tests (Error Cases)

  • test_move_error.ion - Use-after-move errors
  • test_vec_get_ref_moved_error.ion - Vec::get_ref on a moved Vec as a match scrutinee (UseAfterMove)
  • test_vec_get_ref_moved_let_error.ion - Vec::get_ref on a moved Vec as a let initializer (UseAfterMove)
  • test_vec_get_moved_error.ion - Vec::get on a moved Vec (UseAfterMove)
  • test_vec_method_get_ref_moved_error.ion - method get_ref on a moved Vec (UseAfterMove)
  • test_vec_method_get_moved_error.ion - method get on a moved Vec (UseAfterMove)
  • test_move_in_loop.ion - Use-after-move when a non-copy value is moved on a reentering loop path
  • test_move_in_loop_for.ion - Same reentry rule for for loops (outer binding moved in body)
  • test_move_in_loop_break_use_error.ion - Move then break, then use after the loop
  • test_move_in_loop_continue_error.ion - Move then continue (reentry)
  • test_move_in_loop_break_disagree.ion - Break paths disagree; the box is dropped on the path that still owns it (exit 0)
  • test_move_in_while_break_disagree.ion - while head still owns the box and the break path moved it (exit 0)
  • test_move_channel_error.ion - Use-after-move on channel receivers
  • test_ref_return_error.ion - Reference escape errors
  • test_ref_return_error2.ion - Additional reference escape errors
  • test_channel_ref_error.ion - Non-Send channel elements
  • test_unbounded_t_send_error.ion - unbounded generic T is not a Send channel element (also listed under Trait bounds)
  • test_send_ref_error.ion - Non-Send send operations
  • test_send_result_ref_error.ion - SendResult<&int> is not Send as a channel element
  • test_channel_capacity_literal_error.ion - Literal channel<T>(0) is a compile error
  • test_spawn_ref_error.ion - Non-Send spawn captures
  • test_spawn_borrow_error.ion - Spawn capture while lasting borrow is active (negative)
  • test_spawn_move_error.ion - Move errors in spawn blocks
  • test_struct_ref_error.ion - Reference in struct fields
  • test_enum_ref_error.ion - Reference in enum variants
  • test_match_ref_return_error.ion - match/branch returning & local (ReferenceEscape)
  • test_vec_index_ref_error.ion - Helper almost returning &vec[i] via Vec::get (ReferenceEscape)
  • test_vec_get_ref_return_error.ion - Returning &T from Vec::get_ref (ReferenceEscape)
  • test_vec_get_ref_mut_error.ion - Vec::set while get_ref borrow is active (BorrowConflict)
  • test_vec_method_get_ref_push_error.ion - v.push while v.get_ref borrow is active (BorrowConflict)
  • test_get_ref_if_push_error.ion - Vec::push in an if inside a get_ref arm, then a use of the binding (BorrowConflict)
  • test_get_ref_else_push_error.ion - Vec::push in the else of that arm (BorrowConflict)
  • test_get_ref_method_if_push_error.ion - v.push in an if inside a v.get_ref arm (BorrowConflict)
  • test_get_ref_while_if_push_error.ion - push in an if inside a while in the arm, then a use of the binding (BorrowConflict)
  • test_get_ref_loop_if_push_error.ion - a use of the binding at the start of a while covers a later push in that loop (BorrowConflict)
  • test_borrow_if_nested_else_error.ion - push in an if nested in else, then a use of the loan (BorrowConflict)
  • test_arena_slots_get_ref_insert_error.ion - handle::insert while a.slots.get_ref borrow is active (BorrowConflict)
  • test_arena_slots_get_ref_remove_error.ion - handle::remove while a.slots.get_ref borrow is active (BorrowConflict)
  • test_arena_slots_get_ref_let_error.ion - handle::insert while a let of a.slots.get_ref is live (BorrowConflict)
  • test_arena_slots_get_ref_if_insert_error.ion - handle::insert in an if inside an a.slots.get_ref arm, then a use of the slot (BorrowConflict)
  • test_slice_get_ref_return_error.ion - Returning &T from Slice::get_ref (ReferenceEscape)
  • test_slice_get_ref_mut_error.ion - Indexed write arr[i] = ... while Slice::get_ref borrow is active (BorrowConflict)
  • test_slice_len_error.ion - Slice::len on a non-slice argument (TypeMismatch)
  • test_nested_struct_ref_error.ion - Nested struct storing a reference field (ReferenceEscape)
  • test_module_visibility.ion - Module visibility violations
  • test_unsafe_extern_required.ion - Unsafe requirement for extern calls
  • test_extern_rust_error.ion - Non-"C" extern linkage
  • test_if_bool_required.ion - Boolean requirement for if conditions
  • test_break_continue_error.ion - break outside of a loop (negative test)

Manual panic tests (bounds)

test_array_bounds_panic.ion, test_slice_bounds_panic.ion, test_assign_index_oob.ion, test_div_zero_panic.ion, test_signed_div_overflow_panic.ion, and test_shift_width_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

Checker precision, iterators, closures, threads, and protocols

  • test_copy_struct.ion - a struct of integers is Copy (exit 3)
  • test_copy_drop_error.ion - impl Drop keeps the struct move-only (negative)
  • test_index_borrow_split.ion - lasting borrow of one literal index (exit 0)
  • test_index_borrow_distinct.ion - &mut a[0] and &mut a[1] together (exit 0)
  • test_index_borrow_same_error.ion - the same literal index twice (negative)
  • test_index_borrow_var_error.ion - a non-literal index borrows the whole owner (negative)
  • test_loop_dead_after_break.ion - a statement after break is not reentry (exit 1)
  • test_loop_exit_disagree.ion - disagreeing break paths move the binding after the loop (exit 7)
  • test_loop_exit_disagree_error.ion - use after that loop (negative)
  • test_borrow_if_arm.ion - a loan used in one if arm does not block the other (exit 3)
  • test_get_ref_if_after_use.ion - push in an if after the get_ref binding's last use (exit 0)
  • test_const_match_cast.ion - const integer width, as, and match (exit 65)
  • test_iter_for.ion - for over Iter<T> (exit 33)
  • test_iter_for_error.ion - for over a type that is not iterable (negative)
  • test_map_iter.ion - consuming HashMap iterator (exit 33)
  • test_join_value.ion - join moves int out of JoinHandle<int> (exit 7)
  • test_scope_join.ion - scope joins a statement spawn (exit 7)
  • test_scope_move_out.ion - a handle returned from scope is not joined there (exit 0)
  • test_allocator.ion - heap() is Copy; Vec::new_in and Box::new_in (exit 12)
  • test_allocator_unsafe_error.ion - make_allocator outside unsafe (negative)
  • test_protocol.ion - unique endpoint send and recv (exit 7)
  • test_protocol_move_error.ion - a second send of the same endpoint (negative)
  • test_protocol_direction_error.ion - send when the step is recv (negative)
  • test_protocol_copy_error.ion - a non-Copy payload (negative)
  • test_protocol_ended_error.ion - send after end (negative)

Adding Tests

  1. Create a new test_<feature>.ion file in this directory
  2. 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).

  1. Document the test in this README under the appropriate category

Special cases (not in the manifest):

  • test_multifile.ion: multi-file mode harness in test_runner.sh
  • channel_drop_race.c: concurrent sender and receiver drop, including a cloned sender. Linux TSan CI compiles and runs it. Not a manifest row.
  • test_array_bounds_panic.ion / test_slice_bounds_panic.ion / test_assign_index_oob.ion / test_div_zero_panic.ion / test_signed_div_overflow_panic.ion / test_shift_width_panic.ion: codegen-only in manifest; runtime panic is manual (see below)

Environment Variables

  • 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,undefined for sanitizer smoke (detect_leaks=0), a leak-sanitizer (LSan) step (detect_leaks=1) on Box::unwrap tests plus named heap-drop run tests (Vec/Box/tuple/array/break/continue/Vec::set of String), thread sanitizer (TSan) on test_channel_* / test_spawn_* / test_join / test_select* / test_try_recv run tests and on channel_drop_race.c, and runs the full harness with -Wall -Wextra -Werror on Linux. macOS CI runs the harness with Clang.
  • LDFLAGS: Extra C linker flags for generated test executables (default: empty). Pair with CFLAGS for sanitizer runtime flags when needed.
  • RUNTIME_OBJ: Path to the precompiled runtime object file (default: .ion_test_runtime.o in tests/). Rebuilt when runtime/ion_runtime.c is 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