Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build cache support for reportScoverage tasks #74

Open
dsilvasc opened this issue May 8, 2018 · 25 comments
Open

build cache support for reportScoverage tasks #74

dsilvasc opened this issue May 8, 2018 · 25 comments

Comments

@dsilvasc
Copy link

dsilvasc commented May 8, 2018

The reportScoverage task runs on every ./gradlew check invocation, even when running it multiple times in sequence without any changes to the source.

Would it be possible to make it work with up-to-date checks and the gradle build cache?

> Task :testing:reportScoverage
Caching disabled for task ':testing:reportScoverage': Caching has not been enabled for the task
Task ':testing:reportScoverage' is not up-to-date because:
  Task has not declared any outputs despite executing actions.
Starting process 'command '/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/bin/java''. ... Command: /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/bin/java ... org.scoverage.SingleReportApp ...
[scoverage] Generating scoverage reports...
...
:testing:reportScoverage (Thread[Task worker for ':' Thread 3,5,main]) completed. Took 1.531 secs.
@maiflai
Copy link
Contributor

maiflai commented May 8, 2018

Please could you try again using version 2.2.0 of this plugin? I hope I've managed to configure it correctly.

Thanks,
Stu

@dsilvasc
Copy link
Author

Looks like the up-to-date check works:

> Task :common:concurrent:reportScoverage UP-TO-DATE
Skipping task ':common:concurrent:reportScoverage' as it is up-to-date.
:common:concurrent:reportScoverage (Thread[Task worker for ':',5,main]) completed. Took 0.003 secs.

Caching doesn't:

> Task :common:concurrent:reportScoverage UP-TO-DATE
Build cache key for task ':common:concurrent:reportScoverage' is 3d1438d43d16ad3b7b91d94f2a0bf6ae
Caching disabled for task ':common:concurrent:reportScoverage': Caching has not been enabled for the task

Also upgrading from 2.1.0 to 2.2.0 breaks the gradle ScalaStyle plugin:

> Task :common:concurrent:scalaStyle FAILED
Task ':common:concurrent:scalaStyle' is not up-to-date because:
  Task ':common:concurrent:scalaStyle' class path has changed from b2e5ddeb6fd334f3d63d0ac350925277 to 666042731c7bd00bfa95c6eb7157d7c7.
:common:concurrent:scalaStyle (Thread[Task worker for ':',5,main]) completed. Took 0.058 secs.

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':common:concurrent:scalaStyle'.
> scala/Product$class

* Try:
Run with --debug option to get more log output. Run with --scan to get full insights.

* Exception is:
org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':common:concurrent:scalaStyle'.
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:103)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:73)
        at org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter.execute(OutputDirectoryCreatingTaskExecuter.java:51)
        at org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter.execute(SkipUpToDateTaskExecuter.java:59)
        at org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter.execute(ResolveTaskOutputCachingStateExecuter.java:54)
        at org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter.execute(ValidatingTaskExecuter.java:59)
        at org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter.execute(SkipEmptySourceFilesTaskExecuter.java:101)
        at org.gradle.api.internal.tasks.execution.FinalizeInputFilePropertiesTaskExecuter.execute(FinalizeInputFilePropertiesTaskExecuter.java:44)
        at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:91)
        at org.gradle.api.internal.tasks.execution.ResolveTaskArtifactStateTaskExecuter.execute(ResolveTaskArtifactStateTaskExecuter.java:62)
        at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:59)
        at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:54)
        at org.gradle.api.internal.tasks.execution.ExecuteAtMostOnceTaskExecuter.execute(ExecuteAtMostOnceTaskExecuter.java:43)
        at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:34)
        at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker$1.run(DefaultTaskGraphExecuter.java:256)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:317)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:309)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:185)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:97)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:249)
        at org.gradle.execution.taskgraph.DefaultTaskGraphExecuter$EventFiringTaskWorker.execute(DefaultTaskGraphExecuter.java:238)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:104)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker$1.execute(DefaultTaskPlanExecutor.java:98)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.execute(DefaultTaskExecutionPlan.java:663)
        at org.gradle.execution.taskgraph.DefaultTaskExecutionPlan.executeWithTask(DefaultTaskExecutionPlan.java:596)
        at org.gradle.execution.taskgraph.DefaultTaskPlanExecutor$TaskExecutorWorker.run(DefaultTaskPlanExecutor.java:98)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63)
        at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55)
        at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoClassDefFoundError: scala/Product$class
        at scala.xml.NamespaceBinding.<init>(NamespaceBinding.scala:24)
        at scala.xml.TopScope$.<init>(TopScope.scala:17)
        at scala.xml.TopScope$.<clinit>(TopScope.scala)
        at scala.xml.factory.XMLLoader$class.loadXML(XMLLoader.scala:40)
        at scala.xml.XML$.loadXML(XML.scala:60)
        at scala.xml.factory.XMLLoader$class.loadFile(XMLLoader.scala:50)
        at scala.xml.XML$.loadFile(XML.scala:60)
        at org.scalastyle.ScalastyleConfiguration$.readFromXml(ScalastyleConfiguration.scala:87)
        at org.scalastyle.ScalastyleConfiguration.readFromXml(ScalastyleConfiguration.scala)
        at org.scalastyle.ScalastyleConfiguration$readFromXml.call(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
        at org.github.ngbinh.scalastyle.ScalaStyleTask.scalaStyle(ScalaStyleTask.groovy:80)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:73)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:46)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:39)
        at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:26)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:794)
        at org.gradle.api.internal.AbstractTask$TaskActionWrapper.execute(AbstractTask.java:761)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter$1.run(ExecuteActionsTaskExecuter.java:124)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:317)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor$RunnableBuildOperationWorker.execute(DefaultBuildOperationExecutor.java:309)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.execute(DefaultBuildOperationExecutor.java:185)
        at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:97)
        at org.gradle.internal.operations.DelegatingBuildOperationExecutor.run(DelegatingBuildOperationExecutor.java:31)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeAction(ExecuteActionsTaskExecuter.java:113)
        at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeActions(ExecuteActionsTaskExecuter.java:95)
        ... 32 more
