Slackbot
04/28/2023, 7:43 AMSebastian Schuberth
04/28/2023, 8:05 AMAdam
04/28/2023, 8:23 AMour integration tests sometimes skips runningDo you know why? Is this perhaps caused by Gradle noticing the inputs haven’t changed, so it skips the tests? (IntelliJ used to force the all tests to re-run, even if they hadn’t changed, but the latest version of IntelliJ will skip unchanged tests but it doesn’t display it in an intuitive manner)
Emil Kantis
04/28/2023, 8:25 AM> Task appintegrationTest FROM-CACHEThe inputs probably weren't changed, since
integrationTest
doesn't depend on main
but I would like to tell gradle that such dependency existsEmil Kantis
04/28/2023, 8:26 AMEmil Kantis
04/28/2023, 8:27 AM// Associate the "funTest" compilation with the "main" compilation to be able to access "internal" objects from
// functional tests.
kotlin.target.compilations.apply {
getByName("funTest").associateWith(getByName(KotlinCompilation.MAIN_COMPILATION_NAME))
}
I don't want to do this, since my integration tests should not be able to access anything from the main sourcesAdam
04/28/2023, 8:28 AMEmil Kantis
04/28/2023, 8:30 AMsrc/main
changed, yes 🙂Adam
04/28/2023, 8:30 AMEmil Kantis
04/28/2023, 8:31 AMdockerBuildImage
task which packages the application into a container imageAdam
04/28/2023, 8:31 AMEmil Kantis
04/28/2023, 8:32 AMcom.bmuschko.docker-spring-boot-application version 9.3.1
, which must've falsely thought the docker image was up to date..Adam
04/28/2023, 8:34 AMdockerBuildImage
task.Emil Kantis
04/28/2023, 8:35 AMAdam
04/28/2023, 8:35 AMdockerBuildImage
task is correctly wired up, so Gradle can fingerprint the inputs/outputs and skip the task if necessaryAdam
04/28/2023, 8:36 AMEmil Kantis
04/28/2023, 8:36 AM@Suppress("UNUSED_VARIABLE") // Gradle determines name of test suite by the name of the variable
val integrationTest by registering(JvmTestSuite::class) {
testType.set(TestSuiteType.INTEGRATION_TEST)
useJUnitJupiter()
dependencies {
}
targets {
all {
testTask.configure {
dependsOn(tasks.dockerBuildImage)
shouldRunAfter(test)
}
}
}
}
Emil Kantis
04/28/2023, 8:36 AMSebastian Schuberth
04/28/2023, 8:37 AMAdam
04/28/2023, 8:37 AMTask appintegrationTest FROM-CACHEwhat was the result of the dockerBuildImage task? Was it also FROM-CACHE, or something else?
Sebastian Schuberth
04/28/2023, 8:38 AMEmil Kantis
04/28/2023, 8:38 AM> Task :app:dockerBuildImage
Building image using context '/actions-runner/_work/my-service/my-service/app/build/docker'.
Using images 'my-service'.
> Task :app:compileTestKotlin
> Task :app:dockerBuildImage
Step 1/8 : FROM openjdk:17-slim
---> 37cb44321d04
Step 2/8 : LABEL maintainer=root
---> Running in 2ac9bae4fc41
Removing intermediate container 2ac9bae4fc41
---> 4053d7984046
Step 3/8 : WORKDIR /app
---> Running in 7824e4d9d78b
Removing intermediate container 7824e4d9d78b
---> 66775799fb5c
Step 4/8 : COPY libs libs/
---> 4ccb83a13379
Step 5/8 : COPY resources resources/
---> 07594ab8e36a
Step 6/8 : COPY classes classes/
---> da4d15a0b2cc
Step 7/8 : ENTRYPOINT ["java", "-XX:+UseContainerSupport", "-cp", "/app/resources:/app/classes:/app/libs/*", "com.company.my.app.MyApplicationKt"]
---> Running in 6686bad67dc7
Removing intermediate container 6686bad67dc7
---> 23f0960863ea
Step 8/8 : EXPOSE 8080
---> Running in 3e91ee0851f6
Removing intermediate container 3e91ee0851f6
---> df1ac9790427
Successfully built df1ac9790427
Successfully tagged my-service:latest
Created image with ID 'df1ac9790427'.
Adam
04/28/2023, 8:40 AMdependsOn(tasks.dockerBuildImage)
then the intergration test task should also run
Oh wait, Build Cache is enabled so if dockerBuildImage
doesn’t have an output file then Gradle does its magicSebastian Schuberth
04/28/2023, 8:40 AM--rerun
option, maybe just use that explicitly.Adam
04/28/2023, 8:46 AMdockerBuildImage
that contains the hash of the built docker image. If it has an output, then Gradle Build Cache will be able to kick in.
You can use a doLast {}
block for this. It has to be an output file, because that’s the only type of Gradle task output considered by Build Cache.
2. compute and add the Docker image hash as an input to the integrationTest task, so Gradle will re-run the task if the image hash changes. Something like
val integrationTest by registering(JvmTestSuite::class) {
targets.all {
testTask.configure {
val imageName = "my-image"
inputs.property("dockerImageHash",
providers.exec {
executable("docker")
args(parseSpaceSeparatedArgs("inspect --format='{{index .RepoDigests 0}}' $imageName"))
}.standardOutput.asText)
}
}
}
(Docker command from https://stackoverflow.com/q/32046334/4161471)Emil Kantis
04/28/2023, 8:59 AMEmil Kantis
04/28/2023, 11:31 AMtargets {
all {
testTask.configure {
dependsOn(tasks.dockerBuildImage)
shouldRunAfter(test)
inputs.files(fileTree("src/main")) // <-- Here
}
}
}
The docker image hash you suggested @Adam exited with exit code 64 for some reason, even though the command Gradle was trying to use worked when I invoke it directly. Perhaps permission issues..
Either way, I think the above solution has the added benefit that it only runs the integrationTests if the main sources change, even if the docker image is rebuilt. It's good in my case, cause something is off with the caching of the docker image task so it always runs, which would make my tests always run as well..Vampire
04/28/2023, 11:46 AMdependsOn
is not enough to have the docker image tasks output as input for up-to-date cache. Instead of using dependsOn
you would do inputs.files(tasks.dockerBuildImage)
. That way the outputfile of the task is considered when the fingerprint of the test task is calculated and you get the task dependency automatically.
The dockerBuildImage
task also has the id of the built image in a file as output file, also for it's own up-to-dateness, so actually just using that should already have worked.
Besides your problem that the docker image task is not up-to-date.Emil Kantis
04/28/2023, 11:49 AMEmil Kantis
04/28/2023, 11:50 AMdependsOn
only generate temporal dependency, without adding dependencies on the actual outputs from the task?Vampire
04/28/2023, 11:51 AMdependsOn
is a code-smell (except with a lifecycle task on the left-hand side)Vampire
04/28/2023, 11:52 AMEmil Kantis
04/28/2023, 11:53 AMAdam
04/28/2023, 2:02 PMexited with exit code 64 for some reasonpossibly
docker
is on your user path, but not the system path? 🤔 But I’m just guessing.Adam
04/28/2023, 2:08 PM./docker/
dir that contains the Dockerfile and docker-compose.yml (that’s only used for building the image)
Then I define a Gradle Sync task prepareDockerBuild
to sync requisite files into ./docker/build/
, so that it’s clear about what files are used for the building the Docker image.
And then a dockerBuild
exec task that runs docker-compose build
, with working dir set to ./docker/
.
If the prepareDockerBuild
task doesn’t sync any files, then Gradle will skip dockerBuild
task - which is faster than invoking Docker and waiting for Docker to figure out that nothing has changed.Adam
04/28/2023, 2:09 PMEmil Kantis
04/28/2023, 2:19 PMAdam
04/28/2023, 2:19 PMEmil Kantis
04/28/2023, 2:19 PMEmil Kantis
04/28/2023, 2:20 PMAdam
04/28/2023, 2:21 PMEmil Kantis
04/28/2023, 2:23 PMAdam
04/28/2023, 2:27 PMinputs.files(tasks.dockerBuildImage)
as suggested by @Vampire and with reproducible archives 🤞Emil Kantis
04/28/2023, 2:29 PMinputs.files(tasks.dockerBuildImage.get().imageIdFile)
, and then do a small change for the dockerBuildImage task to cache properly (by adding explicit tag, was using :latest implicitly)Adam
04/28/2023, 2:29 PMEmil Kantis
04/28/2023, 2:29 PMAdam
04/28/2023, 2:30 PMmap {}
instead of .get()
though - so the task isn’t configured unless it’s required
inputs.files(tasks.dockerBuildImage.map { it.imageIdFile})
Emil Kantis
04/28/2023, 2:30 PMVampire
04/28/2023, 2:30 PMVampire
04/28/2023, 2:31 PMEmil Kantis
04/28/2023, 2:31 PM- Task `:app:integrationTest` of type `org.gradle.api.tasks.testing.Test`: cannot serialize object of type 'com.bmuschko.gradle.docker.tasks.image.DockerBuildImage', a subtype of 'org.gradle.api.Task', as these are not supported with the configuration cache.
See <https://docs.gradle.org/7.6.1/userguide/configuration_cache.html#config_cache:requirements:task_access>
Vampire
04/28/2023, 2:32 PM> it’s a “why isn’t this enabled by default??” option
hehe 😄 yeah.. legacy reasons I suppose, like so many other things in Gradle..I'd say probably more what makes more sense. 😄 Many people would majorly complain if they loose their timestamps in the archive. And even without that option, the archive will be up-to-date if the inputs and outputs didn't change.
Vampire
04/28/2023, 2:33 PMGradle complained about this:Ah, okay, was not aware about that CC problem
Emil Kantis
04/28/2023, 2:34 PMConfiguration cache entry discarded due to serialization error.
Configuration cache state could not be cached: field `commandProvider` of `com.bmuschko.gradle.docker.tasks.image.Dockerfile$EntryPointInstruction` bean found in field `instructions` of task `:app:dockerCreateDockerfile` of type `com.bmuschko.gradle.docker.tasks.image.Dockerfile`: error writing value of type 'org.gradle.api.internal.provider.DefaultProvider'
Emil Kantis
04/28/2023, 2:34 PMVampire
04/28/2023, 2:35 PM