-
Notifications
You must be signed in to change notification settings - Fork 2
Add support for running GradlePluginTests with configuration-cache #245
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
base: develop
Are you sure you want to change the base?
Conversation
Generate changelog in
|
| .withEnvironment(gradleRunner.getEnvironment()) | ||
| .withArguments(ImmutableList.<String>builder() | ||
| .addAll(gradleRunner.getArguments()) | ||
| .add("--dry-run") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for config-cache to be reused, we need to run the same tasks. In order to avoid running the same tasks twice, we can pass a --dry-run.
Tested that config cache is triggered by a dry-run:
Reusing configuration cache.
:compileJava SKIPPED
:help SKIPPED
BUILD SUCCESSFUL in 62ms
Configuration cache entry reused.
| assertConfigCacheReused(configurationCacheResult.output()); | ||
| } | ||
|
|
||
| return result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
always returning the result of the first invocation.
| public final class GradleInvocation { | ||
| private final GradleRunner gradleRunner; | ||
| private final GradleVersion gradleVersion; | ||
| private final boolean configurationCacheEnabled; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than taking a boolean and doing if statements, can we instead try and change GradleInvoker and GradleInvocation into interfaces, and have a BasicGradleInvoker and a ConfigurationCacheGradleInvoker (likely the same for GradleInvocation too). The configuration cache versions should just be able to call the basic versions as many times as they need - we might be able to avoid copying the code for creating the GradleRunner again on line 50 too.
| final class ConfigurationCacheTest { | ||
|
|
||
| @Test | ||
| void testConfigurationCache(GradleInvoker invoker, RootProject rootProject) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test methods should not start with test, it's superfluous. Additionally, test names should be a snake_case_english_sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually think we should make an errorprone check for this, to ensure that people do this for our Gradle plugins tests.
|
|
||
| // Run a second time to verify the cache is reused, we don't need to run the tasks --dry-run is enough to | ||
| // check if the configuration cache was used. | ||
| GradleRunner configurationCacheRunner = GradleRunner.create() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen cases where the second run doesn't reuse the cache but the subsequent do - normally related to file generation. I'm not sure if that behaviour is ever valid so the solution might just be that we want to fail those cases but could also be nice to have some kind of ratchet - although in those edge cases we could just get users to write there CC tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure that was the case here https://pl.ntr/2xX - but its possible I also just didn't fix it properly!
| record MyClass() {} | ||
| """); | ||
|
|
||
| InvocationResult result = invoker.withArgs("compileJava", "help").buildsSuccessfully(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this is currently testing?
In terms of tests, I think we could have at least a few tests:
- In the build.gradle, print whether the configuration cache is enabled and check it is disabled when
@WithConfigurationCacheis not present, and is enabled when present. You can use the start parameter / configuration cache build feature. This actually raises a good questions - we might not want to run with configuration on Gradle 7, as the implementation and error messages are bad. - Fails when there is a basic configuration cache error eg executing a process at configuration time
- Fails when there is an issue storing the cache entry
- Fails when there is an issue loading the cache entry
- Succeeds when there are no configuration cache issues
| "Running @WithConfigurationCache: Expected configuration cache entry to be reused, but it" | ||
| + " wasn't. Output: %s", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, especially for the second invocation, we need a really good error message that is very explicit in what happened. We need to tell the dev that first invocation worked and stored the cache entry, but then we ran again with --dry-run to verify that loading the cache still worked and this failed, and how they should diagnose it (look at the report? build scan? can we parse these from the output and put it in the error message?)
| File configurationCacheDir = new File(gradleRunner.getProjectDir(), ".gradle/configuration-cache"); | ||
| assertThat(configurationCacheDir.exists()) | ||
| .as(String.format( | ||
| "Running @WithConfigurationCache: Expected the configuration-cache %s directory to exits.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/exits/exist
| @Target(ElementType.TYPE) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @ExtendWith(ConfigurationCacheExtension.class) | ||
| public @interface WithConfigurationCache {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of actually rolling this out, not sure how I feel about opting in (like this with @WithConfigurationCache) vs a global opt in at the Gradle extesion level and opting out with @ConfigurationCacheDisabled. Or even further - default having configuration cache on without any Gradle configuration and relying on a disable annotation.
My worry with the opting in is people will just forget to add it to new tests and we want to ensure all new code/test code is configuration cache compatible.
But in order for upgrades to not get blocked with opt out, we need to work out a way to feed back test failures to the excavator which will enable it to add @ConfigurationCacheDisabled onto the relevant tests/test classes. Opt-in, it's a little easier as you can make a parameterised excavator that tries to opt in each test class, then the excavator will just not merge if it doesn't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thinking was to add this flag to all tests that are extending ConfigurationCacheSpec example here. This way we can do the migration of the nebula tests -> new java framework 1-1.
Then I think it would be pretty easy to add an extension similar to how we set the Gradle versions and make it by default using the CC and add a workflow to add the @ConfigurationCacheDisabled. But I am thinking that would be a separate migration from the nebula test -> new Java framework. wdyt ?
5233348 to
60d02f2
Compare
✅ Successfully generated changelog entry!Need to regenerate?Simply interact with the changelog bot comment again to regenerate these entries. 📋Changelog Preview💡 Improvements
|
| // `withDebug(true)` will run the Gradle tooling inside the same JVM as the test, meaning | ||
| // debugging works, whereas `withDebug(false)` will run Gradle in a new daemon. There can be | ||
| // differences between these two modes! | ||
| return ManagementFactory.getRuntimeMXBean().getInputArguments().stream() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FLUP: cc is not compatible with testkit's debugging option see issue: gradle/issues/25846
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, we should probably just disable configuration cache when debugging and paste a warning log line that we've done so in each test. This is what we do in suppressible-error-prone, would be great to get it in the framework directly.
| assertThat(gradle.withArgs().buildsSuccessfully().output()).contains("project name: root"); | ||
| InvocationResult invocation = gradle.withArgs().buildsSuccessfully(); | ||
| assertThat(invocation.output()).contains("hello from :"); | ||
| assertThat(invocation.output()).contains("project name: root"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've enabled cc here. Previously we were running the tasks twice, which means that cc will not run the second time - hence the second run will not contain the expected output lines. Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, interesting! I have a feeling this more than anything may break existing tests that use printlns at configuration time a lot (though I guess they should be doing it within task actions if they were good).
I wonder if we wanted tests to be more or less configuration cache agnostic whether we delete the configuration cache dir after checking it can be reloaded with --dry-run. I'm not sure if it's a good idea, but it would still check configuration cache but run the configuration every time. Feels a little dodgy though, maybe we just keep it as an option in case converting existing tests is hard?
|
|
||
| tasks.named('test', Test) { | ||
| // by default the tests should run with configuration cache enabled. | ||
| systemProperty 'com.palantir.gradle.testing.configuration_cache_enabled', 'true' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once we publish this version, I'll add the isConfigurationCacheEnabled = true in the gradleTestutils extension
| import com.palantir.gradle.testing.RestrictedCreation; | ||
| import java.nio.file.Path; | ||
|
|
||
| public record ConfigurationCacheInvoker(Path projectDir, GradleInvoker gradleInvoker) implements GradleInvoker { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make this a regular class so we can hide .gradleInvoker() etc
...ing-junit/src/main/java/com/palantir/gradle/testing/execution/ConfigurationCacheInvoker.java
Show resolved
Hide resolved
| @Override | ||
| public void beforeAll(ExtensionContext context) { | ||
| setConfigurationCache(context); | ||
| } | ||
|
|
||
| @Override | ||
| public void beforeEach(ExtensionContext context) { | ||
| setConfigurationCache(context); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's safer to just lazily compute whether configuration cache is enabled or not in the store rather than setting them here - can't forget to set it in all the relevant locations then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess that's a bit strange, as you need to look at the enclosing elements to find configuration cache, and how would we handle a @ConfigurationCacheDisabled arg on a test when there's a @WithConfigurationCache on the test class...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how would we handle a @ConfigurationCacheDisabled arg on a test when there's a @WithConfigurationCache on the test class...
I removed this class in the latest commits. I only kept the @DisableConfigurationCache
...n-testing-junit/src/test/java/com/palantir/gradle/testing/junit/ConfigurationCacheTests.java
Outdated
Show resolved
Hide resolved
...-junit/src/main/java/com/palantir/gradle/testing/execution/ConfigurationCacheInvocation.java
Show resolved
Hide resolved
...unit/src/test/java/com/palantir/gradle/testing/ete/GradleVersionsFromJunitParameterTest.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds configuration cache support to GradlePluginTests, enabling tests to verify that Gradle plugins are compatible with configuration cache functionality. The implementation provides both the ability to enable configuration cache by default and to selectively disable it for incompatible tests.
- Introduces configuration cache support with automatic verification of cache storage and reuse
- Adds
@DisabledConfigurationCacheannotation for excluding incompatible tests - Refactors the invoker architecture to support both standard and configuration cache execution modes
Reviewed Changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
ConfigurationCacheHelper.java |
Defines system property constant for configuration cache enablement |
PluginTestingPlugin.java |
Adds system property configuration for tests to use configuration cache setting |
PluginTestingExtension.java |
Adds configurationCacheEnabled property with default false value |
| Multiple test files | Comprehensive test coverage for configuration cache functionality and edge cases |
GradleInvoker.java |
Converts from class to interface with factory method for invoker selection |
DefaultGradleInvoker.java |
Implements standard Gradle invocation without configuration cache |
ConfigurationCacheInvoker.java |
Implements configuration cache-aware Gradle invocation with verification |
| Exception classes | New exception hierarchy for handling configuration cache and invocation failures |
| JUnit integration files | Extensions and stores for managing configuration cache state in test context |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
...it/src/main/java/com/palantir/gradle/testing/junit/ConfigurationCacheParameterExtension.java
Outdated
Show resolved
Hide resolved
...ing-junit/src/main/java/com/palantir/gradle/testing/execution/ConfigurationCacheInvoker.java
Outdated
Show resolved
Hide resolved
…testing/junit/ConfigurationCacheParameterExtension.java Co-authored-by: Copilot <[email protected]>
…testing/execution/ConfigurationCacheInvoker.java Co-authored-by: Copilot <[email protected]>
…le-plugin-testing into cr/tests-with-config-cache
| @Override | ||
| public InvocationResult buildsWithFailure() { | ||
| InvocationResult result = initialGradleInvocation.buildsWithFailure(); | ||
| assertConfigCacheStored(result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if it fails at configuration time, before executing any tasks? I think this is allowed by buildsWithFailure but the configuration cache entry won't be stored. Should have a test for it at least. Probably want to only check this if a task was executed/it was a task that failed.
| public InvocationResult buildsWithFailure() { | ||
| InvocationResult result = initialGradleInvocation.buildsWithFailure(); | ||
| assertConfigCacheStored(result); | ||
| return result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we actually want to check we can load configuration cache here if it was a task that failed. If the configuration was fine but a task failed, it should be fine to run with --dry-run (I think?)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth writing a test for.
| @Override | ||
| public GradleInvocation withArgs(String... args) { | ||
| // not reusing configuration-cache among multiple gradle invocations in a test. | ||
| cleanupConfigurationCache(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we do this here or after a gradle invocation? I guess this might be a bit safer.
Before this PR
Groovy Test spec that runs with Configuration Cache enabled.
After this PR
Ability to run GradlePluginTests with "--configuration-cache" enabled when the extension properrty
gradleTestUtils.isConfigurationCacheEnabledis set.Based on the value of the configuration cache, we will instantiate a
DefaultGradleInvokeror aConfigurationCacheInvoker.The
ConfigurationCacheInvokerwill do:--configuration-cacheadded to the list of arguments. It will throw anUnexpectedConfigurationCacheFailureexception if the configuration cache was not stored/failed.--configuration-cache&--dry-runto make sure the configuration cache was reused. It will throw anUnexpectedConfigurationCacheFailureif not.Added a new exception type
UnexpectedInvocationResultwith the correspondingUnexpectedInvocationFailure&UnexpectedInvocationSuccessthat are wrappers on top of gradle testkit's exceptionsUnexpectedBuildFailure&UnexpectedBuildSuccess.Added a new
DisabledConfigurationCachewhich will disable per test class/method the configuration-cache. This will be useful later when we are migrating the GradlePluginTests to use by default the configuration-cache and we will need to disable the tests that are incompatible with cc.==COMMIT_MSG==
Ability to run GradlePluginTests with configuration-cache.
==COMMIT_MSG==
Possible downsides?