tools/apiview/parsers/cpp-api-parser/ParseTests/tests.cpp (606 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT #include "ApiViewProcessor.hpp" #include "AstDumper.hpp" #include "AstNode.hpp" #include "JsonDumper.hpp" #include "TextDumper.hpp" #include "gtest/gtest.h" #include <clang/Frontend/FrontendActions.h> #include <clang/Tooling/CompilationDatabase.h> #include <clang/Tooling/Tooling.h> #include <filesystem> #include <fstream> #include <ostream> #include <string_view> using namespace nlohmann::literals; using namespace clang; using namespace clang::tooling; class TestParser : public testing::Test { protected: void SetUp() override {} void TearDown() override {} private: void OutputClassDbToConsole(std::unique_ptr<AzureClassesDatabase> const& classDb) { TextDumper dumpToConsole(std::cout); classDb->DumpClassDatabase(&dumpToConsole); } void OutputClassDbToFile( std::unique_ptr<AzureClassesDatabase> const& classDb, std::string_view const& fileToDump, bool isAzureTest = false, bool isAzureCore = false) { std::ofstream outFile(static_cast<std::string>(fileToDump), std::ios::out); outFile << R"(#include <memory>)" << std::endl; outFile << R"(#include <string>)" << std::endl; outFile << R"(#include <string_view>)" << std::endl; outFile << R"(#include <chrono>)" << std::endl; outFile << R"(#include <map>)" << std::endl; outFile << R"(#include <set>)" << std::endl; outFile << R"(#include <functional>)" << std::endl; outFile << R"(#include <vector>)" << std::endl; outFile << R"(#include <exception>)" << std::endl; outFile << R"(#include <stdexcept>)" << std::endl; if (isAzureTest) { if (!isAzureCore) { outFile << R"(#include <azure/core/nullable.hpp>)" << std::endl; outFile << R"(#include <azure/core/datetime.hpp>)" << std::endl; outFile << R"(#include <azure/core/response.hpp>)" << std::endl; outFile << R"(#include <azure/core/context.hpp>)" << std::endl; outFile << R"(#include <azure/core/credentials/credentials.hpp>)" << std::endl; outFile << R"(#include <azure/core/internal/extendable_enumeration.hpp>)" << std::endl; outFile << R"(#include <azure/core/internal/client_options.hpp>)" << std::endl; } else { outFile << R"(typedef void *HINTERNET;)" << std::endl; } } outFile << std::endl; TextDumper dumpToFile(outFile); classDb->DumpClassDatabase(&dumpToFile); } class TestCompilationDatabase : public CompilationDatabase { std::vector<std::filesystem::path> m_filesToCompile; std::filesystem::path m_sourceLocation; std::vector<std::filesystem::path> m_additionalIncludePaths; std::vector<std::string> m_additionalArguments; public: TestCompilationDatabase( std::vector<std::filesystem::path> const& filesToCompile, std::filesystem::path const& sourceLocation, std::vector<std::filesystem::path> const& additionalIncludePaths, std::vector<std::string> const& additionalArguments) : CompilationDatabase(), m_filesToCompile(filesToCompile), m_sourceLocation(std::filesystem::absolute(sourceLocation)), m_additionalIncludePaths{additionalIncludePaths} { for (auto const& arg : additionalArguments) { m_additionalArguments.push_back(std::string(arg)); } } std::vector<std::string> defaultCommandLine{ "clang++.exe", "-DAZ_RTTI", "-fcxx-exceptions", "-c", "-std=c++14", "-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH"}; // Inherited via CompilationDatabase virtual std::vector<CompileCommand> getCompileCommands(llvm::StringRef FilePath) const override { for (auto const& file : m_filesToCompile) { if (file.compare(FilePath.str()) == 0) { std::vector<std::string> commandLine{defaultCommandLine}; // Add the source location to the include paths. commandLine.push_back( "-I" + m_sourceLocation.string()); llvm::outs() << "Adding include directory: " << m_sourceLocation.string() << "\n"; // Add any additional include directories (as absolute paths). for (auto const& arg : m_additionalIncludePaths) { std::string includePath{ std::filesystem::absolute(arg).string()}; commandLine.push_back("-I" + includePath); llvm::outs() << "Adding include directory: " << includePath << "\n"; } // And finally, include any additional command line arguments. for (auto const& arg : m_additionalArguments) { commandLine.push_back(arg); } commandLine.push_back(file.string()); std::vector<clang::tooling::CompileCommand> rv; std::string outputFile; rv.push_back(CompileCommand( m_sourceLocation.string(), file.string(), commandLine, "")); return rv; } } return std::vector<clang::tooling::CompileCommand>(); } }; protected: // Note that the baseFIleName should be unique within a given test case because clang's // OptionCategory is global. bool SyntaxCheckClassDb( std::unique_ptr<AzureClassesDatabase> const& classDb, std::string const& baseFileName, bool isAzureTest = false, bool isAzureCore = false) { std::filesystem::path tempFileName{std::filesystem::temp_directory_path()}; tempFileName.append(baseFileName); if (std::filesystem::exists(tempFileName)) { std::filesystem::remove(tempFileName); } OutputClassDbToFile( classDb, tempFileName.string(), isAzureTest, isAzureCore); // auto currentTestName{::testing::UnitTest::GetInstance()->current_test_info()->name()}; std::vector<std::filesystem::path> additionalIncludeDirectories; if (isAzureTest && !isAzureCore) { additionalIncludeDirectories.push_back( std::filesystem::absolute(R"(.\Tests\core\azure-core\inc)")); } TestCompilationDatabase compileDb( {std::filesystem::absolute(tempFileName)}, ".", additionalIncludeDirectories, {}); std::vector<std::string> sourceFiles; sourceFiles.push_back( std::filesystem::absolute(tempFileName).string()); clang::tooling::ClangTool tool(compileDb, sourceFiles); int result = tool.run(clang::tooling::newFrontendActionFactory<clang::SyntaxOnlyAction>().get()); return result == 0; } }; TEST_F(TestParser, Create) { ApiViewProcessor processor(".", R"({})"_json); auto& db = processor.GetClassesDatabase(); JsonDumper jsonDumper("My First Review", "Azure Core", "Azure.Core"); processor.GetClassesDatabase()->DumpClassDatabase(&jsonDumper); EXPECT_EQ(0ul, db->GetAstNodeMap().size()); } TEST_F(TestParser, CompileSimple) { { ApiViewProcessor processor("tests", std::string_view("SimpleTest.json")); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(8ul, db->GetAstNodeMap().size()); EXPECT_TRUE(SyntaxCheckClassDb(db, "SimpleTestGenerated.cpp")); } } TEST_F(TestParser, CompileWithErrors) { { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "CompileError1.cpp" ], "additionalIncludeDirectories": [ ], "additionalCompilerSwitches": [], "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_NE(processor.ProcessApiView(), 0); } } struct NsDumper : AstDumper { // Inherited via AstDumper virtual void InsertNewline() override {} virtual void InsertWhitespace(int count) override {} virtual void InsertKeyword(std::string_view const& keyword) override {} virtual void InsertText(std::string_view const& text) override {} virtual void InsertPunctuation(char punctuation) override {} virtual void InsertLineIdMarker() override {} virtual void InsertTypeName( std::string_view const& type, std::string_view const& typeNavigationId) override { } virtual void InsertMemberName(std::string_view const& member, std::string_view const&) override {} virtual void InsertIdentifier(std::string_view const& identifier) override {} virtual void InsertStringLiteral(std::string_view const& str) override {} virtual void InsertLiteral(std::string_view const& str) override {} virtual void InsertComment(std::string_view const& comment) override {} virtual void AddDocumentRangeStart() override {} virtual void AddDocumentRangeEnd() override {} virtual void AddDeprecatedRangeStart() override {} virtual void AddDeprecatedRangeEnd() override {} virtual void AddSkipDiffRangeStart() override {} virtual void AddSkipDiffRangeEnd() override {} virtual void AddExternalLinkStart(std::string_view const& url) override {} virtual void AddExternalLinkEnd() override {} virtual void DumpTypeHierarchyNode( std::shared_ptr<TypeHierarchy::TypeHierarchyNode> const& node) override { } struct Message { std::string_view DiagnosticId; std::string_view FailingId; }; std::vector<Message> Messages; virtual void DumpMessageNode(ApiViewMessage const& msg) override { Messages.push_back({msg.DiagnosticId, msg.TargetId}); } }; TEST_F(TestParser, NamespaceFilter1) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "SimpleTest.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": "Test" } )"_json); NsDumper dumper; EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(8ul, db->GetAstNodeMap().size()); db->DumpClassDatabase(&dumper); EXPECT_EQ(5ul, dumper.Messages.size()); EXPECT_EQ("CPA0003", dumper.Messages[0].DiagnosticId); EXPECT_EQ("GlobalFunction4", dumper.Messages[0].FailingId); EXPECT_EQ("CPA0002", dumper.Messages[1].DiagnosticId); EXPECT_EQ("char *GlobalFunction4(int character)", dumper.Messages[1].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[2].DiagnosticId); EXPECT_EQ("A::AB::ABC::FunctionABC", dumper.Messages[2].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[3].DiagnosticId); EXPECT_EQ("A::AB::FunctionAB", dumper.Messages[3].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[4].DiagnosticId); EXPECT_EQ("A::AB::ABD::ABE::FunctionABE", dumper.Messages[4].FailingId); EXPECT_TRUE(SyntaxCheckClassDb(db, "SimpleTestGenerated1.cpp")); } TEST_F(TestParser, NamespaceFilter2) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "SimpleTest.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": "Test::Inner" } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(8ul, db->GetAstNodeMap().size()); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_EQ(7ul, dumper.Messages.size()); EXPECT_EQ("CPA0003", dumper.Messages[0].DiagnosticId); EXPECT_EQ("Test::Function1", dumper.Messages[0].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[1].DiagnosticId); EXPECT_EQ("Test::Function2", dumper.Messages[1].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[2].DiagnosticId); EXPECT_EQ("GlobalFunction4", dumper.Messages[2].FailingId); EXPECT_EQ("CPA0002", dumper.Messages[3].DiagnosticId); EXPECT_EQ("char *GlobalFunction4(int character)", dumper.Messages[3].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[4].DiagnosticId); EXPECT_EQ("A::AB::ABC::FunctionABC", dumper.Messages[4].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[5].DiagnosticId); EXPECT_EQ("A::AB::FunctionAB", dumper.Messages[5].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[6].DiagnosticId); EXPECT_EQ("A::AB::ABD::ABE::FunctionABE", dumper.Messages[6].FailingId); EXPECT_TRUE(SyntaxCheckClassDb(db, "SimpleTestGenerated2.cpp")); } TEST_F(TestParser, NamespaceFilter3) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "SimpleTest.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": "Axxx" } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(8ul, db->GetAstNodeMap().size()); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_EQ(8ul, dumper.Messages.size()); EXPECT_EQ("CPA0003", dumper.Messages[0].DiagnosticId); EXPECT_EQ("Test::Function1", dumper.Messages[0].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[1].DiagnosticId); EXPECT_EQ("Test::Function2", dumper.Messages[1].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[2].DiagnosticId); EXPECT_EQ("Test::Inner::Function3", dumper.Messages[2].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[3].DiagnosticId); EXPECT_EQ("GlobalFunction4", dumper.Messages[3].FailingId); EXPECT_EQ("CPA0002", dumper.Messages[4].DiagnosticId); EXPECT_EQ("char *GlobalFunction4(int character)", dumper.Messages[4].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[5].DiagnosticId); EXPECT_EQ("A::AB::ABC::FunctionABC", dumper.Messages[5].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[6].DiagnosticId); EXPECT_EQ("A::AB::FunctionAB", dumper.Messages[6].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[7].DiagnosticId); EXPECT_EQ("A::AB::ABD::ABE::FunctionABE", dumper.Messages[7].FailingId); EXPECT_TRUE(SyntaxCheckClassDb(db, "SimpleTestGenerated3.cpp")); } TEST_F(TestParser, NamespaceFilter4) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "SimpleTest.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": null, "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": ["Test::Inner", "A::AB"] } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(8ul, db->GetAstNodeMap().size()); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_EQ(4ul, dumper.Messages.size()); EXPECT_EQ("CPA0003", dumper.Messages[0].DiagnosticId); EXPECT_EQ("Test::Function1", dumper.Messages[0].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[1].DiagnosticId); EXPECT_EQ("Test::Function2", dumper.Messages[1].FailingId); EXPECT_EQ("CPA0003", dumper.Messages[2].DiagnosticId); EXPECT_EQ("GlobalFunction4", dumper.Messages[2].FailingId); EXPECT_EQ("CPA0002", dumper.Messages[3].DiagnosticId); EXPECT_EQ("char *GlobalFunction4(int character)", dumper.Messages[3].FailingId); EXPECT_TRUE(SyntaxCheckClassDb(db, "SimpleTestGenerated4.cpp")); } TEST_F(TestParser, Class1) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "ClassesWithInternalAndDetail.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); processor.ProcessApiView(); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(16ul, db->GetAstNodeMap().size()); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_EQ(55ul, dumper.Messages.size()); size_t internalTypes = 0; for (const auto& msg : dumper.Messages) { if (msg.DiagnosticId == "CPA0007") { internalTypes += 1; } } EXPECT_EQ(internalTypes, 8ul); EXPECT_TRUE(SyntaxCheckClassDb(db, "Classes1.cpp")); } TEST_F(TestParser, Class2) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "ClassesWithInternalAndDetail.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": true, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(16ul, db->GetAstNodeMap().size()); EXPECT_TRUE(SyntaxCheckClassDb(db, "Classes1B.cpp")); } TEST_F(TestParser, Expressions) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "ExpressionTests.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": true, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_TRUE(SyntaxCheckClassDb(db, "Expression1.cpp")); } TEST_F(TestParser, Templates) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "TemplateTests.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": null, "allowInternal": true, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); // Until we get parsing types working correctly, we can't do the syntax check tests. // EXPECT_TRUE(SyntaxCheckClassDb(db, "Template1.cpp")); } TEST_F(TestParser, UsingNamespace) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "UsingNamespace.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": null, "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_TRUE(SyntaxCheckClassDb(db, "UsingNamespace1.cpp")); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_EQ(1ul, dumper.Messages.size()); size_t usingNamespaces = 0; for (const auto& msg : dumper.Messages) { if (msg.DiagnosticId == "CPA000A") { usingNamespaces += 1; } } EXPECT_EQ(usingNamespaces, 1ul); } TEST_F(TestParser, TestDtors) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "DestructorTests.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": null, "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_TRUE(SyntaxCheckClassDb(db, "DestructorTests1.cpp")); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_EQ(2ul, dumper.Messages.size()); size_t nonVirtualDestructor = 0; for (const auto& msg : dumper.Messages) { if (msg.DiagnosticId == "CPA000B") { nonVirtualDestructor += 1; } } EXPECT_EQ(nonVirtualDestructor, 2ul); } TEST_F(TestParser, TestFriends) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "FriendsTests.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": true, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_EQ(4ul, db->GetAstNodeMap().size()); EXPECT_EQ("M2", db->GetAstNodeMap().at(1)->Name()); NsDumper dumper; db->DumpClassDatabase(&dumper); EXPECT_TRUE(SyntaxCheckClassDb(db, "Friends_Test1.cpp")); } TEST_F(TestParser, TestDocuments) { ApiViewProcessor processor("tests", R"({ "sourceFilesToProcess": [ "DocumentationTests.cpp" ], "additionalIncludeDirectories": [], "additionalCompilerSwitches": null, "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": null } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_TRUE(SyntaxCheckClassDb(db, "DocumentationTests1.cpp")); NsDumper dumper; db->DumpClassDatabase(&dumper); } #if 0 TEST_F(TestParser, AzureCore1) { ApiViewProcessor processor("Tests", R"({ "sourceFilesToProcess": [ "core/azure-core/inc/azure/core.hpp" ], "additionalIncludeDirectories": ["core/azure-core/inc"], "additionalCompilerSwitches": ["-Qunused-arguments"], "allowInternal": true, "includeDetail": false, "includePrivate": false, "filterNamespace": "Azure::" } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_LT(1ul, db->GetAstNodeMap().size()); EXPECT_TRUE(SyntaxCheckClassDb(db, "Core1.cpp", true, true)); } TEST_F(TestParser, AzureCore2) { ApiViewProcessor processor(R"(tests\\core\azure-core)"); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_LT(1ul, db->GetAstNodeMap().size()); EXPECT_TRUE(SyntaxCheckClassDb(db, "Core2.cpp", true, true)); } TEST_F(TestParser, AzureAttestation) { ApiViewProcessor processor(R"(tests\attestation\azure-security-attestation)", R"({ "sourceFilesToProcess": null, "additionalIncludeDirectories": ["../../core/azure-core/inc", "inc"], "additionalCompilerSwitches": [], "allowInternal": false, "includeDetail": false, "includePrivate": false, "filterNamespace": "Azure::Security::Attestation" } )"_json); EXPECT_EQ(processor.ProcessApiView(), 0); auto& db = processor.GetClassesDatabase(); EXPECT_TRUE(SyntaxCheckClassDb(db, "Attestation1.cpp", true, false)); } #endif