Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions tests/copyright_check_untracked_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,44 @@ def git(repo, *args):
check(rc.returncode != 0, "the same generated source TRACKED is still an error: it is repo content now",
rc.stdout + rc.stderr)

# --fix must not delete blank lines (#2183 review). The licence block's own blank comment lines reduce to the empty
# string, and canonical_notice_line() treated "" as a notice line. So add_notice() deleted every blank or bare `//`
# line in the header window: on #2183's 13 two-liner headers --fix gave +143 -68, dropping the separator after the
# description, the blank lines between #include groups and the one after `namespace {`. And repair_notice()'s
# doubled-block dedupe dropped every blank line after the first in the WHOLE file. Both cases compare the written
# file with the exact expected text, so a single lost line fails them.
CPP_NOTICE = "".join(("//" + line[1:]) for line in NOTICE.splitlines(keepends=True))
CPP_BODY = ("//\n// x.cpp -- what this file is for.\n//\n// A second paragraph.\n\n"
"#include <cstdio>\n#include <string>\n\n#include \"x.h\"\n\nnamespace {\n\nint g = 0;\n\n} // namespace\n")
with tempfile.TemporaryDirectory() as repo:
git(repo, "init", "-q")
path = os.path.join(repo, "twoliner.cpp")
with open(path, "w") as f:
f.write("// Copyright 2026 bong-water-water-bong\n// SPDX-License-Identifier: Apache-2.0\n" + CPP_BODY)
run(repo, "--fix")
with open(path) as f:
fixed = f.read()
check(fixed == CPP_NOTICE + CPP_BODY,
"--fix on a two-liner header keeps every blank and bare `//` line below it",
"lost %d blank line(s)" % (CPP_BODY.count("\n\n") - fixed.count("\n\n")))
rc = run(repo, "--check")
check(rc.returncode == 0, "...and the file it wrote passes --check", rc.stdout + rc.stderr)

with tempfile.TemporaryDirectory() as repo:
git(repo, "init", "-q")
path = os.path.join(repo, "nearmiss.cpp")
# blank lines well past the 20-line header window: the repair path reads the whole file
long_body = CPP_BODY + "".join("int v%d = %d;\n" % (i, i) for i in range(30)) + "\nint z;\n\nint w;\n"
with open(path, "w") as f:
f.write(CPP_NOTICE + "// Copyright 2026 bong-water-bong\n// SPDX-License-Identifier: Apache-2.0\n" + long_body)
run(repo, "--fix")
with open(path) as f:
fixed = f.read()
check(fixed == CPP_NOTICE + long_body,
"--fix repairing a near-miss holder keeps every blank line in the file, past the header window too",
"lost %d blank line(s)" % (long_body.count("\n\n") - fixed[len(CPP_NOTICE):].count("\n\n")))
rc = run(repo, "--check")
check(rc.returncode == 0, "...and the repaired file passes --check", rc.stdout + rc.stderr)

print(f"copyright_check_untracked_test: {len(FAIL)} failure(s)")
sys.exit(1 if FAIL else 0)
10 changes: 8 additions & 2 deletions tools/copyright.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,12 @@ def near_miss_holder(line):
return n >= HOLDER_NEAR_MISS_PREFIX


_CANON_LINES = {re.sub(r"^\W+", "", n).strip() for n in NOTICE}
# The licence block's own blank comment lines (`#`, `//`) reduce to "", so a set built from every NOTICE line held the
# empty string, and canonical_notice_line() said yes to every blank or bare-comment line in a file. add_notice() then
# deleted each one in the header window (the separator after the description, the blank lines between #include groups
# and after `namespace {`), and repair_notice()'s doubled-block dedupe deleted every blank line after the first in the
# whole file. Only lines with words in them can be a claim, so only those are canonical.
_CANON_LINES = {k for k in (re.sub(r"^\W+", "", n).strip() for n in NOTICE) if k}


def canonical_notice_line(line):
Expand All @@ -241,7 +246,8 @@ def canonical_notice_line(line):
cover it by construction: it returns False for an exact holder, because an exact holder next to the canonical block
is the *repaired* state. The block already contains both lines, so a copy inside the window is not a second claim.
"""
return re.sub(r"^\W+", "", line).strip() in _CANON_LINES
key = re.sub(r"^\W+", "", line).strip()
return bool(key) and key in _CANON_LINES


def repair_notice(text, block):
Expand Down
Loading