Caused by: java.lang.ClassNotFoundException: scala.Product$class
        at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 64 more

My buildSrc/build.gradle.kts has this:

repositories {
  // https://github.com/ngbinh/gradle-scalastyle-plugin/issues/39
  maven {
    name = "ngbinh"
    url = uri("https://dl.bintray.com/ngbinh/maven")
  }
  // https://plugins.gradle.org/plugin/org.scoverage
  maven {
    name = "gradle-plugins"
    url = uri("https://plugins.gradle.org/m2/")
  }
}

dependencies {
  "implementation"("org.github.ngbinh.scalastyle:gradle-scalastyle-plugin_2.12:1.0.1")
  "implementation"("gradle.plugin.org.scoverage:gradle-scoverage:2.2.0")
}

And my root build.gradle.kts includes this:

val scoverageVersion = "1.3.1"
val scalaRelease = "2.12.6"

val scalaLibrary = "org.scala-lang:scala-library:$scalaRelease"
val scalaCompiler = "org.scala-lang:scala-compiler:$scalaRelease"
val scalaReflect = "org.scala-lang:scala-reflect:$scalaRelease"

// Don't manipulate versions for gradle internals: https://github.com/typesafehub/zinc/issues/87
val blacklistConfigs = setOf("zinc", "checkstyle", "scoverage", "scalaStyle")

subprojects {
  apply<org.gradle.api.plugins.scala.ScalaPlugin>()
  apply<org.github.ngbinh.scalastyle.ScalaStylePlugin>()
  apply<org.scoverage.ScoveragePlugin>()

  configurations.all {
    if (!blacklistConfigs.contains(name)) {
      resolutionStrategy {
        preferProjectModules()
        // Enforce consistent third-party package versions across all projects in this repository.
        // This should avoid issues where a common module is built against one version of a third-party library,
        // then deployed as part of a service to run against a different version.
        force(scalaLibrary)
        force(scalaCompiler)
        force(scalaReflect)
      }
    }
  }
  dependencies {
    "compile"(scalaLibrary)
    "compile"(scalaCompiler)
    "compile"(scalaReflect)
    "scoverage"("org.scoverage:scalac-scoverage-plugin_2.12:$scoverageVersion")
    "scoverage"("org.scoverage:scalac-scoverage-runtime_2.12:$scoverageVersion")
  }
}

Running gradle a second time yields the same error as ngbinh/gradle-scalastyle-plugin#38

@maiflai
Copy link
Contributor

maiflai commented May 10, 2018

Caching doesn't

Sorry, I didn't realise they were different things. I think it's a case of just adding an annotation here, so I'll see about getting that done.

Also upgrading from 2.1.0 to 2.2.0 breaks the gradle ScalaStyle plugin

