Skip to content

Commit 56025e8

Browse files
committed
test: add new StorageNativeCanary test
Update native-image related config to be specific for this class. Update maven config to explicitly configure this new class as its only class for test when run under -Pnative
1 parent 4c469fc commit 56025e8

File tree

4 files changed

+198
-31
lines changed

4 files changed

+198
-31
lines changed

google-cloud-storage/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,5 +340,15 @@
340340
</dependency>
341341
</dependencies>
342342
</profile>
343+
<profile>
344+
<id>native</id>
345+
<properties>
346+
<!--
347+
Override the default match pattern for native image tests since we have a custom
348+
test specifically for native image validation.
349+
-->
350+
<test>com.google.cloud.storage.it.StorageNativeCanary</test>
351+
</properties>
352+
</profile>
343353
</profiles>
344354
</project>
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.storage.it;
18+
19+
import static com.google.cloud.storage.TestUtils.assertAll;
20+
import static com.google.common.truth.Truth.assertThat;
21+
22+
import com.google.api.gax.paging.Page;
23+
import com.google.cloud.ReadChannel;
24+
import com.google.cloud.WriteChannel;
25+
import com.google.cloud.storage.Blob;
26+
import com.google.cloud.storage.BlobId;
27+
import com.google.cloud.storage.BlobInfo;
28+
import com.google.cloud.storage.BucketInfo;
29+
import com.google.cloud.storage.DataGenerator;
30+
import com.google.cloud.storage.Storage;
31+
import com.google.cloud.storage.Storage.BlobListOption;
32+
import com.google.cloud.storage.Storage.BlobSourceOption;
33+
import com.google.cloud.storage.Storage.BlobWriteOption;
34+
import com.google.cloud.storage.StorageOptions;
35+
import com.google.common.collect.ImmutableList;
36+
import com.google.common.io.ByteStreams;
37+
import java.io.ByteArrayInputStream;
38+
import java.io.ByteArrayOutputStream;
39+
import java.io.IOException;
40+
import java.nio.channels.Channels;
41+
import java.nio.channels.WritableByteChannel;
42+
import java.util.Arrays;
43+
import java.util.List;
44+
import java.util.UUID;
45+
import java.util.function.Predicate;
46+
import org.junit.Test;
47+
48+
// Intentionally avoid StorageITRunner here. It touches lots of code at a semi-static level making
49+
// native-test have a hard time.
50+
public final class StorageNativeCanary {
51+
52+
private static final int _512KiB = 512 * 1024;
53+
private static final int _256KiB = 256 * 1024;
54+
private static final byte[] bytes = DataGenerator.base64Characters().genBytes(_512KiB);
55+
56+
@Test
57+
public void canary_happyPath_http() throws Exception {
58+
canary_happyPath(StorageOptions.http().build().getService());
59+
}
60+
61+
@Test
62+
public void canary_happyPath_grpc() throws Exception {
63+
canary_happyPath(StorageOptions.grpc().build().getService());
64+
}
65+
66+
/**
67+
* When testing on Native Image, we're primarily wanting to verify the primary code paths are
68+
* properly detected by the native image compiler.
69+
*
70+
* <p>For Storage, we have a few "primary code paths" we want to ensure are validated:
71+
*
72+
* <ul>
73+
* <li>Can a (Unary) Request Succeed?
74+
* <li>Can a (ServerStream) Object Read Request Succeed?
75+
* <li>Can a (ClientStream) Object Write Request Succeed?
76+
* <li>Can a (Page over Unary) Paginated Request Succeed?
77+
* </ul>
78+
*
79+
* To validate this, our happy path test is as follows:
80+
*
81+
* <ul>
82+
* <li>Create a temporary bucket (Unary)
83+
* <li>Insert two (2) objects (Unary, ServerStream)
84+
* <li>List all objects, using a pageSize of 1 (Page over Unary)
85+
* <li>Read all bytes of each object (ServerStream)
86+
* <li>Delete each object (Unary)
87+
* <li>Delete temporary bucket (Unary)
88+
* </ul>
89+
*/
90+
private static void canary_happyPath(Storage storage) throws Exception {
91+
// create a temporary bucket
92+
try (TemporaryBucket tempB =
93+
TemporaryBucket.newBuilder()
94+
.setStorage(storage)
95+
.setBucketInfo(BucketInfo.of("java-storage-grpc-" + UUID.randomUUID()))
96+
.build()) {
97+
String bucketName = tempB.getBucket().getName();
98+
String obj1Name = UUID.randomUUID().toString();
99+
String obj2Name = UUID.randomUUID().toString();
100+
101+
// insert 2 objects
102+
BlobInfo info1 = BlobInfo.newBuilder(bucketName, obj1Name).build();
103+
BlobInfo info2 = BlobInfo.newBuilder(bucketName, obj2Name).build();
104+
uploadUsingWriter(storage, info1);
105+
uploadUsingWriter(storage, info2);
106+
107+
// list objects
108+
Page<Blob> page = storage.list(bucketName, BlobListOption.pageSize(1));
109+
List<Blob> blobs = ImmutableList.copyOf(page.iterateAll());
110+
111+
// read all bytes of each object
112+
List<BlobWithContent> actual =
113+
blobs.stream()
114+
.map(info -> readAll(storage, info))
115+
.collect(ImmutableList.toImmutableList());
116+
117+
List<Boolean> deletes =
118+
blobs.stream()
119+
.map(b -> storage.delete(b.getBlobId(), BlobSourceOption.generationMatch()))
120+
.collect(ImmutableList.toImmutableList());
121+
122+
assertAll(
123+
() -> {
124+
List<String> actualNames =
125+
actual.stream()
126+
.map(BlobWithContent::getInfo)
127+
.map(BlobInfo::getBlobId)
128+
.map(BlobId::getName)
129+
.collect(ImmutableList.toImmutableList());
130+
131+
assertThat(actualNames).containsExactly(info1.getName(), info2.getName());
132+
},
133+
() -> {
134+
// if the content isn't equal carry it through
135+
List<BlobWithContent> contentNotEquals =
136+
actual.stream()
137+
.filter(bwc -> !Arrays.equals(bwc.getContent(), bytes))
138+
.collect(ImmutableList.toImmutableList());
139+
assertThat(contentNotEquals).isEmpty();
140+
},
141+
() -> assertThat(deletes.stream().anyMatch(isFalse())).isFalse());
142+
}
143+
}
144+
145+
private static void uploadUsingWriter(Storage storage, BlobInfo info) throws IOException {
146+
try (WriteChannel w = storage.writer(info, BlobWriteOption.doesNotExist())) {
147+
// set our size to the smallest resumable size, so we can send multiple requests
148+
w.setChunkSize(_256KiB);
149+
ByteStreams.copy(Channels.newChannel(new ByteArrayInputStream(bytes)), w);
150+
}
151+
}
152+
153+
private static BlobWithContent readAll(Storage storage, BlobInfo info) {
154+
try (ReadChannel r = storage.reader(info.getBlobId(), BlobSourceOption.generationMatch());
155+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
156+
WritableByteChannel w = Channels.newChannel(baos)) {
157+
// only buffer up to half the object
158+
r.setChunkSize(_256KiB);
159+
ByteStreams.copy(r, w);
160+
return new BlobWithContent(info, baos.toByteArray());
161+
} catch (IOException e) {
162+
throw new RuntimeException(e);
163+
}
164+
}
165+
166+
private static Predicate<Boolean> isFalse() {
167+
return b -> !b;
168+
}
169+
170+
private static final class BlobWithContent {
171+
private final BlobInfo info;
172+
private final byte[] content;
173+
174+
private BlobWithContent(BlobInfo info, byte[] content) {
175+
this.info = info;
176+
this.content = content;
177+
}
178+
179+
public BlobInfo getInfo() {
180+
return info;
181+
}
182+
183+
public byte[] getContent() {
184+
return content;
185+
}
186+
}
187+
}

google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/native-image.properties

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,4 @@
55
# build time. Initializing these classes explicitly at build time results in a
66
# successful build.
77
Args = \
8-
--initialize-at-build-time=com.google.cloud.conformance.storage.v1,\
9-
com.google.protobuf,\
10-
com.google.auth.oauth2,\
11-
com.google.cloud.storage.conformance.retry,\
12-
com.google.common.base.Charsets,\
13-
com.google.gson.stream.JsonReader,\
14-
com.google.api.client.util,\
15-
com.google.api.client.http.javanet.NetHttpTransport,\
16-
com.google.api.client.http.HttpTransport,\
17-
com.google.api.client.json.JsonParser$1,\
18-
com.google.api.client.json.gson.GsonParser$1,\
19-
com.google.common.io.BaseEncoding,\
20-
com.google.common.math.IntMath$1,\
21-
com.google.common.collect.Platform,\
22-
com.google.gson.Gson,\
23-
com.google.common.truth,\
24-
com.google.common.collect,\
25-
com.google.gson.internal.reflect,\
26-
com.google.gson.internal.bind,\
27-
com.google.gson.internal,\
28-
com.google.gson.internal.sql.SqlTypesSupport,\
29-
com.google.gson.FieldNamingPolicy$3,\
30-
com.google.gson.LongSerializationPolicy$2,\
31-
net.jqwik
32-
33-
8+
--initialize-at-build-time=net.jqwik

google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,4 @@
1414
"allDeclaredFields":true,
1515
"allDeclaredMethods":true,
1616
"methods":[{"name":"<init>","parameterTypes":[] }]}
17-
,
18-
{
19-
"name":"com.google.cloud.storage.conformance.retry.TestBench$RetryTestResource",
20-
"allDeclaredFields":true,
21-
"methods":[{"name":"<init>","parameterTypes":[] }]}
2217
]

0 commit comments

Comments
 (0)