diff options
author | 2017-12-20 08:45:45 -0800 | |
---|---|---|
committer | 2017-12-20 09:18:36 -0800 | |
commit | 3d0a04ddc6b0f4d660256f274ceb3e5c7f654c90 (patch) | |
tree | d9cb81241894d7faf323ad101c7b81cc71a005c4 /src/test/java | |
parent | 8e3afccd8bea45105752ddeb33bde111c556fb8b (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')
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); + } } |