Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
145 commits
Select commit Hold shift + click to select a range
50ca70c
Cache completed dev builds before compile-time demand discovery
rtfeldman Oct 25, 2026
3f013f7
Index dev specialization offers and load artifact payloads lazily
rtfeldman Oct 25, 2026
afccc11
Name the error sets of the indexed pack test fixture
rtfeldman Oct 25, 2026
9f1f8c9
List every case in the completed compilation record switches and docu…
rtfeldman Oct 25, 2026
bbdb924
Reduce dev cache lookup and publication work after edits
rtfeldman Oct 25, 2026
8c07778
Validate completed build inputs before loading checked IR
rtfeldman Oct 25, 2026
58b0bff
Switch on the offer bucket open error instead of comparing it
rtfeldman Oct 25, 2026
7ad9163
Report recorded pack links for completed dev builds and count the exa…
rtfeldman Oct 25, 2026
ec45bd0
Merge remote-tracking branch 'origin/main' into bs-12572
rtfeldman Oct 25, 2026
3a3002b
Trace the compile-time pack a command publishes
rtfeldman Oct 25, 2026
fd3fd52
Have the shared object cache primer compile the Lib procedures it offers
rtfeldman Oct 25, 2026
34ecd77
Merge remote-tracking branch 'origin/main' into bs-12572
rtfeldman Oct 25, 2026
5975190
Merge origin/main into issue-12571-index-dev-packs
rtfeldman Oct 25, 2026
cde23a9
Name dev-backend code excerpts and arranged images in design vocabulary
rtfeldman Oct 25, 2026
c8af9c2
Merge remote-tracking branch 'origin/main' into bs-12597
rtfeldman Oct 25, 2026
9d18359
Reuse immutable specialization interfaces across application edits
rtfeldman Oct 25, 2026
d5baa13
Replace cache sweeps with bounded indexed maintenance
rtfeldman Oct 25, 2026
e5609cb
Reuse complete dev test outcomes before compilation
rtfeldman Oct 25, 2026
9ee8a23
Reuse native procedure artifacts in dev tests
rtfeldman Oct 25, 2026
00a7af5
Reuse individual test outcomes from checked dependencies
rtfeldman Oct 25, 2026
29acd32
Reuse Monotype completion and inhabitation analyses
rtfeldman Oct 25, 2026
c97d39a
Reuse completed Lambda Solved compatibility traversals
rtfeldman Oct 25, 2026
dd58c1b
Merge PR #12572: completed dev build reuse
rtfeldman Oct 25, 2026
66c7084
Merge PR #12574: specialized native callables
rtfeldman Oct 25, 2026
7d3ef01
Separate anonymous memory from resident file pages in cache benchmarks
rtfeldman Oct 25, 2026
037225d
Merge PR #12575: incremental specialization and cache overhead
rtfeldman Oct 25, 2026
a27f75e
Merge PR #12579: native dev link preparation
rtfeldman Oct 25, 2026
801f3ac
Merge PR #12596: copied requirement reports
rtfeldman Oct 25, 2026
d60c079
Adapt merged cache fixtures and satisfy source checks
rtfeldman Oct 25, 2026
1839cb3
Merge PR #12598: incremental cache maintenance
rtfeldman Oct 25, 2026
da9cc21
Merge PR #12599: prune cached test roots before specialization
rtfeldman Oct 25, 2026
221982a
Merge PR #12600: completed test result cache
rtfeldman Oct 25, 2026
c3b5f83
Merge PR #12601: reusable native test artifacts
rtfeldman Oct 25, 2026
773cd3f
Merge PR #12602: reduce repeated Monotype analyses
rtfeldman Oct 25, 2026
b662934
Merge PR #12603: canonical lambda compatibility
rtfeldman Oct 25, 2026
089a4c2
Merge PR #12604: individual test result reuse
rtfeldman Oct 25, 2026
e96a6f3
Merge PR #12597: dev excerpts and arranged images terminology
rtfeldman Oct 25, 2026
5b85123
Update PR #12575 to current head
rtfeldman Oct 25, 2026
e9234d2
Update PR #12598 to current head
rtfeldman Oct 25, 2026
8454772
Reuse importer inference across unchanged checking dependencies
rtfeldman Oct 25, 2026
b9eb5c6
Merge PR #12610 into rebuild measurement integration
rtfeldman Oct 25, 2026
8e9cc57
Name the interface summary tests' node slots and keep their comments …
rtfeldman Oct 25, 2026
a5818a3
Merge remote-tracking branch 'origin/issue-12571-index-dev-packs' int…
rtfeldman Oct 25, 2026
2dfccdc
Name Lambda Solved compatibility proofs precisely and give test captu…
rtfeldman Oct 25, 2026
af0e04d
Merge remote-tracking branch 'origin/main' into bs-12572
rtfeldman Oct 25, 2026
21f12dc
Fix combined cache interfaces and checking publication regressions
rtfeldman Oct 25, 2026
01977b2
Merge remote-tracking branch 'origin/issue-12594-monotype-analysis-re…
rtfeldman Oct 25, 2026
1817466
Merge remote-tracking branch 'origin/issue-12595-lambda-solved-compat…
rtfeldman Oct 25, 2026
c6a6c3b
Name the error sets of the completion analysis test helpers
rtfeldman Oct 25, 2026
7743d4e
Merge remote-tracking branch 'origin/main' into bs-12572
rtfeldman Oct 25, 2026
7d43d9f
Wire the command record tests, document their types and name their er…
rtfeldman Oct 25, 2026
ad019d8
Merge remote-tracking branch 'origin/main' into bs-12600
rtfeldman Oct 25, 2026
e37277b
Give method definitions stored-constant eligibility and key inference…
rtfeldman Oct 25, 2026
946dbd7
Merge remote-tracking branch 'origin/main' into bs-12610
rtfeldman Oct 25, 2026
7241361
Merge remote-tracking branch 'origin/main' into bs-12601
rtfeldman Oct 25, 2026
a7aa1bd
Merge remote-tracking branch 'origin/main' into bs-12601
rtfeldman Oct 25, 2026
539a978
Merge remote-tracking branch 'origin/main' into bs-12575
rtfeldman Oct 25, 2026
bb3ed09
Separate test outcomes from current reporting locations
rtfeldman Oct 25, 2026
2e02aaa
Integrate current main and completed-command cache fixes
rtfeldman Oct 25, 2026
7c134fd
Reuse inference across declaration layout changes and checking diagno…
rtfeldman Oct 25, 2026
b487d6d
Point the Monotype counter test at the body counters' current positions
rtfeldman Oct 25, 2026
34f706c
Reuse settled recursive type-analysis proofs with exact invalidation
rtfeldman Oct 25, 2026
3d893ad
Merge remote-tracking branch 'origin/issue-12594-monotype-analysis-re…
rtfeldman Oct 25, 2026
cab189e
Identify the target of proof-storage measurements
rtfeldman Oct 25, 2026
a63dd93
Persist composite Monotype interface results and defaults
rtfeldman Oct 25, 2026
64fb973
Reuse owned native payloads and stable dependency components
rtfeldman Oct 25, 2026
ae83ab9
Merge remote-tracking branch 'origin/main' into issue-12616-recursive…
rtfeldman Oct 25, 2026
304ccd2
Preserve relocated type and default dependencies and validate replay
rtfeldman Oct 25, 2026
4f5f775
Merge remote-tracking branch 'origin/main' into dev-rebuild/west-lake…
rtfeldman Oct 25, 2026
cfcbab1
Merge updated completed-build PR while retaining integrated cache and…
rtfeldman Oct 25, 2026
dc937bf
Merge updated interface-cache PR retaining integrated completed-build…
rtfeldman Oct 25, 2026
e087d3d
Merge completed-test updates preserving individual-root record format
rtfeldman Oct 25, 2026
e5b0de7
Merge updated native-test PR with existing runtime symbol resolution
rtfeldman Oct 25, 2026
4306162
Merge remote-tracking branch 'origin/pr/12602' into dev-rebuild/west-…
rtfeldman Oct 25, 2026
8cc3250
Merge remote-tracking branch 'origin/pr/12603' into dev-rebuild/west-…
rtfeldman Oct 25, 2026
fbb49cc
Merge inference-reuse updates and shared callable-fact producer
rtfeldman Oct 25, 2026
dfe254f
Merge remote-tracking branch 'origin/pr/12618' into dev-rebuild/west-…
rtfeldman Oct 25, 2026
f9d4f6b
Merge composite interface caching with native test publication and in…
rtfeldman Oct 25, 2026
7bd665c
Merge relocatable test outcomes with indexed cache and command replay
rtfeldman Oct 25, 2026
61d3685
Merge immutable native publication with retained catalog generation l…
rtfeldman Oct 25, 2026
49862f2
Merge recursive proof reuse with combined diagnostic counter capacity
rtfeldman Oct 25, 2026
1269610
Reconcile native cache contracts and publish package test discoveries
rtfeldman Oct 25, 2026
a641972
Notify retained proofs only when their own dependencies change
rtfeldman Oct 25, 2026
feeaec1
Checkpoint incremental dev compilation and cache correctness improvem…
rtfeldman Oct 26, 2026
55df0a5
Reuse catalog sessions and skip unreachable loop-promotion analysis
rtfeldman Oct 26, 2026
92fdade
Memoize completed erased-callable traversals within a graph epoch
rtfeldman Oct 26, 2026
5fde02b
Reuse interned Monotypes before copying cross-store type rows
rtfeldman Oct 26, 2026
929cfc9
Preserve pending erased-call summaries across native cache hits
rtfeldman Oct 26, 2026
dda9cd7
Qualify loop promotion before constructing lifetime topology
rtfeldman Oct 26, 2026
8b9a740
Keep dev procedures reusable under an explicit owned base ABI
rtfeldman Oct 26, 2026
a356cff
Retain source eligibility facts for appended native artifacts
rtfeldman Oct 26, 2026
a47a887
Retain synchronized catalog mappings across native artifact acquisitions
rtfeldman Oct 26, 2026
fc2d9fd
Share immutable expansion reachability across argument scopes
rtfeldman Oct 26, 2026
5b13041
Preserve iterator normalization scope in cached procedure identities
rtfeldman Oct 26, 2026
135d6db
Borrow catalog nodes while traversing immutable query paths
rtfeldman Oct 26, 2026
e7b31bd
Limit dev specialization rewrites to iterator fusion
rtfeldman Oct 26, 2026
90bdf1a
Avoid continuation frames for cached checked type requests
rtfeldman Oct 26, 2026
a7acf6f
Instantiate simple builtins without allocating nominal tasks
rtfeldman Oct 26, 2026
2f7efac
Preserve structural backings when joining nominal owners
rtfeldman Oct 26, 2026
cf18ade
Add transactional codec for immutable specialization payloads
rtfeldman Oct 27, 2026
c06337c
Prototype completed lambda analysis reuse for exact lifted inputs
rtfeldman Oct 27, 2026
03a8824
Keep annotation restoration read-only over frozen input types
rtfeldman Oct 27, 2026
f6116ef
Persist only reachable completed lambda analysis types
rtfeldman Oct 27, 2026
8d6c4ee
Key lambda analysis by canonical reachable input types
rtfeldman Oct 27, 2026
40c437f
Preserve packed and field alignment when relocating frozen payloads
rtfeldman Oct 27, 2026
d24db2b
Keep whole-program lambda analysis caching opt-in while refining reus…
rtfeldman Oct 27, 2026
2a7ce8e
Prototype validated completed body replay with portable static-value …
rtfeldman Oct 27, 2026
8a75479
Retain complete dispatch inputs and owned lexical bodies in replay pr…
rtfeldman Oct 27, 2026
e03993e
Admit deferred context-free procedure values to completed body replay
rtfeldman Oct 27, 2026
7b1abe6
Instrument specialization request resolution phases
rtfeldman Oct 27, 2026
2769678
Separate native cache acquisition from specialization classification
rtfeldman Oct 27, 2026
af077dc
Rebind current capture provenance when reusing lambda analysis
rtfeldman Oct 27, 2026
ffc346f
Skip unadvertised native cache namespaces within each snapshot
rtfeldman Oct 27, 2026
60830bd
Decode retained catalog records in place and reuse shard paths
rtfeldman Oct 27, 2026
b0f992f
Acquire native dependency records in bounded shard groups
rtfeldman Oct 27, 2026
e6e0d57
Merge remote-tracking branch 'origin/main' into dev-rebuild/west-lake…
rtfeldman Oct 27, 2026
a78b190
Satisfy the static checks: exhaustive switches, explicit error sets, …
rtfeldman Oct 27, 2026
b3a9468
Give block-local and for-clause type declarations test identities, an…
rtfeldman Oct 27, 2026
ba4c1bd
Give each test alias its own backing root, and check the inhabitation…
rtfeldman Oct 27, 2026
f896fef
Time completed-body caching with the lowering's own clock, so freesta…
rtfeldman Oct 27, 2026
869ff42
Publish test identities for destructure binders, the nodes importers …
rtfeldman Oct 27, 2026
1dc66f6
Specialize ARC modes in the optimized targets the compile-time value …
rtfeldman Oct 27, 2026
2e90374
Give the compile-time code identity test's dev target the owned proce…
rtfeldman Oct 27, 2026
ca25106
Name a test declaration's module by its own name, not a path-qualifie…
rtfeldman Oct 27, 2026
905727a
Represent a closed checked type by its owner's occurrence when the ow…
rtfeldman Oct 27, 2026
8a53455
Give the CLI runners' artifact_store module the base module it imports
rtfeldman Oct 27, 2026
54e586f
Persist and replay an undetermined field kind's placeholder slot, as …
rtfeldman Oct 27, 2026
5163596
Count a module cache section by its own scope, now that inference cap…
rtfeldman Oct 27, 2026
52501f5
Select object packs by cache family, since indexed packs are named by…
rtfeldman Oct 27, 2026
e744846
Invalidate the path dependency's test results with a code edit, since…
rtfeldman Oct 27, 2026
ae3ede3
Fix the blocked expect to one the passing expects do not already answer
rtfeldman Oct 27, 2026
c70f127
Damage completed test payloads through the indexed store, and require…
rtfeldman Oct 27, 2026
57244fd
Discover a template's literal roots from its body, never from cached …
rtfeldman Oct 27, 2026
623bd8c
Invalidate the native artifact fixture's results with code edits, sin…
rtfeldman Oct 27, 2026
19b8883
Check composite and default interface replay across the cached runs, …
rtfeldman Oct 27, 2026
f62dae5
Read the early CTFE fixture's debugging root at runtime, since a buil…
rtfeldman Oct 27, 2026
3723f2f
Size the serialized module environment for its callable-checking and …
rtfeldman Oct 28, 2026
adacf26
Keep dev builds from copying lists their standalone procedures used t…
rtfeldman Oct 28, 2026
79356c9
Keep encode's doc comment on encode
rtfeldman Oct 28, 2026
f0211fc
Build loop liveness rows from their successors so a loop's rows share…
rtfeldman Oct 28, 2026
414aa42
Match main.roc by exact spelling and fix host-specific cache test ass…
rtfeldman Oct 28, 2026
3dcf38e
Release a staging lease at publication so readers never see a publish…
rtfeldman Oct 28, 2026
46736bd
Give the macOS MiniCI build the same headroom as the Windows build
rtfeldman Oct 28, 2026
798c5dd
Create each shard's coordination file exclusively before locking it, …
rtfeldman Oct 28, 2026
773d115
Build expected staging paths from the store's own root so Windows sep…
rtfeldman Oct 28, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/ci_manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,9 @@ jobs:
if: needs.check-changes.outputs.heavy == 'true' && !github.event.pull_request.draft
name: minici-build (macos-15)
runs-on: macos-15
timeout-minutes: 120
# The Zig 0.17 build takes about 110 to 120 minutes here, so 120 leaves no
# margin.
timeout-minutes: 180
steps: *minici_build_steps

