196 lines
10 KiB
Python
196 lines
10 KiB
Python
"""Runner-owned tests. No file writes, product execution, or real PASS from fixtures."""
|
|
import copy
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
import sys
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
sys.dont_write_bytecode = True
|
|
ROOT = Path(__file__).resolve().parents[5]
|
|
spec = importlib.util.spec_from_file_location('p22_checker', ROOT / 'tools/check_p22_asset_contracts.py')
|
|
m = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(m)
|
|
|
|
|
|
class ContractTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.ledger = m.read(m.OUT + 'asset-contracts.json')
|
|
cls.accepted = m.read(m.INPUT)
|
|
_, cls.records = m.accepted_records(cls.accepted)
|
|
cls.snapshot = m.read(m.CURRENT)
|
|
|
|
def rejected(self, function, *args):
|
|
with self.assertRaises((ValueError, OSError, KeyError, TypeError)):
|
|
function(*args)
|
|
|
|
def proof(self, kind, subject):
|
|
return {'kind': kind, 'subject': copy.deepcopy(subject), 'result': 'accepted',
|
|
'issuer': {'role': 'root', 'name': 'isolated fixture; never P22 evidence'},
|
|
'observations': [{'explanation': 'Synthetic pure-function observation only',
|
|
'anchors': [{'path': 'synthetic.txt', 'sha256': 'a' * 64,
|
|
'line': 1, 'text': 'synthetic'}]}]}
|
|
|
|
def fixture(self):
|
|
row = {'slot_id': 'isolated', 'usage_condition': 'isolated pure function only',
|
|
'required_file_count': 1}
|
|
fmt = {'png_width': 16, 'png_height': 16}
|
|
sources = {'synthetic.png': {'sha256': 'a' * 64, 'format': fmt}}
|
|
asset = {'path': 'synthetic.png', 'sha256': 'a' * 64, 'format': copy.deepcopy(fmt),
|
|
'region': [0, 0, 16, 16], 'code_anchor': None, 'kind': 'image'}
|
|
return row, sources, asset
|
|
|
|
def test_isolated_reachable_and_wrong_proof_quantity_pin(self):
|
|
row, sources, asset = self.fixture()
|
|
m.asset_matches(asset, row, sources)
|
|
quantities = dict.fromkeys(m.QUANTITIES)
|
|
quantities['required_file_count'] = 1
|
|
m.quantities_valid(quantities, row)
|
|
subject = {'asset': asset, 'quantities': quantities, 'record_sha': m.semantic(row)}
|
|
fit = self.proof('fit', subject)
|
|
selected = {'isolated': m.proof_matches(fit, 'fit', subject)}
|
|
unit = {kind: self.proof(kind, subject) for kind in ('mandatory_scope', 'disclosure')}
|
|
self.assertTrue(m.technical_readiness(['isolated'], selected, unit, subject))
|
|
# Never run check() against these fixtures or count them as real assets.
|
|
bad = copy.deepcopy(fit)
|
|
bad['subject']['asset']['sha256'] = 'b' * 64
|
|
self.rejected(m.proof_matches, bad, 'fit', subject)
|
|
bad_quantity = dict(quantities, required_file_count=2)
|
|
self.rejected(m.quantities_valid, bad_quantity, row)
|
|
bad_asset = dict(asset, sha256='b' * 64)
|
|
self.rejected(m.asset_matches, bad_asset, row, sources)
|
|
self.rejected(m.proof_matches, True, 'fit', subject)
|
|
|
|
def test_nonvacuous_and_unmade_denominator(self):
|
|
self.rejected(m.technical_readiness, [], {}, {}, {})
|
|
self.rejected(m.technical_readiness, ['made', 'unmade'], {'made': True}, {}, {})
|
|
self.assertFalse(m.technical_readiness(['made', 'unmade'], {'made': True, 'unmade': False}, {}, {}))
|
|
self.rejected(m.technical_readiness, ['x'], {'x': 'true'}, {}, {})
|
|
|
|
def test_format_layout_state_and_quantity(self):
|
|
row, sources, asset = self.fixture()
|
|
for bad in (dict(asset, format={'png_width': 17, 'png_height': 16}),
|
|
dict(asset, region=[0, 0, 17, 16]),
|
|
dict(asset, region=[True, 0, 16, 16]),
|
|
dict(asset, kind='placeholder_media')):
|
|
self.rejected(m.asset_matches, bad, row, sources)
|
|
self.rejected(m.asset_matches, asset, {'group_id': 'state'}, sources)
|
|
self.rejected(m.quantities_valid, dict.fromkeys(m.QUANTITIES, True), row)
|
|
|
|
def test_codec_and_strict_json(self):
|
|
doc = {'keys': ['x'], 'strings': ['s'], 'pool': [], 'files': ['d', 0, 1]}
|
|
self.assertEqual(m.decode(doc), {'x': 1})
|
|
for files in (['s', True], ['s', -1], ['d', 0, 1, 0, 2],
|
|
['unknown'], ['r', 0], float('nan'), float('inf')):
|
|
self.rejected(m.decode, dict(doc, files=files))
|
|
self.rejected(m.decode, dict(doc, pool=[['l', ['r', 0]]], files=['r', 0]))
|
|
self.rejected(m.decode, dict(doc, pool=[['s', 0]], files=['r', 0]))
|
|
for text in ('{"x":1,"x":2}', '{"x":NaN}', '{"x":Infinity}', '{"x":1e999}'):
|
|
self.rejected(m.loads, text)
|
|
|
|
def test_path_escape(self):
|
|
for path in ('../escape', '/tmp/escape', 'assets/../../escape'):
|
|
self.rejected(m.safe_path, path)
|
|
|
|
def test_real_coverage_and_source_omission(self):
|
|
for action in ('omit_identity', 'duplicate_identity', 'omit_source', 'duplicate_source'):
|
|
d = copy.deepcopy(self.snapshot)
|
|
if action == 'omit_identity':
|
|
d['contracts'].pop()
|
|
elif action == 'duplicate_identity':
|
|
d['contracts'].append(d['contracts'][0])
|
|
elif action == 'omit_source':
|
|
d['candidate_pool'].pop(next(iter(d['candidate_pool'])))
|
|
else:
|
|
d['contracts'][0]['candidate_refs'].append(d['contracts'][0]['candidate_refs'][0])
|
|
self.rejected(m.reference_contracts, d, self.records)
|
|
|
|
def test_real_source_missing_and_stale(self):
|
|
path, digest = next(iter(self.ledger['source_pins'].items()))
|
|
self.rejected(m.pinned, path, '0' * 64)
|
|
self.rejected(m.pinned, m.OUT + 'nonexistent-proof.json', digest)
|
|
|
|
def test_real_png_crc_and_layout(self):
|
|
path = 'assets/kenney/tiny_town/Tilemap/tilemap_packed.png'
|
|
data = m.raw(path)
|
|
fmt = m.png_format(data)
|
|
self.assertEqual((fmt['png_width'], fmt['png_height']), (192, 176))
|
|
damaged = bytearray(data)
|
|
damaged[20] ^= 1
|
|
self.rejected(m.png_format, bytes(damaged))
|
|
sources = {path: {'sha256': m.sha(data), 'format': fmt}}
|
|
asset = {'path': path, 'sha256': m.sha(data), 'format': fmt,
|
|
'region': [191, 0, 16, 16], 'code_anchor': None, 'kind': 'image'}
|
|
self.rejected(m.asset_matches, asset, {}, sources)
|
|
|
|
def test_real_reference_mismatch(self):
|
|
a = copy.deepcopy(self.snapshot['contracts'][0]['source_anchors'][0])
|
|
a['text'] += 'not the actual reference'
|
|
self.rejected(m.anchor, a)
|
|
|
|
def test_real_license_bytes_and_rights_subject(self):
|
|
pack = m.read(m.EVIDENCE + 'publisher-archive-byte-verification.json')['packs'][0]
|
|
license_row = next(x for x in pack['members'] if x['archive_member'] == 'License.txt')
|
|
path, digest = license_row['repo_path'], license_row['raw_sha256']
|
|
original_raw = m.raw
|
|
with mock.patch.object(m, 'raw', side_effect=lambda p: b'fake license' if p == path else original_raw(p)):
|
|
self.rejected(m.pinned, path, digest)
|
|
subject = {'source': next(iter(self.ledger['source_pins'])),
|
|
'rights': {'creator': 'unknown', 'license': 'unknown'}}
|
|
proof = self.proof('rights', subject)
|
|
changed = copy.deepcopy(subject)
|
|
changed['rights']['license'] = 'CC0'
|
|
self.rejected(m.proof_matches, proof, 'rights', changed)
|
|
|
|
def test_real_false_finished_and_fake_booleans(self):
|
|
c = self.snapshot['contracts'][0]
|
|
row = self.records[c['id']][1]
|
|
for value in (True, False, [], {'state': 'finished'}, 'selected'):
|
|
self.rejected(m.selection_ready, c['id'], row, c, value, {})
|
|
self.rejected(m.receipt, {'path': m.OUT + 'validation.json', 'sha256': '0' * 64, 'index': 0}, 'fit', {})
|
|
self.rejected(m.receipt, True, 'fit', {})
|
|
|
|
def test_real_selected_contract_unknown_rights_and_forged_proof(self):
|
|
c = self.snapshot['contracts'][0]
|
|
row = self.records[c['id']][1]
|
|
path = 'assets/kenney/tiny_town/Tilemap/tilemap_packed.png'
|
|
data = m.raw(path)
|
|
sources = {path: {'sha256': m.sha(data), 'format': m.png_format(data)}}
|
|
product = 'scripts/ui/pixel_tower_art.gd'
|
|
product_data = m.raw(product)
|
|
ref = {'path': product, 'sha256': m.sha(product_data), 'line': 1,
|
|
'text': product_data.decode().splitlines()[0]}
|
|
rights_path = 'assets/kenney/tiny_town/License.txt'
|
|
s = {'asset': {'path': path, 'sha256': m.sha(data), 'format': sources[path]['format'],
|
|
'region': [0, 0, 16, 16], 'code_anchor': None, 'kind': 'image'},
|
|
'creator': 'Fixture declaration, not a real attribution',
|
|
'rights': {'license': 'unknown', 'origin': 'fixture only',
|
|
'documents': [{'path': rights_path, 'sha256': m.sha(m.raw(rights_path))}]},
|
|
'name': Path(path).name, 'replacement_chain': [c['id'], path], 'references': [ref],
|
|
'quantities': dict.fromkeys(m.QUANTITIES), 'quantity_basis': 'Fixture, no final count',
|
|
'usability': {'display': 'Requires actual exact-use display', 'hearing': 'Fixture image'}}
|
|
choice = {'state': 'selected', 'contract': s, 'proofs': dict.fromkeys(m.PROOF_KINDS, True)}
|
|
with self.assertRaisesRegex(ValueError, 'unknown license'):
|
|
m.selection_ready(c['id'], row, c, choice, sources)
|
|
s['rights']['license'] = 'CC0'
|
|
with self.assertRaisesRegex(ValueError, 'proof reference schema'):
|
|
m.selection_ready(c['id'], row, c, choice, sources)
|
|
|
|
def test_registered_cli_real_data_is_not_ready(self):
|
|
argv = ['checker', '--verify-only', '--accepted-input', m.INPUT, '--require-ready']
|
|
with mock.patch.object(sys, 'argv', argv), mock.patch('sys.stdout', new_callable=io.StringIO) as output:
|
|
code = m.main()
|
|
result = json.loads(output.getvalue())
|
|
self.assertEqual(code, 2, result)
|
|
self.assertTrue(result['record_integrity'])
|
|
self.assertFalse(result['validation_ready'])
|
|
self.assertEqual(result['pending_identity_count'], 614)
|
|
self.assertFalse(result['acceptance_claimed'])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|