brintos

brintos / llvm-project-archived public Read only

0
0
Text · 7.9 KiB · 8673985 Raw
198 lines · c
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-exception6//7//===----------------------------------------------------------------------===//8 9#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_TWEAKS_TWEAKTESTING_H10#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_UNITTESTS_TWEAKS_TWEAKTESTING_H11 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.48  enum CodeContext {49    // Code snippet is placed directly into the source file. e.g. a declaration.50    File,51    // Snippet will appear within a function body. e.g. a statement.52    Function,53    // Snippet is an expression.54    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 related65  // 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.74  CodeContext Context = File;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 whenever84  //    it is non-null. It is a mapping from absolute path of the edited file to85  //    its new contents. Passing a nullptr to \p EditedFiles when there are86  //    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().128struct TweakResult {129  // A string representation the status of the operation.130  // For failure cases, this is the same as the return value of131  // 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, including135  // 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 the140// 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 that160// involve multiple files which are indexed.161// Usage:162//   - Call `Workspace.addMainFile(filename, contents)` to add163//     source files which are indexer entry points (e.g. would show164//     up in `compile_commands.json`).165//   - Call `Workspace.addSource(filename, contents)` to add other166//     source files (e.g. header files).167//   - Call `apply(filename, range)` to invoke the tweak on the168//     indicated file with the given range selected. Can be called169//     multiple times for the same set of added files.170// The implementation takes care of building an index reflecting171// 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 being174// 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:185  TestWorkspace Workspace;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 clangd195} // namespace clang196 197#endif198