-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Added better filesystem layout testing harness #15874
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Haven't look at the change, but thanks a lot for improving the test infrastructure! |
4f31868
to
c939a79
Compare
9a42834
to
edef1aa
Compare
r? @weihanglo rustbot has assigned @weihanglo. Use |
I updated the implementation to fix the flaws of the initial design. The build-dir tests now pass on all of the platforms tested in CI. |
let actual_layout = LayoutTree::from_path(&self).unwrap(); | ||
|
||
let expected_layout = LayoutTree::parse(expected.as_ref()); | ||
|
||
if !actual_layout.matches_snapshot(&expected_layout) { | ||
let actual_snapshot = actual_layout.to_string(); | ||
let expected_snapshot = expected_layout.to_string(); | ||
|
||
compare::assert_e2e().eq(actual_snapshot, expected_snapshot); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
LayoutTree
struct handles comparison logic (as opposed to using snapbox directly)
This avoids the issues initial implementation (ordering and platform specific files, see full details below)
If the comparison fails, 2 human readable tree's are generated and snapbox is used to display the diffs.
Note: These diffs are in-memory thus, do not support
SNAPSHOTS=overwrite
. This is arguably good as often the overwrite would be incorrect as the snapshots are platform specific.Snapshots would support conditional files based on the target os / platform env
- Files/directories may be suffixed with
[target_platform=<target-platform>]
- Multiple platforms may be specified separated by comma
Our general aim should be to support snapshotting wherever possible.
Also, tracking of platform-specific details is rough for contributors
- Likely won't discover until the PR is posted
- Likely don't have access to a machine, so must do a slow iteration by pushing and seeing CI's result
Unsure f there is something better but felt this was at least worth specifically discussing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I totally agree with you.
One idea I have to only check for files in the snapshot and ignore other files. Meaning if a new file was is created a test would not fail. So tests would be able to ignore many of the platform specific files that are not relevant to the test.
But this comes with the trade off of potentially not including a file that is important.
I think I lean toward the current implementation that explicitly matches the file tree structure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two alternative ideas:
- Have a function that enumerates files in a directory as relative-path-per-line
- a snapshot assert can then be run against that with the expected results having a
...
inside it and.unordered()
called on it - a parameter could take a glob to ignore
- a snapshot assert can then be run against that with the expected results having a
- Have a low level function that accepts a glob to ignore and enumerates files as a tree. A higher level assert function could assume a specific policy for globs and compare against a snapshot
The problem comes in with
[target_platform=windows-msvc]
│ ├── foo[EXE]
[target_platform=macos,linux,windows-gnu]
│ ├── foo-[HASH][EXE]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current tests handle this by using globs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with the existing tests doing format!("build-dir/debug/deps/foo*{EXE_SUFFIX}")
and if we did foo[..][EXE]
is that it over matches. On Unix-like systems, this could match any file and not just the binary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we limp along for now with foo[..][EXE]
as #15947 would unblock removing -Cextra-filename
from more places, see #15947 (comment)
As for why msrv doesn't include the hash, see
cargo/src/cargo/core/compiler/build_runner/compilation_files.rs
Lines 835 to 865 in e0e1a6e
// No metadata in these cases: | |
// | |
// - dylibs: | |
// - if any dylib names are encoded in executables, so they can't be renamed. | |
// - TODO: Maybe use `-install-name` on macOS or `-soname` on other UNIX systems | |
// to specify the dylib name to be used by the linker instead of the filename. | |
// - Windows MSVC executables: The path to the PDB is embedded in the | |
// executable, and we don't want the PDB path to include the hash in it. | |
// - wasm32-unknown-emscripten executables: When using emscripten, the path to the | |
// .wasm file is embedded in the .js file, so we don't want the hash in there. | |
// | |
// This is only done for local packages, as we don't expect to export | |
// dependencies. | |
// | |
// The __CARGO_DEFAULT_LIB_METADATA env var is used to override this to | |
// force metadata in the hash. This is only used for building libstd. For | |
// example, if libstd is placed in a common location, we don't want a file | |
// named /usr/lib/libstd.so which could conflict with other rustc | |
// installs. In addition it prevents accidentally loading a libstd of a | |
// different compiler at runtime. | |
// See https://github.com/rust-lang/cargo/issues/3005 | |
let short_name = bcx.target_data.short_name(&unit.kind); | |
if (unit.target.is_dylib() | |
|| unit.target.is_cdylib() | |
|| (unit.target.is_executable() && short_name == "wasm32-unknown-emscripten") | |
|| (unit.target.is_executable() && short_name.contains("msvc"))) | |
&& unit.pkg.package_id().source_id().is_path() | |
&& bcx.gctx.get_env("__CARGO_DEFAULT_LIB_METADATA").is_err() | |
{ | |
return false; | |
} |
This comment has been minimized.
This comment has been minimized.
edef1aa
to
b9f9cbb
Compare
I rebased to resolve the conflict with #15910 I'm waiting to see what we decide to do on this PR before writing tests for the build-dir layout changes. If we feel this is not a meaningful improvement, I am happy to close this and continue with the existing approach in |
This comment has been minimized.
This comment has been minimized.
b9f9cbb
to
122ee29
Compare
In the latest push I made some changes based on the feedback so far:
With these changes the tests are quite a bit cleaner. :) I left the |
Part of my hope with the rework is we wouldn't need to parse; we generate and then assert. What is left that we still need parsing?
I'd rather stick to what we need than carry around a burden of unused features that exist for speculative purposes, especially if they over encourage the lack of snapshotting. |
Also, while the tree layout works well for inspecting a test, how well does that work for reading the diff and (in rare cases) updating it by hand? |
src/doc/contrib/src/tests/writing.md
Outdated
Tests often to need to verify Cargo created/removed files. | ||
The `CargoPathExt` trait (implmented by `Path` and `PathBuf`) provides a `assert_file_layout()` to verify a file system tree against a snapshot. The snapshot format is very similar to the unix `tree` command output. | ||
|
||
Files vary across operating systems, for example `.pdb` files on Windows. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder instead this platform specific string we should just have different snapshots for different platforms.
Or maybe we do this kind of tree layout test assertions only on one platform, presumably Linux, which is the most available platforms. For example, I don't have a Windows physical machine. Some don't have macbook. But everyone can access a Linux container :)
Also, some tier 2 and 3 platform maintainers are running Cargo tests in their CI. While they already patched out some tests, we may want to be nice not to fail their CI more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder instead this platform specific string we should just have different snapshots for different platforms.
I considered that, but it would end up bloating testing and I think it would be quite painful to write tests if you do not have access to all platforms. (and even if you did, it would probably still be tedious)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that is why another alternative is testing only on Linux. Anything we really want to test platform differences here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As someone that develops primarily on linux, I am okay with this though not sure about other contributors on Windows.
Though I suppose, its already a problem since they need to think about linux and macos in tests already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anything we really want to test platform differences here?
Not really. We are ignoring the most of the platform specific things like debug symbols because they clutter up the tests.
At a high level, the build-dir.rs
tests are trying to verify the file layout implementation.
In a perfect world, we would verify all files in the tree, but in practice its difficult to create such a test that is easy to write and maintainable.
The issue is the file hashes change the order of files/dirs, resulting tests having diff file orders across platforms.
Using a tree layout order of lines matters but not the order of the dirs/files.
Sure I can drop this functionality. Its pretty easy to add back if we need it in the future.
I found the diffs when the test are wrong are pretty good.
|
At least for myself, this would have me lean towards having a flat list of files and directories. I'm not too much of a fan of the complexity involved in having a bespoke format and then having to parse it. This also loses out on several aspects of our snapshot tooling. |
122ee29
to
df753db
Compare
I think this is fair enough. Let me know what y'all think |
df753db
to
9a68a7d
Compare
After some further thought I decided to completely remove (And removing them means I don't need to update these methods in #15947) |
9a68a7d
to
a0a0ab4
Compare
a0a0ab4
to
1e93a2d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Update cargo submodule 24 commits in f2932725b045d361ff5f18ba02b1409dd1f44e71..2394ea6cea8b26d717aa67eb1663a2dbf2d26078 2025-09-24 11:31:26 +0000 to 2025-10-03 14:13:01 +0000 - Recommend `package.rust-version` in the Rust version section of `reference/semver.md`. (rust-lang/cargo#15806) - fix(toml): Prevent non-script fields in Cargo scripts (rust-lang/cargo#16026) - chore(ci): unpin libc (rust-lang/cargo#16044) - chore: Update dependencies (rust-lang/cargo#16034) - Fix FileLock path tracking after rename in package operation (rust-lang/cargo#16036) - Lockfile schemas error cleanup (rust-lang/cargo#16039) - Convert a multi-part diagnostic to a report (rust-lang/cargo#16035) - fix(run): Override arg0 for cargo scripts (rust-lang/cargo#16027) - Public in private manifest errors (rust-lang/cargo#16002) - chore(deps): update actions/checkout action to v5 (rust-lang/cargo#16031) - fix: remove FIXME comment that's no longer a problem (rust-lang/cargo#16025) - Add retry for `git fetch` failures in `CARGO_NET_GIT_FETCH_WITH_CLI` path (rust-lang/cargo#16016) - Added better filesystem layout testing harness (rust-lang/cargo#15874) - Small cleanup to normalize_dependencies (rust-lang/cargo#16022) - fix: better error message for rust version incompatibility (rust-lang/cargo#16021) - fix(shell): Use a distinct style for transient status (rust-lang/cargo#16019) - chore(deps): Depend on `serde_core` in `cargo-platform` (rust-lang/cargo#15992) - Remove package-workspace from unstable doc index (rust-lang/cargo#16014) - fix(shell): Switch to annotate snippets for notes (rust-lang/cargo#15945) - docs: update changelog (rust-lang/cargo#15986) - chore(ci): add rustfmt for docs job (rust-lang/cargo#16013) - chore: bump to 0.93.0 (rust-lang/cargo#16009) - fix(config): combine key error context into one (rust-lang/cargo#16004) - test(docker): openssh requires a newer libcrypto3 (rust-lang/cargo#16010) r? ghost
What does this PR try to resolve?
The goal is to make filesystem layout tests easier to understand and review.
The
build-dir
test's have a helper fns that have imperative checks. While this works, its not easy to see what its validating at a glance.My thought is to reuse the snapbox infrastructure we already have for the cargo ui tests.
The prototype in this PR generates a file tree (like the unix tree command) and uses snapbox to compare against a snapshot. This makes the layout tests much easier to read and failures are much more obvious what what went wrong.
Implementation Details
LayoutTree
struct handles comparison logic (as opposed to using snapbox directly)SNAPSHOTS=overwrite
. This is arguably good as often the overwrite would be incorrect as the snapshots are platform specific.[target_platform=<target-platform>]
initial implementation issues
However there are some problems with the current implementation that limit the usefulness of it.
dSYM
on macos└── foo-[HASH].dSYM [cfg(target_os = "macos")]
└
,├
,│
) to handle optional files.target/<profile>/build/pkg-[HASH]
directories. The hash changes the order of the directories when generating the tree making snapshots inconsistent.[HASH]
, but this does not help with the order.Fun fact: These changes were written and tested at Rust Forge 2025 in New Zealand. 🇳🇿 🥳