Skip to content

Commit 83c23a6

Browse files
committed
fix: Align dry run cache status with normal run by checking caching guards
TaskCache::exists() (used by --dry) was missing the caching_disabled and reads_disabled guard checks that restore_outputs() (used by normal runs) performs. This caused --dry=json to report incorrect cache status when cache:false was set or reads were disabled. Closes #9044
1 parent 014111c commit 83c23a6

2 files changed

Lines changed: 94 additions & 1 deletion

File tree

crates/turborepo-run-cache/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,15 @@ impl TaskCache {
306306
Ok(log_writer)
307307
}
308308

309+
/// Check if a cache entry exists for this task.
310+
///
311+
/// Used by dry runs to report cache status without restoring outputs.
312+
/// Mirrors the guard checks in `restore_outputs()` so that dry runs
313+
/// and real runs agree on cache status.
309314
pub async fn exists(&self) -> Result<Option<CacheHitMetadata>, CacheError> {
315+
if self.caching_disabled || self.run_cache.reads_disabled {
316+
return Ok(None);
317+
}
310318
self.run_cache.cache.exists(&self.hash).await
311319
}
312320

crates/turborepo/tests/dry_run_test.rs

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
mod common;
22

3-
use common::{run_turbo, run_turbo_with_env, setup};
3+
use common::{git, run_turbo, run_turbo_with_env, setup};
44

55
#[test]
66
fn test_dry_run_packages_in_scope() {
@@ -68,3 +68,88 @@ fn test_dry_run_env_var_not_in_output() {
6868
"should not contain 'Environment Variables' header: {stdout}"
6969
);
7070
}
71+
72+
#[test]
73+
fn test_dry_run_cache_hit_after_real_run() {
74+
// Regression test for https://github.com/vercel/turborepo/issues/9044
75+
// After a real run populates the cache, --dry=json should report HIT.
76+
let tempdir = tempfile::tempdir().unwrap();
77+
setup::setup_integration_test(tempdir.path(), "basic_monorepo", "npm@10.5.0", true).unwrap();
78+
79+
// First: real run to populate cache
80+
let output = run_turbo(tempdir.path(), &["run", "build"]);
81+
assert!(output.status.success(), "real run should succeed");
82+
83+
// Second: dry run should see cache hits
84+
let output = run_turbo(tempdir.path(), &["run", "build", "--dry=json"]);
85+
assert!(output.status.success(), "dry run should succeed");
86+
let stdout = String::from_utf8_lossy(&output.stdout);
87+
let json: serde_json::Value = serde_json::from_str(&stdout)
88+
.unwrap_or_else(|e| panic!("failed to parse dry run JSON: {e}\nstdout: {stdout}"));
89+
90+
let tasks = json["tasks"].as_array().expect("tasks should be an array");
91+
for task in tasks {
92+
let task_id = task["taskId"].as_str().unwrap_or("<unknown>");
93+
let command = task["command"].as_str().unwrap_or("");
94+
// Skip tasks that don't have a real command (transit nodes, etc.)
95+
if command == "<NONEXISTENT>" {
96+
continue;
97+
}
98+
let status = task["cache"]["status"].as_str().unwrap_or("UNKNOWN");
99+
assert_eq!(
100+
status, "HIT",
101+
"task {task_id} should be a cache HIT in dry run after a real run"
102+
);
103+
}
104+
}
105+
106+
#[test]
107+
fn test_dry_run_respects_cache_false() {
108+
// When a task has cache:false, --dry=json should report MISS,
109+
// matching what a normal run would do (cache bypass).
110+
let tempdir = tempfile::tempdir().unwrap();
111+
setup::setup_integration_test(tempdir.path(), "basic_monorepo", "npm@10.5.0", true).unwrap();
112+
113+
// Replace turbo.json with a config that disables caching for build
114+
std::fs::write(
115+
tempdir.path().join("turbo.json"),
116+
r#"{
117+
"$schema": "https://turborepo.dev/schema.json",
118+
"tasks": {
119+
"build": {
120+
"cache": false
121+
}
122+
}
123+
}"#,
124+
)
125+
.unwrap();
126+
git(
127+
tempdir.path(),
128+
&["commit", "-am", "disable cache", "--quiet"],
129+
);
130+
131+
// Real run (cache won't be written since cache:false)
132+
let output = run_turbo(tempdir.path(), &["run", "build"]);
133+
assert!(output.status.success());
134+
135+
// Dry run should also report MISS since caching is disabled
136+
let output = run_turbo(tempdir.path(), &["run", "build", "--dry=json"]);
137+
assert!(output.status.success());
138+
let stdout = String::from_utf8_lossy(&output.stdout);
139+
let json: serde_json::Value = serde_json::from_str(&stdout)
140+
.unwrap_or_else(|e| panic!("failed to parse dry run JSON: {e}\nstdout: {stdout}"));
141+
142+
let tasks = json["tasks"].as_array().expect("tasks should be an array");
143+
for task in tasks {
144+
let command = task["command"].as_str().unwrap_or("");
145+
if command == "<NONEXISTENT>" {
146+
continue;
147+
}
148+
let task_id = task["taskId"].as_str().unwrap_or("<unknown>");
149+
let status = task["cache"]["status"].as_str().unwrap_or("UNKNOWN");
150+
assert_eq!(
151+
status, "MISS",
152+
"task {task_id} with cache:false should be MISS in dry run"
153+
);
154+
}
155+
}

0 commit comments

Comments
 (0)