mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-12 21:32:52 +00:00
* AG4: xtask ci --affected cargo xtask ci --affected [--base REV] [--dry-run] runs fmt, clippy, tests (nextest + doctests when available), layers, assets (only on asset changes) and wasm (only for affected L0-L4/web crates) for the packages a change can affect: changed files map to the owning package and to packages whose sources read them via ../ paths, expanded to reverse dependents. Workspace-wide inputs (root Cargo.toml, Cargo.lock, .cargo/, xtask/, toolchain, clippy.toml) fall back to full CI. Documented in docs/testing.md, AGENTS.md and CLAUDE.md. (cherry picked from commit f3ca84fc2ee3948582cbc94e4c99ee510e829540) (cherry picked from commit 8a2cdcadf447eafa57ab14016beae4420f002af0) (cherry picked from commitd8a4301bc1) (cherry picked from commit 36402c61ac2f7e29ecd76a6cfc89365cc2572e0a) * AO1: fast CI profile, looser timing tests - [profile.ci] (release without LTO, 16 codegen units); `xtask ci` runs clippy and tests with --profile ci instead of --release. - Wall-clock asserts in engine memory_tick, gpu OpenGL decline and script socket tests keep their intent with bounds that survive a loaded machine. - docs/testing.md and CONTRIBUTING.md mention the profile. (cherry picked from commit b45c8a379351ebdfda0d7076ad4b740baf6f2087) (cherry picked from commit fa1ea6a107c4c1936cde89b518a05adc73f2b4b0) (cherry picked from commite5d5ed80e9) (cherry picked from commit d2c67660c70764f9c56880a642ad7f0d004a7f92) * AG6: journal replay repros Bug reports become replay scripts and crash regression tests become data files: - engine::replay: a JSON-lines repro format (start line, command steps with expect_error, expect queries with JSON pointer + equals, expect_no_error), a never-panicking runner that reports per-step ok/error and the first failure, and journal export (from the last project open; saves left out; size-capped). - journal.export {path?} and help.exportRepro {path?} (not journaled). - Crash capture: when the last-resort guard catches a panic in the outermost command, the journal plus that command is saved to Repros/crash-*.jsonl in the settings store (newest 20 kept) and the error names the file. - Commands run by a journaled command (script steps) are no longer journaled twice. - effectcraft-cli replay SCRIPT.jsonl [--json] [--keep-going]: exits 1 on a failed step, unmet expectation or panic. - crates/host/tests/repros.rs replays every tests/repros/*.jsonl; three samples from past crash fixes (zero frame rate, Layer Settings with no selection, New Comp from Selection with hostile overlap). - Docs: docs/agents.md "Repro scripts"; AGENTS.md: crash fix regression test = a .jsonl in tests/repros. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit c38509d7dafe507856c48c78db65e00b94b0cebc) (cherry picked from commit e55b56dbf76716adfcbfe4cc773df19a74edc419) (cherry picked from commit96a63855a9) (cherry picked from commit c59351ab3bd697f0868e41230dd883960d94a540) * xtask: ci and ci --affected run cargo test and nextest with --no-fail-fast One run now lists every failing crate instead of stopping at the first. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019qXL3GPNnY9BSFF2XoU2Yu (cherry picked from commit e46c7debe738eca62b35bf7392ab77637f6a33ae) * Agents: confinement write-checks journal.export, help.exportRepro and file.exportTimeline Review on #859 (split into this PR): their output paths were only read-checked (relative ones not at all). Test: confinement_covers_every_command_that_writes_a_file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014d65YdNxQmjRkbXRtP2NwH * #1224: handle bin-only CI targets and BOM repros without weakening timing tests * #1224: run the full gate when nextest policy changes --------- Co-authored-by: Elias <hakifx91@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: FredApps <FredApps@users.noreply.github.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
80 lines
3.0 KiB
Rust
80 lines
3.0 KiB
Rust
//! The last-resort guard: a panic that escapes a command (a bug, never an expected error) is
|
|
//! turned into an [`EngineError`] instead of taking the app, and the user's open project, down
|
|
//! with it. Edits work on a copy of the project (see [`crate::Session::edit`]), so an edit that
|
|
//! panics leaves the project as it was. This is a safety net, not a substitute for returning
|
|
//! errors (AGENTS.md, "Never crash").
|
|
|
|
use std::cell::Cell;
|
|
|
|
use crate::{EngineError, Result};
|
|
|
|
thread_local! {
|
|
/// Panics [`guarded`] caught on this thread (see [`caught`]).
|
|
static CAUGHT: Cell<u64> = const { Cell::new(0) };
|
|
}
|
|
|
|
/// How many panics [`guarded`] has caught on this thread so far: compare two readings to learn
|
|
/// whether a call panicked (crash repros, `effectcraft-cli replay`).
|
|
pub fn caught() -> u64 {
|
|
CAUGHT.with(Cell::get)
|
|
}
|
|
|
|
/// Run `f`; a panic inside it becomes an error naming `what`. The panic itself is logged by
|
|
/// the panic hook ([`crate::logging::install_panic_hook`]).
|
|
pub fn guarded<T>(what: &str, f: impl FnOnce() -> Result<T>) -> Result<T> {
|
|
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
|
|
Ok(r) => r,
|
|
Err(payload) => {
|
|
CAUGHT.with(|c| c.set(c.get().saturating_add(1)));
|
|
let msg = payload
|
|
.downcast_ref::<&str>()
|
|
.map(|s| s.to_string())
|
|
.or_else(|| payload.downcast_ref::<String>().cloned())
|
|
.unwrap_or_else(|| "unknown error".into());
|
|
log::error!("{what} panicked: {msg}");
|
|
Err(EngineError::Other(format!("{what} failed with an internal error ({msg}); it was stopped and your project is still open")))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn a_panic_becomes_an_error() {
|
|
let r: Result<()> = guarded("command `test.panic`", || panic!("boom"));
|
|
let e = r.unwrap_err().to_string();
|
|
assert!(e.contains("test.panic") && e.contains("boom"), "{e}");
|
|
let r: Result<u8> = guarded("x", || {
|
|
let v: Vec<u8> = vec![];
|
|
Ok(v[3])
|
|
});
|
|
assert!(r.is_err());
|
|
assert_eq!(guarded("ok", || Ok(7)).unwrap(), 7);
|
|
assert!(matches!(guarded::<()>("err", || Err(EngineError::NoComp)), Err(EngineError::NoComp)));
|
|
}
|
|
|
|
#[test]
|
|
fn the_session_survives_a_panicking_edit() {
|
|
let mut s = crate::Session::new();
|
|
let before = s.project.clone();
|
|
let r: Result<()> = guarded("command `test.edit`", || {
|
|
s.edit("Broken", None, |p, _| {
|
|
p.next_id += 1;
|
|
panic!("bug in the middle of an edit")
|
|
})
|
|
});
|
|
assert!(r.is_err());
|
|
assert!(std::sync::Arc::ptr_eq(&before, &s.project), "the project is untouched");
|
|
// Later edits still work.
|
|
let next = s.project.next_id;
|
|
s.edit("Fine", None, |p, _| {
|
|
p.next_id += 1;
|
|
Ok(())
|
|
})
|
|
.unwrap();
|
|
assert_eq!(s.project.next_id, next + 1);
|
|
}
|
|
}
|