LittlePlyResearch notebook

test_records.py

python · 96 lines · Exact bytes

Line 1 ↗
Version & provenance · state-v2-cm7

Recorded SHA-256 ffd6bd14ea34b08c1faa187a941f083640d36e6217849623cd012a39a4916261Rendered SHA-256 ffd6bd14ea34b08c1faa187a941f083640d36e6217849623cd012a39a4916261

Apache-2.0 original project code; dependencies retain their own licenses. License notes. Line links are stable within this snapshot. CM-7 completed implementation source; original and public rendered hashes recorded. Host-specific paths use /work placeholders; inspect reproduction instructions before running. Separate Rust helper retains its GPL license.

1import hashlib,json,tempfile,unittest
2from pathlib import Path
3import numpy as np
4from unittest.mock import patch
5import codec as c
6from records import Store,Records,DTYPE
7from prepare import replay,process
8from batches import Balanced,tensors,Prefetch
9from runtime_records import Records as BoundedRecords
10
11def fixture(moves='e2e4 e7e5 g1f3'):
12    b=c.chess.Board();tokens=[69]
13    for u in moves.split():m=c.chess.Move.from_uci(u);tokens.extend(c.triple(m));b.push(m)
14    tokens.append(70)
15    return bytes(tokens),{'plies':len(moves.split()),'final_fen':b.fen(),'sequence_sha256':hashlib.sha256(moves.encode()).hexdigest(),'game_id':'lichess:fixture'}
16
17class Tests(unittest.TestCase):
18    def test_protected_transposition_and_resume(self):
19        class Pool:
20            def map(self,fn,items):return map(fn,items)
21        config={'seed':'fixture','chunk_games':2}
22        protected=fixture('g1f3 g8f6 d2d4 d7d5')
23        # Same final state via a different move order must be excluded, even
24        # though the full-game sequence hash differs. Later position is novel.
25        train=fixture('d2d4 d7d5 g1f3 g8f6 c2c4 e7e6')
26        train[1]['game_id']='lichess:other'
27        with tempfile.TemporaryDirectory() as tmp:
28            s=Store(tmp,{'fixture':True},1_000_000_000)
29            def feed(root,manifest,split,mode,seed,source,done,shuffle):
30                seq,meta=protected if mode=='protect' else train
31                if done<1:yield 1,(seq,meta,mode,seed,source)
32            ref={'root':'unused','manifest_sha256':'fixture'}
33            with patch('prepare.tasks',feed):
34                process(s,Pool(),ref,{},0,'protect',0,config)
35                process(s,Pool(),ref,{},0,'train',4,config)
36                before=s.db.execute('SELECT COUNT(*) FROM seen').fetchone()[0]
37                process(s,Pool(),ref,{},0,'train',4,config)
38            self.assertEqual(before,4);self.assertEqual(s.db.execute('SELECT COUNT(*) FROM seen').fetchone()[0],4)
39            common=replay((*protected,'protect','fixture',0))[1][-1]
40            self.assertIsNone(s.db.execute('SELECT 1 FROM seen WHERE k=?',(b't'+common,)).fetchone())
41            self.assertGreaterEqual(dict(s.db.execute('SELECT k,n FROM counters'))['train:protected_position'],2)
42            s.db.close()
43    def test_whole_game_replay(self):
44        tok,meta=fixture();m,keys,rows=replay((tok,meta,'train','seed',0))
45        self.assertEqual(len(keys),4);self.assertEqual(len(rows),3);self.assertEqual(rows.dtype.itemsize,416)
46        self.assertEqual(rows[0]['y'].tolist(),[12,28,64]);self.assertEqual(rows[0]['x'][-2:].tolist(),[12,28])
47        with self.assertRaises(ValueError):replay((tok[:-4]+bytes([12,12,64,70]),meta,'train','seed',0))
48        with self.assertRaises(ValueError):replay((tok,{**meta,'final_fen':c.chess.Board().fen()},'train','seed',0))
49    def test_history_dropout_deterministic(self):
50        tok,m=fixture();a=replay((tok,m,'train','x',0))[2];b=replay((tok,m,'train','x',0))[2]
51        self.assertEqual(a.tobytes(),b.tobytes())
52    def test_crash_boundaries(self):
53        for fail in ('after_file','before_commit','after_commit'):
54            with self.subTest(fail=fail),tempfile.TemporaryDirectory() as tmp:
55                s=Store(tmp,{'test':1},1_000_000_000)
56                s.db.execute('INSERT INTO seen VALUES(?)',(b'key',))
57                with self.assertRaises(RuntimeError):s.commit(np.zeros(2,dtype=DTYPE),0,0,'cursor',2,fail)
58                s.db.close();s=Store(tmp,{'test':1},1_000_000_000)
59                committed=fail=='after_commit'
60                self.assertEqual(s.get('cursor'),2 if committed else None)
61                self.assertEqual(s.db.execute('SELECT COUNT(*) FROM seen').fetchone()[0],int(committed))
62                self.assertEqual(len(list(Path(tmp).glob('records-*.bin'))),int(committed));s.db.close()
63    def test_manifest_mmap_corruption(self):
64        with tempfile.TemporaryDirectory() as tmp:
65            s=Store(tmp,{'test':2},1_000_000_000);rows=np.zeros(5,dtype=DTYPE);rows['ply']=np.arange(5)
66            s.commit(rows[:2],0,0,'a',2);s.commit(rows[2:],0,0,'a',5);s.seal();s.db.close()
67            r=Records(tmp,0,0);self.assertEqual(r.take([4,1,3])['ply'].tolist(),[4,1,3])
68            bounded=BoundedRecords(tmp,0,0,max_open=1)
69            self.assertEqual(bounded.take([4,1,3,0])['ply'].tolist(),[4,1,3,0]);self.assertEqual(len(bounded.cache),1);bounded.close()
70            with self.assertRaises(IndexError):r.take([5])
71            p=next(Path(tmp).glob('records-*.bin'));p.write_bytes(b'x'+p.read_bytes()[1:])
72            with self.assertRaises(ValueError):Records(tmp,0,0)
73    def test_identity_and_missing_journal(self):
74        with tempfile.TemporaryDirectory() as tmp:
75            s=Store(tmp,{'x':1},1_000_000_000);s.commit(np.zeros(1,dtype=DTYPE),0,0,'x',1);s.db.close()
76            with self.assertRaises(ValueError):Store(tmp,{'x':2},1_000_000_000)
77            (Path(tmp)/'journal.sqlite').unlink()
78            with self.assertRaises(ValueError):Store(tmp,{'x':1},1_000_000_000)
79    def test_balanced_disjoint_final(self):
80        class Source:
81            def __init__(self,s):self.s=s
82            def __len__(self):return 19
83            def take(self,ids):
84                a=np.zeros(len(ids),dtype=DTYPE);a['source']=self.s;a['game']=ids;return a
85        plans=[Balanced(Source(0),Source(1),7,r,2,8) for r in range(2)]
86        allrows=[]
87        for p in plans:
88            pre=Prefetch(p)
89            for i in range(p.steps):
90                a=p.rows(i);self.assertEqual(sum(a['source']==0),sum(a['source']==1));allrows.extend(zip(a['source'].tolist(),a['game'].tolist()))
91                ts,_=pre.take(i);self.assertEqual(ts[0].shape,(len(a),122))
92            pre.close()
93        self.assertEqual(len(allrows),38);self.assertEqual(len(set(allrows)),38)
94
95if __name__=='__main__':unittest.main()
96