Skip to content

[flutter_tools] fix: don't rebuild unit_test_assets when nothing changed#187751

Open
hkarmoush wants to merge 2 commits into
flutter:masterfrom
hkarmoush:fix/187725-unit-test-assets-rebuild
Open

[flutter_tools] fix: don't rebuild unit_test_assets when nothing changed#187751
hkarmoush wants to merge 2 commits into
flutter:masterfrom
hkarmoush:fix/187725-unit-test-assets-rebuild

Conversation

@hkarmoush

Copy link
Copy Markdown

Description

Fixes #187725.

flutter test was rebuilding build/unit_test_assets on every invocation, even when nothing changed. When two flutter test invocations run concurrently in the same project directory, one invocation's delete lands while the other's flutter_tester processes are reading an asset, causing failures like:

Exception: Asset 'shaders/ink_sparkle.frag' not found

Root cause

DevFSFileContent.isModifiedAfter() returned true whenever _fileStat == null. Since _fileStat is only populated by markClean(), and _needsRebuild() in test.dart inspects freshly-constructed AssetBundleEntry objects that never have markClean() called, every asset entry reported "modified" on every run.

This is a regression from #187488 which made the stat accessors pure (_stat()_statFile()). Before that, the mutating _stat() populated _fileStat as a side effect during the asset build, so the baseline happened to be set when _needsRebuild() ran.

Fix

isModifiedAfter(time) now drops all reference to _fileStat and compares the file's current mtime directly against time. This matches the pattern in DevFSByteContent and DevFSStringCompressingBytesContent, which both compare a snapshot time against time with no baseline dependency.

Tests

  • devfs_test.dart: unit test calling isModifiedAfter on a fresh DevFSFileContent with no prior markClean() — the exact path the bug hid in.
  • test_test.dart: regression test running the command twice with no source changes, injecting a sentinel into the built output between runs, and asserting the sentinel survives (proving no rebuild occurred).

Pre-submission checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///?).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • I followed the [breaking change policy] and added a Breaking Change label if necessary.
  • All existing and new tests are passing.

AI-assisted contribution: This PR was developed with AI assistance (Claude Code). The code has been reviewed for correctness by the contributor.

DevFSFileContent.isModifiedAfter() returned true whenever _fileStat was
null (i.e. markClean() had never been called). Since _needsRebuild() in
test.dart inspects freshly-constructed AssetBundleEntry objects that
never have markClean() called, every entry reported "modified" and the
asset directory was deleted and rewritten on every flutter test run.

When two flutter test invocations share a project directory concurrently
one invocation's delete lands while the other's flutter_tester processes
are reading an asset, causing "Asset not found" failures.

Fix: isModifiedAfter now compares the file's current mtime directly
against the provided time, with no dependency on the _fileStat baseline.
This matches DevFSByteContent and DevFSStringCompressingBytesContent.

Fixes flutter#187725

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Jun 9, 2026
@google-cla

google-cla Bot commented Jun 9, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request simplifies the DevFSFileContent.isModifiedAfter check to prevent unnecessary asset bundle rebuilds when no changes have occurred, and adds corresponding regression tests. Feedback suggests returning true instead of false when currentStat is null to ensure file deletions are correctly detected as modifications, adding a test assertion for this deletion case, and refactoring the test to use the local fs instance instead of globals.fs.

Comment thread packages/flutter_tools/lib/src/devfs.dart
Comment thread packages/flutter_tools/test/general.shard/devfs_test.dart
Comment thread packages/flutter_tools/test/commands.shard/hermetic/test_test.dart
When currentStat is null (file deleted from disk), isModifiedAfter was
returning false, meaning asset deletions would not trigger a rebuild and
stale assets could persist in the unit_test_assets directory.

Return true for the null case so deletions are treated as modifications,
consistent with the conservative "rebuild when in doubt" policy of a
build system.

Also add a regression test asserting the deletion case, and switch from
globals.fs to the local fs instance in test_test.dart per style guidelines.

Addresses review feedback on PR flutter#187751.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Regression: flutter test rebuilds build/unit_test_assets on every run, breaking concurrent invocations

1 participant