diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java
index 359c546612e6f..4c6f953dd15d6 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/HotDeploymentWatchedFileBuildItem.java
@@ -9,8 +9,15 @@
* {@link io.quarkus.bootstrap.devmode.DependenciesFilter#getReloadableModules(io.quarkus.bootstrap.model.ApplicationModel)
* reloadable module} that, if modified, may result in a hot redeployment when in the dev mode.
*
- * A file may be identified with an exact location or a matching predicate. See {@link Builder#setLocation(String)} and
+ * A file may be identified with an location or a matching predicate. See {@link Builder#setLocation(String)} and
* {@link Builder#setLocationPredicate(Predicate)}.
+ *
+ * The location may be:
+ *
+ * - a relative OS-agnostic file path where {@code /} is used as a separator; e.g. {@code foo/bar.txt}
+ * - an absolute OS-specific file path; e.g. {@code /home/foo/bar.txt}
+ * - a glob pattern as defined in {@link java.nio.file.FileSystem#getPathMatcher(String)}; e.g. {@code *.sample}
+ *
*
* If multiple build items match the same file then the final value of {@code restartNeeded} is computed as a logical OR of all
* the {@link #isRestartNeeded()} values.
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
index d256e61e2b500..0a92c1909a8fe 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/dev/RuntimeUpdatesProcessor.java
@@ -332,7 +332,7 @@ private void periodicTestCompile() {
}
Set filesChanges = new HashSet<>(checkForFileChange(s -> s.getTest().orElse(null), test));
filesChanges.addAll(checkForFileChange(DevModeContext.ModuleInfo::getMain, test));
- boolean fileRestartNeeded = filesChanges.stream().anyMatch(test::isWatchedFileRestartNeeded);
+ boolean fileRestartNeeded = filesChanges.stream().anyMatch(test::isRestartNeeded);
ClassScanResult merged = ClassScanResult.merge(changedTestClassResult, changedApp);
if (fileRestartNeeded) {
@@ -462,12 +462,12 @@ public boolean doScan(boolean userInitiated, boolean forceRestart) {
main, false);
Set filesChanged = checkForFileChange(DevModeContext.ModuleInfo::getMain, main);
- boolean fileRestartNeeded = forceRestart || filesChanged.stream().anyMatch(main::isWatchedFileRestartNeeded);
+ boolean fileRestartNeeded = forceRestart || filesChanged.stream().anyMatch(main::isRestartNeeded);
boolean instrumentationChange = false;
List changedFilesForRestart = new ArrayList<>();
if (fileRestartNeeded) {
- filesChanged.stream().filter(main::isWatchedFileRestartNeeded).map(Paths::get)
+ filesChanged.stream().filter(main::isRestartNeeded).map(Paths::get)
.forEach(changedFilesForRestart::add);
}
changedFilesForRestart.addAll(changedClassResults.getChangedClasses());
@@ -867,6 +867,23 @@ Set checkForFileChange() {
return checkForFileChange(DevModeContext.ModuleInfo::getMain, main);
}
+ /**
+ * Returns the set of modified files.
+ *
+ * The returned set may contain:
+ *
+ * - an OS-specific absolute path for a HotDeploymentWatchedFileBuildItem that matches an absolute path; e.g.
+ * {@code /some/complex/unix/path/to/file}
+ * - an OS-agnostic relative path for a HotDeploymentWatchedFileBuildItem that matches a relative path; e.g.
+ * {@code templates/foo.html}
+ * - an OS-agnostic relative path for a HotDeploymentWatchedFileBuildItem that matches a glob pattern,
+ * - an OS-agnostic relative path for a new file added to a resource root.
+ *
+ *
+ * @param cuf
+ * @param timestampSet
+ * @return the set of modified files
+ */
Set checkForFileChange(Function cuf,
TimestampSet timestampSet) {
Set ret = new HashSet<>();
@@ -901,14 +918,13 @@ Set checkForFileChange(Function seen = new HashSet<>(moduleResources);
try {
for (Path root : roots) {
- //since the stream is Closeable, use a try with resources so the underlying iterator is closed
try (final Stream walk = Files.walk(root)) {
walk.forEach(path -> {
try {
Path relative = root.relativize(path);
Path target = outputDir.resolve(relative);
seen.remove(target);
- if (!timestampSet.watchedFileTimestamps.containsKey(path)) {
+ if (!timestampSet.watchedPaths.containsKey(path)) {
moduleResources.add(target);
if (!Files.exists(target) || Files.getLastModifiedTime(target).toMillis() < Files
.getLastModifiedTime(path).toMillis()) {
@@ -916,7 +932,9 @@ Set checkForFileChange(Function checkForFileChange(Function watchedRoots = roots;
- if (isAbsolute) {
- // absolute files are assumed to be read directly from the project root.
- // They therefore do not get copied to, and deleted from, the outputdir.
- watchedRoots = List.of(Path.of("/"));
- }
- if (watchedRoots.isEmpty()) {
- // this compilation unit has no resource roots, and therefore can not have this file
+ for (WatchedPath watchedPath : timestampSet.watchedPaths.values()) {
+ boolean isAbsolute = watchedPath.isAbsolute();
+ if (!isAbsolute && roots.stream().noneMatch(watchedPath.filePath::startsWith)) {
+ // The watched path does not come from the current compilation unit
continue;
}
boolean pathCurrentlyExisting = false;
boolean pathPreviouslyExisting = false;
- for (Path root : watchedRoots) {
- Path file = root.resolve(watchedFilePath);
- if (file.toFile().exists()) {
- pathCurrentlyExisting = true;
- try {
- long value = Files.getLastModifiedTime(file).toMillis();
- Long existing = timestampSet.watchedFileTimestamps.get(file);
- //existing can be null when running tests
- //as there is both normal and test resources, but only one set of watched timestampts
- if (existing != null && value > existing) {
- ret.add(watchedFilePath);
- //a write can be a 'truncate' + 'write'
- //if the file is empty we may be seeing the middle of a write
- if (Files.size(file) == 0) {
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- //ignore
- }
+ if (Files.exists(watchedPath.filePath)) {
+ pathCurrentlyExisting = true;
+ try {
+ long current = Files.getLastModifiedTime(watchedPath.filePath).toMillis();
+ long last = watchedPath.lastModified;
+ if (current > last) {
+ // Use either the absolute path or the OS-agnostic path to match the HotDeploymentWatchedFileBuildItem
+ ret.add(isAbsolute ? watchedPath.filePath.toString() : watchedPath.getOSAgnosticMatchPath());
+ //a write can be a 'truncate' + 'write'
+ //if the file is empty we may be seeing the middle of a write
+ if (Files.size(watchedPath.filePath) == 0) {
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ //ignore
}
- //re-read, as we may have read the original TS if the middle of
- //a truncate+write, even if the write had completed by the time
- //we read the size
- value = Files.getLastModifiedTime(file).toMillis();
-
- log.infof("File change detected: %s", file);
- if (!isAbsolute && doCopy && !Files.isDirectory(file)) {
- Path target = outputDir.resolve(watchedFilePath);
- byte[] data = Files.readAllBytes(file);
- try (FileOutputStream out = new FileOutputStream(target.toFile())) {
- out.write(data);
- }
+ }
+ //re-read, as we may have read the original TS if the middle of
+ //a truncate+write, even if the write had completed by the time
+ //we read the size
+ current = Files.getLastModifiedTime(watchedPath.filePath).toMillis();
+
+ log.infof("File change detected: %s", watchedPath.filePath);
+ if (!isAbsolute && doCopy && !Files.isDirectory(watchedPath.filePath)) {
+ Path target = outputDir.resolve(watchedPath.matchPath);
+ byte[] data = Files.readAllBytes(watchedPath.filePath);
+ try (FileOutputStream out = new FileOutputStream(target.toFile())) {
+ out.write(data);
}
- timestampSet.watchedFileTimestamps.put(file, value);
}
- } catch (IOException e) {
- throw new UncheckedIOException(e);
+ watchedPath.lastModified = current;
}
- } else {
- Long prevValue = timestampSet.watchedFileTimestamps.put(file, 0L);
- pathPreviouslyExisting = pathPreviouslyExisting || (prevValue != null && prevValue > 0);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
}
+ } else {
+ long prevValue = watchedPath.lastModified;
+ watchedPath.lastModified = 0L;
+ pathPreviouslyExisting = prevValue > 0;
}
if (!pathCurrentlyExisting) {
if (pathPreviouslyExisting) {
- ret.add(watchedFilePath);
+ // Use either the absolute path or the OS-agnostic path to match the HotDeploymentWatchedFileBuildItem
+ ret.add(isAbsolute ? watchedPath.filePath.toString() : watchedPath.getOSAgnosticMatchPath());
}
-
if (!isAbsolute) {
- Path target = outputDir.resolve(watchedFilePath);
+ Path target = outputDir.resolve(watchedPath.matchPath);
try {
FileUtil.deleteIfExists(target);
} catch (IOException e) {
@@ -1085,19 +1093,21 @@ public RuntimeUpdatesProcessor setWatchedFilePaths(Map watchedF
List, Boolean>> watchedFilePredicates, boolean isTest) {
if (isTest) {
setWatchedFilePathsInternal(watchedFilePaths, test,
- s -> s.getTest().isPresent() ? asList(s.getTest().get(), s.getMain()) : singletonList(s.getMain()));
+ s -> s.getTest().isPresent() ? asList(s.getTest().get(), s.getMain()) : singletonList(s.getMain()),
+ watchedFilePredicates);
} else {
- main.watchedFileTimestamps.clear();
- main.watchedFilePredicates = watchedFilePredicates;
- setWatchedFilePathsInternal(watchedFilePaths, main, s -> singletonList(s.getMain()));
+ setWatchedFilePathsInternal(watchedFilePaths, main, s -> singletonList(s.getMain()), watchedFilePredicates);
}
return this;
}
private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map watchedFilePaths,
- TimestampSet timestamps, Function> cuf) {
+ TimestampSet timestamps, Function> cuf,
+ List, Boolean>> watchedFilePredicates) {
+
timestamps.watchedFilePaths = watchedFilePaths;
- Map extraWatchedFilePaths = new HashMap<>();
+ timestamps.watchedFilePredicates = watchedFilePredicates;
+
for (DevModeContext.ModuleInfo module : context.getAllModules()) {
List compilationUnits = cuf.apply(module);
for (DevModeContext.CompilationUnit unit : compilationUnits) {
@@ -1109,34 +1119,85 @@ private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map
}
rootPaths = PathList.of(Path.of(rootPath));
}
- for (Path root : rootPaths) {
- for (String path : watchedFilePaths.keySet()) {
- Path config = root.resolve(path);
- if (config.toFile().exists()) {
- try {
- FileTime lastModifiedTime = Files.getLastModifiedTime(config);
- timestamps.watchedFileTimestamps.put(config, lastModifiedTime.toMillis());
- } catch (IOException e) {
- throw new UncheckedIOException(e);
+ final List roots = rootPaths.stream()
+ .filter(Files::exists)
+ .filter(Files::isReadable)
+ .collect(Collectors.toList());
+ for (Path root : roots) {
+ Set watchedRootPaths = new HashSet<>();
+ // First find all matching paths from all roots
+ try (final Stream walk = Files.walk(root)) {
+ walk.forEach(path -> {
+ if (path.equals(root)) {
+ return;
+ }
+ // Use the relative path to match the watched file
+ // For example /some/more/complex/path/src/main/resources/foo/bar.txt -> foo/bar.txt
+ Path relativePath = root.relativize(path);
+ // We need to match the OS-agnostic path
+ String relativePathStr = toOSAgnosticPathStr(relativePath.toString());
+ Boolean restart = watchedFilePaths.get(relativePathStr);
+ if (restart == null) {
+ restart = watchedFilePredicates.stream().filter(p -> p.getKey().test(relativePathStr))
+ .map(Entry::getValue).findFirst().orElse(null);
}
- } else {
- timestamps.watchedFileTimestamps.put(config, 0L);
- Map extraWatchedFileTimestamps = expandGlobPattern(root, config);
- timestamps.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
- for (Path extraPath : extraWatchedFileTimestamps.keySet()) {
- extraWatchedFilePaths.put(root.relativize(extraPath).toString(),
- timestamps.watchedFilePaths.get(path));
+ if (restart != null) {
+ log.debugf("Watch %s from: %s", relativePath, root);
+ watchedRootPaths.add(relativePathStr);
+ putLastModifiedTime(path, relativePath, restart, timestamps);
+ }
+ });
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ // Then process glob patterns
+ for (Entry e : watchedFilePaths.entrySet()) {
+ String watchedFilePath = e.getKey();
+ Path path = Paths.get(watchedFilePath);
+ if (!path.isAbsolute() && !watchedRootPaths.contains(e.getKey()) && maybeGlobPattern(watchedFilePath)) {
+ Path resolvedPath = root.resolve(watchedFilePath);
+ for (WatchedPath extra : expandGlobPattern(root, resolvedPath, watchedFilePath, e.getValue())) {
+ timestamps.watchedPaths.put(extra.filePath, extra);
}
- timestamps.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
}
}
}
}
}
- timestamps.watchedFilePaths.putAll(extraWatchedFilePaths);
+
+ // Finally process watched absolute paths
+ for (Entry e : watchedFilePaths.entrySet()) {
+ String watchedFilePath = e.getKey();
+ Path path = Paths.get(watchedFilePath);
+ if (path.isAbsolute()) {
+ log.debugf("Watch %s", path);
+ if (Files.exists(path)) {
+ putLastModifiedTime(path, path, e.getValue(), timestamps);
+ } else {
+ // The watched file does not exist yet but we still need to keep track of this path
+ timestamps.watchedPaths.put(path, new WatchedPath(path, path, e.getValue(), -1));
+ }
+ }
+ }
+
+ log.debugf("Watched paths: %s", timestamps.watchedPaths.values());
return this;
}
+ private boolean maybeGlobPattern(String path) {
+ return path.contains("*") || path.contains("?");
+ }
+
+ private void putLastModifiedTime(Path path, Path relativePath, boolean restart, TimestampSet timestamps) {
+ try {
+ FileTime lastModifiedTime = Files.getLastModifiedTime(path);
+ timestamps.watchedPaths.put(path, new WatchedPath(path, relativePath, restart, lastModifiedTime.toMillis()));
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
public void addHotReplacementSetup(HotReplacementSetup service) {
hotReplacementSetup.add(service);
}
@@ -1171,15 +1232,17 @@ public void close() throws IOException {
}
}
- private Map expandGlobPattern(Path root, Path configFile) {
- PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + configFile.toString());
- Map files = new HashMap<>();
+ private List expandGlobPattern(Path root, Path path, String pattern, boolean restart) {
+ PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:" + path.toString());
+ List files = new ArrayList<>();
try {
Files.walkFileTree(root, new SimpleFileVisitor() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (pathMatcher.matches(file)) {
- files.put(file, attrs.lastModifiedTime().toMillis());
+ Path relativePath = root.relativize(file);
+ log.debugf("Glob pattern [%s] matched %s from %s", pattern, relativePath, root);
+ files.add(new WatchedPath(file, relativePath, restart, attrs.lastModifiedTime().toMillis()));
}
return FileVisitResult.CONTINUE;
}
@@ -1220,25 +1283,30 @@ public boolean isLiveReloadEnabled() {
}
static class TimestampSet {
- final Map watchedFileTimestamps = new ConcurrentHashMap<>();
final Map classFileChangeTimeStamps = new ConcurrentHashMap<>();
final Map classFilePathToSourceFilePath = new ConcurrentHashMap<>();
- // file path -> isRestartNeeded
- private volatile Map watchedFilePaths = Collections.emptyMap();
- volatile List, Boolean>> watchedFilePredicates = Collections.emptyList();
+ volatile Map watchedPaths = new ConcurrentHashMap<>();
+
+ // The current paths and predicates from all HotDeploymentWatchedFileBuildItems
+ volatile Map watchedFilePaths;
+ volatile List, Boolean>> watchedFilePredicates;
public void merge(TimestampSet other) {
- watchedFileTimestamps.putAll(other.watchedFileTimestamps);
classFileChangeTimeStamps.putAll(other.classFileChangeTimeStamps);
classFilePathToSourceFilePath.putAll(other.classFilePathToSourceFilePath);
- Map newVal = new HashMap<>(watchedFilePaths);
- newVal.putAll(other.watchedFilePaths);
- watchedFilePaths = newVal;
- // The list of predicates should be effectively immutable
- watchedFilePredicates = other.watchedFilePredicates;
+ Map newVal = new HashMap<>(watchedPaths);
+ newVal.putAll(other.watchedPaths);
+ watchedPaths = newVal;
}
- boolean isWatchedFileRestartNeeded(String changedFile) {
+ boolean isRestartNeeded(String changedFile) {
+ // First try to match all existing watched paths
+ for (WatchedPath path : watchedPaths.values()) {
+ if (path.matches(changedFile)) {
+ return path.restartNeeded;
+ }
+ }
+ // Then try to match a new file that was added to a resource root
Boolean ret = watchedFilePaths.get(changedFile);
if (ret == null) {
ret = false;
@@ -1250,6 +1318,57 @@ boolean isWatchedFileRestartNeeded(String changedFile) {
}
return ret;
}
+
+ }
+
+ private static class WatchedPath {
+
+ final Path filePath;
+
+ // Used to match a HotDeploymentWatchedFileBuildItem
+ final Path matchPath;
+
+ // Last modification time or -1 if the file does not exist
+ volatile long lastModified;
+
+ // HotDeploymentWatchedFileBuildItem.restartNeeded
+ final boolean restartNeeded;
+
+ private WatchedPath(Path path, Path relativePath, boolean restartNeeded, long lastModified) {
+ this.filePath = path;
+ this.matchPath = relativePath;
+ this.restartNeeded = restartNeeded;
+ this.lastModified = lastModified;
+ }
+
+ private boolean matches(String changedFile) {
+ return isAbsolute() ? filePath.toString().equals(changedFile) : getOSAgnosticMatchPath().equals(changedFile);
+ }
+
+ private String getOSAgnosticMatchPath() {
+ return toOSAgnosticPathStr(matchPath.toString());
+ }
+
+ private boolean isAbsolute() {
+ return matchPath.isAbsolute();
+ }
+
+ @Override
+ public String toString() {
+ return "WatchedPath [matchPath=" + matchPath + ", filePath=" + filePath + ", restartNeeded=" + restartNeeded + "]";
+ }
+
+ }
+
+ /**
+ * @param path
+ * @return an OS-agnostic path; {@code /} is used as a separator
+ */
+ private static String toOSAgnosticPathStr(String path) {
+ if (File.separatorChar != '/') {
+ path = path.replace(File.separatorChar, '/');
+ }
+ return path;
}
public String[] getCommandLineArgs() {
diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
index 0fc9df79094f6..895814b3deff6 100644
--- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
+++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java
@@ -2095,6 +2095,14 @@ void collectTemplates(ApplicationArchivesBuildItem applicationArchives,
List extensionArtifacts = curateOutcome.getApplicationModel().getDependencies().stream()
.filter(Dependency::isRuntimeExtensionArtifact).collect(Collectors.toList());
+ // Make sure the new templates are watched as well
+ watchedPaths.produce(HotDeploymentWatchedFileBuildItem.builder().setLocationPredicate(new Predicate() {
+ @Override
+ public boolean test(String path) {
+ return path.startsWith(BASE_PATH);
+ }
+ }).build());
+
for (ResolvedDependency artifact : extensionArtifacts) {
if (isApplicationArchive(artifact, allApplicationArchives)) {
// Skip extension archives that are also application archives
diff --git a/integration-tests/devmode/src/test/java/io/quarkus/test/qute/HelloResource.java b/integration-tests/devmode/src/test/java/io/quarkus/test/qute/HelloResource.java
new file mode 100644
index 0000000000000..d0165af028e83
--- /dev/null
+++ b/integration-tests/devmode/src/test/java/io/quarkus/test/qute/HelloResource.java
@@ -0,0 +1,32 @@
+package io.quarkus.test.qute;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import org.jboss.resteasy.reactive.RestQuery;
+
+import io.quarkus.qute.Engine;
+import io.quarkus.qute.Template;
+
+@Path("hello")
+public class HelloResource {
+
+ @Inject
+ Template hello;
+
+ @Inject
+ Engine engine;
+
+ @GET
+ public String get(@RestQuery String name) {
+ return hello.data("name", name).render();
+ }
+
+ @GET
+ @Path("ping")
+ public String ping() {
+ return engine.getTemplate("ping").render();
+ }
+
+}
diff --git a/integration-tests/devmode/src/test/java/io/quarkus/test/qute/QuteWatchedResourceTest.java b/integration-tests/devmode/src/test/java/io/quarkus/test/qute/QuteWatchedResourceTest.java
new file mode 100644
index 0000000000000..f61c8969fb8dc
--- /dev/null
+++ b/integration-tests/devmode/src/test/java/io/quarkus/test/qute/QuteWatchedResourceTest.java
@@ -0,0 +1,51 @@
+package io.quarkus.test.qute;
+
+import static io.restassured.RestAssured.when;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusDevModeTest;
+
+public class QuteWatchedResourceTest {
+
+ @RegisterExtension
+ static final QuarkusDevModeTest config = new QuarkusDevModeTest()
+ .withApplicationRoot(
+ root -> root.addClass(HelloResource.class)
+ .addAsResource(new StringAsset("Hello {name}!"), "templates/hello.txt"));
+
+ @Test
+ public void testWatchedFiles() {
+ when().get("/hello?name=Martin").then()
+ .body(containsString("Hello Martin!"))
+ .statusCode(200);
+
+ config.modifyResourceFile("templates/hello.txt", file -> "Hi {name}!");
+
+ when().get("/hello?name=Martin").then()
+ .body(containsString("Hi Martin!"))
+ .statusCode(200);
+
+ config.modifyResourceFile("templates/hello.txt", file -> "Hello {name}!");
+
+ when().get("/hello?name=Martin").then()
+ .body(containsString("Hello Martin!"))
+ .statusCode(200);
+
+ config.addResourceFile("templates/ping.txt", "pong");
+
+ when().get("/hello/ping").then()
+ .body(containsString("pong"))
+ .statusCode(200);
+
+ config.modifyResourceFile("templates/ping.txt", file -> "pong!");
+
+ when().get("/hello/ping").then()
+ .body(containsString("pong!"))
+ .statusCode(200);
+ }
+
+}
diff --git a/integration-tests/devmode/src/test/java/io/quarkus/test/reload/AdditionalWatchedResourcesDevModeTest.java b/integration-tests/devmode/src/test/java/io/quarkus/test/reload/AdditionalWatchedResourcesDevModeTest.java
index 9bd310ec7badf..2f4a3d23c2c0a 100644
--- a/integration-tests/devmode/src/test/java/io/quarkus/test/reload/AdditionalWatchedResourcesDevModeTest.java
+++ b/integration-tests/devmode/src/test/java/io/quarkus/test/reload/AdditionalWatchedResourcesDevModeTest.java
@@ -74,6 +74,10 @@ public void globWatch() {
TEST.modifyResourceFile(SAMPLE_FILE, oldSource -> MODIFIED);
RestAssured.get("/content/{name}", SAMPLE_FILE).then().body(is(MODIFIED));
+
+ TEST.modifyResourceFile(SAMPLE_FILE, oldSource -> INITIAL);
+
+ RestAssured.get("/content/{name}", SAMPLE_FILE).then().body(is(INITIAL));
}
@Test