473 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			473 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
| //===- unittest/AST/ASTImporterFixtures.h - AST unit test support ---------===//
 | |
| //
 | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | |
| // See https://llvm.org/LICENSE.txt for license information.
 | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| //
 | |
| /// \file
 | |
| /// Fixture classes for testing the ASTImporter.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #ifndef LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H
 | |
| #define LLVM_CLANG_UNITTESTS_AST_IMPORTER_FIXTURES_H
 | |
| 
 | |
| #include "gmock/gmock.h"
 | |
| 
 | |
| #include "clang/AST/ASTImporter.h"
 | |
| #include "clang/AST/ASTImporterSharedState.h"
 | |
| #include "clang/Frontend/ASTUnit.h"
 | |
| #include "clang/Testing/CommandLineArgs.h"
 | |
| #include "llvm/Support/Error.h"
 | |
| #include "llvm/Support/ErrorHandling.h"
 | |
| 
 | |
| #include "DeclMatcher.h"
 | |
| #include "MatchVerifier.h"
 | |
| 
 | |
| #include <sstream>
 | |
| 
 | |
| namespace clang {
 | |
| 
 | |
| class ASTImporter;
 | |
| class ASTImporterSharedState;
 | |
| class ASTUnit;
 | |
| 
 | |
| namespace ast_matchers {
 | |
| 
 | |
| const StringRef DeclToImportID = "declToImport";
 | |
| const StringRef DeclToVerifyID = "declToVerify";
 | |
| 
 | |
| // Creates a virtual file and assigns that to the context of given AST. If the
 | |
| // file already exists then the file will not be created again as a duplicate.
 | |
| void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
 | |
|                                std::unique_ptr<llvm::MemoryBuffer> &&Buffer);
 | |
| 
 | |
| void createVirtualFileIfNeeded(ASTUnit *ToAST, StringRef FileName,
 | |
|                                StringRef Code);
 | |
| 
 | |
| // Common base for the different families of ASTImporter tests that are
 | |
| // parameterized on the compiler options which may result a different AST. E.g.
 | |
| // -fms-compatibility or -fdelayed-template-parsing.
 | |
| class CompilerOptionSpecificTest : public ::testing::Test {
 | |
| protected:
 | |
|   // Return the extra arguments appended to runtime options at compilation.
 | |
|   virtual std::vector<std::string> getExtraArgs() const { return {}; }
 | |
| 
 | |
|   // Returns the argument vector used for a specific language option, this set
 | |
|   // can be tweaked by the test parameters.
 | |
|   std::vector<std::string>
 | |
|   getCommandLineArgsForLanguage(TestLanguage Lang) const {
 | |
|     std::vector<std::string> Args = getCommandLineArgsForTesting(Lang);
 | |
|     std::vector<std::string> ExtraArgs = getExtraArgs();
 | |
|     for (const auto &Arg : ExtraArgs) {
 | |
|       Args.push_back(Arg);
 | |
|     }
 | |
|     return Args;
 | |
|   }
 | |
| };
 | |
| 
 | |
| const auto DefaultTestArrayForRunOptions =
 | |
|     std::array<std::vector<std::string>, 4>{
 | |
|         {std::vector<std::string>(),
 | |
|          std::vector<std::string>{"-fdelayed-template-parsing"},
 | |
|          std::vector<std::string>{"-fms-compatibility"},
 | |
|          std::vector<std::string>{"-fdelayed-template-parsing",
 | |
|                                   "-fms-compatibility"}}};
 | |
| 
 | |
| const auto DefaultTestValuesForRunOptions =
 | |
|     ::testing::ValuesIn(DefaultTestArrayForRunOptions);
 | |
| 
 | |
| // This class provides generic methods to write tests which can check internal
 | |
| // attributes of AST nodes like getPreviousDecl(), isVirtual(), etc. Also,
 | |
| // this fixture makes it possible to import from several "From" contexts.
 | |
| class ASTImporterTestBase : public CompilerOptionSpecificTest {
 | |
| 
 | |
|   const char *const InputFileName = "input.cc";
 | |
|   const char *const OutputFileName = "output.cc";
 | |
| 
 | |
| public:
 | |
|   /// Allocates an ASTImporter (or one of its subclasses).
 | |
|   typedef std::function<ASTImporter *(
 | |
|       ASTContext &, FileManager &, ASTContext &, FileManager &, bool,
 | |
|       const std::shared_ptr<ASTImporterSharedState> &SharedState)>
 | |
|       ImporterConstructor;
 | |
| 
 | |
|   // ODR handling type for the AST importer.
 | |
|   ASTImporter::ODRHandlingType ODRHandling;
 | |
| 
 | |
|   // The lambda that constructs the ASTImporter we use in this test.
 | |
|   ImporterConstructor Creator;
 | |
| 
 | |
| private:
 | |
|   // Buffer for the To context, must live in the test scope.
 | |
|   std::string ToCode;
 | |
| 
 | |
|   // Represents a "From" translation unit and holds an importer object which we
 | |
|   // use to import from this translation unit.
 | |
|   struct TU {
 | |
|     // Buffer for the context, must live in the test scope.
 | |
|     std::string Code;
 | |
|     std::string FileName;
 | |
|     std::unique_ptr<ASTUnit> Unit;
 | |
|     TranslationUnitDecl *TUDecl = nullptr;
 | |
|     std::unique_ptr<ASTImporter> Importer;
 | |
|     ImporterConstructor Creator;
 | |
|     ASTImporter::ODRHandlingType ODRHandling;
 | |
| 
 | |
|     TU(StringRef Code, StringRef FileName, std::vector<std::string> Args,
 | |
|        ImporterConstructor C = ImporterConstructor(),
 | |
|        ASTImporter::ODRHandlingType ODRHandling =
 | |
|            ASTImporter::ODRHandlingType::Conservative);
 | |
|     ~TU();
 | |
| 
 | |
|     void
 | |
|     lazyInitImporter(const std::shared_ptr<ASTImporterSharedState> &SharedState,
 | |
|                      ASTUnit *ToAST);
 | |
|     Decl *import(const std::shared_ptr<ASTImporterSharedState> &SharedState,
 | |
|                  ASTUnit *ToAST, Decl *FromDecl);
 | |
|     llvm::Expected<Decl *>
 | |
|     importOrError(const std::shared_ptr<ASTImporterSharedState> &SharedState,
 | |
|                   ASTUnit *ToAST, Decl *FromDecl);
 | |
|     QualType import(const std::shared_ptr<ASTImporterSharedState> &SharedState,
 | |
|                     ASTUnit *ToAST, QualType FromType);
 | |
|   };
 | |
| 
 | |
|   // We may have several From contexts and related translation units. In each
 | |
|   // AST, the buffers for the source are handled via references and are set
 | |
|   // during the creation of the AST. These references must point to a valid
 | |
|   // buffer until the AST is alive. Thus, we must use a list in order to avoid
 | |
|   // moving of the stored objects because that would mean breaking the
 | |
|   // references in the AST. By using a vector a move could happen when the
 | |
|   // vector is expanding, with the list we won't have these issues.
 | |
|   std::list<TU> FromTUs;
 | |
| 
 | |
|   // Initialize the shared state if not initialized already.
 | |
|   void lazyInitSharedState(TranslationUnitDecl *ToTU);
 | |
| 
 | |
|   void lazyInitToAST(TestLanguage ToLang, StringRef ToSrcCode,
 | |
|                      StringRef FileName);
 | |
| 
 | |
| protected:
 | |
|   std::shared_ptr<ASTImporterSharedState> SharedStatePtr;
 | |
| 
 | |
| public:
 | |
|   // We may have several From context but only one To context.
 | |
|   std::unique_ptr<ASTUnit> ToAST;
 | |
| 
 | |
|   // Returns with the TU associated with the given Decl.
 | |
|   TU *findFromTU(Decl *From);
 | |
| 
 | |
|   // Creates an AST both for the From and To source code and imports the Decl
 | |
|   // of the identifier into the To context.
 | |
|   // Must not be called more than once within the same test.
 | |
|   std::tuple<Decl *, Decl *>
 | |
|   getImportedDecl(StringRef FromSrcCode, TestLanguage FromLang,
 | |
|                   StringRef ToSrcCode, TestLanguage ToLang,
 | |
|                   StringRef Identifier = DeclToImportID);
 | |
| 
 | |
|   // Creates a TU decl for the given source code which can be used as a From
 | |
|   // context.  May be called several times in a given test (with different file
 | |
|   // name).
 | |
|   TranslationUnitDecl *getTuDecl(StringRef SrcCode, TestLanguage Lang,
 | |
|                                  StringRef FileName = "input.cc");
 | |
| 
 | |
|   // Creates the To context with the given source code and returns the TU decl.
 | |
|   TranslationUnitDecl *getToTuDecl(StringRef ToSrcCode, TestLanguage ToLang);
 | |
| 
 | |
|   // Import the given Decl into the ToCtx.
 | |
|   // May be called several times in a given test.
 | |
|   // The different instances of the param From may have different ASTContext.
 | |
|   Decl *Import(Decl *From, TestLanguage ToLang);
 | |
| 
 | |
|   template <class DeclT> DeclT *Import(DeclT *From, TestLanguage Lang) {
 | |
|     return cast_or_null<DeclT>(Import(cast<Decl>(From), Lang));
 | |
|   }
 | |
| 
 | |
|   // Import the given Decl into the ToCtx.
 | |
|   // Same as Import but returns the result of the import which can be an error.
 | |
|   llvm::Expected<Decl *> importOrError(Decl *From, TestLanguage ToLang);
 | |
| 
 | |
|   QualType ImportType(QualType FromType, Decl *TUDecl, TestLanguage ToLang);
 | |
| 
 | |
|   ASTImporterTestBase()
 | |
|       : ODRHandling(ASTImporter::ODRHandlingType::Conservative) {}
 | |
|   ~ASTImporterTestBase();
 | |
| };
 | |
| 
 | |
| class ASTImporterOptionSpecificTestBase
 | |
|     : public ASTImporterTestBase,
 | |
|       public ::testing::WithParamInterface<std::vector<std::string>> {
 | |
| protected:
 | |
|   std::vector<std::string> getExtraArgs() const override { return GetParam(); }
 | |
| };
 | |
| 
 | |
| // Base class for those tests which use the family of `testImport` functions.
 | |
| class TestImportBase
 | |
|     : public CompilerOptionSpecificTest,
 | |
|       public ::testing::WithParamInterface<std::vector<std::string>> {
 | |
| 
 | |
|   template <typename NodeType>
 | |
|   llvm::Expected<NodeType> importNode(ASTUnit *From, ASTUnit *To,
 | |
|                                       ASTImporter &Importer, NodeType Node) {
 | |
|     ASTContext &ToCtx = To->getASTContext();
 | |
| 
 | |
|     // Add 'From' file to virtual file system so importer can 'find' it
 | |
|     // while importing SourceLocations. It is safe to add same file multiple
 | |
|     // times - it just isn't replaced.
 | |
|     StringRef FromFileName = From->getMainFileName();
 | |
|     createVirtualFileIfNeeded(To, FromFileName,
 | |
|                               From->getBufferForFile(FromFileName));
 | |
| 
 | |
|     auto Imported = Importer.Import(Node);
 | |
| 
 | |
|     if (Imported) {
 | |
|       // This should dump source locations and assert if some source locations
 | |
|       // were not imported.
 | |
|       SmallString<1024> ImportChecker;
 | |
|       llvm::raw_svector_ostream ToNothing(ImportChecker);
 | |
|       ToCtx.getTranslationUnitDecl()->print(ToNothing);
 | |
| 
 | |
|       // This traverses the AST to catch certain bugs like poorly or not
 | |
|       // implemented subtrees.
 | |
|       (*Imported)->dump(ToNothing);
 | |
|     }
 | |
| 
 | |
|     return Imported;
 | |
|   }
 | |
| 
 | |
|   template <typename NodeType>
 | |
|   testing::AssertionResult
 | |
|   testImport(const std::string &FromCode,
 | |
|              const std::vector<std::string> &FromArgs,
 | |
|              const std::string &ToCode, const std::vector<std::string> &ToArgs,
 | |
|              MatchVerifier<NodeType> &Verifier,
 | |
|              const internal::BindableMatcher<NodeType> &SearchMatcher,
 | |
|              const internal::BindableMatcher<NodeType> &VerificationMatcher) {
 | |
|     const char *const InputFileName = "input.cc";
 | |
|     const char *const OutputFileName = "output.cc";
 | |
| 
 | |
|     std::unique_ptr<ASTUnit> FromAST = tooling::buildASTFromCodeWithArgs(
 | |
|                                  FromCode, FromArgs, InputFileName),
 | |
|                              ToAST = tooling::buildASTFromCodeWithArgs(
 | |
|                                  ToCode, ToArgs, OutputFileName);
 | |
| 
 | |
|     ASTContext &FromCtx = FromAST->getASTContext(),
 | |
|                &ToCtx = ToAST->getASTContext();
 | |
| 
 | |
|     ASTImporter Importer(ToCtx, ToAST->getFileManager(), FromCtx,
 | |
|                          FromAST->getFileManager(), false);
 | |
| 
 | |
|     auto FoundNodes = match(SearchMatcher, FromCtx);
 | |
|     if (FoundNodes.size() != 1)
 | |
|       return testing::AssertionFailure()
 | |
|              << "Multiple potential nodes were found!";
 | |
| 
 | |
|     auto ToImport = selectFirst<NodeType>(DeclToImportID, FoundNodes);
 | |
|     if (!ToImport)
 | |
|       return testing::AssertionFailure() << "Node type mismatch!";
 | |
| 
 | |
|     // Sanity check: the node being imported should match in the same way as
 | |
|     // the result node.
 | |
|     internal::BindableMatcher<NodeType> WrapperMatcher(VerificationMatcher);
 | |
|     EXPECT_TRUE(Verifier.match(ToImport, WrapperMatcher));
 | |
| 
 | |
|     auto Imported = importNode(FromAST.get(), ToAST.get(), Importer, ToImport);
 | |
|     if (!Imported) {
 | |
|       std::string ErrorText;
 | |
|       handleAllErrors(
 | |
|           Imported.takeError(),
 | |
|           [&ErrorText](const ImportError &Err) { ErrorText = Err.message(); });
 | |
|       return testing::AssertionFailure()
 | |
|              << "Import failed, error: \"" << ErrorText << "\"!";
 | |
|     }
 | |
| 
 | |
|     return Verifier.match(*Imported, WrapperMatcher);
 | |
|   }
 | |
| 
 | |
|   template <typename NodeType>
 | |
|   testing::AssertionResult
 | |
|   testImport(const std::string &FromCode,
 | |
|              const std::vector<std::string> &FromArgs,
 | |
|              const std::string &ToCode, const std::vector<std::string> &ToArgs,
 | |
|              MatchVerifier<NodeType> &Verifier,
 | |
|              const internal::BindableMatcher<NodeType> &VerificationMatcher) {
 | |
|     return testImport(
 | |
|         FromCode, FromArgs, ToCode, ToArgs, Verifier,
 | |
|         translationUnitDecl(
 | |
|             has(namedDecl(hasName(DeclToImportID)).bind(DeclToImportID))),
 | |
|         VerificationMatcher);
 | |
|   }
 | |
| 
 | |
| protected:
 | |
|   std::vector<std::string> getExtraArgs() const override { return GetParam(); }
 | |
| 
 | |
| public:
 | |
|   /// Test how AST node named "declToImport" located in the translation unit
 | |
|   /// of "FromCode" virtual file is imported to "ToCode" virtual file.
 | |
|   /// The verification is done by running AMatcher over the imported node.
 | |
|   template <typename NodeType, typename MatcherType>
 | |
|   void testImport(const std::string &FromCode, TestLanguage FromLang,
 | |
|                   const std::string &ToCode, TestLanguage ToLang,
 | |
|                   MatchVerifier<NodeType> &Verifier,
 | |
|                   const MatcherType &AMatcher) {
 | |
|     std::vector<std::string> FromArgs = getCommandLineArgsForLanguage(FromLang);
 | |
|     std::vector<std::string> ToArgs = getCommandLineArgsForLanguage(ToLang);
 | |
|     EXPECT_TRUE(
 | |
|         testImport(FromCode, FromArgs, ToCode, ToArgs, Verifier, AMatcher));
 | |
|   }
 | |
| 
 | |
|   struct ImportAction {
 | |
|     StringRef FromFilename;
 | |
|     StringRef ToFilename;
 | |
|     // FIXME: Generalize this to support other node kinds.
 | |
|     internal::BindableMatcher<Decl> ImportPredicate;
 | |
| 
 | |
|     ImportAction(StringRef FromFilename, StringRef ToFilename,
 | |
|                  DeclarationMatcher ImportPredicate)
 | |
|         : FromFilename(FromFilename), ToFilename(ToFilename),
 | |
|           ImportPredicate(ImportPredicate) {}
 | |
| 
 | |
|     ImportAction(StringRef FromFilename, StringRef ToFilename,
 | |
|                  const std::string &DeclName)
 | |
|         : FromFilename(FromFilename), ToFilename(ToFilename),
 | |
|           ImportPredicate(namedDecl(hasName(DeclName))) {}
 | |
|   };
 | |
| 
 | |
|   using SingleASTUnit = std::unique_ptr<ASTUnit>;
 | |
|   using AllASTUnits = llvm::StringMap<SingleASTUnit>;
 | |
| 
 | |
|   struct CodeEntry {
 | |
|     std::string CodeSample;
 | |
|     TestLanguage Lang;
 | |
|   };
 | |
| 
 | |
|   using CodeFiles = llvm::StringMap<CodeEntry>;
 | |
| 
 | |
|   /// Builds an ASTUnit for one potential compile options set.
 | |
|   SingleASTUnit createASTUnit(StringRef FileName, const CodeEntry &CE) const {
 | |
|     std::vector<std::string> Args = getCommandLineArgsForLanguage(CE.Lang);
 | |
|     auto AST = tooling::buildASTFromCodeWithArgs(CE.CodeSample, Args, FileName);
 | |
|     EXPECT_TRUE(AST.get());
 | |
|     return AST;
 | |
|   }
 | |
| 
 | |
|   /// Test an arbitrary sequence of imports for a set of given in-memory files.
 | |
|   /// The verification is done by running VerificationMatcher against a
 | |
|   /// specified AST node inside of one of given files.
 | |
|   /// \param CodeSamples Map whose key is the file name and the value is the
 | |
|   /// file content.
 | |
|   /// \param ImportActions Sequence of imports. Each import in sequence
 | |
|   /// specifies "from file" and "to file" and a matcher that is used for
 | |
|   /// searching a declaration for import in "from file".
 | |
|   /// \param FileForFinalCheck Name of virtual file for which the final check is
 | |
|   /// applied.
 | |
|   /// \param FinalSelectPredicate Matcher that specifies the AST node in the
 | |
|   /// FileForFinalCheck for which the verification will be done.
 | |
|   /// \param VerificationMatcher Matcher that will be used for verification
 | |
|   /// after all imports in sequence are done.
 | |
|   void testImportSequence(const CodeFiles &CodeSamples,
 | |
|                           const std::vector<ImportAction> &ImportActions,
 | |
|                           StringRef FileForFinalCheck,
 | |
|                           internal::BindableMatcher<Decl> FinalSelectPredicate,
 | |
|                           internal::BindableMatcher<Decl> VerificationMatcher) {
 | |
|     AllASTUnits AllASTs;
 | |
|     using ImporterKey = std::pair<const ASTUnit *, const ASTUnit *>;
 | |
|     llvm::DenseMap<ImporterKey, std::unique_ptr<ASTImporter>> Importers;
 | |
| 
 | |
|     auto GenASTsIfNeeded = [this, &AllASTs, &CodeSamples](StringRef Filename) {
 | |
|       if (!AllASTs.count(Filename)) {
 | |
|         auto Found = CodeSamples.find(Filename);
 | |
|         assert(Found != CodeSamples.end() && "Wrong file for import!");
 | |
|         AllASTs[Filename] = createASTUnit(Filename, Found->getValue());
 | |
|       }
 | |
|     };
 | |
| 
 | |
|     for (const ImportAction &Action : ImportActions) {
 | |
|       StringRef FromFile = Action.FromFilename, ToFile = Action.ToFilename;
 | |
|       GenASTsIfNeeded(FromFile);
 | |
|       GenASTsIfNeeded(ToFile);
 | |
| 
 | |
|       ASTUnit *From = AllASTs[FromFile].get();
 | |
|       ASTUnit *To = AllASTs[ToFile].get();
 | |
| 
 | |
|       // Create a new importer if needed.
 | |
|       std::unique_ptr<ASTImporter> &ImporterRef = Importers[{From, To}];
 | |
|       if (!ImporterRef)
 | |
|         ImporterRef.reset(new ASTImporter(
 | |
|             To->getASTContext(), To->getFileManager(), From->getASTContext(),
 | |
|             From->getFileManager(), false));
 | |
| 
 | |
|       // Find the declaration and import it.
 | |
|       auto FoundDecl = match(Action.ImportPredicate.bind(DeclToImportID),
 | |
|                              From->getASTContext());
 | |
|       EXPECT_TRUE(FoundDecl.size() == 1);
 | |
|       const Decl *ToImport = selectFirst<Decl>(DeclToImportID, FoundDecl);
 | |
|       auto Imported = importNode(From, To, *ImporterRef, ToImport);
 | |
|       EXPECT_TRUE(static_cast<bool>(Imported));
 | |
|       if (!Imported)
 | |
|         llvm::consumeError(Imported.takeError());
 | |
|     }
 | |
| 
 | |
|     // Find the declaration and import it.
 | |
|     auto FoundDecl = match(FinalSelectPredicate.bind(DeclToVerifyID),
 | |
|                            AllASTs[FileForFinalCheck]->getASTContext());
 | |
|     EXPECT_TRUE(FoundDecl.size() == 1);
 | |
|     const Decl *ToVerify = selectFirst<Decl>(DeclToVerifyID, FoundDecl);
 | |
|     MatchVerifier<Decl> Verifier;
 | |
|     EXPECT_TRUE(Verifier.match(
 | |
|         ToVerify, internal::BindableMatcher<Decl>(VerificationMatcher)));
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <typename T> RecordDecl *getRecordDecl(T *D) {
 | |
|   auto *ET = cast<ElaboratedType>(D->getType().getTypePtr());
 | |
|   return cast<RecordType>(ET->getNamedType().getTypePtr())->getDecl();
 | |
| }
 | |
| 
 | |
| template <class T>
 | |
| ::testing::AssertionResult isSuccess(llvm::Expected<T> &ValOrErr) {
 | |
|   if (ValOrErr)
 | |
|     return ::testing::AssertionSuccess() << "Expected<> contains no error.";
 | |
|   else
 | |
|     return ::testing::AssertionFailure()
 | |
|            << "Expected<> contains error: " << toString(ValOrErr.takeError());
 | |
| }
 | |
| 
 | |
| template <class T>
 | |
| ::testing::AssertionResult isImportError(llvm::Expected<T> &ValOrErr,
 | |
|                                          ImportError::ErrorKind Kind) {
 | |
|   if (ValOrErr) {
 | |
|     return ::testing::AssertionFailure() << "Expected<> is expected to contain "
 | |
|                                             "error but does contain value \""
 | |
|                                          << (*ValOrErr) << "\"";
 | |
|   } else {
 | |
|     std::ostringstream OS;
 | |
|     bool Result = false;
 | |
|     auto Err = llvm::handleErrors(
 | |
|         ValOrErr.takeError(), [&OS, &Result, Kind](clang::ImportError &IE) {
 | |
|           if (IE.Error == Kind) {
 | |
|             Result = true;
 | |
|             OS << "Expected<> contains an ImportError " << IE.toString();
 | |
|           } else {
 | |
|             OS << "Expected<> contains an ImportError " << IE.toString()
 | |
|                << " instead of kind " << Kind;
 | |
|           }
 | |
|         });
 | |
|     if (Err) {
 | |
|       OS << "Expected<> contains unexpected error: "
 | |
|          << toString(std::move(Err));
 | |
|     }
 | |
|     if (Result)
 | |
|       return ::testing::AssertionSuccess() << OS.str();
 | |
|     else
 | |
|       return ::testing::AssertionFailure() << OS.str();
 | |
|   }
 | |
| }
 | |
| 
 | |
| } // end namespace ast_matchers
 | |
| } // end namespace clang
 | |
| 
 | |
| #endif
 |