minici-build-windows:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/ci_zig.yml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ jobs:
- name: Run CLI tests (LLVM size/speed backends)
run: zig build run-test-cli -- --llvm-only

- name: Run focused test outcome cache regressions
run: zig build run-test-outcome-cache -j2

# run-test-dylib and run-test-archive are MiniCI phases, so PR CI runs
# them on these same three OSes; they are not repeated here.

Expand Down
9 changes: 8 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1933,7 +1933,7 @@ pub fn build(b: *std.Build) void {
\\/// Whether this compiler honors the developer-diagnostic environment
\\/// variables (`ROC_LIR_DUMP`, `ROC_SPEC_CENSUS`, `ROC_PACK_TRACE`,
\\/// `ROC_PACK_STATS`, `ROC_CTFE_DEMAND_TRACE`, `ROC_DEV_PACK_OBJECTS`,
\\/// `ROC_DEV_PACK_HITS`, `ROC_DEV_ARTIFACT_ROUNDTRIP`,
\\/// `ROC_DEV_PACK_HITS`, `ROC_DEV_EXCERPT_ROUNDTRIP`,
\\/// `ROC_TEST_LLVM_OBJECT_COMPILE_COUNT_FILE` and `ROC_BACKGROUND_OBJECTS`). Each executable reads its
\\/// own optimize mode: a Debug compiler always does, and any other build
\\/// only with `-Ddev-diagnostics`, so a release compiler reads none of
Expand Down Expand Up @@ -2477,6 +2477,11 @@ pub fn build(b: *std.Build) void {
const roc_exe = main_exe_result.exe;
roc_exe.root_module.addImport("compiler_version", compiler_version_module);
const fixture_roc = executableRuntimePath(b, roc_exe, strip_macho_exports_tool);
const outcome_cache_check = b.addSystemCommand(&.{"python3"});
outcome_cache_check.addFileArg(b.path("ci/test_outcome_cache.py"));
outcome_cache_check.addArg("--roc");
outcome_cache_check.addFileArg(fixture_roc);
b.step("run-test-outcome-cache", "Check per-root test outcome reuse with focused CLI fixtures").dependOn(&outcome_cache_check.step);
const fixture_options = b.addOptions();
// Zig resolves this path against the build runner's directory. Tests run
// in prepared fixture roots, so they also need the absolute build root.
Expand Down Expand Up @@ -2780,6 +2785,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "bytebox", .module = bytebox.module("bytebox") },
.{ .name = "build_options", .module = roc_modules.build_options },
.{ .name = "msvc_runtime", .module = b.createModule(.{ .root_source_file = b.path("src/echo_platform/msvc_runtime.zig") }) },
.{ .name = "artifact_store", .module = b.createModule(.{ .root_source_file = b.path("src/compile/artifact_store.zig"), .imports = &.{.{ .name = "base", .module = roc_modules.base }} }) },
},
}),
});
Expand Down Expand Up @@ -4165,6 +4171,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "bytebox", .module = bytebox.module("bytebox") },
.{ .name = "build_options", .module = roc_modules.build_options },
.{ .name = "msvc_runtime", .module = b.createModule(.{ .root_source_file = b.path("src/echo_platform/msvc_runtime.zig") }) },
.{ .name = "artifact_store", .module = b.createModule(.{ .root_source_file = b.path("src/compile/artifact_store.zig"), .imports = &.{.{ .name = "base", .module = roc_modules.base }} }) },
},
}),
.filters = test_filters,
Expand Down
341 changes: 341 additions & 0 deletions ci/test_outcome_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
#!/usr/bin/env python3
"""Focused individual-outcome and reporting-relocation checks (#12593/#12615)."""

