Classpath order of test/resources and test/classes while executing tests


(Etienne Studer) #1

I have a concern about the default classpath order when executing the test task. It seems that test/classes is added before test/resources. This can be a problem (and is a problem for us), since we use pattern matching expressions when defining resources to be loaded by Spring.

Example:

location=“classpath:/com/foo/FooServiceTest-*.bpmn20.xml”

Spring will not find these resources since it first finds the test/classes folder on the path which does not contain these xml files (they are located under test/resources).

I know that I could use classpath*:/… to patch this problem, but it is not really an option for us to change over a hundred xml configuration files while migrating from Maven to Gradle. Also, Gradle should not dictate how I have to define my pattern :wink:

I guess an easy solution is if we could fix (switch) the classpath order of test/classes and test/resources. Any suggestions on how we can achieve this? (a similar question has been posted by my colleague).

Regards, Etienne


(Peter Niederwieser) #2

I don’t understand why the order matters to Spring. Can you elaborate?


(Etienne Studer) #3

If you use a wildcard in a resource location, Spring (more precisely PathMatchingResourcePatternResolver.java) will determine the root folder of the given location and then scan all files under that root folder and then take those files that match the pattern.

Example: location=“classpath:/com/foo/FooServiceTest-*.bpmn20.xml”

–> root folder: /com/foo

Since that root folder also matches the package hierarchy of my Java classes, this root folder can be found under test/classes and under test/resources. Since test/classes comes first on the classpath when using Gradle, Spring will find the root folder test/classes/com/foo that contains my class files, and as a consequence, it will not find my xml files since they are under the test/resources/com/foo.

How can I change the classpath order of test/resources and test/classes?


(Peter Niederwieser) #4

You’ll have to manually configure the test runtime class path. Something like:

sourceSets {
    test {
        runtimeClasspath = files(output.resourcesDir) + files(output.classesDir) + sourceSets.main.output + configurations.testRuntime
      }
}

(Peter Niederwieser) #5

I’ve talked to the Spring folks. They believe that the order shouldn’t matter. Does this contradict what you are seeing?


(Etienne Studer) #6

Yes, the order does matter. I see it in my tests, I see it when I debug, and I see that it used to work before test resources were separated from test classes by Gradle.

Honestly, I don’t know how else I can describe the scenario that explains my problem. To me it seems pretty obvious: you have the same folder hierarchy on your classpath TWICE and Spring stops at the first location, which is test/classes/… and not test/resources/…

We will apply the patch you have posted above.


(Peter Niederwieser) #7

Interesting. Maybe a Spring bug? What I’ve been told is that Spring should scan all locations, even when they go by the same name.

Another solution on the Gradle side is to go back to using the same output directory for classes and resources:

sourceSets.test.output.resourcesDir = sourceSets.test.output.classesDir

(Peter Niederwieser) #8

Could you show exactly what you are doing (bean definition file etc.) so that we can try to reproduce with the Spring folks?


(Etienne Studer) #9

The key is that we use classpath:/com/foo… and not classpath*:/com/foo… (and changing my close to 100 config files to use classpath*:confused: just to make Gradle M8 happy is not feasible).

The Spring docs are very precise on the classpath:/ behavior and describe exactly the behavior I’m observing and expecting from Spring (so, everything good with Spring) - it’s just a classpath order issue with Gradle that is in my way:

Javadoc from PathMatchingResourcePatternResolver that describes exactly the scenario that I have:

WARNING: Ant-style patterns with “classpath:” resources are not guaranteed to find matching resources if the root package to search is available in multiple class path locations. This is because a resource such as

com/mycompany/package1/service-context.xml

may be in only one location, but when a path such as

classpath:com/mycompany/**/service-context.xml

is used to try to resolve it, the resolver will work off the (first) URL returned by getResource(“com/mycompany”);. If this base package node exists in multiple classloader locations, the actual end resource may not be underneath. Therefore, preferably, use “classpath*:” with the same Ant-style pattern in such a case, which will search all class path locations that contain the root package.


(Etienne Studer) #10

Yes, that is our current fix and works well. Though, I like the reordering patch you showed above better since it allows to still keep the resources separate from the class files.


(Peter Niederwieser) #11

In that case, one of the mentioned workarounds seems like the best option (until you are ready to change the config files with a global search & replace).


(Etienne Studer) #12

Yep.

Btw, the reason I do not want to change to classpath*:confused: is that one pays a performance penalty when you have many unit tests that each work on a new app context instance (i.e. the classpath scanning takes place repeatedly). We’ve observed that the more precise the pattern, the better the unit test performance (especially on local machines that might not be great at disk I/O).

I think this issue can now be considered ‘resolved’.


(Szczepan Faber) #13

See revised version of this workaround here: http://forums.gradle.org/gradle/topics/tests_arent_executed_when_setting_the_test_runtimeclasspath