Interesting, given the other conversation about this plugin trying not to modify the Gradle classpath.

I expect that you should not be adding this build plugin to the implementation configuration?

i.e. remove this line implementation ("gradle.plugin.org.scoverage:gradle-scoverage:2.2.0")

@dsilvasc
Copy link
Author

Thanks for looking into this.

The line implementation ("gradle.plugin.org.scoverage:gradle-scoverage:2.2.0") is in buildSrc so it's visible to custom plugins (it behaves like buildscript dependencies).

Here's a minimal repro: https://github.com/dsilvasc/repro-gradle-scoverage-74

I think the issue is caused by the gradle-scoverage dependency on org.scoverage:scalac-scoverage-plugin_2.11:1.1.1 in line 47 here:

scoverage "org.scoverage:scalac-scoverage-plugin_2.11:1.1.1"

If I exclude that, then the problem goes away.

@maiflai
Copy link
Contributor

maiflai commented May 10, 2018

Ah, that's odd.

The dependency in my build.gradle is essentially an api dependency for the plugin; the implementation is intended to be sourced from the scoverage configuration in the consumer's project.

The pom published to maven central doesn't have any dependency: http://search.maven.org/remotecontent?filepath=org/scoverage/gradle-scoverage/2.2.0/gradle-scoverage-2.2.0.pom

The pom published to the plugins repo looks entiely different:
https://plugins.gradle.org/m2/gradle/plugin/org/scoverage/gradle-scoverage/2.2.0/gradle-scoverage-2.2.0.pom

I'll see if I can find out why tomorrow, but for the moment please use maven central?

Thanks,
Stu.

@dsilvasc
Copy link
Author

Thanks, that works too :)

@maiflai
Copy link
Contributor

maiflai commented May 11, 2018

2.3.0 published to the plugin portal with cachable tasks and without dependencies - please would you be able to have a look?

Thanks,
Stu

@dsilvasc
Copy link
Author

@maiflai Sorry for the delay here -- I've upgraded to gradle.plugin.org.scoverage:gradle-scoverage:2.3.0 and caching works for most tasks:

> Task :common:concurrent:compileScala FROM-CACHE
> Task :common:concurrent:processResources NO-SOURCE
> Task :common:concurrent:classes UP-TO-DATE
> Task :common:concurrent:compileScoverageJava NO-SOURCE
> Task :common:concurrent:compileScoverageScala FROM-CACHE
> Task :common:concurrent:processScoverageResources NO-SOURCE
> Task :common:concurrent:scoverageClasses UP-TO-DATE
> Task :common:concurrent:compileTestJava NO-SOURCE
> Task :common:concurrent:compileTestScala FROM-CACHE
> Task :common:concurrent:processTestResources NO-SOURCE
> Task :common:concurrent:testClasses UP-TO-DATE
> Task :common:concurrent:compileTestScoverageJava NO-SOURCE
> Task :common:concurrent:compileTestScoverageScala FROM-CACHE
> Task :common:concurrent:processTestScoverageResources NO-SOURCE
> Task :common:concurrent:testScoverageClasses UP-TO-DATE
> Task :common:concurrent:testScoverage
> Task :common:concurrent:reportScoverage FROM-CACHE

It just didn't work for testScoverage:

> Task :common:concurrent:testScoverage
Build cache key for task ':common:concurrent:testScoverage' is 5c8c3036187a20d226721614b82904b2
Caching disabled for task ':common:concurrent:testScoverage': Gradle does not know how file 'build/scoverage/scoverage.coverage.xml' was created (output property '$1'). Task output caching requires exclusive access to output paths to guarantee correctness.
Task ':common:concurrent:testScoverage' is not up-to-date because:
  Output property '$1' file common/concurrent/build/scoverage/scoverage.measurements.11 has been removed.
  Output property '$2' file common/concurrent/build/scoverage/scoverage.measurements.11 has been removed.
  Output property 'binResultsDir' file common/concurrent/build/test-results/testScoverage/binary has been removed.
