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 (copyscannerCopySymByHolderTest);
66 CPPUNIT_TEST (copyscannerBareKeywordDiscardTest);
67 CPPUNIT_TEST (copyscannerCopyrightedStatementTest);
68 CPPUNIT_TEST (copyscannerCamelCaseHolderTest);
69 CPPUNIT_TEST (copyscannerBinaryNoiseTest);
70 CPPUNIT_TEST (copyscannerSpdxFullLineTest);
71 CPPUNIT_TEST (copyscannerSpdxArrayTest);
72 CPPUNIT_TEST (copyscannerProseExceptionTest);
73 CPPUNIT_TEST (regAuthorTest);
74 CPPUNIT_TEST (regIpraTest);
75 CPPUNIT_TEST (regEccTest);
76 CPPUNIT_TEST (regUrlTest);
77 CPPUNIT_TEST (regEmailTest);
78 CPPUNIT_TEST (regEmailReservedDomainTest);
79 CPPUNIT_TEST (regKeywordTest);
80 CPPUNIT_TEST (cleanEntries);
82 CPPUNIT_TEST_SUITE_END ();
92 void scannerTest (
const scanner& sc,
const char* content,
const string& type, list<const char*> expectedStrings)
98 for (
auto s = expectedStrings.begin();
s != expectedStrings.end(); ++
s)
100 const char * p = strstr(content, *
s);
103 int pos = p - content;
104 expected.push_back(
match(pos, pos+strlen(*
s), type));
108 CPPUNIT_ASSERT_EQUAL(expected, matches);
124 scannerTest(sc, testContent,
"statement", {
"© 2007 Hugh Jackman",
125 "Copyright 2004 my company",
126 "Copyrights by any strange people",
127 "(C) copyright 2007-2011, 2013 my favourite company Google",
128 "(C) 2007-2011, 2013 my favourite company Google",
129 "Copyright (c) 1989, 1993\n* The Regents of the University of California."
141 const char* content1 =
"Copyright \xc2\xa9 .NET Foundation and contributors\n";
142 scannerTest(sc, content1,
"statement",
143 {
"Copyright \xc2\xa9 .NET Foundation and contributors"});
146 const char* content2 =
"\xc2\xa9 .NET Foundation and contributors\n";
147 scannerTest(sc, content2,
"statement",
148 {
"\xc2\xa9 .NET Foundation and contributors"});
151 const char* content3 =
"Copyright \xc2\xa9 2021 .NET Foundation\n";
152 scannerTest(sc, content3,
"statement",
153 {
"Copyright \xc2\xa9 2021 .NET Foundation"});
163 const char*
valid[] = {
164 "Copyright (c) [2021] [Marvin Countryman]",
165 "Copyright (C) [1999] Marvin Countryman",
166 "Copyright (c)[2021] [Marvin Countryman]",
167 "Copyright (c)\t[ 2021 ] [Marvin Countryman]",
168 "Copyright (c) [\t2021\t] [Marvin Countryman]",
169 "Copyright \xc2\xa9 [2021] [Marvin Countryman]",
170 "Copyright © [2021] [Marvin Countryman]",
171 "Copyrighted (C) [2021] [Marvin Countryman]",
172 "Copyright (c) 2021 [Marvin Countryman]",
173 "Copyright [2021] [Marvin Countryman]",
174 "Copyright (c) 2021 Marvin Countryman"
177 for (
const char* statement :
valid)
179 for (
const char* ending : {
"",
"\n"})
182 sc.
ScanString(
string(statement) + ending, matches);
183 const string message = string(
"Expected full active match for: ") + statement;
184 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, (
size_t)1, matches.size());
185 const match& finding = matches.front();
186 CPPUNIT_ASSERT_MESSAGE(message, finding.
is_enabled);
187 CPPUNIT_ASSERT_EQUAL_MESSAGE(message,
string(
"statement"), finding.type);
188 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, 0, finding.
start);
189 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, (
int)strlen(statement), finding.
end);
201 const char* noise[] = {
202 "Copyright (c) [year] [name]\n",
203 "Copyright (c) [yyyy] [name]\n",
204 "Copyright (c) [Marvin Countryman]\n",
205 "Copyright (c) [c] [name]\n",
206 "Copyright (c) [] [name]\n",
207 "Copyright (c) [202] [name]\n",
208 "Copyright (c) [20210] [name]\n",
209 "Copyright (c) [2021suffix] [name]\n",
210 "Copyright (c) [2021 [name]\n",
211 "Copyright (c) [[2021]] [name]\n",
212 "Copyright (c) {year} {name}\n",
213 "Copyright (c) <year> <name>\n",
214 "Copyright (c) $year $name\n",
215 "Copyright (c) %year% %name%\n"
218 for (
const char* statement : noise)
222 for (
const auto& finding : matches)
223 CPPUNIT_ASSERT_MESSAGE(
224 string(
"Expected no active match for: ") + statement,
225 !finding.is_enabled);
236 const char* content =
237 "// Copyright (c) [2021] [Marvin Countryman]. All rights reserved.\n"
238 "// Copyright (c) 2022 Another Company\n";
239 scannerTest(sc, content,
"statement", {
240 "Copyright (c) [2021] [Marvin Countryman].",
241 "Copyright (c) 2022 Another Company"
246 for (
const auto& finding : matches)
247 CPPUNIT_ASSERT_MESSAGE(
"Both copyright statements must stay active",
259 const char*
valid[] = {
260 "Copyright(c) by Mei Qingguang",
261 "Copyright (c) by Mei Qingguang",
262 "Copyright (C) by Heiko Eissfeldt",
263 "Copyright \xc2\xa9 by Mei Qingguang"
266 for (
const char* statement :
valid)
268 for (
const char* ending : {
"",
"\n"})
271 sc.
ScanString(
string(statement) + ending, matches);
272 const string message = string(
"Expected full active match for: ") + statement;
273 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, (
size_t)1, matches.size());
274 const match& finding = matches.front();
275 CPPUNIT_ASSERT_MESSAGE(message, finding.
is_enabled);
276 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, 0, finding.
start);
277 CPPUNIT_ASSERT_EQUAL_MESSAGE(message, (
int)strlen(statement), finding.
end);
283 "This software is licensed under the MIT License.\n\n"
284 "Copyright(c) by Mei Qingguang\n\n"
285 "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
286 "of this software and associated documentation files (the \"Software\"), to deal\n"
287 "in the Software without restriction.\n";
288 scannerTest(sc, mit,
"statement", {
"Copyright(c) by Mei Qingguang"});
291 const char* bare[] = {
"Copyright (c) by\n",
"(c) by\n"};
292 for (
const char* statement : bare)
296 for (
const auto& finding : matches)
297 CPPUNIT_ASSERT_MESSAGE(
298 string(
"Expected no active match for: ") + statement,
299 !finding.is_enabled);
312 const char* bare[] = {
319 for (
int i = 0; bare[i]; ++i)
323 CPPUNIT_ASSERT_MESSAGE(
324 string(
"Expected no match for bare keyword: ") + bare[i],
329 const char*
valid[] = {
330 "Copyright 2021 .NET Foundation\n",
331 "Copyright (c) 2004 My Company\n",
332 "Copyright \xc2\xa9 .NET Foundation and contributors\n",
335 for (
int i = 0;
valid[i]; ++i)
339 CPPUNIT_ASSERT_MESSAGE(
340 string(
"Expected one match for valid copyright: ") +
valid[i],
341 matches.size() == 1);
355 const char*
valid[] = {
356 "Copyrighted (C) 1994 Normunds Saumanis (normunds@rx.tech.swh.lv)\n",
357 "Copyrighted (C) 1994, 1995, 1996 Normunds Saumanis (normunds@fi.ibm.com)\n",
358 "copyrighted (C) 1993 by Hartmut Schirmer\n",
359 "copyrighted 1992 by Mark Adler version c10p1, 10 January 1993\n",
360 "copyrighted 1990 Mark Adler\n",
361 "Copyright (C) Tom Long Nguyen (tom.l.nguyen@intel.com)\n",
362 "Copyright (C) Torres Martinez\n",
363 "Copyright (C) Tobias Schmidt\n",
364 "Copyright (C) Tomas Novak\n",
367 for (
int i = 0;
valid[i]; ++i)
372 CPPUNIT_ASSERT_MESSAGE(
373 string(
"Expected active match for: ") +
valid[i],
374 !matches.empty() && matches.front().is_enabled);
392 const char* holders[] = {
393 "Copyright TOSHIBA CORPORATION, 2022. Part of the SW360 Portal Project.\n",
394 "Copyright Toshiba Software Development (Vietnam) Co., Ltd., 2022. Part of the SW360 Portal Project.\n",
395 "Copyright (C) TOSHIBA CORPORATION, 2025. Part of the SW360 Frontend Project.\n",
396 "Copyright (C) Toshiba Software Development (Vietnam) Co., Ltd., 2025. Part of the SW360 Frontend Project.\n",
397 "Copyright TOSHIBA TEC CORPORATION, 2023\n",
398 "Copyright TORONTO DOMINION BANK, 2019\n",
399 "Copyright TOYOTA MOTOR CORPORATION, 2018\n",
400 "Copyright TOMTOM INTERNATIONAL BV, 2021\n",
403 for (
int i = 0; holders[i]; ++i)
407 CPPUNIT_ASSERT_MESSAGE(
408 string(
"Expected active match for holder: ") + holders[i],
409 !matches.empty() && matches.front().is_enabled);
413 const char* identifiers[] = {
414 "Copyright.ToString() returns the assembly attribute\n",
415 "copyright.ToArray().Select(x => x.Holder)\n",
416 "Copyright ToUpperInvariant helper for holder names\n",
419 for (
int i = 0; identifiers[i]; ++i)
423 bool hasActive = !matches.empty() && matches.front().is_enabled;
424 CPPUNIT_ASSERT_MESSAGE(
425 string(
"Expected no active match for identifier: ") + identifiers[i],
441 const char* noise[] = {
443 "\xc2\xa9 sjw\xc2\xa8noise\n",
444 "\xc2\xa9 OMr\xc2\xa5more\n",
445 "\xc2\xa9 tGa\xc3\x89garbage\n",
447 "\xc2\xa9 ZgU,\xc2\xb5garbage\n",
448 "\xc2\xa9 VJs0\xc3\x93noise\n",
450 "\xc2\xa9 NuXnHl{\xc2\xa4" "noise\n",
452 "\xc2\xa9 KtCtdy\x22s\xc3\xa8noise\n",
456 for (
int i = 0; noise[i]; ++i)
460 bool hasActive = !matches.empty() && matches.front().is_enabled;
461 CPPUNIT_ASSERT_MESSAGE(
462 string(
"Expected no active match for binary noise string #") + to_string(i),
478 const char content[] =
479 "SPDX-FileCopyrightText = [\n"
480 "\"2026 Fraunhofer-Institut f\xC3\xBCr Produktionstechnik und Automatisierung IPA\",\n"
481 "\"2026 Hilscher Gesellschaft f\xC3\xBCr Systemautomation mbH\",\n"
482 "\"2026 Siemens AG\",\n"
488 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"Expected 4 matches total", (
size_t)4, matches.size());
490 auto it = matches.begin();
491 CPPUNIT_ASSERT_MESSAGE(
"Header must be inactive", !it->is_enabled);
493 for (
int i = 1; i <= 3; ++i, ++it)
494 CPPUNIT_ASSERT_MESSAGE(
495 string(
"Array element ") + to_string(i) +
" must be active", it->is_enabled);
501 const char content[] =
502 "SPDX-FileCopyrightText = [\n"
503 "\"2026 Company A\",\n"
504 "\"2026 Company B\",\n"
506 "Copyright 2021 Google LLC\n";
511 bool foundGoogle =
false;
512 for (
auto& m : matches) {
513 int len = m.end - m.start;
514 if (len > 0 && strncmp(content + m.start,
"Copyright 2021 Google", 21) == 0)
517 CPPUNIT_ASSERT_MESSAGE(
518 "Copyright after unclosed SPDX array must still be detected", foundGoogle);
533 const char content[] =
534 "// SPDX-FileCopyrightText: 2026 Fraunhofer-Institut f\xC3\xBCr Produktionstechnik und Automatisierung IPA\n"
535 "// SPDX-FileCopyrightText: 2026 Hilscher Gesellschaft f\xC3\xBCr Systemautomation mbH\n"
536 "// SPDX-FileCopyrightText: 2026 Siemens AG\n";
541 CPPUNIT_ASSERT_EQUAL_MESSAGE(
"Expected 3 SPDX matches", (
size_t)3, matches.size());
544 const char* lineStarts[3];
545 lineStarts[0] = strstr(content,
"SPDX-FileCopyrightText: 2026 Fraunhofer");
546 lineStarts[1] = strstr(content,
"SPDX-FileCopyrightText: 2026 Hilscher");
547 lineStarts[2] = strstr(content,
"SPDX-FileCopyrightText: 2026 Siemens");
550 for (
auto& m : matches)
552 CPPUNIT_ASSERT_MESSAGE(
553 string(
"Match ") + to_string(i) +
" must be active",
556 int expectedStart = lineStarts[i] - content;
557 int expectedEnd = (int)(strchr(lineStarts[i],
'\n') - content);
559 CPPUNIT_ASSERT_EQUAL_MESSAGE(
560 string(
"Match ") + to_string(i) +
" start",
561 expectedStart, m.start);
562 CPPUNIT_ASSERT_EQUAL_MESSAGE(
563 string(
"Match ") + to_string(i) +
" end must reach line end",
587 const char* prose[] = {
588 "copyrights appearing in this test file\n",
589 "copyrights appears in the documentation\n",
590 "COPYRIGHT TO DETECT This section uses the standard header\n",
591 "copyright work that can be distributed\n",
592 "copyright protection under the terms of this License\n",
593 "copyrighted interfaces, the original copyright holder\n",
594 "copyright in the work, if the License is applied\n",
595 "copyright of this Package, but belong to whoever generated\n",
596 "copyright on material distributed under this License\n",
599 for (
int i = 0; prose[i]; ++i)
603 bool hasActive = !matches.empty() && matches.front().is_enabled;
604 CPPUNIT_ASSERT_MESSAGE(
605 string(
"Expected no active match for prose: ") + prose[i],
610 const char*
valid[] = {
611 "Copyright (C) 2021 Toronto Inc.\n",
612 "Copyright 2021 Workday Inc.\n",
613 "Copyright (C) 2021 Interface Logic Inc.\n",
614 "Copyright (C) 2021 In-N-Out Burgers\n",
617 for (
int i = 0;
valid[i]; ++i)
621 CPPUNIT_ASSERT_MESSAGE(
622 string(
"Expected active match for: ") +
valid[i],
623 !matches.empty() && matches.front().is_enabled);
637 scannerTest(sc, testContent,
"author", {
638 "Written by: me, myself and Irene.",
639 "Authors all the people at ABC",
641 "Author1 Author2 Author3",
643 "maintained by benjamin drieu <benj@debian.org>"
656 scannerTest(sc, testContent,
"ipra", {
"US patents 1 , 2 ,3" });
668 scannerTest(sc, testContent,
"ecc", {
"space vehicle designed by NASA" });
680 scannerTest(sc, testContent,
"url", {
"http://mysite.org/FAQ" });
692 scannerTest(sc, testContent,
"email", {
"info@mysite.org",
"benj@debian.org" });
704 const char content[] =
705 "alice@example.com\n"
706 "<bob@example.net>\n"
707 "(carol@example.org)\n"
709 "erin@host.invalid\n"
710 "grace@sub.example.com\n"
711 "frank@realcompany.de\n"
712 "john@myexample.com\n"
713 "maria@example.community\n"
714 "Contact paul@kernel.org.\n"
717 scannerTest(sc, content,
"email", {
"frank@realcompany.de",
718 "john@myexample.com",
"maria@example.community",
"paul@kernel.org",
731 scannerTest(sc, testContent,
"keyword", {
"patent",
"licensed as",
"stolen from"});
744 string actualFileContent;
747 vector<string> binaryStrings;
748 std::stringstream *ss =
new std::stringstream(actualFileContent);
751 while (std::getline(*ss, temp)) {
752 binaryStrings.push_back(temp);
756 vector<match> matches;
758 int size = binaryStrings.size();
759 for (
int i = 0; i < size; i++)
761 int length = binaryStrings[i].length();
763 match(pos, pos + length,
"statement"));
768 string expectedFileContent;
772 ss =
new std::stringstream(expectedFileContent);
773 vector<string> expectedStrings;
774 while (std::getline(*ss, temp)) {
775 expectedStrings.push_back(temp);
778 vector<string> actualStrings;
779 for (
size_t i = 0; i < matches.size(); i ++)
781 actualStrings.push_back(cleanMatch(actualFileContent, matches[i]));
784 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 copyscannerCopySymByHolderTest()
A copyright symbol followed by "by <holder>" without a year must not be stripped down to a bare keywo...
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 regEmailReservedDomainTest()
Test email scanner skips reserved domains (RFC 2606)
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