2
2
3
3
import java .io .IOException ;
4
4
import java .io .InputStream ;
5
+ import java .io .UncheckedIOException ;
5
6
import java .nio .file .Files ;
6
7
import java .nio .file .Path ;
7
- import java .nio .file .Paths ;
8
8
import java .util .ArrayList ;
9
9
import java .util .Arrays ;
10
10
import java .util .Collections ;
11
11
import java .util .HashMap ;
12
- import java .util .LinkedHashSet ;
13
12
import java .util .List ;
14
13
import java .util .Map ;
15
14
import java .util .Objects ;
18
17
import java .util .Set ;
19
18
import java .util .concurrent .CopyOnWriteArrayList ;
20
19
import java .util .concurrent .atomic .AtomicLong ;
20
+ import java .util .function .Consumer ;
21
21
import java .util .regex .Pattern ;
22
22
import java .util .stream .Collectors ;
23
23
26
26
27
27
import io .quarkus .bootstrap .app .CuratedApplication ;
28
28
import io .quarkus .bootstrap .app .QuarkusBootstrap ;
29
+ import io .quarkus .bootstrap .app .QuarkusBootstrap .Mode ;
30
+ import io .quarkus .bootstrap .classloading .ClassPathElement ;
29
31
import io .quarkus .bootstrap .classloading .QuarkusClassLoader ;
32
+ import io .quarkus .bootstrap .model .ApplicationModel ;
30
33
import io .quarkus .deployment .dev .ClassScanResult ;
31
34
import io .quarkus .deployment .dev .CompilationProvider ;
32
35
import io .quarkus .deployment .dev .DevModeContext ;
36
+ import io .quarkus .deployment .dev .DevModeContext .ModuleInfo ;
33
37
import io .quarkus .deployment .dev .QuarkusCompiler ;
34
38
import io .quarkus .deployment .dev .RuntimeUpdatesProcessor ;
35
39
import io .quarkus .dev .spi .DevModeType ;
36
40
import io .quarkus .dev .testing .TestWatchedFiles ;
41
+ import io .quarkus .maven .dependency .ResolvedDependency ;
37
42
import io .quarkus .paths .PathList ;
38
43
import io .quarkus .runtime .configuration .HyphenateEnumConverter ;
39
44
@@ -143,46 +148,36 @@ public void start() {
143
148
}
144
149
}
145
150
151
+ private static Pattern getCompiledPatternOrNull (Optional <String > patternStr ) {
152
+ return patternStr .isPresent () ? Pattern .compile (patternStr .get ()) : null ;
153
+ }
154
+
146
155
public void init () {
147
156
if (moduleRunners .isEmpty ()) {
148
157
TestWatchedFiles .setWatchedFilesListener ((s ) -> RuntimeUpdatesProcessor .INSTANCE .setWatchedFilePaths (s , true ));
158
+ final Pattern includeModulePattern = getCompiledPatternOrNull (config .includeModulePattern );
159
+ final Pattern excludeModulePattern = getCompiledPatternOrNull (config .excludeModulePattern );
149
160
for (var module : context .getAllModules ()) {
150
- boolean mainModule = module == context .getApplicationRoot ();
161
+ final boolean mainModule = module == context .getApplicationRoot ();
151
162
if (config .onlyTestApplicationModule && !mainModule ) {
152
163
continue ;
153
- } else if (config . includeModulePattern . isPresent () ) {
154
- Pattern p = Pattern . compile ( config . includeModulePattern . get ());
155
- if (! p .matcher (module .getArtifactKey ().getGroupId () + ":" + module .getArtifactKey ().getArtifactId ())
164
+ } else if (includeModulePattern != null ) {
165
+ if (! includeModulePattern
166
+ .matcher (module .getArtifactKey ().getGroupId () + ":" + module .getArtifactKey ().getArtifactId ())
156
167
.matches ()) {
157
168
continue ;
158
169
}
159
- } else if (config . excludeModulePattern . isPresent () ) {
160
- Pattern p = Pattern . compile ( config . excludeModulePattern . get ());
161
- if ( p .matcher (module .getArtifactKey ().getGroupId () + ":" + module .getArtifactKey ().getArtifactId ())
170
+ } else if (excludeModulePattern != null ) {
171
+ if ( excludeModulePattern
172
+ .matcher (module .getArtifactKey ().getGroupId () + ":" + module .getArtifactKey ().getArtifactId ())
162
173
.matches ()) {
163
174
continue ;
164
175
}
165
176
}
166
177
167
178
try {
168
- Set <Path > paths = new LinkedHashSet <>();
169
- module .getTest ().ifPresent (test -> {
170
- paths .add (Paths .get (test .getClassesPath ()));
171
- if (test .getResourcesOutputPath () != null ) {
172
- paths .add (Paths .get (test .getResourcesOutputPath ()));
173
- }
174
- });
175
- if (mainModule ) {
176
- curatedApplication .getQuarkusBootstrap ().getApplicationRoot ().forEach (paths ::add );
177
- } else {
178
- paths .add (Paths .get (module .getMain ().getClassesPath ()));
179
- }
180
- for (var i : paths ) {
181
- if (!Files .exists (i )) {
182
- Files .createDirectories (i );
183
- }
184
- }
185
- QuarkusBootstrap .Builder builder = curatedApplication .getQuarkusBootstrap ().clonedBuilder ()
179
+ final Path projectDir = Path .of (module .getProjectDirectory ());
180
+ final QuarkusBootstrap .Builder bootstrapConfig = curatedApplication .getQuarkusBootstrap ().clonedBuilder ()
186
181
.setMode (QuarkusBootstrap .Mode .TEST )
187
182
.setAssertionsEnabled (true )
188
183
.setDisableClasspathCache (false )
@@ -192,20 +187,61 @@ public void init() {
192
187
.setTest (true )
193
188
.setAuxiliaryApplication (true )
194
189
.setHostApplicationIsTestOnly (devModeType == DevModeType .TEST_ONLY )
195
- .setProjectRoot (Paths . get ( module . getProjectDirectory ()) )
196
- .setApplicationRoot (PathList . from ( paths ))
190
+ .setProjectRoot (projectDir )
191
+ .setApplicationRoot (getRootPaths ( module , mainModule ))
197
192
.clearLocalArtifacts ();
193
+
194
+ final QuarkusClassLoader ctParentFirstCl ;
195
+ final Mode currentMode = curatedApplication .getQuarkusBootstrap ().getMode ();
196
+ // in case of quarkus:test the application model will already include test dependencies
197
+ if (Mode .CONTINUOUS_TEST != currentMode && Mode .TEST != currentMode ) {
198
+ // In this case the current application model does not include test dependencies.
199
+ // 1) we resolve an application model for test mode;
200
+ // 2) we create a new CT base classloader that includes parent-first test scoped dependencies
201
+ // so that they are not loaded by augment and base runtime classloaders.
202
+ var appModelFactory = curatedApplication .getQuarkusBootstrap ().newAppModelFactory ();
203
+ appModelFactory .setTest (true );
204
+ appModelFactory .setLocalArtifacts (Set .of ());
205
+ if (!mainModule ) {
206
+ appModelFactory .setAppArtifact (null );
207
+ appModelFactory .setProjectRoot (projectDir );
208
+ }
209
+ final ApplicationModel testModel = appModelFactory .resolveAppModel ().getApplicationModel ();
210
+ bootstrapConfig .setExistingModel (testModel );
211
+
212
+ QuarkusClassLoader .Builder clBuilder = null ;
213
+ var currentParentFirst = curatedApplication .getApplicationModel ().getParentFirst ();
214
+ for (ResolvedDependency d : testModel .getDependencies ()) {
215
+ if (d .isClassLoaderParentFirst () && !currentParentFirst .contains (d .getKey ())) {
216
+ if (clBuilder == null ) {
217
+ clBuilder = QuarkusClassLoader .builder ("Continuous Testing Parent-First" ,
218
+ getClass ().getClassLoader ().getParent (), false );
219
+ }
220
+ clBuilder .addElement (ClassPathElement .fromDependency (d ));
221
+ }
222
+ }
223
+
224
+ ctParentFirstCl = clBuilder == null ? null : clBuilder .build ();
225
+ if (ctParentFirstCl != null ) {
226
+ bootstrapConfig .setBaseClassLoader (ctParentFirstCl );
227
+ }
228
+ } else {
229
+ ctParentFirstCl = null ;
230
+ if (mainModule ) {
231
+ // the model and the app classloader already include test scoped dependencies
232
+ bootstrapConfig .setExistingModel (curatedApplication .getApplicationModel ());
233
+ }
234
+ }
235
+
198
236
//we always want to propagate parent first
199
237
//so it is consistent. Some modules may not have quarkus dependencies
200
238
//so they won't load junit parent first without this
201
239
for (var i : curatedApplication .getApplicationModel ().getDependencies ()) {
202
240
if (i .isClassLoaderParentFirst ()) {
203
- builder .addParentFirstArtifact (i .getKey ());
241
+ bootstrapConfig .addParentFirstArtifact (i .getKey ());
204
242
}
205
243
}
206
- var testCuratedApplication = builder // we want to re-discover the local dependencies with test scope
207
- .build ()
208
- .bootstrap ();
244
+ var testCuratedApplication = bootstrapConfig .build ().bootstrap ();
209
245
if (mainModule ) {
210
246
//horrible hack
211
247
//we really need a compiler per module but we are not setup for this yet
@@ -215,7 +251,7 @@ public void init() {
215
251
//has complained much
216
252
compiler = new QuarkusCompiler (testCuratedApplication , compilationProviders , context );
217
253
}
218
- var testRunner = new ModuleTestRunner (this , context , testCuratedApplication , module );
254
+ var testRunner = new ModuleTestRunner (this , testCuratedApplication , module );
219
255
QuarkusClassLoader cl = (QuarkusClassLoader ) getClass ().getClassLoader ();
220
256
cl .addCloseTask (new Runnable () {
221
257
@ Override
@@ -224,6 +260,9 @@ public void run() {
224
260
close ();
225
261
} finally {
226
262
testCuratedApplication .close ();
263
+ if (ctParentFirstCl != null ) {
264
+ ctParentFirstCl .close ();
265
+ }
227
266
}
228
267
}
229
268
});
@@ -235,6 +274,37 @@ public void run() {
235
274
}
236
275
}
237
276
277
+ private PathList getRootPaths (ModuleInfo module , final boolean mainModule ) {
278
+ final PathList .Builder pathBuilder = PathList .builder ();
279
+ final Consumer <Path > paths = new Consumer <>() {
280
+ @ Override
281
+ public void accept (Path t ) {
282
+ if (!pathBuilder .contains (t )) {
283
+ if (!Files .exists (t )) {
284
+ try {
285
+ Files .createDirectories (t );
286
+ } catch (IOException e ) {
287
+ throw new UncheckedIOException (e );
288
+ }
289
+ }
290
+ pathBuilder .add (t );
291
+ }
292
+ }
293
+ };
294
+ module .getTest ().ifPresent (test -> {
295
+ paths .accept (Path .of (test .getClassesPath ()));
296
+ if (test .getResourcesOutputPath () != null ) {
297
+ paths .accept (Path .of (test .getResourcesOutputPath ()));
298
+ }
299
+ });
300
+ if (mainModule ) {
301
+ curatedApplication .getQuarkusBootstrap ().getApplicationRoot ().forEach (paths ::accept );
302
+ } else {
303
+ paths .accept (Path .of (module .getMain ().getClassesPath ()));
304
+ }
305
+ return pathBuilder .build ();
306
+ }
307
+
238
308
public synchronized void close () {
239
309
closed = true ;
240
310
stop ();
@@ -522,10 +592,6 @@ public boolean isStarted() {
522
592
return started ;
523
593
}
524
594
525
- public CuratedApplication getCuratedApplication () {
526
- return curatedApplication ;
527
- }
528
-
529
595
public QuarkusCompiler getCompiler () {
530
596
return compiler ;
531
597
}
0 commit comments