P23-03: 生活の観察
This commit is contained in:
@@ -1839,7 +1839,58 @@ def git_bytes(root, *arguments):
|
||||
return process.stdout
|
||||
|
||||
|
||||
def verify_composition(root, roles, source_map, inputs, read_git=None):
|
||||
_VERIFIED_VIEWS = {}
|
||||
|
||||
|
||||
def blob_oid(content):
|
||||
return hashlib.sha1(b'blob %d\0' % len(content) + content).hexdigest()
|
||||
|
||||
|
||||
def git_blob_stream(root, oids, read_batch=None):
|
||||
"""Pace requests to one owned child; adapters return (binary stream, status)."""
|
||||
requested = list(oids)
|
||||
require(all(isinstance(oid, str) and re.fullmatch('[0-9a-f]{40}', oid)
|
||||
for oid in requested), 'invalid blob oid')
|
||||
process = stream = None
|
||||
try:
|
||||
if read_batch is None:
|
||||
process = subprocess.Popen(['git', '-C', str(root), 'cat-file', '--batch'],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
||||
stream = process.stdout
|
||||
else:
|
||||
stream, status = read_batch(requested)
|
||||
count = 0
|
||||
for oid in requested:
|
||||
if process is not None:
|
||||
process.stdin.write((oid + '\n').encode())
|
||||
process.stdin.flush()
|
||||
header = stream.readline(128)
|
||||
match = re.fullmatch(rb'([0-9a-f]{40}) blob (0|[1-9][0-9]*)\n', header)
|
||||
require(match is not None and match[1].decode() == oid, 'invalid/out-of-order blob header')
|
||||
size = int(match[2])
|
||||
content = stream.read(size)
|
||||
require(len(content) == size, 'short blob read')
|
||||
require(stream.read(1) == b'\n', 'missing blob LF')
|
||||
require(blob_oid(content) == oid, 'blob content SHA-1 mismatch')
|
||||
count += 1
|
||||
yield oid, content
|
||||
if process is not None:
|
||||
process.stdin.close()
|
||||
require(stream.read(1) == b'', 'extra trailing blob output')
|
||||
require((process.wait() if process is not None else status) == 0, 'cat-file failed')
|
||||
require(count == len(requested), 'blob output count mismatch')
|
||||
finally:
|
||||
if process is not None:
|
||||
if process.poll() is None:
|
||||
process.kill()
|
||||
process.wait()
|
||||
process.stdin.close()
|
||||
process.stdout.close()
|
||||
elif stream is not None:
|
||||
stream.close()
|
||||
|
||||
|
||||
def verify_composition(root, roles, source_map, inputs, read_git=None, view=None, read_batch=None):
|
||||
require(isinstance(roles, dict), 'missing externally bound S/M/T/Q')
|
||||
names = ('submitted_sha', 'main_context_sha', 'tested_tree', 'tested_commit')
|
||||
require(all(isinstance(roles.get(k), str) and re.fullmatch('[0-9a-f]{40}', roles[k]) for k in names),
|
||||
@@ -1865,6 +1916,7 @@ def verify_composition(root, roles, source_map, inputs, read_git=None):
|
||||
continue
|
||||
metadata, path = record.split(b'\t', 1)
|
||||
mode, kind, oid = metadata.decode().split()
|
||||
require(path.decode() not in result, 'duplicate T path')
|
||||
result[path.decode()] = (mode, kind, oid)
|
||||
return result
|
||||
base, submitted, main, tested = map(tree, (BASE_COMMIT, s, m, t))
|
||||
@@ -1882,30 +1934,43 @@ def verify_composition(root, roles, source_map, inputs, read_git=None):
|
||||
return strict_json(git('show', revision + ':' + CONTRACT))
|
||||
verify_marker_join(document(BASE_COMMIT), document(s), document(m), document(t))
|
||||
runner = module('p23_composition_source_filter', ROOT/'tools/run_all_tests.py')
|
||||
with tested_checkout(root, roles, git) as view:
|
||||
require(source_map == {p: row['sha256'] for p, row in runner.source_refs(view).items()},
|
||||
if view is not None:
|
||||
registered = _VERIFIED_VIEWS.get(Path(view).resolve())
|
||||
require(registered is not None, 'unregistered verified view')
|
||||
require(registered[0] == t, 'verified view tree mismatch')
|
||||
require(registered[1] == {p: e for p, e in tested.items() if e[1] == 'blob'},
|
||||
'verified view manifest mismatch')
|
||||
with (contextlib.nullcontext(Path(view)) if view is not None else
|
||||
tested_checkout(root, roles, git, read_batch)) as source_view:
|
||||
require(source_map == {p: row['sha256'] for p, row in runner.source_refs(source_view).items()},
|
||||
'incomplete tested-tree source/settings/QA map')
|
||||
# Source refs include all QA suites and settings, not just S-changed files.
|
||||
required = set(source_map) | {p.split('#', 1)[0] for p in inputs} | {PLAN, CONTRACT,
|
||||
'project.godot', 'tests/business/test_required_kitchen.gd', 'tools/regression/suite-policy.json'}
|
||||
bindings = roles.get('blob_bindings')
|
||||
require(isinstance(bindings, dict) and set(bindings) == required, 'incomplete/extraneous T blob bindings')
|
||||
for path in sorted(required):
|
||||
paths = sorted(required)
|
||||
for path in paths:
|
||||
entry = tested.get(path)
|
||||
require(entry is not None and entry[1] == 'blob' and bindings[path] == entry[2], 'T blob binding: ' + path)
|
||||
content = git('show', t + ':' + path)
|
||||
if head != s or path in changed_s-changed_m:
|
||||
require((root/path).read_bytes() == content, 'checkout bytes differ from T: ' + path)
|
||||
digest = hashlib.sha256(content).hexdigest()
|
||||
if path in source_map:
|
||||
require(source_map[path] == digest, 'evidence source is not T: ' + path)
|
||||
if path in inputs:
|
||||
require(inputs[path] == digest, 'evidence input is not T: ' + path)
|
||||
contents = (((tested[p][2], (Path(view)/p).read_bytes()) for p in paths) if view is not None
|
||||
else git_blob_stream(root, [tested[p][2] for p in paths], read_batch))
|
||||
with contextlib.closing(contents):
|
||||
for number, (oid, content) in enumerate(contents):
|
||||
path = paths[number]
|
||||
require(blob_oid(content) == tested[path][2] == oid, 'binding blob SHA-1 mismatch: ' + path)
|
||||
if head != s or path in changed_s-changed_m:
|
||||
require((root/path).read_bytes() == content, 'checkout bytes differ from T: ' + path)
|
||||
digest = hashlib.sha256(content).hexdigest()
|
||||
if path in source_map:
|
||||
require(source_map[path] == digest, 'evidence source is not T: ' + path)
|
||||
if path in inputs:
|
||||
require(inputs[path] == digest, 'evidence input is not T: ' + path)
|
||||
return {k: roles[k] for k in names}
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def tested_checkout(root, roles, read_git=None):
|
||||
def tested_checkout(root, roles, read_git=None, read_batch=None):
|
||||
"""Disposable Git-blob view shared by the existing filters and readers."""
|
||||
require(isinstance(roles, dict), 'missing externally bound S/M/T/Q')
|
||||
tree = roles.get('tested_tree', '')
|
||||
@@ -1913,6 +1978,7 @@ def tested_checkout(root, roles, read_git=None):
|
||||
git = read_git or (lambda *args: git_bytes(root, *args))
|
||||
with tempfile.TemporaryDirectory(prefix='p23-tested-tree-') as temporary:
|
||||
view = Path(temporary)
|
||||
manifest = {}
|
||||
for record in git('ls-tree', '-r', '-z', tree).split(b'\0'):
|
||||
if not record:
|
||||
continue
|
||||
@@ -1921,10 +1987,20 @@ def tested_checkout(root, roles, read_git=None):
|
||||
path = Path(raw_path.decode())
|
||||
require(not path.is_absolute() and '..' not in path.parts, 'unsafe T path')
|
||||
require(kind == 'blob' and mode in ('100644', '100755'), 'T source aliases refused')
|
||||
target = view/path
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(git('show', tree + ':' + path.as_posix()))
|
||||
yield view
|
||||
require(path.as_posix() not in manifest, 'duplicate T path')
|
||||
manifest[path.as_posix()] = (mode, kind, oid)
|
||||
paths = list(manifest)
|
||||
contents = git_blob_stream(root, [manifest[p][2] for p in paths], read_batch)
|
||||
with contextlib.closing(contents):
|
||||
for number, (_, content) in enumerate(contents):
|
||||
target = view/paths[number]
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(content)
|
||||
_VERIFIED_VIEWS[view.resolve()] = (tree, manifest)
|
||||
try:
|
||||
yield view
|
||||
finally:
|
||||
del _VERIFIED_VIEWS[view.resolve()]
|
||||
|
||||
|
||||
def root_context(directory, index, explicit=None):
|
||||
@@ -2164,7 +2240,7 @@ def check_tested(checkout, directory, root_roles, root):
|
||||
'historical v1 evidence has diagnostic applicability only')
|
||||
runner = module('p23_source_applicability', root/'tools/run_all_tests.py')
|
||||
require(index.get('source_map') == {n: v['sha256'] for n, v in runner.source_refs(root).items()}, 'exact frozen source map')
|
||||
roles = verify_composition(checkout, root_roles, index['source_map'], input_map(root))
|
||||
roles = verify_composition(checkout, root_roles, index['source_map'], input_map(root), view=root)
|
||||
if git_bytes(checkout, 'rev-parse', 'HEAD').decode().strip() != roles['submitted_sha']:
|
||||
require(index['source_map'] == {n: v['sha256'] for n, v in runner.source_refs(checkout).items()},
|
||||
'exact checkout source map')
|
||||
|
||||
Reference in New Issue
Block a user