Skip to content

Commit 12ab618

Browse files
authored
Minor refactor of integration test classes (#2384)
This change promotes `CatalogConfig` and `RestCatalogConfig` to top-level, public annotations and introduces a few "hooks" in `PolarisRestCatalogIntegrationBase` that can be overridden by subclasses. This change is a preparatory work for #2280 (S3 remote signing).
1 parent 1fe6205 commit 12ab618

File tree

7 files changed

+265
-141
lines changed

7 files changed

+265
-141
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.polaris.service.it.env;
21+
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Inherited;
24+
import java.lang.annotation.Retention;
25+
import java.lang.annotation.RetentionPolicy;
26+
import java.lang.annotation.Target;
27+
import org.apache.polaris.core.admin.model.Catalog;
28+
29+
/**
30+
* Annotation to configure the catalog type and properties for integration tests.
31+
*
32+
* <p>This is a server-side setting; it is used to specify the Polaris Catalog type (e.g., INTERNAL,
33+
* EXTERNAL) and any additional properties required for the catalog configuration.
34+
*/
35+
@Retention(RetentionPolicy.RUNTIME)
36+
@Target({ElementType.TYPE, ElementType.METHOD})
37+
@Inherited
38+
public @interface CatalogConfig {
39+
40+
/** The type of the catalog. Defaults to INTERNAL. */
41+
Catalog.TypeEnum value() default Catalog.TypeEnum.INTERNAL;
42+
43+
/** Additional properties for the catalog configuration. */
44+
String[] properties() default {};
45+
}

integration-tests/src/main/java/org/apache/polaris/service/it/env/IcebergHelper.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.google.common.collect.ImmutableMap;
2222
import java.util.Map;
23+
import org.apache.iceberg.CatalogProperties;
2324
import org.apache.iceberg.rest.RESTCatalog;
2425
import org.apache.iceberg.rest.auth.OAuth2Properties;
2526

@@ -35,12 +36,8 @@ public static RESTCatalog restCatalog(
3536

3637
ImmutableMap.Builder<String, String> propertiesBuilder =
3738
ImmutableMap.<String, String>builder()
38-
.put(
39-
org.apache.iceberg.CatalogProperties.URI, endpoints.catalogApiEndpoint().toString())
39+
.put(CatalogProperties.URI, endpoints.catalogApiEndpoint().toString())
4040
.put(OAuth2Properties.TOKEN, authToken)
41-
.put(
42-
org.apache.iceberg.CatalogProperties.FILE_IO_IMPL,
43-
"org.apache.iceberg.inmemory.InMemoryFileIO")
4441
.put("warehouse", catalog)
4542
.put("header." + endpoints.realmHeaderName(), endpoints.realmId())
4643
.putAll(extraProperties);

integration-tests/src/main/java/org/apache/polaris/service/it/env/IntegrationTestsHelper.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@
1919
package org.apache.polaris.service.it.env;
2020

2121
import com.google.common.annotations.VisibleForTesting;
22+
import com.google.common.collect.ImmutableMap;
23+
import java.lang.annotation.Annotation;
24+
import java.lang.reflect.AnnotatedElement;
2225
import java.net.URI;
2326
import java.nio.file.Path;
27+
import java.util.Arrays;
28+
import java.util.Map;
2429
import java.util.function.Function;
30+
import java.util.stream.Stream;
31+
import org.junit.jupiter.api.TestInfo;
2532

2633
public final class IntegrationTestsHelper {
2734

@@ -54,4 +61,66 @@ static URI getTemporaryDirectory(Function<String, String> getenv, Path defaultLo
5461
envVar = envVar.startsWith("/") ? "file://" + envVar : envVar;
5562
return URI.create(envVar + "/").normalize();
5663
}
64+
65+
/**
66+
* Extract a value from the annotated elements of the test method and class.
67+
*
68+
* <p>This method looks for annotations of the specified type on both the test method and the test
69+
* class, extracts the value using the provided extractor function, and returns it.
70+
*
71+
* <p>If the value is present in both the method and class annotations, the value from the method
72+
* annotation will be used. If the value is not present in either the method or class annotations,
73+
* this method returns the default value.
74+
*/
75+
public static <A extends Annotation, T> T extractFromAnnotatedElements(
76+
TestInfo testInfo, Class<A> annotationClass, Function<A, T> extractor, T defaultValue) {
77+
return testInfo
78+
.getTestMethod()
79+
.map(AnnotatedElement.class::cast)
80+
.or(testInfo::getTestClass)
81+
.map(clz -> clz.getAnnotation(annotationClass))
82+
.map(extractor)
83+
.orElse(defaultValue);
84+
}
85+
86+
/**
87+
* Collect properties from annotated elements in the test method and class.
88+
*
89+
* <p>This method looks for annotations of the specified type on both the test method and the test
90+
* class, extracts properties using the provided extractor function, and combines them into a map.
91+
* If a property appears in both the method and class annotations, the value from the method
92+
* annotation will be used.
93+
*/
94+
public static <A extends Annotation> Map<String, String> mergeFromAnnotatedElements(
95+
TestInfo testInfo,
96+
Class<A> annotationClass,
97+
Function<A, String[]> propertiesExtractor,
98+
Map<String, String> defaults) {
99+
String[] methodProperties =
100+
testInfo
101+
.getTestMethod()
102+
.map(m -> m.getAnnotation(annotationClass))
103+
.map(propertiesExtractor)
104+
.orElse(new String[0]);
105+
String[] classProperties =
106+
testInfo
107+
.getTestClass()
108+
.map(clz -> clz.getAnnotation(annotationClass))
109+
.map(propertiesExtractor)
110+
.orElse(new String[0]);
111+
String[] properties =
112+
Stream.concat(Arrays.stream(methodProperties), Arrays.stream(classProperties))
113+
.toArray(String[]::new);
114+
if (properties.length % 2 != 0) {
115+
throw new IllegalArgumentException(
116+
"Properties must be in key-value pairs, but found an odd number of elements: "
117+
+ Arrays.toString(properties));
118+
}
119+
ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
120+
builder.putAll(defaults);
121+
for (int i = 0; i < properties.length; i += 2) {
122+
builder.put(properties[i], properties[i + 1]);
123+
}
124+
return builder.buildKeepingLast();
125+
}
57126
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.polaris.service.it.env;
21+
22+
import java.lang.annotation.ElementType;
23+
import java.lang.annotation.Inherited;
24+
import java.lang.annotation.Retention;
25+
import java.lang.annotation.RetentionPolicy;
26+
import java.lang.annotation.Target;
27+
28+
/**
29+
* Annotation to configure the REST catalog for integration tests.
30+
*
31+
* <p>This is a client-side setting; it is used to configure the client-side REST catalog that is
32+
* used in test code to connect to the Polaris REST API and to the storage layer.
33+
*/
34+
@Retention(RetentionPolicy.RUNTIME)
35+
@Target({ElementType.TYPE, ElementType.METHOD})
36+
@Inherited
37+
public @interface RestCatalogConfig {
38+
String[] value() default {};
39+
}

integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisPolicyServiceIntegrationTest.java

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@
2222
import static org.apache.polaris.service.it.env.PolarisClient.polarisClient;
2323
import static org.assertj.core.api.Assertions.assertThat;
2424

25-
import com.google.common.collect.ImmutableMap;
2625
import jakarta.ws.rs.client.Entity;
2726
import jakarta.ws.rs.core.Response;
28-
import java.lang.annotation.Retention;
29-
import java.lang.annotation.RetentionPolicy;
27+
import java.io.IOException;
3028
import java.lang.reflect.Method;
3129
import java.net.URI;
3230
import java.nio.file.Path;
@@ -64,13 +62,15 @@
6462
import org.apache.polaris.core.entity.CatalogEntity;
6563
import org.apache.polaris.core.policy.PredefinedPolicyTypes;
6664
import org.apache.polaris.core.policy.exceptions.PolicyInUseException;
65+
import org.apache.polaris.service.it.env.CatalogConfig;
6766
import org.apache.polaris.service.it.env.ClientCredentials;
6867
import org.apache.polaris.service.it.env.IcebergHelper;
6968
import org.apache.polaris.service.it.env.IntegrationTestsHelper;
7069
import org.apache.polaris.service.it.env.ManagementApi;
7170
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
7271
import org.apache.polaris.service.it.env.PolarisClient;
7372
import org.apache.polaris.service.it.env.PolicyApi;
73+
import org.apache.polaris.service.it.env.RestCatalogConfig;
7474
import org.apache.polaris.service.it.ext.PolarisIntegrationTestExtension;
7575
import org.apache.polaris.service.types.ApplicablePolicy;
7676
import org.apache.polaris.service.types.AttachPolicyRequest;
@@ -131,25 +131,10 @@ public class PolarisPolicyServiceIntegrationTest {
131131
private final String catalogBaseLocation =
132132
s3BucketBase + "/" + System.getenv("USER") + "/path/to/data";
133133

134-
private static final String[] DEFAULT_CATALOG_PROPERTIES = {
135-
"polaris.config.allow.unstructured.table.location", "true",
136-
"polaris.config.allow.external.table.location", "true"
137-
};
138-
139-
@Retention(RetentionPolicy.RUNTIME)
140-
private @interface CatalogConfig {
141-
Catalog.TypeEnum value() default Catalog.TypeEnum.INTERNAL;
142-
143-
String[] properties() default {
144-
"polaris.config.allow.unstructured.table.location", "true",
145-
"polaris.config.allow.external.table.location", "true"
146-
};
147-
}
148-
149-
@Retention(RetentionPolicy.RUNTIME)
150-
private @interface RestCatalogConfig {
151-
String[] value() default {};
152-
}
134+
private static final Map<String, String> DEFAULT_CATALOG_PROPERTIES =
135+
Map.of(
136+
"polaris.config.allow.unstructured.table.location", "true",
137+
"polaris.config.allow.external.table.location", "true");
153138

154139
@BeforeAll
155140
public static void setup(
@@ -188,28 +173,26 @@ public void before(TestInfo testInfo) {
188173
.setStorageType(StorageConfigInfo.StorageTypeEnum.S3)
189174
.setAllowedLocations(List.of("s3://my-old-bucket/path/to/data"))
190175
.build();
191-
Optional<PolarisPolicyServiceIntegrationTest.CatalogConfig> catalogConfig =
192-
Optional.ofNullable(
193-
method.getAnnotation(PolarisPolicyServiceIntegrationTest.CatalogConfig.class));
194176

195177
CatalogProperties.Builder catalogPropsBuilder = CatalogProperties.builder(catalogBaseLocation);
196-
String[] properties =
197-
catalogConfig
198-
.map(PolarisPolicyServiceIntegrationTest.CatalogConfig::properties)
199-
.orElse(DEFAULT_CATALOG_PROPERTIES);
200-
for (int i = 0; i < properties.length; i += 2) {
201-
catalogPropsBuilder.addProperty(properties[i], properties[i + 1]);
202-
}
178+
179+
Map<String, String> catalogProperties =
180+
IntegrationTestsHelper.mergeFromAnnotatedElements(
181+
testInfo, CatalogConfig.class, CatalogConfig::properties, DEFAULT_CATALOG_PROPERTIES);
182+
catalogPropsBuilder.putAll(catalogProperties);
183+
203184
if (!s3BucketBase.getScheme().equals("file")) {
204185
catalogPropsBuilder.addProperty(
205186
CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:");
206187
}
188+
189+
Catalog.TypeEnum catalogType =
190+
IntegrationTestsHelper.extractFromAnnotatedElements(
191+
testInfo, CatalogConfig.class, CatalogConfig::value, Catalog.TypeEnum.INTERNAL);
192+
207193
Catalog catalog =
208194
PolarisCatalog.builder()
209-
.setType(
210-
catalogConfig
211-
.map(PolarisPolicyServiceIntegrationTest.CatalogConfig::value)
212-
.orElse(Catalog.TypeEnum.INTERNAL))
195+
.setType(catalogType)
213196
.setName(currentCatalogName)
214197
.setProperties(catalogPropsBuilder.build())
215198
.setStorageConfigInfo(
@@ -221,26 +204,14 @@ public void before(TestInfo testInfo) {
221204

222205
managementApi.createCatalog(principalRoleName, catalog);
223206

224-
Optional<PolarisPolicyServiceIntegrationTest.RestCatalogConfig> restCatalogConfig =
225-
testInfo
226-
.getTestMethod()
227-
.flatMap(
228-
m ->
229-
Optional.ofNullable(
230-
m.getAnnotation(
231-
PolarisPolicyServiceIntegrationTest.RestCatalogConfig.class)));
232-
ImmutableMap.Builder<String, String> extraPropertiesBuilder = ImmutableMap.builder();
233-
restCatalogConfig.ifPresent(
234-
config -> {
235-
for (int i = 0; i < config.value().length; i += 2) {
236-
extraPropertiesBuilder.put(config.value()[i], config.value()[i + 1]);
237-
}
238-
});
207+
Map<String, String> restCatalogProperties =
208+
IntegrationTestsHelper.mergeFromAnnotatedElements(
209+
testInfo, RestCatalogConfig.class, RestCatalogConfig::value, Map.of());
239210

240211
String principalToken = client.obtainToken(principalCredentials);
241212
restCatalog =
242213
IcebergHelper.restCatalog(
243-
endpoints, currentCatalogName, extraPropertiesBuilder.build(), principalToken);
214+
endpoints, currentCatalogName, restCatalogProperties, principalToken);
244215
CatalogGrant catalogGrant =
245216
new CatalogGrant(CatalogPrivilege.CATALOG_MANAGE_CONTENT, GrantResource.TypeEnum.CATALOG);
246217
managementApi.createCatalogRole(currentCatalogName, CATALOG_ROLE_1);
@@ -253,8 +224,14 @@ public void before(TestInfo testInfo) {
253224
}
254225

255226
@AfterEach
256-
public void cleanUp() {
257-
client.cleanUp(adminToken);
227+
public void cleanUp() throws IOException {
228+
try {
229+
if (restCatalog != null) {
230+
restCatalog.close();
231+
}
232+
} finally {
233+
client.cleanUp(adminToken);
234+
}
258235
}
259236

260237
@Test

0 commit comments

Comments
 (0)