Beispiel #1
0
  private ImmutableMap<Path, Path> getExpandedSourcePaths(
      ExecutionContext context, ImmutableMap<Path, Path> paths) throws IOException {
    ProjectFilesystem projectFilesystem = context.getProjectFilesystem();
    ImmutableMap.Builder<Path, Path> sources = ImmutableMap.builder();

    for (ImmutableMap.Entry<Path, Path> ent : paths.entrySet()) {
      if (ent.getValue().toString().endsWith(SRC_ZIP)) {
        Path destinationDirectory = projectFilesystem.resolve(tempDir.resolve(ent.getKey()));
        Files.createDirectories(destinationDirectory);

        ImmutableList<Path> zipPaths =
            Unzip.extractZipFile(
                projectFilesystem.resolve(ent.getValue()),
                destinationDirectory,
                Unzip.ExistingFileMode.OVERWRITE);
        for (Path path : zipPaths) {
          Path modulePath = destinationDirectory.relativize(path);
          sources.put(modulePath, path);
        }
      } else {
        sources.put(ent.getKey(), ent.getValue());
      }
    }

    return sources.build();
  }
Beispiel #2
0
  @Test
  public void testCreateRelativeSymlinkToFilesInRoot() throws IOException {
    ProjectFilesystem projectFilesystem = new ProjectFilesystem(tmp.getRoot());
    tmp.newFile("biz.txt");

    Path pathToDesiredLinkUnderProjectRoot = Paths.get("gamma.txt");
    Path pathToExistingFileUnderProjectRoot = Paths.get("biz.txt");
    Path relativePath =
        MorePaths.createRelativeSymlink(
            pathToDesiredLinkUnderProjectRoot,
            pathToExistingFileUnderProjectRoot,
            projectFilesystem);
    assertEquals("biz.txt", relativePath.toString());

    Path absolutePathToDesiredLinkUnderProjectRoot =
        projectFilesystem.resolve(pathToDesiredLinkUnderProjectRoot);
    assertTrue(Files.isSymbolicLink(absolutePathToDesiredLinkUnderProjectRoot));
    Path targetOfSymbolicLink = Files.readSymbolicLink(absolutePathToDesiredLinkUnderProjectRoot);
    assertEquals(relativePath, targetOfSymbolicLink);

    Path absolutePathToExistingFileUnderProjectRoot =
        projectFilesystem.resolve(pathToExistingFileUnderProjectRoot);
    Files.write(absolutePathToExistingFileUnderProjectRoot, "Hello, World!".getBytes());
    String dataReadFromSymlink =
        new String(Files.readAllBytes(absolutePathToDesiredLinkUnderProjectRoot));
    assertEquals("Hello, World!", dataReadFromSymlink);
  }
  private void assertCompDir(Path compDir, Optional<String> failure) throws Exception {
    ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot().toPath());
    CxxPlatform platform = DefaultCxxPlatforms.build(new CxxBuckConfig(new FakeBuckConfig()));

    // Build up the paths to various files the archive step will use.
    ImmutableList<String> compiler =
        platform.getCc().getCommandPrefix(new SourcePathResolver(new BuildRuleResolver()));
    Path output = filesystem.resolve(Paths.get("output.o"));
    Path relativeInput = Paths.get("input.c");
    Path input = filesystem.resolve(relativeInput);
    filesystem.writeContentsToPath("int main() {}", relativeInput);

    ImmutableList.Builder<String> preprocessorCommand = ImmutableList.builder();
    preprocessorCommand.addAll(compiler);

    ImmutableList.Builder<String> compilerCommand = ImmutableList.builder();
    compilerCommand.addAll(compiler);
    compilerCommand.add("-g");

    DebugPathSanitizer sanitizer =
        new DebugPathSanitizer(200, File.separatorChar, compDir, ImmutableBiMap.<Path, Path>of());

    // Build an archive step.
    CxxPreprocessAndCompileStep step =
        new CxxPreprocessAndCompileStep(
            CxxPreprocessAndCompileStep.Operation.COMPILE_MUNGE_DEBUGINFO,
            output,
            relativeInput,
            CxxSource.Type.C,
            Optional.of(preprocessorCommand.build()),
            Optional.of(compilerCommand.build()),
            ImmutableMap.<Path, Path>of(),
            sanitizer);

    // Execute the archive step and verify it ran successfully.
    ExecutionContext executionContext =
        TestExecutionContext.newBuilder()
            .setProjectFilesystem(new ProjectFilesystem(tmp.getRoot().toPath()))
            .build();
    TestConsole console = (TestConsole) executionContext.getConsole();
    int exitCode = step.execute(executionContext);
    if (failure.isPresent()) {
      assertNotEquals("compile step succeeded", 0, exitCode);
      assertThat(
          console.getTextWrittenToStdErr(),
          console.getTextWrittenToStdErr(),
          Matchers.containsString(failure.get()));
    } else {
      assertEquals("compile step failed: " + console.getTextWrittenToStdErr(), 0, exitCode);
      // Verify that we find the expected compilation dir embedded in the file.
      String contents = new String(Files.readAllBytes(output));
      assertThat(contents, Matchers.containsString(sanitizer.getCompilationDirectory()));
    }

    // Cleanup.
    Files.delete(input);
    Files.deleteIfExists(output);
  }
  @Test
  public void testGetBuildStepsWhenThereAreNoClassesToDex()
      throws IOException, InterruptedException {
    JavaLibrary javaLibrary = createMock(JavaLibrary.class);
    expect(javaLibrary.getClassNamesToHashes())
        .andReturn(ImmutableSortedMap.<String, HashCode>of());

    BuildContext context = createMock(BuildContext.class);
    FakeBuildableContext buildableContext = new FakeBuildableContext();
    ProjectFilesystem projectFilesystem = createMock(ProjectFilesystem.class);

    replayAll();

    BuildTarget buildTarget = BuildTargetFactory.newInstance("//foo:bar");
    BuildRuleParams params =
        new FakeBuildRuleParamsBuilder(buildTarget).setProjectFilesystem(projectFilesystem).build();
    DexProducedFromJavaLibrary preDex =
        new DexProducedFromJavaLibrary(
            params, new SourcePathResolver(new BuildRuleResolver()), javaLibrary);
    List<Step> steps = preDex.getBuildSteps(context, buildableContext);

    verifyAll();
    resetAll();

    expect(projectFilesystem.resolve(Paths.get("buck-out/gen/foo")))
        .andReturn(Paths.get("/home/user/buck-out/gen/foo"));
    expect(projectFilesystem.resolve(Paths.get("buck-out/gen/foo/bar.dex.jar")))
        .andReturn(Paths.get("/home/user/buck-out/gen/foo/bar.dex.jar"));
    replayAll();

    ExecutionContext executionContext = TestExecutionContext.newBuilder().build();

    MoreAsserts.assertSteps(
        "Do not generate a .dex.jar file.",
        ImmutableList.of(
            String.format("rm -f %s", Paths.get("/home/user/buck-out/gen/foo/bar.dex.jar")),
            String.format("mkdir -p %s", Paths.get("/home/user/buck-out/gen/foo")),
            "record_empty_dx"),
        steps,
        executionContext);

    verifyAll();
    resetAll();

    replayAll();

    Step recordArtifactAndMetadataStep = steps.get(2);
    assertThat(recordArtifactAndMetadataStep.getShortName(), startsWith("record_"));
    int exitCode = recordArtifactAndMetadataStep.execute(executionContext);
    assertEquals(0, exitCode);

    verifyAll();
  }
