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.
Use the test harness script:
cd tests
chmod +x test_runner.sh
./test_runner.shOr from the project root:
cd tests && ./test_runner.shTo 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 --helpOn 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_defer_before_break.ion-deferbeforebreakruns; exit 1test_scope_drop_block.ion- Automatic Vec drop at block exittest_vec_string_scope_drop.ion-Vec<String>scope-exit drop frees eachStringthen the backing array (exit 0); cgen asserts elemention_string_freeandion_vec_free; Linux CI leak-sanitizertest_vec_struct_string_scope_drop.ion- struct fieldVec<String>drops elements then the array (exit 0)test_box_string_scope_drop.ion-Box<String>drops theStringthenion_box_free(exit 0); Linux CI leak-sanitizertest_loop_continue_nested_if_drop.ion- nestedifcontinuedrops inner and loop-body Strings; fall-through still usesa(exit 4); Linux CI leak-sanitizertest_for_continue_drop_step.ion-forcontinuefrees a bodyStringand still visits later elements (exit 8); Linux CI leak-sanitizertest_loop_break_drop.ion-loopbreakdrops a bodyString(exit 0); Linux CI leak-sanitizertest_nested_loop_inner_break_drop.ion- innerbreakdrops the innerStringand leaves outer Strings usable (exit 11); Linux CI leak-sanitizertest_tuple_string_scope_drop.ion-(String, String)whole-value drop freesf0thenf1(exit 3); Linux CI leak-sanitizertest_array_string_scope_drop.ion-[String; 2]whole-value drop freespath[i]in increasing index (exit 0); Linux CI leak-sanitizertest_drop_order_locals_struct_defer.ion- defers then reverse-local drops; struct fields in declaration order (exit 0); Linux CI leak-sanitizertest_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_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 branchtest_channel_basic.ion- Channel operationstest_channel_contention.ion- Fourclone_senderproducers, one receiver untilNone; sum exit 73test_channel_shutdown.ion- WorkerrecvuntilNoneaftersend_jobsdrops 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 returnsClosed(T)(exit 9)test_channel_queued_string_drop.ion- QueuedStringvalues dropped on channel destroy (exit 0)test_channel_capacity.ion-channel<int>(4)same-thread send/recv (exit 10)test_try_recv.ion-try_recvEmpty thentry_sendSent/Full thentry_recvMsg (exit 0)test_select.ion-selecttakes a ready recv arm;defaultpolls 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 blockingselectvs spawnsend(exit 7)test_select_timeout_neg_error.ion- literaltimeout(-1)isselect timeout must be >= 0test_join.ion-let h = spawn { }; join(h)(exit 0)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_unannotated_let.ion- unannotatedlet flag = Flag::On/let status = Status::Ok { ... }types as the enum, not default int (exit 7); cgen assertsFlag flag =/Status status =test_enum_generic_unannotated_let.ion- unannotatedlet x = Option::Some(42)types asOption<int>(exit 42); cgen assertsOption_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)isTypeMismatchtest_option_some_string_int_error.ion-let x: Option<String> = Option::Some(1)isTypeMismatchin ion-compiler (not gcc)test_option_none_unannotated_error.ion-let empty = Option::Nonethen use asOption<Box<Node>>needs a type annotation (cannot infer type parameter)test_option_none_struct_field.ion-Node { next: Option::None }infersTfrom the field (exit 2)test_option_none_call_arg.ion-take(Option::None)infersTfrom the parameter (exit 3)test_option_none_call_arg_middle.ion-take2(5, Option::None)infersTin a non-final argument (exit 5)test_result_err_call_arg.ion-take(Result::Err(7))infersTfrom the parameter (exit 7)test_send_option_none.ion-send(&tx, Option::None)infersTfromSender<Option<int>>(exit 0)test_send_result_err.ion-send(&tx, Result::Err(4))infersTfromSender<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 expectedBox<Option<int>>(exit 0)test_array_option_none.ion-[Option::None]infersTfrom[Option<int>; 1](exit 2)test_tuple_option_none.ion-(Option::None, 3)infersTfrom(Option<int>, int)(exit 3)test_array_option_some.ion-[Option::Some(4)]type-checks and emitsOption_intnot 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::Noneinfers 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); cgentypedef 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); cgenarr_int_2*test_vec_array.ion-Vec<[int; 2]>push/get (exit 7); cgenVec_arr_int_2test_enum_option_payload_none.ion-Hold::H(Option::None)emitsOption_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 }emitsOption_int(exit 15)test_unannotated_let_non_int.ion- unannotatedlet q = p/let n = w.p/let v = origin()keep struct types, not default int (exit 5); cgen assertsPoint q =/Point n =/Point v =test_enum_generic.ion- Generic enum typestest_result_custom_enum.ion-Result<int, MyError>viastdlib/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- compilerHashforintandString(exit 0)test_stdlib_map.ion-HashMapgrows past 16 slots, replaces a key, and accepts a non-CopyHashkey 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 andPATH(exit 1)test_stdlib_time.ion-millisis non-negative (exit 1)test_match_ref_generic_enum_arith.ion- Match on&Status<int>and non-generic&Flagwith arithmetic on copy payloads (exit 55)test_match_basic.ion- Pattern matchingtest_match_pattern_bindings.ion- Pattern matching with bindingstest_match_complex.ion- Complex pattern matching scenariostest_nested_match_pattern.ion- Nested constructorsOuter::Wrap(Inner::B)(exit 2)test_nested_match_named.ion- Named-field nested constructorOuter::Wrap { inner: Inner::B }(exit 2)test_nested_match_catchall_binding.ion- Nested constructor plus catch-all bindingother(exit 4);default:arms are braced so Clang accepts a declaration after the labeltest_nested_match_unknown_error.ion- Unknown nested enumNopeis TypeMismatchtest_nested_match_nonexhaustive_error.ion- Inner variantBnot coveredtest_while_basic.ion- While loops with assignmenttest_break_continue.ion-breakandcontinueinwhileandforloopstest_match_break_in_while.ion-breakinsidematchwithinwhileexits the loop (exit 3)test_call_basic.ion- Function callstest_string_basic.ion- String literalstest_string_from.ion- String::from() functiontest_string_call_arg_literal.ion- string literal asStringcall argumenttest_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_generic_enum_named_vec.ion- generic named-field enum inVec: push,get_ref, ownedget(exit 42)test_vec_len_mut_param.ion-Vec::len/.len()on a&mut Vecfield (exit 42)test_handle_arena_basic.ion-stdlib/handle.ioninsert,len,contains, remove Hit then Miss (exit 42)test_handle_arena_stale.ion- reused slot: stale handle is Miss /containsfalse (exit 42)test_handle_arena_get_ref.ion- local peek viaVec::get_refonarena.slotsthrough&World/&mut World(exit 42)test_arena_get_ref.ion- compiler builtinarena.get_ref(handle)Occupied peek (exit 0)test_arena_get_ref_field.ion-Arena::get_refon an arena field through&Holder,&mut Holder, and a nested field (exit 0)test_handle_arena_copy_struct.ion-Arena<Item>withStringfield; remove returns owned value (exit 42)test_handle_arena_escape_error.ion- returning&Tfrom a slot peek (ReferenceEscape)test_box_basic.ion- Box heap allocationtest_box_new_struct_let_annotated.ion-let b: Box<Node> = Box::new(Node { ... })multi-field unwrap (exit 10); cgen assertsion_box_free(_box)test_box_new_struct_let.ion- unannotatedlet b = Box::new(Node { ... })multi-field unwrap (exit 10)test_box_unwrap_struct_let.ion- unannotatedlet n = Box::unwrap(boxed)forBox<Node>(exit 10); cgen assertsNode* _boxnotint* _boxtest_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- secondBox::unwrapafter an unwrap inside+(UseAfterMove)test_recursive_struct_box.ion- linked list viaOption<Box<Node>>(exit 3)test_recursive_struct_mutual_box.ion- mutual recursion through Box (exit 30)test_recursive_struct_vec.ion- self-referentialVec<Forest>(exit 2)test_recursive_struct_send_ok.ion- recursive Box node satisfiesT: Send(exit 9)test_recursive_struct_infinite_error.ion- barenext: Node→InfiniteSizetest_recursive_struct_option_infinite_error.ion-next: Option<Node>→InfiniteSizetest_recursive_struct_eq_error.ion- recursive Box node rejected forT: Eqtest_recursive_box_ref_error.ion-Box<&int>field stillReferenceEscapetest_box_ref_let_error.ion-let b: Box<&int> = Box::new(&x)isReferenceEscapetest_box_ref_unannotated_let_error.ion-let b = Box::new(&x)isReferenceEscapetest_box_ref_param_error.ion-fn take(b: Box<&int>)isReferenceEscapetest_vec_ref_let_error.ion-let v: Vec<&int> = Vec::new()isReferenceEscapetest_box_ops.ion- Box operations (new, unwrap); cgen assertsion_box_free(_box)and noion_box_free(boxed)test_vec_basic.ion- Vec dynamic arraystest_vec_new.ion- Vec::new() functiontest_vec_push_pop.ion- Vec push and pop operations; cgen asserts a stackOption(_ion_pop.tag = 0) and noion_vec_popor slotmemsettest_vec_get_set.ion- Vec get and set operations; cgen asserts a stackOption(_ion_get.tag = 0) and noion_vec_getor slotmemsetforVec<int>test_vec_set_string.ion-Vec::setofStringdrops the previous element (exit 0); Linux CI leak-sanitizertest_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 containingString; cgen asserts_ion_get.tag = 0, a hollowmemset, and noion_vec_gettest_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_putback_named.ion- put-back vialet rowthenVec::set(..., row)as the last match-arm statementtest_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_string_get.ion-String::getbyte peek andNoneon OOB/negativetest_string_get_method.ion-s.get(i)method desugar forString::gettest_slice_get_ref_scan.ion- read-only scan viaSlice::get_refover&[]Product, loop bound fromSlice::lentest_slice_get_ref_oob.ion-Slice::get_refOOB/negative/Noneand in-rangetest_slice_get_ref_from_array.ion- array methodarr.get_refandSlice::get_ref(&arr, i)test_slice_len.ion-Slice::len/s.len()on&[]Twith two array sizes;vec.len()still usesVec::lentest_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; cgen assertsion_vec_reserve_one, a typed store of the variable, and noion_vec_pushtest_struct_field_move_vec.ion- move aVecout of a struct field without double-freetest_tuple_vec_int.ion- tuple(Vec<T>, int)mangling, return, andt.f0drop; Linux CI leak-sanitizertest_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_extern_rust_error.ion-extern "Rust"(or any non-"C"ABI) is a compile error
test_array_basic.ion- Fixed-size arraystest_array_literal.ion- Array literalstest_array_indexing.ion- Array indexing and index assignmenttest_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_assign_index_oob.ion-arr[i] =bounds-check panic (codegen grep)test_assign_int_cgen.ion- plainintassignment stays a single store (codegen grep)test_assign_replace_cgen.ion-Boxlocal and field replacement callion_box_freebefore the store (codegen grep)test_int_wrap.ion-int::MAX + 1andint::MIN - 1wraptest_div_zero_panic.ion/test_signed_div_overflow_panic.ion/test_shift_width_panic.ion- codegen grep for panic stringstest_string_from_utf8.ion- ill-formedVec<u8>isNone; ASCII bytes becomeStringtest_match_move_join_error.ion- match arm move joined with a fall-through arm isUseAfterMovetest_unsafe_basic.ion- Unsafe blockstest_unsafe_extern_required.ion- Unsafe requirement for extern calls (negative test)test_capability_show.ion- Capability method, generic bound, andShow::showtest_capability_generic_impl.ion- Genericimpl Show for Wrap<T>test_capability_impl_copy_error.ion-impl Copyis rejectedtest_capability_missing_method_error.ion- Impl must supply every capability methodtest_capability_bound_error.ion- Instantiation must satisfyT: Showtest_capability_ambiguous_error.ion- Two capabilities with the same method nametest_capability_foreign_impl_error.ion- Impl must sit in the type's moduletest_drop_sends.ion-impl Dropruns before the sender field is droppedtest_field_replace_drop.ion- replacing a field drops the previous value (exit 42)test_local_box_replace_drop.ion- replacing a localBoxdrops the previous value (exit 42)test_local_struct_replace_drop.ion- replacing a struct local drops itsString,Vec, andGuard(exit 42)test_array_elem_replace_drop.ion- replacing an array element drops the previousBox; self-assignment does not (exit 42)test_assign_self_drop.ion-b = bdoes not drop; scope exit drops once (exit 42)test_assign_self_cgen.ion-b = bstays 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 localBox,Vec(including a loop),String, orOptionreplaced by a call that moved it is not dropped again (exit 0)test_replace_field_move.ion- field, nested field,&mutfield, partial move, and onematcharm (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::setof a freshBoxand ofVec::geton that index (exit 0)test_shadow_let.ion- same-scopeletreuses a name; the initializer sees the previous binding (exit 0)test_ref_temp_let_error.ion- storing&of an owned temporary isReferenceEscape(Vec, String, field, index, assignment, aggregate, match scrutinee, match result)test_ref_temp_ephemeral.ion-get_refof a temporary as a call argument,lenon a temporary, a borrow of a local, and an ownedmatch(exit 0)test_shadow_borrow.ion- a laterletof the same name is a different binding (exit 3)test_shadow_borrow_same_error.ion- mutating the borrowed binding isBorrowConflicttest_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)thenVec::pushonvm.stack(exit 7)test_get_ref_qualified_same_field_error.ion-Vec::get_ref(&vm.code, 0)thenVec::pushonvm.codeisBorrowConflicttest_replace_consume_cgen.ion- a consuming local update stores the temp and does not free that localtest_replace_field_null_cgen.ion- a field update does not assignNULLafter the storetest_aggregate_eq.ion- struct, enum, and array==compare fields, the active variant, and elements (exit 0)test_drop_partial_move_error.ion-Droptypes cannot be partially movedtest_drop_match_partial_move_error.ion-matchcannot move a non-Copy field out of aDropvaluetest_drop_match_rest.ion-Guard { .. }leaves the field forDrop(exit 7)test_drop_receiver_error.ion-Drop::droptakes&mut Selftest_match_named_field_pun.ion-Event::Set { state, code }field puntest_match_struct_rest.ion-..in a struct-variant patterntest_tuple_rest.ion-let (a, .., b)test_match_or_pattern.ion-Done | Readytest_match_at_binding.ion-p @ State::Donetest_match_or_bindings_error.ion- Or-pattern bindings must matchtest_match_int_range.ion- Integer literal and inclusive rangetest_match_bool.ion-true/falsepatternstest_match_string_lit.ion- String literal pattern compares bytes withmemcmpand does not allocatetest_match_struct_pattern.ion-Point { x: 1, y }and@test_match_struct_string.ion- moving aStringfield out of a struct pattern nulls the source (exit 5)test_match_value_rvalue.ion-matchas 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 armtest_let_refutable_error.ion- Refutableletpatterntest_borrow_last_use.ion- A lasting borrow ends at the last use of that bindingtest_borrow_nested_last_use.ion- A use insideifends the loan when the outer block does not use it againtest_borrow_copy_last_use.ion- A copied reference keeps the loan until the copy's last usetest_borrow_nested_use_error.ion- A nested use does not end an outer loan that is used againtest_borrow_copy_error.ion- Copying&mutdoes not end the loantest_borrow_reborrow_error.ion-&mut a.xkeeps the loan ofatest_borrow_tuple_error.ion- A tuple element keeps the loantest_borrow_match_alias_error.ion- Amatchresult keeps the loantest_borrow_assign_error.ion- Assigning a reference keeps the loantest_ref_reassign.ion- Same-scope reference reassignment ends the previous loan (exit 12)test_ref_reassign_mut.ion- Same-scope&mutreassignment frees the previous owner (exit 12)test_ref_reassign_outer.ion- Anifthat 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&innerto 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- Bothifbranches assign locals that die with theif(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 = r2does not extend the referent (ReferenceEscape)test_ref_assign_field_error.ion-r = &inner.sstill borrowsinner(ReferenceEscape)test_ref_assign_mut_shorter_error.ion-&mutto a narrower local isReferenceEscapetest_ref_assign_get_ref_drop_error.ion-get_refcopied out of the vector's block (ReferenceEscape)test_ref_assign_get_ref_push_error.ion-get_refcopied into another binding still blocksVec::push(BorrowConflict)test_match_yield_local_ref_error.ion- Amatcharm cannot yield&of a local declared in the arm (ReferenceEscape)test_borrow_index_reborrow_error.ion-&mut a[i]keeps the loan ofatest_borrow_enum_ref_error.ion-Option::Somekeeps the loan of the referencetest_borrow_struct_ref_error.ion-Hold { v: a }keeps the loan of the referencetest_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 stest_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 stest_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.xtest_borrow_match_fresh.ion- amatcharm that yields&mut s.xkeeps the new loan (exit 1)test_borrow_match_fresh_error.ion- amatcharm that yields&mut s.xconflicts with a later&mut s.xtest_borrow_match_yield_local.ion- amatcharm that yields a local tuple of&mut s.xkeeps the loan (exit 2)test_borrow_match_yield_local_error.ion- that local yield conflicts with a later&mut s.xtest_borrow_get_ref_yield_error.ion- Amatchthat yields aget_refpayload keeps the loantest_match_ref_struct.ion-matchon&Pairreborrows theStringfield (exit 5)test_match_option_ref.ion-Option<&int>binds the pointer (exit 7)test_borrow_match_use_error.ion- A use insidematchkeeps the loan livetest_field_same_mut_borrow_error.ion- Two&mutof the same fieldtest_field_nested_prefix_borrow_error.ion-s.a.bconflicts withs.a.ctest_const_item.ion-constitem,const fn, and[int; LIMIT]test_const_param.ion-fn pad<const N: int>test_const_assert_error.ion-const_assertfailuretest_const_fn_error.ion-const fncannot call a runtime functiontest_extern_rust_error.ion- Non-"C"externlinkage (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) and!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_ref_param.ion- Method-call syntax on&T/&mut Tparameters (out.push(),vec.get())test_ref_struct_field_to_ref_vec_param.ion- ReborrowedVecfield through&Struct(and nesteda.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 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;recvyieldsOption<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 fromSender<T>(exit 0); also listed under Enumstest_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, hex literals, wrapping shifts, and integerasnarrowing
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_expr_rvalue_string.ion- rvaluematchwhose success arm is a bare ownedStringbinding (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 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_try_result_ok.ion-Resultsuccess and chained?(exit 21)test_try_result_err.ion-Resulterror path returnsErr(exit 7)test_try_option.ion-OptionNoneandSomevia?(exit 6)test_try_result_payload_change.ion-Result<T, E>in aResult<U, E>function (exit 5)test_try_result_string.ion- ownedResult<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 aResultreturned from an imported function (exit 21)test_try_on_int_error.ion-?onint(negative)test_try_in_int_fn_error.ion-Result?infn -> int(negative)test_try_option_in_result_error.ion-Option?in aResultfunction (negative)test_try_mismatched_e_error.ion- mismatchedResulterror type (negative)test_try_readresult_error.ion-ReadResult?infn -> int(negative)test_try_readresult.ion-ReadResult?in a function that returnsReadResult(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-?onSetResult(negative)test_try_spawn_error.ion-?insidespawn(negative)test_try_move_error.ion- use-after-move of a?operand (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- Owned capture is a closure value, notfn(int) -> int(negative) -
test_fn_literal_ref_capture_error.ion- Fn literal referencing outer reference (negative,ClosureCapture) -
test_move_closure.ion- Move closure ofCopycaptures, called twice (exit 21) -
test_move_closure_consume_error.ion- Second call after moving a non-Copycapture (negative) -
test_closure_assign.ion- Store of a capturedint, including+=andif/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 capturedimpl Dropvalue drops the previous one (exit 1) -
test_closure_drop_box.ion- An uncalled closure drops itsBoxcapture (exit 0) -
test_closure_drop_sender.ion- An uncalled closure drops itsSenderso a discardedReceivercan 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 sameint(exit 13) -
test_closure_box_once.ion- One move closure ofBox(exit 1) -
test_closure_spawn_read.ion-spawninside a closure reads the capture (exit 0) -
test_closure_box_twice_error.ion- Second closure moving the sameBox(negative,UseAfterMove) -
test_closure_vec_twice_error.ion- Second closure moving the sameVec(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::unwrapafter the&carrier's last use, outside a closure (exit 1) -
test_closure_copy_after_borrow.ion-Copycapture 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/.1access, 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::setreturnsSetResult::Ok/OutOfBounds(exit 0) -
test_file_rw.ion-File::create/write/open/readround-trip (exit 0) -
test_file_field.ion-File::write/read/closeon aFilefield through&mut Holder(exit 0) -
test_file_send_error.ion-channel<File>()is notSend -
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_send_channel_ok.ion-fn wrap<T: Send>maychannel<T>()withint(exit 42)test_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_unbounded_t_send_error.ion- unboundedTis notSend;channel<T>()insidewrap<T>pluswrap(&x)is rejectedtest_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_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-formmatchwithout 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_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_vec_get_ref_moved_error.ion-Vec::get_refon a movedVecas amatchscrutinee (UseAfterMove)test_vec_get_ref_moved_let_error.ion-Vec::get_refon a movedVecas aletinitializer (UseAfterMove)test_vec_get_moved_error.ion-Vec::geton a movedVec(UseAfterMove)test_vec_method_get_ref_moved_error.ion- methodget_refon a movedVec(UseAfterMove)test_vec_method_get_moved_error.ion- methodgeton a movedVec(UseAfterMove)test_move_in_loop.ion- Use-after-move when a non-copy value is moved on a reentering loop pathtest_move_in_loop_for.ion- Same reentry rule forforloops (outer binding moved in body)test_move_in_loop_break_use_error.ion- Move then break, then use after the looptest_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-whilehead still owns the box and thebreakpath moved it (exit 0)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_unbounded_t_send_error.ion- unbounded genericTis not a Send channel element (also listed under Trait bounds)test_send_ref_error.ion- Non-Send send operationstest_send_result_ref_error.ion-SendResult<&int>is not Send as a channel elementtest_channel_capacity_literal_error.ion- Literalchannel<T>(0)is a compile errortest_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_vec_method_get_ref_push_error.ion-v.pushwhilev.get_refborrow is active (BorrowConflict)test_get_ref_if_push_error.ion-Vec::pushin anifinside aget_refarm, then a use of the binding (BorrowConflict)test_get_ref_else_push_error.ion-Vec::pushin theelseof that arm (BorrowConflict)test_get_ref_method_if_push_error.ion-v.pushin anifinside av.get_refarm (BorrowConflict)test_get_ref_while_if_push_error.ion-pushin anifinside awhilein 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 awhilecovers a laterpushin that loop (BorrowConflict)test_borrow_if_nested_else_error.ion-pushin anifnested inelse, then a use of the loan (BorrowConflict)test_arena_slots_get_ref_insert_error.ion-handle::insertwhilea.slots.get_refborrow is active (BorrowConflict)test_arena_slots_get_ref_remove_error.ion-handle::removewhilea.slots.get_refborrow is active (BorrowConflict)test_arena_slots_get_ref_let_error.ion-handle::insertwhile aletofa.slots.get_refis live (BorrowConflict)test_arena_slots_get_ref_if_insert_error.ion-handle::insertin anifinside ana.slots.get_refarm, then a use of the slot (BorrowConflict)test_slice_get_ref_return_error.ion- Returning&TfromSlice::get_ref(ReferenceEscape)test_slice_get_ref_mut_error.ion- Indexed writearr[i] = ...whileSlice::get_refborrow is active (BorrowConflict)test_slice_len_error.ion-Slice::lenon a non-slice argument (TypeMismatch)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_extern_rust_error.ion- Non-"C"externlinkagetest_if_bool_required.ion- Boolean requirement for if conditionstest_break_continue_error.ion-breakoutside of a loop (negative test)
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 boundstest_copy_struct.ion- a struct of integers isCopy(exit 3)test_copy_drop_error.ion-impl Dropkeeps 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 afterbreakis not reentry (exit 1)test_loop_exit_disagree.ion- disagreeingbreakpaths 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 oneifarm does not block the other (exit 3)test_get_ref_if_after_use.ion-pushin anifafter theget_refbinding's last use (exit 0)test_const_match_cast.ion- const integer width,as, andmatch(exit 65)test_iter_for.ion-foroverIter<T>(exit 33)test_iter_for_error.ion-forover a type that is not iterable (negative)test_map_iter.ion- consumingHashMapiterator (exit 33)test_join_value.ion-joinmovesintout ofJoinHandle<int>(exit 7)test_scope_join.ion-scopejoins a statementspawn(exit 7)test_scope_move_out.ion- a handle returned fromscopeis not joined there (exit 0)test_allocator.ion-heap()isCopy;Vec::new_inandBox::new_in(exit 12)test_allocator_unsafe_error.ion-make_allocatoroutsideunsafe(negative)test_protocol.ion- unique endpoint send and recv (exit 7)test_protocol_move_error.ion- a secondsendof the same endpoint (negative)test_protocol_direction_error.ion-sendwhen the step isrecv(negative)test_protocol_copy_error.ion- a non-Copypayload (negative)test_protocol_ended_error.ion-sendafterend(negative)
- 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.shchannel_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)
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 (detect_leaks=0), a leak-sanitizer (LSan) step (detect_leaks=1) onBox::unwraptests plus named heap-dropruntests (Vec/Box/tuple/array/break/continue/Vec::setofString), thread sanitizer (TSan) ontest_channel_*/test_spawn_*/test_join/test_select*/test_try_recvrun tests and onchannel_drop_race.c, and runs the full harness with-Wall -Wextra -Werroron Linux. macOS CI runs the harness with Clang.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