Add TEST_TMPDIR for Bazel

Fixes #138.

--
MOS_MIGRATED_REVID=91708374
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java
index d7621d9..268ba81 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/StandaloneTestStrategy.java
@@ -58,9 +58,13 @@
     * parsing XML output.
     */
 
+  private final Path workspace;
+
   public StandaloneTestStrategy(OptionsClassProvider requestOptions,
-      OptionsClassProvider startupOptions, BinTools binTools, Map<String, String> clientEnv) {
+      OptionsClassProvider startupOptions, BinTools binTools, Map<String, String> clientEnv,
+      Path workspace) {
     super(requestOptions, startupOptions, binTools, clientEnv);
+    this.workspace = workspace;
   }
 
   private static final String TEST_SETUP = "tools/test/test-setup.sh";
@@ -76,8 +80,11 @@
       throw new TestExecException(e.getMessage());
     }
 
+    Path testTmpDir = TestStrategy.getTmpRoot(
+        workspace, actionExecutionContext.getExecutor().getExecRoot(), executionOptions)
+        .getChild(getTmpDirName(action.getExecutionSettings().getExecutable().getExecPath()));
     Path workingDirectory = runfilesDir.getRelative(action.getRunfilesPrefix());
-    Map<String, String> env = getEnv(action, runfilesDir);
+    Map<String, String> env = getEnv(action, runfilesDir, testTmpDir);
     Spawn spawn = new BaseSpawn(getArgs(action), env,
         action.getTestProperties().getExecutionInfo(),
         action,
@@ -85,6 +92,13 @@
 
     Executor executor = actionExecutionContext.getExecutor();
 
+    try {
+      FileSystemUtils.createDirectoryAndParents(testTmpDir);
+    } catch (IOException e) {
+      executor.getEventHandler().handle(Event.error("Could not create TEST_TMPDIR: " + e));
+      throw new EnvironmentalExecException("Could not create TEST_TMPDIR " + testTmpDir, e);
+    }
+
     ResourceSet resources = null;
     FileOutErr fileOutErr = null;
     try {
@@ -116,15 +130,14 @@
     }
   }
 
-  private Map<String, String> getEnv(TestRunnerAction action, Path runfilesDir) {
+  private Map<String, String> getEnv(TestRunnerAction action, Path runfilesDir, Path tmpDir) {
     Map<String, String> vars = getDefaultTestEnvironment(action);
     BuildConfiguration config = action.getConfiguration();
 
     vars.putAll(config.getDefaultShellEnvironment());
     vars.putAll(action.getTestEnv());
     vars.put("TEST_SRCDIR", runfilesDir.getPathString());
-
-    // TODO(bazel-team): set TEST_TMPDIR.
+    vars.put("TEST_TMPDIR", tmpDir.getPathString());
 
     return vars;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java b/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java
index 6442943..98a2692 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/test/TestStrategy.java
@@ -47,6 +47,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.annotation.Nullable;
 
@@ -114,6 +115,8 @@
   // Used for selecting subset of testcase / testmethods.
   private static final String TEST_BRIDGE_TEST_FILTER_ENV = "TESTBRIDGE_TEST_ONLY";
 
+  // Used for generating unique temporary directory names.
+  private final AtomicInteger tmpIndex = new AtomicInteger(0);
   private final boolean statusServerRunning;
   protected final ImmutableMap<String, String> clientEnv;
   protected final ExecutionOptions executionOptions;
@@ -225,6 +228,19 @@
   }
 
   /**
+   * Returns a unique name for a temporary directory a test could use.
+   *
+   * <p>Since each test within single Blaze run must have a unique TEST_TMPDIR,
+   * we will use rule name and a unique (within single Blaze request) number
+   * to generate directory name.</p>
+   *
+   * <p>This does not create the directory.</p>
+   */
+  protected String getTmpDirName(PathFragment execPath) {
+    return execPath.getBaseName() + "_" + tmpIndex.incrementAndGet();
+  }
+
+  /**
    * Parse a test result XML file into a {@link TestCase}.
    */
   @Nullable
@@ -243,6 +259,19 @@
   }
 
   /**
+   * Returns a temporary directory for all tests in a workspace to use. Individual tests should
+   * create child directories to actually use.
+   *
+   * <p>This either dynamically generates a directory name or uses the directory specified by
+   * --test_tmpdir. This does not create the directory.</p>
+   */
+  public static Path getTmpRoot(Path workspace, Path execRoot, ExecutionOptions executionOptions) {
+    return executionOptions.testTmpDir != null
+        ? workspace.getRelative(executionOptions.testTmpDir).getRelative(TEST_TMP_ROOT)
+        : execRoot.getRelative(TEST_TMP_ROOT);
+  }
+
+  /**
    * For an given environment, returns a subset containing all variables in the given list if they
    * are defined in the given environment.
    */
