Repository: googleapis/java-storage (java)

Issue:
Cloud Storage: provide option to disableGzipContent on InputStream variants
 **Is your feature request related to a problem? Please describe.**

I have a use case that necessitates using the Storage/Bucket write method variants with InputStream arguments. The inputs for my use case to store in Cloud Storage are:

* predominantly already compressed,
* have a wide range of file sizes, from a few KB to GB+,
* we don't always know the size in advance.

 **Describe the solution you'd like**

Given that our content is already compressed, I would prefer to avoid spending the CPU time on compressing the content again en route to the Bucket.

 **Describe alternatives you've considered**

We have used the `byte[]` variants, with `BlobTargetOption.disableGzipContent` and the Compose request. This is suitable but leaves us with a tuning challenge:

* Buffering these streams into memory (`byte[]` chunks) requires additional heap space be available
* Compose is limited to only 32 chunks. If the file content we intend to store is far larger than `32 * bufferSize`, we will upload 31 small sized chunks and 1 large chunk that we have to use the `InputStream` variant for anyways, and pay the additional overhead of gzip compression.

 **Additional context**

I have done some exploration and it appears that the values on the BlobWriteOption (used on `InputStream` variants) and BlobTargetOption (used on `byte[]` variants) enums both are translated into `StorageRpc.Option`. I have a small contribution to offer in the form of a pull request to follow.


Pull request description:
add disableGzipContent option for create from InputStream
 **Is your feature request related to a problem? Please describe.**

I have a use case that necessitates using the Storage/Bucket write method variants with InputStream arguments. The inputs for my use case to store in Cloud Storage are:

* predominantly already compressed,
* have a wide range of file sizes, from a few KB to GB+,
* we don't always know the size in advance.

 **Describe the solution you'd like**

Given that our content is already compressed, I would prefer to avoid spending the CPU time on compressing the content again en route to the Bucket.

 **Describe alternatives you've considered**

We have used the `byte[]` variants, with `BlobTargetOption.disableGzipContent` and the Compose request. This is suitable but leaves us with a tuning challenge:

* Buffering these streams into memory (`byte[]` chunks) requires additional heap space be available
* Compose is limited to only 32 chunks. If the file content we intend to store is far larger than `32 * bufferSize`, we will upload 31 small sized chunks and 1 large chunk that we have to use the `InputStream` variant for anyways, and pay the additional overhead of gzip compression.

 **Additional context**

I have done some exploration and it appears that the values on the BlobWriteOption (used on `InputStream` variants) and BlobTargetOption (used on `byte[]` variants) enums both are translated into `StorageRpc.Option`. I have a small contribution to offer in the form of a pull request to follow.

Fixes #36; ported from https://github.com/googleapis/google-cloud-java/pull/7057

Patch:
diff --git a/.github/trusted-contribution.yml b/.github/trusted-contribution.yml
new file mode 100644
index 00000000..f247d5c7
--- /dev/null
+++ b/.github/trusted-contribution.yml
@@ -0,0 +1,2 @@
+trustedContributors:
+- renovate-bot
\ No newline at end of file
diff --git a/.kokoro/linkage-monitor.sh b/.kokoro/linkage-monitor.sh
index 1c0664ac..158641cb 100755
--- a/.kokoro/linkage-monitor.sh
+++ b/.kokoro/linkage-monitor.sh
@@ -23,7 +23,12 @@ cd github/java-storage/
 java -version
 echo ${JOB_TYPE}
 
-mvn install -DskipTests=true -Dmaven.javadoc.skip=true -Dgcloud.download.skip=true -B -V
+mvn install -B -V \
+  -DskipTests=true \
+  -Dclirr.skip=true \
+  -Denforcer.skip=true \
+  -Dmaven.javadoc.skip=true \
+  -Dgcloud.download.skip=true
 
 # Kokoro job cloud-opensource-java/ubuntu/linkage-monitor-gcs creates this JAR
 JAR=linkage-monitor-latest-all-deps.jar
diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml
index 20c60263..29f04853 100644
--- a/google-cloud-storage/pom.xml
+++ b/google-cloud-storage/pom.xml
@@ -5,7 +5,7 @@
   <version>1.103.2-SNAPSHOT</version><!-- {x-version-update:google-cloud-storage:current} -->
   <packaging>jar</packaging>
   <name>Google Cloud Storage</name>
-  <url>https://github.com/googleapis/google-cloud-java/tree/master/google-cloud-clients/google-cloud-storage</url>
+  <url>https://github.com/googleapis/java-storage</url>
   <description>
     Java idiomatic client for Google Cloud Storage.
   </description>
@@ -34,7 +34,7 @@
     <dependency>
       <groupId>com.google.api-client</groupId>
       <artifactId>google-api-client</artifactId>
