Repository: googleapis/java-storage
Pull request #1832 (2023-01-06 23:15:54)

chore: introduce ByteRangeSpec#endOffsetInclusive
There are some cases where endOffset and endOffsetInclusive are both necessary and externally being able to differentiate is needed.

### Test Refactor

As part of this change, I've update the declaration of ByteRangeSpec.RangeScenario to be more verbose and easier to read, as well as have a more direct description for the test name.

This does add a fair number of lines of code, but I think it brings some clarity compared to a method with 4 different long arguments. It also reduces (and possible errors in declaration) between related scenarios.

In general the new pattern is: define the four properties of any range (beginOffset, endOffset, endOffsetInclusive, length) then specify those ByteRangeSpecs for which it should be applicable.




Patch:
diff --git a/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageSettings.java b/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageSettings.java
index 8c693857..16de430f 100644
--- a/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageSettings.java
+++ b/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/StorageSettings.java
@@ -72,7 +72,10 @@ import javax.annotation.Generated;
  * storageSettingsBuilder
  *     .deleteBucketSettings()
  *     .setRetrySettings(
- *         storageSettingsBuilder.deleteBucketSettings().getRetrySettings().toBuilder()
+ *         storageSettingsBuilder
+ *             .deleteBucketSettings()
+ *             .getRetrySettings()
+ *             .toBuilder()
  *             .setTotalTimeout(Duration.ofSeconds(30))
  *             .build());
  * StorageSettings storageSettings = storageSettingsBuilder.build();
diff --git a/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/stub/StorageStubSettings.java b/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/stub/StorageStubSettings.java
index 0e89bd6b..3f882e38 100644
--- a/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/stub/StorageStubSettings.java
+++ b/gapic-google-cloud-storage-v2/src/main/java/com/google/storage/v2/stub/StorageStubSettings.java
@@ -130,7 +130,10 @@ import org.threeten.bp.Duration;
  * storageSettingsBuilder
  *     .deleteBucketSettings()
  *     .setRetrySettings(
- *         storageSettingsBuilder.deleteBucketSettings().getRetrySettings().toBuilder()
+ *         storageSettingsBuilder
+ *             .deleteBucketSettings()
+ *             .getRetrySettings()
+ *             .toBuilder()
  *             .setTotalTimeout(Duration.ofSeconds(30))
  *             .build());
  * StorageStubSettings storageSettings = storageSettingsBuilder.build();
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 21e88426..c69993d4 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -16,8 +16,8 @@
   </parent>
   <properties>
     <site.installationModule>google-cloud-storage</site.installationModule>
-    <pubsub-proto.version>1.104.2</pubsub-proto.version>
-    <junit-platform.version>5.9.1</junit-platform.version>
+    <pubsub-proto.version>1.105.0</pubsub-proto.version>
+    <junit-platform.version>5.9.2</junit-platform.version>
   </properties>
   <dependencies>
     <dependency>
@@ -243,7 +243,7 @@
     <dependency>
       <groupId>net.jqwik</groupId>
       <artifactId>jqwik</artifactId>
