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
48 changes: 48 additions & 0 deletions tests/copyright_check_untracked_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,54 @@ def git(repo, *args):
rc = run(repo, "--check")
check(rc.returncode == 0, "and the check is green afterwards", rc.stdout + rc.stderr)

# engine#2175: a SHORT header (Copyright + SPDX, no licence block) is not a "missing copyright notice".
# The message has to name the shape, or a reader hunts for a notice that is on line 1 -- measured on
# #2175/#2173 whose audio headers kept only the two lines while #2133 carried the full block.
short = NOTICE.splitlines()[:2]
with open(os.path.join(repo, "short.h"), "w") as f:
f.write("\n".join(short) + "\n// short.h -- the file's own description\n")
git(repo, "add", "short.h")
rc = run(repo, "--check")
check(rc.returncode != 0, "a SHORT header still fails (the licence block is required)", rc.stdout)
check("copyright + SPDX present but the licence block is not" in rc.stdout,
"and the failure names the SHAPE, not a missing notice", rc.stdout + rc.stderr)
check("missing copyright notice: short.h" not in rc.stdout,
"and does NOT claim the notice is missing", rc.stdout + rc.stderr)
# engine#2175: --fix must REPLACE the stub, not stack the block above it. Before this it left both, so the
# file came out with the Copyright/SPDX pair twice, the duplicate rule fired, and --check stayed red -- the
# tool's own remedy could not fix the shape it had just named.
rc = run(repo, "--fix")
check(rc.returncode == 0, "--fix repairs a short header", rc.stdout + rc.stderr)
got = open(os.path.join(repo, "short.h")).read()
check(got.count("SPDX-License-Identifier") == 1,
"and exactly one SPDX line survives the repair", f"{got.count('SPDX-License-Identifier')} found")
check("Licensed under the Apache License, Version 2.0" in got,
"and the full licence block is there", got[:200])
check("the file's own description" in got, "and the file's own header text is kept", got)
rc = run(repo, "--check")
check(rc.returncode == 0, "and --check is green after the repair", rc.stdout + rc.stderr)
# engine#2175 review: a script's short header sits BEHIND its `#!`. The first version of strip_short_header()
# looked at line 1 only, so the stub was not found, the block went in after the shebang, and the file ended up
# with two notices -- the same defect one file type over.
with open(os.path.join(repo, "short.sh"), "w") as f:
f.write("#!/usr/bin/env bash\n" + NOTICE.splitlines()[0] + "\n" + NOTICE.splitlines()[1]
+ "\n# short.sh -- a script\necho hi\n")
git(repo, "add", "short.sh")
rc = run(repo, "--fix")
check(rc.returncode == 0, "--fix repairs a short header behind a shebang", rc.stdout + rc.stderr)
got = open(os.path.join(repo, "short.sh")).read()
check(got.startswith("#!/usr/bin/env bash\n"),
"the shebang stays on line 1", got.splitlines()[0])
check(got.count("SPDX-License-Identifier") == 1,
"and exactly one SPDX line in the script too", f"{got.count('SPDX-License-Identifier')} found")
check("short.sh -- a script" in got, "and the script's own comment survives", got)
rc = run(repo, "--check")
check(rc.returncode == 0, "and --check is green for the script", rc.stdout + rc.stderr)
os.unlink(os.path.join(repo, "short.sh"))
git(repo, "add", "-A", "short.sh")
os.unlink(os.path.join(repo, "short.h"))
git(repo, "add", "-A", "short.h")

# an ignored file is not source: it must not need a notice. The .gitignore itself is source (the
# repo's own carries the notice and is tracked), so it gets one here too.
with open(os.path.join(repo, ".gitignore"), "w") as f:
Expand Down
22 changes: 21 additions & 1 deletion tools/copyright.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,21 @@ def has_notice(text):
return NOTICE[0] in head and NOTICE[1] in head and "Licensed under the Apache License, Version 2.0" in head


def short_notice(text):
"""True when the header carries Copyright + SPDX but not the licence block.

engine#2175: `notice_block()` returns the 16-line form for a known style, so a header that keeps only
`// Copyright ...` and `// SPDX-License-Identifier: Apache-2.0` and then jumps to the file's own
description reads as MISSING -- and the message says "missing copyright notice", which sends the reader
looking for a notice that is on line 1. Measured on engine#2175 / #2173 (their audio headers) against
#2133 (the same work, full block): copyright present, SPDX present, "Licensed under the Apache License"
absent. Naming the shape is the difference between a 15-line paste and a hunt.
"""
head = "\n".join(text.splitlines()[:NOTICE_WINDOW])
return (NOTICE[0] in head and NOTICE[1] in head
and "Licensed under the Apache License, Version 2.0" not in head)


def duplicated_notice(text):
"""True when the Copyright + SPDX pair appears twice inside the header window.

Expand Down Expand Up @@ -367,7 +382,12 @@ def listing(*args):
for path in unknown:
print("no comment style known (add one to LINE_STYLES/BLOCK_STYLES or EXEMPT):", path)
for path in missing:
print("missing copyright notice:", path)
# engine#2175: a short header is not a missing notice. Say which, so the fix is a paste rather than a
# search -- and keep the exit code the same (this is still a failure: the licence block is required).
if short_notice(open(path, encoding="utf-8", errors="replace").read()):
print("copyright + SPDX present but the licence block is not (paste the 16-line form):", path)
else:
print("missing copyright notice:", path)
if missing or unknown or duplicated:
print("run: python3 tools/copyright.py --fix", file=sys.stderr)
return 1
Expand Down
Loading