Beispiel #5
0
  @Override
  public StepExecutionResult execute(ExecutionContext context) throws InterruptedException {
    // Build the process, redirecting output to the provided output file.  In general,
    // it's undesirable that both stdout and stderr are being redirected to the same
    // input stream.  However, due to the nature of OS pipe buffering, we can't really
    // maintain the natural interleaving of multiple output streams in a way that we
    // can correctly associate both stdout/stderr streams to the one correct test out
    // of the many that ran.  So, our best bet is to just combine them all into stdout,
    // so they get properly interleaved with the test start and end messages that we
    // use when we parse the test output.
    ProcessBuilder builder = new ProcessBuilder();
    builder.command(command);
    builder.redirectOutput(filesystem.resolve(output).toFile());
    builder.redirectErrorStream(true);

    Process process;
    try {
      process = BgProcessKiller.startProcess(builder);
    } catch (IOException e) {
      context.logError(e, "Error starting command %s", command);
      return StepExecutionResult.ERROR;
    }

    // Run the test process, saving the exit code.
    ProcessExecutor executor = context.getProcessExecutor();
    ImmutableSet<ProcessExecutor.Option> options =
        ImmutableSet.of(ProcessExecutor.Option.EXPECTING_STD_OUT);
    ProcessExecutor.Result result =
        executor.execute(
            process,
            options,
            /* stdin */ Optional.<String>absent(),
            /* timeOutMs */ testRuleTimeoutMs,
            /* timeOutHandler */ Optional.<Function<Process, Void>>absent());

    if (result.isTimedOut()) {
      throw new HumanReadableException(
          "Timed out after %d ms running test command %s", testRuleTimeoutMs.or(-1L), command);
    }

    // Since test binaries return a non-zero exit code when unittests fail, save the exit code
    // to a file rather than signalling a step failure.
    try (FileOutputStream stream = new FileOutputStream(filesystem.resolve(exitCode).toFile())) {
      stream.write((Integer.toString(result.getExitCode())).getBytes());
    } catch (IOException e) {
      context.logError(e, "Error saving exit code to %s", exitCode);
      return StepExecutionResult.ERROR;
    }

    return StepExecutionResult.SUCCESS;
  }
  @Test
  public void testOutputFailed() throws IOException {
    ProjectFilesystem projectFilesystem = new ProjectFilesystem(tmpDir.getRoot().toPath());

    ChromeTraceBuildListener listener =
        new ChromeTraceBuildListener(
            projectFilesystem,
            new FakeClock(1409702151000000000L),
            new ObjectMapper(),
            Locale.US,
            TimeZone.getTimeZone("America/Los_Angeles"),
            /* tracesToKeep */ 3,
            false);
    try {
      assumeTrue("Can make the root directory read-only", tmpDir.getRoot().setReadOnly());
      listener.outputTrace(new BuildId("BUILD_ID"));
      fail("Expected an exception.");
    } catch (HumanReadableException e) {
      assertEquals(
          "Unable to write trace file: java.nio.file.AccessDeniedException: "
              + projectFilesystem.resolve(BuckConstant.BUCK_OUTPUT_PATH),
          e.getMessage());
    } finally {
      tmpDir.getRoot().setWritable(true);
    }
  }
  @Test
  public void ruleKeyDoesNotChangeWhenOnlyDependencyRuleKeyChanges() throws Exception {
    ProjectFilesystem filesystem = new FakeProjectFilesystem();
    BuildRuleResolver resolver =
        new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
    SourcePathResolver pathResolver = new SourcePathResolver(resolver);

    Path depOutput = Paths.get("output");
    FakeBuildRule dep =
        resolver.addToIndex(
            new FakeBuildRule(BuildTargetFactory.newInstance("//:dep"), filesystem, pathResolver));
    dep.setOutputFile(depOutput.toString());
    filesystem.writeContentsToPath("hello", dep.getPathToOutput());

    FakeFileHashCache hashCache =
        new FakeFileHashCache(ImmutableMap.of(filesystem.resolve(depOutput), HashCode.fromInt(0)));

    BuildRule rule =
        GenruleBuilder.newGenruleBuilder(BuildTargetFactory.newInstance("//:rule"))
            .setOut("out")
            .setSrcs(ImmutableList.<SourcePath>of(new BuildTargetSourcePath(dep.getBuildTarget())))
            .build(resolver, filesystem);

    RuleKey inputKey1 = new InputBasedRuleKeyBuilderFactory(0, hashCache, pathResolver).build(rule);

    RuleKey inputKey2 = new InputBasedRuleKeyBuilderFactory(0, hashCache, pathResolver).build(rule);

    assertThat(inputKey1, Matchers.equalTo(inputKey2));
  }
