Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions toolchains/go/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Sibling modules required by version without a `go.work` no longer create project relationships, since those builds consume the published module rather than the local source. When the `go` binary is unavailable, projects with their own `go.mod` under a workspace `go.work` fall back to resolving relationships from their direct requires.
- `replace` directives keep their meaning in the new model: a require replaced by a local directory always links to the project at that location (it consumes local source even without a `go.work`), while a require replaced by another module never links.
- Imports within a project's own import path are treated as ownership rather than dependencies. `go list -deps ./...` enumerates packages belonging to projects nested inside the scanned project, which previously inferred an edge from the parent to every nested child — forming a cycle whenever a child declared `dependsOn` on its parent.
- Test binary pseudo-packages are no longer inferred as relationships. `go list -deps -test` reports a synthetic `pkg.test` package for each tested package; its `.test` suffix kept it from matching the package under test, so it resolved to whatever ancestor project it nested under (typically the module root) as a phantom development edge. It is now reduced to the real package path and recognised as ownership.

## 1.4.7

Expand Down
10 changes: 8 additions & 2 deletions toolchains/go/src/package_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ fn execute_go_list(dir: &VirtualPath, packages: &[String], test: bool) -> AnyRes
.stdout
.lines()
.filter_map(|line| {
// Test binary pseudo-packages render as `pkg [pkg.test]`;
// only the real package path participates in matching.
// `go list -deps -test` reports a package's test artifacts: the
// package compiled into its test binary as `pkg [pkg.test]`, and
// the synthetic test binary itself as a bare `pkg.test`. Both
// describe the package under test, not a new import — reduce them
// to the real package path so ownership filtering claims them
// rather than leaking an edge to whatever project the `.test`
// path happens to nest under (e.g. the module root).
let import_path = line.trim().split(' ').next().unwrap_or_default();
let import_path = import_path.strip_suffix(".test").unwrap_or(import_path);

(!import_path.is_empty()).then(|| import_path.to_owned())
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package b

import "testing"

func TestB(t *testing.T) {
B()
}
51 changes: 51 additions & 0 deletions toolchains/go/tests/tier2_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,57 @@ mod go_toolchain_tier2 {
})
);
}

#[tokio::test(flavor = "multi_thread")]
async fn doesnt_infer_edges_to_root_from_test_binary_pseudo_packages() {
let sandbox = create_moon_sandbox("projects-single-module");
let plugin = sandbox.create_toolchain("go").await;

let mut input = ExtendProjectGraphInput::default();
// The workspace root owns the module's `go.mod`, so it is
// itself a project — as in a single-module monorepo whose repo
// root is a project that everything nests under.
input.project_sources.insert(Id::raw("root"), ".".into());
input.project_sources.insert(Id::raw("a"), "apps/a".into());
input.project_sources.insert(Id::raw("b"), "libs/b".into());
input.toolchain_config = json!({
"inferRelationships": true,
"inferRelationshipsFromTests": true
});

let output = plugin.extend_project_graph(input).await;

// `go list -deps -test ./...` emits a synthetic `<pkg>.test`
// main package for every tested package (here libs/b). Its
// import path is the package path with a bare `.test` suffix,
// so it isn't nested under the owning project and slips past
// ownership filtering — yet it is still prefixed by the root
// module path. It must be discarded, not resolved to the root
// project as a phantom development edge (`b -> root`).
assert_eq!(
output.extended_projects,
BTreeMap::from_iter([
(
Id::raw("root"),
ExtendProjectOutput {
alias: Some("example.com/org".into()),
..Default::default()
}
),
(
Id::raw("a"),
ExtendProjectOutput {
dependencies: vec![ProjectDependency {
id: Id::raw("b"),
scope: DependencyScope::Production,
via: Some("package example.com/org/libs/b".into()),
}],
..Default::default()
}
),
])
);
}
}
}

Expand Down