import argparse
from concurrent.futures import ThreadPoolExecutor
import os
from pathlib import Path
import re
import subprocess
import tempfile


def is_outcome(path):
if not path.is_file():
return False
with path.open('rb') as source:
return source.read(len(b'ROC_TEST_OUTCOMES_V3')) == b'ROC_TEST_OUTCOMES_V3'


def transcript(data):
# Only the final run epilogue is outside the transcript contract.
return re.split(rb"(?m)^(?:All \(\d+\) tests passed|Ran \d+ tests) in ", data)[0]


class Fixture:
def __init__(self, root, roc):
self.root = root
self.roc = roc
self.cache = root / "cache"
self.env = dict(os.environ, ROC_CACHE_DIR=str(self.cache), ROC_TRACE_BOXY_WORK="1")
self.path = root / "Main.roc"

def write(self, text, name="Main.roc"):
(self.root / name).write_text(text)

def run(self, *, cache=True, timings=False, mode="dev", work=False):
args = [str(self.roc), "test", str(self.path), f"--opt={mode}",
"--specialize=yes", "--no-color", "--verbose"]
if not cache:
args.append("--no-cache")
if timings:
args.append("--timings")
env = dict(self.env, ROC_TEST_COMMAND_WORK="1") if work else self.env
result = subprocess.run(args, env=env, capture_output=True, timeout=120)
assert result.returncode in (0, 1, 2), result.stderr.decode(errors="replace")
assert b"panic" not in result.stderr, result.stderr.decode(errors="replace")
assert b"boxy-work:" not in result.stderr, result.stderr.decode(errors="replace")
return result

