xref: /aosp_15_r20/external/abseil-cpp/absl/log/stripping_test.cc (revision 9356374a3709195abf420251b3e825997ff56c0f)
1 //
2 // Copyright 2022 The Abseil Authors.
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      https://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 // Tests for stripping of literal strings.
17 // ---------------------------------------
18 //
19 // When a `LOG` statement can be trivially proved at compile time to never fire,
20 // e.g. due to `ABSL_MIN_LOG_LEVEL`, `NDEBUG`, or some explicit condition, data
21 // streamed in can be dropped from the compiled program completely if they are
22 // not used elsewhere.  This most commonly affects string literals, which users
23 // often want to strip to reduce binary size and/or redact information about
24 // their program's internals (e.g. in a release build).
25 //
26 // These tests log strings and then validate whether they appear in the compiled
27 // binary.  This is done by opening the file corresponding to the running test
28 // and running a simple string search on its contents.  The strings to be logged
29 // and searched for must be unique, and we must take care not to emit them into
30 // the binary in any other place, e.g. when searching for them.  The latter is
31 // accomplished by computing them using base64; the source string appears in the
32 // binary but the target string is computed at runtime.
33 
34 #include <stdio.h>
35 
36 #if defined(__MACH__)
37 #include <mach-o/dyld.h>
38 #elif defined(_WIN32)
39 #include <Windows.h>
40 #include <tchar.h>
41 #endif
42 
43 #include <algorithm>
44 #include <functional>
45 #include <memory>
46 #include <ostream>
47 #include <string>
48 
49 #include "gmock/gmock.h"
50 #include "gtest/gtest.h"
51 #include "absl/base/internal/strerror.h"
52 #include "absl/base/log_severity.h"
53 #include "absl/flags/internal/program_name.h"
54 #include "absl/log/check.h"
55 #include "absl/log/internal/test_helpers.h"
56 #include "absl/log/log.h"
57 #include "absl/status/status.h"
58 #include "absl/strings/escaping.h"
59 #include "absl/strings/str_format.h"
60 #include "absl/strings/string_view.h"
61 
62 // Set a flag that controls whether we actually execute fatal statements, but
63 // prevent the compiler from optimizing it out.
64 static volatile bool kReallyDie = false;
65 
66 namespace {
67 using ::testing::_;
68 using ::testing::Eq;
69 using ::testing::NotNull;
70 
71 using absl::log_internal::kAbslMinLogLevel;
72 
Base64UnescapeOrDie(absl::string_view data)73 std::string Base64UnescapeOrDie(absl::string_view data) {
74   std::string decoded;
75   CHECK(absl::Base64Unescape(data, &decoded));
76   return decoded;
77 }
78 
79 // -----------------------------------------------------------------------------
80 // A Googletest matcher which searches the running binary for a given string
81 // -----------------------------------------------------------------------------
82 
83 // This matcher is used to validate that literal strings streamed into
84 // `LOG` statements that ought to be compiled out (e.g. `LOG_IF(INFO, false)`)
85 // do not appear in the binary.
86 //
87 // Note that passing the string to be sought directly to `FileHasSubstr()` all
88 // but forces its inclusion in the binary regardless of the logging library's
89 // behavior. For example:
90 //
91 //   LOG_IF(INFO, false) << "you're the man now dog";
92 //   // This will always pass:
93 //   // EXPECT_THAT(fp, FileHasSubstr("you're the man now dog"));
94 //   // So use this instead:
95 //   EXPECT_THAT(fp, FileHasSubstr(
96 //       Base64UnescapeOrDie("eW91J3JlIHRoZSBtYW4gbm93IGRvZw==")));
97 
98 class FileHasSubstrMatcher final : public ::testing::MatcherInterface<FILE*> {
99  public:
FileHasSubstrMatcher(absl::string_view needle)100   explicit FileHasSubstrMatcher(absl::string_view needle) : needle_(needle) {}
101 
MatchAndExplain(FILE * fp,::testing::MatchResultListener * listener) const102   bool MatchAndExplain(
103       FILE* fp, ::testing::MatchResultListener* listener) const override {
104     std::string buf(
105         std::max<std::string::size_type>(needle_.size() * 2, 163840000), '\0');
106     size_t buf_start_offset = 0;  // The file offset of the byte at `buf[0]`.
107     size_t buf_data_size = 0;     // The number of bytes of `buf` which contain
108                                   // data.
109 
110     ::fseek(fp, 0, SEEK_SET);
111     while (true) {
112       // Fill the buffer to capacity or EOF:
113       while (buf_data_size < buf.size()) {
114         const size_t ret = fread(&buf[buf_data_size], sizeof(char),
115                                  buf.size() - buf_data_size, fp);
116         if (ret == 0) break;
117         buf_data_size += ret;
118       }
119       if (ferror(fp)) {
120         *listener << "error reading file";
121         return false;
122       }
123       const absl::string_view haystack(&buf[0], buf_data_size);
124       const auto off = haystack.find(needle_);
125       if (off != haystack.npos) {
126         *listener << "string found at offset " << buf_start_offset + off;
127         return true;
128       }
129       if (feof(fp)) {
130         *listener << "string not found";
131         return false;
132       }
133       // Copy the end of `buf` to the beginning so we catch matches that span
134       // buffer boundaries.  `buf` and `buf_data_size` are always large enough
135       // that these ranges don't overlap.
136       memcpy(&buf[0], &buf[buf_data_size - needle_.size()], needle_.size());
137       buf_start_offset += buf_data_size - needle_.size();
138       buf_data_size = needle_.size();
139     }
140   }
DescribeTo(std::ostream * os) const141   void DescribeTo(std::ostream* os) const override {
142     *os << "contains the string \"" << needle_ << "\" (base64(\""
143         << Base64UnescapeOrDie(needle_) << "\"))";
144   }
145 
DescribeNegationTo(std::ostream * os) const146   void DescribeNegationTo(std::ostream* os) const override {
147     *os << "does not ";
148     DescribeTo(os);
149   }
150 
151  private:
152   std::string needle_;
153 };
154 
155 class StrippingTest : public ::testing::Test {
156  protected:
SetUp()157   void SetUp() override {
158 #ifndef NDEBUG
159     // Non-optimized builds don't necessarily eliminate dead code at all, so we
160     // don't attempt to validate stripping against such builds.
161     GTEST_SKIP() << "StrippingTests skipped since this build is not optimized";
162 #elif defined(__EMSCRIPTEN__)
163     // These tests require a way to examine the running binary and look for
164     // strings; there's no portable way to do that.
165     GTEST_SKIP()
166         << "StrippingTests skipped since this platform is not optimized";
167 #endif
168   }
169 
170   // Opens this program's executable file.  Returns `nullptr` and writes to
171   // `stderr` on failure.
OpenTestExecutable()172   std::unique_ptr<FILE, std::function<void(FILE*)>> OpenTestExecutable() {
173 #if defined(__linux__)
174     std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
175         fopen("/proc/self/exe", "rb"), [](FILE* fp) { fclose(fp); });
176     if (!fp) {
177       const std::string err = absl::base_internal::StrError(errno);
178       absl::FPrintF(stderr, "Failed to open /proc/self/exe: %s\n", err);
179     }
180     return fp;
181 #elif defined(__Fuchsia__)
182     // TODO(b/242579714): We need to restore the test coverage on this platform.
183     std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
184         fopen(absl::StrCat("/pkg/bin/",
185                            absl::flags_internal::ShortProgramInvocationName())
186                   .c_str(),
187               "rb"),
188         [](FILE* fp) { fclose(fp); });
189     if (!fp) {
190       const std::string err = absl::base_internal::StrError(errno);
191       absl::FPrintF(stderr, "Failed to open /pkg/bin/<binary name>: %s\n", err);
192     }
193     return fp;
194 #elif defined(__MACH__)
195     uint32_t size = 0;
196     int ret = _NSGetExecutablePath(nullptr, &size);
197     if (ret != -1) {
198       absl::FPrintF(stderr,
199                     "Failed to get executable path: "
200                     "_NSGetExecutablePath(nullptr) returned %d\n",
201                     ret);
202       return nullptr;
203     }
204     std::string path(size, '\0');
205     ret = _NSGetExecutablePath(&path[0], &size);
206     if (ret != 0) {
207       absl::FPrintF(
208           stderr,
209           "Failed to get executable path: _NSGetExecutablePath(buffer) "
210           "returned %d\n",
211           ret);
212       return nullptr;
213     }
214     std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
215         fopen(path.c_str(), "rb"), [](FILE* fp) { fclose(fp); });
216     if (!fp) {
217       const std::string err = absl::base_internal::StrError(errno);
218       absl::FPrintF(stderr, "Failed to open executable at %s: %s\n", path, err);
219     }
220     return fp;
221 #elif defined(_WIN32)
222     std::basic_string<TCHAR> path(4096, _T('\0'));
223     while (true) {
224       const uint32_t ret = ::GetModuleFileName(nullptr, &path[0],
225                                                static_cast<DWORD>(path.size()));
226       if (ret == 0) {
227         absl::FPrintF(
228             stderr,
229             "Failed to get executable path: GetModuleFileName(buffer) "
230             "returned 0\n");
231         return nullptr;
232       }
233       if (ret < path.size()) break;
234       path.resize(path.size() * 2, _T('\0'));
235     }
236     std::unique_ptr<FILE, std::function<void(FILE*)>> fp(
237         _tfopen(path.c_str(), _T("rb")), [](FILE* fp) { fclose(fp); });
238     if (!fp) absl::FPrintF(stderr, "Failed to open executable\n");
239     return fp;
240 #else
241     absl::FPrintF(stderr,
242                   "OpenTestExecutable() unimplemented on this platform\n");
243     return nullptr;
244 #endif
245   }
246 
FileHasSubstr(absl::string_view needle)247   ::testing::Matcher<FILE*> FileHasSubstr(absl::string_view needle) {
248     return MakeMatcher(new FileHasSubstrMatcher(needle));
249   }
250 };
251 
252 // This tests whether out methodology for testing stripping works on this
253 // platform by looking for one string that definitely ought to be there and one
254 // that definitely ought not to.  If this fails, none of the `StrippingTest`s
255 // are going to produce meaningful results.
TEST_F(StrippingTest,Control)256 TEST_F(StrippingTest, Control) {
257   constexpr char kEncodedPositiveControl[] =
258       "U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w=";
259   const std::string encoded_negative_control =
260       absl::Base64Escape("StrippingTest.NegativeControl");
261 
262   // Verify this mainly so we can encode other strings and know definitely they
263   // won't encode to `kEncodedPositiveControl`.
264   EXPECT_THAT(Base64UnescapeOrDie("U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w="),
265               Eq("StrippingTest.PositiveControl"));
266 
267   auto exe = OpenTestExecutable();
268   ASSERT_THAT(exe, NotNull());
269   EXPECT_THAT(exe.get(), FileHasSubstr(kEncodedPositiveControl));
270   EXPECT_THAT(exe.get(), Not(FileHasSubstr(encoded_negative_control)));
271 }
272 
TEST_F(StrippingTest,Literal)273 TEST_F(StrippingTest, Literal) {
274   // We need to load a copy of the needle string into memory (so we can search
275   // for it) without leaving it lying around in plaintext in the executable file
276   // as would happen if we used a literal.  We might (or might not) leave it
277   // lying around later; that's what the tests are for!
278   const std::string needle = absl::Base64Escape("StrippingTest.Literal");
279   LOG(INFO) << "U3RyaXBwaW5nVGVzdC5MaXRlcmFs";
280   auto exe = OpenTestExecutable();
281   ASSERT_THAT(exe, NotNull());
282   if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) {
283     EXPECT_THAT(exe.get(), FileHasSubstr(needle));
284   } else {
285     EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
286   }
287 }
288 
TEST_F(StrippingTest,LiteralInExpression)289 TEST_F(StrippingTest, LiteralInExpression) {
290   // We need to load a copy of the needle string into memory (so we can search
291   // for it) without leaving it lying around in plaintext in the executable file
292   // as would happen if we used a literal.  We might (or might not) leave it
293   // lying around later; that's what the tests are for!
294   const std::string needle =
295       absl::Base64Escape("StrippingTest.LiteralInExpression");
296   LOG(INFO) << absl::StrCat("secret: ",
297                             "U3RyaXBwaW5nVGVzdC5MaXRlcmFsSW5FeHByZXNzaW9u");
298   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
299   ASSERT_THAT(exe, NotNull());
300   if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) {
301     EXPECT_THAT(exe.get(), FileHasSubstr(needle));
302   } else {
303     EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
304   }
305 }
306 
TEST_F(StrippingTest,Fatal)307 TEST_F(StrippingTest, Fatal) {
308   // We need to load a copy of the needle string into memory (so we can search
309   // for it) without leaving it lying around in plaintext in the executable file
310   // as would happen if we used a literal.  We might (or might not) leave it
311   // lying around later; that's what the tests are for!
312   const std::string needle = absl::Base64Escape("StrippingTest.Fatal");
313   // We don't care if the LOG statement is actually executed, we're just
314   // checking that it's stripped.
315   if (kReallyDie) LOG(FATAL) << "U3RyaXBwaW5nVGVzdC5GYXRhbA==";
316 
317   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
318   ASSERT_THAT(exe, NotNull());
319   if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
320     EXPECT_THAT(exe.get(), FileHasSubstr(needle));
321   } else {
322     EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
323   }
324 }
325 
TEST_F(StrippingTest,DFatal)326 TEST_F(StrippingTest, DFatal) {
327   // We need to load a copy of the needle string into memory (so we can search
328   // for it) without leaving it lying around in plaintext in the executable file
329   // as would happen if we used a literal.  We might (or might not) leave it
330   // lying around later; that's what the tests are for!
331   const std::string needle = absl::Base64Escape("StrippingTest.DFatal");
332   // We don't care if the LOG statement is actually executed, we're just
333   // checking that it's stripped.
334   if (kReallyDie) LOG(DFATAL) << "U3RyaXBwaW5nVGVzdC5ERmF0YWw=";
335 
336   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
337   ASSERT_THAT(exe, NotNull());
338   // `DFATAL` can be `ERROR` or `FATAL`, and a compile-time optimizer doesn't
339   // know which, because `absl::kLogDebugFatal` is declared `extern` and defined
340   // in another TU.  Link-time optimization might do better.  We have six cases:
341   // |         `AMLL` is-> | `<=ERROR` | `FATAL` | `>FATAL` |
342   // | ------------------- | --------- | ------- | -------- |
343   // | `DFATAL` is `ERROR` |   present |       ? | stripped |
344   // | `DFATAL` is `FATAL` |   present | present | stripped |
345 
346   // These constexpr variables are used to suppress unreachable code warnings
347   // in the if-else statements below.
348 
349   // "present" in the table above: `DFATAL` exceeds `ABSL_MIN_LOG_LEVEL`, so
350   // `DFATAL` statements should not be stripped (and they should be logged
351   // when executed, but that's a different testsuite).
352   constexpr bool kExpectPresent = absl::kLogDebugFatal >= kAbslMinLogLevel;
353 
354   // "stripped" in the table above: even though the compiler may not know
355   // which value `DFATAL` has, it should be able to strip it since both
356   // possible values ought to be stripped.
357   constexpr bool kExpectStripped = kAbslMinLogLevel > absl::LogSeverity::kFatal;
358 
359   if (kExpectPresent) {
360     EXPECT_THAT(exe.get(), FileHasSubstr(needle));
361   } else if (kExpectStripped) {
362     EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
363   } else {
364     // "?" in the table above; may or may not be stripped depending on whether
365     // any link-time optimization is done.  Either outcome is ok.
366   }
367 }
368 
TEST_F(StrippingTest,Level)369 TEST_F(StrippingTest, Level) {
370   const std::string needle = absl::Base64Escape("StrippingTest.Level");
371   volatile auto severity = absl::LogSeverity::kWarning;
372   // Ensure that `severity` is not a compile-time constant to prove that
373   // stripping works regardless:
374   LOG(LEVEL(severity)) << "U3RyaXBwaW5nVGVzdC5MZXZlbA==";
375   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
376   ASSERT_THAT(exe, NotNull());
377   if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
378     // This can't be stripped at compile-time because it might evaluate to a
379     // level that shouldn't be stripped.
380     EXPECT_THAT(exe.get(), FileHasSubstr(needle));
381   } else {
382 #if (defined(_MSC_VER) && !defined(__clang__)) || defined(__APPLE__)
383     // Dead code elimination misses this case.
384 #else
385     // All levels should be stripped, so it doesn't matter what the severity
386     // winds up being.
387     EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle)));
388 #endif
389   }
390 }
391 
TEST_F(StrippingTest,Check)392 TEST_F(StrippingTest, Check) {
393   // Here we also need a variable name with enough entropy that it's unlikely to
394   // appear in the binary by chance.  `volatile` keeps the tautological
395   // comparison (and the rest of the `CHECK`) from being optimized away.
396   const std::string var_needle = absl::Base64Escape("StrippingTestCheckVar");
397   const std::string msg_needle = absl::Base64Escape("StrippingTest.Check");
398   volatile int U3RyaXBwaW5nVGVzdENoZWNrVmFy = 0xCAFE;
399   // We don't care if the CHECK is actually executed, just that stripping works.
400   // Hiding it behind `kReallyDie` works around some overly aggressive
401   // optimizations in older versions of MSVC.
402   if (kReallyDie) {
403     CHECK(U3RyaXBwaW5nVGVzdENoZWNrVmFy != U3RyaXBwaW5nVGVzdENoZWNrVmFy)
404         << "U3RyaXBwaW5nVGVzdC5DaGVjaw==";
405   }
406 
407   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
408   ASSERT_THAT(exe, NotNull());
409   if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
410     EXPECT_THAT(exe.get(), FileHasSubstr(var_needle));
411     EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
412   } else {
413     EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle)));
414     EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
415   }
416 }
417 
TEST_F(StrippingTest,CheckOp)418 TEST_F(StrippingTest, CheckOp) {
419   // See `StrippingTest.Check` for some hairy implementation notes.
420   const std::string var_needle1 =
421       absl::Base64Escape("StrippingTestCheckOpVar1");
422   const std::string var_needle2 =
423       absl::Base64Escape("StrippingTestCheckOpVar2");
424   const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckOp");
425   volatile int U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIx = 0xFEED;
426   volatile int U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIy = 0xCAFE;
427   if (kReallyDie) {
428     CHECK_EQ(U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIx, U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIy)
429         << "U3RyaXBwaW5nVGVzdC5DaGVja09w";
430   }
431 
432   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
433   ASSERT_THAT(exe, NotNull());
434 
435   if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
436     EXPECT_THAT(exe.get(), FileHasSubstr(var_needle1));
437     EXPECT_THAT(exe.get(), FileHasSubstr(var_needle2));
438     EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
439   } else {
440     EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle1)));
441     EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle2)));
442     EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
443   }
444 }
445 
TEST_F(StrippingTest,CheckStrOp)446 TEST_F(StrippingTest, CheckStrOp) {
447   // See `StrippingTest.Check` for some hairy implementation notes.
448   const std::string var_needle1 =
449       absl::Base64Escape("StrippingTestCheckStrOpVar1");
450   const std::string var_needle2 =
451       absl::Base64Escape("StrippingTestCheckStrOpVar2");
452   const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckStrOp");
453   const char *volatile U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIx = "FEED";
454   const char *volatile U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIy = "CAFE";
455   if (kReallyDie) {
456     CHECK_STREQ(U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIx,
457                 U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIy)
458         << "U3RyaXBwaW5nVGVzdC5DaGVja1N0ck9w";
459   }
460 
461   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
462   ASSERT_THAT(exe, NotNull());
463 
464   if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
465     EXPECT_THAT(exe.get(), FileHasSubstr(var_needle1));
466     EXPECT_THAT(exe.get(), FileHasSubstr(var_needle2));
467     EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
468   } else {
469     EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle1)));
470     EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle2)));
471     EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
472   }
473 }
474 
TEST_F(StrippingTest,CheckOk)475 TEST_F(StrippingTest, CheckOk) {
476   // See `StrippingTest.Check` for some hairy implementation notes.
477   const std::string var_needle = absl::Base64Escape("StrippingTestCheckOkVar1");
478   const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckOk");
479   volatile bool x = false;
480   auto U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx = absl::OkStatus();
481   if (x) {
482     U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx =
483         absl::InvalidArgumentError("Stripping this is not my job!");
484   }
485   if (kReallyDie) {
486     CHECK_OK(U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx)
487         << "U3RyaXBwaW5nVGVzdC5DaGVja09r";
488   }
489 
490   std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable();
491   ASSERT_THAT(exe, NotNull());
492 
493   if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) {
494     EXPECT_THAT(exe.get(), FileHasSubstr(var_needle));
495     EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle));
496   } else {
497     EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle)));
498     EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle)));
499   }
500 }
501 
502 }  // namespace
503