1 //===-- ConfigYAMLTests.cpp -----------------------------------------------===//
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 #include "Annotations.h"
10 #include "ConfigFragment.h"
11 #include "ConfigTesting.h"
12 #include "Protocol.h"
13 #include "llvm/ADT/None.h"
14 #include "llvm/ADT/StringRef.h"
15 #include "llvm/Support/SMLoc.h"
16 #include "llvm/Support/ScopedPrinter.h"
17 #include "llvm/Support/SourceMgr.h"
18 #include "llvm/Testing/Support/SupportHelpers.h"
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
21 
22 namespace clang {
23 namespace clangd {
24 namespace config {
PrintTo(const Located<T> & V,std::ostream * OS)25 template <typename T> void PrintTo(const Located<T> &V, std::ostream *OS) {
26   *OS << ::testing::PrintToString(*V);
27 }
28 
29 namespace {
30 using ::testing::AllOf;
31 using ::testing::ElementsAre;
32 using ::testing::IsEmpty;
33 
34 MATCHER_P(Val, Value, "") {
35   if (*arg == Value)
36     return true;
37   *result_listener << "value is " << *arg;
38   return false;
39 }
40 
41 MATCHER_P2(PairVal, Value1, Value2, "") {
42   if (*arg.first == Value1 && *arg.second == Value2)
43     return true;
44   *result_listener << "values are [" << *arg.first << ", " << *arg.second
45                    << "]";
46   return false;
47 }
48 
TEST(ParseYAML,SyntacticForms)49 TEST(ParseYAML, SyntacticForms) {
50   CapturedDiags Diags;
51   const char *YAML = R"yaml(
52 If:
53   PathMatch:
54     - 'abc'
55 CompileFlags: { Add: [foo, bar] }
56 ---
57 CompileFlags:
58   Add: |
59     b
60     az
61 ---
62 Index:
63   Background: Skip
64 ---
65 Diagnostics:
66   ClangTidy:
67     CheckOptions:
68       IgnoreMacros: true
69       example-check.ExampleOption: 0
70   )yaml";
71   auto Results = Fragment::parseYAML(YAML, "config.yaml", Diags.callback());
72   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
73   EXPECT_THAT(Diags.Files, ElementsAre("config.yaml"));
74   ASSERT_EQ(Results.size(), 4u);
75   EXPECT_FALSE(Results[0].If.HasUnrecognizedCondition);
76   EXPECT_THAT(Results[0].If.PathMatch, ElementsAre(Val("abc")));
77   EXPECT_THAT(Results[0].CompileFlags.Add, ElementsAre(Val("foo"), Val("bar")));
78 
79   EXPECT_THAT(Results[1].CompileFlags.Add, ElementsAre(Val("b\naz\n")));
80 
81   ASSERT_TRUE(Results[2].Index.Background);
82   EXPECT_EQ("Skip", *Results[2].Index.Background.getValue());
83   EXPECT_THAT(Results[3].Diagnostics.ClangTidy.CheckOptions,
84               ElementsAre(PairVal("IgnoreMacros", "true"),
85                           PairVal("example-check.ExampleOption", "0")));
86 }
87 
TEST(ParseYAML,Locations)88 TEST(ParseYAML, Locations) {
89   CapturedDiags Diags;
90   Annotations YAML(R"yaml(
91 If:
92   PathMatch: [['???bad***regex(((']]
93   )yaml");
94   auto Results =
95       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
96   EXPECT_THAT(Diags.Diagnostics, IsEmpty());
97   ASSERT_EQ(Results.size(), 1u);
98   ASSERT_NE(Results.front().Source.Manager, nullptr);
99   EXPECT_EQ(toRange(Results.front().If.PathMatch.front().Range,
100                     *Results.front().Source.Manager),
101             YAML.range());
102 }
103 
TEST(ParseYAML,ConfigDiagnostics)104 TEST(ParseYAML, ConfigDiagnostics) {
105   CapturedDiags Diags;
106   Annotations YAML(R"yaml(
107 If:
108   $unknown[[UnknownCondition]]: "foo"
109 CompileFlags:
110   Add: 'first'
111 ---
112 CompileFlags: {$unexpected^
113 )yaml");
114   auto Results =
115       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
116 
117   ASSERT_THAT(
118       Diags.Diagnostics,
119       ElementsAre(AllOf(DiagMessage("Unknown If key 'UnknownCondition'"),
120                         DiagKind(llvm::SourceMgr::DK_Warning),
121                         DiagPos(YAML.range("unknown").start),
122                         DiagRange(YAML.range("unknown"))),
123                   AllOf(DiagMessage("Unexpected token. Expected Key, Flow "
124                                     "Entry, or Flow Mapping End."),
125                         DiagKind(llvm::SourceMgr::DK_Error),
126                         DiagPos(YAML.point("unexpected")),
127                         DiagRange(llvm::None))));
128 
129   ASSERT_EQ(Results.size(), 1u); // invalid fragment discarded.
130   EXPECT_THAT(Results.front().CompileFlags.Add, ElementsAre(Val("first")));
131   EXPECT_TRUE(Results.front().If.HasUnrecognizedCondition);
132 }
133 
TEST(ParseYAML,Invalid)134 TEST(ParseYAML, Invalid) {
135   CapturedDiags Diags;
136   const char *YAML = R"yaml(
137 If:
138 
139 horrible
140 ---
141 - 1
142   )yaml";
143   auto Results = Fragment::parseYAML(YAML, "config.yaml", Diags.callback());
144   EXPECT_THAT(Diags.Diagnostics,
145               ElementsAre(DiagMessage("If should be a dictionary"),
146                           DiagMessage("Config should be a dictionary")));
147   ASSERT_THAT(Results, IsEmpty());
148 }
149 
TEST(ParseYAML,ExternalBlockNone)150 TEST(ParseYAML, ExternalBlockNone) {
151   CapturedDiags Diags;
152   Annotations YAML(R"yaml(
153 Index:
154   External: None
155   )yaml");
156   auto Results =
157       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
158   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
159   ASSERT_EQ(Results.size(), 1u);
160   ASSERT_TRUE(Results[0].Index.External);
161   EXPECT_FALSE(Results[0].Index.External.getValue()->File.hasValue());
162   EXPECT_FALSE(Results[0].Index.External.getValue()->MountPoint.hasValue());
163   EXPECT_FALSE(Results[0].Index.External.getValue()->Server.hasValue());
164   EXPECT_THAT(*Results[0].Index.External.getValue()->IsNone, testing::Eq(true));
165 }
166 
TEST(ParseYAML,ExternalBlock)167 TEST(ParseYAML, ExternalBlock) {
168   CapturedDiags Diags;
169   Annotations YAML(R"yaml(
170 Index:
171   External:
172     File: "foo"
173     Server: ^"bar"
174     MountPoint: "baz"
175   )yaml");
176   auto Results =
177       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
178   ASSERT_EQ(Results.size(), 1u);
179   ASSERT_TRUE(Results[0].Index.External);
180   EXPECT_THAT(*Results[0].Index.External.getValue()->File, Val("foo"));
181   EXPECT_THAT(*Results[0].Index.External.getValue()->MountPoint, Val("baz"));
182   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
183   EXPECT_THAT(*Results[0].Index.External.getValue()->Server, Val("bar"));
184 }
185 
TEST(ParseYAML,AllScopes)186 TEST(ParseYAML, AllScopes) {
187   CapturedDiags Diags;
188   Annotations YAML(R"yaml(
189 Completion:
190   AllScopes: True
191   )yaml");
192   auto Results =
193       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
194   ASSERT_THAT(Diags.Diagnostics, IsEmpty());
195   ASSERT_EQ(Results.size(), 1u);
196   EXPECT_THAT(Results[0].Completion.AllScopes, llvm::ValueIs(Val(true)));
197 }
198 
TEST(ParseYAML,AllScopesWarn)199 TEST(ParseYAML, AllScopesWarn) {
200   CapturedDiags Diags;
201   Annotations YAML(R"yaml(
202 Completion:
203   AllScopes: $diagrange[[Truex]]
204   )yaml");
205   auto Results =
206       Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
207   EXPECT_THAT(Diags.Diagnostics,
208               ElementsAre(AllOf(DiagMessage("AllScopes should be a boolean"),
209                                 DiagKind(llvm::SourceMgr::DK_Warning),
210                                 DiagPos(YAML.range("diagrange").start),
211                                 DiagRange(YAML.range("diagrange")))));
212   ASSERT_EQ(Results.size(), 1u);
213   EXPECT_THAT(Results[0].Completion.AllScopes, testing::Eq(llvm::None));
214 }
215 } // namespace
216 } // namespace config
217 } // namespace clangd
218 } // namespace clang
219