-      <version>1.30.7</version>
+      <version>1.30.8</version>
     </dependency>
     <dependency>
       <groupId>com.google.apis</groupId>
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
index f26157d9..03630c61 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java
@@ -169,7 +169,9 @@ public class SignatureInfo {
     canonicalRequest
         .append(serializer.serializeHeaderNames(canonicalizedExtensionHeaders))
         .append(COMPONENT_SEPARATOR);
-    canonicalRequest.append("UNSIGNED-PAYLOAD");
+
+    String userProvidedHash = canonicalizedExtensionHeaders.get("X-Goog-Content-SHA256");
+    canonicalRequest.append(userProvidedHash == null ? "UNSIGNED-PAYLOAD" : userProvidedHash);
 
     return Hashing.sha256()
         .hashString(canonicalRequest.toString(), StandardCharsets.UTF_8)
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
index 77c88e97..52784874 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java
@@ -161,6 +161,21 @@ public interface Storage extends Service<StorageOptions> {
     }
   }
 
+  enum UriScheme {
+    HTTP("http"),
+    HTTPS("https");
+
+    private final String scheme;
+
+    UriScheme(String scheme) {
+      this.scheme = scheme;
+    }
+
+    public String getScheme() {
+      return scheme;
+    }
+  }
+
   /** Class for specifying bucket target options. */
   class BucketTargetOption extends Option {
 
@@ -567,7 +582,8 @@ public interface Storage extends Service<StorageOptions> {
       IF_CRC32C_MATCH,
       CUSTOMER_SUPPLIED_KEY,
       KMS_KEY_NAME,
-      USER_PROJECT;
+      USER_PROJECT,
+      IF_DISABLE_GZIP_CONTENT;
 
       StorageRpc.Option toRpcOption() {
         return StorageRpc.Option.valueOf(this.name());
@@ -699,6 +715,14 @@ public interface Storage extends Service<StorageOptions> {
     public static BlobWriteOption userProject(String userProject) {
       return new BlobWriteOption(Option.USER_PROJECT, userProject);
     }
+
+    /**
+     * Returns an option that signals automatic gzip compression should not be performed en route to
+     * the bucket.
+     */
+    public static BlobWriteOption disableGzipContent() {
+      return new BlobWriteOption(Option.IF_DISABLE_GZIP_CONTENT, true);
+    }
   }
 
   /** Class for specifying blob source options. */
@@ -988,6 +1012,16 @@ public interface Storage extends Service<StorageOptions> {
       return new BlobListOption(StorageRpc.Option.DELIMITER, true);
     }
 
+    /**
+     * Returns an option to set a delimiter.
+     *
+     * @param delimiter generally '/' is the one used most often, but you can used other delimiters
+     *     as well.
+     */
+    public static BlobListOption delimiter(String delimiter) {
+      return new BlobListOption(StorageRpc.Option.DELIMITER, delimiter);
+    }
+
     /**
      * Returns an option to define the billing user project. This option is required by buckets with
      * `requester_pays` flag enabled to assign operation costs.
@@ -1038,6 +1072,7 @@ public interface Storage extends Service<StorageOptions> {
       HOST_NAME,
       PATH_STYLE,
       VIRTUAL_HOSTED_STYLE,
+      BUCKET_BOUND_HOST_NAME,
       QUERY_PARAMS
     }
 
@@ -1160,6 +1195,44 @@ public interface Storage extends Service<StorageOptions> {
       return new SignUrlOption(Option.PATH_STYLE, "");
     }
 
+    /**
+     * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
+     * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
+     * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
+     * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. This
+     * method signature uses HTTP for the URI scheme, and is equivalent to calling {@code
+     * withBucketBoundHostname("...", UriScheme.HTTP).}
+     *
+     * @see <a href="https://cloud.google.com/storage/docs/request-endpoints#cname">CNAME
+     *     Redirects</a>
+     * @see <a
+     *     href="https://cloud.google.com/load-balancing/docs/https/adding-backend-buckets-to-load-balancers">
+     *     GCLB Redirects</a>
+     */
+    public static SignUrlOption withBucketBoundHostname(String bucketBoundHostname) {
+      return withBucketBoundHostname(bucketBoundHostname, UriScheme.HTTP);
+    }
+
+    /**
+     * Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of
+     * a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load
+     * Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this
+     * cannot be used alongside {@code withVirtualHostedStyle()} or {@code withPathStyle()}. The
+     * bucket name itself should not include the URI scheme (http or https), so it is specified via
+     * a local enum.
+     *
+     * @see <a href="https://cloud.google.com/storage/docs/request-endpoints#cname">CNAME
+     *     Redirects</a>
+     * @see <a
+     *     href="https://cloud.google.com/load-balancing/docs/https/adding-backend-buckets-to-load-balancers">
+     *     GCLB Redirects</a>
+     */
+    public static SignUrlOption withBucketBoundHostname(
+        String bucketBoundHostname, UriScheme uriScheme) {
+      return new SignUrlOption(
+          Option.BUCKET_BOUND_HOST_NAME, uriScheme.getScheme() + "://" + bucketBoundHostname);
+    }
+
     /**
      * Use if the URL should contain additional query parameters.
      *
diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
index 63c19855..0f38e382 100644
--- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
+++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java
@@ -660,8 +660,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
 
     checkArgument(
         !(optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)
-            && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)),
-        "Cannot specify both the VIRTUAL_HOSTED_STYLE and PATH_STYLE SignUrlOptions together.");
+            && optionMap.containsKey(SignUrlOption.Option.PATH_STYLE)
+            && optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)),
+        "Only one of VIRTUAL_HOSTED_STYLE, PATH_STYLE, or BUCKET_BOUND_HOST_NAME SignUrlOptions can be"
+            + " specified.");
 
     String bucketName = slashlessBucketNameFromBlobInfo(blobInfo);
     String escapedBlobName = "";
@@ -676,6 +678,10 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
             ? STORAGE_XML_URI_SCHEME + "://" + getBaseStorageHostName(optionMap)
             : STORAGE_XML_URI_SCHEME + "://" + bucketName + "." + getBaseStorageHostName(optionMap);
 
+    if (optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
+      storageXmlHostName = (String) optionMap.get(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME);
+    }
+
     String stPath =
         usePathStyle
             ? constructResourceUriPath(bucketName, escapedBlobName, optionMap)
@@ -753,9 +759,7 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
       }
       return pathBuilder.toString();
     }
-    if (!escapedBlobName.startsWith(PATH_DELIMITER)) {
-      pathBuilder.append(PATH_DELIMITER);
-    }
+    pathBuilder.append(PATH_DELIMITER);
     pathBuilder.append(escapedBlobName);
     return pathBuilder.toString();
   }
@@ -776,7 +780,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   private boolean shouldUsePathStyleForSignedUrl(EnumMap<SignUrlOption.Option, Object> optionMap) {
     // TODO(#6362): If we decide to change the default style used to generate URLs, switch this
     // logic to return false unless PATH_STYLE was explicitly specified.
-    if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)) {
+    if (optionMap.containsKey(SignUrlOption.Option.VIRTUAL_HOSTED_STYLE)
+        || optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
       return false;
     }
     return true;
@@ -836,7 +841,8 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
         extHeadersBuilder.put(
             "host",
             slashlessBucketNameFromBlobInfo(blobInfo) + "." + getBaseStorageHostName(optionMap));
-      } else if (optionMap.containsKey(SignUrlOption.Option.HOST_NAME)) {
+      } else if (optionMap.containsKey(SignUrlOption.Option.HOST_NAME)
+          || optionMap.containsKey(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME)) {
         extHeadersBuilder.put("host", getBaseStorageHostName(optionMap));
       }
     }
@@ -868,9 +874,14 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
   /** Returns the hostname used to send requests to Cloud Storage, e.g. "storage.googleapis.com". */
   private String getBaseStorageHostName(Map<SignUrlOption.Option, Object> optionMap) {
     String specifiedBaseHostName = (String) optionMap.get(SignUrlOption.Option.HOST_NAME);
+    String bucketBoundHostName =
+        (String) optionMap.get(SignUrlOption.Option.BUCKET_BOUND_HOST_NAME);
     if (!Strings.isNullOrEmpty(specifiedBaseHostName)) {
       return specifiedBaseHostName.replaceFirst("http(s)?://", "");
     }
+    if (!Strings.isNullOrEmpty(bucketBoundHostName)) {
+      return bucketBoundHostName.replaceFirst("http(s)?://", "");
+    }
     return STORAGE_XML_URI_HOST_NAME;
   }
 
@@ -1583,9 +1594,11 @@ final class StorageImpl extends BaseService<StorageOptions> implements Storage {
       Object prev = temp.put(option.getRpcOption(), option.getValue());
       checkArgument(prev == null, "Duplicate option %s", option);
     }
-    Boolean value = (Boolean) temp.remove(DELIMITER);
-    if (Boolean.TRUE.equals(value)) {
+    if (Boolean.TRUE.equals(temp.get(DELIMITER))) {
+      temp.remove(DELIMITER);
       temp.put(DELIMITER, PATH_DELIMITER);
+    } else if (null != temp.get(DELIMITER)) {
+      temp.put(DELIMITER, temp.get(DELIMITER));
     }
     if (useAsSource) {
       addToOptionMap(IF_GENERATION_MATCH, IF_SOURCE_GENERATION_MATCH, generation, temp);
diff --git a/pom.xml b/pom.xml
index 4663b72c..76dd3d6c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,7 +69,7 @@
     <threeten.version>1.4.1</threeten.version>
     <javax.annotations.version>1.3.2</javax.annotations.version>
     <animal-sniffer.version>1.18</animal-sniffer.version>
-    <opencensus.version>0.24.0</opencensus.version>
+    <opencensus.version>0.25.0</opencensus.version>
   </properties>
 
   <dependencyManagement>
@@ -103,21 +103,21 @@
       <dependency>
         <groupId>io.grpc</groupId>
         <artifactId>grpc-bom</artifactId>
-        <version>1.26.0</version>
+        <version>1.27.0</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <dependency>
         <groupId>com.google.api</groupId>
         <artifactId>gax-bom</artifactId>
-        <version>1.53.0</version>
+        <version>1.53.1</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <dependency>
         <groupId>com.google.protobuf</groupId>
         <artifactId>protobuf-bom</artifactId>
-        <version>3.11.1</version>
+        <version>3.11.3</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
@@ -212,7 +212,7 @@
       <dependency>
         <groupId>com.google.cloud</groupId>
         <artifactId>google-cloud-conformance-tests</artifactId>
-        <version>0.0.4</version>
+        <version>0.0.5</version>
         <scope>test</scope>
       </dependency>
     </dependencies>
@@ -225,7 +225,10 @@
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-dependency-plugin</artifactId>
           <configuration>
-            <ignoredUnusedDeclaredDependencies>org.objenesis:objenesis</ignoredUnusedDeclaredDependencies>
+            <ignoredUnusedDeclaredDependencies>
+              <ignoredUnusedDeclaredDependency>org.hamcrest:hamcrest</ignoredUnusedDeclaredDependency>
+              <ignoredUnusedDeclaredDependency>org.objenesis:objenesis</ignoredUnusedDeclaredDependency>
+            </ignoredUnusedDeclaredDependencies>
           </configuration>
         </plugin>
       </plugins>
diff --git a/renovate.json b/renovate.json
index a5cdff45..268a4669 100644
--- a/renovate.json
+++ b/renovate.json
@@ -22,7 +22,8 @@
         "^com.google.api:gax",
         "^com.google.auth:",
         "^com.google.cloud:google-cloud-core",
-        "^io.grpc:"
+        "^io.grpc:",
+        "^com.google.guava:"
       ],
       "groupName": "core dependencies"
     },
diff --git a/synth.metadata b/synth.metadata
index 36429ca6..cb135d8a 100644
--- a/synth.metadata
+++ b/synth.metadata
@@ -1,5 +1,5 @@
 {
-  "updateTime": "2020-01-16T09:02:51.520248Z",
+  "updateTime": "2020-02-01T09:02:33.355460Z",
   "sources": [
     {
       "template": {
@@ -8,421 +8,5 @@
         "version": "2019.10.17"
       }
     }
-  ],
-  "newFiles": [
-    {
-      "path": "pom.xml"
-    },
-    {
-      "path": ".repo-metadata.json"
-    },
-    {
-      "path": "java.header"
-    },
-    {
-      "path": "CONTRIBUTING.md"
-    },
-    {
-      "path": "synth.metadata"
-    },
-    {
-      "path": "renovate.json"
-    },
-    {
-      "path": "versions.txt"
-    },
-    {
-      "path": "synth.py"
-    },
-    {
-      "path": "license-checks.xml"
-    },
-    {
-      "path": "CHANGELOG.md"
-    },
-    {
-      "path": "CODE_OF_CONDUCT.md"
-    },
-    {
-      "path": "LICENSE"
-    },
-    {
-      "path": "codecov.yaml"
-    },
-    {
-      "path": ".gitignore"
-    },
-    {
-      "path": "README.md"
-    },
-    {
-      "path": ".github/release-please.yml"
-    },
-    {
-      "path": ".github/PULL_REQUEST_TEMPLATE.md"
-    },
-    {
-      "path": ".github/ISSUE_TEMPLATE/feature_request.md"
-    },
-    {
-      "path": ".github/ISSUE_TEMPLATE/support_request.md"
-    },
-    {
-      "path": ".github/ISSUE_TEMPLATE/bug_report.md"
-    },
-    {
-      "path": ".kokoro/build.sh"
-    },
-    {
-      "path": ".kokoro/common.cfg"
-    },
-    {
-      "path": ".kokoro/build.bat"
-    },
-    {
-      "path": ".kokoro/linkage-monitor.sh"
-    },
-    {
-      "path": ".kokoro/coerce_logs.sh"
-    },
-    {
-      "path": ".kokoro/dependencies.sh"
-    },
-    {
-      "path": ".kokoro/trampoline.sh"
-    },
-    {
-      "path": ".kokoro/continuous/samples.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java8.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/integration.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java11.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/propose_release.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/common.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/propose_release.sh"
-    },
-    {
-      "path": ".kokoro/continuous/java7.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/lint.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java8-osx.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/dependencies.cfg"
-    },
-    {
-      "path": ".kokoro/continuous/java8-win.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/samples.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java8.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/integration.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java11.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/common.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java7.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/lint.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java8-osx.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/dependencies.cfg"
-    },
-    {
-      "path": ".kokoro/nightly/java8-win.cfg"
-    },
-    {
-      "path": ".kokoro/release/publish_javadoc.cfg"
-    },
-    {
-      "path": ".kokoro/release/common.sh"
-    },
-    {
-      "path": ".kokoro/release/promote.sh"
-    },
-    {
-      "path": ".kokoro/release/common.cfg"
-    },
-    {
-      "path": ".kokoro/release/publish_javadoc.sh"
-    },
-    {
-      "path": ".kokoro/release/drop.sh"
-    },
-    {
-      "path": ".kokoro/release/stage.sh"
-    },
-    {
-      "path": ".kokoro/release/promote.cfg"
-    },
-    {
-      "path": ".kokoro/release/snapshot.cfg"
-    },
-    {
-      "path": ".kokoro/release/bump_snapshot.cfg"
-    },
-    {
-      "path": ".kokoro/release/drop.cfg"
-    },
-    {
-      "path": ".kokoro/release/bump_snapshot.sh"
-    },
-    {
-      "path": ".kokoro/release/stage.cfg"
-    },
-    {
-      "path": ".kokoro/release/snapshot.sh"
-    },
-    {
-      "path": ".kokoro/presubmit/samples.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java8.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/integration.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java11.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/linkage-monitor.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/common.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java7.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/lint.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java8-osx.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/dependencies.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/clirr.cfg"
-    },
-    {
-      "path": ".kokoro/presubmit/java8-win.cfg"
-    },
-    {
-      "path": "google-cloud-storage/pom.xml"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/SerializationTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobIdTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchResultTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/OptionTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CorsTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/ServiceAccountTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/AclTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageExceptionTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobReadChannelTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CopyWriterTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageBatchTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/CopyRequestTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/testing/RemoteStorageHelperTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java"
-    },
-    {
-      "path": "google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcherTest.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Acl.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageClass.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageRoles.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageException.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/package-info.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageFactory.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/ServiceAccount.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/CopyWriter.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatch.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Cors.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobReadChannel.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobWriteChannel.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/HttpMethod.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/StorageBatchResult.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/Option.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/BlobId.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/SignedUrlEncodingHelper.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/testing/package-info.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/StorageRpcFactory.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/RpcBatch.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java"
-    },
-    {
-      "path": "google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java"
-    }
   ]
 }
\ No newline at end of file


Test patch:
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
index d3cd439d..22031260 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobWriteChannelTest.java
@@ -27,6 +27,7 @@ import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -46,10 +47,9 @@ import java.util.Random;
 import org.easymock.Capture;
 import org.easymock.CaptureType;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
 public class BlobWriteChannelTest {
 
@@ -65,8 +65,6 @@ public class BlobWriteChannelTest {
   private static final String SIGNED_URL =
       "http://www.test.com/test-bucket/test1.txt?GoogleAccessId=testClient-test@test.com&Expires=1553839761&Signature=MJUBXAZ7";
 
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
   private StorageOptions options;
   private StorageRpcFactory rpcFactoryMock;
   private StorageRpc storageRpcMock;
@@ -113,8 +111,12 @@ public class BlobWriteChannelTest {
     expect(storageRpcMock.open(BLOB_INFO.toPb(), EMPTY_RPC_OPTIONS))
         .andThrow(new RuntimeException());
     replay(storageRpcMock);
-    thrown.expect(RuntimeException.class);
-    new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
+    try {
+      new BlobWriteChannel(options, BLOB_INFO, EMPTY_RPC_OPTIONS);
+      Assert.fail();
+    } catch (RuntimeException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
@@ -391,9 +393,12 @@ public class BlobWriteChannelTest {
     expect(new BlobWriteChannel(options, new URL(SIGNED_URL)))
         .andThrow(new RuntimeException(exceptionMessage));
     replay(storageRpcMock);
-    thrown.expect(StorageException.class);
-    thrown.expectMessage(exceptionMessage);
-    writer = new BlobWriteChannel(options, new URL(SIGNED_URL));
+    try {
+      writer = new BlobWriteChannel(options, new URL(SIGNED_URL));
+      Assert.fail();
+    } catch (StorageException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   private static ByteBuffer randomBuffer(int size) {
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java
index 9923c610..a96422f1 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java
@@ -24,6 +24,7 @@ import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
@@ -52,10 +53,9 @@ import java.util.List;
 import java.util.Map;
 import javax.crypto.spec.SecretKeySpec;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
 public class BucketTest {
 
@@ -135,8 +135,6 @@ public class BucketTest {
   private Bucket expectedBucket;
   private List<Blob> blobResults;
 
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
   @Before
   public void setUp() {
     storage = createStrictMock(Storage.class);
@@ -446,15 +444,17 @@ public class BucketTest {
     replay(storage);
     initializeBucket();
     byte[] content = {0xD, 0xE, 0xA, 0xD};
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage(
-        "Only one option of generationMatch, doesNotExist or generationNotMatch can be provided");
-    bucket.create(
-        "n",
-        content,
-        CONTENT_TYPE,
-        Bucket.BlobTargetOption.generationMatch(42L),
-        Bucket.BlobTargetOption.generationNotMatch(24L));
+    try {
+      bucket.create(
+          "n",
+          content,
+          CONTENT_TYPE,
+          Bucket.BlobTargetOption.generationMatch(42L),
+          Bucket.BlobTargetOption.generationNotMatch(24L));
+      Assert.fail();
+    } catch (IllegalArgumentException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
@@ -464,15 +464,17 @@ public class BucketTest {
     replay(storage);
     initializeBucket();
     byte[] content = {0xD, 0xE, 0xA, 0xD};
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage(
-        "metagenerationMatch and metagenerationNotMatch options can not be both provided");
-    bucket.create(
-        "n",
-        content,
-        CONTENT_TYPE,
-        Bucket.BlobTargetOption.metagenerationMatch(42L),
-        Bucket.BlobTargetOption.metagenerationNotMatch(24L));
+    try {
+      bucket.create(
+          "n",
+          content,
+          CONTENT_TYPE,
+          Bucket.BlobTargetOption.metagenerationMatch(42L),
+          Bucket.BlobTargetOption.metagenerationNotMatch(24L));
+      Assert.fail();
+    } catch (IllegalArgumentException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
@@ -592,15 +594,17 @@ public class BucketTest {
     initializeBucket();
     byte[] content = {0xD, 0xE, 0xA, 0xD};
     InputStream streamContent = new ByteArrayInputStream(content);
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage(
-        "Only one option of generationMatch, doesNotExist or generationNotMatch can be provided");
-    bucket.create(
-        "n",
-        streamContent,
-        CONTENT_TYPE,
-        Bucket.BlobWriteOption.generationMatch(42L),
-        Bucket.BlobWriteOption.generationNotMatch(24L));
+    try {
+      bucket.create(
+          "n",
+          streamContent,
+          CONTENT_TYPE,
+          Bucket.BlobWriteOption.generationMatch(42L),
+          Bucket.BlobWriteOption.generationNotMatch(24L));
+      Assert.fail();
+    } catch (IllegalArgumentException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
@@ -611,15 +615,17 @@ public class BucketTest {
     initializeBucket();
     byte[] content = {0xD, 0xE, 0xA, 0xD};
     InputStream streamContent = new ByteArrayInputStream(content);
-    thrown.expect(IllegalArgumentException.class);
-    thrown.expectMessage(
-        "metagenerationMatch and metagenerationNotMatch options can not be both provided");
-    bucket.create(
-        "n",
-        streamContent,
-        CONTENT_TYPE,
-        Bucket.BlobWriteOption.metagenerationMatch(42L),
-        Bucket.BlobWriteOption.metagenerationNotMatch(24L));
+    try {
+      bucket.create(
+          "n",
+          streamContent,
+          CONTENT_TYPE,
+          Bucket.BlobWriteOption.metagenerationMatch(42L),
+          Bucket.BlobWriteOption.metagenerationNotMatch(24L));
+      Assert.fail();
+    } catch (IllegalArgumentException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
index 6e698123..5c809c7b 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java
@@ -86,11 +86,10 @@ import javax.crypto.spec.SecretKeySpec;
 import org.easymock.Capture;
 import org.easymock.EasyMock;
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 
 public class StorageImplTest {
 
@@ -384,8 +383,6 @@ public class StorageImplTest {
   private Blob expectedBlob1, expectedBlob2, expectedBlob3;
   private Bucket expectedBucket1, expectedBucket2, expectedBucket3;
 
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
   @BeforeClass
   public static void beforeClass() throws NoSuchAlgorithmException, InvalidKeySpecException {
     KeyFactory keyFactory = KeyFactory.getInstance("RSA");
@@ -741,6 +738,33 @@ public class StorageImplTest {
     assertEquals(-1, byteStream.read(streamBytes));
   }
 
+  @Test
+  public void testCreateBlobFromStreamDisableGzipContent() throws IOException {
+    Capture<ByteArrayInputStream> capturedStream = Capture.newInstance();
+
+    ByteArrayInputStream fileStream = new ByteArrayInputStream(BLOB_CONTENT);
+    BlobInfo.Builder infoBuilder = BLOB_INFO1.toBuilder();
+    BlobInfo infoWithHashes = infoBuilder.setMd5(CONTENT_MD5).setCrc32c(CONTENT_CRC32C).build();
+    BlobInfo infoWithoutHashes = infoBuilder.setMd5(null).setCrc32c(null).build();
+    EasyMock.expect(
+            storageRpcMock.create(
+                EasyMock.eq(infoWithoutHashes.toPb()),
+                EasyMock.capture(capturedStream),
+                EasyMock.eq(BLOB_TARGET_OPTIONS_CREATE_DISABLE_GZIP_CONTENT)))
+        .andReturn(BLOB_INFO1.toPb());
+    EasyMock.replay(storageRpcMock);
+    initializeService();
+
+    Blob blob = storage.create(infoWithHashes, fileStream, BlobWriteOption.disableGzipContent());
+
+    assertEquals(expectedBlob1, blob);
+    ByteArrayInputStream byteStream = capturedStream.getValue();
+    byte[] streamBytes = new byte[BLOB_CONTENT.length];
+    assertEquals(BLOB_CONTENT.length, byteStream.read(streamBytes));
+    assertArrayEquals(BLOB_CONTENT, streamBytes);
+    assertEquals(-1, byteStream.read(streamBytes));
+  }
+
   @Test
   public void testCreateBlobFromStreamWithEncryptionKey() throws IOException {
     ByteArrayInputStream fileStream = new ByteArrayInputStream(BLOB_CONTENT);
@@ -785,9 +809,12 @@ public class StorageImplTest {
 
     // Even though this exception is retryable, storage.create(BlobInfo, InputStream)
     // shouldn't retry.
-    thrown.expect(StorageException.class);
-    thrown.expectMessage("internalError");
-    storage.create(infoWithHashes, fileStream);
+    try {
+      storage.create(infoWithHashes, fileStream);
+      Assert.fail();
+    } catch (StorageException ex) {
+      assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
@@ -1166,6 +1193,23 @@ public class StorageImplTest {
     assertArrayEquals(blobList.toArray(), Iterables.toArray(page.getValues(), Blob.class));
   }
 
+  @Test
+  public void testListBlobsDelimiter() {
+    String cursor = "cursor";
+    String delimiter = "/";
+    Map<StorageRpc.Option, ?> options = ImmutableMap.of(StorageRpc.Option.DELIMITER, delimiter);
+    ImmutableList<BlobInfo> blobInfoList = ImmutableList.of(BLOB_INFO1, BLOB_INFO2);
+    Tuple<String, Iterable<com.google.api.services.storage.model.StorageObject>> result =
+        Tuple.of(cursor, Iterables.transform(blobInfoList, BlobInfo.INFO_TO_PB_FUNCTION));
+    EasyMock.expect(storageRpcMock.list(BUCKET_NAME1, options)).andReturn(result);
+    EasyMock.replay(storageRpcMock);
+    initializeService();
+    ImmutableList<Blob> blobList = ImmutableList.of(expectedBlob1, expectedBlob2);
+    Page<Blob> page = storage.list(BUCKET_NAME1, Storage.BlobListOption.delimiter(delimiter));
+    assertEquals(cursor, page.getNextPageToken());
+    assertArrayEquals(blobList.toArray(), Iterables.toArray(page.getValues(), Blob.class));
+  }
+
   @Test
   public void testUpdateBucket() {
     BucketInfo updatedBucketInfo = BUCKET_INFO1.toBuilder().setIndexPage("some-page").build();
@@ -1808,6 +1852,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://storage.googleapis.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(expectedResourcePath)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -1825,6 +1870,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(expectedResourcePath);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -1857,6 +1903,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://example.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(escapedBlobName)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -1874,6 +1921,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(escapedBlobName);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2019,6 +2067,7 @@ public class StorageImplTest {
       String expectedUrl =
           new StringBuilder("https://storage.googleapis.com/")
               .append(BUCKET_NAME1)
+              .append("/")
               .append(expectedBlobName)
               .append("?GoogleAccessId=")
               .append(ACCOUNT)
@@ -2036,6 +2085,7 @@ public class StorageImplTest {
           .append(42L + 1209600)
           .append("\n/")
           .append(BUCKET_NAME1)
+          .append("/")
           .append(expectedBlobName);
 
       Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2075,6 +2125,7 @@ public class StorageImplTest {
       String expectedUrl =
           new StringBuilder("https://example.com/")
               .append(BUCKET_NAME1)
+              .append("/")
               .append(expectedBlobName)
               .append("?GoogleAccessId=")
               .append(ACCOUNT)
@@ -2092,6 +2143,7 @@ public class StorageImplTest {
           .append(42L + 1209600)
           .append("\n/")
           .append(BUCKET_NAME1)
+          .append("/")
           .append(expectedBlobName);
 
       Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2243,6 +2295,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://storage.googleapis.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(escapedBlobName)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -2260,6 +2313,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(escapedBlobName);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2293,6 +2347,7 @@ public class StorageImplTest {
     String expectedUrl =
         new StringBuilder("https://example.com/")
             .append(BUCKET_NAME1)
+            .append("/")
             .append(escapedBlobName)
             .append("?GoogleAccessId=")
             .append(ACCOUNT)
@@ -2310,6 +2365,7 @@ public class StorageImplTest {
         .append(42L + 1209600)
         .append("\n/")
         .append(BUCKET_NAME1)
+        .append("/")
         .append(escapedBlobName);
 
     Signature signer = Signature.getInstance("SHA256withRSA");
@@ -2973,9 +3029,12 @@ public class StorageImplTest {
             .build()
             .getService();
     initializeServiceDependentObjects();
-    thrown.expect(StorageException.class);
-    thrown.expectMessage(exceptionMessage);
-    storage.get(blob);
+    try {
+      storage.get(blob);
+      Assert.fail();
+    } catch (StorageException ex) {
+      Assert.assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
@@ -2991,9 +3050,12 @@ public class StorageImplTest {
             .setRetrySettings(ServiceOptions.getDefaultRetrySettings())
             .build()
             .getService();
-    thrown.expect(StorageException.class);
-    thrown.expectMessage(exceptionMessage);
-    storage.get(blob);
+    try {
+      storage.get(blob);
+      Assert.fail();
+    } catch (StorageException ex) {
+      Assert.assertNotNull(ex.getMessage());
+    }
   }
 
   @Test
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsTest.java
index cee705c4..18ff0343 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsTest.java
@@ -29,7 +29,8 @@ public class StorageOptionsTest {
       StorageOptions.newBuilder()
           .setTransportOptions(EasyMock.<TransportOptions>createMock(TransportOptions.class));
       Assert.fail();
-    } catch (IllegalArgumentException expected) {
+    } catch (IllegalArgumentException ex) {
+      Assert.assertNotNull(ex.getMessage());
     }
   }
 }
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java
index 8e1ad310..d45cb6dc 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/V4SigningTest.java
@@ -16,11 +16,8 @@
 
 package com.google.cloud.storage;
 
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNot.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeThat;
 
 import com.google.api.core.ApiClock;
 import com.google.auth.oauth2.ServiceAccountCredentials;
@@ -94,11 +91,6 @@ public class V4SigningTest {
 
   @Test
   public void test() {
-    assumeThat(
-        "Test skipped until b/136171758 is resolved",
-        testName.getMethodName(),
-        is(not("test[Headers should be trimmed]")));
-
     Storage storage =
         RemoteStorageHelper.create()
             .getOptions()
@@ -110,6 +102,19 @@ public class V4SigningTest {
 
     BlobInfo blob = BlobInfo.newBuilder(testData.getBucket(), testData.getObject()).build();
 
+    SignUrlOption style = SignUrlOption.withPathStyle();
+
+    if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.VIRTUAL_HOSTED_STYLE)) {
+      style = SignUrlOption.withVirtualHostedStyle();
+    } else if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.PATH_STYLE)) {
+      style = SignUrlOption.withPathStyle();
+    } else if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.BUCKET_BOUND_DOMAIN)) {
+      style =
+          SignUrlOption.withBucketBoundHostname(
+              testData.getBucketBoundDomain(),
+              Storage.UriScheme.valueOf(testData.getScheme().toUpperCase()));
+    }
+
     final String signedUrl =
         storage
             .signUrl(
@@ -118,7 +123,9 @@ public class V4SigningTest {
                 TimeUnit.SECONDS,
                 SignUrlOption.httpMethod(HttpMethod.valueOf(testData.getMethod())),
                 SignUrlOption.withExtHeaders(testData.getHeadersMap()),
-                SignUrlOption.withV4Signature())
+                SignUrlOption.withV4Signature(),
+                SignUrlOption.withQueryParams(testData.getQueryParametersMap()),
+                style)
             .toString();
     assertEquals(testData.getExpectedUrl(), signedUrl);
   }
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 e1788e6b..7aad8a74 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
@@ -69,6 +69,7 @@ import com.google.cloud.storage.HttpMethod;
 import com.google.cloud.storage.ServiceAccount;
 import com.google.cloud.storage.Storage;
 import com.google.cloud.storage.Storage.BlobField;
+import com.google.cloud.storage.Storage.BlobWriteOption;
 import com.google.cloud.storage.Storage.BucketField;
 import com.google.cloud.storage.StorageBatch;
 import com.google.cloud.storage.StorageBatchResult;
@@ -597,6 +598,20 @@ public class ITStorageTest {
     assertEquals(BLOB_STRING_CONTENT, new String(readBytes, UTF_8));
   }
 
+  @Test
+  public void testCreateBlobStreamDisableGzipContent() {
+    String blobName = "test-create-blob-stream-disable-gzip-compression";
+    BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).setContentType(CONTENT_TYPE).build();
+    ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8));
+    Blob remoteBlob = storage.create(blob, stream, BlobWriteOption.disableGzipContent());
+    assertNotNull(remoteBlob);
+    assertEquals(blob.getBucket(), remoteBlob.getBucket());
+    assertEquals(blob.getName(), remoteBlob.getName());
+    assertEquals(blob.getContentType(), remoteBlob.getContentType());
+    byte[] readBytes = storage.readAllBytes(BUCKET, blobName);
+    assertEquals(BLOB_STRING_CONTENT, new String(readBytes, UTF_8));
+  }
+
   @Test
   public void testCreateBlobFail() {
     String blobName = "test-create-blob-fail";
diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/RemoteStorageHelperTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/RemoteStorageHelperTest.java
index 98fbe974..44cb82c0 100644
--- a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/RemoteStorageHelperTest.java
+++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/RemoteStorageHelperTest.java
@@ -18,6 +18,7 @@ package com.google.cloud.storage.testing;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import com.google.api.gax.paging.Page;
@@ -37,10 +38,9 @@ import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import org.easymock.EasyMock;
+import org.junit.Assert;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
-import org.junit.rules.ExpectedException;
 import org.threeten.bp.Duration;
 
 public class RemoteStorageHelperTest {
@@ -86,8 +86,6 @@ public class RemoteStorageHelperTest {
   private List<Blob> blobList;
   private Page<Blob> blobPage;
 
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
   @Before
   public void setUp() {
     blob1 = EasyMock.createMock(Blob.class);
@@ -175,9 +173,11 @@ public class RemoteStorageHelperTest {
         .andReturn(blobPage);
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(FATAL_EXCEPTION);
     EasyMock.replay(storageMock, blob1, blob2);
-    thrown.expect(ExecutionException.class);
     try {
       RemoteStorageHelper.forceDelete(storageMock, BUCKET_NAME, 5, TimeUnit.SECONDS);
+      Assert.fail();
+    } catch (ExecutionException ex) {
+      assertNotNull(ex.getMessage());
     } finally {
       EasyMock.verify(storageMock);
     }
@@ -213,9 +213,11 @@ public class RemoteStorageHelperTest {
         .andReturn(blobPage);
     EasyMock.expect(storageMock.delete(BUCKET_NAME)).andThrow(FATAL_EXCEPTION);
     EasyMock.replay(storageMock, blob1, blob2);
-    thrown.expect(StorageException.class);
     try {
       RemoteStorageHelper.forceDelete(storageMock, BUCKET_NAME);
+      Assert.fail();
+    } catch (StorageException ex) {
+      assertNotNull(ex.getMessage());
     } finally {
       EasyMock.verify(storageMock);
     }