Starting process 'Gradle Test Executor 5'. Working directory: common/concurrent Command: /Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/bin/java -Djava.security.manager=worker.org.gradle.process.internal.worker.child.BootstrapSecurityManager -Dorg.gradle.native=false -Dfile.encoding=UTF-8 -Duser.country=US -Duser.language=en -Duser.variant -ea -cp ~/.gradle/caches/4.10/workerMain/gradle-worker.jar worker.org.gradle.process.internal.worker.GradleWorkerMain 'Gradle Test Executor 5'
Successfully started process 'Gradle Test Executor 5'
Finished generating test XML results (0.001 secs) into: common/concurrent/build/test-results/testScoverage
Generating HTML test report...
Finished generating test html results (0.003 secs) into: common/concurrent/build/reports/tests/testScoverage
:common:concurrent:testScoverage (Thread[Task worker for ':',5,main]) completed. Took 1.419 secs.
:common:concurrent:reportScoverage (Thread[Task worker for ':',5,main]) started.

@maiflai
Copy link
Contributor

maiflai commented Aug 31, 2018

Hi,

Compilation writes the scoverage.coverage.xml; it contains the statement identifiers.

Test writes a set of measurement files which reference the statement identifiers.

It looks like Gradle is complaining that the test task claims to own the data directory, but the scoverage.coverage.xml file was created by something else.

I'll see if I can work around it.

@maiflai
Copy link
Contributor

maiflai commented Sep 9, 2018

Please could you re-test this with the new 2.4.0 release?

Thanks,
Stu

@maiflai
Copy link
Contributor

maiflai commented Nov 27, 2018

Please can I close this issue?

@dsilvasc
Copy link
Author

Hi Stu,

Sorry for missing the comment about 2.4.0. I just tried it and this is what I get:

> Task :common:concurrent:testScoverage
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property nonEmptyCandidateClassFiles (RelativePathInput) for task ':common:concurrent:testScoverage'
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property org.gradle.api.internal.tasks.NonCacheableTaskOutputPropertySpec@4c0c82ab for task ':common:concurrent:testScoverage'
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property binResultsDir (Output) for task ':common:concurrent:testScoverage'
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property reports.enabledReports.html.destination (Output) for task ':common:concurrent:testScoverage'
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property reports.enabledReports.junitXml.destination (Output) for task ':common:concurrent:testScoverage'
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property reports.html.destination (Output) for task ':common:concurrent:testScoverage'
09:00:41.508 [DEBUG] [org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository] Fingerprinting property reports.junitXml.destination (Output) for task ':common:concurrent:testScoverage'
09:00:41.509 [INFO] [org.gradle.api.internal.tasks.execution.ResolveBuildCacheKeyExecuter] Build cache key for task ':common:concurrent:testScoverage' is b869dcd46c4983a587861b0d5f65c3f7
09:00:41.509 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationExecutor] Completing Build operation 'Snapshot task inputs for :common:concurrent:testScoverage'
09:00:41.509 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationExecutor] Build operation 'Snapshot task inputs for :common:concurrent:testScoverage' completed
09:00:41.509 [INFO] [org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter] Caching disabled for task ':common:concurrent:testScoverage': Declares multiple output files for the single output property '$1' via `@OutputFiles`, `@OutputDirectories` or `TaskOutputs.files()`
09:00:41.509 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] Determining if task ':common:concurrent:testScoverage' is up-to-date
09:00:41.509 [INFO] [org.gradle.api.internal.tasks.execution.SkipUpToDateTaskExecuter] Task ':common:concurrent:testScoverage' is not up-to-date because:
  Output property 'binResultsDir' file common/concurrent/build/test-results/testScoverage/binary has been removed.
  Output property 'binResultsDir' file common/concurrent/build/test-results/testScoverage/binary/output.bin has been removed.
  Output property 'binResultsDir' file common/concurrent/build/test-results/testScoverage/binary/output.bin.idx has been removed.
09:00:41.509 [DEBUG] [org.gradle.api.internal.tasks.execution.SkipCachedTaskExecuter] Determining if task ':common:concurrent:testScoverage' is cached already
09:00:41.509 [DEBUG] [org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter] Ensuring parent directory exists for property org.gradle.api.internal.tasks.NonCacheableTaskOutputPropertySpec@4c0c82ab at common/concurrent/build/scoverage/scoverage.measurements.*
09:00:41.509 [DEBUG] [org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter] Ensuring directory exists for property binResultsDir (Output) at common/concurrent/build/test-results/testScoverage/binary
09:00:41.509 [DEBUG] [org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter] Ensuring directory exists for property reports.enabledReports.html.destination (Output) at common/concurrent/build/reports/tests/testScoverage
09:00:41.510 [DEBUG] [org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter] Ensuring directory exists for property reports.enabledReports.junitXml.destination (Output) at common/concurrent/build/test-results/testScoverage
09:00:41.510 [DEBUG] [org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter] Ensuring directory exists for property reports.html.destination (Output) at common/concurrent/build/reports/tests/testScoverage
09:00:41.510 [DEBUG] [org.gradle.api.internal.tasks.execution.OutputDirectoryCreatingTaskExecuter] Ensuring directory exists for property reports.junitXml.destination (Output) at common/concurrent/build/test-results/testScoverage
09:00:41.510 [DEBUG] [org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter] Executing actions for task ':common:concurrent:testScoverage'.
09:00:41.510 [DEBUG] [org.gradle.internal.operations.DefaultBuildOperationExecutor] Build operation 'Execute executeTests for :common:concurrent:testScoverage' started