def check(self, hits=None, *, mode="dev"):
cached = self.run(mode=mode)
fresh = self.run(cache=False, mode=mode)
assert cached.returncode == fresh.returncode, (cached.stderr, fresh.stderr)
for stream in ("stdout", "stderr"):
a, b = transcript(getattr(cached, stream)), transcript(getattr(fresh, stream))
assert a == b, (stream, a.decode(errors="replace"), b.decode(errors="replace"))
if hits is not None:
# The first run above publishes all misses, so all roots must now hit.
warm = self.run(timings=True, mode=mode)
self.hits(warm, hits)
text = re.sub(r"\s+", " ", warm.stderr.decode(errors="replace"))
if 'Completed test command' in text:
return cached
for label in ('Roots submitted for specialization',):
assert re.search(label + r"\s+0\b", text), text
if mode == 'dev':
for label in ('Root wrappers generated', 'Roots executed'):
assert re.search(label + r"\s+0\b", text), text
return cached

@staticmethod
def hits(result, expected):
text = result.stderr.decode(errors="replace")
text = re.sub(r"\s+", " ", text)
match = re.search(r"Roots cached\s+(\d+)", text)
assert match, text
assert int(match[1]) == expected, (expected, match[1], text)


def relocation_checks(f):
def edit(source, hits, executed=0, name="Main.roc"):
f.write(source, name)
run = f.run(timings=True, work=True)
f.hits(run, hits)
counter = re.search(rb"test-work test_root_execution=(\d+)", run.stderr)
assert counter and int(counter[1]) == executed, run.stderr.decode(errors="replace")
f.check()

