18
18
import io .quarkus .bootstrap .app .CuratedApplication ;
19
19
import io .quarkus .bootstrap .classloading .ClassPathElement ;
20
20
import io .quarkus .bootstrap .classloading .QuarkusClassLoader ;
21
+ import io .quarkus .bootstrap .model .ApplicationModel ;
21
22
import io .quarkus .bootstrap .workspace .ArtifactSources ;
22
23
import io .quarkus .bootstrap .workspace .SourceDir ;
23
24
import io .quarkus .bootstrap .workspace .WorkspaceModule ;
24
25
import io .quarkus .commons .classloading .ClassLoaderHelper ;
26
+ import io .quarkus .maven .dependency .DependencyFlags ;
25
27
import io .quarkus .paths .PathTree ;
26
28
import io .quarkus .paths .PathVisit ;
27
29
import io .quarkus .runtime .util .ClassPathUtils ;
32
34
public final class PathTestHelper {
33
35
private static final String TARGET = "target" ;
34
36
private static final Map <String , String > TEST_TO_MAIN_DIR_FRAGMENTS = new HashMap <>();
37
+ private static final List <String > TEST_DIRS ;
35
38
36
39
static {
37
40
//region Eclipse
@@ -131,6 +134,7 @@ public final class PathTestHelper {
131
134
}
132
135
});
133
136
}
137
+ TEST_DIRS = List .of (TEST_TO_MAIN_DIR_FRAGMENTS .keySet ().toArray (new String [0 ]));
134
138
}
135
139
136
140
private PathTestHelper () {
@@ -143,32 +147,53 @@ private PathTestHelper() {
143
147
* @return directory or JAR containing the test class
144
148
*/
145
149
public static Path getTestClassesLocation (Class <?> testClass ) {
146
- String classFileName = testClass .getName ().replace ('.' , File .separatorChar ) + ".class" ;
147
- URL resource = testClass .getClassLoader ().getResource (fromClassNameToResourceName (testClass .getName ()));
148
-
150
+ final String classFileName = fromClassNameToResourceName (testClass .getName ());
151
+ URL resource = testClass .getClassLoader ().getResource (classFileName );
149
152
if (resource == null ) {
150
153
throw new IllegalStateException (
151
- "Could not find resource: " + testClass . getName () + " using class loader " + testClass .getClassLoader ());
154
+ "Could not find resource " + classFileName + " using class loader " + testClass .getClassLoader ());
152
155
}
153
156
if (resource .getProtocol ().equals ("jar" )) {
154
157
try {
155
158
resource = URI .create (resource .getFile ().substring (0 , resource .getFile ().indexOf ('!' ))).toURL ();
156
159
return toPath (resource );
157
160
} catch (MalformedURLException e ) {
158
- throw new RuntimeException ("Failed to resolve the location of the JAR containing " + testClass , e );
161
+ throw new RuntimeException ("Failed to resolve the location of the JAR containing " + classFileName , e );
162
+ }
163
+ }
164
+ if (resource .getProtocol ().equals ("quarkus" )) {
165
+ // This is a bytecode enhanced class, the original class is either in the application module or a dependency
166
+ final ApplicationModel appModel = ((QuarkusClassLoader ) testClass .getClassLoader ()).getCuratedApplication ()
167
+ .getApplicationModel ();
168
+
169
+ Path testLocation = getTestClassesDirOrNull (classFileName , appModel .getApplicationModule ());
170
+ if (testLocation != null ) {
171
+ return testLocation ;
172
+ }
173
+
174
+ // JARs containing tests will most of the time be direct test scoped dependencies (e.g. Quarkus platform testsuite).
175
+ // Look among the direct dependencies first to optimize for the majority of cases.
176
+ // Depending on the amount of dependencies and their ordering, could be ~15-20 times faster.
177
+ testLocation = getTestClassLocationFromDepsOrNull (classFileName , appModel ,
178
+ DependencyFlags .DIRECT | DependencyFlags .RUNTIME_CP );
179
+ if (testLocation != null ) {
180
+ return testLocation ;
159
181
}
160
- } else if (resource .getProtocol ().equals ("quarkus" )) {
161
- // This is loaded with a quarkus classloader, so we can (sort of) ask it directly
162
- QuarkusClassLoader qcl = (QuarkusClassLoader ) testClass .getClassLoader ();
163
- return getTestClassesLocation (testClass , qcl .getCuratedApplication ());
182
+ // Look among all the runtime dependencies. We need a more efficient way to handle this case.
183
+ // I don't think we have a test for this case.
184
+ testLocation = getTestClassLocationFromDepsOrNull (classFileName , appModel , DependencyFlags .RUNTIME_CP );
185
+ if (testLocation != null ) {
186
+ return testLocation ;
187
+ }
188
+ throw new RuntimeException ("Failed to locate " + classFileName + " among the application dependencies" );
164
189
}
165
190
Path path = toPath (resource );
166
191
path = path .getRoot ().resolve (path .subpath (0 , path .getNameCount () - Path .of (classFileName ).getNameCount ()));
167
192
168
- if (!isInTestDir (resource ) && !path .getParent ().getFileName ().toString ().equals (TARGET )) {
193
+ if (!isInTestDir (path ) && !path .getParent ().getFileName ().toString ().equals (TARGET )) {
169
194
final StringBuilder msg = new StringBuilder ();
170
195
msg .append ("The test class " ).append (testClass .getName ()).append (" is not located in any of the directories " );
171
- var i = TEST_TO_MAIN_DIR_FRAGMENTS . keySet () .iterator ();
196
+ var i = TEST_DIRS .iterator ();
172
197
msg .append (i .next ());
173
198
while (i .hasNext ()) {
174
199
msg .append (", " ).append (i .next ());
@@ -178,12 +203,43 @@ public static Path getTestClassesLocation(Class<?> testClass) {
178
203
return path ;
179
204
}
180
205
206
+ /**
207
+ * Looks for a resource among the dependencies with specific flags. The method will return the first dependency
208
+ * providing the resource of null, if none of the dependencies provide the resource.
209
+ *
210
+ * @param classFileName classpath resource name
211
+ * @param appModel application model
212
+ * @param depFlags dependency flags
213
+ * @return the first dependency containing the resource or null, if none of the matching dependencies provide the resource
214
+ */
215
+ private static Path getTestClassLocationFromDepsOrNull (String classFileName , ApplicationModel appModel , int depFlags ) {
216
+ for (var d : appModel .getDependencies (depFlags )) {
217
+ final Path root = d .getContentTree ().apply (classFileName , PathTestHelper ::getRootOrNull );
218
+ if (root != null ) {
219
+ return root ;
220
+ }
221
+ }
222
+ return null ;
223
+ }
224
+
181
225
public static Path getTestClassesLocation (Class <?> requiredTestClass , CuratedApplication curatedApplication ) {
182
- final WorkspaceModule module = curatedApplication .getApplicationModel ().getAppArtifact ().getWorkspaceModule ();
226
+ final Path testClassesDir = getTestClassesDirOrNull (
227
+ ClassLoaderHelper .fromClassNameToResourceName (requiredTestClass .getName ()),
228
+ curatedApplication .getApplicationModel ().getApplicationModule ());
229
+ return testClassesDir != null ? testClassesDir : getTestClassesLocation (requiredTestClass );
183
230
231
+ }
232
+
233
+ /**
234
+ * Looks for a resource in the output directories of a workspace module.
235
+ * If a directory containing the resource could not be found, the method will return null.
236
+ *
237
+ * @param testClassFileName classpath resource
238
+ * @param module workspace module
239
+ * @return output directory containing the resource or null, in case the resource could not be found
240
+ */
241
+ private static Path getTestClassesDirOrNull (String testClassFileName , WorkspaceModule module ) {
184
242
ArtifactSources testSources = module .getTestSources ();
185
- final String testClassFileName = ClassLoaderHelper
186
- .fromClassNameToResourceName (requiredTestClass .getName ());
187
243
if (testSources != null ) {
188
244
PathTree paths = testSources .getOutputTree ();
189
245
var testClassesDir = paths .apply (testClassFileName , PathTestHelper ::getRootOrNull );
@@ -202,21 +258,18 @@ public static Path getTestClassesLocation(Class<?> requiredTestClass, CuratedApp
202
258
}
203
259
}
204
260
}
205
-
206
261
// If we got to this point, fall back to the filesystem search
207
262
// This happens for maven source set scenarios
208
263
// TODO getSourceClassifiers() should return the source sets in the maven case, but currently does not - see BuildIT.testCustomTestSourceSets test
209
- return getTestClassesLocation (requiredTestClass );
210
-
264
+ return null ;
211
265
}
212
266
213
267
private static Path getRootOrNull (PathVisit visit ) {
214
268
if (visit == null ) {
215
269
// this path does not exist in this path tree
216
270
return null ;
217
- } else {
218
- return visit .getRoot ();
219
271
}
272
+ return visit .getRoot ();
220
273
}
221
274
222
275
public static void validateTestDir (Class <?> requiredTestClass , Path testClassesDir , WorkspaceModule module ) {
@@ -261,7 +314,7 @@ public static Path getAppClassLocationForTestLocation(Path testClassLocationPath
261
314
// we should replace only the last occurrence of the fragment
262
315
final int i = testClassLocation .lastIndexOf (e .getKey ());
263
316
final StringBuilder buf = new StringBuilder (testClassLocation .length ());
264
- buf .append (testClassLocation . substring ( 0 , i ) ).append (e .getValue ());
317
+ buf .append (testClassLocation , 0 , i ).append (e .getValue ());
265
318
if (i + e .getKey ().length () + 1 < testClassLocation .length ()) {
266
319
buf .append (testClassLocation .substring (i + e .getKey ().length ()));
267
320
}
@@ -362,10 +415,9 @@ public static boolean isTestClass(String className, ClassLoader classLoader, Pat
362
415
return testLocation .equals (Path .of (path ));
363
416
}
364
417
365
- private static boolean isInTestDir (URL resource ) {
366
- String path = toPath (resource ).toString ();
367
- return TEST_TO_MAIN_DIR_FRAGMENTS .keySet ().stream ()
368
- .anyMatch (path ::contains );
418
+ private static boolean isInTestDir (Path resource ) {
419
+ final String path = resource .toString ();
420
+ return TEST_DIRS .stream ().anyMatch (path ::contains );
369
421
}
370
422
371
423
private static Path toPath (URL resource ) {
0 commit comments