// tests/worker_identity_acceptance.rs --- worker identity Stage 1. //! Worker identity Stage 1 (`docs/worker-identity-framing.md` §6, //! `COHERENCE.md` §9). //! //! §9 grades the worker model **mechanism without identity**: a job //! carries a `JobKind` naming the builtin dispatcher it funnelled //! through, so every third-party job renders under a builtin's label, //! and no progress indicator exists anywhere. This suite pins what //! Stage 1 does about that — a required `purpose` on the job and the //! process, the dispatch-name ambient that stops //! `pmacs.workers.dispatch` discarding its handler name, and the first //! indicator a user sees without running a command. //! //! # What is NOT here, deliberately //! //! **Presence is enforced by the COMPILER, not by anything below.** //! `JobSpec::purpose` is non-optional and `JobSpec` has no `Default`, so //! a dispatcher that supplies none does not build. A funnel test would //! prove only that the funnel stores what it was handed, and would say //! nothing about whether fourteen callers handed it anything meaningful. //! Everything below is about *semantics*. //! //! **That a raw `coroutine.yield` inside either dynamic scope is //! prevented — it is not.** R46 forbids package code from yielding //! raw, but it is a convention, and the scheduler diagnoses a non-Handle //! yield only *after* the coroutine has suspended (`async.lua` resumes, //! then inspects what came back), so no refusal sited in a yield helper //! is ever consulted. Rule 1 claims **the two supported yield APIs** and //! nothing more. A test that "proved" coverage this design does not have //! would be worse than the recorded gap, so the gap is recorded instead //! (framing §2, §6, §7). //! //! **That background work is attributable from one place** (Stage 2's //! unified view), **that a terminal PTY is visible anywhere** (Q#W-4), //! or **that any job is attributed to the PACKAGE responsible for it**. //! `purpose` records what work is being done and, under //! `pmacs.workers.dispatch`, which registered handler it ran under. //! Neither is package ownership, which waits for P3 — and there is no //! `owner` field, in any spelling, for it to squat on (framing §3, §7). use std::collections::HashMap; use std::time::{Duration, Instant}; use pmacs::async_runtime::JobKind; use pmacs::cell::{Cell, CellGrid, CellSize, Glyph}; use pmacs::editor::EditorState; use pmacs::protocol::FrontendId; use pmacs::statusline::{ StatuslineEvaluationOutcome, StatuslineEvaluationTarget, StatuslineProviderId, evaluate_statusline, }; #[path = "common/iso.rs"] mod iso; // --------------------------------------------------------------------------- // Harness // --------------------------------------------------------------------------- fn exec(state: &EditorState, source: &str) { state.lua_host.lua().load(source.to_owned()).exec().unwrap(); } fn eval(state: &EditorState, source: &str) -> T { state.lua_host.lua().load(source.to_owned()).eval().unwrap() } fn editor() -> EditorState { let state = EditorState::new_with_roots(&iso::roots()); exec(&state, "pmacs.lsp.config = {}"); state } /// Drive the async runtime until nothing is in flight and no coroutine /// is parked. How many frames that takes is not knowable in advance, so /// this never counts them. /// /// Quiescence is measured as **no `Running` job**, not as an empty /// pending table. Most jobs here are dispatched and never awaited — /// that is the shape the indicator exists to describe — and a settled /// entry stays in the pending table until someone takes its result, so /// `pending_count() == 0` would never come true. fn pump(state: &mut EditorState) { let deadline = Instant::now() + Duration::from_secs(10); loop { let idle: bool = eval( state, "return pmacs._async.parked_count() == 0 and #pmacs.workers.snapshot().active == 0", ); if idle { return; } assert!(Instant::now() < deadline, "async pump deadline exceeded"); state.tick_async(); } } /// The purposes of every job the runtime currently has in flight. /// /// Read through the **Lua** snapshot surface, which is what `*workers*` /// and any package consume, rather than through the Rust struct. fn active_purposes(state: &EditorState) -> Vec { eval( state, "local out = {} for _, job in ipairs(pmacs.workers.snapshot().active) do out[#out + 1] = job.purpose end return out", ) } fn paint(state: &EditorState, rows: u32, cols: u32) -> Vec { let mut cells = vec![Cell::default(); (rows * cols) as usize]; let mut grid = CellGrid { cells: &mut cells, stride: cols, size: CellSize::new(rows, cols), }; let _ = pmacs::editor::paint_frame( state, FrontendId::LOCAL, &HashMap::new(), &mut grid, CellSize::new(rows, cols), ); cells } fn row_text(cells: &[Cell], cols: u32, row: u32) -> String { (0..cols) .map( |column| match &cells[(row * cols + column) as usize].glyph { Glyph::Char(ch) => *ch, Glyph::Cluster(bytes) => std::str::from_utf8(bytes) .ok() .and_then(|text| text.chars().next()) .unwrap_or(' '), Glyph::Continuation => ' ', }, ) .collect() } /// The registration handle of the builtin activity provider. fn activity_provider(state: &EditorState) -> StatuslineProviderId { state .statusline_registry .borrow() .providers() .into_iter() .find(|provider| provider.name == "activity") .expect("builtin activity provider") .id } /// The activity provider's segment for `LOCAL`'s only window, or `None` /// when it produced **no segment at all**. /// /// `Option`, never `String`, is the whole point of this helper: /// "absent" and "empty" must be distinguishable, because a zero-width /// segment still consumes a separator in the composed modeline. fn activity_segment(state: &EditorState) -> Option { let id = activity_provider(state); let evaluation = evaluate_statusline( state.lua_host.lua(), &state.core, &state.statusline_registry, StatuslineEvaluationTarget::Grid { frontend_id: FrontendId::LOCAL, }, ); let StatuslineEvaluationOutcome::Ready(windows) = evaluation.outcome else { panic!("statusline evaluation must be ready in a single-window editor"); }; windows .iter() .flat_map(|window| window.left.iter().chain(window.right.iter())) .find(|segment| segment.provider_id == id) .map(|segment| segment.text.clone()) } /// Dispatch one job that will still be **in flight** when the caller /// looks, without sleeping. /// /// A pending entry leaves `Running` only inside `AsyncRuntime::tick`, so /// a dispatch with no intervening tick is in flight by construction — /// no wall-clock race, and no worker left sleeping past the test. fn dispatch_one_in_flight(state: &EditorState) { exec(state, "IN_FLIGHT = pmacs.workers.sleep(50)"); } // --------------------------------------------------------------------------- // 1 — purpose reaches the three structurally distinct entry paths // --------------------------------------------------------------------------- /// One per distinct **shape**, not one per dispatcher: a pool /// dispatcher, an `register_external` job, and a spawned process. /// /// `register_external` is here because MCP and LSP bypass the worker /// pool entirely — they are the likeliest paths for a later field to be /// added to `PendingJob` and quietly missed — and because its `JobKind` /// is the undifferentiated `LspRequest`/`McpRequest` for every method, /// so `purpose` is the only thing that tells two of its rows apart. #[test] fn every_entry_shape_records_what_its_work_is() { let mut state = editor(); // (a) A pool dispatcher. dispatch_one_in_flight(&state); let purposes = active_purposes(&state); assert_eq!(purposes.len(), 1, "one job in flight: {purposes:?}"); assert_eq!( purposes[0], "sleep 50ms", "a pool job records the work, not just its handler's name" ); // (b) An externally-settled job. The purpose is a PARAMETER here // because `register_external` has nothing to derive one from: its // kind is a category, not a description. let (job_id, _token) = state.async_runtime.register_external( JobKind::LspRequest, None, "lsp textDocument/definition file:///tmp/x.rs", ); let purposes = active_purposes(&state); assert!( purposes .iter() .any(|p| p == "lsp textDocument/definition file:///tmp/x.rs"), "an externally-registered job carries its caller's description: {purposes:?}" ); state.async_runtime.complete_external_cancelled(job_id); // (c) A spawned process. `label` keeps its existing meaning and its // existing callers; `purpose` is the new, separate answer to "what // is this doing". exec( &state, r#"P = pmacs.process.spawn { label = "sh-1", purpose = "probing the repository for a build system", command = "/bin/sh", args = { "-c", "sleep 5" }, }"#, ); let rows: Vec = eval( &state, "local out = {} for _, row in ipairs(pmacs.process.list()) do out[#out + 1] = row.label .. ' | ' .. row.purpose end return out", ); assert!( rows.iter() .any(|row| row == "sh-1 | probing the repository for a build system"), "a spawned process carries a purpose ALONGSIDE its label: {rows:?}" ); exec(&state, "pmacs.process.terminate(P)"); pump(&mut state); } /// A `pmacs.process.spawn` caller that supplies no purpose keeps /// working, and gets its own label back rather than an empty field. /// /// The Rust struct's field is required — the compiler enforces that at /// every construction site. This surface is deliberately lenient, /// because requiring it here would break every existing caller for no /// coverage the compiler is not already providing. #[test] fn a_process_spawned_without_a_purpose_falls_back_to_its_label() { let mut state = editor(); exec( &state, r#"P = pmacs.process.spawn { label = "legacy-caller", command = "/bin/sh", args = { "-c", "sleep 5" }, }"#, ); let purpose: String = eval( &state, "for _, row in ipairs(pmacs.process.list()) do if row.label == 'legacy-caller' then return row.purpose end end return ''", ); assert_eq!(purpose, "legacy-caller"); exec(&state, "pmacs.process.terminate(P)"); pump(&mut state); } /// Q#W-4's preservation half, pinned here as well as by the three /// leak-detector suites: `purpose` is a new KEY on each existing row and /// changes nothing about **which** processes `list()` enumerates. /// /// `m6_8_multi_repl_acceptance`, `compile_mode_acceptance` and /// `lean4_stage1_acceptance` all assert on `#pmacs.process.list()` as a /// leak baseline. If any of them needs editing, the design is wrong. #[test] fn process_list_still_hides_terminal_ptys() { let mut state = editor(); let before: usize = eval(&state, "return #pmacs.process.list()"); exec( &state, "T = pmacs.terminal.open { command = '/bin/sh', args = { '-c', 'sleep 5' } }", ); let after: usize = eval(&state, "return #pmacs.process.list()"); assert_eq!( before, after, "a terminal PTY must stay invisible to pmacs.process.list (Q#W-4)" ); assert!( eval::(&state, "return pmacs.terminal.is_terminal(T)"), "precondition: the PTY really was opened" ); // No explicit close: terminals have no Lua teardown surface, and // `EditorState::drop` shuts the supervisor down with SIGTERM then // SIGKILL, so the child cannot outlive the test. pump(&mut state); } // --------------------------------------------------------------------------- // 2 — the dispatch-name ambient (Q#W-2) // --------------------------------------------------------------------------- /// **Rule 7 + the defect itself.** A job dispatched through /// `pmacs.workers.dispatch("name", …)` reports `"name"`. /// /// The witness is a handler **registered from Lua that calls a real /// dispatcher**, not a synthetic push of the ambient. A test that /// pushed the name by hand would prove the stack works and leave the /// actual defect — `name` dying inside an arbitrary handler, three /// layers above anything that takes a name — completely unwitnessed. #[test] fn a_dispatched_job_reports_the_registered_handler_name() { let mut state = editor(); exec( &state, "pmacs.workers.register('indexer', function() return pmacs.workers.sleep(50) end) H = pmacs.workers.dispatch('indexer')", ); let purposes = active_purposes(&state); assert_eq!(purposes.len(), 1, "one job in flight: {purposes:?}"); assert!( purposes[0].starts_with("indexer"), "the third party's own name must survive the call chain: {purposes:?}" ); // Rule 7: outside any extent, nothing changes. exec(&state, "DIRECT = pmacs.workers.sleep(50)"); let purposes = active_purposes(&state); assert!( purposes.iter().any(|p| p == "sleep 50ms"), "a builtin invoked directly records its own purpose: {purposes:?}" ); pump(&mut state); } /// **Rule 6 — COMPOSE, do not replace.** Both halves asserted, because /// a test on the prefix alone passes when the description is dropped, /// and a test on the description alone passes when the third party is /// lost again. #[test] fn a_dispatched_job_composes_the_handler_name_with_the_work() { let mut state = editor(); exec( &state, "pmacs.workers.register('indexer', function() return pmacs.workers.sleep(50) end) H = pmacs.workers.dispatch('indexer')", ); let purposes = active_purposes(&state); assert_eq!( purposes, vec!["indexer: sleep 50ms".to_owned()], "letting the name win discards the work; letting the work win \ loses the third party" ); pump(&mut state); } /// **Rules 3 and 4 — nesting is a stack (innermost wins) and fan-out /// shares the name.** #[test] fn nesting_takes_the_innermost_name_and_fan_out_shares_it() { let mut state = editor(); exec( &state, "pmacs.workers.register('inner', function() -- Fan-out: two jobs under one handler. A = pmacs.workers.sleep(50) B = pmacs.workers.sleep(51) return A end) pmacs.workers.register('outer', function() pmacs.workers.dispatch('inner') -- Back in `outer`'s extent: the stack restored on return. C = pmacs.workers.sleep(52) return C end) pmacs.workers.dispatch('outer')", ); let mut purposes = active_purposes(&state); purposes.sort(); assert_eq!( purposes, vec![ "inner: sleep 50ms".to_owned(), "inner: sleep 51ms".to_owned(), "outer: sleep 52ms".to_owned(), ], "innermost wins inside, and the outer name is restored after" ); pump(&mut state); } /// **Rule 5 — unwind-safe, and this is the one that makes a naive /// version worse than none.** /// /// A handler that raises must still pop. Otherwise one failure poisons /// every subsequent dispatch in the session with a stale name, and the /// feature stops failing loudly and starts lying silently — a /// regression that would surface as intermittent misattribution long /// after the lane landed. #[test] fn a_raising_handler_still_pops_its_name() { let mut state = editor(); exec( &state, "pmacs.workers.register('boom', function() error('handler failed') end) OK, ERR = pcall(pmacs.workers.dispatch, 'boom')", ); assert!( !eval::(&state, "return OK"), "the handler's error must still reach the caller" ); assert!( eval::(&state, "return tostring(ERR)").contains("handler failed"), "and must reach it unchanged" ); exec(&state, "LATER = pmacs.workers.sleep(50)"); let purposes = active_purposes(&state); assert_eq!( purposes, vec!["sleep 50ms".to_owned()], "an unrelated later dispatch must not inherit the failed \ handler's name: {purposes:?}" ); pump(&mut state); } /// **Preservation.** `pmacs.workers.dispatch` was `return /// handler(args, opts)` — a tail call that propagates **every** return /// value. Bracketing it must not quietly truncate that. /// /// A `local ok, result = pcall(...)` bracketing would pass every other /// test in this file and lose a two-value handler's second value with no /// error anywhere, which is the shape of regression that surfaces months /// later in somebody else's package. #[test] fn dispatch_still_propagates_every_value_the_handler_returns() { let mut state = editor(); let values: Vec = eval( &state, "pmacs.workers.register('multi', function() return pmacs.workers.sleep(50), 'second', 'third' end) local a, b, c = pmacs.workers.dispatch('multi') return { type(a), tostring(b), tostring(c) }", ); assert_eq!( values, vec!["table".to_owned(), "second".to_owned(), "third".to_owned()], "a multi-value handler must survive the bracketing" ); pump(&mut state); } /// **Rule 2 — work dispatched LATER is not covered, deliberately.** /// /// A job dispatched from an `on_complete` callback runs ticks later, /// outside the extent, and carries only its own purpose. Asserted so /// that the boundary reads as designed rather than as broken; covering /// it would need the asynchronous lifetime mechanism Stage 3 owns /// (Q#W-5). #[test] fn work_dispatched_from_a_completion_callback_carries_no_handler_name() { let mut state = editor(); exec( &state, "LATE = nil pmacs.workers.register('deferred', function() local h = pmacs.workers.sleep(1) h:on_complete(function() LATE = pmacs.workers.sleep(50) end) return h end) pmacs.workers.dispatch('deferred')", ); // One tick settles the first job and fires the callback; the job the // callback dispatches is what this test is about, so do not pump to // quiescence before reading it. let deadline = Instant::now() + Duration::from_secs(10); while !eval::(&state, "return LATE ~= nil") { assert!(Instant::now() < deadline, "callback never fired"); state.tick_async(); } let purposes = active_purposes(&state); assert_eq!( purposes, vec!["sleep 50ms".to_owned()], "the extent is the handler CALL, not the job's lifetime: {purposes:?}" ); pump(&mut state); } // --------------------------------------------------------------------------- // 3 — rule 1: the extent is non-yieldable, and that is ENFORCED // --------------------------------------------------------------------------- /// **Rule 1, first supported yield API.** Two assertions, and the /// second is the load-bearing one. /// /// A guard that raises but leaves the name pushed has converted a silent /// misattribution into a silent misattribution *plus* an error. So the /// witness dispatches again after the rejection and asserts the new job /// carries no stale name. #[test] fn awaiting_inside_a_handler_is_refused_and_the_scope_restores() { let mut state = editor(); // The awaited handle is created OUTSIDE the extent on purpose: the // second assertion below is about what a job allocated *after* the // refusal carries, and a job the handler allocated for itself would // legitimately wear the handler's name and blur that. exec( &state, "OUTSIDE = pmacs.workers.sleep(1) REFUSAL = nil pmacs.workers.register('awaits', function() local ok, err = pcall(function() return OUTSIDE:await() end) REFUSAL = (not ok) and tostring(err) or '' return OUTSIDE end) pmacs.async(function() pmacs.workers.dispatch('awaits') end)", ); let refusal: String = eval(&state, "return REFUSAL"); assert!( refusal.contains("cannot await inside") && refusal.contains("pmacs.workers.dispatch"), "the refusal must name the rule it enforces; got {refusal:?}" ); assert!( !eval::(&state, "return pmacs._async._in_dispatch_name_scope()"), "a refused await must still leave the scope popped" ); exec(&state, "AFTER = pmacs.workers.sleep(50)"); let purposes = active_purposes(&state); assert!( purposes.iter().any(|p| p == "sleep 50ms"), "and a later dispatch must carry no stale name: {purposes:?}" ); assert!( !purposes.iter().any(|p| p.starts_with("awaits:")), "no job allocated after the refusal may inherit the handler's \ name: {purposes:?}" ); pump(&mut state); } /// **Rule 1, unconditionally.** The refusal fires even when the awaited /// handle has already settled. /// /// This is the case that separates an unconditional guard from one whose /// behaviour depends on a race: a guard placed after the `_is_complete` /// check would fire only when a yield would really occur, passing under /// test and failing intermittently in production depending on whether /// the job happened to finish first. #[test] fn the_await_refusal_fires_even_for_an_already_complete_handle() { let mut state = editor(); exec(&state, "SETTLED = pmacs.workers.sleep(0)"); let deadline = Instant::now() + Duration::from_secs(10); while !eval::(&state, "return SETTLED:is_complete()") { assert!(Instant::now() < deadline, "the canary never settled"); state.tick_async(); } exec( &state, "REFUSAL = nil pmacs.workers.register('awaits-settled', function() local ok, err = pcall(function() return SETTLED:await() end) REFUSAL = (not ok) and tostring(err) or '' return pmacs.workers.sleep(50) end) pmacs.workers.dispatch('awaits-settled')", ); let refusal: String = eval(&state, "return REFUSAL"); assert!( refusal.contains("cannot await inside") && refusal.contains("pmacs.workers.dispatch"), "a settled handle must be refused too, or the guard's behaviour \ depends on a race; got {refusal:?}" ); assert!( !eval::(&state, "return pmacs._async._in_dispatch_name_scope()"), "and the scope must still be popped" ); pump(&mut state); } /// **Rule 1, second supported yield API.** Guarding `:await()` and not /// `yield_to_next_tick` would leave the extent open through a second /// door — and Q#W-7 below is the proof that exactly that happens when /// only one door is guarded. #[test] fn yield_to_next_tick_inside_a_handler_is_refused_and_the_scope_restores() { let mut state = editor(); exec( &state, "REFUSAL = nil pmacs.workers.register('yields', function() local ok, err = pcall(pmacs.async.yield_to_next_tick) REFUSAL = (not ok) and tostring(err) or '' return pmacs.workers.sleep(50) end) pmacs.async(function() pmacs.workers.dispatch('yields') end)", ); let refusal: String = eval(&state, "return REFUSAL"); assert!( refusal.contains("cannot yield inside") && refusal.contains("pmacs.workers.dispatch"), "the second yield API must refuse too; got {refusal:?}" ); assert!( !eval::(&state, "return pmacs._async._in_dispatch_name_scope()"), "and must leave the scope popped" ); exec(&state, "AFTER = pmacs.workers.sleep(51)"); let purposes = active_purposes(&state); assert!( purposes.iter().any(|p| p == "sleep 51ms"), "a later dispatch must carry no stale name: {purposes:?}" ); pump(&mut state); } // --------------------------------------------------------------------------- // 4 — Q#W-7: the same hole in `commit_to`, closed here // --------------------------------------------------------------------------- /// **Q#W-7 — a pre-existing defect, found by reading and repaired in /// this lane.** /// /// `Handle:await()` refuses inside `pmacs.window.commit_to` precisely so /// a coroutine cannot park with the frontend scope pushed (Journey Stage /// 1a, Q#JR14b). But `pmacs.async.yield_to_next_tick()` also yields, is /// public, and carried **no** such refusal — so that invariant had a /// second entrance. /// /// **Reachability by a real caller is UNPROVEN.** No production caller /// is known to yield through this door inside a commit; this pins the /// guard rather than reproducing a user-visible bug. /// /// Both halves asserted, for the same reason as rule 1's: a refusal that /// leaves the scope pushed swaps a silent misrouting for a loud one and /// fixes neither. #[test] fn yield_to_next_tick_inside_commit_to_is_refused_and_the_commit_scope_restores() { let mut state = editor(); let dir = tempfile::tempdir().expect("tempdir"); std::fs::write(dir.path().join("alpha.txt"), b"alpha\n").expect("write"); // A GENUINE destination, produced by the production capture: the // listener claims (returns false), so nothing commits and what lands // in `dest` is exactly the userdata dired would have received. // Nothing in a test can construct one. exec( &state, "dest = nil pmacs.hook.add('path.open-directory', function(_, d) dest = d return false end)", ); state.open_directory_target(dir.path()); pump(&mut state); assert!( eval::(&state, "return dest ~= nil"), "the chain must hand listeners a destination" ); exec( &state, "REFUSAL = nil pmacs.async(function() local ok, err = pcall(pmacs.window.commit_to, dest, function() pmacs.async.yield_to_next_tick() end) REFUSAL = (not ok) and tostring(err) or '' end)", ); let refusal: String = eval(&state, "return REFUSAL"); assert!( refusal.contains("cannot yield inside") && refusal.contains("commit_to"), "the second door into the commit scope must be shut; got {refusal:?}" ); assert!( !eval::(&state, "return pmacs._async._in_commit_scope()"), "and the commit scope must still be restored afterwards" ); pump(&mut state); } // --------------------------------------------------------------------------- // 5 — the statusline activity indicator (Q#W-3, Q#W-6) // --------------------------------------------------------------------------- /// **Absent at zero, asserted as an absent SEGMENT rather than as an /// empty string.** A zero-width segment still consumes a separator in /// the composed modeline, so "returns nothing" and "returns nothing /// visible" are different claims and only one of them is the design. #[test] fn the_indicator_produces_no_segment_at_all_when_nothing_is_running() { let state = editor(); assert_eq!( activity_segment(&state), None, "an idle editor must produce NO activity segment" ); } /// **A count plus the oldest in-flight job's purpose, witnessed through /// the real per-frame evaluation path.** /// /// Driven through `paint_frame`, not by calling the provider function /// directly: a provider that works in isolation and never gets evaluated /// is exactly the failure this must exclude. #[test] fn the_indicator_shows_a_count_and_the_oldest_purpose_in_a_painted_frame() { let mut state = editor(); exec( &state, "FIRST = pmacs.workers.sleep(50) SECOND = pmacs.workers.grep({ root = '/tmp', pattern = 'zzz-no-match' })", ); let cells = paint(&state, 24, 160); let modeline = row_text(&cells, 160, 22); assert!( modeline.contains("⋯2 sleep 50ms"), "the painted modeline must carry the count and the OLDEST job's \ purpose (not the newest); got {modeline:?}" ); // And the same value reaches the evaluator's segment vector, which is // what the semantic frontend ships. assert_eq!( activity_segment(&state).as_deref(), Some("⋯2 sleep 50ms"), "the segment and the painted row must agree" ); exec(&state, "SECOND:cancel()"); pump(&mut state); } /// **Q#W-6 — the setting, witnessed with work genuinely in flight.** /// /// The discriminating case: an assertion taken on an idle editor cannot /// tell "disabled" from "nothing is happening", which is the only thing /// this setting changes. #[test] fn the_indicator_honours_its_setting_while_work_is_in_flight() { let mut state = editor(); dispatch_one_in_flight(&state); assert!( activity_segment(&state).is_some(), "precondition: work is in flight and the indicator is on" ); exec(&state, "pmacs.config.set('ui.activity-indicator', false)"); assert_eq!( activity_segment(&state), None, "disabled means NO segment, with work still running" ); exec(&state, "pmacs.config.set('ui.activity-indicator', true)"); assert!( activity_segment(&state).is_some(), "and re-enabling brings it back without a restart" ); pump(&mut state); } /// The setting is a real registry entry, not an ad-hoc global: it is /// discoverable through `pmacs.config.describe` like every other /// setting, which is what `COHERENCE.md` §11 grades. #[test] fn the_setting_is_registered_with_a_true_default() { let state = editor(); let (kind, default): (String, bool) = eval( &state, "local d = pmacs.config.describe('ui.activity-indicator') return d.type, d.default", ); assert_eq!(kind, "boolean"); assert!(default, "visible by default — no configuration, no command"); } // --------------------------------------------------------------------------- // 6 — `*workers*` renders the purpose // --------------------------------------------------------------------------- /// The view §9 already has, now answering §9's question. /// /// `Kind` names the builtin dispatcher a job funnelled through, which /// for a third-party job is a builtin's label rather than the caller's; /// the `Purpose` column is what carries the caller's own account. #[test] fn the_workers_buffer_renders_the_purpose_column() { let mut state = editor(); exec( &state, "pmacs.workers.register('indexer', function() return pmacs.workers.sleep(50) end) pmacs.workers.dispatch('indexer') BUF = pmacs.workers.show()", ); let text: String = eval(&state, "return BUF:slice(0, BUF:len())"); assert!( text.contains("Purpose"), "the active table must have a Purpose column:\n{text}" ); assert!( text.contains("indexer: sleep 50ms"), "and the row must render it:\n{text}" ); exec(&state, "pmacs.workers.hide()"); pump(&mut state); }