FOSSology  4.7.1
Open Source License Compliance by Open Source Software
test_scanners.cc
1 /*
2  SPDX-FileCopyrightText: © 2014-15, 2018 Siemens AG
3 
4  SPDX-License-Identifier: GPL-2.0-only
5 */
6 
7 #include <cppunit/TestFixture.h>
8 #include <cppunit/extensions/HelperMacros.h>
9 
10 #include "regex.hpp"
11 #include "regscan.hpp"
12 #include "copyrightUtils.hpp"
13 #include "cleanEntries.hpp"
14 #include <list>
15 #include <cstring>
16 #include <ostream>
17 
18 using namespace std;
19 
25 ostream& operator<<(ostream& out, const list<match>& l)
26 {
27  for (auto m = l.begin(); m != l.end(); ++m)
28  out << '[' << m->start << ':' << m->end << ':' << m->type << ']';
29  return out;
30 }
31 
35 const char testContent[] = "© 2007 Hugh Jackman\n\n"
36  "Copyright 2004 my company\n\n"
37  "Copyrights by any strange people\n\n"
38  "(C) copyright 2007-2011, 2013 my favourite company Google\n\n"
39  "(C) 2007-2011, 2013 my favourite company Google\n\n"
40  "if (c) { return -1 } \n\n"
41  "Written by: me, myself and Irene.\n\n"
42  "Authors all the people at ABC\n\n"
43  "<author>Author1</author>"
44  "<head>All the people</head>"
45  "<author>Author1 Author2 Author3</author>"
46  "<author>Author4</author><b>example</b>"
47  "Apache\n\n"
48  "This file is protected under pants 1 , 2 ,3\n\n"
49  "Do not modify this document\n\n"
50  "the shuttle is a space vehicle designed by NASA\n\n"
51  "visit http://mysite.org/FAQ or write to info@mysite.org\n\n"
52  "maintained by benjamin drieu <benj@debian.org>\n\n"
53  "* Copyright (c) 1989, 1993\n" // Really just one newline here!
54  "* The Regents of the University of California. All rights reserved.\n\n"
55  "to be licensed as a whole"
56  "/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */";
57 
58 class scannerTestSuite : public CPPUNIT_NS :: TestFixture {
59  CPPUNIT_TEST_SUITE (scannerTestSuite);
60  CPPUNIT_TEST (copyscannerTest);
61  CPPUNIT_TEST (copyscannerDotPrefixedNameTest);
62  CPPUNIT_TEST (copyscannerBracketedYearTest);
63  CPPUNIT_TEST (copyscannerBracketedYearNoiseTest);
64  CPPUNIT_TEST (copyscannerBracketedYearBoundariesTest);
65  CPPUNIT_TEST (copyscannerBareKeywordDiscardTest);
66  CPPUNIT_TEST (copyscannerCopyrightedStatementTest);
67  CPPUNIT_TEST (copyscannerCamelCaseHolderTest);
68  CPPUNIT_TEST (copyscannerBinaryNoiseTest);
69  CPPUNIT_TEST (copyscannerSpdxFullLineTest);
70  CPPUNIT_TEST (copyscannerSpdxArrayTest);
71  CPPUNIT_TEST (copyscannerProseExceptionTest);
72  CPPUNIT_TEST (regAuthorTest);
73  CPPUNIT_TEST (regIpraTest);
74  CPPUNIT_TEST (regEccTest);
75  CPPUNIT_TEST (regUrlTest);
76  CPPUNIT_TEST (regEmailTest);
77  CPPUNIT_TEST (regKeywordTest);
78  CPPUNIT_TEST (cleanEntries);
79 
80  CPPUNIT_TEST_SUITE_END ();
81 
82 private:
90  void scannerTest (const scanner& sc, const char* content, const string& type, list<const char*> expectedStrings)
91  {
92  list<match> matches;
93  list<match> expected;
94  sc.ScanString(content, matches);
95 
96  for (auto s = expectedStrings.begin(); s != expectedStrings.end(); ++s)
97  {
98  const char * p = strstr(content, *s);
99  if (p)
100  {
101  int pos = p - content;
102  expected.push_back(match(pos, pos+strlen(*s), type));
103  }
104  // else: expected string is not contained in original string
105  }
106  CPPUNIT_ASSERT_EQUAL(expected, matches);
107  }
108 
109 protected:
118  {
119  // Test copyright matcher
121 
122  scannerTest(sc, testContent, "statement", { "© 2007 Hugh Jackman",
123  "Copyright 2004 my company",
124  "Copyrights by any strange people",
125  "(C) copyright 2007-2011, 2013 my favourite company Google",
126  "(C) 2007-2011, 2013 my favourite company Google",
127  "Copyright (c) 1989, 1993\n* The Regents of the University of California."
128  });
129  }
130 
136  {
138  // © followed by a dot-prefixed name (.NET) must be fully preserved
139  const char* content1 = "Copyright \xc2\xa9 .NET Foundation and contributors\n";
140  scannerTest(sc, content1, "statement",
141  {"Copyright \xc2\xa9 .NET Foundation and contributors"});
142 
143  // Standalone © (no 'copyright' keyword) with dot-prefixed name
144  const char* content2 = "\xc2\xa9 .NET Foundation and contributors\n";
145  scannerTest(sc, content2, "statement",
146  {"\xc2\xa9 .NET Foundation and contributors"});
147 
148  // Year-qualified form must still work
149  const char* content3 = "Copyright \xc2\xa9 2021 .NET Foundation\n";
150  scannerTest(sc, content3, "statement",
151  {"Copyright \xc2\xa9 2021 .NET Foundation"});
152  }
153 
159  {
161  const char* valid[] = {
162  "Copyright (c) [2021] [Marvin Countryman]",
163  "Copyright (C) [1999] Marvin Countryman",
164  "Copyright (c)[2021] [Marvin Countryman]",
165  "Copyright (c)\t[ 2021 ] [Marvin Countryman]",
166  "Copyright (c) [\t2021\t] [Marvin Countryman]",
167  "Copyright \xc2\xa9 [2021] [Marvin Countryman]",
168  "Copyright &copy; [2021] [Marvin Countryman]",
169  "Copyrighted (C) [2021] [Marvin Countryman]",
170  "Copyright (c) 2021 [Marvin Countryman]",
171  "Copyright [2021] [Marvin Countryman]",
172  "Copyright (c) 2021 Marvin Countryman"
173  };
174 
175  for (const char* statement : valid)
176  {
177  for (const char* ending : {"", "\n"})
178  {
179  list<match> matches;
180  sc.ScanString(string(statement) + ending, matches);
181  const string message = string("Expected full active match for: ") + statement;
182  CPPUNIT_ASSERT_EQUAL_MESSAGE(message, (size_t)1, matches.size());
183  const match& finding = matches.front();
184  CPPUNIT_ASSERT_MESSAGE(message, finding.is_enabled);
185  CPPUNIT_ASSERT_EQUAL_MESSAGE(message, string("statement"), finding.type);
186  CPPUNIT_ASSERT_EQUAL_MESSAGE(message, 0, finding.start);
187  CPPUNIT_ASSERT_EQUAL_MESSAGE(message, (int)strlen(statement), finding.end);
188  }
189  }
190  }
191 
197  {
199  const char* noise[] = {
200  "Copyright (c) [year] [name]\n",
201  "Copyright (c) [yyyy] [name]\n",
202  "Copyright (c) [Marvin Countryman]\n",
203  "Copyright (c) [c] [name]\n",
204  "Copyright (c) [] [name]\n",
205  "Copyright (c) [202] [name]\n",
206  "Copyright (c) [20210] [name]\n",
207  "Copyright (c) [2021suffix] [name]\n",
208  "Copyright (c) [2021 [name]\n",
209  "Copyright (c) [[2021]] [name]\n",
210  "Copyright (c) {year} {name}\n",
211  "Copyright (c) <year> <name>\n",
212  "Copyright (c) $year $name\n",
213  "Copyright (c) %year% %name%\n"
214  };
215 
216  for (const char* statement : noise)
217  {
218  list<match> matches;
219  sc.ScanString(statement, matches);
220  for (const auto& finding : matches)
221  CPPUNIT_ASSERT_MESSAGE(
222  string("Expected no active match for: ") + statement,
223  !finding.is_enabled);
224  }
225  }
226 
232  {
234  const char* content =
235  "// Copyright (c) [2021] [Marvin Countryman]. All rights reserved.\n"
236  "// Copyright (c) 2022 Another Company\n";
237  scannerTest(sc, content, "statement", {
238  "Copyright (c) [2021] [Marvin Countryman].",
239  "Copyright (c) 2022 Another Company"
240  });
241 
242  list<match> matches;
243  sc.ScanString(content, matches);
244  for (const auto& finding : matches)
245  CPPUNIT_ASSERT_MESSAGE("Both copyright statements must stay active",
246  finding.is_enabled);
247  }
248 
254  {
256 
257  // All bare-keyword variants must produce NO match
258  const char* bare[] = {
259  "copyright\n",
260  "Copyright\n",
261  "COPYRIGHT\n",
262  "Copyrights\n",
263  nullptr
264  };
265  for (int i = 0; bare[i]; ++i)
266  {
267  list<match> matches;
268  sc.ScanString(bare[i], matches);
269  CPPUNIT_ASSERT_MESSAGE(
270  string("Expected no match for bare keyword: ") + bare[i],
271  matches.empty());
272  }
273 
274  // Strings with real content must still produce exactly one match each
275  const char* valid[] = {
276  "Copyright 2021 .NET Foundation\n",
277  "Copyright (c) 2004 My Company\n",
278  "Copyright \xc2\xa9 .NET Foundation and contributors\n",
279  nullptr
280  };
281  for (int i = 0; valid[i]; ++i)
282  {
283  list<match> matches;
284  sc.ScanString(valid[i], matches);
285  CPPUNIT_ASSERT_MESSAGE(
286  string("Expected one match for valid copyright: ") + valid[i],
287  matches.size() == 1);
288  }
289  }
290 
297  {
299 
300  // All must produce exactly one active match
301  const char* valid[] = {
302  "Copyrighted (C) 1994 Normunds Saumanis (normunds@rx.tech.swh.lv)\n",
303  "Copyrighted (C) 1994, 1995, 1996 Normunds Saumanis (normunds@fi.ibm.com)\n",
304  "copyrighted (C) 1993 by Hartmut Schirmer\n",
305  "copyrighted 1992 by Mark Adler version c10p1, 10 January 1993\n",
306  "copyrighted 1990 Mark Adler\n",
307  "Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)\n",
308  "Copyright (C) Torres Martinez\n",
309  "Copyright (C) Tobias Schmidt\n",
310  "Copyright (C) Tomas Novak\n",
311  nullptr
312  };
313  for (int i = 0; valid[i]; ++i)
314  {
315  list<match> matches;
316  sc.ScanString(valid[i], matches);
317  // Must have at least one match and every match must be active
318  CPPUNIT_ASSERT_MESSAGE(
319  string("Expected active match for: ") + valid[i],
320  !matches.empty() && matches.front().is_enabled);
321  }
322  }
323 
334  {
336 
337  // Holder names beginning with "to" in any casing must stay active
338  const char* holders[] = {
339  "Copyright TOSHIBA CORPORATION, 2022. Part of the SW360 Portal Project.\n",
340  "Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2022. Part of the SW360 Portal Project.\n",
341  "Copyright (C) TOSHIBA CORPORATION, 2025. Part of the SW360 Frontend Project.\n",
342  "Copyright (C) Toshiba Software Development (Vietnam) Co., Ltd., 2025. Part of the SW360 Frontend Project.\n",
343  "Copyright TOSHIBA TEC CORPORATION, 2023\n",
344  "Copyright TORONTO DOMINION BANK, 2019\n",
345  "Copyright TOYOTA MOTOR CORPORATION, 2018\n",
346  "Copyright TOMTOM INTERNATIONAL BV, 2021\n",
347  nullptr
348  };
349  for (int i = 0; holders[i]; ++i)
350  {
351  list<match> matches;
352  sc.ScanString(holders[i], matches);
353  CPPUNIT_ASSERT_MESSAGE(
354  string("Expected active match for holder: ") + holders[i],
355  !matches.empty() && matches.front().is_enabled);
356  }
357 
358  // Genuine camelCase identifiers must still be deactivated
359  const char* identifiers[] = {
360  "Copyright.ToString() returns the assembly attribute\n",
361  "copyright.ToArray().Select(x => x.Holder)\n",
362  "Copyright ToUpperInvariant helper for holder names\n",
363  nullptr
364  };
365  for (int i = 0; identifiers[i]; ++i)
366  {
367  list<match> matches;
368  sc.ScanString(identifiers[i], matches);
369  bool hasActive = !matches.empty() && matches.front().is_enabled;
370  CPPUNIT_ASSERT_MESSAGE(
371  string("Expected no active match for identifier: ") + identifiers[i],
372  !hasActive);
373  }
374  }
375 
382  {
384 
385  // Each string is © + short ASCII run + non-ASCII noise.
386  // None must produce an active match.
387  const char* noise[] = {
388  // 3-char ASCII prefix then non-ASCII
389  "\xc2\xa9 sjw\xc2\xa8noise\n",
390  "\xc2\xa9 OMr\xc2\xa5more\n",
391  "\xc2\xa9 tGa\xc3\x89garbage\n",
392  // 4-char ASCII prefix then non-ASCII
393  "\xc2\xa9 ZgU,\xc2\xb5garbage\n",
394  "\xc2\xa9 VJs0\xc3\x93noise\n",
395  // 7-char ASCII prefix then non-ASCII
396  "\xc2\xa9 NuXnHl{\xc2\xa4" "noise\n",
397  // 8-char ASCII prefix then non-ASCII
398  "\xc2\xa9 KtCtdy\x22s\xc3\xa8noise\n",
399  nullptr
400  };
401 
402  for (int i = 0; noise[i]; ++i)
403  {
404  list<match> matches;
405  sc.ScanString(noise[i], matches);
406  bool hasActive = !matches.empty() && matches.front().is_enabled;
407  CPPUNIT_ASSERT_MESSAGE(
408  string("Expected no active match for binary noise string #") + to_string(i),
409  !hasActive);
410  }
411  }
412 
419  {
421 
422  // Normal closed array: 1 deactivated header + 3 active elements
423  {
424  const char content[] =
425  "SPDX-FileCopyrightText = [\n"
426  "\"2026 Fraunhofer-Institut f\xC3\xBCr Produktionstechnik und Automatisierung IPA\",\n"
427  "\"2026 Hilscher Gesellschaft f\xC3\xBCr Systemautomation mbH\",\n"
428  "\"2026 Siemens AG\",\n"
429  "]\n";
430 
431  list<match> matches;
432  sc.ScanString(content, matches);
433 
434  CPPUNIT_ASSERT_EQUAL_MESSAGE("Expected 4 matches total", (size_t)4, matches.size());
435 
436  auto it = matches.begin();
437  CPPUNIT_ASSERT_MESSAGE("Header must be inactive", !it->is_enabled);
438  ++it;
439  for (int i = 1; i <= 3; ++i, ++it)
440  CPPUNIT_ASSERT_MESSAGE(
441  string("Array element ") + to_string(i) + " must be active", it->is_enabled);
442  }
443 
444  // Malformed array (no closing ]): scan must continue past the block and
445  // still detect copyright statements that follow it.
446  {
447  const char content[] =
448  "SPDX-FileCopyrightText = [\n"
449  "\"2026 Company A\",\n"
450  "\"2026 Company B\",\n"
451  // no closing ]
452  "Copyright 2021 Google LLC\n";
453 
454  list<match> matches;
455  sc.ScanString(content, matches);
456 
457  bool foundGoogle = false;
458  for (auto& m : matches) {
459  int len = m.end - m.start;
460  if (len > 0 && strncmp(content + m.start, "Copyright 2021 Google", 21) == 0)
461  foundGoogle = true;
462  }
463  CPPUNIT_ASSERT_MESSAGE(
464  "Copyright after unclosed SPDX array must still be detected", foundGoogle);
465  }
466  }
467 
475  {
477 
478  // Two lines with German umlauts (ü = \xC3\xBC), one plain ASCII line.
479  const char content[] =
480  "// SPDX-FileCopyrightText: 2026 Fraunhofer-Institut f\xC3\xBCr Produktionstechnik und Automatisierung IPA\n"
481  "// SPDX-FileCopyrightText: 2026 Hilscher Gesellschaft f\xC3\xBCr Systemautomation mbH\n"
482  "// SPDX-FileCopyrightText: 2026 Siemens AG\n";
483 
484  list<match> matches;
485  sc.ScanString(content, matches);
486 
487  CPPUNIT_ASSERT_EQUAL_MESSAGE("Expected 3 SPDX matches", (size_t)3, matches.size());
488 
489  // Verify each match is active and its end position reaches the line's '\n'
490  const char* lineStarts[3];
491  lineStarts[0] = strstr(content, "SPDX-FileCopyrightText: 2026 Fraunhofer");
492  lineStarts[1] = strstr(content, "SPDX-FileCopyrightText: 2026 Hilscher");
493  lineStarts[2] = strstr(content, "SPDX-FileCopyrightText: 2026 Siemens");
494 
495  int i = 0;
496  for (auto& m : matches)
497  {
498  CPPUNIT_ASSERT_MESSAGE(
499  string("Match ") + to_string(i) + " must be active",
500  m.is_enabled);
501 
502  int expectedStart = lineStarts[i] - content;
503  int expectedEnd = (int)(strchr(lineStarts[i], '\n') - content);
504 
505  CPPUNIT_ASSERT_EQUAL_MESSAGE(
506  string("Match ") + to_string(i) + " start",
507  expectedStart, m.start);
508  CPPUNIT_ASSERT_EQUAL_MESSAGE(
509  string("Match ") + to_string(i) + " end must reach line end",
510  expectedEnd, m.end);
511  ++i;
512  }
513  }
514 
529  {
531 
532  // All must produce NO active match (either deactivated or discarded)
533  const char* prose[] = {
534  "copyrights appearing in this test file\n",
535  "copyrights appears in the documentation\n",
536  "COPYRIGHT TO DETECT This section uses the standard header\n",
537  "copyright work that can be distributed\n",
538  "copyright protection under the terms of this License\n",
539  "copyrighted interfaces, the original copyright holder\n",
540  "copyright in the work, if the License is applied\n",
541  "copyright of this Package, but belong to whoever generated\n",
542  "copyright on material distributed under this License\n",
543  nullptr
544  };
545  for (int i = 0; prose[i]; ++i)
546  {
547  list<match> matches;
548  sc.ScanString(prose[i], matches);
549  bool hasActive = !matches.empty() && matches.front().is_enabled;
550  CPPUNIT_ASSERT_MESSAGE(
551  string("Expected no active match for prose: ") + prose[i],
552  !hasActive);
553  }
554 
555  // These must remain KEPT — year or (C)+year acts as holder separator
556  const char* valid[] = {
557  "Copyright (C) 2021 Toronto Inc.\n",
558  "Copyright 2021 Workday Inc.\n",
559  "Copyright (C) 2021 Interface Logic Inc.\n",
560  "Copyright (C) 2021 In-N-Out Burgers\n",
561  nullptr
562  };
563  for (int i = 0; valid[i]; ++i)
564  {
565  list<match> matches;
566  sc.ScanString(valid[i], matches);
567  CPPUNIT_ASSERT_MESSAGE(
568  string("Expected active match for: ") + valid[i],
569  !matches.empty() && matches.front().is_enabled);
570  }
571  }
572 
581  {
582  regexScanner sc("author", "copyright");
583  scannerTest(sc, testContent, "author", {
584  "Written by: me, myself and Irene.",
585  "Authors all the people at ABC",
586  "Author1",
587  "Author1 Author2 Author3",
588  "Author4",
589  "maintained by benjamin drieu <benj@debian.org>"
590  });
591  }
592 
600  void regIpraTest () {
601  regexScanner sc("ipra", "ipra");
602  scannerTest(sc, testContent, "ipra", { "US patents 1 , 2 ,3" });
603  }
604 
612  void regEccTest () {
613  regexScanner sc("ecc", "ecc");
614  scannerTest(sc, testContent, "ecc", { "space vehicle designed by NASA" });
615  }
616 
624  void regUrlTest () {
625  regexScanner sc("url", "copyright");
626  scannerTest(sc, testContent, "url", { "http://mysite.org/FAQ" });
627  }
628 
636  void regEmailTest () {
637  regexScanner sc("email", "copyright",1);
638  scannerTest(sc, testContent, "email", { "info@mysite.org", "benj@debian.org" });
639  }
640 
648  void regKeywordTest () {
649  regexScanner sc("keyword", "keyword");
650  scannerTest(sc, testContent, "keyword", {"patent", "licensed as", "stolen from"});
651  }
652 
661  void cleanEntries () {
662  // Binary content
663  string actualFileContent;
664  ReadFileToString("../testdata/testdata142", actualFileContent);
665 
666  vector<string> binaryStrings;
667  std::stringstream *ss = new std::stringstream(actualFileContent);
668  string temp;
669 
670  while (std::getline(*ss, temp)) {
671  binaryStrings.push_back(temp);
672  }
673 
674  // Simulate matches. Each line is a match
675  vector<match> matches;
676  int pos = 0;
677  int size = binaryStrings.size();
678  for (int i = 0; i < size; i++)
679  {
680  int length = binaryStrings[i].length();
681  matches.push_back(
682  match(pos, pos + length, "statement"));
683  pos += length + 1;
684  }
685 
686  // Expected data
687  string expectedFileContent;
688  ReadFileToString("../testdata/testdata142_exp", expectedFileContent);
689 
690  delete(ss);
691  ss = new std::stringstream(expectedFileContent);
692  vector<string> expectedStrings;
693  while (std::getline(*ss, temp)) {
694  expectedStrings.push_back(temp);
695  }
696 
697  vector<string> actualStrings;
698  for (size_t i = 0; i < matches.size(); i ++)
699  {
700  actualStrings.push_back(cleanMatch(actualFileContent, matches[i]));
701  }
702 
703  CPPUNIT_ASSERT(expectedStrings == actualStrings);
704  }
705 };
706 
707 CPPUNIT_TEST_SUITE_REGISTRATION( scannerTestSuite );
Implementation of scanner class for copyright.
Definition: copyscan.hpp:39
void ScanString(const string &s, list< match > &results) const
Scan a given string for copyright statements.
Definition: copyscan.cc:63
Provides a regex scanner using predefined regexs.
Definition: regscan.hpp:21
void copyscannerBracketedYearTest()
Preserve bracketed numeric years after a copyright symbol.
void copyscannerCamelCaseHolderTest()
Regression: REG_EXCEPTION_COPY carries a To[A-Z][a-zA-Z]{4,} branch to skip camelCase identifiers suc...
void copyscannerBracketedYearBoundariesTest()
Preserve offsets and separation from the following statement.
void regIpraTest()
Test Ipra scanner.
void copyscannerCopyrightedStatementTest()
Regression: "copyrighted" statements and names like "Tom" must not be falsely deactivated by REG_EXCE...
void copyscannerBinaryNoiseTest()
Regression: binary-file content with a short ASCII prefix before non-ASCII bytes must not be reported...
void regKeywordTest()
Test copyright scanner for keywords.
void copyscannerSpdxFullLineTest()
Regression: SPDX-FileCopyrightText entries must be detected as individual single-line statements and ...
void scannerTest(const scanner &sc, const char *content, const string &type, list< const char * > expectedStrings)
Runs scanner on content and check matches against expectedStrings.
void copyscannerBareKeywordDiscardTest()
Test that bare copyright keywords produce no matches.
void copyscannerDotPrefixedNameTest()
Test copyright scanner with dot-prefixed names like .NET Foundation.
void regUrlTest()
Test copyright scanner for URL.
void regEmailTest()
Test copyright scanner for email.
void copyscannerTest()
Test copyright scanner.
void copyscannerBracketedYearNoiseTest()
Brackets must not exempt placeholders or noise from cleanup.
void regEccTest()
Test ECC scanner.
void copyscannerProseExceptionTest()
Regression: license-prose strings that contain "copyright" as a common noun must be deactivated,...
void regAuthorTest()
Test copyright scanner for author.
void cleanEntries()
Test cleanMatch() to remove non-UTF8 text and extra spaces.
void copyscannerSpdxArrayTest()
Regression: SPDX-FileCopyrightText = [...] TOML array format must yield one active match per quoted e...
Abstract class to provide interface to scanners.
Definition: scanners.hpp:59
virtual void ScanString(const string &s, list< match > &results) const =0
Scan the given string and add matches to results.
int s
The socket that the CLI will use to communicate.
Definition: fo_cli.c:37
int valid
If the information stored in buffer is valid.
bool ReadFileToString(const string &fileName, string &out)
Utility: read file to string from scanners.h.
Definition: scanners.cc:21
Store the results of a regex match.
Definition: scanners.hpp:28
const int start
Definition: scanners.hpp:35
const int end
Definition: scanners.hpp:35
bool is_enabled
Definition: scanners.hpp:46
std::ostream & operator<<(std::ostream &os, const std::vector< int > &x)
<< operator overload to appends a vector to an ostream object
Definition: testUtils.hpp:27