tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

showcase-preparation.test.mjs (4891B)


      1 import test from 'node:test';
      2 import assert from 'node:assert/strict';
      3 import {execFileSync} from 'node:child_process';
      4 import {fileURLToPath} from 'node:url';
      5 import {readFile} from 'node:fs/promises';
      6 
      7 test('showcase staging rejects immutable/duplicate conflicts before discovery or assets',()=>{
      8  const site=fileURLToPath(new URL('../',import.meta.url));
      9  const output=execFileSync('python3',['-c',String.raw`
     10 import importlib.util,json,tempfile
     11 from pathlib import Path
     12 site=Path(__import__('sys').argv[1])
     13 spec=importlib.util.spec_from_file_location('showcase',site/'prepare-showcase.py')
     14 m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
     15 index=json.loads((site/'packages.json').read_text())
     16 releases=[next(r for r in reversed(index['releases']) if r['name']==name) for name in ['tricu-graphs','parser-demo','lexer-demo']]
     17 demo=[e for e in json.loads((site/'catalogue.json').read_text()) if e['id'] in ['parser','lexer']]
     18 goldens=[json.loads((site/(e['id']+'-demo.json')).read_text()) for e in demo]
     19 artifacts={r['hash']:site/'bundles'/(r['hash']+'.arboricx') for r in releases}
     20 with tempfile.TemporaryDirectory() as tmp:
     21  root=Path(tmp);(root/'bundles').mkdir()
     22  (root/'catalogue.json').write_text('[]\n')
     23  bad=dict(releases[0],hash='0'*64)
     24  for rows in [[bad],[releases[0],releases[0]]]:
     25   before=json.dumps(dict(schema=1,releases=rows))
     26   (root/'packages.json').write_text(before)
     27   try:m.stage(root,artifacts,releases,demo,goldens)
     28   except ValueError:pass
     29   else:raise AssertionError('conflicting index accepted')
     30   assert (root/'packages.json').read_text()==before
     31   assert (root/'catalogue.json').read_text()=='[]\n'
     32   assert not list((root/'bundles').iterdir())
     33  # A conflict on the second demo must not discover the first or copy assets.
     34  (root/'packages.json').write_text(json.dumps(dict(schema=1,releases=[])))
     35  conflict=json.dumps([dict(demo[1],title='changed')]);(root/'catalogue.json').write_text(conflict)
     36  try:m.stage(root,artifacts,releases,demo,goldens)
     37  except ValueError:pass
     38  else:raise AssertionError('conflicting second demo accepted')
     39  assert (root/'catalogue.json').read_text()==conflict
     40  assert not json.loads((root/'packages.json').read_text())['releases']
     41  assert not list((root/'bundles').iterdir())
     42  (root/'catalogue.json').write_text('[]')
     43  m.stage(root,artifacts,releases,demo,goldens)
     44  assert json.loads((root/'catalogue.json').read_text())==demo
     45  assert len(json.loads((root/'packages.json').read_text())['releases'])==3
     46  for pin,path in artifacts.items():assert (root/'bundles'/(pin+'.arboricx')).read_bytes()==path.read_bytes()
     47  m.stage(root,artifacts,releases,demo,goldens) # Exact retry is idempotent.
     48  assert len(json.loads((root/'packages.json').read_text())['releases'])==3
     49  # A reviewed new version discovers new bytes without changing the old pin.
     50  import hashlib
     51  data=b'new independently accepted test asset';pin=hashlib.sha256(data).hexdigest()
     52  path=Path(tmp)/'new.arboricx';path.write_bytes(data)
     53  next_release=dict(releases[0],version='new-test-version',hash=pin)
     54  m.stage(root,{pin:path},[next_release],demo,goldens)
     55  rows=json.loads((root/'packages.json').read_text())['releases']
     56  assert rows[:3]==releases and rows[-1]==next_release
     57  assert (root/'bundles'/(pin+'.arboricx')).read_bytes()==data
     58  # A changed native catalogue selection requires a fresh version of its own
     59  # already indexed package, not merely an unrelated accepted release.
     60  updated=dict(demo[0],hash=pin)
     61  vectors=[dict(goldens[0],hash=pin),goldens[1]]
     62  before=(root/'catalogue.json').read_bytes()
     63  for invalid in [[],[dict(releases[1],hash=pin)],
     64                  [dict(releases[1],name='unrelated',version='new-demo',hash=pin)]]:
     65   try:m.stage(root,{pin:path},invalid,[updated,demo[1]],vectors)
     66   except ValueError:pass
     67   else:raise AssertionError('unversioned or unrelated demo upgrade accepted')
     68   assert (root/'catalogue.json').read_bytes()==before
     69  upgrade=dict(releases[1],version='new-demo',hash=pin)
     70  m.stage(root,{pin:path},[upgrade],[updated,demo[1]],vectors)
     71  assert json.loads((root/'catalogue.json').read_text())==[updated,demo[1]]
     72  assert json.loads((root/'packages.json').read_text())['releases']==releases+[next_release,upgrade]
     73  m.stage(root,{pin:path},[upgrade],[updated,demo[1]],vectors)
     74 print('accepted')
     75 `,site],{encoding:'utf8',timeout:15000,maxBuffer:100000});
     76  assert.equal(output.trim(),'accepted');
     77 });
     78 
     79 test('core preparation preserves separately accepted native demo editability',async()=>{
     80  const prepare=await readFile(new URL('../prepare.py',import.meta.url),'utf8');
     81  const index=await readFile(new URL('../prepare-index.py',import.meta.url),'utf8');
     82  assert.match(prepare,/catalogue\.extend\(entry for entry in previous if entry\.get\('preparation'\) == 'native'/);
     83  assert.match(index,/if entry\.get\('preparation'\) == 'native':[\s\S]*?continue/);
     84  assert.match(index,/Native demo has no accepted release/);
     85 });