7 #include <cppunit/TestFixture.h>
8 #include <cppunit/extensions/HelperMacros.h>
11 #include "regscan.hpp"
12 #include "copyrightUtils.hpp"
13 #include "cleanEntries.hpp"
25 ostream&
operator<<(ostream& out,
const list<match>& l)
27 for (
auto m = l.begin(); m != l.end(); ++m)
28 out <<
'[' << m->start <<
':' << m->end <<
':' << m->type <<
']';
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>"
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"
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. */";
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);
80 CPPUNIT_TEST_SUITE_END ();
90 void scannerTest (
const scanner& sc,
const char* content,
const string& type, list<const char*> expectedStrings)
96 for (
auto s = expectedStrings.begin();
s != expectedStrings.end(); ++
s)
98 const char * p = strstr(content, *
s);
101 int pos = p - content;
102 expected.push_back(
match(pos, pos+strlen(*
s), type));
106 CPPUNIT_ASSERT_EQUAL(expected, matches);
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."
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"});
144 const char* content2 =
"\xc2\xa9 .NET Foundation and contributors\n";
145 scannerTest(sc, content2,
"statement",
146 {
"\xc2\xa9 .NET Foundation and contributors"});
149 const char* content3 =
"Copyright \xc2\xa9 2021 .NET Foundation\n";
150 scannerTest(sc, content3,
"statement",
151 {
"Copyright \xc2\xa9 2021 .NET Foundation"});
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 © [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"
175 for (
const char* statement :
valid)
177 for (
const char* ending : {
"",
"\n"})
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);
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"
216 for (
const char* statement : noise)
220 for (
const auto& finding : matches)
221 CPPUNIT_ASSERT_MESSAGE(
222 string(
"Expected no active match for: ") + statement,
223 !finding.is_enabled);
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"
244 for (
const auto& finding : matches)
245 CPPUNIT_ASSERT_MESSAGE(
"Both copyright statements must stay active",
258 const char* bare[] = {
265 for (
int i = 0; bare[i]; ++i)
269 CPPUNIT_ASSERT_MESSAGE(
270 string(
"Expected no match for bare keyword: ") + bare[i],
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",
281 for (
int i = 0;
valid[i]; ++i)
285 CPPUNIT_ASSERT_MESSAGE(
286 string(
"Expected one match for valid copyright: ") +
valid[i],
287 matches.size() == 1);
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",
313 for (
int i = 0;
valid[i]; ++i)
318 CPPUNIT_ASSERT_MESSAGE(
319 string(
"Expected active match for: ") +
valid[i],
320 !matches.empty() && matches.front().is_enabled);
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",
349 for (
int i = 0; holders[i]; ++i)
353 CPPUNIT_ASSERT_MESSAGE(
354 string(
"Expected active match for holder: ") + holders[i],
355 !matches.empty() && matches.front().is_enabled);
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",
365 for (
int i = 0; identifiers[i]; ++i)
369 bool hasActive = !matches.empty() && matches.front().is_enabled;
370 CPPUNIT_ASSERT_MESSAGE(
371 string(
"Expected no active match for identifier: ") + identifiers[i],
387 const char* noise[] = {
389 "\xc2\xa9 sjw\xc2\xa8noise\n",
390 "\xc2\xa9 OMr\xc2\xa5more\n",
391 "\xc2\xa9 tGa\xc3\x89garbage\n",
393 "\xc2\xa9 ZgU,\xc2\xb5garbage\n",
394 "\xc2\xa9 VJs0\xc3\x93noise\n",
396 "\xc2\xa9 NuXnHl{\xc2\xa4" "noise\n",
398 "\xc2\xa9 KtCtdy\x22s\xc3\xa8noise\n",
402 for (
int i = 0; noise[i]; ++i)
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),
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"
434 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"Expected 4 matches total", (
size_t)4, matches.size());
436 auto it = matches.begin();
437 CPPUNIT_ASSERT_MESSAGE(
"Header must be inactive", !it->is_enabled);
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);
447 const char content[] =
448 "SPDX-FileCopyrightText = [\n"
449 "\"2026 Company A\",\n"
450 "\"2026 Company B\",\n"
452 "Copyright 2021 Google LLC\n";
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)
463 CPPUNIT_ASSERT_MESSAGE(
464 "Copyright after unclosed SPDX array must still be detected", foundGoogle);
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";
487 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"Expected 3 SPDX matches", (
size_t)3, matches.size());
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");
496 for (
auto& m : matches)
498 CPPUNIT_ASSERT_MESSAGE(
499 string(
"Match ") + to_string(i) +
" must be active",
502 int expectedStart = lineStarts[i] - content;
503 int expectedEnd = (int)(strchr(lineStarts[i],
'\n') - content);
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",
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",
545 for (
int i = 0; prose[i]; ++i)
549 bool hasActive = !matches.empty() && matches.front().is_enabled;
550 CPPUNIT_ASSERT_MESSAGE(
551 string(
"Expected no active match for prose: ") + prose[i],
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",
563 for (
int i = 0;
valid[i]; ++i)
567 CPPUNIT_ASSERT_MESSAGE(
568 string(
"Expected active match for: ") +
valid[i],
569 !matches.empty() && matches.front().is_enabled);
583 scannerTest(sc, testContent,
"author", {
584 "Written by: me, myself and Irene.",
585 "Authors all the people at ABC",
587 "Author1 Author2 Author3",
589 "maintained by benjamin drieu <benj@debian.org>"
602 scannerTest(sc, testContent,
"ipra", {
"US patents 1 , 2 ,3" });
614 scannerTest(sc, testContent,
"ecc", {
"space vehicle designed by NASA" });
626 scannerTest(sc, testContent,
"url", {
"http://mysite.org/FAQ" });
638 scannerTest(sc, testContent,
"email", {
"info@mysite.org",
"benj@debian.org" });
650 scannerTest(sc, testContent,
"keyword", {
"patent",
"licensed as",
"stolen from"});
663 string actualFileContent;
666 vector<string> binaryStrings;
667 std::stringstream *ss =
new std::stringstream(actualFileContent);
670 while (std::getline(*ss, temp)) {
671 binaryStrings.push_back(temp);
675 vector<match> matches;
677 int size = binaryStrings.size();
678 for (
int i = 0; i < size; i++)
680 int length = binaryStrings[i].length();
682 match(pos, pos + length,
"statement"));
687 string expectedFileContent;
691 ss =
new std::stringstream(expectedFileContent);
692 vector<string> expectedStrings;
693 while (std::getline(*ss, temp)) {
694 expectedStrings.push_back(temp);
697 vector<string> actualStrings;
698 for (
size_t i = 0; i < matches.size(); i ++)
700 actualStrings.push_back(cleanMatch(actualFileContent, matches[i]));
703 CPPUNIT_ASSERT(expectedStrings == actualStrings);
Implementation of scanner class for copyright.
void ScanString(const string &s, list< match > &results) const
Scan a given string for copyright statements.
Provides a regex scanner using predefined regexs.
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.
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.
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.
Store the results of a regex match.
std::ostream & operator<<(std::ostream &os, const std::vector< int > &x)
<< operator overload to appends a vector to an ostream object