Supply temporary directory to java_test()

Similar to genrule() give the test running JVM a temporary directory
below buck-out that is uniquely named for this test target. Send
the path in by both the TMP environment variable and java.io.tmpdir
system property.

Fixes https://github.com/facebook/buck/issues/13
diff --git a/src/com/facebook/buck/android/RobolectricTestRule.java b/src/com/facebook/buck/android/RobolectricTestRule.java
index 9712afd..4c6b5b6 100644
--- a/src/com/facebook/buck/android/RobolectricTestRule.java
+++ b/src/com/facebook/buck/android/RobolectricTestRule.java
@@ -26,6 +26,7 @@
 import com.facebook.buck.rules.BuildRuleResolver;
 import com.facebook.buck.rules.BuildRuleType;
 import com.facebook.buck.rules.SourcePath;
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -42,7 +43,8 @@
       Optional<String> proguardConfig,
       JavacOptions javacOptions,
       List<String> vmArgs,
-      ImmutableSet<JavaLibraryRule> sourceUnderTest) {
+      ImmutableSet<JavaLibraryRule> sourceUnderTest,
+      Function<String, String> relativeToAbsolutePathFunction) {
     super(buildRuleParams,
         srcs,
         resources,
@@ -50,7 +52,8 @@
         proguardConfig,
         javacOptions,
         vmArgs,
-        sourceUnderTest);
+        sourceUnderTest,
+        relativeToAbsolutePathFunction);
   }
 
   @Override
@@ -97,7 +100,8 @@
           proguardConfig,
           javacOptions.build(),
           allVmArgs.build(),
-          sourceUnderTest);
+          sourceUnderTest,
+          relativeToAbsolutePathFunction);
     }
 
     @Override
diff --git a/src/com/facebook/buck/java/JUnitStep.java b/src/com/facebook/buck/java/JUnitStep.java
index 109d959..a7ac707 100644
--- a/src/com/facebook/buck/java/JUnitStep.java
+++ b/src/com/facebook/buck/java/JUnitStep.java
@@ -24,6 +24,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 
@@ -60,6 +61,8 @@
 
   private final boolean isDebugEnabled;
 
+  private final String tmpDirectory;
+
   private final String testRunnerClassesDirectory;
 
   /**
@@ -69,12 +72,14 @@
    *     that will be run, as well as an entry for JUnit.
    * @param testClassNames the fully qualified names of the Java tests to run
    * @param directoryForTestResults directory where test results should be written
+   * @param tmpDirectory directory tests can use for local file scratch space.
    */
   public JUnitStep(
       Set<String> classpathEntries,
       Set<String> testClassNames,
       List<String> vmArgs,
       String directoryForTestResults,
+      String tmpDirectory,
       boolean isCodeCoverageEnabled,
       boolean isDebugEnabled) {
     this(classpathEntries,
@@ -83,6 +88,7 @@
         directoryForTestResults,
         isCodeCoverageEnabled,
         isDebugEnabled,
+        tmpDirectory,
         System.getProperty("buck.testrunner_classes",
             new File("build/testrunner/classes").getAbsolutePath()));
   }
@@ -95,6 +101,7 @@
       String directoryForTestResults,
       boolean isCodeCoverageEnabled,
       boolean isDebugEnabled,
+      String tmpDirectory,
       String testRunnerClassesDirectory) {
     this.classpathEntries = ImmutableSet.copyOf(classpathEntries);
     this.testClassNames = ImmutableSet.copyOf(testClassNames);
@@ -102,6 +109,7 @@
     this.directoryForTestResults = Preconditions.checkNotNull(directoryForTestResults);
     this.isCodeCoverageEnabled = isCodeCoverageEnabled;
     this.isDebugEnabled = isDebugEnabled;
+    this.tmpDirectory = tmpDirectory;
     this.testRunnerClassesDirectory = Preconditions.checkNotNull(testRunnerClassesDirectory);
   }
 
@@ -114,6 +122,7 @@
   protected ImmutableList<String> getShellCommandInternal(ExecutionContext context) {
     ImmutableList.Builder<String> args = ImmutableList.builder();
     args.add("java");
+    args.add(String.format("-Djava.io.tmpdir=%s", tmpDirectory));
 
     // Add the output property for EMMA so if the classes are instrumented, coverage.ec will be
     // placed in the EMMA output folder.
@@ -180,6 +189,13 @@
     return args.build();
   }
 
+  @Override
+  public ImmutableMap<String, String> getEnvironmentVariables(ExecutionContext context) {
+	ImmutableMap.Builder<String, String> env = ImmutableMap.builder();
+	env.put("TMP", tmpDirectory);
+	return env.build();
+  }
+
   private void warnUser(ExecutionContext context, String message) {
     context.getStdErr().println(context.getAnsi().asWarningText(message));
   }
diff --git a/src/com/facebook/buck/java/JavaTestRule.java b/src/com/facebook/buck/java/JavaTestRule.java
index 4e7acb0..bdb6bf5 100644
--- a/src/com/facebook/buck/java/JavaTestRule.java
+++ b/src/com/facebook/buck/java/JavaTestRule.java
@@ -36,10 +36,12 @@
 import com.facebook.buck.step.TargetDevice;
 import com.facebook.buck.step.fs.MakeCleanDirectoryStep;
 import com.facebook.buck.util.BuckConstant;
+import com.facebook.buck.util.Functions;
 import com.facebook.buck.util.HumanReadableException;
 import com.facebook.buck.util.ProjectFilesystem;
 import com.facebook.buck.util.ZipFileTraversal;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
