1 /*
2  * SessionFindTests.cpp
3  *
4  * Copyright (C) 2021 by RStudio, PBC
5  *
6  * Unless you have received this program directly from RStudio pursuant
7  * to the terms of a commercial license agreement with RStudio, then
8  * this program is licensed to you under the terms of version 3 of the
9  * GNU Affero General Public License. This program is distributed WITHOUT
10  * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11  * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12  * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13  *
14  */
15 
16 #include "SessionFind.hpp"
17 
18 #include <core/system/ShellUtils.hpp>
19 
20 #define RSTUDIO_NO_TESTTHAT_ALIASES
21 #include <tests/TestThat.hpp>
22 
23 namespace rstudio {
24 namespace session {
25 namespace modules {
26 namespace find {
27 namespace tests {
28 
29 using namespace rstudio::core;
30 using namespace modules::find;
31 
32 namespace {
33 
34 const std::string kLine("RStudio is great");
35 const std::string kReplaceString("awesome");
36 const size_t matchOn = 11;
37 const size_t matchOff = 16;
38 
39 const std::string kRegexLine("aba OOOkkk okab AAOO aaabbb aa abab");
40 const size_t rMatchOn = 4;
41 const size_t rMatchOff = 10;
42 const size_t caseMatchOn = 21;
43 const size_t caseMatchOff = 27;
44 
45 const std::string kFindRegex("\\([a-z]\\)\\1\\{2\\}\\([a-z]\\)\\2\\{2\\}");
46 const std::string kReplaceRegex("\\1\\2\\1\\2");
47 
48 const std::string kGrepPattern("aba \033[01m\033[KOOOkkk\033[m\033[K okab AAOO awesome aa abab");
49 const std::string kGitGrepPattern("aba \033[1;31mOOOkkk\033[m okab AAOO awesome aa abab");
50 } // anonymous namespace
51 
52 TEST_CASE("SessionFind")
53 {
54    SECTION("Replace literal ignore case")
55    {
56       std::string line(kLine);
57       size_t replaceMatchOff;
58 
59       Replacer replacer(true);
60       replacer.replaceLiteral(matchOn, matchOff,
61          kReplaceString, &line, &replaceMatchOff);
62       CHECK(line.compare("RStudio is awesome") == 0);
63       CHECK(replaceMatchOff == 18);
64    }
65 
66    SECTION("Replace regex case sensitive")
67    {
68       std::string line(kRegexLine);
69       size_t replaceMatchOff;
70 
71       Replacer replacer(false);
72       replacer.replaceRegex(caseMatchOn, caseMatchOff,
73          kFindRegex, kReplaceString, &line, &replaceMatchOff);
74       CHECK(line.compare("aba OOOkkk okab AAOO awesome aa abab") == 0);
75       CHECK(replaceMatchOff == 28);
76    }
77 
78    SECTION("Replace regex ignore case")
79    {
80       std::string line(kRegexLine);
81       size_t replaceMatchOff;
82 
83       Replacer replacer(true);
84       replacer.replaceRegex(rMatchOn, rMatchOff, kFindRegex, kReplaceRegex, &line,
85          &replaceMatchOff);
86       CHECK(line.compare("aba OkOk okab AAOO aaabbb aa abab") == 0);
87       CHECK(replaceMatchOff == 8);
88 
89       size_t matchOn = 18;
90       size_t matchOff = 24;
91       replacer.replaceRegex(matchOn, matchOff, kFindRegex, kReplaceRegex, &line,
92          &replaceMatchOff);
93       CHECK(line.compare("aba OkOk okab AAOO abab aa abab") == 0);
94       CHECK(replaceMatchOff == 22);
95    }
96 
97    SECTION("Replace regex case sensitive")
98    {
99       std::string line(kRegexLine);
100       size_t replaceMatchOff;
101 
102       Replacer replacer(false);
103       replacer.replaceRegex(caseMatchOn, caseMatchOff, kFindRegex, kReplaceRegex, &line,
104          &replaceMatchOff);
105       CHECK(line.compare("aba OOOkkk okab AAOO abab aa abab") == 0);
106       CHECK(replaceMatchOff == 25);
107    }
108 
109    SECTION("Replace ASCII encoding")
110    {
111       std::string line("äSCII ìs ƒun");
112       std::string find("ƒun");
113       std::string replace("Ök");
114 
115       size_t matchOn = 11;
116       size_t matchOff = 15;
117       size_t replaceMatchOff;
118 
119       Replacer replacer(false, "ASCII");
120       replacer.replaceRegex(matchOn, matchOff, find, replace, &line, &replaceMatchOff);
121       CHECK(line.compare("äSCII ìs Ök") == 0);
122       CHECK(replaceMatchOff == 14);
123    }
124 
125    SECTION("Replace BIG5 encoding")
126    {
127       std::string line("´sπƒ∆GƒßµM");
128       std::string find("∆G");
129       std::string replace("…@");
130 
131       size_t matchOn = 7;
132       size_t matchOff = 11;
133       size_t replaceMatchOff;
134 
135       Replacer replacer(false, "BIG5");
136       replacer.replaceRegex(matchOn, matchOff, find, replace, &line, &replaceMatchOff);
137       CHECK(line.compare("´sπƒ…@ƒßµM") == 0);
138       CHECK(replaceMatchOff == 11);
139 
140    }
141 
142    SECTION("Attempt replace without valid match")
143    {
144       std::string line(kLine);
145       size_t replaceMatchOff = 99;
146 
147       Replacer replacer(true);
148       Error error = replacer.replaceRegex(rMatchOn, rMatchOff, kFindRegex, kReplaceRegex,
149          &line, &replaceMatchOff);
150       CHECK(line.compare(kLine) == 0);
151    }
152 
153    SECTION("Attempt replace with consecutive matches")
154    {
155       std::string line("hellohellohello");
156       std::string replacePattern("hello world");
157       size_t replaceMatchOff;
158       size_t on = 10;
159       size_t off = 15;
160 
161       Replacer replacer(true);
162       replacer.replaceLiteral(on, off, replacePattern, &line, &replaceMatchOff);
163       CHECK(line.compare("hellohellohello world") == 0);
164       CHECK(replaceMatchOff == 21);
165 
166       on = 5;
167       off = 10;
168       replacer.replaceRegex(on, off, "hello", replacePattern, &line, &replaceMatchOff);
169       CHECK(line.compare("hellohello worldhello world") == 0);
170       CHECK(replaceMatchOff == 16);
171 
172       on = 0;
173       off = 5;
174       replacer.replaceRegex(on, off, "hello", replacePattern, &line, &replaceMatchOff);
175       CHECK(line.compare("hello worldhello worldhello world") == 0);
176       CHECK(replaceMatchOff == 11);
177    }
178 
179    SECTION("Attempt regex replace with nested results")
180    {
181       std::string line("hehello worldllo");
182       std::string findPattern("he[^ ].*llo");
183       std::string replacePattern("hello world");
184       size_t replaceMatchOff;
185       size_t on = 0;
186       size_t off = 16;
187 
188       Replacer replacer(false);
189       replacer.replaceRegex(on, off, findPattern, replacePattern, &line, &replaceMatchOff);
190       CHECK(line.compare("hello world") == 0);
191       CHECK(replaceMatchOff == 11);
192    }
193 
194    SECTION("Grep get file, line number, and contents")
195    {
196       boost::regex regex = getGrepOutputRegex(/*isGitGrep*/ false);
197       std::string contents(
198          "case.test:2:aba \033[01m\033[KOOOkkk\033[m\033[K okab AAOO awesome aa abab");
199 
200       boost::smatch match;
201       CHECK(regex_utils::match(contents, match, regex));
202       CHECK(match[1].str().compare("case.test") == 0);
203       CHECK(match[2].str().compare("2") == 0);
204       CHECK(match[3].str().compare(kGrepPattern) == 0);
205    }
206 
207    SECTION("Grep get color encoding regex")
208    {
209       boost::regex regex = getColorEncodingRegex(/*isGitGrep*/ false);
210       boost::cmatch match;
211       CHECK(regex_utils::search(kGrepPattern.c_str(), match, regex));
212       CHECK(match[1].str().compare("01") == 0);
213    }
214 
215    SECTION("Git grep get file, line number, and contents")
216    {
217       boost::regex regex = getGrepOutputRegex(/*isGitGrep*/ true);
218       std::string contents(
219    "case.test\033[36m:\033[m2\033[36m:\033[maba \033[1;31mOOOkkk\033[m okab AAOO awesome aa abab");
220 
221       boost::smatch match;
222       CHECK(regex_utils::match(contents, match, regex));
223       CHECK(match[1].str().compare("case.test") == 0);
224       CHECK(match[2].str().compare("2") == 0);
225       CHECK(match[3].str().compare(kGitGrepPattern) == 0);
226    }
227 
228    SECTION("Git grep with colon get file, line number, and contents")
229    {
230       boost::regex regex = getGrepOutputRegex(/*isGitGrep*/ true);
231       std::string contents(
232          "file.test\033[36m:\033[m9\033[36m:\033[m  - \033[1;31mr:\033[m devel");
233 
234       boost::smatch match;
235       CHECK(regex_utils::match(contents, match, regex));
236       CHECK(match[1].str().compare("file.test") == 0);
237       CHECK(match[2].str().compare("9") == 0);
238       CHECK(match[3].str().compare("  - \033[1;31mr:\033[m devel") == 0);
239    }
240 
241    SECTION("Git grep get color encoding regex")
242    {
243       boost::regex regex = getColorEncodingRegex(/*isGitGrep*/ true);
244       boost::cmatch match;
245       CHECK(regex_utils::search(kGitGrepPattern.c_str(), match, regex));
246       CHECK(match[2].str().compare("1") == 0);
247    }
248 
249 }
250 
251 } // end namespace tests
252 } // end namespace modules
253 } // end namespace find
254 } // end namespace session
255 } // end namespace rstudio
256 
257