Beispiel #8
0
 private static boolean inputFilesUnderSymlink(
     // We use Collection<Path> instead of Iterable<Path> to prevent
     // accidentally passing in Path, since Path itself is Iterable<Path>.
     Collection<Path> inputs,
     ProjectFilesystem projectFilesystem,
     Map<Path, Path> symlinkExistenceCache,
     Map<Path, Path> newSymlinksEncountered)
     throws IOException {
   boolean result = false;
   for (Path input : inputs) {
     for (int i = 1; i < input.getNameCount(); i++) {
       Path subpath = input.subpath(0, i);
       Path resolvedSymlink = symlinkExistenceCache.get(subpath);
       if (resolvedSymlink != null) {
         LOG.debug("Detected cached symlink %s -> %s", subpath, resolvedSymlink);
         newSymlinksEncountered.put(subpath, resolvedSymlink);
         result = true;
       } else if (projectFilesystem.isSymLink(subpath)) {
         Path symlinkTarget = projectFilesystem.resolve(subpath).toRealPath();
         Path relativeSymlinkTarget =
             projectFilesystem.getPathRelativeToProjectRoot(symlinkTarget).or(symlinkTarget);
         LOG.debug("Detected symbolic link %s -> %s", subpath, relativeSymlinkTarget);
         newSymlinksEncountered.put(subpath, relativeSymlinkTarget);
         symlinkExistenceCache.put(subpath, relativeSymlinkTarget);
         result = true;
       }
     }
   }
   return result;
 }
  @Test
  public void usesCorrectCommandForPreprocess() {

    // Setup some dummy values for inputs to the CxxPreprocessAndCompile.
    SourcePathResolver pathResolver =
        new SourcePathResolver(
            new BuildRuleResolver(TargetGraph.EMPTY, new BuildTargetNodeToBuildRuleTransformer()));
    BuildTarget target = BuildTargetFactory.newInstance("//foo:bar");
    BuildRuleParams params = new FakeBuildRuleParamsBuilder(target).build();
    ProjectFilesystem filesystem = new FakeProjectFilesystem();
    ImmutableList<String> platformFlags = ImmutableList.of("-Dtest=blah");
    ImmutableList<String> ruleFlags = ImmutableList.of("-Dfoo=bar");
    Path output = Paths.get("test.ii");
    Path depFile = Paths.get("test.ii.dep");
    Path input = Paths.get("test.cpp");
    Path prefixHeader = Paths.get("prefix.pch");

    CxxPreprocessAndCompile buildRule =
        CxxPreprocessAndCompile.preprocess(
            params,
            pathResolver,
            new PreprocessorDelegate(
                pathResolver,
                DEFAULT_SANITIZER,
                DEFAULT_WORKING_DIR,
                DEFAULT_PREPROCESSOR,
                platformFlags,
                ruleFlags,
                ImmutableSet.<Path>of(),
                ImmutableSet.<Path>of(),
                ImmutableSet.<Path>of(),
                DEFAULT_FRAMEWORK_ROOTS,
                DEFAULT_FRAMEWORK_PATH_SEARCH_PATH_FUNCTION,
                Optional.<SourcePath>of(new FakeSourcePath(filesystem, prefixHeader.toString())),
                ImmutableList.of(CxxHeaders.builder().build())),
            output,
            new FakeSourcePath(input.toString()),
            DEFAULT_INPUT_TYPE,
            DEFAULT_SANITIZER);

    // Verify it uses the expected command.
    ImmutableList<String> expectedPreprocessCommand =
        ImmutableList.<String>builder()
            .add("preprocessor")
            .add("-Dtest=blah")
            .add("-Dfoo=bar")
            .add("-include")
            .add(filesystem.resolve(prefixHeader).toString())
            .add("-x", "c++")
            .add("-E")
            .add("-MD")
            .add("-MF")
            .add(depFile.toString() + ".tmp")
            .add(input.toString())
            .build();
    ImmutableList<String> actualPreprocessCommand = buildRule.makeMainStep().getCommand();
    assertEquals(expectedPreprocessCommand, actualPreprocessCommand);
  }
  @Test
  public void usesFirstCache() throws IOException {
    ProjectFilesystem filesystem = FakeProjectFilesystem.createJavaOnlyFilesystem();

    Path path = Paths.get("world.txt");
    filesystem.touch(path);

    Path fullPath = filesystem.resolve(path);
    DefaultFileHashCache innerCache = new DefaultFileHashCache(filesystem);
    StackedFileHashCache cache = new StackedFileHashCache(ImmutableList.of(innerCache));
    cache.get(fullPath);
    assertTrue(innerCache.willGet(path));
  }
