Skip to content

Commit

Permalink
Fix issues with changes to test resources
Browse files Browse the repository at this point in the history
Previously these were not picked up and
would not trigger test changes.
  • Loading branch information
stuartwdouglas committed May 13, 2021
1 parent 4708850 commit 49a3f19
Show file tree
Hide file tree
Showing 22 changed files with 284 additions and 113 deletions.
7 changes: 7 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,13 @@
<artifactId>quarkus-vertx-http-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-web</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,27 @@

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.HotDeploymentWatchedFileBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.ServiceStartBuildItem;
import io.quarkus.dev.testing.TestWatchedFiles;

public class HotDeploymentConfigFileBuildStep {

@BuildStep
ServiceStartBuildItem setupConfigFileHotDeployment(List<HotDeploymentWatchedFileBuildItem> files) {
ServiceStartBuildItem setupConfigFileHotDeployment(List<HotDeploymentWatchedFileBuildItem> files,
LaunchModeBuildItem launchModeBuildItem) {
// TODO: this should really be an output of the RuntimeRunner
RuntimeUpdatesProcessor processor = RuntimeUpdatesProcessor.INSTANCE;
if (processor != null) {
if (processor != null || launchModeBuildItem.isAuxiliaryApplication()) {
Map<String, Boolean> watchedFilePaths = files.stream()
.collect(Collectors.toMap(HotDeploymentWatchedFileBuildItem::getLocation,
HotDeploymentWatchedFileBuildItem::isRestartNeeded,
(isRestartNeeded1, isRestartNeeded2) -> isRestartNeeded1 || isRestartNeeded2));
processor.setWatchedFilePaths(watchedFilePaths);
if (launchModeBuildItem.isAuxiliaryApplication()) {
TestWatchedFiles.setWatchedFilePaths(watchedFilePaths);
} else {
processor.setWatchedFilePaths(watchedFilePaths, false);
}
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -81,9 +82,6 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable
private final DevModeType devModeType;
volatile Throwable compileProblem;

// file path -> isRestartNeeded
private volatile Map<String, Boolean> watchedFilePaths = Collections.emptyMap();

private volatile Predicate<ClassInfo> disableInstrumentationForClassPredicate = new AlwaysFalsePredicate<>();
private volatile Predicate<Index> disableInstrumentationForIndexPredicate = new AlwaysFalsePredicate<>();

Expand All @@ -95,12 +93,6 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable
private final TimestampSet test = new TimestampSet();
final Map<Path, Long> sourceFileTimestamps = new ConcurrentHashMap<>();

/**
* Resources that appear in both src and target, these will be removed if the src resource subsequently disappears.
* This map contains the paths in the target dir, one for each module, otherwise on a second module we will delete files
* from the first one
*/
private final Map<String, Set<Path>> correspondingResources = new ConcurrentHashMap<>();
private final List<Runnable> preScanSteps = new CopyOnWriteArrayList<>();
private final List<Consumer<Set<String>>> noRestartChangesConsumers = new CopyOnWriteArrayList<>();
private final List<HotReplacementSetup> hotReplacementSetup = new ArrayList<>();
Expand All @@ -115,6 +107,13 @@ public class RuntimeUpdatesProcessor implements HotReplacementContext, Closeable
*/
private static volatile IndexView lastStartIndex;

/**
* Resources that appear in both src and target, these will be removed if the src resource subsequently disappears.
* This map contains the paths in the target dir, one for each module, otherwise on a second module we will delete files
* from the first one
*/
private final Map<DevModeContext.CompilationUnit, Set<Path>> correspondingResources = new ConcurrentHashMap<>();

private final TestSupport testSupport;
private volatile boolean firstTestScanComplete;
private volatile Boolean instrumentationEnabled;
Expand Down Expand Up @@ -248,9 +247,11 @@ private void periodicTestCompile() {
try {
ClassScanResult changedTestClassResult = compileTestClasses();
ClassScanResult changedApp = checkForChangedClasses(compiler, DevModeContext.ModuleInfo::getMain, false, test);
Set<String> filesChanged = checkForFileChange(DevModeContext.ModuleInfo::getMain, test);
boolean configFileRestartNeeded = filesChanged.stream().map(watchedFilePaths::get)
Set<String> filesChanges = new HashSet<>(checkForFileChange(s -> s.getTest().get(), test));
filesChanges.addAll(checkForFileChange(DevModeContext.ModuleInfo::getMain, test));
boolean configFileRestartNeeded = filesChanges.stream().map(test.watchedFilePaths::get)
.anyMatch(Boolean.TRUE::equals);

ClassScanResult merged = ClassScanResult.merge(changedTestClassResult, changedApp);
if (configFileRestartNeeded) {
if (compileProblem != null) {
Expand Down Expand Up @@ -369,7 +370,8 @@ public boolean doScan(boolean userInitiated, boolean force) throws IOException {
main);
Set<String> filesChanged = checkForFileChange(DevModeContext.ModuleInfo::getMain, main);

boolean configFileRestartNeeded = filesChanged.stream().map(watchedFilePaths::get).anyMatch(Boolean.TRUE::equals);
boolean configFileRestartNeeded = filesChanged.stream().map(main.watchedFilePaths::get)
.anyMatch(Boolean.TRUE::equals);
boolean instrumentationChange = false;
if (ClassChangeAgent.getInstrumentation() != null && lastStartIndex != null && !configFileRestartNeeded
&& devModeType != DevModeType.REMOTE_LOCAL_SIDE) {
Expand Down Expand Up @@ -561,7 +563,7 @@ ClassScanResult checkForChangedClasses(QuarkusCompiler compiler,
changedSourceFiles = sourcesStream
.parallel()
.filter(p -> matchingHandledExtension(p).isPresent()
&& sourceFileWasRecentModified(p, ignoreFirstScanChanges, timestampSet))
&& sourceFileWasRecentModified(p, ignoreFirstScanChanges))
.map(Path::toFile)
//Needing a concurrent Set, not many standard options:
.collect(Collectors.toCollection(ConcurrentSkipListSet::new));
Expand Down Expand Up @@ -695,7 +697,7 @@ Set<String> checkForFileChange(Function<DevModeContext.ModuleInfo, DevModeContex
TimestampSet timestampSet) {
Set<String> ret = new HashSet<>();
for (DevModeContext.ModuleInfo module : context.getAllModules()) {
final Set<Path> moduleResources = correspondingResources.computeIfAbsent(module.getName(),
final Set<Path> moduleResources = correspondingResources.computeIfAbsent(cuf.apply(module),
m -> Collections.newSetFromMap(new ConcurrentHashMap<>()));
boolean doCopy = true;
String rootPath = cuf.apply(module).getResourcePath();
Expand Down Expand Up @@ -759,13 +761,15 @@ Set<String> checkForFileChange(Function<DevModeContext.ModuleInfo, DevModeContex
}
}

for (String path : watchedFilePaths.keySet()) {
for (String path : timestampSet.watchedFilePaths.keySet()) {
Path file = root.resolve(path);
if (file.toFile().exists()) {
try {
long value = Files.getLastModifiedTime(file).toMillis();
Long existing = timestampSet.watchedFileTimestamps.get(file);
if (value > existing) {
//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(path);
log.infof("File change detected: %s", file);
if (doCopy && !Files.isDirectory(file)) {
Expand Down Expand Up @@ -795,8 +799,7 @@ Set<String> checkForFileChange(Function<DevModeContext.ModuleInfo, DevModeContex
return ret;
}

private boolean sourceFileWasRecentModified(final Path sourcePath, boolean ignoreFirstScanChanges,
TimestampSet timestampSet) {
private boolean sourceFileWasRecentModified(final Path sourcePath, boolean ignoreFirstScanChanges) {
return checkIfFileModified(sourcePath, sourceFileTimestamps, ignoreFirstScanChanges);
}

Expand Down Expand Up @@ -850,48 +853,55 @@ public RuntimeUpdatesProcessor setDisableInstrumentationForIndexPredicate(
return this;
}

public RuntimeUpdatesProcessor setWatchedFilePaths(Map<String, Boolean> watchedFilePaths) {
boolean includeTest = test.watchedFileTimestamps.isEmpty();
this.watchedFilePaths = watchedFilePaths;
main.watchedFileTimestamps.clear();
public RuntimeUpdatesProcessor setWatchedFilePaths(Map<String, Boolean> watchedFilePaths, boolean isTest) {
if (isTest) {
setWatchedFilePathsInternal(watchedFilePaths, test, s -> Arrays.asList(s.getTest().get(), s.getMain()));
} else {
main.watchedFileTimestamps.clear();
setWatchedFilePathsInternal(watchedFilePaths, main, s -> Collections.singletonList(s.getMain()));
}
return this;
}

private RuntimeUpdatesProcessor setWatchedFilePathsInternal(Map<String, Boolean> watchedFilePaths,
TimestampSet timestamps, Function<DevModeContext.ModuleInfo, List<DevModeContext.CompilationUnit>> cuf) {
timestamps.watchedFilePaths = watchedFilePaths;
Map<String, Boolean> extraWatchedFilePaths = new HashMap<>();
for (DevModeContext.ModuleInfo module : context.getAllModules()) {
String rootPath = module.getMain().getResourcePath();
List<DevModeContext.CompilationUnit> compilationUnits = cuf.apply(module);
for (DevModeContext.CompilationUnit unit : compilationUnits) {
String rootPath = unit.getResourcePath();

if (rootPath == null) {
rootPath = module.getMain().getClassesPath();
}
if (rootPath == null) {
continue;
}
Path root = Paths.get(rootPath);
for (String path : watchedFilePaths.keySet()) {
Path config = root.resolve(path);
if (config.toFile().exists()) {
try {
FileTime lastModifiedTime = Files.getLastModifiedTime(config);
main.watchedFileTimestamps.put(config, lastModifiedTime.toMillis());
if (includeTest) {
test.watchedFileTimestamps.put(config, lastModifiedTime.toMillis());
if (rootPath == null) {
rootPath = unit.getClassesPath();
}
if (rootPath == null) {
continue;
}
Path root = Paths.get(rootPath);
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);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} else {
main.watchedFileTimestamps.put(config, 0L);
Map<Path, Long> extraWatchedFileTimestamps = expandGlobPattern(root, config);
main.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
for (Path extraPath : extraWatchedFileTimestamps.keySet()) {
extraWatchedFilePaths.put(root.relativize(extraPath).toString(), this.watchedFilePaths.get(path));
}
if (includeTest) {
test.watchedFileTimestamps.put(config, 0L);
} else {
timestamps.watchedFileTimestamps.put(config, 0L);
Map<Path, Long> extraWatchedFileTimestamps = expandGlobPattern(root, config);
timestamps.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
for (Path extraPath : extraWatchedFileTimestamps.keySet()) {
extraWatchedFilePaths.put(root.relativize(extraPath).toString(),
timestamps.watchedFilePaths.get(path));
}
timestamps.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
}
main.watchedFileTimestamps.putAll(extraWatchedFileTimestamps);
}
}
}
this.watchedFilePaths.putAll(extraWatchedFilePaths);
timestamps.watchedFilePaths.putAll(extraWatchedFilePaths);
return this;
}

Expand Down Expand Up @@ -970,11 +980,17 @@ static class TimestampSet {
final Map<Path, Long> watchedFileTimestamps = new ConcurrentHashMap<>();
final Map<Path, Long> classFileChangeTimeStamps = new ConcurrentHashMap<>();
final Map<Path, Path> classFilePathToSourceFilePath = new ConcurrentHashMap<>();
// file path -> isRestartNeeded
private volatile Map<String, Boolean> watchedFilePaths = Collections.emptyMap();

public void merge(TimestampSet other) {
watchedFileTimestamps.putAll(other.watchedFileTimestamps);
classFileChangeTimeStamps.putAll(other.classFileChangeTimeStamps);
classFilePathToSourceFilePath.putAll(other.classFilePathToSourceFilePath);
Map<String, Boolean> newVal = new HashMap<>(watchedFilePaths);
newVal.putAll(other.watchedFilePaths);
watchedFilePaths = newVal;

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
import io.quarkus.bootstrap.app.CuratedApplication;
import io.quarkus.deployment.dev.ClassScanResult;
import io.quarkus.deployment.dev.DevModeContext;
import io.quarkus.deployment.dev.RuntimeUpdatesProcessor;
import io.quarkus.dev.testing.TestWatchedFiles;
import io.quarkus.runtime.configuration.HyphenateEnumConverter;

public class TestRunner {
Expand Down Expand Up @@ -243,6 +245,10 @@ public void runComplete(TestRunResults results) {
synchronized (this) {
runner = null;
}
Map<String, Boolean> watched = TestWatchedFiles.retrieveWatchedFilePaths();
if (watched != null) {
RuntimeUpdatesProcessor.INSTANCE.setWatchedFilePaths(watched, true);
}
if (disabled) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.dev.testing;

import java.util.Map;

/**
* provides a way for a test run to tell the external application about watched paths.
*
* This could be a test specific application.properties or import.sql for example
*/
public class TestWatchedFiles {

private static volatile Map<String, Boolean> watchedFilePaths;

public static Map<String, Boolean> retrieveWatchedFilePaths() {
Map<String, Boolean> watchedFilePaths = TestWatchedFiles.watchedFilePaths;
TestWatchedFiles.watchedFilePaths = null;
return watchedFilePaths;
}

public static void setWatchedFilePaths(Map<String, Boolean> watchedFilePaths) {
TestWatchedFiles.watchedFilePaths = watchedFilePaths;
}
}
1 change: 1 addition & 0 deletions devtools/maven/src/main/java/io/quarkus/maven/DevMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,7 @@ private void addProject(MavenDevModeLauncher.Builder builder, LocalProject local
.setTestSourcePaths(testSourcePaths)
.setTestClassesPath(testClassesPath)
.setTestResourcePath(testResourcePath)
.setTestResourcesOutputPath(testClassesPath)
.build();

if (root) {
Expand Down
16 changes: 16 additions & 0 deletions extensions/hibernate-orm/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
Expand All @@ -82,6 +87,17 @@
<artifactId>quarkus-smallrye-metrics-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-http-deployment</artifactId>
<scope>test</scope>
<type>test-jar</type>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -861,11 +861,9 @@ private static void producePersistenceUnitDescriptorFromConfig(
"Unable to find file referenced in '" + HIBERNATE_ORM_CONFIG_PREFIX + "sql-load-script="
+ persistenceUnitConfig.sqlLoadScript.get() + "'. Remove property or add file to your path.");
}
if (launchMode == LaunchMode.DEVELOPMENT) {
// in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist
// as a user could still add it after performing the initial configuration
hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile.get()));
}
// in dev mode we want to make sure that we watch for changes to file even if it doesn't currently exist
// as a user could still add it after performing the initial configuration
hotDeploymentWatchedFiles.produce(new HotDeploymentWatchedFileBuildItem(importFile.get()));
} else {
//Disable implicit loading of the default import script (import.sql)
descriptor.getProperties().setProperty(AvailableSettings.HBM2DDL_IMPORT_FILES, "");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.hibernate.orm;

import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;

/**
* Test for continuous testing with hibernate
*/
@QuarkusTest
public class HibernateET {

@Test
public void testImport() {
RestAssured.when().get("/my-entity/1").then().body(is("MyEntity:TEST ENTITY"));
}

}
Loading

0 comments on commit 49a3f19

Please sign in to comment.