Changeset 02d288d in flexoentity for tests/test_id_stress.py
- Timestamp:
- 10/23/25 13:27:08 (3 months ago)
- Branches:
- master
- Children:
- 4ceca57
- Parents:
- 6a7dec1
- File:
-
- 1 edited
-
tests/test_id_stress.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
tests/test_id_stress.py
r6a7dec1 r02d288d 3 3 Focus: collision avoidance, version ceiling, reproducibility. 4 4 """ 5 5 6 import pytest 6 7 import random 8 import logging 9 from flexoentity import FlexOID, EntityType, EntityState 10 from builder.questions import RadioQuestion, AnswerOption 7 11 8 from flexoentity import FlexOID, EntityType, EntityState, Domain 12 logger = logging.getLogger(__name__) 9 13 10 from tests.conftest import DummyEntity 11 12 # ────────────────────────────────────────────────────────────────────────────── 13 def test_bulk_generation_uniqueness(): 14 """Generate 10,000 IDs and assert uniqueness (statistical test).""" 15 domain = Domain(domain="SIG", etype=EntityType.DOMAIN, state=EntityState.DRAFT, 16 fullname="Signal Corps", classification="RESTRICTED", owner="MESE") 17 14 def test_bulk_generation_uniqueness(domain): 15 """ 16 Generate 10,000 IDs and ensure uniqueness using safe_generate(). 17 If a collision occurs, safe_generate() must resolve it automatically 18 via salt + date adjustment. 19 """ 18 20 etype = EntityType.QUESTION 19 21 estate = EntityState.DRAFT 20 seeds = [f"question {i}" for i in range( 10_000)]22 seeds = [f"question {i}" for i in range(4000000)] 21 23 22 ids = [FlexOID.generate(domain, etype, estate, seed) for seed in seeds] 24 # Simulate a simple in-memory repository for collision detection 25 repo = {} 23 26 24 assert len(ids) == len(set(ids)), "ID collisions detected in bulk generation" 27 def repo_get(oid_str): 28 return repo.get(str(oid_str)) 25 29 30 # Generate IDs using safe_generate 31 ids = [] 32 for seed in seeds: 33 oid = FlexOID.safe_generate(domain.domain, etype, estate, seed, repo=repo) 34 assert isinstance(oid, FlexOID) 35 ids.append(str(oid)) 36 repo[str(oid)] = oid # register for future collision detection 26 37 27 def test_disambiguator_trigger(): 38 unique_count = len(set(ids)) 39 total_count = len(ids) 40 collisions = total_count - unique_count 41 42 logger.info(f"Generated {total_count} IDs ({collisions} collisions handled).") 43 44 # Assert that safe_generate avoided duplicates 45 assert total_count == unique_count, f"Unexpected duplicate IDs ({collisions} found)" 46 47 # Sanity check: IDs should look canonical 48 assert all(id_str.startswith("SIG-") for id_str in ids) 49 assert all("@" in id_str for id_str in ids) 50 51 def test_id_generation_is_deterministic(domain): 28 52 """ 29 53 Generating the same entity twice with same inputs yields identical ID. 30 54 (No runtime disambiguation; IDs are deterministic by design.) 31 55 """ 32 domain = "AF"33 56 etype = EntityType.QUESTION 34 57 estate = EntityState.DRAFT 35 58 text = "identical question text" 36 id1 = FlexOID.generate(domain, etype, estate, text) 37 id2 = FlexOID.generate(domain, etype, estate, text) 38 # IDs must be identical, because we now enforce determinism, not randomization 59 60 id1 = FlexOID.generate(domain.domain, etype, estate, text) 61 id2 = FlexOID.generate(domain.domain, etype, estate, text) 62 # IDs must be identical because generation is deterministic 39 63 assert id1 == id2 40 assert id1.signature == id2.signature41 64 42 65 43 def test_id_reproducibility_across_runs( ):66 def test_id_reproducibility_across_runs(domain): 44 67 """ 45 68 The same seed on a new process (fresh _seen_hashes) 46 69 should yield the same base ID (without suffix). 47 70 """ 48 domain = Domain(domain="SIG", etype=EntityType.DOMAIN, state=EntityState.DRAFT,49 fullname="Signal Corps", classification="RESTRICTED")50 71 etype = EntityType.CATALOG 51 72 estate = EntityState.DRAFT 52 73 seed = "reproducibility test seed" 53 id1 = FlexOID.generate(domain, etype, estate, seed) 54 # Reset hash cache74 75 id1 = FlexOID.generate(domain.domain, etype, estate, seed) 55 76 FlexOID._seen_hashes.clear() 56 id2 = FlexOID.generate(domain, etype, estate, seed) 77 id2 = FlexOID.generate(domain.domain, etype, estate, seed) 78 57 79 assert id1 == id2 58 assert id1.signature == id2.signature59 80 60 81 61 def test_version_ceiling_enforcement( ):82 def test_version_ceiling_enforcement(radio_question): 62 83 """Simulate approaching @999 to trigger obsolescence guard.""" 63 entity = DummyEntity(domain="AF", etype=EntityType.EXAM, state=EntityState.DRAFT, seed="Final Exam 2025") 64 entity.approve() 84 q = radio_question 85 q.approve() 86 65 87 # artificially bump version number to near ceiling 66 entity.flexo_id = FlexOID.from_oid_and_version(entity.flexo_id, 998)88 q.flexo_id = FlexOID.from_oid_and_version(q.flexo_id, 998) 67 89 68 90 # 998 → 999 is allowed 69 entity.sign()70 assert entity.flexo_id.version == 99991 q.sign() 92 assert q.flexo_id.version == 999 71 93 72 94 # 999 → 1000 should raise RuntimeError 73 95 with pytest.raises(RuntimeError): 74 entity.sign()96 q.sign() 75 97 76 98 77 def test_massive_lifecycle_simulation( ):99 def test_massive_lifecycle_simulation(domain): 78 100 """ 79 Generate 100 random entities, simulate multiple edits and state transitions,101 Generate 100 random RadioQuestions, simulate multiple edits and state transitions, 80 102 ensure all final IDs and fingerprints are unique and valid. 81 103 """ 82 entities = [DummyEntity(domain="AF", etype=EntityType.QUESTION, state=EntityState.DRAFT, seed=f"random question {i}") for i in range(100)] 104 entities = [ 105 RadioQuestion( 106 domain=domain, 107 etype=EntityType.QUESTION, 108 state=EntityState.DRAFT, 109 text=f"random question {i}", 110 options=[ 111 AnswerOption(id="opt4", text="HF (3–30 MHz)", points=1), 112 AnswerOption(id="opt5", text="VHF (30–300 MHz)", points=0), 113 ], 114 ) 115 for i in range(100) 116 ] 83 117 84 118 for e in entities: 85 # random edit , approval, signing86 e. _seed+= " updated"119 # random edit 120 e.text += " updated" 87 121 e._update_fingerprint() 122 123 # lifecycle transitions 88 124 e.approve() 89 125 if random.random() > 0.3: … … 92 128 e.publish() 93 129 94 ids = [e.flexo_id for e in entities] 95 fps = [e.flexo_id.signature for e in entities] 96 assert len(ids) == len(set(ids)), "Duplicate IDs after random lifecycle" 97 assert len(fps) == len(set(fps)), "Duplicate fingerprints after random lifecycle" 130 flexoids = [e.flexo_id for e in entities] 131 assert len(flexoids) == len(set(flexoids)), "Duplicate FlexOIDs after lifecycle simulation"
Note:
See TracChangeset
for help on using the changeset viewer.