Beispiel #11
0
  public static FakeFileHashCache createFromStrings(
      ProjectFilesystem filesystem, Map<String, String> pathsToHashes) {
    Map<Path, HashCode> cachedValues = new HashMap<>();
    for (Map.Entry<String, String> entry : pathsToHashes.entrySet()) {
      // Retain the original behaviour
      cachedValues.put(Paths.get(entry.getKey()), HashCode.fromString(entry.getValue()));

      // And ensure that the absolute path is also present.
      if (!entry.getKey().startsWith("/")) {
        cachedValues.put(filesystem.resolve(entry.getKey()), HashCode.fromString(entry.getValue()));
      }
    }
    return new FakeFileHashCache(cachedValues);
  }
Beispiel #12
0
  @Test
  public void shouldSetSrcAndOutToNameParameterIfNeitherAreSet() throws IOException {
    ProjectFilesystem projectFilesystem = FakeProjectFilesystem.createJavaOnlyFilesystem();
    ExportFile exportFile =
        (ExportFile)
            ExportFileBuilder.newExportFileBuilder(target)
                .build(new BuildRuleResolver(), projectFilesystem);

    List<Step> steps = exportFile.getBuildSteps(context, new FakeBuildableContext());

    MoreAsserts.assertSteps(
        "The output directory should be created and then the file should be copied there.",
        ImmutableList.of(
            "mkdir -p /opt/src/buck/buck-out/gen",
            "cp " + projectFilesystem.resolve("example.html") + " buck-out/gen/example.html"),
        steps,
        TestExecutionContext.newInstance());
    assertEquals(Paths.get("buck-out/gen/example.html"), exportFile.getPathToOutput());
  }
