This method is provided as convenience for those methods which have void return. In general
+ * {@link Ctx#map(EFunction)} should be used.
+ */
+ public Ctx peek(EConsumer Functions are grouped into nested classes which try to hint at the area they operate within.
+ * Client side-only, or performing an RPC, setup or tear down and so on.
+ *
+ * @see RpcMethodMapping
+ * @see RpcMethodMapping.Builder
+ * @see RpcMethodMappings
+ */
+final class CtxFunctions {
+
+ private static final class Util {
+ private static final CtxFunction blobIdAndBlobInfo =
+ (ctx, c) -> ctx.map(state -> state.with(BlobInfo.newBuilder(state.getBlobId()).build()));
+ }
+
+ static final class Local {
+ static final CtxFunction blobCopy =
+ (ctx, c) -> ctx.map(s -> s.withCopyDest(BlobId.of(c.getBucketName2(), c.getObjectName())));
+
+ static final CtxFunction bucketInfo =
+ (ctx, c) -> ctx.map(s -> s.with(BucketInfo.of(c.getBucketName())));
+ static final CtxFunction blobIdWithoutGeneration =
+ (ctx, c) -> ctx.map(s -> s.with(BlobId.of(c.getBucketName(), c.getObjectName())));
+ static final CtxFunction blobIdWithGenerationZero =
+ (ctx, c) -> ctx.map(s -> s.with(BlobId.of(c.getBucketName(), c.getObjectName(), 0L)));
+ static final CtxFunction blobInfoWithoutGeneration =
+ blobIdWithoutGeneration.andThen(Util.blobIdAndBlobInfo);
+ static final CtxFunction blobInfoWithGenerationZero =
+ blobIdWithGenerationZero.andThen(Util.blobIdAndBlobInfo);
+ }
+
+ static final class Rpc {
+ static final CtxFunction bucket =
+ (ctx, c) ->
+ ctx.map(state -> state.with(ctx.getStorage().get(state.getBucketInfo().getName())));
+ static final CtxFunction blobWithGeneration =
+ (ctx, c) -> ctx.map(state -> state.with(ctx.getStorage().get(state.getBlobId())));
+ static final CtxFunction createEmptyBlob =
+ (ctx, c) -> ctx.map(state -> state.with(ctx.getStorage().create(state.getBlobInfo())));
+ }
+
+ static final class ResourceSetup {
+ private static final CtxFunction bucket =
+ (ctx, c) -> {
+ BucketInfo bucketInfo = BucketInfo.newBuilder(c.getBucketName()).build();
+ Bucket resolvedBucket = ctx.getStorage().create(bucketInfo);
+ return ctx.map(s -> s.with(resolvedBucket));
+ };
+ private static final CtxFunction object =
+ (ctx, c) -> {
+ BlobInfo blobInfo =
+ BlobInfo.newBuilder(ctx.getState().getBucket().getName(), c.getObjectName()).build();
+ Blob resolvedBlob = ctx.getStorage().create(blobInfo);
+ return ctx.map(s -> s.with(resolvedBlob));
+ };
+ private static final CtxFunction serviceAccount =
+ (ctx, c) ->
+ ctx.map(s -> s.with(ServiceAccount.of(c.getServiceAccountSigner().getAccount())));
+ private static final CtxFunction hmacKey =
+ (ctx, c) -> ctx.map(s -> s.with(ctx.getStorage().createHmacKey(s.getServiceAccount())));
+
+ private static final CtxFunction processResources =
+ (ctx, c) -> {
+ HashSet All functions allow checked exceptions to be thrown, whereas their siblings in {@code
+ * java.util.function} do not.
+ */
+final class Functions {
+
+ /**
+ * A specialized BiFunction which cuts down on boilerplate and provides an {@link
+ * CtxFunction#andThen(CtxFunction) andThen} which carries through the BiFunction-ness.
+ */
+ @FunctionalInterface
+ interface CtxFunction {
+
+ Ctx apply(Ctx ctx, TestRetryConformance trc) throws Throwable;
+
+ default CtxFunction andThen(CtxFunction f) {
+ return (Ctx ctx, TestRetryConformance trc) -> f.apply(apply(ctx, trc), trc);
+ }
+
+ static CtxFunction identity() {
+ return (ctx, c) -> ctx;
+ }
+ }
+
+ /**
+ * Define a Function which can throw, this simplifies the code where a checked exception is
+ * declared. These Functions only exist in the context of tests so if a throw happens it will be
+ * handled at a per-test level.
+ */
+ @FunctionalInterface
+ interface EFunction {
+ B apply(A a) throws Throwable;
+ }
+
+ /**
+ * Define a Consumer which can throw, this simplifies the code where a checked exception is
+ * declared. These Consumers only exist in the context of tests so if a throw happens it will be
+ * handled at a per-test level.
+ */
+ @FunctionalInterface
+ interface EConsumer {
+ void consume(A a) throws Throwable;
+ }
+
+ /**
+ * Define a function which has a void return. This is definition is absolutely not pure and only
+ * exists because some methods on the public api for storage have void return type.
+ */
+ @FunctionalInterface
+ interface VoidFunction {
+ void apply() throws Throwable;
+ }
+}
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/GracefulConformanceEnforcement.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/GracefulConformanceEnforcement.java
new file mode 100644
index 0000000000..2fd4f90bb1
--- /dev/null
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/GracefulConformanceEnforcement.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2021 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.conformance.retry;
+
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.io.CharStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Set;
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * As the adherence of {@link com.google.cloud.storage.Storage} to the retry conformance test suite
+ * is an ongoing effort, we need a way in which those tests which are not yet in compliance do not
+ * server as blockers for other features and commits.
+ *
+ * This class provides a transparent means of enforcing the reporting of failed tests when ran in
+ * a CI environment. When a test is run, if it fails for any reason the test name will be checked
+ * against a list of known complying tests. If the tests name is missing from the known list, then
+ * the failure will be wrapped in an assumption failure to show up as a skipped test rather than a
+ * failed one.
+ */
+final class GracefulConformanceEnforcement implements TestRule {
+
+ private final String testName;
+ private final Set This class dynamically generates test cases based on resources from the
+ * google-cloud-conformance-tests artifact and a set of defined mappings from {@link
+ * RpcMethodMappings}.
+ */
+@RunWith(ParallelParameterized.class)
+public class ITRetryConformanceTest {
+ private static final Logger LOGGER = Logger.getLogger(ITRetryConformanceTest.class.getName());
+
+ @ClassRule public static final TestBench TEST_BENCH = TestBench.newBuilder().build();
+
+ @Rule(order = 1)
+ public final GracefulConformanceEnforcement gracefulConformanceEnforcement;
+
+ @Rule(order = 2)
+ public final RetryTestFixture retryTestFixture;
+
+ private final TestRetryConformance testRetryConformance;
+ private final RpcMethodMapping mapping;
+
+ public ITRetryConformanceTest(
+ TestRetryConformance testRetryConformance, RpcMethodMapping mapping) {
+ this.testRetryConformance = testRetryConformance;
+ this.mapping = mapping;
+ this.gracefulConformanceEnforcement =
+ new GracefulConformanceEnforcement(testRetryConformance.getTestName());
+ this.retryTestFixture =
+ new RetryTestFixture(CleanupStrategy.ALWAYS, TEST_BENCH, testRetryConformance);
+ }
+
+ /**
+ * Run an individual test case. 1. Create two storage clients, one for setup/teardown and one for
+ * test execution 2. Run setup 3. Run test 4. Run teardown
+ */
+ @Test
+ public void test() throws Throwable {
+ Storage nonTestStorage = retryTestFixture.getNonTestStorage();
+ Storage testStorage = retryTestFixture.getTestStorage();
+
+ Ctx ctx = ctx(nonTestStorage, empty());
+
+ LOGGER.fine("Running setup...");
+ Ctx postSetupCtx =
+ mapping.getSetup().apply(ctx, testRetryConformance).leftMap(s -> testStorage);
+ LOGGER.fine("Running setup complete");
+
+ LOGGER.fine("Running test...");
+ Ctx postTestCtx =
+ getReplaceStorageInObjectsFromCtx()
+ .andThen(mapping.getTest())
+ .apply(postSetupCtx, testRetryConformance)
+ .leftMap(s -> nonTestStorage);
+ LOGGER.fine("Running test complete");
+
+ LOGGER.fine("Running teardown...");
+ getReplaceStorageInObjectsFromCtx()
+ .andThen(mapping.getTearDown())
+ .apply(postTestCtx, testRetryConformance);
+ LOGGER.fine("Running teardown complete");
+ }
+
+ /**
+ * Load all of the tests and return a {@code Collection