Adding stable sorting for execlog
RELNOTES: Introducing --execution_log_binary_file and --execution_log_json_file that output a stable sorted execution log. They will offer a stable replacement to --experimental_execution_log_file.
PiperOrigin-RevId: 243808737
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index 34a6a8d..1c1bc6d 100644
--- a/src/main/java/com/google/devtools/build/lib/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/BUILD
@@ -140,6 +140,7 @@
"//src/main/java/com/google/devtools/build/lib/vfs",
"//third_party:guava",
"//third_party/protobuf:protobuf_java",
+ "//third_party/protobuf:protobuf_java_util",
],
)
@@ -867,6 +868,7 @@
":runtime",
":util",
"//src/main/java/com/google/devtools/build/lib/actions",
+ "//src/main/java/com/google/devtools/build/lib/bazel/execlog:stable_sort",
"//src/main/java/com/google/devtools/build/lib/shell",
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
"//src/main/java/com/google/devtools/build/lib/vfs",
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/SpawnLogModule.java b/src/main/java/com/google/devtools/build/lib/bazel/SpawnLogModule.java
index ab03ba8..55779ab 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/SpawnLogModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/SpawnLogModule.java
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.bazel;
+import com.google.devtools.build.lib.bazel.execlog.StableSort;
import com.google.devtools.build.lib.buildtool.BuildRequest;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.exec.ExecutionOptions;
@@ -23,46 +24,136 @@
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.io.AsynchronousFileOutputStream;
+import com.google.devtools.build.lib.util.io.MessageOutputStreamWrapper.BinaryOutputStreamWrapper;
+import com.google.devtools.build.lib.util.io.MessageOutputStreamWrapper.JsonOutputStreamWrapper;
+import com.google.devtools.build.lib.util.io.MessageOutputStreamWrapper.MessageOutputStreamCollection;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
/**
* Module providing on-demand spawn logging.
*/
public final class SpawnLogModule extends BlazeModule {
+ /**
+ * SpawnLogContext will log to a temporary file as the execution is being performed. rawOutput is
+ * the path to that temporary file.
+ */
private SpawnLogContext spawnLogContext;
+ private Path rawOutput;
+
+ /**
+ * After the execution is done, the temporary file contents will be sorted and logged as the user
+ * requested, to binary and/or json files. We will open the streams at the beginning of the
+ * command so that any errors (e.g., unwritable location) will be surfaced before the execution
+ * begins.
+ */
+ private MessageOutputStreamCollection outputStreams;
+
+ private CommandEnvironment env;
+
+ private void clear() {
+ spawnLogContext = null;
+ outputStreams = new MessageOutputStreamCollection();
+ rawOutput = null;
+ env = null;
+ }
+
+ private void initOutputs(CommandEnvironment env) throws IOException {
+ clear();
+ this.env = env;
+
+ ExecutionOptions executionOptions = env.getOptions().getOptions(ExecutionOptions.class);
+ if (executionOptions == null) {
+ return;
+ }
+ FileSystem fileSystem = env.getRuntime().getFileSystem();
+ Path workingDirectory = env.getWorkingDirectory();
+
+ if (executionOptions.executionLogBinaryFile != null) {
+ outputStreams.addStream(
+ new BinaryOutputStreamWrapper(
+ workingDirectory
+ .getRelative(executionOptions.executionLogBinaryFile)
+ .getOutputStream()));
+ }
+
+ if (executionOptions.executionLogJsonFile != null) {
+ outputStreams.addStream(
+ new JsonOutputStreamWrapper(
+ workingDirectory
+ .getRelative(executionOptions.executionLogJsonFile)
+ .getOutputStream()));
+ }
+
+ AsynchronousFileOutputStream outStream = null;
+ if (executionOptions.executionLogFile != null) {
+ rawOutput = env.getRuntime().getFileSystem().getPath(executionOptions.executionLogFile);
+ outStream =
+ new AsynchronousFileOutputStream(
+ workingDirectory.getRelative(executionOptions.executionLogFile));
+ } else if (!outputStreams.isEmpty()) {
+ // Execution log requested but raw log file not specified
+ File file = File.createTempFile("exec", ".log");
+ rawOutput = fileSystem.getPath(file.getAbsolutePath());
+ outStream = new AsynchronousFileOutputStream(rawOutput);
+ }
+
+ if (outStream == null) {
+ // No logging needed
+ clear();
+ return;
+ }
+
+ spawnLogContext = new SpawnLogContext(env.getExecRoot(), outStream);
+ }
+
@Override
public void executorInit(CommandEnvironment env, BuildRequest request, ExecutorBuilder builder) {
env.getEventBus().register(this);
- ExecutionOptions executionOptions = env.getOptions().getOptions(ExecutionOptions.class);
- if (executionOptions != null
- && executionOptions.executionLogFile != null
- && !executionOptions.executionLogFile.isEmpty()) {
- try {
- spawnLogContext = new SpawnLogContext(
- env.getExecRoot(),
- new AsynchronousFileOutputStream(executionOptions.executionLogFile));
- } catch (IOException e) {
- env.getReporter().handle(Event.error(e.getMessage()));
- env.getBlazeModuleEnvironment().exit(new AbruptExitException(
- "Error found creating SpawnLogContext", ExitCode.COMMAND_LINE_ERROR));
- }
+
+ try {
+ initOutputs(env);
+ } catch (IOException e) {
+ env.getReporter().handle(Event.error(e.getMessage()));
+ env.getBlazeModuleEnvironment()
+ .exit(
+ new AbruptExitException(
+ "Error initializing execution log", ExitCode.COMMAND_LINE_ERROR, e));
+ }
+
+ if (spawnLogContext != null) {
builder.addActionContext(spawnLogContext);
builder.addStrategyByContext(SpawnLogContext.class, "");
- } else {
- spawnLogContext = null;
}
}
@Override
- public void afterCommand() {
+ public void afterCommand() throws AbruptExitException {
+ boolean done = false;
if (spawnLogContext != null) {
try {
spawnLogContext.close();
+ if (!outputStreams.isEmpty()) {
+ InputStream in = rawOutput.getInputStream();
+ StableSort.stableSort(in, outputStreams);
+ outputStreams.close();
+ }
+ done = true;
} catch (IOException e) {
- throw new RuntimeException(e);
+ throw new AbruptExitException(ExitCode.LOCAL_ENVIRONMENTAL_ERROR, e);
} finally {
- spawnLogContext = null;
+ if (!done && !outputStreams.isEmpty()) {
+ env.getReporter()
+ .handle(
+ Event.warn(
+ "Execution log might not have been populated. Raw execution log is at "
+ + rawOutput));
+ }
+ clear();
}
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/debug/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/debug/BUILD
index 59f1202..8cb3158 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/debug/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/debug/BUILD
@@ -57,6 +57,7 @@
"//src/main/java/com/google/devtools/build/lib:io",
"//src/main/java/com/google/devtools/build/lib:runtime",
"//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/common/options",
"//third_party:guava",
],
@@ -66,6 +67,8 @@
name = "debugging-options",
srcs = ["DebuggingOptions.java"],
deps = [
+ "//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment",
"//src/main/java/com/google/devtools/common/options",
],
)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/debug/DebuggingOptions.java b/src/main/java/com/google/devtools/build/lib/bazel/debug/DebuggingOptions.java
index 4bed8b0..db2fef8 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/debug/DebuggingOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/debug/DebuggingOptions.java
@@ -14,6 +14,8 @@
package com.google.devtools.build.lib.bazel.debug;
+import com.google.devtools.build.lib.util.OptionsUtils;
+import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
@@ -27,7 +29,8 @@
category = "verbosity",
documentationCategory = OptionDocumentationCategory.LOGGING,
effectTags = {OptionEffectTag.UNKNOWN},
+ converter = OptionsUtils.PathFragmentConverter.class,
help =
"Log certain Workspace Rules events into this file as delimited WorkspaceEvent protos.")
- public String workspaceRulesLogFile;
+ public PathFragment workspaceRulesLogFile;
}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/debug/WorkspaceRuleModule.java b/src/main/java/com/google/devtools/build/lib/bazel/debug/WorkspaceRuleModule.java
index 9efbcf6..90f6da2 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/debug/WorkspaceRuleModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/debug/WorkspaceRuleModule.java
@@ -23,6 +23,7 @@
import com.google.devtools.build.lib.util.AbruptExitException;
import com.google.devtools.build.lib.util.ExitCode;
import com.google.devtools.build.lib.util.io.AsynchronousFileOutputStream;
+import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.common.options.OptionsBase;
import java.io.IOException;
@@ -42,10 +43,12 @@
return;
}
- String logFile = env.getOptions().getOptions(DebuggingOptions.class).workspaceRulesLogFile;
- if (logFile != null && !logFile.isEmpty()) {
+ PathFragment logFile =
+ env.getOptions().getOptions(DebuggingOptions.class).workspaceRulesLogFile;
+ if (logFile != null) {
try {
- outFileStream = new AsynchronousFileOutputStream(logFile);
+ outFileStream =
+ new AsynchronousFileOutputStream(env.getWorkingDirectory().getRelative(logFile));
} catch (IOException e) {
env.getReporter().handle(Event.error(e.getMessage()));
env.getBlazeModuleEnvironment()
diff --git a/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java b/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java
index 3247b924..843178d 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/ExecutionOptions.java
@@ -436,14 +436,36 @@
public boolean statsSummary;
@Option(
- name = "experimental_execution_log_file",
- defaultValue = "",
- category = "verbosity",
- documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
- effectTags = {OptionEffectTag.UNKNOWN},
- help = "Log the executed spawns into this file as delimited Spawn protos."
- )
- public String executionLogFile;
+ name = "experimental_execution_log_file",
+ defaultValue = "null",
+ category = "verbosity",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = OptionsUtils.PathFragmentConverter.class,
+ help = "Log the executed spawns into this file as delimited Spawn protos.")
+ public PathFragment executionLogFile;
+
+ @Option(
+ name = "execution_log_binary_file",
+ defaultValue = "null",
+ category = "verbosity",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = OptionsUtils.PathFragmentConverter.class,
+ help = "Log the executed spawns into this file as delimited Spawn protos.")
+ public PathFragment executionLogBinaryFile;
+
+ @Option(
+ name = "execution_log_json_file",
+ defaultValue = "null",
+ category = "verbosity",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ converter = OptionsUtils.PathFragmentConverter.class,
+ help =
+ "Log the executed spawns into this file as json representation of the delimited Spawn"
+ + " protos.")
+ public PathFragment executionLogJsonFile;
@Option(
name = "experimental_split_xml_generation",
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
index 9420d8c..894b767 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteModule.java
@@ -174,8 +174,10 @@
try {
List<ClientInterceptor> interceptors = new ArrayList<>();
- if (!remoteOptions.experimentalRemoteGrpcLog.isEmpty()) {
- rpcLogFile = new AsynchronousFileOutputStream(remoteOptions.experimentalRemoteGrpcLog);
+ if (remoteOptions.experimentalRemoteGrpcLog != null) {
+ rpcLogFile =
+ new AsynchronousFileOutputStream(
+ env.getWorkingDirectory().getRelative(remoteOptions.experimentalRemoteGrpcLog));
interceptors.add(new LoggingInterceptor(rpcLogFile, env.getRuntime().getClock()));
}
diff --git a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
index 961b108..755b6e5 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/options/RemoteOptions.java
@@ -205,9 +205,10 @@
@Option(
name = "experimental_remote_grpc_log",
- defaultValue = "",
+ defaultValue = "null",
category = "remote",
documentationCategory = OptionDocumentationCategory.REMOTE,
+ converter = OptionsUtils.PathFragmentConverter.class,
effectTags = {OptionEffectTag.UNKNOWN},
help =
"If specified, a path to a file to log gRPC call related details. This log consists of a"
@@ -216,7 +217,7 @@
+ "protobufs with each message prefixed by a varint denoting the size of the"
+ " following serialized protobuf message, as performed by the method "
+ "LogEntry.writeDelimitedTo(OutputStream).")
- public String experimentalRemoteGrpcLog;
+ public PathFragment experimentalRemoteGrpcLog;
@Option(
name = "incompatible_remote_symlinks",
diff --git a/src/main/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStream.java b/src/main/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStream.java
index 172750b..1482e54 100644
--- a/src/main/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStream.java
+++ b/src/main/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStream.java
@@ -22,6 +22,7 @@
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.devtools.build.lib.concurrent.ThreadSafety;
+import com.google.devtools.build.lib.vfs.Path;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.Message;
import com.google.protobuf.MessageLite;
@@ -29,8 +30,6 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Paths;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.LinkedBlockingDeque;
@@ -50,11 +49,11 @@
// To store any exception raised from the writes.
private final AtomicReference<Throwable> exception = new AtomicReference<>();
- public AsynchronousFileOutputStream(String filename) throws IOException {
+ public AsynchronousFileOutputStream(Path path) throws IOException {
this(
- filename,
+ path.toString(),
new BufferedOutputStream( // Use a buffer of 100 kByte, scientifically chosen at random.
- Files.newOutputStream(Paths.get(filename)), 100000));
+ path.getOutputStream(), 100000));
}
@VisibleForTesting
diff --git a/src/main/java/com/google/devtools/build/lib/util/io/MessageOutputStreamWrapper.java b/src/main/java/com/google/devtools/build/lib/util/io/MessageOutputStreamWrapper.java
new file mode 100644
index 0000000..58aea9c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/util/io/MessageOutputStreamWrapper.java
@@ -0,0 +1,107 @@
+// Copyright 2019 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.util.io;
+
+import com.google.common.base.Preconditions;
+import com.google.protobuf.Message;
+import com.google.protobuf.util.JsonFormat;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+
+/** Creating a MessageOutputStream from an OutputStream */
+public class MessageOutputStreamWrapper {
+ /** Outputs the messages in binary format */
+ public static class BinaryOutputStreamWrapper implements MessageOutputStream {
+ private final OutputStream stream;
+
+ public BinaryOutputStreamWrapper(OutputStream stream) {
+ this.stream = Preconditions.checkNotNull(stream);
+ }
+
+ @Override
+ public void write(Message m) throws IOException {
+ Preconditions.checkNotNull(m);
+ m.writeDelimitedTo(stream);
+ }
+
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+ }
+
+ /** Outputs the messages in JSON text format */
+ public static class JsonOutputStreamWrapper implements MessageOutputStream {
+ private final OutputStream stream;
+ private final JsonFormat.Printer printer = JsonFormat.printer().includingDefaultValueFields();
+
+ public JsonOutputStreamWrapper(OutputStream stream) {
+ Preconditions.checkNotNull(stream);
+ this.stream = stream;
+ }
+
+ @Override
+ public void write(Message m) throws IOException {
+ Preconditions.checkNotNull(m);
+ stream.write(printer.print(m).getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void close() throws IOException {
+ stream.close();
+ }
+ }
+
+ /** Outputs the messages in JSON text format */
+ public static class MessageOutputStreamCollection implements MessageOutputStream {
+ private final ArrayList<MessageOutputStream> streams = new ArrayList<>();
+
+ public boolean isEmpty() {
+ return streams.isEmpty();
+ }
+
+ public void addStream(MessageOutputStream m) {
+ streams.add(m);
+ }
+
+ @Override
+ public void write(Message m) throws IOException {
+ for (MessageOutputStream stream : streams) {
+ stream.write(m);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ IOException firstException = null;
+ for (MessageOutputStream stream : streams) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ if (firstException == null) {
+ firstException = e;
+ } else {
+ firstException.addSuppressed(e);
+ }
+ }
+ }
+ if (firstException != null) {
+ throw firstException;
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 6c10c22..bdcc604 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -386,6 +386,8 @@
"//src/main/java/com/google/devtools/build/lib:io",
"//src/main/java/com/google/devtools/build/lib:out-err",
"//src/main/java/com/google/devtools/build/lib:util",
+ "//src/main/java/com/google/devtools/build/lib/vfs",
+ "//src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs",
"//src/main/protobuf:bazel_flags_java_proto",
"//third_party:mockito",
],
diff --git a/src/test/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStreamTest.java b/src/test/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStreamTest.java
index b9e6077..2d5cc77 100644
--- a/src/test/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStreamTest.java
+++ b/src/test/java/com/google/devtools/build/lib/util/io/AsynchronousFileOutputStreamTest.java
@@ -18,22 +18,21 @@
import com.google.common.io.ByteStreams;
import com.google.devtools.build.lib.runtime.commands.proto.BazelFlagsProto.FlagInfo;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom;
import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Mockito;
@@ -42,7 +41,6 @@
/** Tests {@link AsynchronousFileOutputStream}. */
@RunWith(JUnit4.class)
public class AsynchronousFileOutputStreamTest {
- @Rule public TemporaryFolder tmp = new TemporaryFolder();
private final Random random = ThreadLocalRandom.current();
private static final char[] RAND_CHARS = "abcdefghijklmnopqrstuvwxzy0123456789-".toCharArray();
private static final int RAND_STRING_LENGTH = 10;
@@ -80,8 +78,9 @@
@Test
public void testConcurrentWrites() throws Exception {
- Path logPath = tmp.newFile().toPath();
- AsynchronousFileOutputStream out = new AsynchronousFileOutputStream(logPath.toString());
+ FileSystem fileSystem = new InMemoryFileSystem();
+ Path logPath = fileSystem.getPath("/logFile");
+ AsynchronousFileOutputStream out = new AsynchronousFileOutputStream(logPath);
Thread[] writers = new Thread[10];
final CountDownLatch start = new CountDownLatch(writers.length);
for (int i = 0; i < writers.length; ++i) {
@@ -108,7 +107,7 @@
}
out.close();
String contents =
- new String(ByteStreams.toByteArray(Files.newInputStream(logPath)), StandardCharsets.UTF_8);
+ new String(ByteStreams.toByteArray(logPath.getInputStream()), StandardCharsets.UTF_8);
for (int i = 0; i < writers.length; ++i) {
for (int j = 0; j < 10; ++j) {
assertThat(contents).contains("Thread # " + i + " time # " + j + "\n");
@@ -118,8 +117,10 @@
@Test
public void testConcurrentProtoWrites() throws Exception {
- Path logPath = tmp.newFile().toPath();
- AsynchronousFileOutputStream out = new AsynchronousFileOutputStream(logPath.toString());
+ final String filename = "/logFile";
+ FileSystem fileSystem = new InMemoryFileSystem();
+ Path logPath = fileSystem.getPath(filename);
+ AsynchronousFileOutputStream out = new AsynchronousFileOutputStream(logPath);
ArrayList<FlagInfo> messages = new ArrayList<>();
for (int i = 0; i < 100; ++i) {
messages.add(generateRandomMessage());
@@ -150,7 +151,7 @@
}
out.close();
ArrayList<FlagInfo> readMessages = new ArrayList<>();
- try (InputStream in = Files.newInputStream(logPath)) {
+ try (InputStream in = fileSystem.getPath(filename).getInputStream()) {
for (int i = 0; i < messages.size(); ++i) {
readMessages.add(FlagInfo.parseDelimitedFrom(in));
}
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index e3500ad..b8536ca 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -855,6 +855,15 @@
)
sh_test(
+ name = "bazel_execlog_test",
+ srcs = ["bazel_execlog_test.sh"],
+ data = [
+ ":test-deps",
+ ],
+ tags = ["no_windows"],
+)
+
+sh_test(
name = "new_local_repo_test",
srcs = ["new_local_repo_test.sh"],
data = [":test-deps"],
diff --git a/src/test/shell/bazel/bazel_execlog_test.sh b/src/test/shell/bazel/bazel_execlog_test.sh
new file mode 100755
index 0000000..389e6da
--- /dev/null
+++ b/src/test/shell/bazel/bazel_execlog_test.sh
@@ -0,0 +1,110 @@
+#!/bin/bash
+#
+# Copyright 2019 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.
+#
+# Tests verbosity behavior on workspace initialization
+
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "${CURRENT_DIR}/../integration_test_setup.sh" \
+ || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+function test_dir_depends() {
+ create_new_workspace
+ cat > skylark.bzl <<'EOF'
+def _dir_impl(ctx):
+ output_dir = ctx.actions.declare_directory(ctx.attr.outdir)
+ ctx.actions.run_shell(
+ outputs = [output_dir],
+ inputs = [],
+ command = """
+ mkdir -p $1/sub; \
+ echo "first file!" > $1/foo; \
+ echo "second file." > $1/sub/bar; \
+ """,
+ arguments = [output_dir.path],
+ )
+ return [ DefaultInfo(files = depset(direct = [output_dir])) ]
+
+gen_dir = rule(
+ implementation = _dir_impl,
+ attrs = {
+ "outdir": attr.string(mandatory = True),
+ }
+)
+EOF
+ cat > BUILD <<'EOF'
+load(":skylark.bzl", "gen_dir")
+
+gen_dir(
+ name = "dir",
+ outdir = "dir_name1",
+)
+
+genrule(
+ name = "rule1",
+ srcs = ["dir"],
+ outs = ["combined1.txt"],
+ cmd = "cat $(location dir)/foo $(location dir)/sub/bar > $(location combined1.txt)"
+)
+
+gen_dir(
+ name = "dir2",
+ outdir = "dir_name2",
+)
+
+genrule(
+ name = "rule2",
+ srcs = ["dir2"],
+ outs = ["combined2.txt"],
+ cmd = "cat $(location dir2)/foo $(location dir2)/sub/bar > $(location combined2.txt)"
+)
+EOF
+
+ bazel build //:all --execution_log_json_file output.json 2>&1 >> $TEST_log || fail "could not build"
+
+ # dir and dir2 are skylark functions that create a directory output
+ # rule1 and rule2 are functions that consume the directory output
+ #
+ # the output files are named such that the rule's would be placed first in the
+ # stable sorting order, followed by the dir's.
+ # Since rule1 depends on dir1 and rule2 depends on dir2, we expect the
+ # following order:
+ # dir1, rule1, dir2, rule2
+ #
+ # If dependencies were not properly accounted for, the order would have been:
+ # rule1, rule2, dir1, dir2
+
+ dir1Num=`grep "SkylarkAction dir_name1" -n output.json | grep -Eo '^[^:]+'`
+ dir2Num=`grep "SkylarkAction dir_name2" -n output.json | grep -Eo '^[^:]+'`
+ rule1Num=`grep "Executing genrule //:rule1" -n output.json | grep -Eo '^[^:]+'`
+ rule2Num=`grep "Executing genrule //:rule2" -n output.json | grep -Eo '^[^:]+'`
+
+ if [ "$rule1Num" -lt "$dir1Num" ]
+ then
+ fail "rule1 dependency on dir1 is not recornized"
+ fi
+
+ if [ "$dir2Num" -lt "$rule1Num" ]
+ then
+ fail "rule1 comes after dir2"
+ fi
+
+ if [ "$rule2Num" -lt "$dir2Num" ]
+ then
+ fail "rule2 dependency on dir2 is not recornized"
+ fi
+}
+
+run_suite "execlog_tests"