@@ -71,6 +73,7 @@
   private CompiledClassFileFinder compiledClassFileFinder;
 
   private final ImmutableSet<String> labels;
+  private final String tmpDirectory;
 
   protected JavaTestRule(BuildRuleParams buildRuleParams,
       Set<String> srcs,
@@ -79,7 +82,8 @@
       Optional<String> proguardConfig,
       JavacOptions javacOptions,
       List<String> vmArgs,
-      ImmutableSet<JavaLibraryRule> sourceUnderTest) {
+      ImmutableSet<JavaLibraryRule> sourceUnderTest,
+      Function<String, String> relativeToAbsolutePathFunction) {
     super(buildRuleParams,
         srcs,
         resources,
@@ -89,6 +93,13 @@
     this.vmArgs = ImmutableList.copyOf(vmArgs);
     this.sourceUnderTest = Preconditions.checkNotNull(sourceUnderTest);
     this.labels = ImmutableSet.copyOf(labels);
+
+    String temp = String.format("%s/%s/%s__tmp",
+	        BuckConstant.GEN_DIR,
+	        buildRuleParams.getBuildTarget().getBasePath(),
+	        getBuildTarget().getShortName()
+	        );
+    this.tmpDirectory = relativeToAbsolutePathFunction.apply(temp);
   }
 
   @Override
@@ -147,8 +158,8 @@
     ImmutableList.Builder<Step> steps = ImmutableList.builder();
 
     String pathToTestOutput = getPathToTestOutput();
-    MakeCleanDirectoryStep mkdirClean = new MakeCleanDirectoryStep(pathToTestOutput);
-    steps.add(mkdirClean);
+    steps.add(new MakeCleanDirectoryStep(pathToTestOutput));
+    steps.add(new MakeCleanDirectoryStep(tmpDirectory));
 
     // If there are android resources, then compile the uber R.java files and add them to the
     // classpath used to run the test runner.
@@ -171,6 +182,7 @@
         testClassNames,
         amendVmArgs(vmArgs, executionContext.getTargetDeviceOptional()),
         pathToTestOutput,
+        tmpDirectory,
         executionContext.isCodeCoverageEnabled(),
         executionContext.isDebugEnabled());
     steps.add(junit);
@@ -366,6 +378,8 @@
     @Nullable protected List<String> vmArgs = ImmutableList.of();
     protected ImmutableSet<BuildTarget> sourcesUnderTest = ImmutableSet.of();
     protected ImmutableSet<String> labels = ImmutableSet.of();
+    protected Function<String, String> relativeToAbsolutePathFunction =
+        Functions.RELATIVE_TO_ABSOLUTE_PATH;
 
     protected Builder(AbstractBuildRuleBuilderParams params) {
       super(params);
@@ -385,7 +399,8 @@
           proguardConfig,
           javacOptions.build(),
           vmArgs,
-          sourceUnderTest);
+          sourceUnderTest,
+          relativeToAbsolutePathFunction);
     }
 
     @Override
@@ -422,6 +437,13 @@
       return this;
     }
 
+    @VisibleForTesting
+    Builder setRelativeToAbsolutePathFunction(
+        Function<String, String> relativeToAbsolutePathFunction) {
+      this.relativeToAbsolutePathFunction = relativeToAbsolutePathFunction;
+      return this;
+    }
+
     /**
      * Generates the set of build rules that contain the source that will be under test.
      */
diff --git a/test/com/facebook/buck/java/JUnitStepTest.java b/test/com/facebook/buck/java/JUnitStepTest.java
index 2f2fa0a..0c50a49 100644
--- a/test/com/facebook/buck/java/JUnitStepTest.java
+++ b/test/com/facebook/buck/java/JUnitStepTest.java
@@ -50,6 +50,7 @@
     List<String> vmArgs = ImmutableList.of(vmArg1, vmArg2);
 
     String directoryForTestResults = "buck-out/gen/theresults/";
+    String directoryForTemp = "buck-out/gen/thetmp/";
     boolean isCodeCoverageEnabled = false;
     boolean isDebugEnabled = false;
     String testRunnerClassesDirectory = "build/classes/junit";
@@ -61,6 +62,7 @@
         directoryForTestResults,
         isCodeCoverageEnabled,
         isDebugEnabled,
+        directoryForTemp,
         testRunnerClassesDirectory);
 
     ExecutionContext executionContext = EasyMock.createMock(ExecutionContext.class);
@@ -74,6 +76,7 @@
     MoreAsserts.assertListEquals(
         ImmutableList.of(
             "java",
+            "-Djava.io.tmpdir=" + directoryForTemp,
             vmArg1,
             vmArg2,
             "-verbose",
@@ -102,6 +105,7 @@
     List<String> vmArgs = ImmutableList.of(vmArg1, vmArg2);
 
     String directoryForTestResults = "buck-out/gen/theresults/";
+    String directoryForTemp = "buck-out/gen/thetmp/";
     boolean isCodeCoverageEnabled = false;
     boolean isDebugEnabled = true;
     String testRunnerClassesDirectory = "build/classes/junit";
@@ -113,6 +117,7 @@
         directoryForTestResults,
         isCodeCoverageEnabled,
         isDebugEnabled,
+        directoryForTemp,
         testRunnerClassesDirectory);
 
 
@@ -129,6 +134,7 @@
     MoreAsserts.assertListEquals(
         ImmutableList.of(
             "java",
+            "-Djava.io.tmpdir=" + directoryForTemp,
             "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005",
             vmArg1,
             vmArg2,