// Copyright 2011 Google Inc. 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. #include "clean.h" #include "build.h" #include "util.h" #include "test.h" #ifndef _WIN32 #include #endif namespace { const char kTestFilename[] = "CleanTest-tempfile"; struct CleanTest : public StateTestWithBuiltinRules { VirtualFileSystem fs_; BuildConfig config_; virtual void SetUp() { config_.verbosity = BuildConfig::QUIET; } }; TEST_F(CleanTest, CleanAll) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build in1: cat src1\n" "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(4, cleaner.cleaned_files_count()); EXPECT_EQ(4u, fs_.files_removed_.size()); // Check they are removed. string err; EXPECT_EQ(0, fs_.Stat("in1", &err)); EXPECT_EQ(0, fs_.Stat("out1", &err)); EXPECT_EQ(0, fs_.Stat("in2", &err)); EXPECT_EQ(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanAllDryRun) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build in1: cat src1\n" "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); config_.dry_run = true; Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(4, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); // Check they are not removed. string err; EXPECT_LT(0, fs_.Stat("in1", &err)); EXPECT_LT(0, fs_.Stat("out1", &err)); EXPECT_LT(0, fs_.Stat("in2", &err)); EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(4, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanTarget) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build in1: cat src1\n" "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanTarget("out1")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); // Check they are removed. string err; EXPECT_EQ(0, fs_.Stat("in1", &err)); EXPECT_EQ(0, fs_.Stat("out1", &err)); EXPECT_LT(0, fs_.Stat("in2", &err)); EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanTarget("out1")); EXPECT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanTargetDryRun) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build in1: cat src1\n" "build out1: cat in1\n" "build in2: cat src2\n" "build out2: cat in2\n")); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); config_.dry_run = true; Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanTarget("out1")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); // Check they are not removed. string err; EXPECT_LT(0, fs_.Stat("in1", &err)); EXPECT_LT(0, fs_.Stat("out1", &err)); EXPECT_LT(0, fs_.Stat("in2", &err)); EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanTarget("out1")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanRule) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cat_e\n" " command = cat -e $in > $out\n" "build in1: cat_e src1\n" "build out1: cat in1\n" "build in2: cat_e src2\n" "build out2: cat in2\n")); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanRule("cat_e")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); // Check they are removed. string err; EXPECT_EQ(0, fs_.Stat("in1", &err)); EXPECT_LT(0, fs_.Stat("out1", &err)); EXPECT_EQ(0, fs_.Stat("in2", &err)); EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanRule("cat_e")); EXPECT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanRuleDryRun) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cat_e\n" " command = cat -e $in > $out\n" "build in1: cat_e src1\n" "build out1: cat in1\n" "build in2: cat_e src2\n" "build out2: cat in2\n")); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); config_.dry_run = true; Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanRule("cat_e")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); // Check they are not removed. string err; EXPECT_LT(0, fs_.Stat("in1", &err)); EXPECT_LT(0, fs_.Stat("out1", &err)); EXPECT_LT(0, fs_.Stat("in2", &err)); EXPECT_LT(0, fs_.Stat("out2", &err)); fs_.files_removed_.clear(); ASSERT_EQ(0, cleaner.CleanRule("cat_e")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanRuleGenerator) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule regen\n" " command = cat $in > $out\n" " generator = 1\n" "build out1: cat in1\n" "build out2: regen in2\n")); fs_.Create("out1", ""); fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(1, cleaner.cleaned_files_count()); EXPECT_EQ(1u, fs_.files_removed_.size()); fs_.Create("out1", ""); EXPECT_EQ(0, cleaner.CleanAll(/*generator=*/true)); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanDepFile) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n" " command = cc $in > $out\n" " depfile = $out.d\n" "build out1: cc in1\n")); fs_.Create("out1", ""); fs_.Create("out1.d", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanDepFileOnCleanTarget) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n" " command = cc $in > $out\n" " depfile = $out.d\n" "build out1: cc in1\n")); fs_.Create("out1", ""); fs_.Create("out1.d", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanTarget("out1")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanDepFileOnCleanRule) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n" " command = cc $in > $out\n" " depfile = $out.d\n" "build out1: cc in1\n")); fs_.Create("out1", ""); fs_.Create("out1.d", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanRule("cc")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanDyndep) { // Verify that a dyndep file can be loaded to discover a new output // to be cleaned. ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat in || dd\n" " dyndep = dd\n" )); fs_.Create("in", ""); fs_.Create("dd", "ninja_dyndep_version = 1\n" "build out | out.imp: dyndep\n" ); fs_.Create("out", ""); fs_.Create("out.imp", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); string err; EXPECT_EQ(0, fs_.Stat("out", &err)); EXPECT_EQ(0, fs_.Stat("out.imp", &err)); } TEST_F(CleanTest, CleanDyndepMissing) { // Verify that a missing dyndep file is tolerated. ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out: cat in || dd\n" " dyndep = dd\n" )); fs_.Create("in", ""); fs_.Create("out", ""); fs_.Create("out.imp", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(1, cleaner.cleaned_files_count()); EXPECT_EQ(1u, fs_.files_removed_.size()); string err; EXPECT_EQ(0, fs_.Stat("out", &err)); EXPECT_EQ(1, fs_.Stat("out.imp", &err)); } TEST_F(CleanTest, CleanRspFile) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc\n" " command = cc $in > $out\n" " rspfile = $rspfile\n" " rspfile_content=$in\n" "build out1: cc in1\n" " rspfile = cc1.rsp\n")); fs_.Create("out1", ""); fs_.Create("cc1.rsp", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(2u, fs_.files_removed_.size()); } TEST_F(CleanTest, CleanRsp) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cat_rsp \n" " command = cat $rspfile > $out\n" " rspfile = $rspfile\n" " rspfile_content = $in\n" "build in1: cat src1\n" "build out1: cat in1\n" "build in2: cat_rsp src2\n" " rspfile=in2.rsp\n" "build out2: cat_rsp in2\n" " rspfile=out2.rsp\n" )); fs_.Create("in1", ""); fs_.Create("out1", ""); fs_.Create("in2.rsp", ""); fs_.Create("out2.rsp", ""); fs_.Create("in2", ""); fs_.Create("out2", ""); Cleaner cleaner(&state_, config_, &fs_); ASSERT_EQ(0, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanTarget("out1")); EXPECT_EQ(2, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanTarget("in2")); EXPECT_EQ(2, cleaner.cleaned_files_count()); ASSERT_EQ(0, cleaner.CleanRule("cat_rsp")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_EQ(6u, fs_.files_removed_.size()); // Check they are removed. string err; EXPECT_EQ(0, fs_.Stat("in1", &err)); EXPECT_EQ(0, fs_.Stat("out1", &err)); EXPECT_EQ(0, fs_.Stat("in2", &err)); EXPECT_EQ(0, fs_.Stat("out2", &err)); EXPECT_EQ(0, fs_.Stat("in2.rsp", &err)); EXPECT_EQ(0, fs_.Stat("out2.rsp", &err)); } TEST_F(CleanTest, CleanFailure) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build dir: cat src1\n")); fs_.MakeDir("dir"); Cleaner cleaner(&state_, config_, &fs_); EXPECT_NE(0, cleaner.CleanAll()); } TEST_F(CleanTest, CleanPhony) { string err; ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build phony: phony t1 t2\n" "build t1: cat\n" "build t2: cat\n")); fs_.Create("phony", ""); fs_.Create("t1", ""); fs_.Create("t2", ""); // Check that CleanAll does not remove "phony". Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_LT(0, fs_.Stat("phony", &err)); fs_.Create("t1", ""); fs_.Create("t2", ""); // Check that CleanTarget does not remove "phony". EXPECT_EQ(0, cleaner.CleanTarget("phony")); EXPECT_EQ(2, cleaner.cleaned_files_count()); EXPECT_LT(0, fs_.Stat("phony", &err)); } TEST_F(CleanTest, CleanDepFileAndRspFileWithSpaces) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule cc_dep\n" " command = cc $in > $out\n" " depfile = $out.d\n" "rule cc_rsp\n" " command = cc $in > $out\n" " rspfile = $out.rsp\n" " rspfile_content = $in\n" "build out$ 1: cc_dep in$ 1\n" "build out$ 2: cc_rsp in$ 1\n" )); fs_.Create("out 1", ""); fs_.Create("out 2", ""); fs_.Create("out 1.d", ""); fs_.Create("out 2.rsp", ""); Cleaner cleaner(&state_, config_, &fs_); EXPECT_EQ(0, cleaner.CleanAll()); EXPECT_EQ(4, cleaner.cleaned_files_count()); EXPECT_EQ(4u, fs_.files_removed_.size()); string err; EXPECT_EQ(0, fs_.Stat("out 1", &err)); EXPECT_EQ(0, fs_.Stat("out 2", &err)); EXPECT_EQ(0, fs_.Stat("out 1.d", &err)); EXPECT_EQ(0, fs_.Stat("out 2.rsp", &err)); } struct CleanDeadTest : public CleanTest, public BuildLogUser{ virtual void SetUp() { // In case a crashing test left a stale file behind. unlink(kTestFilename); CleanTest::SetUp(); } virtual void TearDown() { unlink(kTestFilename); } virtual bool IsPathDead(StringPiece) const { return false; } }; TEST_F(CleanDeadTest, CleanDead) { State state; ASSERT_NO_FATAL_FAILURE(AssertParse(&state, "rule cat\n" " command = cat $in > $out\n" "build out1: cat in\n" "build out2: cat in\n" )); ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "build out2: cat in\n" )); fs_.Create("in", ""); fs_.Create("out1", ""); fs_.Create("out2", ""); BuildLog log1; string err; EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err)); ASSERT_EQ("", err); log1.RecordCommand(state.edges_[0], 15, 18); log1.RecordCommand(state.edges_[1], 20, 25); log1.Close(); BuildLog log2; EXPECT_TRUE(log2.Load(kTestFilename, &err)); ASSERT_EQ("", err); ASSERT_EQ(2u, log2.entries().size()); ASSERT_TRUE(log2.LookupByOutput("out1")); ASSERT_TRUE(log2.LookupByOutput("out2")); // First use the manifest that describe how to build out1. Cleaner cleaner1(&state, config_, &fs_); EXPECT_EQ(0, cleaner1.CleanDead(log2.entries())); EXPECT_EQ(0, cleaner1.cleaned_files_count()); EXPECT_EQ(0u, fs_.files_removed_.size()); EXPECT_NE(0, fs_.Stat("in", &err)); EXPECT_NE(0, fs_.Stat("out1", &err)); EXPECT_NE(0, fs_.Stat("out2", &err)); // Then use the manifest that does not build out1 anymore. Cleaner cleaner2(&state_, config_, &fs_); EXPECT_EQ(0, cleaner2.CleanDead(log2.entries())); EXPECT_EQ(1, cleaner2.cleaned_files_count()); EXPECT_EQ(1u, fs_.files_removed_.size()); EXPECT_EQ("out1", *(fs_.files_removed_.begin())); EXPECT_NE(0, fs_.Stat("in", &err)); EXPECT_EQ(0, fs_.Stat("out1", &err)); EXPECT_NE(0, fs_.Stat("out2", &err)); // Nothing to do now. EXPECT_EQ(0, cleaner2.CleanDead(log2.entries())); EXPECT_EQ(0, cleaner2.cleaned_files_count()); EXPECT_EQ(1u, fs_.files_removed_.size()); EXPECT_EQ("out1", *(fs_.files_removed_.begin())); EXPECT_NE(0, fs_.Stat("in", &err)); EXPECT_EQ(0, fs_.Stat("out1", &err)); EXPECT_NE(0, fs_.Stat("out2", &err)); log2.Close(); } } // anonymous namespace