Beispiel #13
0
  @Test
  public void recursiveIgnorePaths() throws IOException, InterruptedException {
    Path ignoredBuildFile = Paths.get("a", "b", "BUCK");
    ImmutableSet<Path> ignore = ImmutableSet.of(ignoredBuildFile.getParent());
    ProjectFilesystem filesystem = new ProjectFilesystem(tmp.getRoot().toPath(), ignore);
    Path buildFile = Paths.get("a", "BUCK");
    filesystem.mkdirs(buildFile.getParent());
    filesystem.writeContentsToPath("", buildFile);

    filesystem.mkdirs(ignoredBuildFile.getParent());
    filesystem.writeContentsToPath("", ignoredBuildFile);

    // Test a recursive spec with an ignored dir.

    BuildFileSpec recursiveSpec = BuildFileSpec.fromRecursivePath(buildFile.getParent());
    ImmutableSet<Path> expectedBuildFiles = ImmutableSet.of(filesystem.resolve(buildFile));
    Cell cell = new TestCellBuilder().setFilesystem(filesystem).build();
    ImmutableSet<Path> actualBuildFiles = recursiveSpec.findBuildFiles(cell);
    assertEquals(expectedBuildFiles, actualBuildFiles);
  }
Beispiel #14
0
 @Override
 public int execute(ExecutionContext context) throws IOException, InterruptedException {
   MoreFiles.makeExecutable(filesystem.resolve(file));
   return 0;
 }