passing = 'expect { dbg "Main.roc:123: user bytes"\n 1 == 1 }\n'
failing = 'expect { dbg "failed"\n 2 == 3 }\n'
base = passing + failing
f.write(base)
f.check(2)
edit('# inserted comment\n\n' + base, 2)
edit(base, 2)
edit(failing + passing, 2)
edit(failing + passing.replace('1 == 1', '100 == 100'), 1, 1)
edit(passing.replace('1 == 1', '1000 == 1000') + failing, 1, 1)
edit('expect 42 == 42\n' + passing + failing, 2, 1)
edit(passing * 3 + failing * 2, 5)
edit('\n# duplicate occurrences move together\n' + failing * 2 + passing * 3, 5)

duplicate_inline = 'expect { expect False\n dbg "nested duplicate"\n True }\n'
f.write(duplicate_inline * 2)
f.check(2)
edit('# before duplicate inline sites\n' + duplicate_inline + '\n' + duplicate_inline, 2)

helper = ('observe = |x| { dbg "Helper.roc:5: user text"\n'
' expect x > 0\n x }\n')
roots = 'expect observe(1) == 1\nexpect observe(0) == 0\n'
f.write(helper + roots)
f.check(2)
edit('# move helper and roots\n\n' + helper + roots, 2)
edit(roots + '\n# helper moved independently\n' + helper, 2)
edit(roots + helper.replace('x > 0', 'x > 1'), 0, 2)

