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_UNITTESTS_CLANGD_TWEAKTESTING_H
10 #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_TWEAKTESTING_H
11 
12 #include "TestTU.h"
13 #include "index/Index.h"
14 #include "llvm/ADT/StringMap.h"
15 #include "llvm/ADT/StringRef.h"
16 #include "gmock/gmock.h"
17 #include "gtest/gtest.h"
18 #include <memory>
19 #include <string>
20 
21 namespace clang {
22 namespace clangd {
23 
24 // Fixture base for testing tweaks. Intended to be subclassed for each tweak.
25 //
26 // Usage:
27 // TWEAK_TEST(ExpandAutoType);
28 //
29 // TEST_F(ExpandAutoTypeTest, ShortensTypes) {
30 //   Header = R"cpp(
31 //     namespace foo { template<typename> class X{}; }
32 //     using namespace foo;
33 //   )cpp";
34 //   Context = Function;
35 //   EXPECT_THAT(apply("[[auto]] X = foo<int>();"),
36 //               "foo<int> X = foo<int();");
37 //   EXPECT_AVAILABLE("^a^u^t^o^ X = foo<int>();");
38 //   EXPECT_UNAVAILABLE("auto ^X^ = ^foo<int>();");
39 // }
40 class TweakTest : public ::testing::Test {
41   const char *TweakID;
42 
43 public:
44   // Inputs are wrapped in file boilerplate before attempting to apply a tweak.
45   // Context describes the type of boilerplate.
46   enum CodeContext {
47     // Code snippet is placed directly into the source file. e.g. a declaration.
48     File,
49     // Snippet will appear within a function body. e.g. a statement.
50     Function,
51     // Snippet is an expression.
52     Expression,
53   };
54 
55   // Mapping from file name to contents.
56   llvm::StringMap<std::string> ExtraFiles;
57 
58 protected:
TweakTest(const char * TweakID)59   TweakTest(const char *TweakID) : TweakID(TweakID) {}
60 
61   // Contents of a header file to be implicitly included.
62   // This typically contains declarations that will be used for a set of related
63   // testcases.
64   std::string Header;
65 
66   llvm::StringRef FileName = "TestTU.cpp";
67 
68   // Extra flags passed to the compilation in apply().
69   std::vector<const char *> ExtraArgs;
70 
71   // Context in which snippets of code should be placed to run tweaks.
72   CodeContext Context = File;
73 
74   // Index to be passed into Tweak::Selection.
75   std::unique_ptr<const SymbolIndex> Index = nullptr;
76 
77   // Apply the current tweak to the range (or point) in MarkedCode.
78   // MarkedCode will be wrapped according to the Context.
79   //  - if the tweak produces edits, returns the edited code (without markings)
80   //    for the main file.
81   //    Populates \p EditedFiles if there were changes to other files whenever
82   //    it is non-null. It is a mapping from absolute path of the edited file to
83   //    its new contents. Passing a nullptr to \p EditedFiles when there are
84   //    changes, will result in a failure.
85   //    The context added to MarkedCode will be stripped away before returning,
86   //    unless the tweak edited it.
87   //  - if the tweak produces a message, returns "message:\n<message>"
88   //  - if prepare() returns false, returns "unavailable"
89   //  - if apply() returns an error, returns "fail: <message>"
90   std::string apply(llvm::StringRef MarkedCode,
91                     llvm::StringMap<std::string> *EditedFiles = nullptr) const;
92 
93   // Accepts a code snippet with many ranges (or points) marked, and returns a
94   // list of snippets with one range marked each.
95   // Primarily used from EXPECT_AVAILABLE/EXPECT_UNAVAILABLE macro.
96   static std::vector<std::string> expandCases(llvm::StringRef MarkedCode);
97 
98   // Returns a matcher that accepts marked code snippets where the tweak is
99   // available at the marked range.
100   ::testing::Matcher<llvm::StringRef> isAvailable() const;
101 };
102 
103 #define TWEAK_TEST(TweakID)                                                    \
104   class TweakID##Test : public ::clang::clangd::TweakTest {                    \
105   protected:                                                                   \
106     TweakID##Test() : TweakTest(#TweakID) {}                                   \
107   }
108 
109 #define EXPECT_AVAILABLE(MarkedCode)                                           \
110   do {                                                                         \
111     for (const auto &Case : expandCases(MarkedCode))                           \
112       EXPECT_THAT(Case, ::clang::clangd::TweakTest::isAvailable());            \
113   } while (0)
114 
115 #define EXPECT_UNAVAILABLE(MarkedCode)                                         \
116   do {                                                                         \
117     for (const auto &Case : expandCases(MarkedCode))                           \
118       EXPECT_THAT(Case,                                                        \
119                   ::testing::Not(::clang::clangd::TweakTest::isAvailable()));  \
120   } while (0)
121 
122 } // namespace clangd
123 } // namespace clang
124 
125 #endif
126