@dsilvasc
Copy link
Author

dsilvasc commented Nov 28, 2018

Note that Gradle is removing the restriction on how many output files can be listed for a cacheable task in version 5.0:

https://docs.gradle.org/5.0-rc-2/release-notes.html#plural-task-output-properties-don't-disable-caching-anymore

Plural task output properties don't disable caching anymore
When using @OutputFiles or @OutputDirectories with an Iterable type, Gradle used to disable caching for the task with the following message:

Declares multiple output files for the single output property 'outputFiles' via @OutputFiles, @OutputDirectories or TaskOutputs.files()

This is no longer the case, and using such properties doesn't prevent the task from being cached. The only remaining reason to disable caching for the task is if the output contains file trees.

The log I included in the previous comment was from Gradle 4.10.

@dsilvasc
Copy link
Author

dsilvasc commented Nov 28, 2018

@dsilvasc
Copy link
Author

Not sure if the behavior on Gradle 4.10 is related to this in ScoverageExtension:

            t.tasks[ScoveragePlugin.TEST_NAME].outputs.upToDateWhen { extension.dataDir.listFiles(measurementFile) }

Note also that outputs.upToDateWhen { ... } is tricky and doesn't necessarily re-run tests: https://blog.gradle.org/stop-rerunning-tests

It will only mark the task out-of-date, forcing Gradle to recreate the output. But here’s the thing, if the build cache is enabled, Gradle doesn’t need to run the task to recreate the output. It will find an entry in the cache and unpack the result into the test’s output directory.

@maiflai
Copy link
Contributor

maiflai commented Nov 28, 2018

Ah, ok.

I might need to take another look at specifying wildcards to match the measurement files; my last attempt with a .* broke on Windows.

The task outputs can only be determined after the task has run (and this gets very messy when you try to aggregate coverage across modules).

@wolfs
Copy link

wolfs commented Oct 30, 2019

Regarding the upToDateWhen on the test task: Why is this needed? I expect that running with coverage is an input to the Test task, so if it ran without coverage and coverage is enabled afterwards then it well re-executed already. There shouldn't be a need to check whether the coverage file exists. That would also make it work well with the build cache.

@eyalroth
Copy link
Contributor

@wolfs I'm coming late into this, but I believe you're right.

I don't see much point in configuring the upToDateWhen of Test tasks, as these tasks are expected to always execute when they appear on the task graph, regardless of previous results.

In general, I don't think there's any need for a cache configuration specific for this plugin. The instrumentation is made via the scala compiler, and it has its own "cache mechanism" (aka incremental compilation). Other than the compiler and the tests, there's only the aggregation and check tasks, but they are incredibly fast in comparison to the other tasks, so I don't see much point in caching them.

@maiflai
Copy link
Contributor

maiflai commented Nov 5, 2019

I think this was introduced in response to #29 (comment), but it was some time ago.

I seem to recall that we have an directory for outputs that is shared between two tasks:

  1. compile writes the file that describes the measurements (and wipes the actual measurements)
  2. test writes the measurements

Gradle didn't handle this very well, but things may have changed since then.

Test tasks, as these tasks are expected to always execute when they appear on the task graph, regardless of previous results.

I'm not sure I agree here. Personally I like unit tests to skip execution when no inputs have changed. This said, I do often mark tests that execute against external systems to run whenever they are included in the graph.

@eyalroth
Copy link
Contributor

Test tasks, as these tasks are expected to always execute when they appear on the task graph, regardless of previous results.

I'm not sure I agree here. Personally I like unit tests to skip execution when no inputs have changed. This said, I do often mark tests that execute against external systems to run whenever they are included in the graph.

I was under the impression that whenever I gradle test my tests will run always, but that's not true. I guess I never did try to re-run tests without changing some input.

