CacheConfigurationTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.maven.impl.cache;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.maven.api.Constants;
import org.apache.maven.api.RemoteRepository;
import org.apache.maven.api.Session;
import org.apache.maven.api.cache.CacheRetention;
import org.apache.maven.api.model.Profile;
import org.apache.maven.api.services.ModelBuilderRequest;
import org.apache.maven.api.services.ModelSource;
import org.apache.maven.api.services.ModelTransformer;
import org.apache.maven.api.services.Request;
import org.apache.maven.api.services.RequestTrace;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for cache configuration functionality.
*/
class CacheConfigurationTest {
@Mock
private Session session;
@Mock
private Request<?> request;
@Mock
private ModelBuilderRequest modelBuilderRequest;
private Map<String, String> userProperties;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
userProperties = new HashMap<>();
when(session.getUserProperties()).thenReturn(userProperties);
when(request.getSession()).thenReturn(session);
when(modelBuilderRequest.getSession()).thenReturn(session);
}
@Test
void testDefaultConfiguration() {
CacheConfig config = CacheConfigurationResolver.resolveConfig(request, session);
assertEquals(CacheRetention.REQUEST_SCOPED, config.scope());
assertEquals(Cache.ReferenceType.SOFT, config.referenceType());
}
@Test
void testParseSimpleSelector() {
String configString = "ModelBuilderRequest { scope: session, ref: hard }";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertEquals(1, selectors.size());
CacheSelector selector = selectors.get(0);
assertEquals("ModelBuilderRequest", selector.requestType());
assertNull(selector.parentRequestType());
assertEquals(CacheRetention.SESSION_SCOPED, selector.config().scope());
assertEquals(Cache.ReferenceType.HARD, selector.config().referenceType());
}
@Test
void testParseParentChildSelector() {
String configString = "ModelBuildRequest ModelBuilderRequest { ref: weak }";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertEquals(1, selectors.size());
CacheSelector selector = selectors.get(0);
assertEquals("ModelBuilderRequest", selector.requestType());
assertEquals("ModelBuildRequest", selector.parentRequestType());
assertNull(selector.config().scope()); // not specified
assertEquals(Cache.ReferenceType.WEAK, selector.config().referenceType());
}
@Test
void testParseWildcardSelector() {
String configString = "* ModelBuilderRequest { scope: persistent }";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertEquals(1, selectors.size());
CacheSelector selector = selectors.get(0);
assertEquals("ModelBuilderRequest", selector.requestType());
assertEquals("*", selector.parentRequestType());
assertEquals(CacheRetention.PERSISTENT, selector.config().scope());
assertNull(selector.config().referenceType()); // not specified
}
@Test
void testParseMultipleSelectors() {
String configString =
"""
ModelBuilderRequest { scope: session, ref: soft }
ArtifactResolutionRequest { scope: request, ref: hard }
* VersionRangeRequest { ref: weak }
""";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertEquals(3, selectors.size());
// Check first selector
CacheSelector first = selectors.get(0);
assertEquals("VersionRangeRequest", first.requestType());
assertEquals("*", first.parentRequestType());
// Check second selector
CacheSelector second = selectors.get(1);
assertEquals("ModelBuilderRequest", second.requestType());
assertNull(second.parentRequestType());
// Check third selector
CacheSelector third = selectors.get(2);
assertEquals("ArtifactResolutionRequest", third.requestType());
assertNull(third.parentRequestType());
}
@Test
void testConfigurationResolution() {
userProperties.put(Constants.MAVEN_CACHE_CONFIG_PROPERTY, "ModelBuilderRequest { scope: session, ref: hard }");
ModelBuilderRequest request = new TestRequestImpl();
CacheConfig config = CacheConfigurationResolver.resolveConfig(request, session);
assertEquals(CacheRetention.SESSION_SCOPED, config.scope());
assertEquals(Cache.ReferenceType.HARD, config.referenceType());
}
@Test
void testSelectorMatching() {
PartialCacheConfig config =
PartialCacheConfig.complete(CacheRetention.SESSION_SCOPED, Cache.ReferenceType.HARD);
CacheSelector selector = CacheSelector.forRequestType("ModelBuilderRequest", config);
ModelBuilderRequest request = new TestRequestImpl();
assertTrue(selector.matches(request));
}
@Test
void testInterfaceMatching() {
// Test that selectors match against implemented interfaces, not just class names
PartialCacheConfig config =
PartialCacheConfig.complete(CacheRetention.SESSION_SCOPED, Cache.ReferenceType.HARD);
CacheSelector selector = CacheSelector.forRequestType("ModelBuilderRequest", config);
// Create a test request instance that implements ModelBuilderRequest interface
TestRequestImpl testRequest = new TestRequestImpl();
// Should match because TestRequestImpl implements ModelBuilderRequest
assertTrue(selector.matches(testRequest));
// Test with a selector for a different interface
CacheSelector requestSelector = CacheSelector.forRequestType("Request", config);
assertTrue(requestSelector.matches(testRequest)); // Should match Request interface
}
// Test implementation class that implements ModelBuilderRequest
private static class TestRequestImpl implements ModelBuilderRequest {
@Override
public Session getSession() {
return null;
}
@Override
public RequestTrace getTrace() {
return null;
}
@Override
public RequestType getRequestType() {
return RequestType.BUILD_PROJECT;
}
@Override
public boolean isLocationTracking() {
return false;
}
@Override
public boolean isRecursive() {
return false;
}
@Override
public ModelSource getSource() {
return null;
}
@Override
public java.util.Collection<Profile> getProfiles() {
return java.util.List.of();
}
@Override
public java.util.List<String> getActiveProfileIds() {
return java.util.List.of();
}
@Override
public java.util.List<String> getInactiveProfileIds() {
return java.util.List.of();
}
@Override
public java.util.Map<String, String> getSystemProperties() {
return java.util.Map.of();
}
@Override
public java.util.Map<String, String> getUserProperties() {
return java.util.Map.of();
}
@Override
public RepositoryMerging getRepositoryMerging() {
return RepositoryMerging.POM_DOMINANT;
}
@Override
public java.util.List<RemoteRepository> getRepositories() {
return java.util.List.of();
}
@Override
public ModelTransformer getLifecycleBindingsInjector() {
return null;
}
}
@Test
void testInvalidConfiguration() {
String configString = "InvalidSyntax without braces";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertTrue(selectors.isEmpty());
}
@Test
void testEmptyConfiguration() {
String configString = "";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertTrue(selectors.isEmpty());
}
@Test
void testPartialConfigurationMerging() {
userProperties.put(
Constants.MAVEN_CACHE_CONFIG_PROPERTY,
"""
ModelBuilderRequest { scope: session }
* ModelBuilderRequest { ref: hard }
""");
ModelBuilderRequest request = new TestRequestImpl();
CacheConfig config = CacheConfigurationResolver.resolveConfig(request, session);
assertEquals(CacheRetention.SESSION_SCOPED, config.scope()); // from first selector
assertEquals(Cache.ReferenceType.HARD, config.referenceType()); // from second selector
}
@Test
void testPartialConfigurationScopeOnly() {
String configString = "ModelBuilderRequest { scope: persistent }";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertEquals(1, selectors.size());
CacheSelector selector = selectors.get(0);
assertEquals(CacheRetention.PERSISTENT, selector.config().scope());
assertNull(selector.config().referenceType());
// Test conversion to complete config
CacheConfig complete = selector.config().toComplete();
assertEquals(CacheRetention.PERSISTENT, complete.scope());
assertEquals(Cache.ReferenceType.SOFT, complete.referenceType()); // default
}
@Test
void testPartialConfigurationRefOnly() {
String configString = "ModelBuilderRequest { ref: weak }";
List<CacheSelector> selectors = CacheSelectorParser.parse(configString);
assertEquals(1, selectors.size());
CacheSelector selector = selectors.get(0);
assertNull(selector.config().scope());
assertEquals(Cache.ReferenceType.WEAK, selector.config().referenceType());
// Test conversion to complete config
CacheConfig complete = selector.config().toComplete();
assertEquals(CacheRetention.REQUEST_SCOPED, complete.scope()); // default
assertEquals(Cache.ReferenceType.WEAK, complete.referenceType());
}
@Test
void testPartialConfigurationMergeLogic() {
PartialCacheConfig base = PartialCacheConfig.withScope(CacheRetention.SESSION_SCOPED);
PartialCacheConfig override = PartialCacheConfig.withReferenceType(Cache.ReferenceType.HARD);
PartialCacheConfig merged = base.mergeWith(override);
assertEquals(CacheRetention.SESSION_SCOPED, merged.scope());
assertEquals(Cache.ReferenceType.HARD, merged.referenceType());
// Test override precedence
PartialCacheConfig override2 = PartialCacheConfig.complete(CacheRetention.PERSISTENT, Cache.ReferenceType.WEAK);
PartialCacheConfig merged2 = base.mergeWith(override2);
assertEquals(CacheRetention.SESSION_SCOPED, merged2.scope()); // base takes precedence
assertEquals(Cache.ReferenceType.WEAK, merged2.referenceType()); // from override2
}
@Test
void testParentInterfaceMatching() {
// Test that parent request matching works with interfaces
PartialCacheConfig config =
PartialCacheConfig.complete(CacheRetention.SESSION_SCOPED, Cache.ReferenceType.HARD);
CacheSelector selector = CacheSelector.forParentAndRequestType("ModelBuilderRequest", "Request", config);
// Create a child request with a parent that implements ModelBuilderRequest
TestRequestImpl parentRequest = new TestRequestImpl();
// Mock the trace to simulate parent-child relationship
RequestTrace parentTrace = mock(RequestTrace.class);
RequestTrace childTrace = mock(RequestTrace.class);
Request childRequest = mock(Request.class);
when(parentTrace.data()).thenReturn(parentRequest);
when(childTrace.parent()).thenReturn(parentTrace);
when(childRequest.getTrace()).thenReturn(childTrace);
// Should match because parent implements ModelBuilderRequest interface
assertTrue(selector.matches(childRequest));
}
}