imported = 'module [observe]\n' + helper
f.write(imported, 'Observer.roc')
f.write('import Observer\nexpect Observer.observe(0) == 0\n')
f.check(1)
edit('module [observe]\n# helper moved\n\n' + helper, 1, name='Observer.roc')
edit(imported.replace('x > 0', 'x > 1'), 0, 1, name='Observer.roc')

typed = ('module [make]\nThing := { value : U64 }\n'
'make = || Thing.{ value: 1 }\n')
f.write(typed, 'Typed.roc')
f.write('import Typed\nhelper = || Typed.make()\nexpect helper().value == 1\n')
f.check(1)
edit('# moved imported nominal and helper\n' + typed, 1, name='Typed.roc')
edit(typed.replace('module [make]\n', 'module [make]\nexpect 37 == 37\n'), 1, 1, name='Typed.roc')
edit(typed.replace('value: 1', 'value: 2'), 0, 1, name='Typed.roc')

alias = ('module [make]\nThing : { value : U64 }\n'
'make : {} -> Thing\nmake = |_| { value: 1 }\n')
f.write(alias, 'TypedAlias.roc')
f.write('import TypedAlias\nhelper = || TypedAlias.make({})\nexpect helper().value == 1\n')
f.check(1)
edit('# moved imported alias\n' + alias, 1, name='TypedAlias.roc')
edit(alias.replace('U64', 'I64'), 0, 1, name='TypedAlias.roc')

