Skip to content

Commit

Permalink
Merge pull request #17198 from stuartwdouglas/test-resources-fix
Browse files Browse the repository at this point in the history
Fix issues with changes to test resources
  • Loading branch information
stuartwdouglas authored May 13, 2021
2 parents 942e8c9 + 49a3f19 commit 7d04331
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 @@ -1464,6 +1464,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 @@ -974,11 +984,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 @@ -867,11 +867,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 7d04331

Please sign in to comment.