Beispiel #15
0
  @Override
  protected ImmutableList<String> getShellCommandInternal(ExecutionContext context) {
    Optional<Path> ndkRoot = context.getAndroidPlatformTarget().getNdkDirectory();
    if (!ndkRoot.isPresent()) {
      throw new HumanReadableException(
          "Must define a local.properties file"
              + " with a property named 'ndk.dir' that points to the absolute path of"
              + " your Android NDK directory, or set ANDROID_NDK.");
    }
    Optional<Path> ndkBuild =
        new ExecutableFinder().getOptionalExecutable(Paths.get("ndk-build"), ndkRoot.get());
    if (!ndkBuild.isPresent()) {
      throw new HumanReadableException("Unable to find ndk-build");
    }

    ConcurrencyLimit concurrencyLimit = context.getConcurrencyLimit();

    ImmutableList.Builder<String> builder = ImmutableList.builder();
    builder.add(
        ndkBuild.get().toAbsolutePath().toString(),
        "-j",
        // TODO(user): using -j here is wrong.  It lets make run too many work when we do other
        // work in parallel.  Instead, implement the GNU Make job server so make and Buck can
        // coordinate job concurrency.
        Integer.toString(concurrencyLimit.threadLimit),
        "-C",
        this.root.toString());

    if (concurrencyLimit.loadLimit < Double.POSITIVE_INFINITY) {
      builder.add("--load-average", Double.toString(concurrencyLimit.loadLimit));
    }

    Iterable<String> flags = Iterables.transform(this.flags, macroExpander);
    builder.addAll(flags);

    ProjectFilesystem projectFilesystem = context.getProjectFilesystem();
    Function<Path, Path> absolutifier = projectFilesystem.getAbsolutifier();

    // We want relative, not absolute, paths in the debug-info for binaries we build using
    // ndk_library.  Absolute paths are machine-specific, but relative ones should be the
    // same everywhere.

    Path relativePathToProject =
        absolutifier.apply(this.root).relativize(projectFilesystem.getRootPath());
    builder.add(
        "APP_PROJECT_PATH=" + absolutifier.apply(buildArtifactsDirectory) + File.separatorChar,
        "APP_BUILD_SCRIPT=" + absolutifier.apply(makefile),
        "NDK_OUT=" + absolutifier.apply(buildArtifactsDirectory) + File.separatorChar,
        "NDK_LIBS_OUT=" + projectFilesystem.resolve(binDirectory),
        "BUCK_PROJECT_DIR=" + relativePathToProject);

    // Suppress the custom build step messages (e.g. "Compile++ ...").
    if (Platform.detect() == Platform.WINDOWS) {
      builder.add("host-echo-build-step=@REM");
    } else {
      builder.add("host-echo-build-step=@#");
    }

    // If we're running verbosely, force all the subcommands from the ndk build to be printed out.
    if (context.getVerbosity().shouldPrintCommand()) {
      builder.add("V=1");
      // Otherwise, suppress everything, including the "make: entering directory..." messages.
    } else {
      builder.add("--silent");
    }

    return builder.build();
  }
  private void runCombinedTest(
      CxxPreprocessMode strategy, ImmutableList<String> expectedArguments) {
    BuildTarget testBuildTarget =
        BuildTarget.builder(BuildTargetFactory.newInstance("//foo:baz"))
            .addAllFlavors(ImmutableSet.of(CxxCompilationDatabase.COMPILATION_DATABASE))
            .build();

    final String root = "/Users/user/src";
    final Path fakeRoot = Paths.get(root);
    ProjectFilesystem filesystem = new FakeProjectFilesystem(fakeRoot);

    BuildRuleParams testBuildRuleParams =
        new FakeBuildRuleParamsBuilder(testBuildTarget).setProjectFilesystem(filesystem).build();

    BuildRuleResolver testBuildRuleResolver =
        new BuildRuleResolver(TargetGraph.EMPTY, new DefaultTargetNodeToBuildRuleTransformer());
    SourcePathResolver testSourcePathResolver = new SourcePathResolver(testBuildRuleResolver);

    BuildTarget preprocessTarget =
        BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget())
            .addFlavors(ImmutableFlavor.of("preprocess-test.cpp"))
            .build();
    BuildTarget compileTarget =
        BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget())
            .addFlavors(ImmutableFlavor.of("compile-test.cpp"))
            .build();

    PreprocessorFlags preprocessorFlags =
        PreprocessorFlags.builder()
            .addSystemIncludePaths(filesystem.resolve("foo/bar"), filesystem.resolve("test"))
            .build();

    ImmutableSortedSet.Builder<CxxPreprocessAndCompile> rules = ImmutableSortedSet.naturalOrder();
    BuildRuleParams compileBuildRuleParams;
    switch (strategy) {
      case SEPARATE:
        CxxPreprocessAndCompile preprocessRule =
            CxxPreprocessAndCompile.preprocess(
                new FakeBuildRuleParamsBuilder(preprocessTarget)
                    .setProjectFilesystem(filesystem)
                    .build(),
                testSourcePathResolver,
                new PreprocessorDelegate(
                    testSourcePathResolver,
                    CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER,
                    CxxPlatformUtils.DEFAULT_CONFIG.getHeaderVerification(),
                    filesystem.getRootPath(),
                    new DefaultPreprocessor(new HashedFileTool(Paths.get("compiler"))),
                    preprocessorFlags,
                    new RuleKeyAppendableFunction<FrameworkPath, Path>() {
                      @Override
                      public void appendToRuleKey(RuleKeyObjectSink sink) {
                        // Do nothing.
                      }

                      @Override
                      public Path apply(FrameworkPath input) {
                        throw new UnsupportedOperationException("should not be called");
                      }
                    },
                    ImmutableList.<CxxHeaders>of()),
                new CompilerDelegate(
                    testSourcePathResolver,
                    CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER,
                    new GccCompiler(new HashedFileTool(Paths.get("compiler"))),
                    CxxToolFlags.of()),
                Paths.get("test.ii"),
                new FakeSourcePath(filesystem, "test.cpp"),
                CxxSource.Type.CXX,
                CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER);
        rules.add(preprocessRule);
        compileBuildRuleParams =
            new FakeBuildRuleParamsBuilder(compileTarget)
                .setProjectFilesystem(filesystem)
                .setDeclaredDeps(ImmutableSortedSet.<BuildRule>of(preprocessRule))
                .build();
        rules.add(
            CxxPreprocessAndCompile.compile(
                compileBuildRuleParams,
                testSourcePathResolver,
                new CompilerDelegate(
                    testSourcePathResolver,
                    CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER,
                    new GccCompiler(new HashedFileTool(Paths.get("compiler"))),
                    CxxToolFlags.of()),
                Paths.get("test.o"),
                new FakeSourcePath(filesystem, "test.ii"),
                CxxSource.Type.CXX_CPP_OUTPUT,
                CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER));
        break;
      case COMBINED:
      case PIPED:
        compileBuildRuleParams =
            new FakeBuildRuleParamsBuilder(compileTarget).setProjectFilesystem(filesystem).build();
        rules.add(
            CxxPreprocessAndCompile.preprocessAndCompile(
                compileBuildRuleParams,
                testSourcePathResolver,
                new PreprocessorDelegate(
                    testSourcePathResolver,
                    CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER,
                    CxxPlatformUtils.DEFAULT_CONFIG.getHeaderVerification(),
                    filesystem.getRootPath(),
                    new DefaultPreprocessor(new HashedFileTool(Paths.get("preprocessor"))),
                    preprocessorFlags,
                    new RuleKeyAppendableFunction<FrameworkPath, Path>() {
                      @Override
                      public void appendToRuleKey(RuleKeyObjectSink sink) {
                        // Do nothing.
                      }

                      @Override
                      public Path apply(FrameworkPath input) {
                        throw new UnsupportedOperationException("should not be called");
                      }
                    },
                    ImmutableList.<CxxHeaders>of()),
                new CompilerDelegate(
                    testSourcePathResolver,
                    CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER,
                    new GccCompiler(new HashedFileTool(Paths.get("compiler"))),
                    CxxToolFlags.of()),
                Paths.get("test.o"),
                new FakeSourcePath(filesystem, "test.cpp"),
                CxxSource.Type.CXX,
                Optional.<PrecompiledHeaderReference>absent(),
                CxxPlatformUtils.DEFAULT_DEBUG_PATH_SANITIZER,
                strategy));
        break;
      default:
        throw new RuntimeException("Invalid strategy");
    }

    HeaderSymlinkTree privateSymlinkTree =
        CxxDescriptionEnhancer.createHeaderSymlinkTree(
            testBuildRuleParams,
            testBuildRuleResolver,
            testSourcePathResolver,
            CxxPlatformUtils.DEFAULT_PLATFORM,
            ImmutableMap.<Path, SourcePath>of(),
            HeaderVisibility.PRIVATE);
    HeaderSymlinkTree exportedSymlinkTree =
        CxxDescriptionEnhancer.createHeaderSymlinkTree(
            testBuildRuleParams,
            testBuildRuleResolver,
            testSourcePathResolver,
            CxxPlatformUtils.DEFAULT_PLATFORM,
            ImmutableMap.<Path, SourcePath>of(),
            HeaderVisibility.PUBLIC);
    CxxCompilationDatabase compilationDatabase =
        CxxCompilationDatabase.createCompilationDatabase(
            testBuildRuleParams,
            testSourcePathResolver,
            strategy,
            rules.build(),
            ImmutableSortedSet.of(privateSymlinkTree, exportedSymlinkTree));

    assertThat(
        compilationDatabase.getRuntimeDeps(),
        Matchers.<BuildRule>contains(exportedSymlinkTree, privateSymlinkTree));

    assertEquals(
        "getPathToOutput() should be a function of the build target.",
        BuildTargets.getGenPath(filesystem, testBuildTarget, "__%s.json"),
        compilationDatabase.getPathToOutput());

    BuildContext buildContext = FakeBuildContext.NOOP_CONTEXT;
    BuildableContext buildableContext = new FakeBuildableContext();
    List<Step> buildSteps = compilationDatabase.getPostBuildSteps(buildContext, buildableContext);
    assertEquals(2, buildSteps.size());
    assertTrue(buildSteps.get(0) instanceof MkdirStep);
    assertTrue(buildSteps.get(1) instanceof CxxCompilationDatabase.GenerateCompilationCommandsJson);

    CxxCompilationDatabase.GenerateCompilationCommandsJson step =
        (CxxCompilationDatabase.GenerateCompilationCommandsJson) buildSteps.get(1);
    Iterable<CxxCompilationDatabaseEntry> observedEntries = step.createEntries();
    Iterable<CxxCompilationDatabaseEntry> expectedEntries =
        ImmutableList.of(
            CxxCompilationDatabaseEntry.of(root, root + "/test.cpp", expectedArguments));
    MoreAsserts.assertIterablesEquals(expectedEntries, observedEntries);
  }