diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java index 194c9d217..cb718e35f 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java @@ -108,9 +108,11 @@ static BaseServiceException coalesce(Throwable t) { * @returns {@code StorageException} */ public static StorageException translate(IOException exception) { - if (exception.getMessage().contains("Connection closed prematurely")) { - return new StorageException( - 0, exception.getMessage(), CONNECTION_CLOSED_PREMATURELY, exception); + String message = exception.getMessage(); + if (message != null + && (message.contains("Connection closed prematurely") + || message.contains("Premature EOF"))) { + return new StorageException(0, message, CONNECTION_CLOSED_PREMATURELY, exception); } else { // default return new StorageException(exception); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index 70f349329..05086abc2 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -24,6 +24,7 @@ import com.google.api.client.googleapis.batch.BatchRequest; import com.google.api.client.googleapis.batch.json.JsonBatchCallback; import com.google.api.client.googleapis.json.GoogleJsonError; +import com.google.api.client.googleapis.media.MediaHttpDownloader; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.EmptyContent; import com.google.api.client.http.GenericUrl; @@ -283,7 +284,7 @@ public void onFailure(GoogleJsonError googleJsonError, HttpHeaders httpHeaders) } private static StorageException translate(IOException exception) { - return new StorageException(exception); + return StorageException.translate(exception); } private static StorageException translate(GoogleJsonError exception) { @@ -750,10 +751,14 @@ public long read( } else { req.setReturnRawInputStream(false); } - req.getMediaHttpDownloader().setBytesDownloaded(position); - req.getMediaHttpDownloader().setDirectDownloadEnabled(true); + + if (position > 0) { + req.getRequestHeaders().setRange(String.format("bytes=%d-", position)); + } + MediaHttpDownloader mediaHttpDownloader = req.getMediaHttpDownloader(); + mediaHttpDownloader.setDirectDownloadEnabled(true); req.executeMedia().download(outputStream); - return req.getMediaHttpDownloader().getNumBytesDownloaded(); + return mediaHttpDownloader.getNumBytesDownloaded(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java index ad5653cff..587577a5b 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/DefaultRetryHandlingBehaviorTest.java @@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.fail; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.io.JsonEOFException; @@ -96,6 +97,7 @@ public void validateBehavior() { } else if (shouldRetry && !defaultShouldRetryResult && !legacyShouldRetryResult) { actualBehavior = Behavior.SAME; message = "both are rejecting when we want a retry"; + fail(message); } else if (shouldRetry && defaultShouldRetryResult && legacyShouldRetryResult) { actualBehavior = Behavior.SAME; message = "both are allowing"; @@ -111,6 +113,7 @@ public void validateBehavior() { } else if (!shouldRetry && defaultShouldRetryResult && legacyShouldRetryResult) { actualBehavior = Behavior.SAME; message = "both are too permissive"; + fail(message); } else if (!shouldRetry && defaultShouldRetryResult && !legacyShouldRetryResult) { actualBehavior = Behavior.DEFAULT_MORE_PERMISSIBLE; message = "default is too permissive"; @@ -298,7 +301,7 @@ enum ThrowableCategory { STORAGE_EXCEPTION_GOOGLE_JSON_ERROR_503(new StorageException(C.JSON_503)), STORAGE_EXCEPTION_GOOGLE_JSON_ERROR_504(new StorageException(C.JSON_504)), STORAGE_EXCEPTION_SOCKET_TIMEOUT_EXCEPTION(new StorageException(C.SOCKET_TIMEOUT_EXCEPTION)), - STORAGE_EXCEPTION_SOCKET_EXCEPTION(new StorageException(C.SOCKET_EXCEPTION)), + STORAGE_EXCEPTION_SOCKET_EXCEPTION(StorageException.translate(C.SOCKET_EXCEPTION)), STORAGE_EXCEPTION_SSL_EXCEPTION(new StorageException(C.SSL_EXCEPTION)), STORAGE_EXCEPTION_SSL_EXCEPTION_CONNECTION_SHUTDOWN( new StorageException(C.SSL_EXCEPTION_CONNECTION_SHUTDOWN)), @@ -322,6 +325,9 @@ enum ThrowableCategory { "connectionClosedPrematurely", "connectionClosedPrematurely", C.CONNECTION_CLOSED_PREMATURELY)), + STORAGE_EXCEPTION_0_CONNECTION_CLOSED_PREMATURELY_IO_CAUSE_NO_REASON( + StorageException.translate(C.CONNECTION_CLOSED_PREMATURELY)), + STORAGE_EXCEPTION_0_IO_PREMATURE_EOF(StorageException.translate(C.IO_PREMATURE_EOF)), EMPTY_JSON_PARSE_ERROR(new IllegalArgumentException("no JSON input found")), JACKSON_EOF_EXCEPTION(C.JACKSON_EOF_EXCEPTION), STORAGE_EXCEPTION_0_JACKSON_EOF_EXCEPTION( @@ -400,6 +406,7 @@ private static final class C { new JsonEOFException(null, JsonToken.VALUE_STRING, "parse-exception"); private static final MalformedJsonException GSON_MALFORMED_EXCEPTION = new MalformedJsonException("parse-exception"); + private static final IOException IO_PREMATURE_EOF = new IOException("Premature EOF"); private static HttpResponseException newHttpResponseException( int httpStatusCode, String name) { @@ -919,6 +926,28 @@ private static ImmutableList getAllCases() { HandlerCategory.NONIDEMPOTENT, ExpectRetry.NO, Behavior.DEFAULT_MORE_STRICT), + new Case( + ThrowableCategory + .STORAGE_EXCEPTION_0_CONNECTION_CLOSED_PREMATURELY_IO_CAUSE_NO_REASON, + HandlerCategory.IDEMPOTENT, + ExpectRetry.YES, + Behavior.SAME), + new Case( + ThrowableCategory + .STORAGE_EXCEPTION_0_CONNECTION_CLOSED_PREMATURELY_IO_CAUSE_NO_REASON, + HandlerCategory.NONIDEMPOTENT, + ExpectRetry.NO, + Behavior.DEFAULT_MORE_STRICT), + new Case( + ThrowableCategory.STORAGE_EXCEPTION_0_IO_PREMATURE_EOF, + HandlerCategory.IDEMPOTENT, + ExpectRetry.YES, + Behavior.SAME), + new Case( + ThrowableCategory.STORAGE_EXCEPTION_0_IO_PREMATURE_EOF, + HandlerCategory.NONIDEMPOTENT, + ExpectRetry.NO, + Behavior.DEFAULT_MORE_STRICT), new Case( ThrowableCategory.STORAGE_EXCEPTION_0_INTERNAL_ERROR, HandlerCategory.IDEMPOTENT, diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java index b713a2222..8b5847789 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/ITRetryConformanceTest.java @@ -154,7 +154,7 @@ public static Collection testCases() throws IOException { .and( (m, trc) -> trc.getScenarioId() - < 7) // Temporarily exclude resumable media scenarios + != 7) // Temporarily exclude resumable upload scenarios ) .build(); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java index 668fa902c..259c2a7dc 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RpcMethodMappings.java @@ -18,6 +18,7 @@ import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.defaultSetup; import static com.google.cloud.storage.conformance.retry.CtxFunctions.ResourceSetup.serviceAccount; +import static com.google.common.base.Predicates.and; import static com.google.common.base.Predicates.not; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertTrue; @@ -66,6 +67,7 @@ import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.ObjectAcl; import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.Objects; import com.google.cloud.storage.conformance.retry.RpcMethodMappings.Mappings.ServiceAccount; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ListMultimap; @@ -108,6 +110,11 @@ final class RpcMethodMappings { private static final Logger LOGGER = Logger.getLogger(RpcMethodMappings.class.getName()); + private static final Predicate groupIsDownload = + methodGroupIs("storage.objects.download"); + private static final Predicate groupIsResumableUpload = + methodGroupIs("storage.resumable.upload"); + static final int _2MiB = 2 * 1024 * 1024; final Multimap funcMap; @@ -1079,7 +1086,8 @@ private static void delete(ArrayList a) { private static void get(ArrayList a) { a.add( RpcMethodMapping.newBuilder(39, objects.get) - .withApplicable(not(TestRetryConformance::isPreconditionsProvided)) + .withApplicable( + and(not(TestRetryConformance::isPreconditionsProvided), not(groupIsDownload))) .withSetup(defaultSetup.andThen(Local.blobInfoWithoutGeneration)) .withTest( (ctx, c) -> @@ -1088,13 +1096,15 @@ private static void get(ArrayList a) { .build()); a.add( RpcMethodMapping.newBuilder(239, objects.get) - .withApplicable(TestRetryConformance::isPreconditionsProvided) + .withApplicable( + and(TestRetryConformance::isPreconditionsProvided, not(groupIsDownload))) .withTest( (ctx, c) -> ctx.peek(state -> ctx.getStorage().get(state.getBlob().getBlobId()))) .build()); a.add( RpcMethodMapping.newBuilder(40, objects.get) + .withApplicable(not(groupIsDownload)) .withTest( (ctx, c) -> ctx.map( @@ -1108,6 +1118,7 @@ private static void get(ArrayList a) { .build()); a.add( RpcMethodMapping.newBuilder(41, objects.get) + .withApplicable(not(groupIsDownload)) .withTest( (ctx, c) -> ctx.map( @@ -1196,7 +1207,8 @@ private static void get(ArrayList a) { .build()); a.add( RpcMethodMapping.newBuilder(60, objects.get) - .withApplicable(not(TestRetryConformance::isPreconditionsProvided)) + .withApplicable( + and(not(TestRetryConformance::isPreconditionsProvided), not(groupIsDownload))) .withTest((ctx, c) -> ctx.peek(state -> assertTrue(state.getBlob().exists()))) .build()); a.add( @@ -1297,10 +1309,12 @@ private static void get(ArrayList a) { .build()); a.add( RpcMethodMapping.newBuilder(75, objects.get) + .withApplicable(not(groupIsDownload)) .withTest((ctx, c) -> ctx.peek(state -> state.getBlob().reload())) .build()); a.add( RpcMethodMapping.newBuilder(76, objects.get) + .withApplicable(not(groupIsDownload)) .withTest( (ctx, c) -> ctx.peek( @@ -1311,7 +1325,8 @@ private static void get(ArrayList a) { .build()); a.add( RpcMethodMapping.newBuilder(107, objects.get) - .withApplicable(not(TestRetryConformance::isPreconditionsProvided)) + .withApplicable( + and(not(TestRetryConformance::isPreconditionsProvided), not(groupIsDownload))) .withTest( (ctx, c) -> ctx.map(state -> state.with(state.getBucket().get(c.getObjectName())))) @@ -1321,7 +1336,8 @@ private static void get(ArrayList a) { private static void insert(ArrayList a) { a.add( RpcMethodMapping.newBuilder(46, objects.insert) - .withApplicable(TestRetryConformance::isPreconditionsProvided) + .withApplicable( + and(TestRetryConformance::isPreconditionsProvided, not(groupIsResumableUpload))) .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero)) .withTest( (ctx, c) -> @@ -1336,7 +1352,8 @@ private static void insert(ArrayList a) { .build()); a.add( RpcMethodMapping.newBuilder(47, objects.insert) - .withApplicable(TestRetryConformance::isPreconditionsProvided) + .withApplicable( + and(TestRetryConformance::isPreconditionsProvided, not(groupIsResumableUpload))) .withSetup(defaultSetup.andThen(Local.blobInfoWithGenerationZero)) .withTest( (ctx, c) -> @@ -1932,4 +1949,8 @@ private static void get(ArrayList a) { private static void put(ArrayList a) {} } } + + private static Predicate methodGroupIs(String s) { + return (c) -> s.equals(c.getMethod().getGroup()); + } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java index b9b8456dd..03d671458 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/TestRetryConformance.java @@ -24,6 +24,7 @@ import com.google.cloud.conformance.storage.v1.InstructionList; import com.google.cloud.conformance.storage.v1.Method; import com.google.common.base.Joiner; +import com.google.common.base.Suppliers; import com.google.errorprone.annotations.Immutable; import java.io.IOException; import java.io.InputStream; @@ -37,7 +38,9 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.IntStream; /** * An individual resolved test case correlating config from {@link @@ -58,13 +61,16 @@ final class TestRetryConformance { BASE_ID = formatter.format(now).replaceAll("[:]", "").substring(0, 6); } + private static final int _512KiB = 512 * 1024; + private static final int _8MiB = 8 * 1024 * 1024; + private final String projectId; private final String bucketName; private final String bucketName2; private final String userProject; private final String objectName; - private final byte[] helloWorldUtf8Bytes = "Hello, World!!!".getBytes(StandardCharsets.UTF_8); + private final Supplier lazyHelloWorldUtf8Bytes; private final Path helloWorldFilePath = resolvePathForResource(); private final ServiceAccountCredentials serviceAccountCredentials = resolveServiceAccountCredentials(); @@ -126,6 +132,33 @@ final class TestRetryConformance { String.format( "%s_s%03d-%s-m%03d_obj1", BASE_ID, scenarioId, instructionsString.toLowerCase(), mappingId); + lazyHelloWorldUtf8Bytes = + Suppliers.memoize( + () -> { + // define a lazy supplier for bytes. + // Not all tests need data for an object, though some tests - resumable upload - needs + // more than 8MiB. + // We want to avoid allocating 8.1MiB for each test unnecessarily, especially since we + // instantiate all permuted test cases. ~1000 * 8.1MiB ~~ > 8GiB. + String helloWorld = "Hello, World!"; + int baseDataSize; + switch (method.getName()) { + case "storage.objects.insert": + baseDataSize = _8MiB + 1; + break; + case "storage.objects.get": + baseDataSize = _512KiB; + break; + default: + baseDataSize = helloWorld.length(); + break; + } + int endInclusive = (baseDataSize / helloWorld.length()); + return IntStream.rangeClosed(1, endInclusive) + .mapToObj(i -> helloWorld) + .collect(Collectors.joining()) + .getBytes(StandardCharsets.UTF_8); + }); } public String getProjectId() { @@ -153,7 +186,7 @@ public String getObjectName() { } public byte[] getHelloWorldUtf8Bytes() { - return helloWorldUtf8Bytes; + return lazyHelloWorldUtf8Bytes.get(); } public Path getHelloWorldFilePath() { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITDownloadToTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITDownloadToTest.java new file mode 100644 index 000000000..0904c5742 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITDownloadToTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2022 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.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.cloud.storage.testing.RemoteStorageHelper; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.zip.GZIPOutputStream; +import org.junit.BeforeClass; +import org.junit.Test; + +public final class ITDownloadToTest { + + private static final String BUCKET = RemoteStorageHelper.generateBucketName(); + private static final byte[] helloWorldTextBytes = "hello world".getBytes(); + private static final byte[] helloWorldGzipBytes = gzipBytes(helloWorldTextBytes); + + private static Storage storage; + private static BlobId blobId; + + @BeforeClass + public static void beforeClass() { + BucketInfo bucketInfo = BucketInfo.of(BUCKET); + blobId = BlobId.of(BUCKET, "zipped_blob"); + + BlobInfo blobInfo = + BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build(); + + storage = StorageOptions.newBuilder().build().getService(); + storage.create(bucketInfo); + storage.create(blobInfo, helloWorldGzipBytes); + } + + @Test + public void downloadTo_returnRawInputStream_yes() throws IOException { + Path helloWorldTxtGz = File.createTempFile("helloWorld", ".txt.gz").toPath(); + storage.downloadTo( + blobId, helloWorldTxtGz, Storage.BlobSourceOption.shouldReturnRawInputStream(true)); + + byte[] actualTxtGzBytes = Files.readAllBytes(helloWorldTxtGz); + if (Arrays.equals(actualTxtGzBytes, helloWorldTextBytes)) { + fail("expected gzipped bytes, but got un-gzipped bytes"); + } + assertThat(actualTxtGzBytes).isEqualTo(helloWorldGzipBytes); + } + + @Test + public void downloadTo_returnRawInputStream_no() throws IOException { + Path helloWorldTxt = File.createTempFile("helloWorld", ".txt").toPath(); + storage.downloadTo( + blobId, helloWorldTxt, Storage.BlobSourceOption.shouldReturnRawInputStream(false)); + byte[] actualTxtBytes = Files.readAllBytes(helloWorldTxt); + assertThat(actualTxtBytes).isEqualTo(helloWorldTextBytes); + } + + private static byte[] gzipBytes(byte[] bytes) { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + try (OutputStream out = new GZIPOutputStream(byteArrayOutputStream)) { + out.write(bytes); + } catch (IOException ignore) { + } + + return byteArrayOutputStream.toByteArray(); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index b1863b38e..462b4bbde 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -119,7 +119,6 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; @@ -127,7 +126,6 @@ import java.net.URLConnection; import java.nio.ByteBuffer; import java.nio.file.Files; -import java.nio.file.Path; import java.security.Key; import java.util.ArrayList; import java.util.Arrays; @@ -147,7 +145,6 @@ import java.util.logging.Logger; import java.util.stream.StreamSupport; import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; import javax.crypto.spec.SecretKeySpec; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; @@ -906,39 +903,6 @@ public void testGetBlobFailNonExistingGeneration() { } } - @Test - public void testGetBlobRawInput() throws IOException { - Path file = File.createTempFile("temp", ".txt").toPath(); - Files.write(file, "hello world".getBytes()); - - File gzippedFile = File.createTempFile("temp", ".gz"); - - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(new FileOutputStream(gzippedFile)); - Files.copy(file, gzipOutputStream); - gzipOutputStream.close(); - - String blobName = "zipped_blob"; - BlobId blobId = BlobId.of(BUCKET, blobName); - BlobInfo blobInfo = - BlobInfo.newBuilder(blobId).setContentEncoding("gzip").setContentType("text/plain").build(); - - storage.createFrom(blobInfo, gzippedFile.toPath()); - - Path rawInputGzippedFile = File.createTempFile("rawinputgzippedfile", ".txt").toPath(); - - storage.downloadTo( - blobId, rawInputGzippedFile, Storage.BlobSourceOption.shouldReturnRawInputStream(true)); - - assertArrayEquals( - Files.readAllBytes(gzippedFile.toPath()), Files.readAllBytes(rawInputGzippedFile)); - - Path unzippedFile = File.createTempFile("unzippedfile", ".txt").toPath(); - storage.downloadTo( - blobId, unzippedFile, Storage.BlobSourceOption.shouldReturnRawInputStream(false)); - - assertArrayEquals("hello world".getBytes(), Files.readAllBytes(unzippedFile)); - } - @Test(timeout = 5000) public void testListBlobsSelectedFields() throws InterruptedException { String[] blobNames = { @@ -3699,25 +3663,6 @@ public void testDeleteLifecycleRules() throws ExecutionException, InterruptedExc } } - @Test - public void testUploadFromDownloadTo() throws Exception { - String blobName = "test-uploadFrom-downloadTo-blob"; - BlobId blobId = BlobId.of(BUCKET, blobName); - BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); - - Path tempFileFrom = Files.createTempFile("ITStorageTest_", ".tmp"); - Files.write(tempFileFrom, BLOB_BYTE_CONTENT); - Blob blob = storage.createFrom(blobInfo, tempFileFrom); - assertEquals(BUCKET, blob.getBucket()); - assertEquals(blobName, blob.getName()); - assertEquals(BLOB_BYTE_CONTENT.length, (long) blob.getSize()); - - Path tempFileTo = Files.createTempFile("ITStorageTest_", ".tmp"); - storage.downloadTo(blobId, tempFileTo); - byte[] readBytes = Files.readAllBytes(tempFileTo); - assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); - } - @Test public void testUploadWithEncryption() throws Exception { String blobName = "test-upload-withEncryption";