1 //===--- TweakTesting.h - Test helpers for refactoring actions ---*- C++-*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_TWEAKS_TWEAKTESTING_H 10 #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_TWEAKS_TWEAKTESTING_H 11 12 #include "ParsedAST.h" 13 #include "index/Index.h" 14 #include "llvm/ADT/StringMap.h" 15 #include "llvm/ADT/StringRef.h" 16 #include "llvm/Testing/Annotations/Annotations.h" 17 #include "gmock/gmock.h" 18 #include "gtest/gtest.h" 19 #include <memory> 20 #include <string> 21 22 namespace clang { 23 namespace clangd { 24 25 // Fixture base for testing tweaks. Intended to be subclassed for each tweak. 26 // 27 // Usage: 28 // TWEAK_TEST(ExpandDeducedType); 29 // 30 // TEST_F(ExpandDeducedTypeTest, ShortensTypes) { 31 // Header = R"cpp( 32 // namespace foo { template<typename> class X{}; } 33 // using namespace foo; 34 // )cpp"; 35 // Context = Function; 36 // EXPECT_THAT(apply("[[auto]] X = foo<int>();"), 37 // "foo<int> X = foo<int();"); 38 // EXPECT_AVAILABLE("^a^u^t^o^ X = foo<int>();"); 39 // EXPECT_UNAVAILABLE("auto ^X^ = ^foo<int>();"); 40 // } 41 class TweakTest : public ::testing::Test { 42 const char *TweakID; 43 44 public: 45 // Inputs are wrapped in file boilerplate before attempting to apply a tweak. 46 // Context describes the type of boilerplate. 47 enum CodeContext { 48 // Code snippet is placed directly into the source file. e.g. a declaration. 49 File, 50 // Snippet will appear within a function body. e.g. a statement. 51 Function, 52 // Snippet is an expression. 53 Expression, 54 }; 55 56 // Mapping from file name to contents. 57 llvm::StringMap<std::string> ExtraFiles; 58 59 protected: TweakTest(const char * TweakID)60 TweakTest(const char *TweakID) : TweakID(TweakID) {} 61 62 // Contents of a header file to be implicitly included. 63 // This typically contains declarations that will be used for a set of related 64 // testcases. 65 std::string Header; 66 67 llvm::StringRef FileName = "TestTU.cpp"; 68 69 // Extra flags passed to the compilation in apply(). 70 std::vector<std::string> ExtraArgs; 71 72 // Context in which snippets of code should be placed to run tweaks. 73 CodeContext Context = File; 74 75 // Index to be passed into Tweak::Selection. 76 std::unique_ptr<const SymbolIndex> Index = nullptr; 77 78 // Apply the current tweak to the range (or point) in MarkedCode. 79 // MarkedCode will be wrapped according to the Context. 80 // - if the tweak produces edits, returns the edited code (without markings) 81 // for the main file. 82 // Populates \p EditedFiles if there were changes to other files whenever 83 // it is non-null. It is a mapping from absolute path of the edited file to 84 // its new contents. Passing a nullptr to \p EditedFiles when there are 85 // changes, will result in a failure. 86 // The context added to MarkedCode will be stripped away before returning, 87 // unless the tweak edited it. 88 // - if the tweak produces a message, returns "message:\n<message>" 89 // - if prepare() returns false, returns "unavailable" 90 // - if apply() returns an error, returns "fail: <message>" 91 std::string apply(llvm::StringRef MarkedCode, 92 llvm::StringMap<std::string> *EditedFiles = nullptr) const; 93 94 // Helpers for EXPECT_AVAILABLE/EXPECT_UNAVAILABLE macros. 95 using WrappedAST = std::pair<ParsedAST, /*WrappingOffset*/ unsigned>; 96 WrappedAST build(llvm::StringRef) const; 97 bool isAvailable(WrappedAST &, llvm::Annotations::Range) const; 98 // Return code re-decorated with a single point/range. 99 static std::string decorate(llvm::StringRef, unsigned); 100 static std::string decorate(llvm::StringRef, llvm::Annotations::Range); 101 }; 102 103 MATCHER_P2(FileWithContents, FileName, Contents, "") { 104 return arg.first() == FileName && arg.second == Contents; 105 } 106 107 #define TWEAK_TEST(TweakID) \ 108 class TweakID##Test : public ::clang::clangd::TweakTest { \ 109 protected: \ 110 TweakID##Test() : TweakTest(#TweakID) {} \ 111 } 112 113 #define EXPECT_AVAILABLE_(MarkedCode, Available) \ 114 do { \ 115 llvm::Annotations A{llvm::StringRef(MarkedCode)}; \ 116 auto AST = build(A.code()); \ 117 assert(!A.points().empty() || !A.ranges().empty()); \ 118 for (const auto &P : A.points()) \ 119 EXPECT_EQ(Available, isAvailable(AST, {P, P})) << decorate(A.code(), P); \ 120 for (const auto &R : A.ranges()) \ 121 EXPECT_EQ(Available, isAvailable(AST, R)) << decorate(A.code(), R); \ 122 } while (0) 123 #define EXPECT_AVAILABLE(MarkedCode) EXPECT_AVAILABLE_(MarkedCode, true) 124 #define EXPECT_UNAVAILABLE(MarkedCode) EXPECT_AVAILABLE_(MarkedCode, false) 125 126 } // namespace clangd 127 } // namespace clang 128 129 #endif 130