69 lines
4.8 KiB
Python
69 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
# dm/tools/usage.py and dm/tools/impact_map.tsv (scripts/regress.py usage / impact): the map's globs name files of the repository, its patterns name usage kinds, the
|
|
# function scanner, the rule precedence and the manifests' format
|
|
import fnmatch, subprocess, sys, tempfile, unittest
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parents[3]
|
|
sys.path.insert(0, str(REPO / 'dm/tools'))
|
|
import usage
|
|
|
|
KINDS = {'api', 'key', 'present', 'shader', 'nvn', 'hle', 'svc', '*'}
|
|
|
|
|
|
class Usage(unittest.TestCase):
|
|
def test_map_names_files_and_kinds(self):
|
|
files = [str(p.relative_to(REPO)) for p in REPO.rglob('*') if p.is_file() and '.git' not in p.parts]
|
|
for glob, frx, pat, why in usage.load_map():
|
|
self.assertTrue(any(fnmatch.fnmatch(f, glob) for f in files), f'impact_map.tsv: {glob} matches no file')
|
|
self.assertTrue(why, f'impact_map.tsv: {glob} gives no reason')
|
|
if pat not in ('-', 'profile'): self.assertIn(pat.partition(':')[0], KINDS, f'impact_map.tsv: {glob}: unknown kind in {pat}')
|
|
if frx is not None and glob.endswith('dmsh_build.py'): # a function rule of the shader build names a function it has
|
|
names = {n for n, _, _ in usage.functions(REPO / glob)}
|
|
self.assertTrue(any(frx.search(n) for n in names), f'impact_map.tsv: {glob}:{frx.pattern} matches no function')
|
|
|
|
def test_functions(self):
|
|
src = 'static int a(void) {\n return b(1);\n}\nNVN(nvnQueueFinish, void, NVNqueue *q) {\n a();\n}\nHLE("_ZN2nn2fs4OpenEv", Result, h_open, void) {\n return 0;\n}\n'
|
|
self.assertEqual([n for n, _, _ in usage.functions('x.c', src)], ['a', 'nvnQueueFinish', 'h_open'])
|
|
self.assertEqual([(n, a, b) for n, a, b in usage.functions('x.py', 'import os\ndef f():\n pass\nclass C:\n pass\n')], [('f', 2, 3), ('C', 4, 5)])
|
|
|
|
def test_changed_functions_and_headers(self): # a hunk maps to every function it spans; a header's static inline function reaches the entry points of its includers
|
|
with tempfile.TemporaryDirectory() as d:
|
|
run = lambda *c: subprocess.run(['git', '-c', 'user.name=t', '-c', 'user.email=t@t', *c], cwd=d, check=True, capture_output=True)
|
|
(Path(d) / 'dm/gfx/nvn').mkdir(parents=True); run('init', '-q')
|
|
(Path(d) / 'dm/gfx/nvn/f.h').write_text('static inline int step(int k) {\n return k;\n}\n')
|
|
(Path(d) / 'dm/gfx/nvn/hle_nvn.c').write_text('#include "f.h"\nNVN(nvnQueuePresentTexture, void, int k) {\n step(k);\n}\n')
|
|
run('add', '.'); run('commit', '-qm', '1')
|
|
(Path(d) / 'dm/gfx/nvn/f.h').write_text('static inline int step(int k) {\n return k + 1;\n}\nstatic inline int hold(int k) {\n return k;\n}\n')
|
|
(Path(d) / 'dm/gfx/nvn/hle_nvn.c').write_text('#include "f.h"\nNVN(nvnQueuePresentTexture, void, int k) {\n}\n') # a pure deletion inside the entry point
|
|
run('commit', '-qam', '2')
|
|
ch = usage.changed_functions('HEAD^..HEAD', root=Path(d))
|
|
self.assertEqual(ch['dm/gfx/nvn/f.h'], {'step', 'hold'}); self.assertEqual(ch['dm/gfx/nvn/hle_nvn.c'], {'nvnQueuePresentTexture'})
|
|
callers, roots, _ = usage.host_graph(Path(d), rev='HEAD^') # read from git: the step call exists only in the first revision
|
|
self.assertEqual(usage.reach({('dm/gfx/nvn/f.h', 'step')}, callers, roots)[0], {('nvn', 'nvnQueuePresentTexture')})
|
|
self.assertEqual(usage.reach({('dm/gfx/nvn/f.h', 'step')}, *usage.host_graph(Path(d))[:2])[0], set())
|
|
|
|
def test_rule_precedence(self):
|
|
rules = usage.load_map()
|
|
rs, mapped = usage.rules_for('dm/gfx/tools/dmsh_build.py', {'fs_uint'}, rules)
|
|
self.assertTrue(mapped); self.assertEqual({r[2] for r in rs}, {'shader:integer_outputs'}) # the function's rule, not the file's catch-all
|
|
rs, _ = usage.rules_for('dm/gfx/tools/dmsh_build.py', {'some_new_function'}, rules)
|
|
self.assertEqual({r[2] for r in rs}, {'shader:stage:*'})
|
|
rs, mapped = usage.rules_for('dm/games/01007EF00011E000/README.md', {''}, rules)
|
|
self.assertTrue(mapped); self.assertEqual(rs, []) # documentation reaches no game
|
|
|
|
def test_manifest_roundtrip(self):
|
|
with tempfile.TemporaryDirectory() as d:
|
|
p = Path(d) / 'usage.tsv'
|
|
usage.write_manifest(p, {('nvn', 'nvnQueueFinish'): 12, ('api', 'nvn'): 1, ('shader', 'image_atomic'): 3}, 'test')
|
|
self.assertEqual(usage.read_manifest(p), {('nvn', 'nvnQueueFinish'): 12, ('api', 'nvn'): 1, ('shader', 'image_atomic'): 3})
|
|
|
|
def test_committed_manifests(self): # names and counts only
|
|
for p in (REPO / 'dm/games').glob('*/usage.tsv'):
|
|
for k, n in usage.read_manifest(p):
|
|
self.assertIn(k, KINDS - {'*'}, p); self.assertNotIn('/', n if k != 'shader' else n.replace('/', ''), p)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|