//===--- iwyu_output_test.cc - test iwyu_output.{h,cc} --------------------===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // Tests iwyu_output.{h,cc} #include "iwyu_output.h" #include #include #include "iwyu_ast_util.h" #include "iwyu_globals.h" #include "iwyu_verrs.h" #include "testing/base/public/gunit.h" #undef ATTRIBUTE_UNUSED #include "clang/Basic/SourceLocation.h" using clang::NamedDecl; using clang::SourceLocation; namespace include_what_you_use { namespace internal { // Internal routines from iwyu_output.cc exposed for testing purposes. string MungedForwardDeclareLine(const NamedDecl* decl); set CalculateMinimalIncludes( const set& direct_includes, const set& associated_direct_includes, vector* uses); void ProcessForwardDeclare(OneUse* use); void ProcessFullUse(OneUse* use); void ProcessSymbolUse(OneUse* use); void CalculateIwyuForForwardDeclareUse(OneUse* use, const set& actual_includes, const set& desired_includes); void CalculateIwyuForFullUse(OneUse* use, const set& actual_includes, const set& desired_includes); void CalculateDesiredIncludesAndForwardDeclares( const vector& uses, const set associated_desired_includes, vector* lines); string PrintableIncludeOrForwardDeclareLine( const OneIncludeOrForwardDeclareLine& line, const set& associated_quoted_includes); string PrintableDiffs(const string& filename, const set& associated_quoted_includes, const vector& lines); struct FakeSourceLocation : public SourceLocation { FakeSourceLocation(const string& fp, int ln) : SourceLocation(), filepath(fp), linenum(ln) { } string filepath; int linenum; }; } // namespace internal using internal::FakeNamedDecl; // from iwyu_output.h using internal::FakeSourceLocation; // Some helper routines on fakes. // TODO(csilvers): these don't get called where we want them to. :-( string GetFilePath(const FakeSourceLocation& fake_loc) { return fake_loc.filepath; } string GetFilePath(const FakeNamedDecl* fake_decl) { return fake_decl->decl_filepath(); } // Note these return a string, not a FileEntry*. string GetFileEntry(const FakeSourceLocation& fake_loc) { return fake_loc.filepath; } string GetFileEntry(const FakeNamedDecl* fake_decl) { return fake_decl->decl_filepath(); } // DynCasting a fake always fails. inline internal::DynCastPtr DynCastFrom(FakeNamedDecl* ptr) { return internal::DynCastPtr(NULL); } namespace { // A helper for creating an IncludeLineData object. template OneIncludeOrForwardDeclareLine MakeDesiredIncludeLine( const string& quoted_includee, const char* (&used_symbols)[N]) { OneIncludeOrForwardDeclareLine retval(quoted_includee, 1); retval.set_desired(); for (int i = 0; i < N; i++) retval.AddSymbolUse(used_symbols[i]); return retval; } // Common case where we only need one symbol. OneIncludeOrForwardDeclareLine MakeDesiredIncludeLine( const string& quoted_includee, const char* used_symbol) { const char* used_symbols[1] = { used_symbol }; return MakeDesiredIncludeLine(quoted_includee, used_symbols); } TEST(MungedForwardDeclareLineTest, Works) { // TODO(csilvers): figure out how to test on a real RecordDecl/etc. } TEST(CalculateMinimalIncludes, Works) { // TODO(csilvers): add tests here } TEST(ProcessForwardDeclareTest, A1) { // TODO(csilvers): figure out how to test with a real record_decl/tpl_decl } TEST(ProcessForwardDeclareTest, A2) { // TODO(csilvers): figure out how to test with a real class_tpl_decl } TEST(ProcessForwardDeclareTest, A3) { // TODO(csilvers): figure out how to test with a real record_decl } TEST(ProcessForwardDeclareTest, A4) { // TODO(csilvers): figure out how to test with a real record_decl } TEST(ProcessFullUseTest, B1) { return; // TODO(csilvers): re-enable when we can fake the SourceLoc and decl FakeNamedDecl decl("class", "MyClass", "src/includes/myclass.h", 10); // Test the use being *before* the definition in the file (shouldn't matter) OneUse samefile_use(&decl, FakeSourceLocation("src/includes/myclass.h", 5), OneUse::kFullUse, false, NULL); OneUse difffile_use(&decl, FakeSourceLocation("src/myclass.cc", 10), OneUse::kFullUse, false, NULL); internal::ProcessFullUse(&samefile_use); internal::ProcessFullUse(&difffile_use); EXPECT_TRUE(samefile_use.ignore_use()); EXPECT_FALSE(difffile_use.ignore_use()); } TEST(ProcessFullUseTest, B2) { // TODO(csilvers): figure out how to test with a real fn_decl } TEST(ProcessFullUseTest, B3) { return; // TODO(csilvers): re-enable when we can fake the SourceLoc and decl FakeNamedDecl cc_decl("class", "MyClass", "src/myclass.cc", 10); // Test the use being *before* the definition in the file (shouldn't matter) OneUse h_use(&cc_decl, FakeSourceLocation("src/includes/myclass.h", 5), OneUse::kFullUse, false, NULL); OneUse cc_use(&cc_decl, FakeSourceLocation("src/main.cc", 10), OneUse::kFullUse, false, NULL); internal::ProcessFullUse(&h_use); internal::ProcessFullUse(&cc_use); EXPECT_TRUE(h_use.ignore_use()); EXPECT_FALSE(cc_use.ignore_use()); } TEST(ProcessFullUseTest, B4) { // TODO(csilvers): figure out how to test with a real cxx_method_decl } TEST(ProcessSymbolUseTest, B5) { return; // TODO(csilvers): re-enable when we can fake the SourceLoc and decl // Test the use being *before* the definition in the file (shouldn't matter) OneUse samefile_use("mysym", "src/includes/myclass.h", FakeSourceLocation("src/includes/myclass.h", 5)); OneUse difffile_use("sym2", "src/includes/myclass.h", FakeSourceLocation("src/myclass.cc", 5)); internal::ProcessSymbolUse(&samefile_use); internal::ProcessSymbolUse(&difffile_use); EXPECT_TRUE(samefile_use.ignore_use()); EXPECT_FALSE(difffile_use.ignore_use()); } TEST(CalculateIwyuForForwardDeclareUseTest, D1) { // TODO(csilvers): figure out how to test with a real record_decl } TEST(CalculateIwyuForForwardDeclareUseTest, D2) { // TODO(csilvers): figure out how to test with a real record_decl } TEST(CalculateIwyuForFullUse, E1) { OneUse iwyu_violation("mysym", "src/includes/myclass.h", FakeSourceLocation("src/myclass.cc", 5)); OneUse iwyu_ok("mysym", "src/includes/myclass.h", FakeSourceLocation("src/myclass.cc", 5)); iwyu_violation.set_suggested_header(""); iwyu_ok.set_suggested_header("\"includes/myclass.h\""); set actual_includes; actual_includes.insert("\"includes/myclass.h\""); actual_includes.insert(""); const set empty; // desired includes: doesn't matter for this test internal::CalculateIwyuForFullUse(&iwyu_violation, actual_includes, empty); internal::CalculateIwyuForFullUse(&iwyu_ok, actual_includes, empty); EXPECT_TRUE(iwyu_violation.is_iwyu_violation()); EXPECT_FALSE(iwyu_ok.is_iwyu_violation()); } TEST(SanitizeSymbolTest, Works) { // TODO(csilvers): Do this when we can create non-trivial test decls. } TEST(CalculateDesiredIncludesAndForwardDeclaresTest, Works) { // TODO(csilvers): Implement. } // This test fixture automatically saves/restores the verbose level // between tests to prevent leaking side effects. class VerboseTest : public ::testing::Test { protected: VerboseTest() : old_verbose_level_(GetVerboseLevel()) { // 2 is the default verbose level for tests that don't care much // about the level. SetVerboseLevel(2); } ~VerboseTest() { SetVerboseLevel(old_verbose_level_); } private: const int old_verbose_level_; }; class PrintableIncludeOrForwardDeclareLineTest : public VerboseTest { }; TEST_F(PrintableIncludeOrForwardDeclareLineTest, CutsOffAt80Columns) { const set empty; OneIncludeOrForwardDeclareLine foo_line("", 1); foo_line.AddSymbolUse("FOO"); foo_line.AddSymbolUse("FooClass"); foo_line.AddSymbolUse("FooClass::a"); foo_line.AddSymbolUse("FooClass::~FooClass"); foo_line.AddSymbolUse("FunctionOnFoo"); foo_line.AddSymbolUse("Foo_Enum"); foo_line.AddSymbolUse("Foo_Typedef"); foo_line.set_desired(); EXPECT_EQ("#include " " // for FOO, FooClass, FooClass::a, etc\n", internal::PrintableIncludeOrForwardDeclareLine(foo_line, empty)); OneIncludeOrForwardDeclareLine bar_line("", 2); bar_line.AddSymbolUse("this_symbol_is_so_big_there_is_no_room_for_it_at_all"); bar_line.set_desired(); EXPECT_EQ("#include \n", internal::PrintableIncludeOrForwardDeclareLine(bar_line, empty)); OneIncludeOrForwardDeclareLine long_line( "\"deep/enough/to/go/past/the/indent.h\"", 3); long_line.AddSymbolUse("DeepFn"); long_line.AddSymbolUse("Deep_Typedef"); long_line.AddSymbolUse("DEEP"); long_line.AddSymbolUse("TheInkyDeeps"); long_line.set_desired(); EXPECT_EQ("#include \"deep/enough/to/go/past/the/indent.h\"" " // for DEEP, DeepFn, etc\n", internal::PrintableIncludeOrForwardDeclareLine(long_line, empty)); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, SortsSymbolsByFreqThenAlpha) { SetVerboseLevel(3); // so we see a printout of *all* the symbols const set empty; OneIncludeOrForwardDeclareLine line("", 2); line.AddSymbolUse("FOO"); line.AddSymbolUse("FooClass"); line.AddSymbolUse("FooClass::a"); line.AddSymbolUse("Foo_Enum"); line.AddSymbolUse("FooClass::~FooClass"); line.AddSymbolUse("FunctionOnFoo"); line.AddSymbolUse("FooClass"); line.AddSymbolUse("Foo_Typedef"); line.AddSymbolUse("FooClass"); line.AddSymbolUse("Foo_Typedef"); line.AddSymbolUse("FunctionOnFoo"); line.set_desired(); EXPECT_EQ("#include " " // for FooClass, Foo_Typedef," " FunctionOnFoo, FOO, FooClass::a, FooClass::~FooClass, Foo_Enum\n", internal::PrintableIncludeOrForwardDeclareLine(line, empty)); } TEST(PrintableDiffsTest, PrintsEmptyIncludes) { vector no_lines; EXPECT_EQ("\n" "(baz.cc has correct #includes/fwd-decls)\n", internal::PrintableDiffs("baz.cc", set(), no_lines)); } TEST(PrintableDiffsTest, ProperlyOrdersIncludeLines) { set associated_includes; associated_includes.insert("\"baz.h\""); associated_includes.insert("\"baz-inl.h\""); vector lines; lines.push_back(MakeDesiredIncludeLine("\"foo.h\"", "FOO")); lines.push_back(MakeDesiredIncludeLine("\"bar.h\"", "BAR")); lines.push_back(MakeDesiredIncludeLine("\"baz.h\"", "BAZ")); lines.push_back(MakeDesiredIncludeLine("\"foo-inl.h\"", "FooFn")); lines.push_back(MakeDesiredIncludeLine("\"bar-inl.h\"", "BarFn")); lines.push_back(MakeDesiredIncludeLine("\"baz-inl.h\"", "BazFn")); lines.push_back(MakeDesiredIncludeLine("", "printf")); lines.push_back(MakeDesiredIncludeLine("", "string::iterator")); lines.push_back(MakeDesiredIncludeLine("", "sstring")); lines.push_back(MakeDesiredIncludeLine("", "isascii")); EXPECT_EQ("\n" "baz.cc should add these lines:\n" "#include \"baz.h\"\n" "#include \"baz-inl.h\"\n" "#include // for isascii\n" "#include // for printf\n" "#include // for sstring\n" "#include // for string::iterator\n" "#include \"bar-inl.h\" // for BarFn\n" "#include \"bar.h\" // for BAR\n" "#include \"foo-inl.h\" // for FooFn\n" "#include \"foo.h\" // for FOO\n" "\n" "baz.cc should remove these lines:\n" "\n" "The full include-list for baz.cc:\n" "#include \"baz.h\"\n" "#include \"baz-inl.h\"\n" "#include // for isascii\n" "#include // for printf\n" "#include // for sstring\n" "#include // for string::iterator\n" "#include \"bar-inl.h\" // for BarFn\n" "#include \"bar.h\" // for BAR\n" "#include \"foo-inl.h\" // for FooFn\n" "#include \"foo.h\" // for FOO\n" "---\n", internal::PrintableDiffs("baz.cc", associated_includes, lines)); } TEST(PrintableDiffsTest, ShowLineNumbersForDeletedIncludesEvenWithUses) { const set empty; vector lines; OneIncludeOrForwardDeclareLine line("\"foo.h\"", 1); line.set_present(); // *not* desired line.AddSymbolUse("Foo (ptr only)"); lines.push_back(line); EXPECT_EQ("\n" "baz.cc should add these lines:\n" "\n" "baz.cc should remove these lines:\n" "- #include \"foo.h\" // lines 1-1\n" "\n" "The full include-list for baz.cc:\n" "---\n", internal::PrintableDiffs("baz.cc", empty, lines)); } #if 0 TEST_F(IwyuFileInfo_PrintableIncludeInformation, PrintsForwardDeclares) { IwyuFileInfo f("baz.cc"); f.AddDesiredIncludeNeededBySymbol("\"foo.h\"", "FOO"); f.AddDesiredIncludeNeededBySymbol("\"bar.h\"", "BAR"); f.AddDesiredIncludeNeededBySymbol("\"baz.h\"", "BAZ"); f.AddDesiredIncludeNeededBySymbol("", "string::iterator"); f.AddDesiredIncludeNeededBySymbol("", "printf"); FakeNamedDecl record1("struct", "Foo"); f.AddDesiredForwardDeclare("\"record1.h\"", &record1); f.NormalizeDesiredSetsForCc(set()); EXPECT_EQ("\n" "baz.cc should add these lines:\n" "#include \"baz.h\"\n" "#include // for printf\n" "#include // for string::iterator\n" "#include \"bar.h\" // for BAR\n" "#include \"foo.h\" // for FOO\n" "struct Foo;\n" "\n" "baz.cc should remove these lines:\n" "\n" "The full include-list for baz.cc:\n" "#include \"baz.h\"\n" "#include // for printf\n" "#include // for string::iterator\n" "#include \"bar.h\" // for BAR\n" "#include \"foo.h\" // for FOO\n" "struct Foo;\n" "---\n", f.PrintableIncludeInformation()); } using internal::PrintableIncludeOrForwardDeclareLine; // This test fixture automatically saves/restores the verbose level // between tests to prevent leaking side effects. class PrintableIncludeOrForwardDeclareLineTest : public VerboseTest { }; TEST_F(PrintableIncludeOrForwardDeclareLineTest, ShowsReasonForNonMainCUHeaders) { // The reason should be printed even at a low verbose level. SetVerboseLevel(1); const char* symbols[] = { "Foo" }; EXPECT_EQ("#include \"foo.h\" " "// for Foo\n", // single symbol PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"foo.h\"", symbols))); const char* symbols2[] = { "Foo", "Bar" }; EXPECT_EQ("#include \"foo.h\" " "// for Foo, Bar\n", // multiple symbols PrintableIncludeOrForwardDeclareLine( "x.h", MakeIncludeLine("\"foo.h\"", symbols2))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, HidesReasonForMainCUHeaders) { // The reason should be hidden even at a high verbose level. SetVerboseLevel(5); const char* symbols[] = { "Foo" }; EXPECT_EQ("#include \"foo.h\"\n", PrintableIncludeOrForwardDeclareLine( "foo.cc", MakeIncludeLine("\"foo.h\"", symbols))); EXPECT_EQ("#include \"foo.h\"\n", PrintableIncludeOrForwardDeclareLine( "foo_test.cc", MakeIncludeLine("\"foo.h\"", symbols))); EXPECT_EQ("#include \"foo-inl.h\"\n", PrintableIncludeOrForwardDeclareLine( "foo.cc", MakeIncludeLine("\"foo-inl.h\"", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, TruncatesLongSymbolListAtLowVerbosityLevels) { SetVerboseLevel(2); const char* symbols[] = { "Foo", "Bar", "SomeA", "SomeB", "SomeC", "BigX", "BigY", "BigZ", "What", "Which", "Where" }; EXPECT_EQ("#include " " // for Foo, Bar, SomeA, SomeB, etc\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("", symbols))); EXPECT_EQ( "#include " "// for Foo, etc\n", PrintableIncludeOrForwardDeclareLine( "a.cc", MakeIncludeLine("", symbols))); EXPECT_EQ( // This line is exactly 80-character long, including the \n. "#include " "// for Foo, etc\n", PrintableIncludeOrForwardDeclareLine( "foo.cc", MakeIncludeLine( "", symbols))); EXPECT_EQ( // There's no space on this line for " // for Foo, etc". We // should cut off the comment entirely instead of printing // " // for etc" or " // for ". "#include \n", PrintableIncludeOrForwardDeclareLine( "foo.cc", MakeIncludeLine( "", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, PreservesLongSymbolListAtHighVerbosityLevels) { SetVerboseLevel(3); const char* symbols[] = { "Foo", "Bar", "SomeA", "SomeB", "SomeC", "BigX", "BigY", "BigZ", "What", "Which", "Where" }; EXPECT_EQ("#include " "// for Foo, Bar, SomeA, SomeB, SomeC, BigX, BigY, BigZ, What, " "Which, Where\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, OmitsReasonForSuperLongPathsAtLowVerbosityLevels) { SetVerboseLevel(2); const char* symbols[] = { "Foo" }; EXPECT_EQ("#include \"some/really/really/really/really/really/deeply/buried/" "package/foo.h\"\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"some/really/really/really/really/really/" "deeply/buried/package/foo.h\"", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, PreservesReasonForSuperLongPathsAtHighVerbosityLevels) { SetVerboseLevel(3); const char* symbols[] = { "Foo" }; EXPECT_EQ("#include \"some/really/really/really/really/really/deeply/buried/" "package/foo.h\" // for Foo\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"some/really/really/really/really/really/" "deeply/buried/package/foo.h\"", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, PadsSpacesForShortPaths) { const char* symbols[] = { "Foo" }; EXPECT_EQ("#include \"foo.h\" // for Foo\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"foo.h\"", symbols))); EXPECT_EQ("#include \"foo/bar/b.h\" // for Foo\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"foo/bar/b.h\"", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, DoesNotPadSpacesForLongPaths) { const char* symbols[] = { "Foo" }; EXPECT_EQ("#include \"foo/bar/bluh.h\" // for Foo\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"foo/bar/bluh.h\"", symbols))); EXPECT_EQ("#include \"foo/bar/bluh-bluh.h\" // for Foo\n", PrintableIncludeOrForwardDeclareLine( "bar.cc", MakeIncludeLine("\"foo/bar/bluh-bluh.h\"", symbols))); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, PrintsLineNumbersWhenThereIsNoUsedSymbol) { IncludeOrForwardDeclareLineData data; data.line = "#include "; data.line_num_range = make_pair(12, 12); // data.used_symbols is intentionally left empty. // We don't really care about .is_present and .is_desired. EXPECT_EQ("#include // lines 12-12\n", PrintableIncludeOrForwardDeclareLine("bar.cc", data)); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, PrintsForwardDeclaresProperlyWithLineNumbers) { IncludeOrForwardDeclareLineData data; data.line = "class Foo;"; data.line_num_range = make_pair(7, 8); EXPECT_EQ("class Foo; // lines 7-8\n", PrintableIncludeOrForwardDeclareLine("bar.cc", data)); } TEST_F(PrintableIncludeOrForwardDeclareLineTest, PrintsForwardDeclaresProperlyWithNoLineNumbers) { IncludeOrForwardDeclareLineData data; data.line = "class Foo;"; data.line_num_range = make_pair(-1, -1); EXPECT_EQ("class Foo;\n", PrintableIncludeOrForwardDeclareLine("bar.cc", data)); } #endif } // unnamed namespace } // namespace include_what_you_use int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); include_what_you_use::InitGlobalsAndFlagsForTesting(); return RUN_ALL_TESTS(); }