@Test
  public void testCompilationDatabseWithSeperatedPreprocessAndCompileStrategy() {
    String root = "/Users/user/src";
    final Path fakeRoot = Paths.get(root);
    ProjectFilesystem filesystem =
        new FakeProjectFilesystem() {
          @Override
          public Path getRootPath() {
            return fakeRoot;
          }

          @Override
          public Path resolve(Path relativePath) {
            return fakeRoot.resolve(relativePath);
          }
        };

    BuildTarget testBuildTarget =
        BuildTarget.builder(BuildTargetFactory.newInstance("//foo:baz"))
            .addAllFlavors(ImmutableSet.of(CxxCompilationDatabase.COMPILATION_DATABASE))
            .build();
    BuildRuleParams testBuildRuleParams =
        new FakeBuildRuleParamsBuilder(testBuildTarget).setProjectFilesystem(filesystem).build();

    BuildRuleResolver testBuildRuleResolver = new BuildRuleResolver();
    SourcePathResolver testSourcePathResolver = new SourcePathResolver(testBuildRuleResolver);

    BuildTarget preprocessTarget =
        BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget())
            .addFlavors(ImmutableFlavor.of("preprocess-test.cpp"))
            .build();
    BuildRuleParams preprocessBuildRuleParams =
        new FakeBuildRuleParamsBuilder(preprocessTarget).setProjectFilesystem(filesystem).build();
    CxxPreprocessAndCompile testPreprocessRule =
        new CxxPreprocessAndCompile(
            preprocessBuildRuleParams,
            testSourcePathResolver,
            CxxPreprocessAndCompileStep.Operation.PREPROCESS,
            Optional.<Preprocessor>of(
                new DefaultPreprocessor(new HashedFileTool(Paths.get("compiler")))),
            Optional.of(ImmutableList.<String>of()),
            Optional.of(ImmutableList.<String>of()),
            Optional.<Compiler>absent(),
            Optional.<ImmutableList<String>>absent(),
            Optional.<ImmutableList<String>>absent(),
            Paths.get("test.ii"),
            new TestSourcePath("test.cpp"),
            CxxSource.Type.CXX_CPP_OUTPUT,
            ImmutableSet.of(Paths.get("foo/bar"), Paths.get("test")),
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            Optional.<SourcePath>absent(),
            ImmutableList.<CxxHeaders>of(),
            CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER);

    BuildTarget compileTarget =
        BuildTarget.builder(testBuildRuleParams.getBuildTarget().getUnflavoredBuildTarget())
            .addFlavors(ImmutableFlavor.of("compile-test.cpp"))
            .build();
    BuildRuleParams compileBuildRuleParams =
        new FakeBuildRuleParamsBuilder(compileTarget)
            .setProjectFilesystem(filesystem)
            .setDeclaredDeps(ImmutableSortedSet.<BuildRule>of(testPreprocessRule))
            .build();
    CxxPreprocessAndCompile testCompileRule =
        new CxxPreprocessAndCompile(
            compileBuildRuleParams,
            testSourcePathResolver,
            CxxPreprocessAndCompileStep.Operation.COMPILE,
            Optional.<Preprocessor>absent(),
            Optional.<ImmutableList<String>>absent(),
            Optional.<ImmutableList<String>>absent(),
            Optional.<Compiler>of(new DefaultCompiler(new HashedFileTool(Paths.get("compiler")))),
            Optional.of(ImmutableList.<String>of()),
            Optional.of(ImmutableList.<String>of()),
            Paths.get("test.o"),
            new TestSourcePath("test.ii"),
            CxxSource.Type.CXX_CPP_OUTPUT,
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            Optional.<SourcePath>absent(),
            ImmutableList.<CxxHeaders>of(),
            CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER);

    CxxCompilationDatabase compilationDatabase =
        CxxCompilationDatabase.createCompilationDatabase(
            testBuildRuleParams,
            testSourcePathResolver,
            CxxPreprocessMode.SEPARATE,
            ImmutableSortedSet.of(testPreprocessRule, testCompileRule));

    assertEquals(
        "getPathToOutput() should be a function of the build target.",
        Paths.get("buck-out/gen/foo/__baz#compilation-database.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",
                ImmutableList.of(
                    "compiler",
                    "-I",
                    "foo/bar",
                    "-I",
                    "test",
                    "-x",
                    "c++-cpp-output",
                    "-c",
                    "-o",
                    "test.o",
                    "test.cpp")));
    MoreAsserts.assertIterablesEquals(expectedEntries, observedEntries);
  }
  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() {
          @Override
          public Path getRootPath() {
            return fakeRoot;
          }

          @Override
          public Path resolve(Path relativePath) {
            return fakeRoot.resolve(relativePath);
          }
        };

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

    BuildRuleResolver testBuildRuleResolver = new BuildRuleResolver();
    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();

    ImmutableSortedSet.Builder<CxxPreprocessAndCompile> rules = ImmutableSortedSet.naturalOrder();
    CxxPreprocessAndCompileStep.Operation operation;
    BuildRuleParams compileBuildRuleParams;
    switch (strategy) {
      case SEPARATE:
        operation = CxxPreprocessAndCompileStep.Operation.COMPILE;
        CxxPreprocessAndCompile preprocessRule =
            new CxxPreprocessAndCompile(
                new FakeBuildRuleParamsBuilder(preprocessTarget)
                    .setProjectFilesystem(filesystem)
                    .build(),
                testSourcePathResolver,
                operation,
                Optional.<Preprocessor>of(
                    new DefaultPreprocessor(new HashedFileTool(Paths.get("preprocessor")))),
                Optional.of(ImmutableList.<String>of()),
                Optional.of(ImmutableList.<String>of()),
                Optional.<Compiler>absent(),
                Optional.<ImmutableList<String>>absent(),
                Optional.<ImmutableList<String>>absent(),
                Paths.get("test.o"),
                new TestSourcePath("test.cpp"),
                CxxSource.Type.CXX,
                ImmutableSet.of(Paths.get("foo/bar"), Paths.get("test")),
                ImmutableSet.<Path>of(),
                ImmutableSet.<Path>of(),
                ImmutableSet.<Path>of(),
                Optional.<SourcePath>absent(),
                ImmutableList.<CxxHeaders>of(),
                CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER);
        rules.add(preprocessRule);
        compileBuildRuleParams =
            new FakeBuildRuleParamsBuilder(compileTarget)
                .setProjectFilesystem(filesystem)
                .setDeclaredDeps(ImmutableSortedSet.<BuildRule>of(preprocessRule))
                .build();
        break;
      case COMBINED:
        operation = CxxPreprocessAndCompileStep.Operation.COMPILE_MUNGE_DEBUGINFO;
        compileBuildRuleParams =
            new FakeBuildRuleParamsBuilder(compileTarget).setProjectFilesystem(filesystem).build();
        break;
      case PIPED:
        operation = CxxPreprocessAndCompileStep.Operation.PIPED_PREPROCESS_AND_COMPILE;
        compileBuildRuleParams =
            new FakeBuildRuleParamsBuilder(compileTarget).setProjectFilesystem(filesystem).build();
        break;
      default:
        throw new RuntimeException("Invalid strategy");
    }
    rules.add(
        new CxxPreprocessAndCompile(
            compileBuildRuleParams,
            testSourcePathResolver,
            operation,
            Optional.<Preprocessor>of(
                new DefaultPreprocessor(new HashedFileTool(Paths.get("preprocessor")))),
            Optional.of(ImmutableList.<String>of()),
            Optional.of(ImmutableList.<String>of()),
            Optional.<Compiler>of(new DefaultCompiler(new HashedFileTool(Paths.get("compiler")))),
            Optional.of(ImmutableList.<String>of()),
            Optional.of(ImmutableList.<String>of()),
            Paths.get("test.o"),
            new TestSourcePath("test.cpp"),
            CxxSource.Type.CXX,
            ImmutableSet.of(Paths.get("foo/bar"), Paths.get("test")),
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            ImmutableSet.<Path>of(),
            Optional.<SourcePath>absent(),
            ImmutableList.<CxxHeaders>of(),
            CxxPlatforms.DEFAULT_DEBUG_PATH_SANITIZER));

    CxxCompilationDatabase compilationDatabase =
        CxxCompilationDatabase.createCompilationDatabase(
            testBuildRuleParams, testSourcePathResolver, strategy, rules.build());

    assertEquals(
        "getPathToOutput() should be a function of the build target.",
        Paths.get("buck-out/gen/foo/__baz#compilation-database.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);
  }
  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);
  }