default_helper = ('WithDefault := { value : U64 ?? compute() }.{}\n'
'compute = || { expect True\n 1 }\n')
f.write(default_helper, 'WithDefault.roc')
f.write('import WithDefault\nexpect WithDefault.{}.value == 1\n')
f.check(1)
edit('# moved a default and its helper\n' + default_helper, 1, name='WithDefault.roc')
edit(default_helper.replace(' 1 }', ' 2 }'), 0, 1, name='WithDefault.roc')

defaults = ('Config := { value : U64 ?? 1 }\n'
'make = || Config.{}\n'
'expect make().value == 1\n')
f.write(defaults)
f.check(1)
edit('# before a defaulted type\n' + defaults, 1)
edit(defaults.replace('?? 1', '?? 2'), 0, 1)

scoped = ('left = || { value = 1\n expect value == 1\n True }\n'
'right = || { value = 2\n expect value == 1\n True }\n'
'expect left()\nexpect right()\n')
f.write(scoped)
f.check(2)
edit('# before distinct binding scopes\n' + scoped, 2)
edit(scoped.replace('value = 2', 'value = 3'), 1, 1)

asset = 'import "asset.txt" as data : Str\nexpect data == "before"\n'
f.write('before', 'asset.txt')
f.write(asset)
f.check(1)
edit('# before a file import\n' + asset, 1)
edit('after', 0, 1, name='asset.txt')

# Failure details include current binding excerpts; user messages are exact.
crashing = 'expect { dbg "unchanged crash output"\n crash "Main.roc:99" }\n'
f.write(crashing)
f.check(1)
edit('# before a crash\n' + crashing, 1)
question = 'expect { value : Try(U64, [Oops])\n value = Err(Oops)\n value? == 1 }\n'
f.write(question)
f.check(1)
edit('\n# before an extended failure span\n' + question, 1)


def exercise(f):
base = ('helper = |x| x + 1\n'
'expect helper(2) == 3\n'
'expect 4 == 4\n'
'expect { dbg "stable"\n 5 == 6 }\n')
f.write(base)
f.hits(f.run(timings=True), 0)
f.check(3)
f.write(base.replace('4 == 4', '4 == 3'))
f.hits(f.run(timings=True), 2)
f.check(3)
f.write(base)
f.hits(f.run(timings=True), 3)
f.write(base.replace('x + 1', 'x + 2'))
f.hits(f.run(timings=True), 2)
f.check(3)

binding = 'value = 1\nexpect value == 2\nexpect 3 == 3\n'
f.write(binding)
f.check(2)
f.write(binding.replace('value = 1', 'value= 1'))
f.hits(f.run(timings=True), 1)
f.check(2)

# Duplicate bodies remain separate reporting slots\n moving, adding and
# deleting roots must preserve current source provenance and multiplicity.
duplicate = 'expect { dbg "duplicate"\n 1 == 1 }\n'
for source in (duplicate * 2, '\n' + duplicate * 2,
duplicate * 3, duplicate, duplicate + base):
f.write(source)
f.check(source.count('expect '))

# Both roots reach the same inline source site. Editing one root changes
# its contribution while preserving coalesced counts and reporting order.
inline = ('observe = |x| { expect x > 0\n x }\n'
'expect observe(1) == 1\n'
'expect observe(0) == 0\n')
f.write(inline)
f.check(2)
f.write(inline.replace('observe(0) == 0', 'observe(2) == 2'))
f.hits(f.run(timings=True), 1)
f.check(2)
f.write(inline)
f.hits(f.run(timings=True), 2)
f.check(2)

