Files
scst/scripts/tests/test_lint_scope.py
Gleb Chesnokov 1c455d4785 scripts: Validate analyzer diff bases as single commits
Passing the base directly to git diff allows an option such as --quiet to
be interpreted as a flag. On a clean worktree it can succeed without
selecting any sources, instead of rejecting the invalid revision.

Resolve the base with rev-parse --verify --end-of-options and require a
commit before building the diff command. Reuse the resulting object ID so
options, ranges, tree objects and unknown revisions cannot turn an invalid
request into an empty selection.

Add a fixture covering rejected bases on a clean tree and a valid tag
before and after a source change. Expose the resolver for future patch
consumers as well.
2026-10-19 19:18:14 +03:00

107 lines
5.0 KiB
Python

"""Exercise source selection against Git, including paths and renames."""
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
sys.dont_write_bytecode = True
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from lint_scope import changed_sources, git_pathspecs, in_scope, select_sources, tracked_sources
class ScopeTests(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory(prefix="scst scope [paths] ")
self.addCleanup(tmp.cleanup)
self.root = Path(tmp.name)
self.git("init", "-q", "--template=")
def git(self, *args):
return subprocess.check_output(["git", "-C", str(self.root), *args])
def source(self, name):
path = self.root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("int example(void);\n")
self.git("add", "-f", "--", name)
return path
def test_full_and_patch_scope_agree(self):
kept = ("scst/src/core.c", "scst/src/kunit/test.c", "iscsi-scst/usr/user.c",
"qla2x00t-32gbit/qla2x00-target/scst_qla2xxx.c",
"qla2x00t-32gbit/qla2x00-target/scst_qla2xxx.h", "srpt/src/driver.c",
"fcst/name with spaces.c", "usr/fileio/main.c", "scst_local/local.c")
excluded = ("scripts/example.c", "scst/src/generated.mod.c",
"srpt/conftest/probe/main.c", "iscsi-scst/conftest/main.c",
"qla2x00t/qla2xxx-orig/main.c", "qla2x00t/old.c",
"qla2x00t/qla2x00-target/scst_qla2xxx.c",
"qla2x00t-32gbit/qla_target.c", "qla2x00t-32gbit/qla_target.h",
"qla2x00t-32gbit/qla2xxx-orig/main.c")
for name in (*kept, *excluded):
self.assertEqual(in_scope(self.source(name), self.root), name in kept)
self.assertEqual(set(tracked_sources(self.root)), {self.root / n for n in kept})
names = self.git("diff", "--cached", "--name-only", "-z", "--", *git_pathspecs())
self.assertEqual({os.fsdecode(n) for n in names.split(b"\0") if n}, set(kept))
for name in excluded:
with self.subTest(name=name), self.assertRaises(ValueError):
select_sources(self.root, [self.root / name], None)
self.git("-c", "user.name=Test", "-c", "user.email=test@example.org",
"-c", "commit.gpgsign=false", "commit", "-qm", "base")
for name in (*kept, *excluded):
(self.root / name).write_text("int changed(void);\n")
self.assertEqual(set(changed_sources(self.root, "HEAD")), {self.root / n for n in kept})
def test_alias_cannot_reinclude_qlogic_sources(self):
kept = self.source("scst/kept.c")
excluded = self.source("qla2x00t-32gbit/qla_target.c")
for name, target in (("scst/vendor.c", excluded),
("qla2x00t-32gbit/alias.c", kept)):
alias = self.root / name
alias.symlink_to(target)
self.assertFalse(in_scope(alias, self.root))
with self.assertRaises(ValueError):
select_sources(self.root, [alias], None)
def test_deleted_untracked_and_escaping_links(self):
kept = self.source("scst/kept.c")
self.source("scst/deleted.c").unlink()
outside = self.root / "outside.c"
outside.write_text("int outside;\n")
alias = self.root / "scst/alias.c"
alias.symlink_to(outside)
self.git("add", "scst/alias.c")
new = self.root / "scst/new.c"
new.write_text("int fresh;\n")
self.assertEqual(tracked_sources(self.root), [kept])
self.assertEqual(select_sources(self.root, [new], None), [new])
with self.assertRaises(ValueError):
select_sources(self.root, [alias], None)
def test_diff_checks_new_name_and_rejects_bad_base(self):
self.source("scst/before.c")
self.git("-c", "user.name=Test", "-c", "user.email=test@example.org",
"-c", "commit.gpgsign=false", "commit", "-qm", "base")
self.git("mv", "scst/before.c", "scst/after.c")
self.assertEqual(changed_sources(self.root, "HEAD"), [self.root / "scst/after.c"])
with self.assertRaises(subprocess.CalledProcessError):
changed_sources(self.root, "missing-reference")
def test_diff_requires_one_commit_even_when_the_worktree_is_clean(self):
source = self.source("scst/core.c")
self.git("-c", "user.name=Test", "-c", "user.email=test@example.org",
"-c", "commit.gpgsign=false", "commit", "-qm", "base")
for base in ("--quiet", "--exit-code", "--cached", "HEAD..HEAD", "HEAD^{tree}"):
with self.subTest(base=base), self.assertRaises(subprocess.CalledProcessError):
changed_sources(self.root, base)
self.git("tag", "baseline")
self.assertEqual(changed_sources(self.root, "baseline"), [])
source.write_text("int changed(void);\n")
self.assertEqual(changed_sources(self.root, "baseline"), [source])
if __name__ == "__main__":
unittest.main()