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: + * + *

+ * + * To validate this, our happy path test is as follows: + * + * + */ + @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":[] }]} ]