Still, I'm not sure the additional upToDateWhen is in due here. I believe the default definition of Test tasks is enough to detect whether a test should re-run or not. This additional configuration also causes tests which are not expected to yield any measurements to re-run all the time (say, a test of a class that was excluded from coverage).

@maiflai
Copy link
Contributor

maiflai commented Nov 10, 2019

I guess I never did try to re-run tests without changing some input.

I often run the root test task and let Gradle decide which of multiple modules need re-testing.

This additional configuration also causes tests which are not expected to yield any measurements to re-run all the time (say, a test of a class that was excluded from coverage).

Perhaps - but I think this is a worst-case scenario where a Test task has no dependency on instrumented classes? Or has Gradle's Runner caught up with SBT and test-quick such that it will only re-run individual tests which have been recompiled?

The instrumentation is made via the scala compiler, and it has its own "cache mechanism" (aka incremental compilation)

It always used the be the case that the scoverage compiler plugin wiped the measurement directory, breaking incremental compilation.

@eyalroth
Copy link
Contributor

I often run the root test task and let Gradle decide which of multiple modules need re-testing.

Right, me too.

Perhaps - but I think this is a worst-case scenario where a Test task has no dependency on instrumented classes? Or has Gradle's Runner caught up with SBT and test-quick such that it will only re-run individual tests which have been recompiled?

I don't know about the test-quick feature, but indeed the test tasks depend on the instrumented classes. Thing is, there's no difference between instrumented classes and normal classes; they are both the output of a compilation task, it's just that one of them use an additional compiler plugin. And of course, Gradle already knows not to run tests that didn't recompile.

This is not such a rare case to have tests that do not depend on instrumented classes. One more example would be java modules in a hybrid multi-module project which are covered by a Java coverage tool (like cobertura).

In fact, I'm not even sure there's a use-case where the builtin dependency mechanism misses an opportunity to not re-run tests while the reliance on the measurement files does. So in that sense, it seems redundant to have the additional upToDateWhen.

It always used the be the case that the scoverage compiler plugin wiped the measurement directory, breaking incremental compilation.

You mean the instrumented classes or the measurements collected by the tests? That would be weird if it deleted the measurements, but in such a case I believe gradle will decide to re-run the tests anyhow since a new compilation has been made (otherwise how did the compiler plugin delete the files?).

@maiflai
Copy link
Contributor

maiflai commented Nov 11, 2019

https://github.com/scoverage/scalac-scoverage-plugin/blob/master/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala#L119 shows that the plugin wipes the measurement directory when it recompiles.

Looking back I think the problem was that we originally declared the measument directory to be an output of the instrumentation task.

This measurement directory then changed when the tests were run, which triggered a recompilation, and so on.

The current code declares that only the measurement index is an output of the instrumentation task while measurement files are outputs of the test task.

I'm not sure how the build cache can work properly if we do not accurately state the outputs of the tasks? I think the next version of the scalac plugin uses these measurement files for reporting, rather than the statically named XML files.

@eyalroth
Copy link
Contributor

https://github.com/scoverage/scalac-scoverage-plugin/blob/master/scalac-scoverage-plugin/src/main/scala/scoverage/plugin.scala#L119 shows that the plugin wipes the measurement directory when it recompiles.

Indeed, it seems to delete the measurement files regardless if the source code changed or not. The plugin would normally execute only when the source did change, but one might also explicitly run it via compileScoverageScala (and without changing the source), in which case it will delete the measurements for no good reason.

Interestingly enough, trying to execute only the scoverage compilation results in a build failure:

> ./gradlew clean
> ./gradlew reportScoverage
> ./gradlew compileScoverageScala
> ./gradlew reportScoverage 
Task compileTestScala FAILED
BUILD FAILED in 14s

This is a bug that may be unrelated to this issue, though.

I would argue that the scalac plugin should not delete the measurement files at all, even if the source code did change. It's a confusing side-effect, as noted in scoverage/scalac-scoverage-plugin#67. In comparison, normal Java / Scala compilation never deletes test results.

The test tasks should perhaps declare the index file as an additional input, but I believe that would only serve as a sort of a "good practice" rather than actually making any difference. If I'm not mistaken, the scalac plugin either changes both the index file and the instrumented classes (at least one), or doesn't change any of them; i.e, there's no case when one changes and the other doesn't. Given that the classes are already declared as input for the test tasks, declaring the index as an input as well will affect nothing.

@AceHack
Copy link

AceHack commented May 1, 2020

Any update, this causes extremely long build times.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants