Does anyone have experience with creating an "appl...
# community-support
s
Does anyone have experience with creating an "application" (based on the Gradle Application plugins) that does not actually run the application's
main
, but which uses
junit-platform-console
to create a distribution that, when run, executes all (functional) tests? In essence, I want to be able to run functional tests without Gradle.
v
Never tried, but where should be the problem? Depend on
junit-platform-console
and declare
org.junit.platform.console.ConsoleLauncher
as main class, that's it.
a
related issue, and one of the comments links to the JUnit repo which uses a custom JavaExec task to run the console launcher
👍 1
s
@Vampire there's a little bit more to it. For example, you also need to manually take care that test classes are included in the application, esp. when using non-standard test source sets, like
funTest
or
integTest
.
v
Yeah, well, you said you want to use the application plugin. Thus I assumed your test code is in the
main
source set and you have a dedicated project for those tests.
s
No, using the
application
plugin just seemed like the natural choice to me to create a new CLI Gradle module that puts all functional tests from all
funTest
source set from my multi-module project into its runtime classpath.
But I actually struggle to find the right idiomatic syntax to get all
funTest
classpaths from all projects in the multi-project in order to add them to the runtime classpath of the test launcher... any hints there?
v
Add a feature variant for the fun test source sets, then depend on that feature variant.
Basically like the java test fixtures plugin is doing for you for fixtures.
s
As I've never dealt with feature variants before, I'm trying to get something simpler to run, like
Copy code
configurations.dependencyScope("allTests")
configurations["runtimeOnly"].extendsFrom(configurations["allTests"])

rootProject.allprojects.forEach { project ->
    project.tasks.withType<Test>().matching { it.name == "funTest" }.configureEach {
        configurations["allTests"].asFileTree.plus(classpath)
    }
}
but this does not seem to find any tests.
v
NEVER try anything even similar to that, that is extremestly bad practice, unsafe, and not working reliably!
😅 1
Using the feature variant is the way to go and it should be quite easy for your case.
s
Coming back to this, while introducing a feature variant is indeed easy, I still struggle for find a way to depend on all subprojects that offer that specific feature / sourceSet. Here is what I did so far. But instead of
Copy code
implementation(project(":plugins:package-managers:node-package-manager")) {
        capabilities {
            // Note that this uses kebab-case although "registerFeature()" uses camelCase, see
            // <https://github.com/gradle/gradle/issues/31362>.
            @Suppress("UnstableApiUsage")
            requireFeature("fun-test")
        }
    }
I'd like an implementation dependency on all subprojects with that feature. Any hints?
v
Afair you cannot say "all that have such a feature", but you have to explicitly include what you need and want. Unless you for example use an artifact view which might ignore the absence (or in lenient mode any problem in resolving including download errors and so on). Or you make sure that all projects do have that feature, even if it produces an empty artifact and then just depend on all projects or sub projects.
s
As there are too many subproject to list them manually, and it also does not scale with adding new subprojects with that feature, I guess I'm back to an "artifact view" then.
Or you make sure that all projects do have that feature, even if it produces an empty artifact and then just depend on all projects or sub projects.
Ah, wait that's still an option. So how do I depend on all subprojects?
Just
implementation(rootProject.subprojects)
does not work.
ChatGPT suggests:
Copy code
afterEvaluate {
    rootProject.subprojects.findAll { subproject ->
        subproject != project &&
        subproject.plugins.hasPlugin('java-library')
    }.each { libraryProject ->
        dependencies {
            implementation project(libraryProject.path)
        }
    }
}
v
You know tath ChatGPT is not knowing what it talks about, don't you?
Do not reach into the model of another plugin, that is almost as bad as cross-project configuration
s
Yeah, just pasting it for reference 🙂
v
rootProject.subprojects.forEach { implementation(it) }
s
implementation(it)
does not compile (in Koltin DSL), but
implementation(project(it.path))
does, so I'll be using that.
v
it does
image.png
s
Ok, I should have been more precise: It does not when using `capabilities`:
v
Ah, yeah, that might be
s
Anyway, I think I got it working now with
Copy code
fun Project.hasSourceSet(name: String): Boolean {
    val javaExt = extensions.findByType(JavaPluginExtension::class.java) ?: return false
    return javaExt.sourceSets.findByName(name) != null
}

dependencies {
    rootProject.subprojects.filter { it != project && it.hasSourceSet("funTest") }.forEach {
        implementation(project(it.path)) {
            capabilities {
                // Note that this uses kebab-case although "registerFeature()" uses camelCase, see
                // <https://github.com/gradle/gradle/issues/31362>.
                @Suppress("UnstableApiUsage")
                requireFeature("fun-test")
            }
        }
    }
}
v
Again, don't do that
You are reaching into the other project's model and you should never do that just like you should not do cross-project configuration
s
You mean the
hasSourceSet
function? Otherwise the code should be the one that you suggested.
v
Exactly, it is reaching into the other project's model to get an extension and a source set of it.
s
Ok, I've replaced that with
it.projectDir.resolve("src/funTest").isDirectory
by now.
Not nice, but that should create no coupling.
👌 1