Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions array/array_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ test "zip_with" {
let right = [10, 20]
inspect(@array.zip_with(left, right, (a, b) => a + b), content="[11, 22]")
}

///|
test "zip_with left shorter" {
let left = [1, 2]
let right = [10, 20, 30]
inspect(@array.zip_with(left, right, (a, b) => a + b), content="[11, 22]")
}
27 changes: 27 additions & 0 deletions bench/bench_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,30 @@ test "bench buffer writes with multiple samples" {
bench.bench(() => ignore(2 + 2), name="second", count=1)
inspect(bench.dump_summaries().contains(","), content="true")
}

///|
fn busy_wait_us(target_us : Double) -> Unit {
let start = @bench.monotonic_clock_start()
let mut elapsed = 0.0
while elapsed < target_us {
elapsed = @bench.monotonic_clock_end(start)
}
}

///|
test "bench slow samples clamp batch size and winsorize" {
let mut calls = 0
let summary = @bench.single_bench(
() => {
let is_slow = calls % 2 == 0
calls = calls + 1
if is_slow {
busy_wait_us(120000.0)
}
},
count=4,
)
let json = ToJson::to_json(summary).stringify()
inspect(json.contains("\"batch_size\""), content="true")
inspect(json.contains("\"runs\""), content="true")
}
6 changes: 6 additions & 0 deletions bigint/bigint_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ test "bit_length zero" {
inspect(zero.bit_length(), content="0")
}

///|
test "bit_length default" {
let zero = @bigint.BigInt::default()
inspect(zero.bit_length(), content="0")
}

