//===--- TweakTesting.h - Test helpers for refactoring actions ---*- C++-*-===// // // 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 // //===----------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_TWEAKS_TWEAKTESTING_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_TWEAKS_TWEAKTESTING_H #include "ParsedAST.h" #include "index/Index.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/Testing/Annotations/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include namespace clang { namespace clangd { // Fixture base for testing tweaks. Intended to be subclassed for each tweak. // // Usage: // TWEAK_TEST(ExpandDeducedType); // // TEST_F(ExpandDeducedTypeTest, ShortensTypes) { // Header = R"cpp( // namespace foo { template class X{}; } // using namespace foo; // )cpp"; // Context = Function; // EXPECT_THAT(apply("[[auto]] X = foo();"), // "foo X = foo();"); // EXPECT_UNAVAILABLE("auto ^X^ = ^foo();"); // } class TweakTest : public ::testing::Test { const char *TweakID; public: // Inputs are wrapped in file boilerplate before attempting to apply a tweak. // Context describes the type of boilerplate. enum CodeContext { // Code snippet is placed directly into the source file. e.g. a declaration. File, // Snippet will appear within a function body. e.g. a statement. Function, // Snippet is an expression. Expression, }; // Mapping from file name to contents. llvm::StringMap ExtraFiles; protected: TweakTest(const char *TweakID) : TweakID(TweakID) {} // Contents of a header file to be implicitly included. // This typically contains declarations that will be used for a set of related // testcases. std::string Header; llvm::StringRef FileName = "TestTU.cpp"; // Extra flags passed to the compilation in apply(). std::vector ExtraArgs; // Context in which snippets of code should be placed to run tweaks. CodeContext Context = File; // Index to be passed into Tweak::Selection. std::unique_ptr Index = nullptr; // Apply the current tweak to the range (or point) in MarkedCode. // MarkedCode will be wrapped according to the Context. // - if the tweak produces edits, returns the edited code (without markings) // for the main file. // Populates \p EditedFiles if there were changes to other files whenever // it is non-null. It is a mapping from absolute path of the edited file to // its new contents. Passing a nullptr to \p EditedFiles when there are // changes, will result in a failure. // The context added to MarkedCode will be stripped away before returning, // unless the tweak edited it. // - if the tweak produces a message, returns "message:\n" // - if prepare() returns false, returns "unavailable" // - if apply() returns an error, returns "fail: " std::string apply(llvm::StringRef MarkedCode, llvm::StringMap *EditedFiles = nullptr) const; // Helpers for EXPECT_AVAILABLE/EXPECT_UNAVAILABLE macros. using WrappedAST = std::pair; WrappedAST build(llvm::StringRef) const; bool isAvailable(WrappedAST &, llvm::Annotations::Range) const; // Return code re-decorated with a single point/range. static std::string decorate(llvm::StringRef, unsigned); static std::string decorate(llvm::StringRef, llvm::Annotations::Range); }; MATCHER_P2(FileWithContents, FileName, Contents, "") { return arg.first() == FileName && arg.second == Contents; } #define TWEAK_TEST(TweakID) \ class TweakID##Test : public ::clang::clangd::TweakTest { \ protected: \ TweakID##Test() : TweakTest(#TweakID) {} \ } #define EXPECT_AVAILABLE_(MarkedCode, Available) \ do { \ llvm::Annotations A{llvm::StringRef(MarkedCode)}; \ auto AST = build(A.code()); \ assert(!A.points().empty() || !A.ranges().empty()); \ for (const auto &P : A.points()) \ EXPECT_EQ(Available, isAvailable(AST, {P, P})) << decorate(A.code(), P); \ for (const auto &R : A.ranges()) \ EXPECT_EQ(Available, isAvailable(AST, R)) << decorate(A.code(), R); \ } while (0) #define EXPECT_AVAILABLE(MarkedCode) EXPECT_AVAILABLE_(MarkedCode, true) #define EXPECT_UNAVAILABLE(MarkedCode) EXPECT_AVAILABLE_(MarkedCode, false) } // namespace clangd } // namespace clang #endif