aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorGravatar ccalvarin <ccalvarin@google.com>2018-08-13 10:31:15 -0700
committerGravatar Copybara-Service <copybara-piper@google.com>2018-08-13 10:33:11 -0700
commit37c70a125a0282d331a1ec92397b750d50367551 (patch)
tree6a93a272bbed696544410a13405255d66aa5cddd /src
parent6ab4e16b89aca166fdee85628e264eb6ca5288af (diff)
Add easy access to MessageDigests in DigestHashFunction
Move the message-digest cloning to DigestHashFunction and out of Fingerprint, to make it possible to configure Fingerprint to use different hash functions. We keep the default MD5 for now, we'd like it to use the global default but want to isolate the configuration change from any change adding potential contention. RELNOTES: None. PiperOrigin-RevId: 208502993
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/exec/BinTools.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/remote/util/DigestUtil.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/skyframe/ConfigurationFragmentValue.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/util/Fingerprint.java80
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/DigestHashFunction.java99
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/Path.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionGlobalsTest.java (renamed from src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionTest.java)28
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionsTest.java61
13 files changed, 199 insertions, 94 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
index e4db5a4ebe..a760fd9aa4 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -189,7 +189,7 @@ public final class BuildOptions implements Cloneable, Serializable {
}
public String computeChecksum() {
- return Fingerprint.md5Digest(computeCacheKey());
+ return Fingerprint.getHexDigest(computeCacheKey());
}
/** String representation of build options. */
diff --git a/src/main/java/com/google/devtools/build/lib/exec/BinTools.java b/src/main/java/com/google/devtools/build/lib/exec/BinTools.java
index 46991b3f85..a24ba60812 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/BinTools.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/BinTools.java
@@ -262,7 +262,7 @@ public final class BinTools {
private static FileArtifactValue hash(Path path) throws IOException {
DigestHashFunction hashFn = path.getFileSystem().getDigestFunction();
- Hasher hasher = hashFn.getHash().newHasher();
+ Hasher hasher = hashFn.getHashFunction().newHasher();
int bytesCopied = 0;
try (InputStream in = path.getInputStream()) {
byte[] buffer = new byte[1024];
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java b/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java
index 1b5e82096e..4fe3c31711 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java
@@ -221,7 +221,7 @@ public class SpawnLogContext implements ActionContext {
((VirtualActionInput) input).writeTo(buffer);
byte[] blob = buffer.toByteArray();
return digest
- .setHash(hashFunction.getHash().hashBytes(blob).toString())
+ .setHash(hashFunction.getHashFunction().hashBytes(blob).toString())
.setSizeBytes(blob.length)
.build();
}
diff --git a/src/main/java/com/google/devtools/build/lib/remote/util/DigestUtil.java b/src/main/java/com/google/devtools/build/lib/remote/util/DigestUtil.java
index 5759568e79..bbe1da1d2d 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/util/DigestUtil.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/util/DigestUtil.java
@@ -59,7 +59,7 @@ public class DigestUtil {
}
public Digest compute(byte[] blob) {
- return buildDigest(hashFn.getHash().hashBytes(blob).toString(), blob.length);
+ return buildDigest(hashFn.getHashFunction().hashBytes(blob).toString(), blob.length);
}
public Digest compute(Path file) throws IOException {
@@ -114,7 +114,7 @@ public class DigestUtil {
}
public HashingOutputStream newHashingOutputStream(OutputStream out) {
- return new HashingOutputStream(hashFn.getHash(), out);
+ return new HashingOutputStream(hashFn.getHashFunction(), out);
}
public String toString(Digest digest) {
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
index a177598036..0e0ba2d8bf 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/SandboxModule.java
@@ -86,8 +86,11 @@ public final class SandboxModule extends BlazeModule {
if (options.sandboxBase.isEmpty()) {
return env.getOutputBase().getRelative("sandbox");
} else {
- String dirName = String.format("%s-sandbox.%s", env.getRuntime().getProductName(),
- Fingerprint.md5Digest(env.getOutputBase().toString()));
+ String dirName =
+ String.format(
+ "%s-sandbox.%s",
+ env.getRuntime().getProductName(),
+ Fingerprint.getHexDigest(env.getOutputBase().toString()));
FileSystem fileSystem = env.getRuntime().getFileSystem();
Path resolvedSandboxBase = fileSystem.getPath(options.sandboxBase).resolveSymbolicLinks();
return resolvedSandboxBase.getRelative(dirName);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ConfigurationFragmentValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/ConfigurationFragmentValue.java
index 648cd67d0c..e6e15616a5 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ConfigurationFragmentValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ConfigurationFragmentValue.java
@@ -72,7 +72,7 @@ public class ConfigurationFragmentValue implements SkyValue {
private ConfigurationFragmentKey(
BuildOptions buildOptions, Class<? extends Fragment> fragmentType) {
this.buildOptions = Preconditions.checkNotNull(buildOptions);
- this.checksum = Fingerprint.md5Digest(buildOptions.computeCacheKey());
+ this.checksum = Fingerprint.getHexDigest(buildOptions.computeCacheKey());
this.fragmentType = Preconditions.checkNotNull(fragmentType);
}
diff --git a/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java b/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java
index a2bafa6f6a..556bafdb0a 100644
--- a/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java
+++ b/src/main/java/com/google/devtools/build/lib/util/Fingerprint.java
@@ -15,6 +15,7 @@
package com.google.devtools.build.lib.util;
import com.google.common.io.ByteStreams;
+import com.google.devtools.build.lib.vfs.DigestHashFunction;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.protobuf.CodedOutputStream;
@@ -23,47 +24,45 @@ import java.nio.charset.StandardCharsets;
import java.security.DigestException;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.UUID;
import java.util.function.Consumer;
import javax.annotation.Nullable;
/**
- * Simplified wrapper for MD5 message digests.
+ * Simplified wrapper for computing message digests.
*
* @see java.security.MessageDigest
*/
public final class Fingerprint implements Consumer<String> {
- private static final MessageDigest MD5_PROTOTYPE;
- private static final boolean MD5_PROTOTYPE_SUPPORTS_CLONE;
-
- static {
- MD5_PROTOTYPE = getMd5Instance();
- MD5_PROTOTYPE_SUPPORTS_CLONE = supportsClone(MD5_PROTOTYPE);
- }
-
// Make novel use of a CodedOutputStream, which is good at efficiently serializing data. By
// flushing at the end of each digest we can continue to use the stream.
private final CodedOutputStream codedOut;
- private final MessageDigest md5;
+ private final MessageDigest messageDigest;
/** Creates and initializes a new instance. */
- public Fingerprint() {
- md5 = cloneOrCreateMd5();
+ public Fingerprint(DigestHashFunction digestFunction) {
+ messageDigest = digestFunction.cloneOrCreateMessageDigest();
// This is a lot of indirection, but CodedOutputStream does a reasonable job of converting
// strings to bytes without creating a whole bunch of garbage, which pays off.
- codedOut = CodedOutputStream.newInstance(
- new DigestOutputStream(ByteStreams.nullOutputStream(), md5),
- /*bufferSize=*/ 1024);
+ codedOut =
+ CodedOutputStream.newInstance(
+ new DigestOutputStream(ByteStreams.nullOutputStream(), messageDigest),
+ /*bufferSize=*/ 1024);
+ }
+
+ public Fingerprint() {
+ // TODO(b/112460990): Use the value from DigestHashFunction.getDefault(), but check for
+ // contention.
+ this(DigestHashFunction.MD5);
}
/**
* Completes the hash computation by doing final operations and resets the underlying state,
* allowing this instance to be used again.
*
- * @return the MD5 digest as a 16-byte array
+ * @return the digest as a 16-byte array
* @see java.security.MessageDigest#digest()
*/
public byte[] digestAndReset() {
@@ -72,7 +71,7 @@ public final class Fingerprint implements Consumer<String> {
} catch (IOException e) {
throw new IllegalStateException("failed to flush", e);
}
- return md5.digest();
+ return messageDigest.digest();
}
/**
@@ -87,7 +86,7 @@ public final class Fingerprint implements Consumer<String> {
public void digestAndReset(byte[] buf, int offset, int len) {
try {
codedOut.flush();
- md5.digest(buf, offset, len);
+ messageDigest.digest(buf, offset, len);
} catch (IOException e) {
throw new IllegalStateException("failed to flush", e);
} catch (DigestException e) {
@@ -250,18 +249,6 @@ public final class Fingerprint implements Consumer<String> {
return this;
}
- private static MessageDigest cloneOrCreateMd5() {
- if (MD5_PROTOTYPE_SUPPORTS_CLONE) {
- try {
- return (MessageDigest) MD5_PROTOTYPE.clone();
- } catch (CloneNotSupportedException e) {
- throw new IllegalStateException("Could not clone md5", e);
- }
- } else {
- return getMd5Instance();
- }
- }
-
private static String hexDigest(byte[] digest) {
StringBuilder b = new StringBuilder(32);
for (int i = 0; i < digest.length; i++) {
@@ -272,33 +259,22 @@ public final class Fingerprint implements Consumer<String> {
return b.toString();
}
- private static MessageDigest getMd5Instance() {
- try {
- return MessageDigest.getInstance("md5");
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException("md5 not available", e);
- }
- }
-
- private static boolean supportsClone(MessageDigest toCheck) {
- try {
- toCheck.clone();
- return true;
- } catch (CloneNotSupportedException e) {
- return false;
- }
- }
-
// -------- Convenience methods ----------------------------
/**
- * Computes the hex digest from a String using UTF8 encoding and returning
- * the hexDigest().
+ * Computes the hex digest from a String using UTF8 encoding and returning the hexDigest().
*
* @param input the String from which to compute the digest
*/
- public static String md5Digest(String input) {
- return hexDigest(cloneOrCreateMd5().digest(input.getBytes(StandardCharsets.UTF_8)));
+ public static String getHexDigest(String input) {
+ // TODO(b/112460990): This convenience method, if kept should not use MD5 by default, but should
+ // use the value from DigestHashFunction.getDefault(). However, this gets called during class
+ // loading in a few places, before setDefault() has been called, so these call-sites should be
+ // removed before this can be done safely.
+ return hexDigest(
+ DigestHashFunction.MD5
+ .cloneOrCreateMessageDigest()
+ .digest(input.getBytes(StandardCharsets.UTF_8)));
}
@Override
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/DigestHashFunction.java b/src/main/java/com/google/devtools/build/lib/vfs/DigestHashFunction.java
index 12aee0757b..123e4b5a9c 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/DigestHashFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/DigestHashFunction.java
@@ -14,16 +14,26 @@
package com.google.devtools.build.lib.vfs;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.OptionsParsingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
-/** Type of hash function to use for digesting files. */
+/**
+ * Type of hash function to use for digesting files.
+ *
+ * <p>This tracks parallel {@link java.security.MessageDigest} and {@link HashFunction} interfaces
+ * for each provided hash, as Bazel uses both - MessageDigest where performance is critical and
+ * HashFunctions where ease-of-use wins over.
+ */
// The underlying HashFunctions are immutable and thread safe.
public class DigestHashFunction {
// This map must be declared first to make sure that calls to register() have it ready.
@@ -36,33 +46,24 @@ public class DigestHashFunction {
private static DigestHashFunction defaultHash;
private static boolean defaultHasBeenSet = false;
- private final HashFunction hash;
+ private final HashFunction hashFunction;
private final String name;
+ private final MessageDigest messageDigestPrototype;
+ private final boolean messageDigestPrototypeSupportsClone;
- private DigestHashFunction(HashFunction hash, String name) {
- this.hash = hash;
+ private DigestHashFunction(HashFunction hashFunction, String name) {
+ this.hashFunction = hashFunction;
this.name = name;
- }
-
- public HashFunction getHash() {
- return hash;
- }
-
- public boolean isValidDigest(byte[] digest) {
- // TODO(b/109764197): Remove this check to accept variable-length hashes.
- return digest != null && digest.length * 8 == hash.bits();
- }
-
- @Override
- public String toString() {
- return name;
+ this.messageDigestPrototype = getMessageDigestInstance();
+ this.messageDigestPrototypeSupportsClone = supportsClone(messageDigestPrototype);
}
/**
* Creates a new DigestHashFunction that is registered to be recognized by its name in {@link
* DigestFunctionConverter}.
*
- * @param hashName the canonical name for this hash function.
+ * @param hashName the canonical name for this hash function - and the name that can be used to
+ * uncover the MessageDigest.
* @param altNames alternative names that will be mapped to this function by the converter but
* will not serve as the canonical name for the DigestHashFunction.
* @param hash The {@link HashFunction} to register.
@@ -70,6 +71,15 @@ public class DigestHashFunction {
*/
public static DigestHashFunction register(
HashFunction hash, String hashName, String... altNames) {
+ try {
+ MessageDigest.getInstance(hashName);
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalArgumentException(
+ "The hash function name provided does not correspond to a valid MessageDigest: "
+ + hashName,
+ e);
+ }
+
DigestHashFunction hashFunction = new DigestHashFunction(hash, hashName);
List<String> names = ImmutableList.<String>builder().add(hashName).add(altNames).build();
synchronized (hashFunctionRegistry) {
@@ -149,4 +159,55 @@ public class DigestHashFunction {
return "hash function";
}
}
+
+ public HashFunction getHashFunction() {
+ return hashFunction;
+ }
+
+ public MessageDigest cloneOrCreateMessageDigest() {
+ if (messageDigestPrototypeSupportsClone) {
+ try {
+ return (MessageDigest) messageDigestPrototype.clone();
+ } catch (CloneNotSupportedException e) {
+ // We checked at initialization that this could be cloned, so this should never happen.
+ throw new IllegalStateException("Could not clone message digest", e);
+ }
+ } else {
+ return getMessageDigestInstance();
+ }
+ }
+
+ public boolean isValidDigest(byte[] digest) {
+ // TODO(b/109764197): Remove this check to accept variable-length hashes.
+ return digest != null && digest.length * 8 == hashFunction.bits();
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ private MessageDigest getMessageDigestInstance() {
+ try {
+ return MessageDigest.getInstance(name);
+ } catch (NoSuchAlgorithmException e) {
+ // We check when we register() this digest function that the message digest exists. This
+ // should never happen.
+ throw new IllegalStateException("message digest " + name + " not available", e);
+ }
+ }
+
+ private static boolean supportsClone(MessageDigest toCheck) {
+ try {
+ toCheck.clone();
+ return true;
+ } catch (CloneNotSupportedException e) {
+ return false;
+ }
+ }
+
+ @VisibleForTesting
+ static Collection<DigestHashFunction> getPossibleHashFunctions() {
+ return hashFunctionRegistry.values();
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index 1c3a163f62..2305aa0dc0 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -275,7 +275,7 @@ public abstract class FileSystem {
public InputStream openStream() throws IOException {
return getInputStream(path);
}
- }.hash(digestFunction.getHash()).asBytes();
+ }.hash(digestFunction.getHashFunction()).asBytes();
}
/**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
index 497b0bbd5d..f20c25a8b9 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -777,7 +777,7 @@ public class Path
public String getDirectoryDigest() throws IOException {
List<String> entries = new ArrayList<String>(fileSystem.getDirectoryEntries(this));
Collections.sort(entries);
- Hasher hasher = fileSystem.getDigestFunction().getHash().newHasher();
+ Hasher hasher = fileSystem.getDigestFunction().getHashFunction().newHasher();
for (String entry : entries) {
Path path = this.getChild(entry);
FileStatus stat = path.stat(Symlinks.NOFOLLOW);
diff --git a/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java b/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java
index 372b48d80c..b3fd3c4ef2 100644
--- a/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java
+++ b/src/test/java/com/google/devtools/build/lib/util/FingerprintTest.java
@@ -58,7 +58,7 @@ public class FingerprintTest {
public void bytesFingerprint() {
assertThat(new Fingerprint().addBytes("Hello World!".getBytes(UTF_8)).hexDigestAndReset())
.isEqualTo("ed076287532e86365e841e92bfc50d8c");
- assertThat(Fingerprint.md5Digest("Hello World!"))
+ assertThat(Fingerprint.getHexDigest("Hello World!"))
.isEqualTo("ed076287532e86365e841e92bfc50d8c");
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionTest.java b/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionGlobalsTest.java
index 1e4044bd31..77f7b62b21 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionGlobalsTest.java
@@ -29,7 +29,7 @@ import org.junit.runners.JUnit4;
* equality.
*/
@RunWith(JUnit4.class)
-public class DigestHashFunctionTest {
+public class DigestHashFunctionGlobalsTest {
private final DigestFunctionConverter converter = new DigestFunctionConverter();
@Before
@@ -64,22 +64,26 @@ public class DigestHashFunctionTest {
@Test
public void lateRegistrationGetsPickedUpByConverter() throws Exception {
- DigestHashFunction.register(Hashing.goodFastHash(32), "goodFastHash32");
+ DigestHashFunction.register(Hashing.goodFastHash(32), "SHA-512");
- assertThat(converter.convert("goodFastHash32")).isSameAs(converter.convert("GOODFASTHASH32"));
+ assertThat(converter.convert("SHA-512")).isSameAs(converter.convert("sha-512"));
}
@Test
public void lateRegistrationWithAlternativeNamesGetsPickedUpByConverter() throws Exception {
- DigestHashFunction.register(
- Hashing.goodFastHash(64), "goodFastHash64", "goodFastHash-64", "good-fast-hash-64");
-
- assertThat(converter.convert("goodFastHash64")).isSameAs(converter.convert("GOODFASTHASH64"));
- assertThat(converter.convert("goodFastHash64")).isSameAs(converter.convert("goodFastHash-64"));
- assertThat(converter.convert("goodFastHash64"))
- .isSameAs(converter.convert("good-fast-hash-64"));
- assertThat(converter.convert("goodFastHash64"))
- .isSameAs(converter.convert("GOOD-fast-HASH-64"));
+ DigestHashFunction.register(Hashing.goodFastHash(64), "SHA-384", "SHA384", "SHA_384");
+
+ assertThat(converter.convert("SHA-384")).isSameAs(converter.convert("SHA-384"));
+ assertThat(converter.convert("Sha-384")).isSameAs(converter.convert("SHA-384"));
+ assertThat(converter.convert("sha-384")).isSameAs(converter.convert("SHA-384"));
+
+ assertThat(converter.convert("SHA384")).isSameAs(converter.convert("SHA-384"));
+ assertThat(converter.convert("Sha384")).isSameAs(converter.convert("SHA-384"));
+ assertThat(converter.convert("sha384")).isSameAs(converter.convert("SHA-384"));
+
+ assertThat(converter.convert("SHA_384")).isSameAs(converter.convert("SHA-384"));
+ assertThat(converter.convert("Sha_384")).isSameAs(converter.convert("SHA-384"));
+ assertThat(converter.convert("sha_384")).isSameAs(converter.convert("SHA-384"));
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionsTest.java
new file mode 100644
index 0000000000..6b3b19923d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/DigestHashFunctionsTest.java
@@ -0,0 +1,61 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.vfs;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests different {@link DigestHashFunction} for consistency between the MessageDigests and the
+ * HashFunctions that it exposes.
+ */
+@RunWith(Parameterized.class)
+public class DigestHashFunctionsTest {
+ @Parameters(name = "{index}: digestHashFunction={0}")
+ public static Collection<DigestHashFunction[]> hashFunctions() {
+ // TODO(b/112537387): Remove the array-ification and return Collection<DigestHashFunction>. This
+ // is possible in Junit4.12, but 4.11 requires the array. Bazel 0.18 will have Junit4.12, so
+ // this can change then.
+ return DigestHashFunction.getPossibleHashFunctions()
+ .stream()
+ .map(dhf -> new DigestHashFunction[] {dhf})
+ .collect(ImmutableList.toImmutableList());
+ }
+
+ @Parameter public DigestHashFunction digestHashFunction;
+
+ private void assertHashFunctionAndMessageDigestEquivalentForInput(byte[] input) {
+ byte[] hashFunctionOutput = digestHashFunction.getHashFunction().hashBytes(input).asBytes();
+ byte[] messageDigestOutput = digestHashFunction.cloneOrCreateMessageDigest().digest(input);
+ assertThat(hashFunctionOutput).isEqualTo(messageDigestOutput);
+ }
+
+ @Test
+ public void emptyDigestIsConsistent() {
+ assertHashFunctionAndMessageDigestEquivalentForInput(new byte[] {});
+ }
+
+ @Test
+ public void shortDigestIsConsistent() {
+ assertHashFunctionAndMessageDigestEquivalentForInput("Bazel".getBytes(StandardCharsets.UTF_8));
+ }
+}