1 | /* |
2 | * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. |
3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
4 | * |
5 | * This code is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License version 2 only, as |
7 | * published by the Free Software Foundation. |
8 | * |
9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
12 | * version 2 for more details (a copy is included in the LICENSE file that |
13 | * accompanied this code). |
14 | * |
15 | * You should have received a copy of the GNU General Public License version |
16 | * 2 along with this work; if not, write to the Free Software Foundation, |
17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
18 | * |
19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
20 | * or visit www.oracle.com if you need additional information or have any |
21 | * questions. |
22 | */ |
23 | |
24 | #include "precompiled.hpp" |
25 | #include "jvm.h" |
26 | #include "logTestFixture.hpp" |
27 | #include "logTestUtils.inline.hpp" |
28 | #include "logging/logConfiguration.hpp" |
29 | #include "logging/logFileStreamOutput.hpp" |
30 | #include "logging/logLevel.hpp" |
31 | #include "logging/logOutput.hpp" |
32 | #include "logging/logTag.hpp" |
33 | #include "logging/logTagSet.hpp" |
34 | #include "memory/resourceArea.hpp" |
35 | #include "unittest.hpp" |
36 | #include "utilities/ostream.hpp" |
37 | |
38 | class LogConfigurationTest : public LogTestFixture { |
39 | protected: |
40 | static char _all_decorators[256]; |
41 | |
42 | public: |
43 | static void SetUpTestCase(); |
44 | }; |
45 | |
46 | char LogConfigurationTest::_all_decorators[256]; |
47 | |
48 | // Prepare _all_decorators to contain the full list of decorators (comma separated) |
49 | void LogConfigurationTest::SetUpTestCase() { |
50 | char *pos = _all_decorators; |
51 | for (size_t i = 0; i < LogDecorators::Count; i++) { |
52 | pos += jio_snprintf(pos, sizeof(_all_decorators) - (pos - _all_decorators), "%s%s" , |
53 | (i == 0 ? "" : "," ), |
54 | LogDecorators::name(static_cast<LogDecorators::Decorator>(i))); |
55 | } |
56 | } |
57 | |
58 | // Check if the given text is included by LogConfiguration::describe() |
59 | static bool is_described(const char* text) { |
60 | ResourceMark rm; |
61 | stringStream ss; |
62 | LogConfiguration::describe(&ss); |
63 | return string_contains_substring(ss.as_string(), text); |
64 | } |
65 | |
66 | TEST_VM_F(LogConfigurationTest, describe) { |
67 | ResourceMark rm; |
68 | stringStream ss; |
69 | LogConfiguration::describe(&ss); |
70 | const char* description = ss.as_string(); |
71 | |
72 | // Verify that stdout and stderr are listed by default |
73 | EXPECT_PRED2(string_contains_substring, description, StdoutLog.name()); |
74 | EXPECT_PRED2(string_contains_substring, description, StderrLog.name()); |
75 | |
76 | // Verify that each tag, level and decorator is listed |
77 | for (size_t i = 0; i < LogTag::Count; i++) { |
78 | EXPECT_PRED2(string_contains_substring, description, LogTag::name(static_cast<LogTagType>(i))); |
79 | } |
80 | for (size_t i = 0; i < LogLevel::Count; i++) { |
81 | EXPECT_PRED2(string_contains_substring, description, LogLevel::name(static_cast<LogLevelType>(i))); |
82 | } |
83 | for (size_t i = 0; i < LogDecorators::Count; i++) { |
84 | EXPECT_PRED2(string_contains_substring, description, LogDecorators::name(static_cast<LogDecorators::Decorator>(i))); |
85 | } |
86 | |
87 | // Verify that the default configuration is printed |
88 | char expected_buf[256]; |
89 | int ret = jio_snprintf(expected_buf, sizeof(expected_buf), "=%s" , LogLevel::name(LogLevel::Default)); |
90 | ASSERT_NE(-1, ret); |
91 | EXPECT_PRED2(string_contains_substring, description, expected_buf); |
92 | EXPECT_PRED2(string_contains_substring, description, "#1: stderr all=off" ); |
93 | |
94 | // Verify default decorators are listed |
95 | LogDecorators default_decorators; |
96 | expected_buf[0] = '\0'; |
97 | for (size_t i = 0; i < LogDecorators::Count; i++) { |
98 | LogDecorators::Decorator d = static_cast<LogDecorators::Decorator>(i); |
99 | if (default_decorators.is_decorator(d)) { |
100 | ASSERT_LT(strlen(expected_buf), sizeof(expected_buf)); |
101 | ret = jio_snprintf(expected_buf + strlen(expected_buf), |
102 | sizeof(expected_buf) - strlen(expected_buf), |
103 | "%s%s" , |
104 | strlen(expected_buf) > 0 ? "," : "" , |
105 | LogDecorators::name(d)); |
106 | ASSERT_NE(-1, ret); |
107 | } |
108 | } |
109 | EXPECT_PRED2(string_contains_substring, description, expected_buf); |
110 | |
111 | // Add a new output and verify that it gets described after it has been added |
112 | const char* what = "all=trace" ; |
113 | EXPECT_FALSE(is_described(TestLogFileName)) << "Test output already exists!" ; |
114 | set_log_config(TestLogFileName, what); |
115 | EXPECT_TRUE(is_described(TestLogFileName)); |
116 | EXPECT_TRUE(is_described("all=trace" )); |
117 | } |
118 | |
119 | // Test updating an existing log output |
120 | TEST_VM_F(LogConfigurationTest, update_output) { |
121 | // Update stdout twice, first using it's name, and the second time its index # |
122 | const char* test_outputs[] = { "stdout" , "#0" }; |
123 | for (size_t i = 0; i < ARRAY_SIZE(test_outputs); i++) { |
124 | set_log_config(test_outputs[i], "all=info" ); |
125 | |
126 | // Verify configuration using LogConfiguration::describe |
127 | EXPECT_TRUE(is_described("#0: stdout" )); |
128 | EXPECT_TRUE(is_described("all=info" )); |
129 | |
130 | // Verify by iterating over tagsets |
131 | LogOutput* o = &StdoutLog; |
132 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
133 | EXPECT_TRUE(ts->has_output(o)); |
134 | EXPECT_TRUE(ts->is_level(LogLevel::Info)); |
135 | EXPECT_FALSE(ts->is_level(LogLevel::Debug)); |
136 | } |
137 | |
138 | // Now change the level and verify the change propagated |
139 | set_log_config(test_outputs[i], "all=debug" ); |
140 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
141 | EXPECT_TRUE(ts->has_output(o)); |
142 | EXPECT_TRUE(ts->is_level(LogLevel::Debug)); |
143 | EXPECT_FALSE(ts->is_level(LogLevel::Trace)); |
144 | } |
145 | } |
146 | } |
147 | |
148 | // Test adding a new output to the configuration |
149 | TEST_VM_F(LogConfigurationTest, add_new_output) { |
150 | const char* what = "all=trace" ; |
151 | |
152 | ASSERT_FALSE(is_described(TestLogFileName)); |
153 | set_log_config(TestLogFileName, what); |
154 | |
155 | // Verify new output using LogConfiguration::describe |
156 | EXPECT_TRUE(is_described(TestLogFileName)); |
157 | EXPECT_TRUE(is_described("all=trace" )); |
158 | |
159 | // Also verify by iterating over tagsets, checking levels on tagsets |
160 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
161 | EXPECT_TRUE(ts->is_level(LogLevel::Trace)); |
162 | } |
163 | } |
164 | |
165 | TEST_VM_F(LogConfigurationTest, disable_logging) { |
166 | // Add TestLogFileName as an output |
167 | set_log_config(TestLogFileName, "logging=info" ); |
168 | |
169 | // Add a second file output |
170 | char other_file_name[2 * K]; |
171 | jio_snprintf(other_file_name, sizeof(other_file_name), "%s-other" , TestLogFileName); |
172 | set_log_config(other_file_name, "logging=info" ); |
173 | |
174 | LogConfiguration::disable_logging(); |
175 | |
176 | // Verify that both file outputs were disabled |
177 | EXPECT_FALSE(is_described(TestLogFileName)); |
178 | EXPECT_FALSE(is_described(other_file_name)); |
179 | delete_file(other_file_name); |
180 | |
181 | // Verify that no tagset has logging enabled |
182 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
183 | EXPECT_FALSE(ts->has_output(&StdoutLog)); |
184 | EXPECT_FALSE(ts->has_output(&StderrLog)); |
185 | EXPECT_FALSE(ts->is_level(LogLevel::Error)); |
186 | } |
187 | } |
188 | |
189 | // Test disabling a particular output |
190 | TEST_VM_F(LogConfigurationTest, disable_output) { |
191 | // Disable the default configuration for stdout |
192 | set_log_config("stdout" , "all=off" ); |
193 | |
194 | // Verify configuration using LogConfiguration::describe |
195 | EXPECT_TRUE(is_described("#0: stdout all=off" )); |
196 | |
197 | // Verify by iterating over tagsets |
198 | LogOutput* o = &StdoutLog; |
199 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
200 | EXPECT_FALSE(ts->has_output(o)); |
201 | EXPECT_FALSE(ts->is_level(LogLevel::Error)); |
202 | } |
203 | |
204 | // Add a new file output |
205 | const char* what = "all=debug" ; |
206 | set_log_config(TestLogFileName, what); |
207 | EXPECT_TRUE(is_described(TestLogFileName)); |
208 | |
209 | // Now disable it, verifying it is removed completely |
210 | set_log_config(TestLogFileName, "all=off" ); |
211 | EXPECT_FALSE(is_described(TestLogFileName)); |
212 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
213 | EXPECT_FALSE(ts->is_level(LogLevel::Error)); |
214 | } |
215 | } |
216 | |
217 | // Test reconfiguration of the selected decorators for an output |
218 | TEST_VM_F(LogConfigurationTest, reconfigure_decorators) { |
219 | // Configure stderr with all decorators |
220 | set_log_config("stderr" , "all=off" , _all_decorators); |
221 | char buf[256]; |
222 | int ret = jio_snprintf(buf, sizeof(buf), "#1: stderr all=off %s" , _all_decorators); |
223 | ASSERT_NE(-1, ret); |
224 | EXPECT_TRUE(is_described(buf)) << "'" << buf << "' not described after reconfiguration" ; |
225 | |
226 | // Now reconfigure logging on stderr with no decorators |
227 | set_log_config("stderr" , "all=off" , "none" ); |
228 | EXPECT_TRUE(is_described("#1: stderr all=off none (reconfigured)\n" )) << "Expecting no decorators" ; |
229 | } |
230 | |
231 | // Test that invalid options cause configuration errors |
232 | TEST_VM_F(LogConfigurationTest, invalid_configure_options) { |
233 | LogConfiguration::disable_logging(); |
234 | const char* invalid_outputs[] = { "#2" , "invalidtype=123" , ":invalid/path}to*file?" }; |
235 | for (size_t i = 0; i < ARRAY_SIZE(invalid_outputs); i++) { |
236 | EXPECT_FALSE(set_log_config(invalid_outputs[i], "" , "" , "" , true)) |
237 | << "Accepted invalid output '" << invalid_outputs[i] << "'" ; |
238 | } |
239 | EXPECT_FALSE(LogConfiguration::parse_command_line_arguments("all=invalid_level" )); |
240 | EXPECT_FALSE(LogConfiguration::parse_command_line_arguments("what=invalid" )); |
241 | EXPECT_FALSE(LogConfiguration::parse_command_line_arguments("all::invalid_decorator" )); |
242 | EXPECT_FALSE(LogConfiguration::parse_command_line_arguments("*" )); |
243 | } |
244 | |
245 | // Test empty configuration options |
246 | TEST_VM_F(LogConfigurationTest, parse_empty_command_line_arguments) { |
247 | const char* empty_variations[] = { "" , ":" , "::" , ":::" , "::::" }; |
248 | for (size_t i = 0; i < ARRAY_SIZE(empty_variations); i++) { |
249 | const char* cmdline = empty_variations[i]; |
250 | bool ret = LogConfiguration::parse_command_line_arguments(cmdline); |
251 | EXPECT_TRUE(ret) << "Error parsing command line arguments '" << cmdline << "'" ; |
252 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
253 | EXPECT_EQ(LogLevel::Unspecified, ts->level_for(&StdoutLog)); |
254 | } |
255 | } |
256 | } |
257 | |
258 | // Test basic command line parsing & configuration |
259 | TEST_VM_F(LogConfigurationTest, parse_command_line_arguments) { |
260 | // Prepare a command line for logging*=debug on stderr with all decorators |
261 | int ret; |
262 | char buf[256]; |
263 | ret = jio_snprintf(buf, sizeof(buf), "logging*=debug:stderr:%s" , _all_decorators); |
264 | ASSERT_NE(-1, ret); |
265 | |
266 | bool success = LogConfiguration::parse_command_line_arguments(buf); |
267 | EXPECT_TRUE(success) << "Error parsing valid command line arguments '" << buf << "'" ; |
268 | // Ensure the new configuration applied |
269 | EXPECT_TRUE(is_described("logging*=debug" )); |
270 | EXPECT_TRUE(is_described(_all_decorators)); |
271 | |
272 | // Test the configuration of file outputs as well |
273 | ret = jio_snprintf(buf, sizeof(buf), ":%s" , TestLogFileName); |
274 | ASSERT_NE(-1, ret); |
275 | EXPECT_TRUE(LogConfiguration::parse_command_line_arguments(buf)); |
276 | } |
277 | |
278 | // Test split up log configuration arguments |
279 | TEST_VM_F(LogConfigurationTest, parse_log_arguments) { |
280 | ResourceMark rm; |
281 | stringStream ss; |
282 | // Verify that it's possible to configure each individual tag |
283 | for (size_t t = 1 /* Skip _NO_TAG */; t < LogTag::Count; t++) { |
284 | const LogTagType tag = static_cast<LogTagType>(t); |
285 | EXPECT_TRUE(LogConfiguration::parse_log_arguments("stdout" , LogTag::name(tag), "" , "" , &ss)); |
286 | } |
287 | // Same for each level |
288 | for (size_t l = 0; l < LogLevel::Count; l++) { |
289 | const LogLevelType level = static_cast<LogLevelType>(l); |
290 | char expected_buf[256]; |
291 | int ret = jio_snprintf(expected_buf, sizeof(expected_buf), "all=%s" , LogLevel::name(level)); |
292 | ASSERT_NE(-1, ret); |
293 | EXPECT_TRUE(LogConfiguration::parse_log_arguments("stderr" , expected_buf, "" , "" , &ss)); |
294 | } |
295 | // And for each decorator |
296 | for (size_t d = 0; d < LogDecorators::Count; d++) { |
297 | const LogDecorators::Decorator decorator = static_cast<LogDecorators::Decorator>(d); |
298 | EXPECT_TRUE(LogConfiguration::parse_log_arguments("#0" , "" , LogDecorators::name(decorator), "" , &ss)); |
299 | } |
300 | } |
301 | |
302 | TEST_VM_F(LogConfigurationTest, configure_stdout) { |
303 | // Start out with all logging disabled |
304 | LogConfiguration::disable_logging(); |
305 | |
306 | // Enable 'logging=info', verifying it has been set |
307 | LogConfiguration::configure_stdout(LogLevel::Info, true, LOG_TAGS(logging)); |
308 | EXPECT_TRUE(log_is_enabled(Info, logging)); |
309 | EXPECT_FALSE(log_is_enabled(Debug, logging)); |
310 | EXPECT_FALSE(log_is_enabled(Info, gc)); |
311 | LogTagSet* logging_ts = &LogTagSetMapping<LOG_TAGS(logging)>::tagset(); |
312 | EXPECT_EQ(LogLevel::Info, logging_ts->level_for(&StdoutLog)); |
313 | |
314 | // Enable 'gc=debug' (no wildcard), verifying no other tags are enabled |
315 | LogConfiguration::configure_stdout(LogLevel::Debug, true, LOG_TAGS(gc)); |
316 | EXPECT_TRUE(log_is_enabled(Debug, gc)); |
317 | EXPECT_TRUE(log_is_enabled(Info, logging)); |
318 | EXPECT_FALSE(log_is_enabled(Debug, gc, heap)); |
319 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
320 | if (ts->contains(PREFIX_LOG_TAG(gc))) { |
321 | if (ts->ntags() == 1) { |
322 | EXPECT_EQ(LogLevel::Debug, ts->level_for(&StdoutLog)); |
323 | } else { |
324 | EXPECT_EQ(LogLevel::Off, ts->level_for(&StdoutLog)); |
325 | } |
326 | } |
327 | } |
328 | |
329 | // Enable 'gc*=trace' (with wildcard), verifying that all tag combinations with gc are enabled (gc+...) |
330 | LogConfiguration::configure_stdout(LogLevel::Trace, false, LOG_TAGS(gc)); |
331 | EXPECT_TRUE(log_is_enabled(Trace, gc)); |
332 | EXPECT_TRUE(log_is_enabled(Trace, gc, heap)); |
333 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
334 | if (ts->contains(PREFIX_LOG_TAG(gc))) { |
335 | EXPECT_EQ(LogLevel::Trace, ts->level_for(&StdoutLog)); |
336 | } else if (ts == logging_ts) { |
337 | // Previous setting for 'logging' should remain |
338 | EXPECT_EQ(LogLevel::Info, ts->level_for(&StdoutLog)); |
339 | } else { |
340 | EXPECT_EQ(LogLevel::Off, ts->level_for(&StdoutLog)); |
341 | } |
342 | } |
343 | |
344 | // Disable 'gc*' and 'logging', verifying all logging is properly disabled |
345 | LogConfiguration::configure_stdout(LogLevel::Off, true, LOG_TAGS(logging)); |
346 | EXPECT_FALSE(log_is_enabled(Error, logging)); |
347 | LogConfiguration::configure_stdout(LogLevel::Off, false, LOG_TAGS(gc)); |
348 | EXPECT_FALSE(log_is_enabled(Error, gc)); |
349 | EXPECT_FALSE(log_is_enabled(Error, gc, heap)); |
350 | for (LogTagSet* ts = LogTagSet::first(); ts != NULL; ts = ts->next()) { |
351 | EXPECT_EQ(LogLevel::Off, ts->level_for(&StdoutLog)); |
352 | } |
353 | } |
354 | |
355 | static int Test_logconfiguration_subscribe_triggered = 0; |
356 | static void Test_logconfiguration_subscribe_helper() { |
357 | Test_logconfiguration_subscribe_triggered++; |
358 | } |
359 | |
360 | TEST_VM_F(LogConfigurationTest, subscribe) { |
361 | ResourceMark rm; |
362 | Log(logging) log; |
363 | set_log_config("stdout" , "logging*=trace" ); |
364 | |
365 | LogConfiguration::register_update_listener(&Test_logconfiguration_subscribe_helper); |
366 | |
367 | LogStream ls(log.error()); |
368 | LogConfiguration::parse_log_arguments("stdout" , "logging=trace" , NULL, NULL, &ls); |
369 | ASSERT_EQ(1, Test_logconfiguration_subscribe_triggered); |
370 | |
371 | LogConfiguration::configure_stdout(LogLevel::Debug, true, LOG_TAGS(gc)); |
372 | ASSERT_EQ(2, Test_logconfiguration_subscribe_triggered); |
373 | |
374 | LogConfiguration::disable_logging(); |
375 | ASSERT_EQ(3, Test_logconfiguration_subscribe_triggered); |
376 | } |
377 | |
378 | TEST_VM_F(LogConfigurationTest, parse_invalid_tagset) { |
379 | static const char* invalid_tagset = "logging+start+exit+safepoint+gc" ; // Must not exist for test to function. |
380 | |
381 | // Make sure warning is produced if one or more configured tagsets are invalid |
382 | ResourceMark rm; |
383 | stringStream ss; |
384 | bool success = LogConfiguration::parse_log_arguments("stdout" , invalid_tagset, NULL, NULL, &ss); |
385 | const char* msg = ss.as_string(); |
386 | EXPECT_TRUE(success) << "Should only cause a warning, not an error" ; |
387 | EXPECT_TRUE(string_contains_substring(msg, "No tag set matches selection:" )); |
388 | EXPECT_TRUE(string_contains_substring(msg, invalid_tagset)); |
389 | } |
390 | |
391 | TEST_VM_F(LogConfigurationTest, output_name_normalization) { |
392 | const char* patterns[] = { "%s" , "file=%s" , "\"%s\"" , "file=\"%s\"" }; |
393 | char buf[1 * K]; |
394 | for (size_t i = 0; i < ARRAY_SIZE(patterns); i++) { |
395 | int ret = jio_snprintf(buf, sizeof(buf), patterns[i], TestLogFileName); |
396 | ASSERT_NE(-1, ret); |
397 | set_log_config(buf, "logging=trace" ); |
398 | EXPECT_TRUE(is_described("#2: " )); |
399 | EXPECT_TRUE(is_described(TestLogFileName)); |
400 | EXPECT_FALSE(is_described("#3: " )) |
401 | << "duplicate file output due to incorrect normalization for pattern: " << patterns[i]; |
402 | } |
403 | |
404 | // Make sure prefixes are ignored when used within quotes |
405 | // (this should create a log with "file=" in its filename) |
406 | // Note that the filename cannot contain directories because |
407 | // it is being prefixed with "file=". |
408 | const char* leafFileName = "\"file=leaf_file_name\"" ; |
409 | set_log_config(leafFileName, "logging=trace" ); |
410 | EXPECT_TRUE(is_described("#3: " )) << "prefix within quotes not ignored as it should be" ; |
411 | set_log_config(leafFileName, "all=off" ); |
412 | |
413 | // Remove the extra log file created |
414 | delete_file("file=leaf_file_name" ); |
415 | } |
416 | |
417 | static size_t count_occurrences(const char* haystack, const char* needle) { |
418 | size_t count = 0; |
419 | for (const char* p = strstr(haystack, needle); p != NULL; p = strstr(p + 1, needle)) { |
420 | count++; |
421 | } |
422 | return count; |
423 | } |
424 | |
425 | TEST_OTHER_VM(LogConfiguration, output_reconfigured) { |
426 | ResourceMark rm; |
427 | stringStream ss; |
428 | |
429 | EXPECT_FALSE(is_described("(reconfigured)" )); |
430 | |
431 | bool success = LogConfiguration::parse_log_arguments("#1" , "all=warning" , NULL, NULL, &ss); |
432 | ASSERT_TRUE(success); |
433 | EXPECT_EQ(0u, ss.size()); |
434 | |
435 | LogConfiguration::describe(&ss); |
436 | EXPECT_EQ(1u, count_occurrences(ss.as_string(), "(reconfigured)" )); |
437 | |
438 | ss.reset(); |
439 | LogConfiguration::configure_stdout(LogLevel::Info, false, LOG_TAGS(logging)); |
440 | LogConfiguration::describe(&ss); |
441 | EXPECT_EQ(2u, count_occurrences(ss.as_string(), "(reconfigured)" )); |
442 | } |
443 | |
444 | TEST_VM_F(LogConfigurationTest, suggest_similar_selection) { |
445 | static const char* nonexisting_tagset = "logging+start+exit+safepoint+gc" ; |
446 | |
447 | ResourceMark rm; |
448 | stringStream ss; |
449 | LogConfiguration::parse_log_arguments("stdout" , nonexisting_tagset, NULL, NULL, &ss); |
450 | |
451 | const char* suggestion = ss.as_string(); |
452 | SCOPED_TRACE(suggestion); |
453 | EXPECT_TRUE(string_contains_substring(ss.as_string(), "Did you mean any of the following?" )); |
454 | EXPECT_TRUE(string_contains_substring(suggestion, "logging" ) || |
455 | string_contains_substring(suggestion, "start" ) || |
456 | string_contains_substring(suggestion, "exit" ) || |
457 | string_contains_substring(suggestion, "safepoint" ) || |
458 | string_contains_substring(suggestion, "gc" )) << |
459 | "suggestion must contain AT LEAST one of the tags in user supplied selection" ; |
460 | } |
461 | |