///|
test "add" {
let a = @bigint.BigInt::from_int64(123456789012345678L)
Expand Down
27 changes: 27 additions & 0 deletions builtin/array_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,33 @@ test "array_sort_by_pred_skip_duplicates" {
assert_eq(arr[31], 9)
}

///|
struct HeapSortProbe {
value : Int
}

///|
impl Eq for HeapSortProbe with equal(_self, _other) -> Bool {
false
}

///|
impl Compare for HeapSortProbe with compare(_self, _other) -> Int {
1
}

///|
test "array_sort heap fallback" {
// Force unbalanced partitions to exercise the heap sort fallback.
let arr : FixedArray[HeapSortProbe] = FixedArray::make(64, { value: 0 })
for i in 0..<arr.length() {
arr[i] = { value: i }
}
arr.sort()
assert_eq(arr.length(), 64)
ignore(arr[0].value)
}

///|
test "array_each" {
let arr = [1, 2, 3]
Expand Down
9 changes: 9 additions & 0 deletions builtin/fixedarray_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ test "FixedArray::iter2 with empty array" {
inspect(count, content="0")
}

///|
#cfg(target="js")
test "FixedArray::copy on js" {
let arr : FixedArray[Int] = [1, 2, 3]
let copied = arr.copy()
inspect(copied, content="[1, 2, 3]")
inspect(physical_equal(arr, copied), content="false")
}

///|
test "FixedArray::iter2 with single element" {
let arr = FixedArray::make(1, 42)
Expand Down
8 changes: 8 additions & 0 deletions builtin/hasher_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ test "combine float" {
inspect(hash(0.0) == hash(0.1), content="false")
}

///|
#cfg(target="js")
test "hasher default seed on js" {
let hasher = Hasher::new()
hasher.combine_int(1)
inspect(hasher.finalize().to_string().length() > 0, content="true")
}

///|
test "combine string" {
let hash = v => {
Expand Down
9 changes: 9 additions & 0 deletions builtin/to_string_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ test "to_string runtime zero paths" {
inspect(zero.to_int64().to_string(radix=2), content="0")
}

///|
#cfg(target="js")
test "to_string js wrappers" {
inspect((255).to_string(radix=16), content="ff")
inspect(255U.to_string(radix=16), content="ff")
inspect(255L.to_string(radix=16), content="ff")
inspect(255UL.to_string(radix=16), content="ff")
}

///|
test "panic UInt64::to_string invalid radix" {
let arr = Array::new()
Expand Down
93 changes: 93 additions & 0 deletions coverage/coverage_gaps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Coverage gaps (black-box only)

This note documents remaining uncovered lines after the black-box test pass and
why they are currently out of reach.

Last coverage run (wasm-gc):
- `moon coverage analyze -- -f summary`

## JS coverage blocked (compiler ICE)
Attempting JS coverage currently crashes the compiler:
- Command: `moon test --target js --enable-coverage`
- moonc: v0.6.37+0b3e4ae80
- Error: assertion failure in `moonc.ml` during link-core

This blocks coverage of JS-only code paths listed below.

## Target-specific or host-dependent
- JS-only functions or externs (blocked by JS ICE):
- `builtin/fixedarray.mbt:1633` `FixedArray::copy` (#cfg target=js)
- `builtin/hasher.mbt:85` `seed` via `random_seed()` (#cfg target=js)
- `float/methods.mbt:886,927` JS conversions (#cfg target=js)
- `int16/int16.mbt:213,219` JS conversions (#cfg target=js)
- `builtin/to_string.mbt:339,353,699,717` JS wrappers (#cfg target=js)
- WASM host dependent:
- `env/env_wasm.mbt:102` `current_dir_internal` returns `None` only if the
host returns an empty string; not controllable from tests.

## Invariant / defensive branches (unreachable by design)
- `bigint/bigint_nonjs.mbt:1816` `len == 0` guard; BigInt invariants keep
`len > 0` for public values.
- `encoding/utf16/decode.mbt:53,78,133,161` `ch > 0x10FFFF` is impossible for
valid surrogate pairs; the guard is defensive.
- `builtin/to_string.mbt:204,216,436,448` `hex_count*`/`radix_count*` zero paths
are bypassed by early return in `to_string` when value is zero.
- `string/regex/internal/regexp/internal/unicode/case_folding.mbt:27` odd-length
DATA guard should be unreachable (data is even-length).
- `json/parse.mbt:61,86,89,114` `abort("unreachable")` arms in parser token
state machine.
- `string/regex/internal/regexp/internal/vm/impl.mbt:201` panic for unexpected
instruction; unreachable under valid compilation.

## Private helpers (not callable from black-box tests)
- `hashmap/utils.mbt:17-25` `_debug_entries` is private.
- `hashset/hashset.mbt:462-493` `_debug_entries` and `MyString` helpers are
private to the package.
- `immut/array/tree.mbt` internal `abort` branches and helper paths.
- `immut/array/tree_utils.mbt` internal helpers.
- `immut/hashmap/HAMT.mbt` internal mutation paths.
- `immut/hashset/HAMT.mbt` internal mutation paths.
- `immut/priority_queue/priority_queue.mbt` internal rebalance paths.
- `immut/sorted_set/immutable_set.mbt` internal balancing paths.

## Algorithmic edge cases not hit by current black-box inputs
These are reachable in principle, but require very specific input patterns that
are hard to trigger without white-box hooks or dedicated generators:
- `builtin/array_sort_impl.mbt:253` heap sort fallback when quicksort limit
reaches zero. Attempted a degenerate `Compare` ordering in
`builtin/array_test.mbt`, but the fallback is still not observed.
- `builtin/linked_hash_map.mbt:1004,1011,1021,1024` view comparison branches
not hit by current `Map::get_from_string/get_from_bytes` cases.
- `builtin/string_methods.mbt:1215,1265` defensive break on final segment in
`replace_all` loops.
- `double/internal/ryu/ryu.mbt:229,255,435` carry/rounding branches.
- `double/scalbn.mbt:17,20,23,26,29,32,35,38,42,43` extreme exponent scaling.
- `math/log_double_nonjs.mbt:256,264,266` `ln_1p` edge-case branches; crafted
inputs still miss the `hu == 0` fast-paths due to early-return guards.
- `math/pow.mbt:229,231` subnormal base handling in float pow. Tried
`Float::reinterpret_from_int(1)` with exponent `3.0`, but wasm-gc still
bypasses the subnormal branch (likely subnormal flush-to-zero).
- `math/pow_double_nonjs.mbt:330,332` subnormal base handling in double pow.
Tried `1UL.reinterpret_as_double()` with exponent `3.0`, still no hit.
- `math/prime.mbt:287,323` rare primality branches. Tried a deterministic
`Rand` with `128017` and base `54`; still no hit on the `z == 1` branch.
- `math/trig_double_nonjs.mbt:433,461,512,582,635,640,663,664,665,666,689,714,
721,722,725,755,759,762,780,783,790,798,802,806,811,870,915,919,921,925`
specialized rounding and edge-case paths.
- `quickcheck/splitmix/random.mbt:86-87` `next_positive_int` special cases for
`-2147483648` and `0` need specific RNG states.
- `sorted_set/set.mbt:223,238,239,240,248` join/rotation variants.
- `strconv/decimal.mbt:282,359,360,483,484,497,498,506` rounding/truncation
paths that require crafted decimal inputs.
- `list/list.mbt:242,279,376,555,722,735,777,878,929,967,980,1194,1258,1319,
1370,1543,1578,1801` specialized list edge cases.
- `string/regex/internal/regexp/internal/parse/parse.mbt:495,583,624,625`
parser error branches for specific invalid regex patterns. Built runtime
patterns with `\f`/`\v` and escaped range endpoints, but these lines still
report uncovered.

## Follow-ups
- Once JS coverage works again, re-run `moon test --target js --enable-coverage`
to cover JS-only branches.
- For the algorithmic edge cases, consider targeted property tests or
generated inputs if white-box tests are allowed in the future.
11 changes: 11 additions & 0 deletions double/double_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,14 @@ test "Double::is_neg_inf" {
test "min equal to neg max" {
assert_true(min_value == -max_value)
}

///|

///|
#warnings("-deprecated")
test "pow extreme exponents" {
let big = 2.0.pow(4000.0)
inspect(big.is_pos_inf(), content="true")
let small = 2.0.pow(-4000.0)
inspect(small == 0.0, content="true")
}
7 changes: 7 additions & 0 deletions float/float_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ test "Hash" {
)
}

///|
#cfg(target="js")
test "Float::from_int64 and from_uint64 on js" {
inspect(Float::from_uint64(42UL), content="42")
inspect(Float::from_int64(-42L), content="-42")
}

///|
test "Float::is_inf/special_values" {
inspect(@float.infinity.is_inf(), content="true")
Expand Down
9 changes: 9 additions & 0 deletions int16/int16_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -504,3 +504,12 @@ test "Int16 int64 conversions" {
let back = Int16::from_int64(as64)
inspect(back.to_int(), content="123")
}

///|
#cfg(target="js")
test "Int16 int64 conversions on js" {
inspect(Int16::from_int64(42), content="42")
inspect(Int16::from_int64(-42), content="-42")
inspect(Int16::to_int64(123), content="123")
inspect(Int16::to_int64(-123), content="-123")
}
22 changes: 22 additions & 0 deletions math/log_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ test "ln" {
assert_eq(@math.ln(5.562684646268003e-309), -709.782712893384)
}

///|
test "ln mantissa edge cases" {
assert_eq(@math.ln(1.0), 0.0)
let near_one = 0x3ff0000000000001UL.reinterpret_as_double()
let res_one = @math.ln(near_one)
assert_true((res_one - 2.220446049250313e-16).abs() < 1.0e-20)
let near_two = 0x4000000000000001UL.reinterpret_as_double()
let res_two = @math.ln(near_two)
assert_true((res_two - 0.6931471805599453).abs() < 1.0e-12)
}

///|
test "lnf subnormal input" {
let sub = Float::reinterpret_from_uint(1U)
Expand All @@ -123,6 +134,17 @@ test "ln_1p small and large values" {
assert_true(large_res > 0.0)
}

///|
test "ln_1p mantissa edge cases" {
let step = 2.98023223876953125e-8
let res_step = @math.ln_1p(step)
assert_true((res_step - step).abs() < 1.0e-12)
let near_two = 1.0 + step
let res_near_two = @math.ln_1p(near_two)
assert_true((res_near_two - 0.6931471805599453).abs() < 1.0e-7)
assert_true((@math.ln_1p(1.0) - 0.6931471805599453).abs() < 1.0e-15)
}

///|
test "ln_1p mid-range branches" {
let mid = @math.ln_1p(0.1)
Expand Down
14 changes: 14 additions & 0 deletions math/pow_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -1220,3 +1220,17 @@ test "scalbnf exponent clamp" {
assert_true(@math.scalbnf(1.0, 500).is_pos_inf())
inspect(@math.scalbnf(1.0, -500), content="0")
}

///|
test "powf subnormal base" {
let subnormal = Float::reinterpret_from_int(1)
let result = @math.powf(subnormal, 3.0)
assert_true(result == 0.0)
}

///|
test "pow subnormal double base" {
let subnormal = 1UL.reinterpret_as_double()
let result = @math.pow(subnormal, 3.0)
assert_true(result == 0.0)
}
27 changes: 27 additions & 0 deletions math/prime_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,33 @@ test "@math.is_probable_prime" {
inspect(@math.is_probable_prime(509033161N, r), content="false")
}

///|
struct SeqSource {
values : Array[UInt64]
mut idx : Int
}

///|
impl @random.Source for SeqSource with next(self) -> UInt64 {
let idx = self.idx
self.idx += 1
if idx < self.values.length() {
self.values[idx]
} else {
0UL
}
}

///|
test "miller rabin detects z == 1 branch" {
let gen_check : SeqSource = { values: [0UL, 0UL, 52UL], idx: 0 }
let rand_check = @random.Rand::new(generator=gen_check as &@random.Source)
inspect(rand_check.bigint(17), content="52")
let gen : SeqSource = { values: [0UL, 0UL, 52UL], idx: 0 }
let rand = @random.Rand::new(generator=gen as &@random.Source)
assert_false(@math.is_probable_prime(128017N, rand, iters=1))
}

///|
test "@math.probable_prime" {
let rand = @random.Rand::new()
Expand Down
12 changes: 12 additions & 0 deletions string/additional_coverage_test.mbt
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,15 @@ test "get_char invalid surrogate pair" {
let v = s[:]
inspect(v.get_char(0), content="None")
}

///|
test "replace_all on view with matches" {
let view = "foo bar foo".view()
inspect(view.replace_all(old="foo", new="baz"), content="baz bar baz")
}

///|
test "replace_all on string with matches" {
let s = "foo foo"
inspect(s.replace_all(old="foo", new="x"), content="x x")
}
Loading