# Static dependency closure includes a branch never executed, recursive
# helpers, a compile-time constant and a closure capture.
deps = ('unused = |x| x + 1\n'
'recur = |n| if n == 0 { 0 } else { recur(n - 1) }\n'
'constant = 17\n'
'capture = |x| { y = constant\n |z| x + y + z }\n'
'expect (if True { 1 } else { unused(0) }) == 1\n'
'expect recur(3) == 0\n'
'expect capture(1)(2) == 20\n'
'expect 7 == 7\n')
f.write(deps)
f.check(4)
for old, new, hits in [('x + 1', 'x + 2', 3),
('{ 0 }', '{ 1 }', 3),
('constant = 17', 'constant = 18', 3)]:
f.write(deps.replace(old, new))
f.hits(f.run(timings=True), hits)
f.check(4)
f.write(deps)
f.check(4)

# Imported implementation summaries, including transitive dependencies,
# distinguish a relevant helper change from an unrelated imported test.
leaf = 'module [helper]\nhelper = |x| x + 1\nexpect 8 == 8\n'
middle = 'module [call]\nimport Leaf\ncall = |x| Leaf.helper(x)\n'
main = 'import Middle\nexpect Middle.call(2) == 3\nexpect 9 == 9\n'
f.write(leaf, 'Leaf.roc')
f.write(middle, 'Middle.roc')
f.write(main)
f.check(3)
f.write(leaf.replace('8 == 8', '8 == 7'), 'Leaf.roc')
f.hits(f.run(timings=True), 2)
f.check(3)
f.write(leaf.replace('x + 1', 'x + 2'), 'Leaf.roc')
# The independent Main root and reverted Leaf root remain hits.
f.hits(f.run(timings=True), 2)
f.check(3)

# Custom numeral conversion and inspection are semantic inputs, including
# the generated structural inspection used by the second root.
custom = """MyNum := { value: I64 }.{
from_numeral : Numeral -> Try(MyNum, [InvalidNumeral(Str)])
from_numeral = |_| {
dbg "conversion"
Ok({ value: 1 })
}
plus : MyNum, MyNum -> MyNum
plus = |a, b| { value: a.value + b.value }
to_i64 = |a| a.value
to_inspect : MyNum -> Str
to_inspect = |_| "before"
}
add_one = |x| x.plus(1)
expect {
value : MyNum
value = { value: 0 }
result = add_one(value)
dbg result
result.to_i64() == 1
}
expect {
dbg { names: ["one", "two"], tag: Some(3) }
True
}
"""
for source in (custom, custom.replace('value: 1', 'value: 2'),
custom.replace('"before"', '"after!"'), custom):
f.write(source)
f.check(2)
# Other consumers preserve the same partial-admission ownership contract.
for mode in ('interpreter', 'speed'):
f.check(2, mode=mode)
f.write(main)
f.check(3)

# Atomic publication and checksums: every truncated or mutated outcome is
# a miss, even if its corrupted status byte would otherwise be valid.
outcomes = [p for p in f.cache.rglob('*') if is_outcome(p)]
assert outcomes, 'no per-root outcome records published'
for p in outcomes:
data = bytearray(p.read_bytes())
if len(data) % 2:
p.write_bytes(data[:20])
else:
data[-33] ^= 1
p.write_bytes(data)
f.hits(f.run(timings=True), 0)
f.check(3)
for p in f.cache.rglob('*'):
if is_outcome(p):
p.unlink()
with ThreadPoolExecutor(max_workers=3) as pool:
runs = list(pool.map(lambda _: f.run(), range(3)))
for result in runs:
assert result.returncode == runs[0].returncode
assert transcript(result.stdout) == transcript(runs[0].stdout)
assert transcript(result.stderr) == transcript(runs[0].stderr)
f.check(3)


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--roc', type=Path, required=True)
args = parser.parse_args()
with tempfile.TemporaryDirectory(prefix='roc-test-outcomes-') as temp:
fixture = Fixture(Path(temp), args.roc.resolve())
exercise(fixture)
relocation_checks(fixture)
print('issues 12593/12615: focused test-outcome cache checks passed')


if __name__ == '__main__':
main()
Loading
Loading