From df2de353a40615729be0e7a9946026424a08b113 Mon Sep 17 00:00:00 2001 From: jackieismpc Date: Mon, 30 Mar 2026 09:12:05 +0800 Subject: [PATCH 1/6] test: cover subproject path normalization in target discovery Signed-off-by: jackieismpc --- Cargo.lock | 42 ++++++++++++++++++ orion/Cargo.toml | 3 ++ orion/src/buck_controller.rs | 84 ++++++++++++++++++++++++++++++++++-- 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63c3f0fdf..9a0f885c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4878,6 +4878,7 @@ dependencies = [ "scorpiofs", "serde", "serde_json", + "serial_test", "td_util", "td_util_buck", "thiserror 2.0.18", @@ -6760,6 +6761,15 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.29" @@ -6848,6 +6858,12 @@ dependencies = [ "sha2", ] +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "sea-bae" version = "0.2.1" @@ -7270,6 +7286,32 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot 0.12.5", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sha1" version = "0.10.6" diff --git a/orion/Cargo.toml b/orion/Cargo.toml index 4439dc04d..3e4a66af5 100644 --- a/orion/Cargo.toml +++ b/orion/Cargo.toml @@ -32,3 +32,6 @@ utoipa.workspace = true common = { path = "../common" } scorpiofs = "0.2.1" tokio-util = { workspace = true } + +[dev-dependencies] +serial_test = { workspace = true } diff --git a/orion/src/buck_controller.rs b/orion/src/buck_controller.rs index 5556ab977..7a9b1432b 100644 --- a/orion/src/buck_controller.rs +++ b/orion/src/buck_controller.rs @@ -540,9 +540,6 @@ pub async fn build( // e.g., repo="/project/git-internal/git-internal" → repo_prefix="project/git-internal/git-internal" let repo_prefix = repo.strip_prefix('/').unwrap_or(&repo); - // Changes are already relative to the sub-project (buck2 project root). - // Do NOT prefix them with repo_prefix — buck2 runs from the sub-project dir. - const MAX_TARGETS_ATTEMPTS: usize = 2; let mut mount_point = None; let mut old_repo_mount_point_saved = None; @@ -757,3 +754,84 @@ pub async fn build( build_result } + +#[cfg(test)] +mod tests { + use std::{ + fs, + path::{Path, PathBuf}, + }; + + use api_model::buck2::{status::Status, types::ProjectRelativePath}; + use serial_test::serial; + use td_util_buck::types::TargetLabel; + + use super::get_build_targets; + + struct JsonlCleanupGuard { + paths: Vec, + } + + impl JsonlCleanupGuard { + fn new(paths: impl IntoIterator) -> Self { + Self { + paths: paths.into_iter().collect(), + } + } + } + + impl Drop for JsonlCleanupGuard { + fn drop(&mut self) { + for path in &self.paths { + let _ = fs::remove_file(path); + } + } + } + + fn workspace_root() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("workspace root") + .to_path_buf() + } + + fn subproject_root(relative: &str) -> PathBuf { + workspace_root().join(relative) + } + + fn path_exists(path: &Path) -> bool { + path.exists() + } + + #[tokio::test] + #[serial] + async fn test_get_build_targets_detects_real_subproject_source_change() { + let subproject_relative = "jupiter/callisto"; + let subproject_root = subproject_root(subproject_relative); + assert!( + path_exists(&subproject_root.join("BUCK")), + "expected test fixture project to exist at {:?}", + subproject_root + ); + + let _cleanup = JsonlCleanupGuard::new([ + subproject_root.join("base.jsonl"), + subproject_root.join("diff.jsonl"), + ]); + + let targets = get_build_targets( + subproject_root.to_str().expect("subproject root path"), + subproject_root.to_str().expect("subproject root path"), + vec![Status::Modified(ProjectRelativePath::new( + "src/access_token.rs", + ))], + ) + .await + .expect("target discovery should complete"); + + assert!( + targets.contains(&TargetLabel::new("root//jupiter/callisto:callisto")), + "expected source change to rebuild root//jupiter/callisto:callisto, got {targets:?}" + ); + } +} From 3aaacce62e9ae942a51afc216b561373d1df8ab2 Mon Sep 17 00:00:00 2001 From: jackieismpc Date: Mon, 30 Mar 2026 09:13:12 +0800 Subject: [PATCH 2/6] orion: normalize changed paths against buck root Signed-off-by: jackieismpc --- orion/src/buck_controller.rs | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/orion/src/buck_controller.rs b/orion/src/buck_controller.rs index 7a9b1432b..ecbf974b7 100644 --- a/orion/src/buck_controller.rs +++ b/orion/src/buck_controller.rs @@ -293,6 +293,7 @@ fn get_repo_targets(file_name: &str, repo_path: &Path) -> anyhow::Result>, ) -> anyhow::Result> { tracing::info!("Get cells at {:?}", mount_point); @@ -316,7 +317,8 @@ async fn get_build_targets( )?; let base = get_repo_targets("base.jsonl", &old_repo)?; - let changes = Changes::new(&cells, mega_changes)?; + let normalized_changes = normalize_changes_for_repo_prefix(repo_prefix, mega_changes); + let changes = Changes::new(&cells, normalized_changes)?; tracing::debug!("Changes {changes:?}"); let diff = get_repo_targets("diff.jsonl", &mount_path)?; @@ -333,6 +335,30 @@ async fn get_build_targets( .collect()) } +fn normalize_changes_for_repo_prefix( + repo_prefix: &str, + mega_changes: Vec>, +) -> Vec> { + let normalized_prefix = repo_prefix.trim_matches('/'); + if normalized_prefix.is_empty() { + return mega_changes; + } + + mega_changes + .into_iter() + .map(|status| status.into_map(|path| normalize_change_path(normalized_prefix, path))) + .collect() +} + +fn normalize_change_path(repo_prefix: &str, path: ProjectRelativePath) -> ProjectRelativePath { + let raw_path = path.as_str().trim_start_matches('/'); + if raw_path == repo_prefix || raw_path.starts_with(&format!("{repo_prefix}/")) { + return ProjectRelativePath::new(raw_path); + } + + ProjectRelativePath::new(&format!("{repo_prefix}/{raw_path}")) +} + #[derive(Debug)] pub struct BuildStatusTracker { pub cancellation: CancellationToken, @@ -540,6 +566,10 @@ pub async fn build( // e.g., repo="/project/git-internal/git-internal" → repo_prefix="project/git-internal/git-internal" let repo_prefix = repo.strip_prefix('/').unwrap_or(&repo); + // Buck2 still resolves cells and target inputs relative to the monorepo root + // even when commands run from a sub-project directory, so normalize incoming + // change paths against `repo_prefix` before target discovery. + const MAX_TARGETS_ATTEMPTS: usize = 2; let mut mount_point = None; let mut old_repo_mount_point_saved = None; @@ -578,6 +608,7 @@ pub async fn build( match get_build_targets( old_project_root.to_str().unwrap_or(&old_repo_mount_point), new_project_root.to_str().unwrap_or(&repo_mount_point), + repo_prefix, changes.clone(), ) .await @@ -822,6 +853,7 @@ mod tests { let targets = get_build_targets( subproject_root.to_str().expect("subproject root path"), subproject_root.to_str().expect("subproject root path"), + subproject_relative, vec![Status::Modified(ProjectRelativePath::new( "src/access_token.rs", ))], From 02c763958a47a1e40017fbaa065cbe5cd484daa9 Mon Sep 17 00:00:00 2001 From: jackieismpc Date: Mon, 30 Mar 2026 09:14:10 +0800 Subject: [PATCH 3/6] test: expand discovery coverage for repo-relative changes Signed-off-by: jackieismpc --- orion/src/buck_controller.rs | 53 +++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/orion/src/buck_controller.rs b/orion/src/buck_controller.rs index ecbf974b7..1d1635231 100644 --- a/orion/src/buck_controller.rs +++ b/orion/src/buck_controller.rs @@ -797,7 +797,7 @@ mod tests { use serial_test::serial; use td_util_buck::types::TargetLabel; - use super::get_build_targets; + use super::{get_build_targets, normalize_changes_for_repo_prefix}; struct JsonlCleanupGuard { paths: Vec, @@ -834,6 +834,57 @@ mod tests { path.exists() } + #[test] + fn test_normalize_changes_for_repo_prefix_prefixes_subproject_relative_paths() { + let normalized = normalize_changes_for_repo_prefix( + "jupiter/callisto", + vec![Status::Modified(ProjectRelativePath::new( + "src/access_token.rs", + ))], + ); + + assert_eq!( + normalized, + vec![Status::Modified(ProjectRelativePath::new( + "jupiter/callisto/src/access_token.rs" + ))] + ); + } + + #[test] + fn test_normalize_changes_for_repo_prefix_keeps_repo_relative_paths_idempotent() { + let normalized = normalize_changes_for_repo_prefix( + "jupiter/callisto", + vec![Status::Modified(ProjectRelativePath::new( + "jupiter/callisto/src/access_token.rs", + ))], + ); + + assert_eq!( + normalized, + vec![Status::Modified(ProjectRelativePath::new( + "jupiter/callisto/src/access_token.rs" + ))] + ); + } + + #[test] + fn test_normalize_changes_for_repo_prefix_keeps_monorepo_root_paths_unchanged() { + let normalized = normalize_changes_for_repo_prefix( + "", + vec![Status::Modified(ProjectRelativePath::new( + "src/access_token.rs", + ))], + ); + + assert_eq!( + normalized, + vec![Status::Modified(ProjectRelativePath::new( + "src/access_token.rs" + ))] + ); + } + #[tokio::test] #[serial] async fn test_get_build_targets_detects_real_subproject_source_change() { From c7ed15251f99cc04966b1169b651a7ca5f36575a Mon Sep 17 00:00:00 2001 From: jackieismpc Date: Mon, 30 Mar 2026 09:14:22 +0800 Subject: [PATCH 4/6] test: fix api-model doctest import path Signed-off-by: jackieismpc --- api-model/src/buck2/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api-model/src/buck2/types.rs b/api-model/src/buck2/types.rs index 0609eed29..5755493ad 100644 --- a/api-model/src/buck2/types.rs +++ b/api-model/src/buck2/types.rs @@ -28,7 +28,7 @@ impl ProjectRelativePath { } /// ``` - /// use api-models::types::ProjectRelativePath; + /// use api_model::buck2::types::ProjectRelativePath; /// assert_eq!( /// ProjectRelativePath::new("foo/bar.bzl").extension(), /// Some("bzl") From e4164570e18d79a8de4063060a1679b5c95aeab7 Mon Sep 17 00:00:00 2001 From: jackieismpc Date: Mon, 30 Mar 2026 10:04:20 +0800 Subject: [PATCH 5/6] orion: preserve repo-relative changes in subprojects Signed-off-by: jackieismpc --- orion/src/buck_controller.rs | 295 +++++++++++++++--- .../fixtures/change_detector_mixed/app/BUCK | 11 + .../change_detector_mixed/app/src/lib.rs | 3 + .../change_detector_mixed/shared/BUCK | 8 + .../change_detector_mixed/shared/src/lib.rs | 3 + 5 files changed, 280 insertions(+), 40 deletions(-) create mode 100644 orion/tests/fixtures/change_detector_mixed/app/BUCK create mode 100644 orion/tests/fixtures/change_detector_mixed/app/src/lib.rs create mode 100644 orion/tests/fixtures/change_detector_mixed/shared/BUCK create mode 100644 orion/tests/fixtures/change_detector_mixed/shared/src/lib.rs diff --git a/orion/src/buck_controller.rs b/orion/src/buck_controller.rs index 1d1635231..cd2968105 100644 --- a/orion/src/buck_controller.rs +++ b/orion/src/buck_controller.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, error::Error, io::BufReader, path::{Path, PathBuf}, @@ -20,7 +20,7 @@ use td_util_buck::{ run::{Buck2, targets_arguments}, target_status::{BuildState, EVENT_LOG_FILE, Event, LogicalActionId, TargetBuildStatusUpdate}, targets::Targets, - types::TargetLabel, + types::{CellPath, TargetLabel}, }; use tokio::{ io::AsyncBufReadExt, @@ -317,10 +317,20 @@ async fn get_build_targets( )?; let base = get_repo_targets("base.jsonl", &old_repo)?; - let normalized_changes = normalize_changes_for_repo_prefix(repo_prefix, mega_changes); + let diff = get_repo_targets("diff.jsonl", &mount_path)?; + let known_paths = collect_known_change_paths(&base, &diff); + let old_repo_root = repo_root_for_project_root(&old_repo, repo_prefix); + let new_repo_root = repo_root_for_project_root(&mount_path, repo_prefix); + let normalized_changes = normalize_changes_for_repo_prefix( + &cells, + repo_prefix, + &old_repo_root, + &new_repo_root, + &known_paths, + mega_changes, + ); let changes = Changes::new(&cells, normalized_changes)?; tracing::debug!("Changes {changes:?}"); - let diff = get_repo_targets("diff.jsonl", &mount_path)?; tracing::debug!("Base targets number: {}", base.len_targets_upperbound()); tracing::debug!("Diff targets number: {}", diff.len_targets_upperbound()); @@ -335,28 +345,160 @@ async fn get_build_targets( .collect()) } +fn collect_known_change_paths(base: &Targets, diff: &Targets) -> HashSet { + let mut known_paths = HashSet::new(); + + for targets in [base, diff] { + for target in targets.targets() { + known_paths.extend(target.inputs.iter().cloned()); + } + for import in targets.imports() { + known_paths.insert(import.file.clone()); + known_paths.extend(import.imports.iter().cloned()); + } + } + + known_paths +} + +fn repo_root_for_project_root(project_root: &Path, repo_prefix: &str) -> PathBuf { + let mut repo_root = project_root.to_path_buf(); + for _ in repo_prefix + .trim_matches('/') + .split('/') + .filter(|segment| !segment.is_empty()) + { + repo_root = repo_root + .parent() + .map(Path::to_path_buf) + .unwrap_or(repo_root); + } + repo_root +} + fn normalize_changes_for_repo_prefix( + cells: &CellInfo, repo_prefix: &str, + old_repo_root: &Path, + new_repo_root: &Path, + known_paths: &HashSet, mega_changes: Vec>, ) -> Vec> { let normalized_prefix = repo_prefix.trim_matches('/'); - if normalized_prefix.is_empty() { - return mega_changes; + let mut normalized_changes = Vec::new(); + let mut seen = HashSet::new(); + + for status in mega_changes { + let candidates = normalize_change_path_candidates( + cells, + normalized_prefix, + old_repo_root, + new_repo_root, + known_paths, + status.get(), + ); + for candidate in candidates { + let normalized_status = status_with_path(&status, candidate); + if seen.insert(normalized_status.clone()) { + normalized_changes.push(normalized_status); + } + } } - mega_changes - .into_iter() - .map(|status| status.into_map(|path| normalize_change_path(normalized_prefix, path))) - .collect() + normalized_changes } -fn normalize_change_path(repo_prefix: &str, path: ProjectRelativePath) -> ProjectRelativePath { +fn normalize_change_path_candidates( + cells: &CellInfo, + repo_prefix: &str, + old_repo_root: &Path, + new_repo_root: &Path, + known_paths: &HashSet, + path: &ProjectRelativePath, +) -> Vec { let raw_path = path.as_str().trim_start_matches('/'); - if raw_path == repo_prefix || raw_path.starts_with(&format!("{repo_prefix}/")) { - return ProjectRelativePath::new(raw_path); + if repo_prefix.is_empty() + || raw_path == repo_prefix + || raw_path.starts_with(&format!("{repo_prefix}/")) + { + return vec![ProjectRelativePath::new(raw_path)]; } - ProjectRelativePath::new(&format!("{repo_prefix}/{raw_path}")) + let prefixed_path = format!("{repo_prefix}/{raw_path}"); + let raw_matches = path_matches_repo(cells, known_paths, old_repo_root, new_repo_root, raw_path); + let prefixed_matches = path_matches_repo( + cells, + known_paths, + old_repo_root, + new_repo_root, + &prefixed_path, + ); + + if raw_matches && prefixed_matches { + tracing::warn!( + raw_path, + prefixed_path, + repo_prefix, + "Change path matches both repo-relative and subproject-relative candidates; keeping both" + ); + } + + select_change_path_candidates(raw_path, &prefixed_path, raw_matches, prefixed_matches) +} + +fn path_matches_repo( + cells: &CellInfo, + known_paths: &HashSet, + old_repo_root: &Path, + new_repo_root: &Path, + relative_path: &str, +) -> bool { + path_exists_in_repo(old_repo_root, relative_path) + || path_exists_in_repo(new_repo_root, relative_path) + || path_matches_known_targets(cells, known_paths, relative_path) +} + +fn path_exists_in_repo(repo_root: &Path, relative_path: &str) -> bool { + repo_root.join(relative_path).exists() +} + +fn path_matches_known_targets( + cells: &CellInfo, + known_paths: &HashSet, + relative_path: &str, +) -> bool { + cells + .unresolve(&ProjectRelativePath::new(relative_path)) + .ok() + .is_some_and(|cell_path| known_paths.contains(&cell_path)) +} + +fn select_change_path_candidates( + raw_path: &str, + prefixed_path: &str, + raw_matches: bool, + prefixed_matches: bool, +) -> Vec { + match (raw_matches, prefixed_matches) { + (false, true) => vec![ProjectRelativePath::new(prefixed_path)], + (true, false) => vec![ProjectRelativePath::new(raw_path)], + (true, true) => vec![ + ProjectRelativePath::new(raw_path), + ProjectRelativePath::new(prefixed_path), + ], + (false, false) => vec![ProjectRelativePath::new(raw_path)], + } +} + +fn status_with_path( + status: &Status, + path: ProjectRelativePath, +) -> Status { + match status { + Status::Modified(_) => Status::Modified(path), + Status::Added(_) => Status::Added(path), + Status::Removed(_) => Status::Removed(path), + } } #[derive(Debug)] @@ -789,15 +931,18 @@ pub async fn build( #[cfg(test)] mod tests { use std::{ + collections::HashSet, fs, path::{Path, PathBuf}, }; use api_model::buck2::{status::Status, types::ProjectRelativePath}; use serial_test::serial; - use td_util_buck::types::TargetLabel; + use td_util_buck::{cells::CellInfo, types::TargetLabel}; - use super::{get_build_targets, normalize_changes_for_repo_prefix}; + use super::{ + get_build_targets, normalize_change_path_candidates, select_change_path_candidates, + }; struct JsonlCleanupGuard { paths: Vec, @@ -835,53 +980,88 @@ mod tests { } #[test] - fn test_normalize_changes_for_repo_prefix_prefixes_subproject_relative_paths() { - let normalized = normalize_changes_for_repo_prefix( - "jupiter/callisto", - vec![Status::Modified(ProjectRelativePath::new( - "src/access_token.rs", - ))], + fn test_select_change_path_candidates_prefixes_subproject_relative_paths() { + let normalized = select_change_path_candidates( + "src/access_token.rs", + "jupiter/callisto/src/access_token.rs", + false, + true, ); assert_eq!( normalized, - vec![Status::Modified(ProjectRelativePath::new( + vec![ProjectRelativePath::new( "jupiter/callisto/src/access_token.rs" - ))] + )] ); } #[test] - fn test_normalize_changes_for_repo_prefix_keeps_repo_relative_paths_idempotent() { - let normalized = normalize_changes_for_repo_prefix( + fn test_normalize_change_path_candidates_keeps_repo_relative_paths_idempotent() { + let normalized = normalize_change_path_candidates( + &CellInfo::testing(), "jupiter/callisto", - vec![Status::Modified(ProjectRelativePath::new( - "jupiter/callisto/src/access_token.rs", - ))], + &workspace_root(), + &workspace_root(), + &HashSet::new(), + &ProjectRelativePath::new("jupiter/callisto/src/access_token.rs"), ); assert_eq!( normalized, - vec![Status::Modified(ProjectRelativePath::new( + vec![ProjectRelativePath::new( "jupiter/callisto/src/access_token.rs" - ))] + )] ); } #[test] - fn test_normalize_changes_for_repo_prefix_keeps_monorepo_root_paths_unchanged() { - let normalized = normalize_changes_for_repo_prefix( - "", - vec![Status::Modified(ProjectRelativePath::new( - "src/access_token.rs", - ))], + fn test_select_change_path_candidates_keeps_unrelated_repo_relative_paths_unchanged() { + let normalized = select_change_path_candidates( + "common/src/lib.rs", + "jupiter/callisto/common/src/lib.rs", + true, + false, ); assert_eq!( normalized, - vec![Status::Modified(ProjectRelativePath::new( - "src/access_token.rs" - ))] + vec![ProjectRelativePath::new("common/src/lib.rs")] + ); + } + + #[test] + fn test_normalize_change_path_candidates_keeps_existing_repo_relative_paths() { + let normalized = normalize_change_path_candidates( + &CellInfo::testing(), + "jupiter/callisto", + &workspace_root(), + &workspace_root(), + &HashSet::new(), + &ProjectRelativePath::new("common/src/lib.rs"), + ); + + assert_eq!( + normalized, + vec![ProjectRelativePath::new("common/src/lib.rs")] + ); + } + + #[test] + fn test_select_change_path_candidates_keeps_ambiguous_paths_as_both_candidates() { + let normalized = select_change_path_candidates( + "src/access_token.rs", + "jupiter/callisto/src/access_token.rs", + true, + true, + ); + + assert_eq!( + normalized, + vec![ + ProjectRelativePath::new("src/access_token.rs"), + ProjectRelativePath::new("jupiter/callisto/src/access_token.rs"), + ] ); } @@ -917,4 +1097,39 @@ mod tests { "expected source change to rebuild root//jupiter/callisto:callisto, got {targets:?}" ); } + + #[tokio::test] + #[serial] + async fn test_get_build_targets_keeps_repo_relative_shared_dependency_changes() { + let subproject_relative = "orion/tests/fixtures/change_detector_mixed/app"; + let subproject_root = subproject_root(subproject_relative); + assert!( + path_exists(&subproject_root.join("BUCK")), + "expected fixture project to exist at {:?}", + subproject_root + ); + + let _cleanup = JsonlCleanupGuard::new([ + subproject_root.join("base.jsonl"), + subproject_root.join("diff.jsonl"), + ]); + + let targets = get_build_targets( + subproject_root.to_str().expect("subproject root path"), + subproject_root.to_str().expect("subproject root path"), + subproject_relative, + vec![Status::Modified(ProjectRelativePath::new( + "orion/tests/fixtures/change_detector_mixed/shared/src/lib.rs", + ))], + ) + .await + .expect("target discovery should complete"); + + assert!( + targets.contains(&TargetLabel::new( + "root//orion/tests/fixtures/change_detector_mixed/app:app" + )), + "expected shared dependency change to rebuild root//orion/tests/fixtures/change_detector_mixed/app:app, got {targets:?}" + ); + } } diff --git a/orion/tests/fixtures/change_detector_mixed/app/BUCK b/orion/tests/fixtures/change_detector_mixed/app/BUCK new file mode 100644 index 000000000..0ee514008 --- /dev/null +++ b/orion/tests/fixtures/change_detector_mixed/app/BUCK @@ -0,0 +1,11 @@ +rust_library ( + name = "app", + srcs = ["src/lib.rs"], + crate_root = "src/lib.rs", + deps = [ + "//orion/tests/fixtures/change_detector_mixed/shared:shared", + ], + visibility = [ + "PUBLIC", + ], +) diff --git a/orion/tests/fixtures/change_detector_mixed/app/src/lib.rs b/orion/tests/fixtures/change_detector_mixed/app/src/lib.rs new file mode 100644 index 000000000..b1bcbdec9 --- /dev/null +++ b/orion/tests/fixtures/change_detector_mixed/app/src/lib.rs @@ -0,0 +1,3 @@ +pub fn app_value() -> i32 { + 7 +} diff --git a/orion/tests/fixtures/change_detector_mixed/shared/BUCK b/orion/tests/fixtures/change_detector_mixed/shared/BUCK new file mode 100644 index 000000000..922c3906b --- /dev/null +++ b/orion/tests/fixtures/change_detector_mixed/shared/BUCK @@ -0,0 +1,8 @@ +rust_library ( + name = "shared", + srcs = ["src/lib.rs"], + crate_root = "src/lib.rs", + visibility = [ + "PUBLIC", + ], +) diff --git a/orion/tests/fixtures/change_detector_mixed/shared/src/lib.rs b/orion/tests/fixtures/change_detector_mixed/shared/src/lib.rs new file mode 100644 index 000000000..8b64c4b66 --- /dev/null +++ b/orion/tests/fixtures/change_detector_mixed/shared/src/lib.rs @@ -0,0 +1,3 @@ +pub fn shared_value() -> i32 { + 42 +} From 30a8d3a1199ef8410739d1d61170977c6017b785 Mon Sep 17 00:00:00 2001 From: jackieismpc Date: Mon, 30 Mar 2026 10:14:21 +0800 Subject: [PATCH 6/6] test: cover mixed change lists in orion target discovery Signed-off-by: jackieismpc --- orion/src/buck_controller.rs | 38 +++++++++++++++++++ .../change_detector_mixed/app/README.md | 1 + 2 files changed, 39 insertions(+) create mode 100644 orion/tests/fixtures/change_detector_mixed/app/README.md diff --git a/orion/src/buck_controller.rs b/orion/src/buck_controller.rs index cd2968105..aa156dd45 100644 --- a/orion/src/buck_controller.rs +++ b/orion/src/buck_controller.rs @@ -1132,4 +1132,42 @@ mod tests { "expected shared dependency change to rebuild root//orion/tests/fixtures/change_detector_mixed/app:app, got {targets:?}" ); } + + #[tokio::test] + #[serial] + async fn test_get_build_targets_handles_mixed_subproject_and_repo_relative_changes() { + let subproject_relative = "orion/tests/fixtures/change_detector_mixed/app"; + let subproject_root = subproject_root(subproject_relative); + assert!( + path_exists(&subproject_root.join("README.md")), + "expected fixture file to exist at {:?}", + subproject_root.join("README.md") + ); + + let _cleanup = JsonlCleanupGuard::new([ + subproject_root.join("base.jsonl"), + subproject_root.join("diff.jsonl"), + ]); + + let targets = get_build_targets( + subproject_root.to_str().expect("subproject root path"), + subproject_root.to_str().expect("subproject root path"), + subproject_relative, + vec![ + Status::Modified(ProjectRelativePath::new("README.md")), + Status::Modified(ProjectRelativePath::new( + "orion/tests/fixtures/change_detector_mixed/shared/src/lib.rs", + )), + ], + ) + .await + .expect("target discovery should complete"); + + assert!( + targets.contains(&TargetLabel::new( + "root//orion/tests/fixtures/change_detector_mixed/app:app" + )), + "expected mixed change list to rebuild root//orion/tests/fixtures/change_detector_mixed/app:app, got {targets:?}" + ); + } } diff --git a/orion/tests/fixtures/change_detector_mixed/app/README.md b/orion/tests/fixtures/change_detector_mixed/app/README.md new file mode 100644 index 000000000..79864739d --- /dev/null +++ b/orion/tests/fixtures/change_detector_mixed/app/README.md @@ -0,0 +1 @@ +This file exists to exercise mixed change lists in Buck2 change detection tests.