-      <version>1.7.1</version>
+      <version>1.7.2</version>
       <scope>test</scope>
     </dependency>
 
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java
index 676f521c..737e1a82 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/ByteRangeSpec.java
@@ -50,6 +50,8 @@ abstract class ByteRangeSpec implements Serializable {
 
   abstract long endOffset() throws ArithmeticException;
 
+  abstract long endOffsetInclusive() throws ArithmeticException;
+
   abstract long length() throws ArithmeticException;
 
   // TODO: add validation to this if it ever becomes public
@@ -97,9 +99,7 @@ abstract class ByteRangeSpec implements Serializable {
 
   @Override
   public String toString() {
-    return append(MoreObjects.toStringHelper(""))
-        .add("httpRangeHeader", getHttpRangeHeader())
-        .toString();
+    return append(MoreObjects.toStringHelper("ByteRangeSpec")).toString();
   }
 
   protected abstract MoreObjects.ToStringHelper append(MoreObjects.ToStringHelper tsh);
@@ -162,6 +162,11 @@ abstract class ByteRangeSpec implements Serializable {
 
     @Override
     long endOffset() throws ArithmeticException {
+      return Math.addExact(beginOffset, length);
+    }
+
+    @Override
+    long endOffsetInclusive() throws ArithmeticException {
       return Math.addExact(beginOffset, length) - 1;
     }
 
@@ -209,7 +214,7 @@ abstract class ByteRangeSpec implements Serializable {
 
     @Override
     protected String fmtAsHttpRangeHeader() throws ArithmeticException {
-      return String.format("bytes=%d-%d", beginOffset, endOffset());
+      return String.format("bytes=%d-%d", beginOffset, endOffsetInclusive());
     }
 
     @Override
@@ -239,9 +244,14 @@ abstract class ByteRangeSpec implements Serializable {
       return endOffsetExclusive;
     }
 
+    @Override
+    long endOffsetInclusive() throws ArithmeticException {
+      return Math.subtractExact(endOffsetExclusive, 1);
+    }
+
     @Override
     long length() throws ArithmeticException {
-      return endOffsetExclusive - beginOffset;
+      return Math.subtractExact(endOffsetExclusive, beginOffset);
     }
 
     @Override
@@ -284,7 +294,7 @@ abstract class ByteRangeSpec implements Serializable {
 
     @Override
     protected String fmtAsHttpRangeHeader() throws ArithmeticException {
-      return String.format("bytes=%d-%d", beginOffset, endOffsetExclusive - 1);
+      return String.format("bytes=%d-%d", beginOffset, endOffsetInclusive());
     }
 
     @Override
@@ -311,12 +321,17 @@ abstract class ByteRangeSpec implements Serializable {
 
     @Override
     long endOffset() throws ArithmeticException {
+      return Math.addExact(endOffsetInclusive, 1L);
+    }
+
+    @Override
+    long endOffsetInclusive() throws ArithmeticException {
       return endOffsetInclusive;
     }
 
     @Override
     long length() throws ArithmeticException {
-      return endOffsetInclusive - beginOffset;
+      return Math.addExact(Math.subtractExact(endOffsetInclusive, beginOffset), 1);
     }
 
     @Override
@@ -346,7 +361,7 @@ abstract class ByteRangeSpec implements Serializable {
     @Override
     ByteRangeSpec withNewEndOffsetClosed(long endOffsetInclusive) {
       if (endOffsetInclusive != this.endOffsetInclusive) {
-        return new LeftClosedRightOpenByteRangeSpec(beginOffset, endOffsetInclusive);
+        return new LeftClosedRightClosedByteRangeSpec(beginOffset, endOffsetInclusive);
       } else {
         return this;
       }
@@ -387,6 +402,11 @@ abstract class ByteRangeSpec implements Serializable {
       return EFFECTIVE_INFINITY;
     }
 
+    @Override
+    long endOffsetInclusive() throws ArithmeticException {
+      return EFFECTIVE_INFINITY;
+    }
+
     @Override
     long length() throws ArithmeticException {
       return EFFECTIVE_INFINITY;
@@ -455,6 +475,11 @@ abstract class ByteRangeSpec implements Serializable {
       return EFFECTIVE_INFINITY;
     }
 
+    @Override
+    long endOffsetInclusive() throws ArithmeticException {
+      return EFFECTIVE_INFINITY;
+    }
+
     @Override
     long length() throws ArithmeticException {
       return EFFECTIVE_INFINITY;
diff --git a/pom.xml b/pom.xml
index a26499fe..2567c31b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,7 +54,7 @@
     <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
     <github.global.server>github</github.global.server>
     <site.installationModule>google-cloud-storage-parent</site.installationModule>
-    <google.cloud.shared-dependencies.version>3.1.0</google.cloud.shared-dependencies.version>
+    <google.cloud.shared-dependencies.version>3.1.1</google.cloud.shared-dependencies.version>
   </properties>
 
   <dependencyManagement>
@@ -80,7 +80,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-pubsub</artifactId>
-        <version>1.122.2</version>
+        <version>1.123.0</version>
         <scope>test</scope>
       </dependency>
       <dependency>
diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml
index bc075099..cd4bcb3c 100644
--- a/samples/install-without-bom/pom.xml
+++ b/samples/install-without-bom/pom.xml
@@ -61,7 +61,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.122.2</version>
+      <version>1.123.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/native-image-sample/pom.xml b/samples/native-image-sample/pom.xml
index 8dfc1fc6..cf93b127 100644
--- a/samples/native-image-sample/pom.xml
+++ b/samples/native-image-sample/pom.xml
@@ -61,7 +61,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.122.2</version>
+      <version>1.123.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
@@ -114,7 +114,7 @@
         <dependency>
           <groupId>org.junit.vintage</groupId>
           <artifactId>junit-vintage-engine</artifactId>
-          <version>5.9.1</version>
+          <version>5.9.2</version>
           <scope>test</scope>
         </dependency>
       </dependencies>
diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml
index b558a451..3dafa41a 100644
--- a/samples/snapshot/pom.xml
+++ b/samples/snapshot/pom.xml
@@ -52,7 +52,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.122.2</version>
+      <version>1.123.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml
index 5401a42c..b1ef71b4 100644
--- a/samples/snippets/pom.xml
+++ b/samples/snippets/pom.xml
@@ -72,7 +72,7 @@
     <dependency>
       <groupId>com.google.cloud</groupId>
       <artifactId>google-cloud-pubsub</artifactId>
-      <version>1.122.2</version>
+      <version>1.123.0</version>
       <scope>test</scope>
     </dependency>
   </dependencies>


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ByteRangeSpecTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ByteRangeSpecTest.java
index bdf1ce5e..234a24b4 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ByteRangeSpecTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ByteRangeSpecTest.java
@@ -16,8 +16,10 @@
 
 package com.google.cloud.storage;
 
+import static com.google.cloud.storage.ByteRangeSpec.EFFECTIVE_INFINITY;
 import static com.google.cloud.storage.TestUtils.assertAll;
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
@@ -27,6 +29,7 @@ import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.util.Arrays;
 import java.util.stream.Stream;
 import org.checkerframework.checker.nullness.qual.Nullable;
 import org.junit.Test;
@@ -172,19 +175,19 @@ public final class ByteRangeSpecTest {
     @Test
     public void withNewEndOffset_sameInstanceIfNotDifferent_null() {
       ByteRangeSpec spec = ByteRangeSpec.relativeLength(null, null);
-      assertThat(spec.withNewEndOffset(RangeScenarios.INF)).isSameInstanceAs(spec);
+      assertThat(spec.withNewEndOffset(EFFECTIVE_INFINITY)).isSameInstanceAs(spec);
     }
 
     @Test
     public void withNewEndOffsetClosed_sameInstanceIfNotDifferent_null() {
       ByteRangeSpec spec = ByteRangeSpec.relativeLength(null, null);
-      assertThat(spec.withNewEndOffsetClosed(RangeScenarios.INF)).isSameInstanceAs(spec);
+      assertThat(spec.withNewEndOffsetClosed(EFFECTIVE_INFINITY)).isSameInstanceAs(spec);
     }
 
     @Test
     public void withNewRelativeLength_sameInstanceIfNotDifferent_null() {
       ByteRangeSpec spec = ByteRangeSpec.relativeLength(null, null);
-      assertThat(spec.withNewRelativeLength(RangeScenarios.INF)).isSameInstanceAs(spec);
+      assertThat(spec.withNewRelativeLength(EFFECTIVE_INFINITY)).isSameInstanceAs(spec);
     }
 
     @Test
@@ -320,7 +323,7 @@ public final class ByteRangeSpecTest {
       ByteRangeSpec actual = spec.withNewEndOffsetClosed(4L);
       assertAll(
           () -> assertThat(actual.beginOffset()).isEqualTo(3),
-          () -> assertThat(actual.endOffset()).isEqualTo(4));
+          () -> assertThat(actual.endOffsetInclusive()).isEqualTo(4));
     }
 
     @Test
@@ -329,7 +332,7 @@ public final class ByteRangeSpecTest {
       ByteRangeSpec actual = spec.withNewEndOffsetClosed(4L);
       assertAll(
           () -> assertThat(actual.beginOffset()).isEqualTo(0),
-          () -> assertThat(actual.endOffset()).isEqualTo(4));
+          () -> assertThat(actual.endOffsetInclusive()).isEqualTo(4));
     }
 
     @Test
@@ -338,7 +341,7 @@ public final class ByteRangeSpecTest {
       ByteRangeSpec actual = spec.withNewEndOffsetClosed(4L);
       assertAll(
           () -> assertThat(actual.beginOffset()).isEqualTo(3),
-          () -> assertThat(actual.endOffset()).isEqualTo(4));
+          () -> assertThat(actual.endOffsetInclusive()).isEqualTo(4));
     }
 
     @Test
@@ -347,7 +350,7 @@ public final class ByteRangeSpecTest {
       ByteRangeSpec actual = spec.withNewEndOffsetClosed(4L);
       assertAll(
           () -> assertThat(actual.beginOffset()).isEqualTo(3),
-          () -> assertThat(actual.endOffset()).isEqualTo(4));
+          () -> assertThat(actual.endOffsetInclusive()).isEqualTo(4));
     }
 
     @Test
@@ -356,7 +359,7 @@ public final class ByteRangeSpecTest {
       ByteRangeSpec actual = spec.withNewEndOffsetClosed(4L);
       assertAll(
           () -> assertThat(actual.beginOffset()).isEqualTo(3),
-          () -> assertThat(actual.endOffset()).isEqualTo(4));
+          () -> assertThat(actual.endOffsetInclusive()).isEqualTo(4));
     }
 
     @Test
@@ -441,129 +444,170 @@ public final class ByteRangeSpecTest {
   @RunWith(Parameterized.class)
   public static final class RangeScenarios {
 
-    private static final long INF = Long.MAX_VALUE;
     private final RangeScenario rs;
+    private final RangeScenario.Expectations expect;
 
     public RangeScenarios(RangeScenario rs) {
       this.rs = rs;
+      this.expect = rs.getExpectations();
     }
 
     @Test
     public void httpRangeHeader() {
-      assertThat(rs.getSpec().getHttpRangeHeader()).isEqualTo(rs.getExpectedHttpRange());
+      assertThat(rs.getSpec().getHttpRangeHeader()).isEqualTo(expect.getHttpRange());
     }
 
     @Test
     public void beginOffset() {
-      assertThat(rs.getSpec().beginOffset()).isEqualTo(rs.getExpectedBeginOffset());
+      assertThat(rs.getSpec().beginOffset()).isEqualTo(expect.getBeginOffset());
     }
 
     @Test
     public void endOffset() {
-      assertThat(rs.getSpec().endOffset()).isEqualTo(rs.getExpectedEndOffset());
+      assertThat(rs.getSpec().endOffset()).isEqualTo(expect.getEndOffset());
+    }
+
+    @Test
+    public void endOffsetInclusive() {
+      assertThat(rs.getSpec().endOffsetInclusive()).isEqualTo(expect.getEndOffsetInclusive());
     }
 
     @Test
     public void length() {
-      assertThat(rs.getSpec().length()).isEqualTo(rs.getExpectedLength());
+      assertThat(rs.getSpec().length()).isEqualTo(expect.getLength());
     }
 
     @Parameters(name = "{0}")
     public static Iterable<Object[]> testCases() {
+      // expect that by default, a range should be from zero to infinity
       Stream<RangeScenario> bothNullOrEmpty =
-          Stream.of(
+          RangeScenario.expectThat()
+              .beginOffset(0L)
+              .endOffset(EFFECTIVE_INFINITY)
+              .endOffsetInclusive(EFFECTIVE_INFINITY)
+              .length(EFFECTIVE_INFINITY)
+              .httpRange(null)
+              .isApplicableTo(
                   ByteRangeSpec.relativeLength(null, null),
                   ByteRangeSpec.explicit(null, null),
                   ByteRangeSpec.explicitClosed(null, null),
                   ByteRangeSpec.relativeLength(0L, null),
                   ByteRangeSpec.explicit(0L, null),
                   ByteRangeSpec.explicitClosed(0L, null),
-                  ByteRangeSpec.relativeLength(null, INF),
-                  ByteRangeSpec.explicit(null, INF),
-                  ByteRangeSpec.explicitClosed(null, INF),
-                  ByteRangeSpec.relativeLength(0L, INF),
-                  ByteRangeSpec.explicit(0L, INF),
-                  ByteRangeSpec.explicitClosed(0L, INF))
-              .map(brs -> RangeScenario.of(brs, 0, INF, INF, null));
+                  ByteRangeSpec.relativeLength(null, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.explicit(null, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.explicitClosed(null, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.relativeLength(0L, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.explicit(0L, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.explicitClosed(0L, EFFECTIVE_INFINITY));
+      // expect that, if the range is effectively LeftClosed only, all upper bounds should
+      // be EFFECTIVE_INFINITY and the requests should be open-ended
       Stream<RangeScenario> effectivelyOnlyBegin =
-          Stream.of(
+          RangeScenario.expectThat()
+              .beginOffset(3L)
+              .endOffset(EFFECTIVE_INFINITY)
+              .endOffsetInclusive(EFFECTIVE_INFINITY)
+              .length(EFFECTIVE_INFINITY)
+              .httpRange(headerRangeOpen(3))
+              .isApplicableTo(
                   ByteRangeSpec.relativeLength(3L, null),
                   ByteRangeSpec.explicit(3L, null),
                   ByteRangeSpec.explicitClosed(3L, null),
                   // effective infinity means it should not impact things
-                  ByteRangeSpec.relativeLength(3L, INF),
-                  ByteRangeSpec.explicit(3L, INF),
-                  ByteRangeSpec.explicitClosed(3L, INF))
-              .map(brs -> RangeScenario.of(brs, 3, INF, INF, rangeOpen(3)));
+                  ByteRangeSpec.relativeLength(3L, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.explicit(3L, EFFECTIVE_INFINITY),
+                  ByteRangeSpec.explicitClosed(3L, EFFECTIVE_INFINITY));
+      // expect that, if the range is effectively Right{Closed,Open} only, lower bounds should
+      // be zero and the requests should be bounded from 0-N
       Stream<RangeScenario> effectivelyOnlyEnd =
-          Stream.of(
-              RangeScenario.of(
-                  ByteRangeSpec.relativeLength(null, 31L), 0L, 30L, 31L, rangeClosed(0, 30)),
-              RangeScenario.of(ByteRangeSpec.explicit(null, 31L), 0L, 31L, 31L, rangeClosed(0, 30)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicitClosed(null, 31L), 0L, 31L, 31L, rangeClosed(0, 31)),
-              RangeScenario.of(
-                  ByteRangeSpec.relativeLength(0L, 31L), 0L, 30L, 31L, rangeClosed(0, 30)),
-              RangeScenario.of(ByteRangeSpec.explicit(0L, 31L), 0L, 31L, 31L, rangeClosed(0, 30)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicitClosed(0L, 31L), 0L, 31L, 31L, rangeClosed(0, 31)));
-
+          Stream.concat(
+              RangeScenario.expectThat()
+                  .beginOffset(0L)
+                  .endOffset(131L)
+                  .endOffsetInclusive(130L)
+                  .length(131L)
+                  .bounded()
+                  .isApplicableTo(
+                      ByteRangeSpec.relativeLength(null, 131L),
+                      ByteRangeSpec.explicit(null, 131L),
+                      ByteRangeSpec.explicitClosed(null, 130L)),
+              RangeScenario.expectThat()
+                  .beginOffset(0L)
+                  .endOffset(251L)
+                  .endOffsetInclusive(250L)
+                  .length(251L)
+                  .bounded()
+                  .isApplicableTo(
+                      ByteRangeSpec.relativeLength(0L, 251L),
+                      ByteRangeSpec.explicit(0L, 251L),
+                      ByteRangeSpec.explicitClosed(0L, 250L)));
+
+      // expect that, when a range has both a lower and upper bound, all values match along with
+      // requests being bounded
       Stream<RangeScenario> bothSpecified =
-          Stream.of(
-              RangeScenario.of(
-                  ByteRangeSpec.relativeLength(3L, 15L), 3L, 17L, 15L, rangeClosed(3, 17)),
-              RangeScenario.of(ByteRangeSpec.explicit(3L, 15L), 3L, 15L, 12L, rangeClosed(3, 14)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicitClosed(3L, 15L), 3L, 15L, 12L, rangeClosed(3, 15)));
-
-      long effectiveMax = INF - 1;
+          RangeScenario.expectThat()
+              .beginOffset(4L)
+              .endOffset(10L)
+              .endOffsetInclusive(9L)
+              .length(6L)
+              .bounded()
+              .isApplicableTo(
+                  ByteRangeSpec.relativeLength(4L, 6L),
+                  ByteRangeSpec.explicit(4L, 10L),
+                  ByteRangeSpec.explicitClosed(4L, 9L));
+
+      // variable name should be read as "effective max minus zero"
+      long effectiveMax_0 = EFFECTIVE_INFINITY - 1;
+      // variable name should be read as "effective max minus one"
+      long effectiveMax_1 = effectiveMax_0 - 1;
+      // edge cases near default values
       Stream<RangeScenario> edgeCases =
-          Stream.of(
-              // edge cases near default values
-              RangeScenario.of(ByteRangeSpec.relativeLength(1L, null), 1L, INF, INF, rangeOpen(1)),
-              RangeScenario.of(
-                  ByteRangeSpec.relativeLength(null, effectiveMax),
-                  0,
-                  effectiveMax - 1,
-                  effectiveMax,
-                  rangeClosed(0, effectiveMax - 1)),
-              RangeScenario.of(
-                  ByteRangeSpec.relativeLength(INF, null), INF, INF, INF, rangeOpen(INF)),
-              RangeScenario.of(
-                  ByteRangeSpec.relativeLength(1L, effectiveMax),
-                  1L,
-                  effectiveMax,
-                  effectiveMax,
-                  rangeClosed(1L, effectiveMax)),
-              RangeScenario.of(ByteRangeSpec.explicit(1L, null), 1L, INF, INF, rangeOpen(1)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicit(null, effectiveMax),
-                  0,
-                  effectiveMax,
-                  effectiveMax,
-                  rangeClosed(0, effectiveMax - 1)),
-              RangeScenario.of(ByteRangeSpec.explicit(INF, null), INF, INF, INF, rangeOpen(INF)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicit(1L, effectiveMax),
-                  1L,
-                  effectiveMax,
-                  effectiveMax - 1,
-                  rangeClosed(1L, effectiveMax - 1)),
-              RangeScenario.of(ByteRangeSpec.explicitClosed(1L, null), 1L, INF, INF, rangeOpen(1)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicitClosed(null, effectiveMax),
-                  0,
-                  effectiveMax,
-                  effectiveMax,
-                  rangeClosed(0, effectiveMax)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicitClosed(INF, null), INF, INF, INF, rangeOpen(INF)),
-              RangeScenario.of(
-                  ByteRangeSpec.explicitClosed(1L, effectiveMax),
-                  1L,
-                  effectiveMax,
-                  effectiveMax - 1,
-                  rangeClosed(1L, effectiveMax)));
+          Streams.concat(
+              // expect that, if the range is effectively LeftClosed only, all upper bounds should
+              // be EFFECTIVE_INFINITY and the requests should be open-ended
+              RangeScenario.expectThat()
+                  .beginOffset(1L)
+                  .endOffset(EFFECTIVE_INFINITY)
+                  .endOffsetInclusive(EFFECTIVE_INFINITY)
+                  .length(EFFECTIVE_INFINITY)
+                  .httpRange(headerRangeOpen(1L))
+                  .isApplicableTo(
+                      ByteRangeSpec.relativeLength(1L, null),
+                      ByteRangeSpec.explicit(1L, null),
+                      ByteRangeSpec.explicitClosed(1L, null)),
+              // expect that, we can start reading from effective_infinity with not upper bound
+              RangeScenario.expectThat()
+                  .beginOffset(EFFECTIVE_INFINITY)
+                  .endOffset(EFFECTIVE_INFINITY)
+                  .endOffsetInclusive(EFFECTIVE_INFINITY)
+                  .length(EFFECTIVE_INFINITY)
+                  .httpRange(headerRangeOpen(EFFECTIVE_INFINITY))
+                  .isApplicableTo(
+                      ByteRangeSpec.relativeLength(EFFECTIVE_INFINITY, null),
+                      ByteRangeSpec.explicit(EFFECTIVE_INFINITY, null),
+                      ByteRangeSpec.explicitClosed(EFFECTIVE_INFINITY, null)),
+              // expect that, we can read up to Long.MAX_VALUE - 1
+              RangeScenario.expectThat()
+                  .beginOffset(0L)
+                  .endOffset(effectiveMax_0)
+                  .endOffsetInclusive(effectiveMax_1)
+                  .length(effectiveMax_0)
+                  .bounded()
+                  .isApplicableTo(
+                      ByteRangeSpec.relativeLength(null, effectiveMax_0),
+                      ByteRangeSpec.explicit(null, effectiveMax_0),
+                      ByteRangeSpec.explicitClosed(null, effectiveMax_1)),
+              // expect that, we can read from 1 up to Long.MAX_VALUE - 1
+              RangeScenario.expectThat()
+                  .beginOffset(1L)
+                  .endOffset(effectiveMax_0)
+                  .endOffsetInclusive(effectiveMax_1)
+                  .length(effectiveMax_1)
+                  .bounded()
+                  .isApplicableTo(
+                      ByteRangeSpec.relativeLength(1L, effectiveMax_1),
+                      ByteRangeSpec.explicit(1L, effectiveMax_0),
+                      ByteRangeSpec.explicitClosed(1L, effectiveMax_1)));
 
       return Streams.concat(
               bothNullOrEmpty, effectivelyOnlyBegin, effectivelyOnlyEnd, bothSpecified, edgeCases)
@@ -572,80 +616,169 @@ public final class ByteRangeSpecTest {
     }
   }
 
-  private static String rangeOpen(long min) {
+  private static String headerRangeOpen(long min) {
     return String.format("bytes=%d-", min);
   }
 
-  private static String rangeClosed(long min, long max) {
+  private static String headerRangeClosed(long min, long max) {
     return String.format("bytes=%d-%d", min, max);
   }
 
   private static final class RangeScenario {
     private final ByteRangeSpec spec;
-    private final long expectedBeginOffset;
-    private final long expectedEndOffset;
-    private final long expectedLength;
-    @Nullable private final String expectedHttpRange;
-
-    private RangeScenario(
-        ByteRangeSpec spec,
-        long expectedBeginOffset,
-        long expectedEndOffset,
-        long expectedLength,
-        @Nullable String expectedHttpRange) {
+    private final Expectations expectations;
+
+    private RangeScenario(ByteRangeSpec spec, Expectations expectations) {
       this.spec = spec;
-      this.expectedBeginOffset = expectedBeginOffset;
-      this.expectedEndOffset = expectedEndOffset;
-      this.expectedLength = expectedLength;
-      this.expectedHttpRange = expectedHttpRange;
+      this.expectations = expectations;
     }
 
     public ByteRangeSpec getSpec() {
       return spec;
     }
 
-    public long getExpectedBeginOffset() {
-      return expectedBeginOffset;
+    public Expectations getExpectations() {
+      return expectations;
     }
 
-    public long getExpectedEndOffset() {
-      return expectedEndOffset;
-    }
+    @Override
+    public String toString() {
+      return String.format(
+          "Expect that %s is applicable to %s", expectations.testNameFormat(), spec);
+    }
+
+    static ExpectationsBuilder expectThat() {
+      return new ExpectationsBuilder();
+    }
+
+    private static final class Expectations {
+      private final long beginOffset;
+      private final long endOffset;
+      private final long endOffsetInclusive;
+      private final long length;
+      @Nullable private final String httpRange;
+
+      private Expectations(
+          long beginOffset,
+          long endOffset,
+          long endOffsetInclusive,
+          long length,
+          @Nullable String httpRange) {
+        this.beginOffset = beginOffset;
+        this.endOffset = endOffset;
+        this.endOffsetInclusive = endOffsetInclusive;
+        this.length = length;
+        this.httpRange = httpRange;
+      }
 
-    public long getExpectedLength() {
-      return expectedLength;
-    }
+      public long getBeginOffset() {
+        return beginOffset;
+      }
+
+      public long getEndOffset() {
+        return endOffset;
+      }
+
+      public long getEndOffsetInclusive() {
+        return endOffsetInclusive;
+      }
+
+      public long getLength() {
+        return length;
+      }
+
+      public @Nullable String getHttpRange() {
+        return httpRange;
+      }
+
+      String testNameFormat() {
+        return MoreObjects.toStringHelper("")
+            .add("bo", fmt(beginOffset))
+            .add("eo", fmt(endOffset))
+            .add("eoi", fmt(endOffsetInclusive))
+            .add("l", fmt(length))
+            .toString();
+      }
 
-    public @Nullable String getExpectedHttpRange() {
-      return expectedHttpRange;
+      @Override
+      public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("beginOffset", fmt(beginOffset))
+            .add("endOffset", fmt(endOffset))
+            .add("endOffsetInclusive", fmt(endOffsetInclusive))
+            .add("length", fmt(length))
+            .add("httpRange", httpRange)
+            .toString();
+      }
+
+      private static String fmt(@Nullable Long l) {
+        if (l == null) {
+          return null;
+        } else if (l == Long.MAX_VALUE) {
+          return "Long.MAX_VALUE";
+        } else {
+          long diff = Long.MAX_VALUE - l;
+          // When testing near the upperbound of Long it can be challenging to read how close we are
+          // to the max value at a glance. In an effort to help this, for any value that is within
+          // 20 of Long.MAX_VALUE format it as a difference.
+          if (diff <= 20) {
+            return String.format("(Long.MAX_VALUE - %d)", diff);
+          } else {
+            return l.toString();
+          }
+        }
+      }
     }
 
-    @Override
-    public String toString() {
-      return MoreObjects.toStringHelper(this)
-          .add("spec", spec)
-          .add("expectedBeginOffset", fmt(expectedBeginOffset))
-          .add("expectedEndOffset", fmt(expectedEndOffset))
-          .add("expectedLength", fmt(expectedLength))
-          .add("expectedHttpRange", expectedHttpRange)
-          .toString();
-    }
-
-    static RangeScenario of(
-        ByteRangeSpec spec,
-        long expectedBeginOffset,
-        long expectedEndOffset,
-        long expectedLength,
-        @Nullable String expectedHttpRange) {
-      return new RangeScenario(
-          spec, expectedBeginOffset, expectedEndOffset, expectedLength, expectedHttpRange);
-    }
-
-    private static String fmt(@Nullable Long l) {
-      if (l == null) {
-        return null;
+    private static final class ExpectationsBuilder {
+      private Long beginOffset;
+      private Long endOffset;
+      private Long endOffsetInclusive;
+      private Long length;
+      @Nullable private String httpRange;
+
+      public ExpectationsBuilder beginOffset(Long beginOffset) {
+        this.beginOffset = beginOffset;
+        return this;
+      }
+
+      public ExpectationsBuilder endOffset(Long endOffset) {
+        this.endOffset = endOffset;
+        return this;
+      }
+
+      public ExpectationsBuilder endOffsetInclusive(Long endOffsetInclusive) {
+        this.endOffsetInclusive = endOffsetInclusive;
+        return this;
+      }
+
+      public ExpectationsBuilder length(Long length) {
+        this.length = length;
+        return this;
+      }
+
+      public ExpectationsBuilder httpRange(String httpRange) {
+        this.httpRange = httpRange;
+        return this;
+      }
+
+      public ExpectationsBuilder bounded() {
+        return this.httpRange(headerRangeClosed(beginOffset, endOffsetInclusive));
+      }
+
+      public Stream<RangeScenario> isApplicableTo(ByteRangeSpec... brss) {
+        Expectations expectations = this.build();
+        return Arrays.stream(brss).map(brs -> new RangeScenario(brs, expectations));
+      }
+
+      private Expectations build() {
+        return new Expectations(
+            requireNonNull(beginOffset, "beginOffset must be non null"),
+            requireNonNull(endOffset, "endOffset must be non null"),
+            requireNonNull(endOffsetInclusive, "endOffsetInclusive must be non null"),
+            requireNonNull(length, "length must be non null"),
+            httpRange);
       }
-      return l == Long.MAX_VALUE ? "Long.MAX_VALUE" : l.toString();
     }
   }
 }
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 2bf34e59..8aa55955 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
@@ -30,17 +30,16 @@ import com.google.cloud.conformance.storage.v1.RetryTest;
 import com.google.cloud.conformance.storage.v1.RetryTests;
 import com.google.cloud.storage.CIUtils;
 import com.google.cloud.storage.Storage;
-import com.google.cloud.storage.TransportCompatibility.Transport;
 import com.google.cloud.storage.conformance.retry.Functions.CtxFunction;
 import com.google.cloud.storage.conformance.retry.ITRetryConformanceTest.RetryConformanceParameterProvider;
 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.annotations.ParallelFriendly;
 import com.google.cloud.storage.it.runner.annotations.Parameterized;
 import com.google.cloud.storage.it.runner.annotations.Parameterized.Parameter;
 import com.google.cloud.storage.it.runner.annotations.Parameterized.ParametersProvider;
+import com.google.cloud.storage.it.runner.annotations.SingleBackend;
 import com.google.cloud.storage.it.runner.registry.TestBench;
 import com.google.common.base.Charsets;
 import com.google.common.base.Joiner;
@@ -78,7 +77,8 @@ import org.junit.runner.RunWith;
  * RpcMethodMappings}.
  */
 @RunWith(StorageITRunner.class)
-@CrossRun(transports = Transport.HTTP, backends = Backend.TEST_BENCH)
+// @CrossRun(transports = Transport.HTTP, backends = Backend.TEST_BENCH)
+@SingleBackend(Backend.TEST_BENCH)
 @Parameterized(RetryConformanceParameterProvider.class)
 @ParallelFriendly
 public class ITRetryConformanceTest {
@@ -127,11 +127,17 @@ public class ITRetryConformanceTest {
   @Test
   public void test() throws Throwable {
     LOGGER.fine("Running test...");
-    ctx =
-        getReplaceStorageInObjectsFromCtx()
-            .andThen(mapping.getTest())
-            .apply(ctx, testRetryConformance)
-            .leftMap(s -> nonTestStorage);
+    try {
+      ctx =
+          getReplaceStorageInObjectsFromCtx()
+              .andThen(mapping.getTest())
+              .apply(ctx, testRetryConformance)
+              .leftMap(s -> nonTestStorage);
+      retryTestFixture.succeeded(null);
+    } catch (Throwable e) {
+      retryTestFixture.failed(e, null);
+      throw e;
+    }
     LOGGER.fine("Running test complete");
   }
 
@@ -154,14 +160,7 @@ public class ITRetryConformanceTest {
               .setMappings(new RpcMethodMappings())
               .setProjectId("conformance-tests")
               .setHost(testBench.getBaseUri().replaceAll("https?://", ""))
-              .setTestAllowFilter(
-                  RetryTestCaseResolver.includeAll()
-                      // .and(RetryTestCaseResolver.specificMappings(44, 45))
-                      .and(
-                          (m, trc) ->
-                              trc.getScenarioId()
-                                  != 7) // Temporarily exclude resumable upload scenarios
-                  )
+              .setTestAllowFilter(RetryTestCaseResolver.includeAll())
               .build();
 
       List<RetryTestCase> retryTestCases;
@@ -384,6 +383,15 @@ public class ITRetryConformanceTest {
       return (m, c) -> set.contains(c.getMappingId());
     }
 
+    static BiPredicate<RpcMethod, TestRetryConformance> instructionsAre(String... instructions) {
+      return (m, trc) ->
+          trc.getInstruction().getInstructionsList().equals(ImmutableList.copyOf(instructions));
+    }
+
+    static BiPredicate<RpcMethod, TestRetryConformance> scenarioIdIs(int scenarioId) {
+      return (m, trc) -> trc.getScenarioId() == scenarioId;
+    }
+
     static final class Builder {
       private String retryTestsJsonResourcePath;
       private RpcMethodMappings mappings;
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RetryTestFixture.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RetryTestFixture.java
index 25c16f09..755bcc20 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RetryTestFixture.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/conformance/retry/RetryTestFixture.java
@@ -120,6 +120,11 @@ final class RetryTestFixture extends TestWatcher {
     testSuccess = true;
   }
 
+  @Override
+  protected void failed(Throwable e, Description description) {
+    super.failed(e, description);
+  }
+
   @Override
   protected void skipped(AssumptionViolatedException e, Description description) {
     testSkipped = true;
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 458bffdd..b2401f32 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
@@ -1780,7 +1780,8 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(118, objects.insert)
-                .withApplicable(TestRetryConformance::isPreconditionsProvided)
+                .withApplicable(
+                    and(TestRetryConformance::isPreconditionsProvided, not(groupIsResumableUpload)))
                 .withTest(
                     (ctx, c) ->
                         ctx.map(
@@ -1795,7 +1796,8 @@ final class RpcMethodMappings {
                 .build());
         a.add(
             RpcMethodMapping.newBuilder(119, objects.insert)
-                .withApplicable(TestRetryConformance::isPreconditionsProvided)
+                .withApplicable(
+                    and(TestRetryConformance::isPreconditionsProvided, not(groupIsResumableUpload)))
                 .withTest(
                     (ctx, c) ->
                         ctx.map(
@@ -1806,7 +1808,7 @@ final class RpcMethodMappings {
                                         .create(
                                             c.getObjectName(),
                                             c.getHelloWorldUtf8Bytes(),
-                                            "text/plain);charset=utf-8",
+                                            "text/plain;charset=utf-8",
                                             Bucket.BlobTargetOption.doesNotExist()))))
                 .build());
         a.add(
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 0a467256..d37e0950 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
@@ -16,6 +16,7 @@
 
 package com.google.cloud.storage.conformance.retry;
 
+import static com.google.common.truth.Truth.assertThat;
 import static java.util.Objects.requireNonNull;
 import static org.junit.Assert.assertNotNull;
 
@@ -23,16 +24,18 @@ import com.google.auth.ServiceAccountSigner;
 import com.google.auth.oauth2.ServiceAccountCredentials;
 import com.google.cloud.conformance.storage.v1.InstructionList;
 import com.google.cloud.conformance.storage.v1.Method;
+import com.google.cloud.storage.DataGenerator;
 import com.google.common.base.Joiner;
 import com.google.common.base.Suppliers;
+import com.google.common.io.ByteStreams;
 import com.google.errorprone.annotations.Immutable;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URISyntaxException;
-import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.time.Clock;
 import java.time.Instant;
 import java.time.ZoneId;
@@ -40,7 +43,6 @@ 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
@@ -72,9 +74,8 @@ final class TestRetryConformance {
   private final String topicName;
 
   private final Supplier<byte[]> lazyHelloWorldUtf8Bytes;
-  private final Path helloWorldFilePath = resolvePathForResource();
-  private final ServiceAccountCredentials serviceAccountCredentials =
-      resolveServiceAccountCredentials();
+  private final Supplier<Path> helloWorldFilePath;
+  private final ServiceAccountCredentials serviceAccountCredentials;
 
   private final String host;
 
@@ -137,33 +138,14 @@ final class TestRetryConformance {
         String.format(
             "%s_s%03d-%s-m%03d_top1",
             BASE_ID, scenarioId, instructionsString.toLowerCase(), mappingId);
-    lazyHelloWorldUtf8Bytes =
+    this.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);
+              return genBytes(method);
             });
+    this.helloWorldFilePath = resolvePathForResource(objectName, method);
+    this.serviceAccountCredentials = resolveServiceAccountCredentials();
   }
 
   public String getProjectId() {
@@ -195,7 +177,7 @@ final class TestRetryConformance {
   }
 
   public Path getHelloWorldFilePath() {
-    return helloWorldFilePath;
+    return helloWorldFilePath.get();
   }
 
   public int getScenarioId() {
@@ -238,15 +220,24 @@ final class TestRetryConformance {
     return getTestName();
   }
 
-  private static Path resolvePathForResource() {
-    ClassLoader cl = Thread.currentThread().getContextClassLoader();
-    URL url = cl.getResource("com/google/cloud/storage/conformance/retry/hello-world.txt");
-    assertNotNull(url);
-    try {
-      return Paths.get(url.toURI());
-    } catch (URISyntaxException e) {
-      throw new RuntimeException(e);
-    }
+  private static Supplier<Path> resolvePathForResource(String objectName, Method method) {
+    return () -> {
+      try {
+        File tempFile = File.createTempFile(objectName, "");
+        tempFile.deleteOnExit();
+
+        byte[] bytes = genBytes(method);
+        try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);
+            FileOutputStream out = new FileOutputStream(tempFile)) {
+          long copy = ByteStreams.copy(in, out);
+          assertThat(copy).isEqualTo(bytes.length);
+        }
+
+        return tempFile.toPath();
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    };
   }
 
   private static ServiceAccountCredentials resolveServiceAccountCredentials() {
@@ -265,4 +256,19 @@ final class TestRetryConformance {
   public String getTopicName() {
     return topicName;
   }
+
+  private static byte[] genBytes(Method method) {
+    // 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.
+    switch (method.getName()) {
+      case "storage.objects.insert":
+        return DataGenerator.base64Characters().genBytes(_8MiB * 2 + _512KiB);
+      case "storage.objects.get":
+        return DataGenerator.base64Characters().genBytes(_512KiB);
+      default:
+        return "Hello, World!".getBytes(StandardCharsets.UTF_8);
+    }
+  }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketReadMaskTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketReadMaskTest.java
index e0dfdc81..4bd5c21c 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketReadMaskTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketReadMaskTest.java
@@ -19,6 +19,7 @@ package com.google.cloud.storage.it;
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.api.gax.paging.Page;
+import com.google.cloud.storage.Acl;
 import com.google.cloud.storage.Bucket;
 import com.google.cloud.storage.BucketInfo;
 import com.google.cloud.storage.Rpo;
@@ -111,8 +112,15 @@ public final class ITBucketReadMaskTest {
               new Args<>(
                   BucketField.DEFAULT_OBJECT_ACL,
                   (jsonT, grpcT) -> {
-                    assertThat(jsonT.getDefaultAcl()).isNotEmpty();
-                    assertThat(grpcT.getDefaultAcl()).isNull(); // workaround for b/261771961
+                    List<Acl> jsonDefaultAcl = jsonT.getDefaultAcl();
+                    List<Acl> grpcDefaultAcl = grpcT.getDefaultAcl();
+                    if (!(jsonDefaultAcl == null || jsonDefaultAcl.isEmpty())
+                        && !(grpcDefaultAcl == null || grpcDefaultAcl.isEmpty())) {
+                      assertThat(grpcDefaultAcl).isEqualTo(jsonDefaultAcl);
+                    } else {
+                      assertThat(jsonDefaultAcl).isNotEmpty();
+                      assertThat(grpcDefaultAcl).isNull(); // workaround for b/261771961
+                    }
                   }),
               new Args<>(BucketField.ENCRYPTION, LazyAssertion.equal()),
               new Args<>(BucketField.ETAG, LazyAssertion.equal()),
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java
index 1c5f27d7..f6b86ad4 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/TestBench.java
@@ -188,8 +188,7 @@ public final class TestBench implements ManagedLifecycle {
   @Override
   public void start() {
     try {
-      String fullContainerName = String.format("storage-testbench_%s", containerName);
-      tempDirectory = Files.createTempDirectory(fullContainerName);
+      tempDirectory = Files.createTempDirectory(containerName);
       outPath = tempDirectory.resolve("stdout");
       errPath = tempDirectory.resolve("stderr");
 
@@ -234,7 +233,7 @@ public final class TestBench implements ManagedLifecycle {
               port + ":9000",
               "--publish",
               gRPCPort + ":9090",
-              String.format("--name=%s", fullContainerName),
+              String.format("--name=%s", containerName),
               dockerImage);
       process =
           new ProcessBuilder()
@@ -290,14 +289,31 @@ public final class TestBench implements ManagedLifecycle {
     try {
       process.destroy();
       process.waitFor(5, TimeUnit.SECONDS);
-      int processExitValue = process.exitValue();
-      LOGGER.warning("Container exit value = " + processExitValue);
-
-      // Process shutdownProcess = new ProcessBuilder(ImmutableList.of("docker", "kill",
-      // containerName)).start();
-      // shutdownProcess.waitFor(5, TimeUnit.SECONDS);
-      // int shutdownProcessExitValue = shutdownProcess.exitValue();
-      // LOGGER1.info("shutdownProcessExitValue = {}", shutdownProcessExitValue);
+      boolean attemptForceStopContainer = false;
+      try {
+        int processExitValue = process.exitValue();
+        if (processExitValue != 0) {
+          attemptForceStopContainer = true;
+        }
+        System.out.println("processExitValue = " + processExitValue);
+        LOGGER.warning("Container exit value = " + processExitValue);
+      } catch (IllegalThreadStateException e) {
+        e.printStackTrace(System.out);
+        attemptForceStopContainer = true;
+      }
+
+      if (attemptForceStopContainer) {
+        LOGGER.warning("Container did not gracefully exit, attempting to explicitly stop it.");
+        System.out.println("Container did not gracefully exit, attempting to explicitly stop it.");
+        ImmutableList<String> command = ImmutableList.of("docker", "kill", containerName);
+        System.out.println("command = " + command);
+        LOGGER.log(Level.WARNING, command.toString());
+        Process shutdownProcess = new ProcessBuilder(command).start();
+        shutdownProcess.waitFor(5, TimeUnit.SECONDS);
+        int shutdownProcessExitValue = shutdownProcess.exitValue();
+        LOGGER.warning("Container exit value = " + shutdownProcessExitValue);
+      }
+
       // wait for the server to shutdown
       runWithRetries(
           () -> {
@@ -329,7 +345,7 @@ public final class TestBench implements ManagedLifecycle {
       } catch (IOException e) {
         throw new RuntimeException(e);
       }
-    } catch (InterruptedException e) {
+    } catch (InterruptedException | IOException e) {
       throw new RuntimeException(e);
     }
   }
@@ -465,7 +481,12 @@ public final class TestBench implements ManagedLifecycle {
 
     public TestBench build() {
       return new TestBench(
-          ignorePullError, baseUri, gRPCBaseUri, dockerImageName, dockerImageTag, containerName);
+          ignorePullError,
+          baseUri,
+          gRPCBaseUri,
+          dockerImageName,
+          dockerImageTag,
+          String.format("storage-testbench_%s", containerName));
     }
   }
 
diff --git a/google-cloud-storage/src/test/resources/com/google/cloud/storage/conformance/retry/hello-world.txt b/google-cloud-storage/src/test/resources/com/google/cloud/storage/conformance/retry/hello-world.txt
deleted file mode 100644
index 5ac001b7..00000000
--- a/google-cloud-storage/src/test/resources/com/google/cloud/storage/conformance/retry/hello-world.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-Hello, World!!!