org.gradle.internal.resolve.caching.CrossBuildCachingRuleExecutorTest.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.internal.resolve.caching
import com.google.common.collect.ImmutableMultimap
import org.gradle.api.Action
import org.gradle.api.Transformer
import org.gradle.api.artifacts.CacheableRule
import org.gradle.api.internal.artifacts.configurations.dynamicversion.CachePolicy
import org.gradle.cache.CacheBuilder
import org.gradle.cache.CacheDecorator
import org.gradle.cache.CacheRepository
import org.gradle.cache.PersistentCache
import org.gradle.cache.PersistentIndexedCache
import org.gradle.cache.internal.InMemoryCacheDecoratorFactory
import org.gradle.internal.action.DefaultConfigurableRule
import org.gradle.internal.action.DefaultConfigurableRules
import org.gradle.internal.action.InstantiatingAction
import org.gradle.internal.hash.Hashing
import org.gradle.internal.reflect.Instantiator
import org.gradle.internal.serialize.Serializer
import org.gradle.internal.service.ServiceRegistry
import org.gradle.internal.snapshot.ValueSnapshot
import org.gradle.internal.snapshot.ValueSnapshotter
import org.gradle.internal.snapshot.impl.StringValueSnapshot
import org.gradle.util.BuildCommencedTimeProvider
import org.gradle.util.TestUtil
import spock.lang.Specification
import spock.lang.Subject
import javax.inject.Inject
class CrossBuildCachingRuleExecutorTest extends Specification {
CacheRepository cacheRepository = Mock()
InMemoryCacheDecoratorFactory cacheDecoratorFactory = Mock()
ValueSnapshotter valueSnapshotter = Mock()
BuildCommencedTimeProvider timeProvider = new BuildCommencedTimeProvider()
PersistentIndexedCache> store = Mock()
CrossBuildCachingRuleExecutor.EntryValidator validator = Mock()
Transformer keyTransformer = new Transformer() {
@Override
Serializable transform(Id id) {
id.name
}
}
Transformer detailsToResult = new Transformer() {
@Override
Result transform(Details details) {
new Result(length: details.name.length())
}
}
Transformer onCacheMiss = new Transformer() {
@Override
Details transform(Id id) {
new Details(name: id.name)
}
}
CachePolicy cachePolicy = Mock()
ServiceRegistry serviceRegistry = Mock()
InstantiatingAction rule
Serializer resultSerializer = Mock()
Result result
@Subject
CrossBuildCachingRuleExecutor executor
Map services = [:]
Instantiator implicitInputsCapturingInstantiator = new ImplicitInputsCapturingInstantiator(serviceRegistry, TestUtil.instantiatorFactory()) {
@Override
def ImplicitInputsProvidingService findInputCapturingServiceByName(String name) {
services[name]
}
}
def setup() {
def cacheBuilder
cacheBuilder = Mock(CacheBuilder) {
withLockOptions(_) >> { cacheBuilder }
open() >> {
Mock(PersistentCache) {
createCache(_) >> {
store
}
}
}
}
cacheRepository.cache(_) >> cacheBuilder
cacheDecoratorFactory.decorator(_, _) >> Mock(CacheDecorator)
executor = new CrossBuildCachingRuleExecutor(
"test",
cacheRepository,
cacheDecoratorFactory,
valueSnapshotter,
timeProvider,
validator,
keyTransformer,
resultSerializer,
)
}
def "doesn't do anything if rule is null"() {
def id = new Id('Alicia')
when:
executor.execute(id, null, detailsToResult, onCacheMiss, cachePolicy)
then:
0 * _
}
def "executes the rule without caching if the rule is not cacheable"() {
withNonCacheableToUpperCaseRule()
def id = new Id('Alicia')
when:
execute(id)
then:
result.length == 6
0 * _
}
def "executes the rule on cache miss"() {
withToUpperCaseRule()
def id = new Id('Alicia')
when:
execute(id)
then:
1 * valueSnapshotter.snapshot(_) >> {
def snapshot = new StringValueSnapshot(it.toString())
def hasher = Hashing.newHasher()
snapshot.appendToHasher(hasher)
def keyHash = hasher.hash()
1 * store.put(keyHash, _)
snapshot
}
1 * store.get(_) >> null
0 * _
result.length == 6
}
void "validates entries on cache hit"() {
withToUpperCaseRule()
def id = new Id('Alicia')
when:
execute(id)
then:
1 * valueSnapshotter.snapshot(_) >> {
def snapshot = new StringValueSnapshot(it.toString())
snapshot
}
1 * store.get(_) >> Stub(CrossBuildCachingRuleExecutor.CachedEntry) {
getResult() >> new Result(length: 123)
}
1 * validator.isValid(_, _) >> true
0 * _
result.length == 123
}
void "if cache expired, re-executes the rule"() {
withToUpperCaseRule()
def snapshot
def keyHash
def id = new Id('Alicia')
when:
execute(id)
then:
1 * valueSnapshotter.snapshot(_) >> {
snapshot = new StringValueSnapshot(it.toString())
def hasher = Hashing.newHasher()
snapshot.appendToHasher(hasher)
keyHash = hasher.hash()
snapshot
}
1 * store.get(_) >> Stub(CrossBuildCachingRuleExecutor.CachedEntry) {
getResult() >> new Result(length: 123)
}
1 * validator.isValid(_, _) >> false
1 * store.put({ it == keyHash }, { it.timestamp == timeProvider.currentTime })
0 * _
result.length == 6
}
void "can expire entries based on implicit inputs"() {
withServiceInjectedRule()
def id = new Id('Alicia')
def implicitInput = Mock(ImplicitInputRecord)
def service = Mock(SomeService)
services['SomeService'] = service
when:
execute(id)
then:
1 * valueSnapshotter.snapshot(_) >> {
def snapshot = new StringValueSnapshot(it.toString())
snapshot
}
1 * store.get(_) >> Mock(CrossBuildCachingRuleExecutor.CachedEntry) {
getResult() >> new Result(length: 123)
getImplicits() >> ImmutableMultimap.builder()
.putAll("SomeService", implicitInput)
.build()
}
1 * implicitInput.getInput() >> '/foo/bar'
1 * implicitInput.getOutput() >> 'abcdef012'
1 * validator.isValid(cachePolicy, _) >> true
1 * service.isUpToDate('/foo/bar', 'abcdef012') >> false
1 * serviceRegistry.find(SomeService) >> service
1 * service.withImplicitInputRecorder(_) >> service
1 * store.put(_, { it.timestamp == timeProvider.currentTime })
0 * _
result.length == 6
}
void "can expire entries based on implicit inputs when multiple rules are used"() {
withServiceInjectedRules()
def id = new Id('Alicia')
def implicitInput1 = Mock(ImplicitInputRecord)
def implicitInput2 = Mock(ImplicitInputRecord)
def service = Mock(SomeService)
services['SomeService'] = service
when:
execute(id)
then:
1 * valueSnapshotter.snapshot(_) >> {
def snapshot = new StringValueSnapshot(it.toString())
snapshot
}
1 * store.get(_) >> Mock(CrossBuildCachingRuleExecutor.CachedEntry) {
getResult() >> new Result(length: 123)
getImplicits() >> ImmutableMultimap.builder()
.putAll("SomeService", implicitInput1, implicitInput2)
.build()
}
1 * implicitInput1.getInput() >> '/foo/bar'
1 * implicitInput1.getOutput() >> 'abcdef012'
1 * implicitInput2.getInput() >> '/foo/baz'
1 * implicitInput2.getOutput() >> 'abcdef987'
1 * validator.isValid(cachePolicy, _) >> true
1 * service.isUpToDate('/foo/bar', 'abcdef012') >> true
1 * service.isUpToDate('/foo/baz', 'abcdef987') >> false
2 * serviceRegistry.find(SomeService) >> service
2 * service.withImplicitInputRecorder(_) >> service
1 * store.put(_, { it.timestamp == timeProvider.currentTime })
0 * _
result.length == 6
}
void execute(Id id) {
def executionResult = executor.execute(id, rule, detailsToResult, onCacheMiss, cachePolicy)
result = executionResult
}
void withToUpperCaseRule() {
rule = new InstantiatingAction(DefaultConfigurableRules.of(DefaultConfigurableRule.of(
ToUpperCase
)), TestUtil.instantiatorFactory().decorateLenient(), shouldNotFail())
}
void withNonCacheableToUpperCaseRule() {
rule = new InstantiatingAction(DefaultConfigurableRules.of(DefaultConfigurableRule.of(
ToUpperCaseNotCached
)), TestUtil.instantiatorFactory().decorateLenient(), shouldNotFail())
}
void withServiceInjectedRule() {
rule = new InstantiatingAction(DefaultConfigurableRules.of(DefaultConfigurableRule.of(
WithServiceInjected
)), implicitInputsCapturingInstantiator, shouldNotFail())
}
void withServiceInjectedRules() {
def rule1 = DefaultConfigurableRule.of(
WithServiceInjected
)
def rule2 = DefaultConfigurableRule.of(
WithServiceInjected
)
rule = new InstantiatingAction(new DefaultConfigurableRules([rule1, rule2]), implicitInputsCapturingInstantiator, shouldNotFail())
}
InstantiatingAction.ExceptionHandler shouldNotFail() {
return new InstantiatingAction.ExceptionHandler() {
@Override
void handleException(Details target, Throwable throwable) {
throw new AssertionError("Expected the test not to fail, but it did", throwable)
}
}
}
static class Id {
final String name
Id(String name) {
this.name = name
}
}
static class Details {
String name
}
static class Result {
int length
}
@CacheableRule
static class ToUpperCase implements Action {
@Override
void execute(Details details) {
details.name = details.name.toUpperCase()
}
}
static class ToUpperCaseNotCached implements Action {
@Override
void execute(Details details) {
details.name = details.name.toUpperCase()
}
}
@CacheableRule
static class WithServiceInjected implements Action {
private final SomeService service
@Inject
WithServiceInjected(SomeService service){
this.service = service
}
@Override
void execute(Details details) {
details.name = details.name.toUpperCase()
}
}
interface SomeService extends ImplicitInputsProvidingService {
void serve()
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy