diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 97a7d2e44b..3d0f32e21b 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -340,5 +340,21 @@
+
+ native
+
+
+ com.google.cloud.storage.it.StorageNativeCanaryTest
+
+
+ *
+
+
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/StorageNativeCanaryTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/StorageNativeCanaryTest.java
new file mode 100644
index 0000000000..eefeda993f
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/StorageNativeCanaryTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * 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 com.google.cloud.storage.it;
+
+import static com.google.cloud.storage.TestUtils.assertAll;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.api.gax.paging.Page;
+import com.google.cloud.ReadChannel;
+import com.google.cloud.WriteChannel;
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.BlobId;
+import com.google.cloud.storage.BlobInfo;
+import com.google.cloud.storage.BucketInfo;
+import com.google.cloud.storage.DataGenerator;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.Storage.BlobListOption;
+import com.google.cloud.storage.Storage.BlobSourceOption;
+import com.google.cloud.storage.Storage.BlobWriteOption;
+import com.google.cloud.storage.TransportCompatibility.Transport;
+import com.google.cloud.storage.it.runner.StorageITRunner;
+import com.google.cloud.storage.it.runner.annotations.Backend;
+import com.google.cloud.storage.it.runner.annotations.CrossRun;
+import com.google.cloud.storage.it.runner.annotations.Inject;
+import com.google.cloud.storage.it.runner.registry.Generator;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.channels.Channels;
+import java.nio.channels.WritableByteChannel;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(StorageITRunner.class)
+@CrossRun(
+ backends = {Backend.PROD},
+ transports = {Transport.HTTP, Transport.GRPC})
+public final class StorageNativeCanaryTest {
+
+ private static final int _512KiB = 512 * 1024;
+ private static final int _256KiB = 256 * 1024;
+ private static final byte[] bytes = DataGenerator.base64Characters().genBytes(_512KiB);
+
+ @Inject public Storage storage;
+ @Inject public Generator generator;
+
+ /**
+ * When testing on Native Image, we're primarily wanting to verify the primary code paths are
+ * properly detected by the native image compiler.
+ *
+ *
For Storage, we have a few "primary code paths" we want to ensure are validated:
+ *
+ *
+ * - Can a (Unary) Request Succeed?
+ *
- Can a (ServerStream) Object Read Request Succeed?
+ *
- Can a (ClientStream) Object Write Request Succeed?
+ *
- Can a (Page over Unary) Paginated Request Succeed?
+ *
+ *
+ * To validate this, our happy path test is as follows:
+ *
+ *
+ * - Create a temporary bucket (Unary)
+ *
- Insert two (2) objects (Unary, ServerStream)
+ *
- List all objects, using a pageSize of 1 (Page over Unary)
+ *
- Read all bytes of each object (ServerStream)
+ *
- Delete each object (Unary)
+ *
- Delete temporary bucket (Unary)
+ *
+ */
+ @Test
+ public void canary_happyPath() throws Exception {
+ // create a temporary bucket
+ try (TemporaryBucket tempB =
+ TemporaryBucket.newBuilder()
+ .setStorage(storage)
+ .setBucketInfo(BucketInfo.of(generator.randomBucketName()))
+ .build()) {
+ String bucketName = tempB.getBucket().getName();
+
+ // insert 2 objects
+ BlobInfo info1 = BlobInfo.newBuilder(bucketName, generator.randomObjectName()).build();
+ BlobInfo info2 = BlobInfo.newBuilder(bucketName, generator.randomObjectName()).build();
+ uploadUsingWriter(info1);
+ uploadUsingWriter(info2);
+
+ // list objects
+ Page page = storage.list(bucketName, BlobListOption.pageSize(1));
+ List blobs = ImmutableList.copyOf(page.iterateAll());
+
+ // read all bytes of each object
+ List actual =
+ blobs.stream().map(this::readAll).collect(ImmutableList.toImmutableList());
+
+ List deletes =
+ blobs.stream()
+ .map(b -> storage.delete(b.getBlobId(), BlobSourceOption.generationMatch()))
+ .collect(ImmutableList.toImmutableList());
+
+ assertAll(
+ () -> {
+ List actualNames =
+ actual.stream()
+ .map(BlobWithContent::getInfo)
+ .map(BlobInfo::getBlobId)
+ .map(BlobId::getName)
+ .collect(ImmutableList.toImmutableList());
+
+ assertThat(actualNames).isEqualTo(ImmutableList.of(info1.getName(), info2.getName()));
+ },
+ () -> {
+ // if the content isn't equal carry it through
+ List contentNotEquals =
+ actual.stream()
+ .filter(bwc -> !Arrays.equals(bwc.getContent(), bytes))
+ .collect(ImmutableList.toImmutableList());
+ assertThat(contentNotEquals).isEmpty();
+ },
+ () -> assertThat(deletes.stream().anyMatch(isFalse())).isFalse());
+ }
+ }
+
+ private void uploadUsingWriter(BlobInfo info) throws IOException {
+ try (WriteChannel w = storage.writer(info, BlobWriteOption.doesNotExist())) {
+ // set our size to the smallest resumable size so we can send multiple requests
+ w.setChunkSize(_256KiB);
+ ByteStreams.copy(Channels.newChannel(new ByteArrayInputStream(bytes)), w);
+ }
+ }
+
+ private BlobWithContent readAll(BlobInfo info) {
+ try (ReadChannel r = storage.reader(info.getBlobId(), BlobSourceOption.generationMatch());
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ WritableByteChannel w = Channels.newChannel(baos)) {
+ // only buffer up to half the object
+ r.setChunkSize(_256KiB);
+ ByteStreams.copy(r, w);
+ return new BlobWithContent(info, baos.toByteArray());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static Predicate isFalse() {
+ return b -> !b;
+ }
+
+ private static final class BlobWithContent {
+ private final BlobInfo info;
+ private final byte[] content;
+
+ private BlobWithContent(BlobInfo info, byte[] content) {
+ this.info = info;
+ this.content = content;
+ }
+
+ public BlobInfo getInfo() {
+ return info;
+ }
+
+ public byte[] getContent() {
+ return content;
+ }
+ }
+}
diff --git a/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/native-image.properties b/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/native-image.properties
index 0bb3e83ecc..f3153a788a 100644
--- a/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/native-image.properties
+++ b/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/native-image.properties
@@ -5,29 +5,23 @@
# build time. Initializing these classes explicitly at build time results in a
# successful build.
Args = \
- --initialize-at-build-time=com.google.cloud.conformance.storage.v1,\
- com.google.protobuf,\
- com.google.auth.oauth2,\
- com.google.cloud.storage.conformance.retry,\
- com.google.common.base.Charsets,\
- com.google.gson.stream.JsonReader,\
+ --initialize-at-build-time=com.google.cloud.storage.it.StorageNativeCanaryTest,\
+ com.fasterxml.jackson,\
+ com.google.api.client.http,\
+ com.google.api.client.json,\
com.google.api.client.util,\
- com.google.api.client.http.javanet.NetHttpTransport,\
- com.google.api.client.http.HttpTransport,\
- com.google.api.client.json.JsonParser$1,\
- com.google.api.client.json.gson.GsonParser$1,\
- com.google.common.io.BaseEncoding,\
- com.google.common.math.IntMath$1,\
- com.google.common.collect.Platform,\
- com.google.gson.Gson,\
- com.google.common.truth,\
+ com.google.auth.oauth2,\
+ com.google.cloud.storage.it.runner,\
+ com.google.common.base,\
com.google.common.collect,\
- com.google.gson.internal.reflect,\
- com.google.gson.internal.bind,\
- com.google.gson.internal,\
- com.google.gson.internal.sql.SqlTypesSupport,\
- com.google.gson.FieldNamingPolicy$3,\
- com.google.gson.LongSerializationPolicy$2,\
+ com.google.common.io,\
+ com.google.common.math,\
+ com.google.common.truth,\
+ com.google.gson,\
+ com.google.protobuf,\
+ com.google.rpc,\
+ io.grpc,\
+ io.opencensus,\
net.jqwik
diff --git a/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json b/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json
index 06d0837d78..f6abb08d49 100644
--- a/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json
+++ b/google-cloud-storage/src/test/resources/META-INF/native-image/com/google/cloud/storage/reflect-config.json
@@ -16,7 +16,7 @@
"methods":[{"name":"","parameterTypes":[] }]}
,
{
- "name":"com.google.cloud.storage.conformance.retry.TestBench$RetryTestResource",
+ "name":"com.google.cloud.storage.it.runner.registry.TestBench$RetryTestResource",
"allDeclaredFields":true,
"methods":[{"name":"","parameterTypes":[] }]}
]