diff --git a/flow-devloop-daemon/src/main/java/com/vaadin/flow/devloop/daemon/Compile.java b/flow-devloop-daemon/src/main/java/com/vaadin/flow/devloop/daemon/Compile.java index 38a2e93b5c6..f1c6edf3c9c 100644 --- a/flow-devloop-daemon/src/main/java/com/vaadin/flow/devloop/daemon/Compile.java +++ b/flow-devloop-daemon/src/main/java/com/vaadin/flow/devloop/daemon/Compile.java @@ -432,8 +432,30 @@ void forgetResources(List resources) { /** Seeds the fingerprints, so an untouched project reports no changes. */ void seedResources() { + seedResources(Long.MAX_VALUE); + } + + /** + * Seeds the fingerprints of the resources the running application can have + * read. + *

+ * The same bound {@link #seedFromDisk(long, long)} puts on sources, and for + * a sharper reason: a startup-only resource has no other way back into a + * change-set. The re-resolve that rebuilds the baseline after a pom edit + * has also copied the file onto the classpath, so its copy is current, and + * a baseline that took the edit as acted on would report "no changes" for a + * config the running JVM never loaded - on this apply and every later one. + * + * @param startedAtMillis + * when the running application was launched + */ + void seedResources(long startedAtMillis) { notified.clear(); - forEachResource((module, source, stamp) -> notified.put(source, stamp)); + forEachResource((module, source, stamp) -> { + if (stamp.modified() <= startedAtMillis) { + notified.put(source, stamp); + } + }); } /** @@ -902,7 +924,7 @@ void seedFromDisk(long startedAtMillis, long frontendCutoffMillis) { applied.put(source, stamp); } }); - seedResources(); + seedResources(startedAtMillis); // Load-bearing for the frontend leg, not just tidiness: a bundled // frontend edit escalates to a restart, the restart re-registers, and // this runs again. Without it the same file would be offered after the diff --git a/flow-devloop-daemon/src/test/java/com/vaadin/flow/devloop/daemon/CompileTest.java b/flow-devloop-daemon/src/test/java/com/vaadin/flow/devloop/daemon/CompileTest.java index 1793637984b..b29f9a01507 100644 --- a/flow-devloop-daemon/src/test/java/com/vaadin/flow/devloop/daemon/CompileTest.java +++ b/flow-devloop-daemon/src/test/java/com/vaadin/flow/devloop/daemon/CompileTest.java @@ -596,6 +596,40 @@ public class Main { } assertEquals(List.of(config), changes.startup().modified()); } + @Test + void staleResources_seedingKeepsAnEditMadeSinceTheAppStarted() + throws IOException { + // A pom edit re-seeds the baseline while the app runs, and the Maven + // re-resolve before it has already copied the edited config onto the + // classpath. Taking that edit as acted on would report "no changes" + // for a config the running JVM never loaded, and no later apply could + // see it again. + Reactor.Module app = module("app", "Main", """ + package app; + public class Main { } + """); + Path config = write("app/src/main/resources/application.properties", + "server.port=8080"); + Path served = write( + "app/src/main/resources/META-INF/resources/site.css", "body{}"); + Compile compile = new Compile(project(app)); + compile.copyResources(List.of(served)); + long appStarted = System.currentTimeMillis(); + touch("app/src/main/resources/application.properties"); + // What the re-resolve's process-resources leaves behind: a copy at + // least as new as its source. + compile.copyResources(List.of(config)); + touch("app/target/classes/application.properties"); + + compile.seedFromDisk(appStarted, appStarted); + + Compile.ResourceChanges changes = compile.staleResources(); + assertEquals(List.of(config), changes.startup().modified()); + // What the app did start with is still seeded, or every apply would + // report every resource. + assertTrue(changes.live().isEmpty()); + } + @Test void staleResources_reportADeletedResourceTheWalkCannotSee() throws IOException {