aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java
diff options
context:
space:
mode:
authorGravatar Hadrien Chauvin <hadrienchauvin@gmail.com>2017-12-20 08:45:45 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2017-12-20 09:18:36 -0800
commit3d0a04ddc6b0f4d660256f274ceb3e5c7f654c90 (patch)
treed9cb81241894d7faf323ad101c7b81cc71a005c4 /src/test/java
parent8e3afccd8bea45105752ddeb33bde111c556fb8b (diff)
remote: add directory support for remote caching and execution
Add support for directory trees as artifacts. Closes #4011. PiperOrigin-RevId: 179691001
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/FakeActionInputFileCache.java9
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/FakeImmutableCacheByteStreamImpl.java19
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteCacheTest.java221
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java3
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCacheTest.java321
-rw-r--r--src/test/java/com/google/devtools/build/lib/remote/TreeNodeRepositoryTest.java51
7 files changed, 619 insertions, 7 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/remote/FakeActionInputFileCache.java b/src/test/java/com/google/devtools/build/lib/remote/FakeActionInputFileCache.java
index 3f509684f2..dd37b3cfbd 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/FakeActionInputFileCache.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/FakeActionInputFileCache.java
@@ -24,6 +24,7 @@ import com.google.devtools.build.lib.skyframe.FileArtifactValue;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.remoteexecution.v1test.Digest;
+import com.google.devtools.remoteexecution.v1test.Tree;
import com.google.protobuf.ByteString;
import java.io.IOException;
import javax.annotation.Nullable;
@@ -76,4 +77,12 @@ final class FakeActionInputFileCache implements ActionInputFileCache {
setDigest(input, digest.getHash());
return digest;
}
+
+ public Digest createScratchInputDirectory(ActionInput input, Tree content) throws IOException {
+ Path inputFile = execRoot.getRelative(input.getExecPath());
+ FileSystemUtils.createDirectoryAndParents(inputFile);
+ Digest digest = digestUtil.compute(content);
+ setDigest(input, digest.getHash());
+ return digest;
+ }
} \ No newline at end of file
diff --git a/src/test/java/com/google/devtools/build/lib/remote/FakeImmutableCacheByteStreamImpl.java b/src/test/java/com/google/devtools/build/lib/remote/FakeImmutableCacheByteStreamImpl.java
index e418a29af6..f8d8fe5ef6 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/FakeImmutableCacheByteStreamImpl.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/FakeImmutableCacheByteStreamImpl.java
@@ -32,14 +32,25 @@ class FakeImmutableCacheByteStreamImpl extends ByteStreamImplBase {
// Start returning the correct response after this number of errors is reached.
private static final int MAX_ERRORS = 3;
- public FakeImmutableCacheByteStreamImpl(Map<Digest, String> contents) {
+ public FakeImmutableCacheByteStreamImpl(Map<Digest, Object> contents) {
ImmutableMap.Builder<ReadRequest, ReadResponse> b = ImmutableMap.builder();
- for (Map.Entry<Digest, String> e : contents.entrySet()) {
+ for (Map.Entry<Digest, Object> e : contents.entrySet()) {
+ Object obj = e.getValue();
+ ByteString data;
+ if (obj instanceof String) {
+ data = ByteString.copyFromUtf8((String) obj);
+ } else if (obj instanceof ByteString) {
+ data = (ByteString) obj;
+ } else {
+ throw new AssertionError(
+ "expected object to be either a String or a ByteString, got a "
+ + obj.getClass().getCanonicalName());
+ }
b.put(
ReadRequest.newBuilder()
.setResourceName("blobs/" + e.getKey().getHash() + "/" + e.getKey().getSizeBytes())
.build(),
- ReadResponse.newBuilder().setData(ByteString.copyFromUtf8(e.getValue())).build());
+ ReadResponse.newBuilder().setData(data).build());
}
cannedReplies = b.build();
numErrors = new HashMap<>();
@@ -55,7 +66,7 @@ class FakeImmutableCacheByteStreamImpl extends ByteStreamImplBase {
@Override
public void read(ReadRequest request, StreamObserver<ReadResponse> responseObserver) {
- assertThat(cannedReplies.containsKey(request)).isTrue();
+ assertThat(cannedReplies.keySet()).contains(request);
int errCount = numErrors.getOrDefault(request, 0);
if (errCount < MAX_ERRORS) {
numErrors.put(request, errCount + 1);
diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteCacheTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteCacheTest.java
index 76d35871e9..83d4efdfd0 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcRemoteCacheTest.java
@@ -26,6 +26,7 @@ import com.google.bytestream.ByteStreamProto.ReadResponse;
import com.google.bytestream.ByteStreamProto.WriteRequest;
import com.google.bytestream.ByteStreamProto.WriteResponse;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.authandtls.GoogleAuthUtils;
@@ -44,9 +45,13 @@ import com.google.devtools.remoteexecution.v1test.ActionCacheGrpc.ActionCacheImp
import com.google.devtools.remoteexecution.v1test.ActionResult;
import com.google.devtools.remoteexecution.v1test.ContentAddressableStorageGrpc.ContentAddressableStorageImplBase;
import com.google.devtools.remoteexecution.v1test.Digest;
+import com.google.devtools.remoteexecution.v1test.Directory;
+import com.google.devtools.remoteexecution.v1test.DirectoryNode;
+import com.google.devtools.remoteexecution.v1test.FileNode;
import com.google.devtools.remoteexecution.v1test.FindMissingBlobsRequest;
import com.google.devtools.remoteexecution.v1test.FindMissingBlobsResponse;
import com.google.devtools.remoteexecution.v1test.GetActionResultRequest;
+import com.google.devtools.remoteexecution.v1test.Tree;
import com.google.devtools.remoteexecution.v1test.UpdateActionResultRequest;
import com.google.protobuf.ByteString;
import io.grpc.CallCredentials;
@@ -233,6 +238,96 @@ public class GrpcRemoteCacheTest {
}
@Test
+ public void testDownloadDirectory() throws Exception {
+ GrpcRemoteCache client = newClient();
+ Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
+ Digest quxDigest = DIGEST_UTIL.computeAsUtf8("qux-contents");
+ Tree barTreeMessage =
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setName("qux")
+ .setDigest(quxDigest)
+ .setIsExecutable(true)))
+ .build();
+ Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
+ serviceRegistry.addService(
+ new FakeImmutableCacheByteStreamImpl(
+ ImmutableMap.of(
+ fooDigest, "foo-contents",
+ barTreeDigest, barTreeMessage.toByteString(),
+ quxDigest, "qux-contents")));
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
+ client.download(result.build(), execRoot, null);
+
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar/qux"))).isEqualTo(quxDigest);
+ assertThat(execRoot.getRelative("a/bar/qux").isExecutable()).isTrue();
+ }
+
+ @Test
+ public void testDownloadDirectoryEmpty() throws Exception {
+ GrpcRemoteCache client = newClient();
+ Tree barTreeMessage = Tree.newBuilder().setRoot(Directory.newBuilder()).build();
+ Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
+ serviceRegistry.addService(
+ new FakeImmutableCacheByteStreamImpl(
+ ImmutableMap.of(barTreeDigest, barTreeMessage.toByteString())));
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
+ client.download(result.build(), execRoot, null);
+
+ assertThat(execRoot.getRelative("a/bar").isDirectory()).isTrue();
+ }
+
+ @Test
+ public void testDownloadDirectoryNested() throws Exception {
+ GrpcRemoteCache client = newClient();
+ Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
+ Digest quxDigest = DIGEST_UTIL.computeAsUtf8("qux-contents");
+ Directory wobbleDirMessage =
+ Directory.newBuilder()
+ .addFiles(FileNode.newBuilder().setName("qux").setDigest(quxDigest))
+ .build();
+ Digest wobbleDirDigest = DIGEST_UTIL.compute(wobbleDirMessage);
+ Tree barTreeMessage =
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setName("qux")
+ .setDigest(quxDigest)
+ .setIsExecutable(true))
+ .addDirectories(
+ DirectoryNode.newBuilder().setName("wobble").setDigest(wobbleDirDigest)))
+ .addChildren(wobbleDirMessage)
+ .build();
+ Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
+ serviceRegistry.addService(
+ new FakeImmutableCacheByteStreamImpl(
+ ImmutableMap.of(
+ fooDigest, "foo-contents",
+ barTreeDigest, barTreeMessage.toByteString(),
+ quxDigest, "qux-contents")));
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
+ client.download(result.build(), execRoot, null);
+
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar/wobble/qux"))).isEqualTo(quxDigest);
+ assertThat(execRoot.getRelative("a/bar/wobble/qux").isExecutable()).isFalse();
+ }
+
+ @Test
public void testUploadBlobCacheHitWithRetries() throws Exception {
final GrpcRemoteCache client = newClient();
final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
@@ -391,6 +486,132 @@ public class GrpcRemoteCacheTest {
}
@Test
+ public void testUploadDirectory() throws Exception {
+ final GrpcRemoteCache client = newClient();
+ final Digest fooDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("a/foo"), "xyz");
+ final Digest quxDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/qux"), "abc");
+ final Digest barDigest =
+ fakeFileCache.createScratchInputDirectory(
+ ActionInputHelper.fromPath("bar"),
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setIsExecutable(true)
+ .setName("qux")
+ .setDigest(quxDigest)
+ .build())
+ .build())
+ .build());
+ final Path fooFile = execRoot.getRelative("a/foo");
+ final Path quxFile = execRoot.getRelative("bar/qux");
+ quxFile.setExecutable(true);
+ final Path barDir = execRoot.getRelative("bar");
+ serviceRegistry.addService(
+ new ContentAddressableStorageImplBase() {
+ @Override
+ public void findMissingBlobs(
+ FindMissingBlobsRequest request,
+ StreamObserver<FindMissingBlobsResponse> responseObserver) {
+ assertThat(request.getBlobDigestsList())
+ .containsExactly(fooDigest, quxDigest, barDigest);
+ // Nothing is missing.
+ responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
+ responseObserver.onCompleted();
+ }
+ });
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ client.upload(execRoot, ImmutableList.<Path>of(fooFile, barDir), outErr, result);
+ ActionResult.Builder expectedResult = ActionResult.newBuilder();
+ expectedResult.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
+ assertThat(result.build()).isEqualTo(expectedResult.build());
+ }
+
+ @Test
+ public void testUploadDirectoryEmpty() throws Exception {
+ final GrpcRemoteCache client = newClient();
+ final Digest barDigest =
+ fakeFileCache.createScratchInputDirectory(
+ ActionInputHelper.fromPath("bar"),
+ Tree.newBuilder().setRoot(Directory.newBuilder().build()).build());
+ final Path barDir = execRoot.getRelative("bar");
+ serviceRegistry.addService(
+ new ContentAddressableStorageImplBase() {
+ @Override
+ public void findMissingBlobs(
+ FindMissingBlobsRequest request,
+ StreamObserver<FindMissingBlobsResponse> responseObserver) {
+ assertThat(request.getBlobDigestsList()).containsExactly(barDigest);
+ // Nothing is missing.
+ responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
+ responseObserver.onCompleted();
+ }
+ });
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ client.upload(execRoot, ImmutableList.<Path>of(barDir), outErr, result);
+ ActionResult.Builder expectedResult = ActionResult.newBuilder();
+ expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
+ assertThat(result.build()).isEqualTo(expectedResult.build());
+ }
+
+ @Test
+ public void testUploadDirectoryNested() throws Exception {
+ final GrpcRemoteCache client = newClient();
+ final Digest wobbleDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/test/wobble"), "xyz");
+ final Digest quxDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/qux"), "abc");
+ final Directory testDirMessage =
+ Directory.newBuilder()
+ .addFiles(FileNode.newBuilder().setName("wobble").setDigest(wobbleDigest).build())
+ .build();
+ final Digest testDigest = DIGEST_UTIL.compute(testDirMessage);
+ final Tree barTree =
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setIsExecutable(true)
+ .setName("qux")
+ .setDigest(quxDigest))
+ .addDirectories(
+ DirectoryNode.newBuilder().setName("test").setDigest(testDigest)))
+ .addChildren(testDirMessage)
+ .build();
+ final Digest barDigest =
+ fakeFileCache.createScratchInputDirectory(ActionInputHelper.fromPath("bar"), barTree);
+ final Path quxFile = execRoot.getRelative("bar/qux");
+ quxFile.setExecutable(true);
+ final Path barDir = execRoot.getRelative("bar");
+ serviceRegistry.addService(
+ new ContentAddressableStorageImplBase() {
+ @Override
+ public void findMissingBlobs(
+ FindMissingBlobsRequest request,
+ StreamObserver<FindMissingBlobsResponse> responseObserver) {
+ assertThat(request.getBlobDigestsList())
+ .containsExactly(quxDigest, barDigest, wobbleDigest);
+ // Nothing is missing.
+ responseObserver.onNext(FindMissingBlobsResponse.getDefaultInstance());
+ responseObserver.onCompleted();
+ }
+ });
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ client.upload(execRoot, ImmutableList.<Path>of(barDir), outErr, result);
+ ActionResult.Builder expectedResult = ActionResult.newBuilder();
+ expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
+ assertThat(result.build()).isEqualTo(expectedResult.build());
+ }
+
+ @Test
public void testUploadCacheHits() throws Exception {
final GrpcRemoteCache client = newClient();
final Digest fooDigest =
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
index 65c7d88960..b14fb71d16 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
@@ -87,7 +87,7 @@ public class RemoteSpawnCacheTest {
private Path execRoot;
private SimpleSpawn simpleSpawn;
private FakeActionInputFileCache fakeFileCache;
- @Mock private RemoteActionCache remoteCache;
+ @Mock private AbstractRemoteActionCache remoteCache;
private RemoteSpawnCache cache;
private FileOutErr outErr;
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
index 730244d627..c2973b0ae6 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
@@ -87,8 +87,7 @@ public class RemoteSpawnRunnerTest {
private FakeActionInputFileCache fakeFileCache;
private FileOutErr outErr;
- @Mock
- private RemoteActionCache cache;
+ @Mock private AbstractRemoteActionCache cache;
@Mock
private GrpcRemoteExecutor executor;
diff --git a/src/test/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCacheTest.java b/src/test/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCacheTest.java
new file mode 100644
index 0000000000..4088a83bfc
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/remote/SimpleBlobStoreActionCacheTest.java
@@ -0,0 +1,321 @@
+// Copyright 2017 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.remote;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.ActionInputHelper;
+import com.google.devtools.build.lib.clock.JavaClock;
+import com.google.devtools.build.lib.remote.blobstore.ConcurrentMapBlobStore;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+import com.google.devtools.remoteexecution.v1test.ActionResult;
+import com.google.devtools.remoteexecution.v1test.Digest;
+import com.google.devtools.remoteexecution.v1test.Directory;
+import com.google.devtools.remoteexecution.v1test.DirectoryNode;
+import com.google.devtools.remoteexecution.v1test.FileNode;
+import com.google.devtools.remoteexecution.v1test.Tree;
+import io.grpc.Context;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for {@link SimpleBlobStoreActionCache}. */
+@RunWith(JUnit4.class)
+public class SimpleBlobStoreActionCacheTest {
+ private static final DigestUtil DIGEST_UTIL = new DigestUtil(HashFunction.SHA256);
+
+ private FileSystem fs;
+ private Path execRoot;
+ private FakeActionInputFileCache fakeFileCache;
+
+ @Before
+ public final void setUp() throws Exception {
+ Chunker.setDefaultChunkSizeForTesting(1000); // Enough for everything to be one chunk.
+ fs = new InMemoryFileSystem(new JavaClock(), HashFunction.SHA256);
+ execRoot = fs.getPath("/exec/root");
+ FileSystemUtils.createDirectoryAndParents(execRoot);
+ fakeFileCache = new FakeActionInputFileCache(execRoot);
+
+ Path stdout = fs.getPath("/tmp/stdout");
+ Path stderr = fs.getPath("/tmp/stderr");
+ FileSystemUtils.createDirectoryAndParents(stdout.getParentDirectory());
+ FileSystemUtils.createDirectoryAndParents(stderr.getParentDirectory());
+ Context withEmptyMetadata =
+ TracingMetadataUtils.contextWithMetadata(
+ "none", "none", DIGEST_UTIL.asActionKey(Digest.getDefaultInstance()));
+ withEmptyMetadata.attach();
+ }
+
+ private SimpleBlobStoreActionCache newClient() {
+ return newClient(new ConcurrentHashMap<>());
+ }
+
+ private SimpleBlobStoreActionCache newClient(ConcurrentMap<String, byte[]> map) {
+ return new SimpleBlobStoreActionCache(new ConcurrentMapBlobStore(map), DIGEST_UTIL);
+ }
+
+ @Test
+ public void testDownloadEmptyBlob() throws Exception {
+ SimpleBlobStoreActionCache client = newClient();
+ Digest emptyDigest = DIGEST_UTIL.compute(new byte[0]);
+ // Will not call the mock Bytestream interface at all.
+ assertThat(client.downloadBlob(emptyDigest)).isEmpty();
+ }
+
+ @Test
+ public void testDownloadBlob() throws Exception {
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
+ map.put(digest.getHash(), "abcdefg".getBytes(Charsets.UTF_8));
+ final SimpleBlobStoreActionCache client = newClient(map);
+ assertThat(new String(client.downloadBlob(digest), UTF_8)).isEqualTo("abcdefg");
+ }
+
+ @Test
+ public void testDownloadAllResults() throws Exception {
+ Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
+ Digest barDigest = DIGEST_UTIL.computeAsUtf8("bar-contents");
+ Digest emptyDigest = DIGEST_UTIL.compute(new byte[0]);
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ map.put(fooDigest.getHash(), "foo-contents".getBytes(Charsets.UTF_8));
+ map.put(barDigest.getHash(), "bar-contents".getBytes(Charsets.UTF_8));
+ SimpleBlobStoreActionCache client = newClient(map);
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ result.addOutputFilesBuilder().setPath("b/empty").setDigest(emptyDigest);
+ result.addOutputFilesBuilder().setPath("a/bar").setDigest(barDigest).setIsExecutable(true);
+ client.download(result.build(), execRoot, null);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("b/empty"))).isEqualTo(emptyDigest);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar"))).isEqualTo(barDigest);
+ assertThat(execRoot.getRelative("a/bar").isExecutable()).isTrue();
+ }
+
+ @Test
+ public void testDownloadDirectory() throws Exception {
+ Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
+ Digest quxDigest = DIGEST_UTIL.computeAsUtf8("qux-contents");
+ Tree barTreeMessage =
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setName("qux")
+ .setDigest(quxDigest)
+ .setIsExecutable(true)))
+ .build();
+ Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ map.put(fooDigest.getHash(), "foo-contents".getBytes(Charsets.UTF_8));
+ map.put(barTreeDigest.getHash(), barTreeMessage.toByteArray());
+ map.put(quxDigest.getHash(), "qux-contents".getBytes(Charsets.UTF_8));
+ SimpleBlobStoreActionCache client = newClient(map);
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
+ client.download(result.build(), execRoot, null);
+
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar/qux"))).isEqualTo(quxDigest);
+ assertThat(execRoot.getRelative("a/bar/qux").isExecutable()).isTrue();
+ }
+
+ @Test
+ public void testDownloadDirectoryEmpty() throws Exception {
+ Tree barTreeMessage = Tree.newBuilder().setRoot(Directory.newBuilder()).build();
+ Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ map.put(barTreeDigest.getHash(), barTreeMessage.toByteArray());
+ SimpleBlobStoreActionCache client = newClient(map);
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
+ client.download(result.build(), execRoot, null);
+
+ assertThat(execRoot.getRelative("a/bar").isDirectory()).isTrue();
+ }
+
+ @Test
+ public void testDownloadDirectoryNested() throws Exception {
+ Digest fooDigest = DIGEST_UTIL.computeAsUtf8("foo-contents");
+ Digest quxDigest = DIGEST_UTIL.computeAsUtf8("qux-contents");
+ Directory wobbleDirMessage =
+ Directory.newBuilder()
+ .addFiles(FileNode.newBuilder().setName("qux").setDigest(quxDigest))
+ .build();
+ Digest wobbleDirDigest = DIGEST_UTIL.compute(wobbleDirMessage);
+ Tree barTreeMessage =
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setName("qux")
+ .setDigest(quxDigest)
+ .setIsExecutable(true))
+ .addDirectories(
+ DirectoryNode.newBuilder().setName("wobble").setDigest(wobbleDirDigest)))
+ .addChildren(wobbleDirMessage)
+ .build();
+ Digest barTreeDigest = DIGEST_UTIL.compute(barTreeMessage);
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ map.put(fooDigest.getHash(), "foo-contents".getBytes(Charsets.UTF_8));
+ map.put(barTreeDigest.getHash(), barTreeMessage.toByteArray());
+ map.put(quxDigest.getHash(), "qux-contents".getBytes(Charsets.UTF_8));
+ SimpleBlobStoreActionCache client = newClient(map);
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ result.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ result.addOutputDirectoriesBuilder().setPath("a/bar").setTreeDigest(barTreeDigest);
+ client.download(result.build(), execRoot, null);
+
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/foo"))).isEqualTo(fooDigest);
+ assertThat(DIGEST_UTIL.compute(execRoot.getRelative("a/bar/wobble/qux"))).isEqualTo(quxDigest);
+ assertThat(execRoot.getRelative("a/bar/wobble/qux").isExecutable()).isFalse();
+ }
+
+ @Test
+ public void testUploadBlob() throws Exception {
+ final Digest digest = DIGEST_UTIL.computeAsUtf8("abcdefg");
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ final SimpleBlobStoreActionCache client = newClient(map);
+
+ byte[] bytes = "abcdefg".getBytes(UTF_8);
+ assertThat(client.uploadBlob(bytes)).isEqualTo(digest);
+ assertThat(map.keySet()).containsExactly(digest.getHash());
+ assertThat(map.entrySet()).hasSize(1);
+ assertThat(map.get(digest.getHash())).isEqualTo(bytes);
+ }
+
+ @Test
+ public void testUploadDirectory() throws Exception {
+ final Digest fooDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("a/foo"), "xyz");
+ final Digest quxDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/qux"), "abc");
+ final Digest barDigest =
+ fakeFileCache.createScratchInputDirectory(
+ ActionInputHelper.fromPath("bar"),
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setIsExecutable(true)
+ .setName("qux")
+ .setDigest(quxDigest)
+ .build())
+ .build())
+ .build());
+ final Path fooFile = execRoot.getRelative("a/foo");
+ final Path quxFile = execRoot.getRelative("bar/qux");
+ quxFile.setExecutable(true);
+ final Path barDir = execRoot.getRelative("bar");
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ final SimpleBlobStoreActionCache client = newClient(map);
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ client.upload(result, execRoot, ImmutableList.<Path>of(fooFile, barDir));
+ ActionResult.Builder expectedResult = ActionResult.newBuilder();
+ expectedResult.addOutputFilesBuilder().setPath("a/foo").setDigest(fooDigest);
+ expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
+ assertThat(result.build()).isEqualTo(expectedResult.build());
+
+ assertThat(map.keySet())
+ .containsExactly(fooDigest.getHash(), quxDigest.getHash(), barDigest.getHash());
+ }
+
+ @Test
+ public void testUploadDirectoryEmpty() throws Exception {
+ final Digest barDigest =
+ fakeFileCache.createScratchInputDirectory(
+ ActionInputHelper.fromPath("bar"),
+ Tree.newBuilder().setRoot(Directory.newBuilder().build()).build());
+ final Path barDir = execRoot.getRelative("bar");
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ final SimpleBlobStoreActionCache client = newClient(map);
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ client.upload(result, execRoot, ImmutableList.<Path>of(barDir));
+ ActionResult.Builder expectedResult = ActionResult.newBuilder();
+ expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
+ assertThat(result.build()).isEqualTo(expectedResult.build());
+
+ assertThat(map.keySet()).containsExactly(barDigest.getHash());
+ }
+
+ @Test
+ public void testUploadDirectoryNested() throws Exception {
+ final Digest wobbleDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/test/wobble"), "xyz");
+ final Digest quxDigest =
+ fakeFileCache.createScratchInput(ActionInputHelper.fromPath("bar/qux"), "abc");
+ final Directory testDirMessage =
+ Directory.newBuilder()
+ .addFiles(FileNode.newBuilder().setName("wobble").setDigest(wobbleDigest).build())
+ .build();
+ final Digest testDigest = DIGEST_UTIL.compute(testDirMessage);
+ final Tree barTree =
+ Tree.newBuilder()
+ .setRoot(
+ Directory.newBuilder()
+ .addFiles(
+ FileNode.newBuilder()
+ .setIsExecutable(true)
+ .setName("qux")
+ .setDigest(quxDigest))
+ .addDirectories(
+ DirectoryNode.newBuilder().setName("test").setDigest(testDigest)))
+ .addChildren(testDirMessage)
+ .build();
+ final Digest barDigest =
+ fakeFileCache.createScratchInputDirectory(ActionInputHelper.fromPath("bar"), barTree);
+
+ final ConcurrentMap<String, byte[]> map = new ConcurrentHashMap<>();
+ final SimpleBlobStoreActionCache client = newClient(map);
+
+ final Path quxFile = execRoot.getRelative("bar/qux");
+ quxFile.setExecutable(true);
+ final Path barDir = execRoot.getRelative("bar");
+
+ ActionResult.Builder result = ActionResult.newBuilder();
+ client.upload(result, execRoot, ImmutableList.<Path>of(barDir));
+ ActionResult.Builder expectedResult = ActionResult.newBuilder();
+ expectedResult.addOutputDirectoriesBuilder().setPath("bar").setTreeDigest(barDigest);
+ assertThat(result.build()).isEqualTo(expectedResult.build());
+
+ assertThat(map.keySet())
+ .containsExactly(wobbleDigest.getHash(), quxDigest.getHash(), barDigest.getHash());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/remote/TreeNodeRepositoryTest.java b/src/test/java/com/google/devtools/build/lib/remote/TreeNodeRepositoryTest.java
index 9c0f43afc8..7ea8b0db44 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/TreeNodeRepositoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/TreeNodeRepositoryTest.java
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.ActionInput;
import com.google.devtools.build.lib.actions.ActionInputFileCache;
+import com.google.devtools.build.lib.actions.ActionInputHelper;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Root;
import com.google.devtools.build.lib.clock.BlazeClock;
@@ -152,4 +153,54 @@ public class TreeNodeRepositoryTest {
assertThat(root.getChildEntries()).isEmpty();
}
+
+ @Test
+ public void testDirectoryInput() throws Exception {
+ Artifact foo = new Artifact(scratch.dir("/exec/root/a/foo"), rootDir);
+ scratch.file("/exec/root/a/foo/foo.h", "1");
+ ActionInput fooH = ActionInputHelper.fromPath("/exec/root/a/foo/foo.h");
+ scratch.file("/exec/root/a/foo/foo.cc", "2");
+ ActionInput fooCc = ActionInputHelper.fromPath("/exec/root/a/foo/foo.cc");
+ Artifact bar = new Artifact(scratch.file("/exec/root/a/bar.txt"), rootDir);
+ TreeNodeRepository repo = createTestTreeNodeRepository();
+
+ TreeNode root = repo.buildFromActionInputs(ImmutableList.<ActionInput>of(foo, bar));
+ TreeNode aNode = root.getChildEntries().get(0).getChild();
+ TreeNode fooNode = aNode.getChildEntries().get(1).getChild(); // foo > bar in sort order!
+ TreeNode barNode = aNode.getChildEntries().get(0).getChild();
+
+ TreeNode fooHNode =
+ fooNode.getChildEntries().get(1).getChild(); // foo.h > foo.cc in sort order!
+ TreeNode fooCcNode = fooNode.getChildEntries().get(0).getChild();
+
+ repo.computeMerkleDigests(root);
+ ImmutableCollection<Digest> digests = repo.getAllDigests(root);
+ Digest rootDigest = repo.getMerkleDigest(root);
+ Digest aDigest = repo.getMerkleDigest(aNode);
+ Digest fooDigest = repo.getMerkleDigest(fooNode);
+ Digest fooHDigest = repo.getMerkleDigest(fooHNode);
+ Digest fooCcDigest = repo.getMerkleDigest(fooCcNode);
+ Digest barDigest = repo.getMerkleDigest(barNode);
+ assertThat(digests)
+ .containsExactly(rootDigest, aDigest, barDigest, fooDigest, fooHDigest, fooCcDigest);
+
+ ArrayList<Directory> directories = new ArrayList<>();
+ ArrayList<ActionInput> actionInputs = new ArrayList<>();
+ repo.getDataFromDigests(digests, actionInputs, directories);
+ assertThat(actionInputs).containsExactly(bar, fooH, fooCc);
+ assertThat(directories).hasSize(3); // root, root/a and root/a/foo
+ Directory rootDirectory = directories.get(0);
+ assertThat(rootDirectory.getDirectories(0).getName()).isEqualTo("a");
+ assertThat(rootDirectory.getDirectories(0).getDigest()).isEqualTo(aDigest);
+ Directory aDirectory = directories.get(1);
+ assertThat(aDirectory.getFiles(0).getName()).isEqualTo("bar.txt");
+ assertThat(aDirectory.getFiles(0).getDigest()).isEqualTo(barDigest);
+ assertThat(aDirectory.getDirectories(0).getName()).isEqualTo("foo");
+ assertThat(aDirectory.getDirectories(0).getDigest()).isEqualTo(fooDigest);
+ Directory fooDirectory = directories.get(2);
+ assertThat(fooDirectory.getFiles(0).getName()).isEqualTo("foo.cc");
+ assertThat(fooDirectory.getFiles(0).getDigest()).isEqualTo(fooCcDigest);
+ assertThat(fooDirectory.getFiles(1).getName()).isEqualTo("foo.h");
+ assertThat(fooDirectory.getFiles(1).getDigest()).isEqualTo(fooHDigest);
+ }
}