diff --git a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneContextProvider.java b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneContextProvider.java
index 7a8789f..ad2d5c2 100644
--- a/src/main/java/com/google/devtools/build/lib/standalone/StandaloneContextProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/standalone/StandaloneContextProvider.java
@@ -72,12 +72,12 @@
   public StandaloneContextProvider(BlazeRuntime runtime, BuildRequest buildRequest) {
     boolean verboseFailures = buildRequest.getOptions(ExecutionOptions.class).verboseFailures;
 
-    localSpawnStrategy = new LocalSpawnStrategy(
-        runtime.getDirectories().getExecRoot(), verboseFailures);
+    localSpawnStrategy = new LocalSpawnStrategy(runtime.getExecRoot(), verboseFailures);
     this.runtime = runtime;
 
     TestActionContext testStrategy = new StandaloneTestStrategy(buildRequest,
-        runtime.getStartupOptionsProvider(), runtime.getBinTools(), runtime.getClientEnv());
+        runtime.getStartupOptionsProvider(), runtime.getBinTools(), runtime.getClientEnv(),
+        runtime.getWorkspace());
     Builder<ActionContext> strategiesBuilder = ImmutableList.builder();
     // order of strategies passed to builder is significant - when there are many strategies that
     // could potentially be used and a spawnActionContext doesn't specify which one it wants, the
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index 875207a..3654fb8 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -34,8 +34,8 @@
 )
 
 sh_test(
-    name = "bazel_testjobs_test",
-    srcs = ["bazel_testjobs_test.sh"],
+    name = "bazel_test_test",
+    srcs = ["bazel_test_test.sh"],
     data = [":test-deps"],
 )
 
diff --git a/src/test/shell/bazel/bazel_testjobs_test.sh b/src/test/shell/bazel/bazel_test_test.sh
similarity index 77%
rename from src/test/shell/bazel/bazel_testjobs_test.sh
rename to src/test/shell/bazel/bazel_test_test.sh
index 79939aa..b457f09 100755
--- a/src/test/shell/bazel/bazel_testjobs_test.sh
+++ b/src/test/shell/bazel/bazel_test_test.sh
@@ -83,4 +83,30 @@
   bazel test --test_output=errors --local_resources=10000,3,100 --runs_per_test=10 //dir:test
 }
 
-run_suite "testjobs"
+function test_tmpdir() {
+  mkdir -p foo
+  cat > foo/bar_test.sh <<EOF
+#!/bin/bash
+echo "I'm a test"
+EOF
+  chmod +x foo/bar_test.sh
+  cat > foo/BUILD <<EOF
+sh_test(
+    name = "bar_test",
+    srcs = ["bar_test.sh"],
+)
+EOF
+  bazel test --test_output=all -s //foo:bar_test >& $TEST_log || \
+    fail "Running sh_test failed"
+  expect_log "TEST_TMPDIR=/.*"
+
+  cat > foo/bar_test.sh <<EOF
+#!/bin/bash
+echo "I'm a different test"
+EOF
+  bazel test --test_output=all --test_tmpdir=$TEST_TMPDIR -s //foo:bar_test \
+    >& $TEST_log || fail "Running sh_test failed"
+  expect_log "TEST_TMPDIR=$TEST_TMPDIR"
+}
+
+run_suite "test tests"