clang-tools 22.0.0git
TweakTesting.h
Go to the documentation of this file.
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 "TestWorkspace.h"
14#include "index/Index.h"
15#include "llvm/ADT/StringMap.h"
16#include "llvm/ADT/StringRef.h"
17#include "llvm/Testing/Annotations/Annotations.h"
18#include "gmock/gmock.h"
19#include "gtest/gtest.h"
20#include <memory>
21#include <string>
22
23namespace clang {
24namespace clangd {
25
26// Fixture base for testing tweaks. Intended to be subclassed for each tweak.
27//
28// Usage:
29// TWEAK_TEST(ExpandDeducedType);
30//
31// TEST_F(ExpandDeducedTypeTest, ShortensTypes) {
32// Header = R"cpp(
33// namespace foo { template<typename> class X{}; }
34// using namespace foo;
35// )cpp";
36// Context = Function;
37// EXPECT_THAT(apply("[[auto]] X = foo<int>();"),
38// "foo<int> X = foo<int();");
39// EXPECT_AVAILABLE("^a^u^t^o^ X = foo<int>();");
40// EXPECT_UNAVAILABLE("auto ^X^ = ^foo<int>();");
41// }
42class TweakTest : public ::testing::Test {
43 const char *TweakID;
44
45public:
46 // Inputs are wrapped in file boilerplate before attempting to apply a tweak.
47 // Context describes the type of boilerplate.
49 // Code snippet is placed directly into the source file. e.g. a declaration.
51 // Snippet will appear within a function body. e.g. a statement.
53 // Snippet is an expression.
55 };
56
57 // Mapping from file name to contents.
58 llvm::StringMap<std::string> ExtraFiles;
59
60protected:
61 TweakTest(const char *TweakID) : TweakID(TweakID) {}
62
63 // Contents of a header file to be implicitly included.
64 // This typically contains declarations that will be used for a set of related
65 // testcases.
66 std::string Header;
67
68 llvm::StringRef FileName = "TestTU.cpp";
69
70 // Extra flags passed to the compilation in apply().
71 std::vector<std::string> ExtraArgs;
72
73 // Context in which snippets of code should be placed to run tweaks.
75
76 // Index to be passed into Tweak::Selection.
77 std::unique_ptr<const SymbolIndex> Index = nullptr;
78
79 // Apply the current tweak to the range (or point) in MarkedCode.
80 // MarkedCode will be wrapped according to the Context.
81 // - if the tweak produces edits, returns the edited code (without markings)
82 // for the main file.
83 // Populates \p EditedFiles if there were changes to other files whenever
84 // it is non-null. It is a mapping from absolute path of the edited file to
85 // its new contents. Passing a nullptr to \p EditedFiles when there are
86 // changes, will result in a failure.
87 // The context added to MarkedCode will be stripped away before returning,
88 // unless the tweak edited it.
89 // - if the tweak produces a message, returns "message:\n<message>"
90 // - if prepare() returns false, returns "unavailable"
91 // - if apply() returns an error, returns "fail: <message>"
92 std::string apply(llvm::StringRef MarkedCode,
93 llvm::StringMap<std::string> *EditedFiles = nullptr) const;
94
95 // Helpers for EXPECT_AVAILABLE/EXPECT_UNAVAILABLE macros.
96 using WrappedAST = std::pair<ParsedAST, /*WrappingOffset*/ unsigned>;
97 WrappedAST build(llvm::StringRef) const;
98 bool isAvailable(WrappedAST &, llvm::Annotations::Range) const;
99 // Return code re-decorated with a single point/range.
100 static std::string decorate(llvm::StringRef, unsigned);
101 static std::string decorate(llvm::StringRef, llvm::Annotations::Range);
102};
103
104MATCHER_P2(FileWithContents, FileName, Contents, "") {
105 return arg.first() == FileName && arg.second == Contents;
106}
107
108#define TWEAK_TEST(TweakID) \
109 class TweakID##Test : public ::clang::clangd::TweakTest { \
110 protected: \
111 TweakID##Test() : TweakTest(#TweakID) {} \
112 }
113
114#define EXPECT_AVAILABLE_(MarkedCode, Available) \
115 do { \
116 llvm::Annotations A{llvm::StringRef(MarkedCode)}; \
117 auto AST = build(A.code()); \
118 assert(!A.points().empty() || !A.ranges().empty()); \
119 for (const auto &P : A.points()) \
120 EXPECT_EQ(Available, isAvailable(AST, {P, P})) << decorate(A.code(), P); \
121 for (const auto &R : A.ranges()) \
122 EXPECT_EQ(Available, isAvailable(AST, R)) << decorate(A.code(), R); \
123 } while (0)
124#define EXPECT_AVAILABLE(MarkedCode) EXPECT_AVAILABLE_(MarkedCode, true)
125#define EXPECT_UNAVAILABLE(MarkedCode) EXPECT_AVAILABLE_(MarkedCode, false)
126
127// A helper class to represent the return value of TweakWorkspaceTest::apply().
129 // A string representation the status of the operation.
130 // For failure cases, this is the same as the return value of
131 // TweakTest::apply() (see the comment above that for details).
132 // For success cases, this is "success".
133 std::string Status;
134 // The contents of all files changed by the tweak, including
135 // the file in which it was invoked. Keys are absolute paths.
136 llvm::StringMap<std::string> EditedFiles = {};
137};
138
139// GTest matchers to allow more easily writing assertions about the
140// expected value of a TweakResult.
141MATCHER_P(withStatus, S, "") { return arg.Status == S; }
142template <class EditedFilesMatcher>
143::testing::Matcher<TweakResult> editedFiles(EditedFilesMatcher M) {
144 return ::testing::Field(&TweakResult::EditedFiles, M);
145}
146
147// Used for formatting TweakResult objects in assertion failure messages,
148// so it's easier to understand what didn't match.
149inline llvm::raw_ostream &operator<<(llvm::raw_ostream &Stream,
150 const TweakResult &Result) {
151 Stream << "{ status: " << Result.Status << ", editedFiles: [";
152 for (const auto &F : Result.EditedFiles) {
153 Stream << F.first() << ":\n";
154 Stream << F.second;
155 }
156 return Stream << "] }";
157}
158
159// A version of TweakTest that makes it easier to create test cases that
160// involve multiple files which are indexed.
161// Usage:
162// - Call `Workspace.addMainFile(filename, contents)` to add
163// source files which are indexer entry points (e.g. would show
164// up in `compile_commands.json`).
165// - Call `Workspace.addSource(filename, contents)` to add other
166// source files (e.g. header files).
167// - Call `apply(filename, range)` to invoke the tweak on the
168// indicated file with the given range selected. Can be called
169// multiple times for the same set of added files.
170// The implementation takes care of building an index reflecting
171// all added source files, and making it available to the tweak.
172// Unlike TweakTest, this does not have a notion of a `CodeContext`
173// (i.e. the contents of all added files are interpreted as being
174// in a File context).
175class TweakWorkspaceTest : public ::testing::Test {
176 const char *TweakID;
177
178public:
179 TweakWorkspaceTest(const char *TweakID) : TweakID(TweakID) {}
180
181 TweakResult apply(StringRef InvocationFile,
182 llvm::Annotations::Range InvocationRange);
183
184protected:
186};
187
188#define TWEAK_WORKSPACE_TEST(TweakID) \
189 class TweakID##WorkspaceTest : public ::clang::clangd::TweakWorkspaceTest { \
190 protected: \
191 TweakID##WorkspaceTest() : TweakWorkspaceTest(#TweakID) {} \
192 }
193
194} // namespace clangd
195} // namespace clang
196
197#endif
Stores and provides access to parsed AST.
Definition ParsedAST.h:46
std::vector< std::string > ExtraArgs
TweakTest(const char *TweakID)
llvm::StringRef FileName
llvm::StringMap< std::string > ExtraFiles
static std::string decorate(llvm::StringRef, unsigned)
WrappedAST build(llvm::StringRef) const
std::pair< ParsedAST, unsigned > WrappedAST
std::string apply(llvm::StringRef MarkedCode, llvm::StringMap< std::string > *EditedFiles=nullptr) const
bool isAvailable(WrappedAST &, llvm::Annotations::Range) const
std::unique_ptr< const SymbolIndex > Index
TweakWorkspaceTest(const char *TweakID)
TweakResult apply(StringRef InvocationFile, llvm::Annotations::Range InvocationRange)
FIXME: Skip testing on windows temporarily due to the different escaping code mode.
Definition AST.cpp:45
MATCHER_P2(hasFlag, Flag, Path, "")
llvm::raw_ostream & operator<<(llvm::raw_ostream &OS, const CodeCompletion &C)
MATCHER_P(named, N, "")
::testing::Matcher< TweakResult > editedFiles(EditedFilesMatcher M)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
llvm::StringMap< std::string > EditedFiles