From 85ba481418a40979bc73a5f0b79bb5951e5f8b3d Mon Sep 17 00:00:00 2001 From: wan9chi Date: Fri, 25 Sep 2026 03:45:41 +0800 Subject: [PATCH] feat(cache): show remote cache hits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A cache hit now records whether it came from the local or the remote cache. Remote hits are marked inline (`◉ remote cache hit, replaying`), in the compact summary (`vp run: remote cache hit, 1s saved.` for a single task, `2/3 cache hit (66%, 1 remote)` otherwise), in the full summary's statistics (`2 cache hits (1 remote)`), and in the task details (`→ Remote cache hit - output replayed - 1s saved`). Local hits look as before. The saved summary records the source, and summaries saved without it read as local hits. Co-authored-by: Claude Opus 5.5 --- CHANGELOG.md | 2 +- crates/vt/src/session/cache/display.rs | 28 +++- crates/vt/src/session/cache/mod.rs | 25 ++- crates/vt/src/session/event.rs | 4 +- crates/vt/src/session/execute/mod.rs | 10 +- crates/vt/src/session/reporter/summary.rs | 154 ++++++++++++++++-- .../fixtures/remote_cache/snapshots.toml | 39 +++++ .../snapshots/local_and_remote_hits.md | 63 +++++++ .../remote_cache/snapshots/restore.md | 4 +- .../fixtures/remote_cache/vite-task.json | 10 ++ 10 files changed, 308 insertions(+), 31 deletions(-) create mode 100644 crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/local_and_remote_hits.md diff --git a/CHANGELOG.md b/CHANGELOG.md index c968a1a86..0d64401e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ - **Fixed** An invalid glob in `--filter` no longer shows its error message twice ([#763](https://github.com/voidzero-dev/vite-task/pull/763)). - **Changed** The detailed summary from `vp run --verbose` and `vp run --last-details` now shows each underlying cause of an error on its own line ([#761](https://github.com/voidzero-dev/vite-task/pull/761)). -- **Added** Remote caching. Configure an endpoint with the workspace's `cache: { remote: { url } }` or `VP_REMOTE_CACHE_URL`, and choose access with `--remote-cache=off|read|read-write` or `VP_REMOTE_CACHE`. The default is `read` with an endpoint and `off` without one. After a local cache miss, `vp run` looks the task up in the remote cache and, on a hit, restores its outputs and caches it locally. A failed read is just a cache miss, with the failure as its reason. In `read-write` mode, `vp run` also uploads the results of successful, cacheable tasks after caching them locally. A failed upload doesn't fail the task; the run summary shows a warning instead. Tasks can opt out with `cache: { remote: false }` ([#727](https://github.com/voidzero-dev/vite-task/pull/727), [#755](https://github.com/voidzero-dev/vite-task/pull/755), [#756](https://github.com/voidzero-dev/vite-task/pull/756)). +- **Added** Remote caching. Configure an endpoint with the workspace's `cache: { remote: { url } }` or `VP_REMOTE_CACHE_URL`, and choose access with `--remote-cache=off|read|read-write` or `VP_REMOTE_CACHE`. The default is `read` with an endpoint and `off` without one. After a local cache miss, `vp run` looks the task up in the remote cache and, on a hit, restores its outputs and caches it locally. The task output and the run summary show which hits came from the remote cache. A failed read is just a cache miss, with the failure as its reason. In `read-write` mode, `vp run` also uploads the results of successful, cacheable tasks after caching them locally. A failed upload doesn't fail the task; the run summary shows a warning instead. Tasks can opt out with `cache: { remote: false }` ([#727](https://github.com/voidzero-dev/vite-task/pull/727), [#755](https://github.com/voidzero-dev/vite-task/pull/755), [#756](https://github.com/voidzero-dev/vite-task/pull/756), [#757](https://github.com/voidzero-dev/vite-task/pull/757)). - **Fixed** On Windows, environment variable names used by `vp run` now match regardless of ASCII letter case. Assignments in task commands override earlier assignments and inherited variables spelled differently, and `FORCE_COLOR`, `VP_RUN_CONCURRENCY_LIMIT`, and variables requested through `@voidzero-dev/vite-task-client` are found under any spelling ([#747](https://github.com/voidzero-dev/vite-task/pull/747)). - **Changed** A task's cache settings now go inside `cache`, e.g. `cache: { env: ["NODE_ENV"], input: ["src/**"] }`; `cache: true` is the same as `cache: {}`. `env`, `untrackedEnv`, `input`, and `output` are no longer supported at the top level of a task ([#749](https://github.com/voidzero-dev/vite-task/pull/749)). - **Fixed** Cached tasks on macOS no longer intermittently fail with exit 2 and `oils I/O error (main): No such process` when a fast command finishes before the shell gets scheduled. The bundled shell that runs task commands is updated to Oils 0.38.0, which fixes this race ([#702](https://github.com/voidzero-dev/vite-task/issues/702), [#703](https://github.com/voidzero-dev/vite-task/pull/703)). diff --git a/crates/vt/src/session/cache/display.rs b/crates/vt/src/session/cache/display.rs index b7ba4eb18..155ad67b3 100644 --- a/crates/vt/src/session/cache/display.rs +++ b/crates/vt/src/session/cache/display.rs @@ -8,7 +8,9 @@ use serde::{Deserialize, Serialize}; use vt_plan::cache_metadata::SpawnFingerprint; use vt_str::Str; -use super::{CacheMiss, EnvMismatch, FingerprintMismatch, InputChangeKind, split_path}; +use super::{ + CacheHitSource, CacheMiss, EnvMismatch, FingerprintMismatch, InputChangeKind, split_path, +}; use crate::session::event::CacheStatus; /// Describes a single atomic change between two spawn fingerprints. @@ -141,7 +143,7 @@ fn format_env_changed_inline(names: &[&Str]) -> Str { /// Format cache status for inline display (during Start event). /// /// Returns `Some(formatted_string)` for Hit, Miss with reason, and Disabled, None for `NotFound`. -/// - Cache Hit: Shows "cache hit" indicator +/// - Cache Hit: Shows "cache hit" indicator, or "remote cache hit" for a remote hit /// - Cache Miss (NotFound): No inline message (just command) /// - Cache Miss (with mismatch): Shows "cache miss" with brief reason /// - Cache Disabled: Shows "cache disabled" with reason @@ -149,9 +151,12 @@ fn format_env_changed_inline(names: &[&Str]) -> Str { /// Note: Returns plain text without styling. The reporter applies colors. pub fn format_cache_status_inline(cache_status: &CacheStatus) -> Option { match cache_status { - CacheStatus::Hit { .. } => { + CacheStatus::Hit { source, .. } => { // Show "cache hit" indicator when replaying from cache - Some(Str::from("◉ cache hit, replaying")) + Some(match source { + CacheHitSource::Local => Str::from("◉ cache hit, replaying"), + CacheHitSource::Remote => Str::from("◉ remote cache hit, replaying"), + }) } CacheStatus::Miss(CacheMiss::NotFound) => { // No inline message for "not found" case - just show command @@ -249,6 +254,21 @@ mod tests { ); } + #[test] + fn inline_cache_hit_names_remote_source() { + let hit = + |source| CacheStatus::Hit { replayed_duration: std::time::Duration::ZERO, source }; + + assert_eq!( + format_cache_status_inline(&hit(CacheHitSource::Local)).as_deref(), + Some("◉ cache hit, replaying") + ); + assert_eq!( + format_cache_status_inline(&hit(CacheHitSource::Remote)).as_deref(), + Some("◉ remote cache hit, replaying") + ); + } + #[test] fn inline_tracked_env_mismatch_preserves_kind() { let added = CacheStatus::Miss(CacheMiss::FingerprintMismatch( diff --git a/crates/vt/src/session/cache/mod.rs b/crates/vt/src/session/cache/mod.rs index 7e1c887c7..3b155c7bf 100644 --- a/crates/vt/src/session/cache/mod.rs +++ b/crates/vt/src/session/cache/mod.rs @@ -148,6 +148,23 @@ pub struct ExecutionCache { remote_clients: RemoteClients, } +/// A cache hit: the entry to replay, and the cache it came from. +#[derive(Debug)] +pub struct CacheHit { + pub value: CacheEntryValue, + pub source: CacheHitSource, +} + +/// The cache a hit came from. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum CacheHitSource { + /// The local cache. + #[default] + Local, + /// The remote cache. The entry has since been recorded locally. + Remote, +} + #[derive(Debug, Clone)] #[expect( clippy::large_enum_variant, @@ -329,7 +346,7 @@ impl ExecutionCache { } /// Try to hit cache by looking up the cache entry key and validating inputs. - /// Returns `Ok(Ok(cache_value))` on cache hit, `Ok(Err(cache_miss))` on miss. + /// Returns `Ok(Ok(cache_hit))` on cache hit, `Ok(Err(cache_miss))` on miss. /// /// After a local miss, the remote cache is queried if the task has one. A /// remote hit is recorded locally, with its output archive downloaded into @@ -343,14 +360,14 @@ impl ExecutionCache { globbed_inputs: &BTreeMap, workspace_root: &AbsolutePath, cache_dir: &AbsolutePath, - ) -> anyhow::Result> { + ) -> anyhow::Result> { let cache_key = CacheEntryKey::from_metadata(cache_metadata); let local_miss = match self .try_hit_local(cache_metadata, &cache_key, globbed_inputs, workspace_root) .await? { - Ok(cache_value) => return Ok(Ok(cache_value)), + Ok(value) => return Ok(Ok(CacheHit { value, source: CacheHitSource::Local })), Err(miss) => miss, }; #[expect( @@ -375,7 +392,7 @@ impl ExecutionCache { ) .await? { - Ok(cache_value) => return Ok(Ok(cache_value)), + Ok(value) => return Ok(Ok(CacheHit { value, source: CacheHitSource::Remote })), Err(miss) => miss, }; Ok(Err(match local_miss { diff --git a/crates/vt/src/session/event.rs b/crates/vt/src/session/event.rs index 410f49a4b..7459e90b4 100644 --- a/crates/vt/src/session/event.rs +++ b/crates/vt/src/session/event.rs @@ -3,7 +3,7 @@ use std::{process::ExitStatus, time::Duration}; use vt_path::RelativePathBuf; use vt_server::Error as IpcServerError; -use super::cache::{CacheMiss, remote::UploadError}; +use super::cache::{CacheHitSource, CacheMiss, remote::UploadError}; /// The cache operation that failed. #[derive(Debug)] @@ -129,7 +129,7 @@ pub enum CacheUpdateStatus { pub enum CacheStatus { Disabled(CacheDisabledReason), Miss(CacheMiss), - Hit { replayed_duration: Duration }, + Hit { replayed_duration: Duration, source: CacheHitSource }, } /// Convert `ExitStatus` to an i32 exit code. diff --git a/crates/vt/src/session/execute/mod.rs b/crates/vt/src/session/execute/mod.rs index 2c26587c9..793bc69da 100644 --- a/crates/vt/src/session/execute/mod.rs +++ b/crates/vt/src/session/execute/mod.rs @@ -31,7 +31,7 @@ use self::{ spawn::{ChildHandle, ChildOutcome, SpawnStdio, spawn}, }; use super::{ - cache::{CacheEntryValue, CacheMiss, ExecutionCache, archive}, + cache::{CacheEntryValue, CacheHit, CacheMiss, ExecutionCache, archive}, event::{ CacheDisabledReason, CacheErrorKind, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus, ExecutionError, @@ -384,9 +384,9 @@ async fn run( // runs exactly once on every arm) and either replay the hit — no need // to execute the command — or carry the globbed inputs into the run. let (stdio_config, globbed_inputs) = match lookup { - CacheLookup::Hit(cached) => { + CacheLookup::Hit(CacheHit { value: cached, source }) => { let mut stdio_config = - reporter.start(CacheStatus::Hit { replayed_duration: cached.duration }); + reporter.start(CacheStatus::Hit { replayed_duration: cached.duration, source }); return Ok(replay_cache_hit( &mut stdio_config, &cached, @@ -501,8 +501,8 @@ async fn run( /// reason plus the globbed inputs (reused by the cache-update phase after the /// run), and disabled has neither. enum CacheLookup { - /// Cache hit — the cached entry to replay. - Hit(CacheEntryValue), + /// Cache hit — the cached entry to replay, and where it came from. + Hit(CacheHit), /// Cache miss — the detailed reason (`NotFound` or `FingerprintMismatch`). Miss { miss: CacheMiss, globbed_inputs: BTreeMap }, /// Caching is disabled for this task (no cache metadata). diff --git a/crates/vt/src/session/reporter/summary.rs b/crates/vt/src/session/reporter/summary.rs index 12459cc20..e64bf3a37 100644 --- a/crates/vt/src/session/reporter/summary.rs +++ b/crates/vt/src/session/reporter/summary.rs @@ -17,8 +17,9 @@ use vt_str::Str; use super::{CACHE_MISS_STYLE, COMMAND_STYLE, ColorizeExt}; use crate::session::{ cache::{ - CacheMiss, EnvMismatch, FingerprintMismatch, InputChangeKind, SpawnFingerprintChange, - detect_spawn_fingerprint_changes, format_input_change_str, format_spawn_change, + CacheHitSource, CacheMiss, EnvMismatch, FingerprintMismatch, InputChangeKind, + SpawnFingerprintChange, detect_spawn_fingerprint_changes, format_input_change_str, + format_spawn_change, }, event::{CacheDisabledReason, CacheNotUpdatedReason, CacheStatus, CacheUpdateStatus}, execute::fingerprint::TrackedEnvQuery, @@ -65,7 +66,12 @@ pub struct TaskSummary { )] pub enum TaskResult { /// Cache hit — output was replayed from cache. Always successful. - CacheHit { saved_duration_ms: u64 }, + CacheHit { + saved_duration_ms: u64, + /// Summaries saved before the source was recorded read as local hits. + #[serde(default)] + source: CacheHitSource, + }, /// In-process execution (built-in command like echo). Always successful. InProcess, @@ -170,7 +176,9 @@ pub struct SavedError { struct SummaryStats { total: usize, + /// Cache hits from either cache, including `remote_cache_hits`. cache_hits: usize, + remote_cache_hits: usize, cache_misses: usize, cache_disabled: usize, failed: usize, @@ -191,6 +199,7 @@ impl SummaryStats { let mut stats = Self { total: tasks.len(), cache_hits: 0, + remote_cache_hits: 0, cache_misses: 0, cache_disabled: 0, failed: 0, @@ -201,8 +210,11 @@ impl SummaryStats { for task in tasks { match &task.result { - TaskResult::CacheHit { saved_duration_ms } => { + TaskResult::CacheHit { saved_duration_ms, source } => { stats.cache_hits += 1; + if *source == CacheHitSource::Remote { + stats.remote_cache_hits += 1; + } stats.total_saved += Duration::from_millis(*saved_duration_ms); } TaskResult::InProcess => { @@ -326,9 +338,10 @@ impl TaskResult { }; match cache_status { - CacheStatus::Hit { replayed_duration } => { - Self::CacheHit { saved_duration_ms: duration_to_ms(*replayed_duration) } - } + CacheStatus::Hit { replayed_duration, source } => Self::CacheHit { + saved_duration_ms: duration_to_ms(*replayed_duration), + source: *source, + }, CacheStatus::Disabled(CacheDisabledReason::InProcessExecution) => Self::InProcess, CacheStatus::Disabled(CacheDisabledReason::NoCacheMetadata) => Self::Spawned { cache_status: SpawnedCacheStatus::Disabled, @@ -521,6 +534,7 @@ impl TaskResult { /// /// Examples: /// - "→ Cache hit - output replayed - 102.96ms saved" + /// - "→ Remote cache hit - output replayed - 102.96ms saved" /// - "→ Cache miss: no previous cache entry found" /// - "→ Cache disabled in task configuration" fn format_cache_detail(&self) -> (Str, &[Str]) { @@ -569,10 +583,14 @@ impl TaskResult { } let detail = match self { - Self::CacheHit { saved_duration_ms } => { + Self::CacheHit { saved_duration_ms, source } => { let d = Duration::from_millis(*saved_duration_ms); let formatted_duration = format_summary_duration(d); - vt_str::format!("→ Cache hit - output replayed - {formatted_duration} saved") + let hit = match source { + CacheHitSource::Local => "Cache hit", + CacheHitSource::Remote => "Remote cache hit", + }; + vt_str::format!("→ {hit} - output replayed - {formatted_duration} saved") } Self::InProcess => Str::from("→ Cache disabled for built-in command"), Self::Spawned { cache_status, .. } => match cache_status { @@ -711,12 +729,16 @@ pub fn format_full_summary(summary: &LastRunSummary) -> Vec { let total = stats.total; let cache_hits = stats.cache_hits; let cache_misses = stats.cache_misses; + let cache_hits_str = match stats.remote_cache_hits { + 0 => vt_str::format!("• {cache_hits} cache hits"), + remote => vt_str::format!("• {cache_hits} cache hits ({remote} remote)"), + }; let _ = write!( buf, "{} {} {} {}", "Statistics:".style(Style::new().bold()), vt_str::format!(" {total} tasks").style(Style::new().bright_white()), - vt_str::format!("• {cache_hits} cache hits").style(Style::new().green()), + cache_hits_str.style(Style::new().green()), vt_str::format!("• {cache_misses} cache misses").style(CACHE_MISS_STYLE), ); if !cache_disabled_str.is_empty() { @@ -877,8 +899,10 @@ fn write_causes(buf: &mut Vec, causes: &[Str], style: Style) { /// Rules: /// - Single task + not cache hit → empty (no summary at all) /// - Single task + cache hit → thin line + "vp run: cache hit, {duration} saved." +/// ("remote cache hit" for a remote hit) /// - Multi-task → thin line + "vp run: {hits}/{total} cache hit ({rate}%), {duration} saved." -/// with optional failure count and `--verbose` hint. +/// with an optional remote hit count ("({rate}%, {remote} remote)"), failure count, +/// and `--verbose` hint. pub fn format_compact_summary(summary: &LastRunSummary, program_name: &str) -> Vec { let stats = SummaryStats::compute(&summary.tasks); @@ -903,9 +927,10 @@ pub fn format_compact_summary(summary: &LastRunSummary, program_name: &str) -> V if is_single_task && stats.cache_hits > 0 { // Single task cache hit — no need for --last-details hint let formatted_total_saved = format_summary_duration(stats.total_saved); + let hit = if stats.remote_cache_hits > 0 { "remote cache hit" } else { "cache hit" }; let _ = write!( buf, - "{} cache hit, {} saved.", + "{} {hit}, {} saved.", run_label.as_str().style(Style::new().blue().bold()), formatted_total_saved.style(Style::new().green().bold()), ); @@ -928,9 +953,13 @@ pub fn format_compact_summary(summary: &LastRunSummary, program_name: &str) -> V let _ = write!( buf, - "{} {hits}/{total} cache hit ({rate}%)", + "{} {hits}/{total} cache hit ({rate}%", run_label.as_str().style(Style::new().blue().bold()), ); + if stats.remote_cache_hits > 0 { + let _ = write!(buf, ", {} remote", stats.remote_cache_hits); + } + let _ = write!(buf, ")"); if stats.total_saved > Duration::ZERO { let formatted_total_saved = format_summary_duration(stats.total_saved); @@ -1045,10 +1074,109 @@ mod tests { vt_str::format!("{}", anstream::adapter::strip_str(std::str::from_utf8(bytes).unwrap())) } + fn cache_hit_task(task_name: &str, source: CacheHitSource) -> TaskSummary { + TaskSummary { + package_name: Str::from("pkg"), + task_name: Str::from(task_name), + command: Str::from("build"), + cwd: Str::default(), + result: TaskResult::CacheHit { saved_duration_ms: 1000, source }, + } + } + + fn cache_miss_task(task_name: &str) -> TaskSummary { + TaskSummary { + package_name: Str::from("pkg"), + task_name: Str::from(task_name), + command: Str::from("build"), + cwd: Str::default(), + result: TaskResult::Spawned { + cache_status: SpawnedCacheStatus::Miss(SavedCacheMissReason::NotFound), + outcome: SpawnOutcome::Success { + infra_error: None, + input_modified_path: None, + fspy_unsupported: false, + ipc_server_error: None, + tracking_incomplete: false, + tool_disabled_cache: false, + upload_error: None, + }, + }, + } + } + fn compact_summary(tasks: Vec) -> Str { strip(&format_compact_summary(&LastRunSummary { tasks, exit_code: 0 }, "vp")) } + fn full_summary(tasks: Vec) -> Str { + strip(&format_full_summary(&LastRunSummary { tasks, exit_code: 0 })) + } + + #[test] + fn compact_summary_names_remote_hits() { + assert_eq!( + compact_summary(vec![cache_hit_task("a", CacheHitSource::Remote)]).as_str(), + "---\nvp run: remote cache hit, 1s saved.\n" + ); + assert_eq!( + compact_summary(vec![cache_hit_task("a", CacheHitSource::Local)]).as_str(), + "---\nvp run: cache hit, 1s saved.\n" + ); + assert_eq!( + compact_summary(vec![ + cache_hit_task("a", CacheHitSource::Local), + cache_hit_task("b", CacheHitSource::Remote), + cache_miss_task("c"), + ]) + .as_str(), + "---\nvp run: 2/3 cache hit (66%, 1 remote), 2s saved. \ + (Run `vp run --last-details` for full details)\n" + ); + assert_eq!( + compact_summary(vec![cache_hit_task("a", CacheHitSource::Local), cache_miss_task("b")]) + .as_str(), + "---\nvp run: 1/2 cache hit (50%), 1s saved. \ + (Run `vp run --last-details` for full details)\n" + ); + } + + #[test] + fn full_summary_names_remote_hits() { + let summary = full_summary(vec![ + cache_hit_task("a", CacheHitSource::Local), + cache_hit_task("b", CacheHitSource::Remote), + cache_miss_task("c"), + ]); + let lines: Vec<&str> = summary.as_str().lines().collect(); + assert!( + lines.contains(&"Statistics: 3 tasks • 2 cache hits (1 remote) • 1 cache misses") + ); + assert!(lines.contains(&" → Cache hit - output replayed - 1s saved")); + assert!(lines.contains(&" → Remote cache hit - output replayed - 1s saved")); + + let summary = full_summary(vec![cache_hit_task("a", CacheHitSource::Local)]); + assert!( + summary + .as_str() + .lines() + .any(|line| line == "Statistics: 1 tasks • 1 cache hits • 0 cache misses") + ); + } + + #[test] + fn saved_cache_hit_without_source_is_local() { + let summary: LastRunSummary = serde_json::from_str( + r#"{"tasks":[{"package_name":"pkg","task_name":"a","command":"build","cwd":"", + "result":{"CacheHit":{"saved_duration_ms":5}}}],"exit_code":0}"#, + ) + .unwrap(); + assert!(matches!( + summary.tasks[0].result, + TaskResult::CacheHit { saved_duration_ms: 5, source: CacheHitSource::Local } + )); + } + #[test] fn upload_failure_notice_shows_a_shared_reason() { let summary = compact_summary(vec![ diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots.toml b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots.toml index 93e714b8c..d9bea2423 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots.toml +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots.toml @@ -128,6 +128,45 @@ steps = [ ], comment = "The exact entry fails validation, so the archive isn't downloaded." }, ] +[[e2e]] +name = "local_and_remote_hits" +cfg = "not(windows)" +ignore = true +steps = [ + { argv = [ + "remote-cache-server", + "vt", + "run", + "build", + ], envs = [ + [ + "VP_REMOTE_CACHE", + "read-write", + ], + ] }, + [ + "vt", + "cache", + "clean", + ], + { argv = [ + "vt", + "run", + "check", + ], comment = "Without an endpoint, check is cached only locally." }, + { argv = [ + "remote-cache-server", + "vt", + "run", + "all", + ], comment = "build is a remote hit, and check is a local hit. The summary counts the remote hit." }, + { argv = [ + "vt", + "run", + "--last-details", + ], comment = "The details show which hit came from the remote cache." }, +] + [[e2e]] name = "fallback" cfg = "not(windows)" diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/local_and_remote_hits.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/local_and_remote_hits.md new file mode 100644 index 000000000..5941e8e06 --- /dev/null +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/local_and_remote_hits.md @@ -0,0 +1,63 @@ +# local_and_remote_hits + +## `VP_REMOTE_CACHE=read-write remote-cache-server vt run build` + +``` +$ vtt write-file dist/output.txt built + +[remote-cache] POST /fetch 200 not_found +[remote-cache] POST /store 200 +``` + +## `vt cache clean` + +``` +``` + +## `vt run check` + +Without an endpoint, check is cached only locally. + +``` +$ vtt print checked +checked +``` + +## `remote-cache-server vt run all` + +build is a remote hit, and check is a local hit. The summary counts the remote hit. + +``` +$ vtt write-file dist/output.txt built ◉ remote cache hit, replaying + +$ vtt print checked ◉ cache hit, replaying +checked + +--- +vt run: 2/2 cache hit (100%, 1 remote). (Run `vt run --last-details` for full details) +[remote-cache] POST /fetch 200 exact +[remote-cache] GET /blob/1 200 +``` + +## `vt run --last-details` + +The details show which hit came from the remote cache. + +``` + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Vite+ Task Runner • Execution Summary +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +Statistics: 2 tasks • 2 cache hits (1 remote) • 0 cache misses +Performance: 100% cache hit rate + +Task Details: +──────────────────────────────────────────────── + [1] remote-cache#build: $ vtt write-file dist/output.txt built ✓ + → Remote cache hit - output replayed - + ······················································· + [2] remote-cache#check: $ vtt print checked ✓ + → Cache hit - output replayed - +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/restore.md b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/restore.md index b3434525f..9d8b31327 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/restore.md +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/snapshots/restore.md @@ -24,10 +24,10 @@ $ vtt write-file dist/output.txt built A remote hit downloads the output archive. Hits never upload. ``` -$ vtt write-file dist/output.txt built ◉ cache hit, replaying +$ vtt write-file dist/output.txt built ◉ remote cache hit, replaying --- -vt run: cache hit. +vt run: remote cache hit. [remote-cache] POST /fetch 200 exact [remote-cache] GET /blob/1 200 ``` diff --git a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/vite-task.json b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/vite-task.json index 33a2ca322..3d2c4e967 100644 --- a/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/vite-task.json +++ b/crates/vt_bin/tests/e2e_snapshots/fixtures/remote_cache/vite-task.json @@ -6,6 +6,16 @@ "input": ["src/**"], "output": ["dist/**"] } + }, + "check": { + "command": "vtt print checked", + "cache": { + "input": ["src/**"], + "output": [] + } + }, + "all": { + "command": "vt run build && vt run check" } } }