xref: /llvm-project/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.h (revision 1feb7af046889728233e67e3163ab30020207bb2)
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