-- EditTestImpl.mesa -- written by Bill Paxton, April 1981 -- last edit by Bill Paxton, 2-Nov-81 10:29:03 -- This package provides random testing for editing Text nodes DIRECTORY EditTest, EditSpan, TextEdit, TextFind, TextLooks, TextLooksSupport, UndoEvent, RopeEdit, GetTree, TreeCheck, TextNode, EditNotify, RunReader, RopeReader, Rope, RopeInline, CheckNode, RandomCard, RandomLongInt, Inline; EditTestImpl: PROGRAM IMPORTS EditTest, EditSpan, TextEdit, RopeEdit, EditNotify, TextLooks, TextLooksSupport, TextFind, RunReader, RopeReader, Rope, TextNode, UndoEvent, GetTree, TreeCheck, CheckNode, RandomCard, RandomLongInt, Inline EXPORTS EditTest = BEGIN OPEN rI:Rope, EditTest, spanI:EditSpan, editI:TextEdit, findI:TextFind, looksI:TextLooks, looksSI:TextLooksSupport, undoI:UndoEvent, ropeI:RopeEdit, nodeI:TextNode, runrdrI:RunReader, roperdrI:RopeReader, checkI:CheckNode, notify:EditNotify, randC:RandomCard, randLI:RandomLongInt; alpha, beta: PUBLIC Node; alphaTree, betaTree: PUBLIC Ref; runrdr1, runrdr2: PUBLIC runrdrI.Ref; roperdr1, roperdr2: PUBLIC roperdrI.Ref; numTests: NAT = 28; testCountArray: ARRAY [0..numTests) OF NAT; -- number of times each test is executed by Run testProcArray: ARRAY [0..numTests) OF PROC = [ ReplaceText, DeleteText, CopyText, MoveText, MoveTextOnto, TransposeText, ReplaceByChar, InsertChar, AppendChar, ReplaceByString, InsertString, AppendString, ReplaceByRope, InsertRope, AppendRope, Find, BackwardsFind, ChangeLooks, AddLooks, RemoveLooks, SetLooks, ClearLooks, PutGet, PutGetRope, GetBigFile, PutGetTree, PutGetToRope, PutGetToStream ]; numNodeTests: NAT = 11; nodeTestCountArray: ARRAY [0..numNodeTests) OF NAT; nodeTestProcArray: ARRAY [0..numNodeTests) OF PROC = [ ReplaceNodes, DeleteNodes, CopyNodes, MoveNodes, MoveNodesOnto, TransposeNodes, SplitNode, MergeNodes, NestNodes, UnNestNodes, InsertNode ]; shortest: Offset = 20; -- don't let nodes get shorter than this longest: Offset = 2000; -- don't let nodes get longer than this -- ***** Looks Edit operations noLooks: Looks = looksI.noLooks; allLooks: Looks = looksI.allLooks; ChangeLooks: PUBLIC PROC = { node: Node _ PickNode[]; start, end: Offset; rem, add: Looks; rem _ PickLooks[]; add _ PickLooks[]; [start,end] _ PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.ChangeLooks[TextSpan[node,start,end-start],rem,add,event]; CheckLooks[node,rem,add,start,end-start] }; AddLooks: PUBLIC PROC = { node: Node _ PickNode[]; start, end: Offset; add: Looks _ PickLooks[]; [start,end] _ PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.AddLooks[TextSpan[node,start,end-start],add,event]; CheckLooks[node,noLooks,add,start,end-start] }; RemoveLooks: PUBLIC PROC = { node: Node _ PickNode[]; start, end: Offset; rem: Looks _ PickLooks[]; [start,end] _ PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.RemoveLooks[TextSpan[node,start,end-start],rem,event]; CheckLooks[node,rem,noLooks,start,end-start] }; SetLooks: PUBLIC PROC = { node: Node _ PickNode[]; start, end: Offset; new: Looks _ PickLooks[]; [start,end] _ PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.SetLooks[TextSpan[node,start,end-start],new,event]; CheckLooks[node,allLooks,new,start,end-start] }; ClearLooks: PUBLIC PROC = { node: Node _ PickNode[]; start, end: Offset; [start,end] _ PickTwo[node]; IF start=end THEN RETURN; BeforeUndo[node]; spanI.ClearLooks[TextSpan[node,start,end-start],event]; CheckLooks[node,allLooks,noLooks,start,end-start] }; CheckLooks: PROC [ node: Node, remove, add: Looks, start, len: Offset] = { out: Looks _ looksSI.LooksAND[remove,looksSI.LooksNOT[add]]; -- out contains looks that we are removing and not adding loc, runLen: Offset _ start; end: Offset _ start+len; runs: Runs _ editI.GetRuns[node]; IF runs=NIL THEN { IF add # noLooks THEN ERROR; RETURN }; runrdrI.SetPosition[runrdr1,runs,start]; UNTIL loc >= end DO -- check the runs in the changed section looks: Looks; [runLen,looks] _ runrdrI.Get[runrdr1]; IF looksSI.LooksAND[looks,add] # add THEN ERROR; IF looksSI.LooksAND[looks,out] # noLooks THEN ERROR; loc _ loc+runLen; ENDLOOP; TestUndo[] }; -- ***** Display text node as string nodeText: STRING _ [2000]; ShowNode: PROC [n: Node] RETURNS [s: STRING] = { -- for debugging s _ nodeText; s.length _ 0; AppendNode[n,s,0] }; AppendNode: PROC [n: Node, s: STRING, indent: NAT] = { THROUGH [0..indent) DO s[s.length] _ ' ; s.length _ s.length+1; ENDLOOP; roperdrI.SetPosition[roperdr1,n.rope]; FOR i:Offset _ editI.Size[n], i-1 UNTIL i=0 DO s[s.length] _ roperdrI.Get[roperdr1]; s.length _ s.length+1; ENDLOOP; s[s.length] _ 'M-100B; s.length _ s.length+1; FOR c:editI.Ref _ n.child, nodeI.Next[c] UNTIL c=NIL DO WITH cc:c SELECT FROM text => AppendNode[@cc,s,indent+4]; ENDCASE; ENDLOOP }; -- ***** Find and BackwardsFind backFindFlag: BOOLEAN _ FALSE; BackwardsFind: PUBLIC PROC = { DoFind: PROC = { [found,at,atEnd,before,after] _ findI.BackwardsFind[pattern: pattern, text: text, literal: FALSE, word: FALSE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE] }; pattern, text: Node; found: BOOLEAN; patternLooks, textLooks: Looks; at, atEnd, before, after: Offset; IF backFindFlag THEN RETURN; backFindFlag _ TRUE; -- only do this once text _ editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; -- 0 5 0 5 0 5 -- first just simple test, no special characters pattern _ editI.FromString["DEF"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- test for only lower case pattern _ editI.FromString["'D'E'F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- pattern in lowercase, target in upper case pattern _ editI.FromString["def"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use # inside pattern pattern _ editI.FromString["D#F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 0 characters pattern _ editI.FromString["D*EF"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 1 character pattern _ editI.FromString["D*F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use @ inside pattern pattern _ editI.FromString["D@F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 0 characters pattern _ editI.FromString["D&EF"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 1 character pattern _ editI.FromString["D&F"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at OR after#atEnd THEN ERROR; -- use ! and {}'s pattern _ editI.FromString["!{DEF}!"]; DoFind; IF ~found OR at#4 OR atEnd#7 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use word search pattern _ editI.FromString["DEF"]; [found,at,atEnd,before,after] _ findI.BackwardsFind[pattern: pattern, text: text, literal: FALSE, word: TRUE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE]; IF ~found OR at#4 OR atEnd#7 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use ~ and {}'s pattern _ editI.FromString["DEF~{GHI}~x"]; DoFind; IF ~found OR at#9 OR atEnd#12 THEN ERROR; -- use | at start pattern _ editI.FromString["|abc"]; DoFind; IF ~found OR at#0 OR atEnd#3 THEN ERROR; -- use | at end pattern _ editI.FromString["ghi|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- use | at start and end pattern _ editI.FromString["|abc*{ghi}|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- requires unwinding wildcard stack pattern _ editI.FromString["b{YX}~YX&."]; text _ editI.FromString["bYX-YXaYXa."]; DoFind; IF ~found OR at#1 OR atEnd#3 THEN ERROR; -- test looks in patterns patternLooks _ PickLooks[]; textLooks _ looksSI.LooksOR[PickLooks[],patternLooks]; text _ editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; pattern _ editI.FromString["DEF"]; editI.AddLooks[pattern,patternLooks]; editI.AddLooks[text,textLooks,4,3]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern _ editI.FromString["D#@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern _ editI.FromString["D*@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern _ editI.FromString["D&@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; pattern _ editI.FromString["D@@"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#4 OR atEnd#7 THEN ERROR; }; findFlag: BOOLEAN _ FALSE; Find: PUBLIC PROC = { DoFind: PROC = { [found,at,atEnd,before,after] _ findI.Find[pattern: pattern, text: text, literal: FALSE, word: FALSE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE] }; pattern, text: Node; found: BOOLEAN; patternLooks, textLooks: Looks; at, atEnd, before, after: Offset; IF findFlag THEN RETURN; findFlag _ TRUE; -- only do this once text _ editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; -- 0 5 0 5 0 5 -- first just simple test, no special characters pattern _ editI.FromString["ghi"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- test for only lower case pattern _ editI.FromString["'g'h'i"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- pattern in uppercase, target in lower case pattern _ editI.FromString["GHI"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use # inside pattern pattern _ editI.FromString["G#I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 0 characters pattern _ editI.FromString["G*HI"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use * inside pattern to match 1 character pattern _ editI.FromString["G*I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use @ inside pattern pattern _ editI.FromString["G@I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 0 characters pattern _ editI.FromString["G&HI"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use & inside pattern to match 1 character pattern _ editI.FromString["G&I"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at OR after#atEnd THEN ERROR; -- use ! and {}'s pattern _ editI.FromString["!{GHI}!"]; DoFind; IF ~found OR at#9 OR atEnd#12 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use word search pattern _ editI.FromString["GHI"]; [found,at,atEnd,before,after] _ findI.Find[pattern: pattern, text: text, literal: FALSE, word: TRUE, ignoreLooks: FALSE, ignoreCase: TRUE, looksExact: FALSE]; IF ~found OR at#9 OR atEnd#12 OR before#at-1 OR after#atEnd+1 THEN ERROR; -- use ~ and {}'s pattern _ editI.FromString["DEF~{GHI}~x"]; DoFind; IF ~found OR at#9 OR atEnd#12 THEN ERROR; -- use | at start pattern _ editI.FromString["|abc"]; DoFind; IF ~found OR at#0 OR atEnd#3 THEN ERROR; -- use | at end pattern _ editI.FromString["ghi|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- use | at start and end pattern _ editI.FromString["|abc*{ghi}|"]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; -- requires unwinding wildcard stack pattern _ editI.FromString[".&XY~{XY}b"]; text _ editI.FromString[".aXYaXY-XYb"]; DoFind; IF ~found OR at#8 OR atEnd#10 THEN ERROR; -- test looks in patterns patternLooks _ PickLooks[]; textLooks _ looksSI.LooksOR[PickLooks[],patternLooks]; text _ editI.FromString["abc DEF .ghi. xjkly lmn ghi"]; pattern _ editI.FromString["ghi"]; editI.AddLooks[pattern,patternLooks]; editI.AddLooks[text,textLooks,24,3]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern _ editI.FromString["@#i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern _ editI.FromString["@*i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern _ editI.FromString["@&i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; pattern _ editI.FromString["@@i"]; editI.AddLooks[pattern,patternLooks]; DoFind; IF ~found OR at#24 OR atEnd#27 THEN ERROR; }; -- ***** Support routines for node edits LocateSpan: PUBLIC PROC [span: Span] RETURNS [start, len, size: Offset] = { first: Ref _ span.start.node; last: Ref _ span.end.node; root: Ref _ nodeI.Root[first]; size _ 0; start _ len _ -1; FOR n: Ref _ nodeI.FirstChild[root], nodeI.StepForward[n] DO SELECT n FROM NIL => RETURN; first => { start _ size; IF n=last THEN len _ 1 }; last => len _ size-start+1; ENDCASE; size _ size+1; ENDLOOP }; LocateNode: PUBLIC PROC [node: Ref] RETURNS [loc, size: Offset] = { root: Ref _ nodeI.Root[node]; size _ 0; loc _ -1; FOR n: Ref _ nodeI.FirstChild[root], nodeI.StepForward[n] DO SELECT n FROM NIL => RETURN; node => loc _ size; ENDCASE; size _ size+1; ENDLOOP }; PickTrees: PUBLIC PROC RETURNS [tree1, tree2: Ref] = { RETURN [PickTree[], PickTree[]] }; PickTree: PUBLIC PROC RETURNS [tree: Ref] = { RETURN [IF RandomBoolean[] THEN alphaTree ELSE betaTree] }; PickPlace: PUBLIC PROC RETURNS [Place] = { RETURN [SELECT randLI.Choose[1,3] FROM 1 => before, 2 => after, 3 => child, ENDCASE => ERROR] }; PickNodeSpan: PUBLIC PROC [tree: Ref] RETURNS [span: Span, start, len, size: Offset] = { end: Offset; first, last: Ref; IF (size _ TreeSize[tree]) = 0 THEN RETURN [nodeI.nullSpan, 0, 0, 0]; DO [start, end] _ ChooseTwo[0,size]; IF (len _ end-start) > 0 THEN EXIT; ENDLOOP; first _ FindFirstNode[tree, start]; last _ FindLastNode[first, len]; span _ nodeI.MakeNodeSpan[first, last] }; PickNodeLoc: PUBLIC PROC [tree: Ref] RETURNS [dest: Location, loc, size: Offset] = { node: Ref; IF (size _ TreeSize[tree]) = 0 THEN RETURN [nodeI.nullLocation, 0, 0]; loc _ Choose[0,size-1]; node _ FindTreeNode[tree, loc]; dest _ nodeI.MakeNodeLoc[node] }; FindTreeNode, FindFirstNode: PUBLIC PROC [tree: Ref, loc: Offset] RETURNS [node: Ref] = { place: Offset _ 0; FOR n: Ref _ nodeI.FirstChild[tree], nodeI.StepForward[n] DO IF place=loc OR n=NIL THEN RETURN [n]; place _ place+1; ENDLOOP }; FindLastNode: PUBLIC PROC [first: Ref, len: Offset] RETURNS [last: Ref] = { IF len <= 0 THEN RETURN [NIL]; FOR n: Ref _ first, nodeI.StepForward[n] DO IF (len_len-1)=0 OR n=NIL THEN RETURN [n]; ENDLOOP }; TreeSize: PUBLIC PROC [tree: Ref] RETURNS [size: Offset] = { size _ 0; FOR n: Ref _ nodeI.FirstChild[tree], nodeI.StepForward[n] UNTIL n=NIL DO size _ size+1; ENDLOOP }; biggestTree: Offset = 200; smallestTree: Offset = 50; AdjustTree: PUBLIC PROC [tree: Ref, size: Offset] = { DO SELECT size FROM > biggestTree => size _ DeleteFromTree[tree, size, size-biggestTree]; < smallestTree => size _ InsertInTree[tree, size, smallestTree-size]; ENDCASE => RETURN; ENDLOOP }; -- ***** Support routines Choose: PUBLIC PROC [min,max: Offset] RETURNS [Offset] = { IF min >= max THEN RETURN [max]; RETURN [SELECT randLI.Choose[1,8] FROM 1 => min, 2 => max, ENDCASE => LOOPHOLE[randLI.Choose[min,max]]] }; ChooseTwo: PUBLIC PROC [min,max: Offset] RETURNS [first,second: Offset] = { temp: Offset; first _ Choose[min,max]; second _ Choose[min,max]; IF first > second THEN { temp_first; first_second; second_temp }}; ChooseNAT: PUBLIC PROC [min,max: NAT] RETURNS [NAT] = { maxNAT: NAT = LAST[NAT]; IF min >= max THEN RETURN [max]; RETURN [SELECT randLI.Choose[1,8] FROM 1 => min, 2 => max, ENDCASE => LOOPHOLE[Inline.LowHalf[randLI.Choose[min,max]]]] }; ChooseTwoNATs: PUBLIC PROC [min,max: NAT] RETURNS [first,second: NAT] = { temp: NAT; first _ ChooseNAT[min,max]; second _ ChooseNAT[min,max]; IF first > second THEN { temp_first; first_second; second_temp }}; RandomBoolean: PUBLIC PROC RETURNS [BOOLEAN] = { RETURN [randC.Choose[0,1]=0] }; OneInN: PUBLIC PROC [n: NAT] RETURNS [BOOLEAN] = { RETURN [randC.Choose[1,n]=1] }; PickNode: PUBLIC PROC RETURNS [Node] = { RETURN [ IF RandomBoolean[] THEN alpha ELSE beta] }; PickNodes: PUBLIC PROC RETURNS [source,dest: Node] = { RETURN [PickNode[], PickNode[]] }; PickOne: PUBLIC PROC [node: Node] RETURNS [loc: Offset] = { -- picks a random places within node RETURN [Choose[0,editI.Size[node]]] }; PickTwo: PUBLIC PROC [node: Node] RETURNS [start, end: Offset] = { -- picks two random places within node -- orders them such that start <= end size, temp: Offset; IF (size _ editI.Size[node]) = 0 THEN RETURN; start _ Choose[0,size]; end _ Choose[0,size]; IF start > end THEN { temp_start; start_end; end_temp }}; PickLooks: PUBLIC PROC RETURNS [Looks] = { RETURN [LOOPHOLE[randLI.Random[]]] }; PickRope: PUBLIC PROC RETURNS [Rope] = { source: Node _ PickNode[]; sourceStart, sourceEnd: Offset; [sourceStart,sourceEnd] _ PickTwo[source]; RETURN [ropeI.Substr[editI.GetRope[source],sourceStart,sourceEnd-sourceStart]] }; AdjustLengths: PUBLIC PROC = { AdjustLength[alpha]; AdjustLength[beta] }; AdjustLength: PUBLIC PROC [node: Node] = { DO SELECT editI.Size[node] FROM > longest => DeleteFromNode[node]; < shortest => InsertStringInNode[node]; ENDCASE => RETURN; ENDLOOP }; event: PUBLIC Event _ undoI.Create[]; undoEvent: PUBLIC Event _ undoI.Create[]; txt1, txt2: Node; t1Rope, t2Rope: rI.Ref; t1Runs, t2Runs: looksI.Runs; BeforeUndo: PUBLIC PROC [n1, n2: Node _ NIL] = { txt1 _ n1; t1Rope _ txt1.rope; t1Runs _ txt1.runs; IF n2 = NIL OR n2 = n1 THEN txt2 _ NIL ELSE { txt2 _ n2; t2Rope _ txt2.rope; t2Runs _ txt2.runs }; undoI.Reset[event] }; TestUndo: PUBLIC PROC = { -- t1RopeBefore, t2RopeBefore, t1RopeAfter, t2RopeAfter: rI.Ref; t1RunsBefore, t2RunsBefore, t1RunsAfter, t2RunsAfter: looksI.Runs; oldSize1, oldSize2, newSize1, newSize2: Offset; node1, node2: nodeI.RefTextNode; t1RopeBefore _ t1Rope; t2RopeBefore _ t2Rope; -- set by PreEdit t1RunsBefore _ t1Runs; t2RunsBefore _ t2Runs; -- set by PreEdit IF (node1 _ txt1) # NIL THEN { t1RopeAfter _ node1.rope; t1RunsAfter _ node1.runs }; IF (node2 _ txt2) # NIL THEN { t2RopeAfter _ node2.rope; t2RunsAfter _ node2.runs }; undoI.Reset[undoEvent]; undoI.Undo[event,undoEvent]; IF node1 # NIL THEN { oldSize1 _ ropeI.Size[t1RopeBefore]; IF ropeI.Size[node1.rope] # oldSize1 THEN ERROR; CheckRopes[node1.rope,0,t1RopeBefore,0,oldSize1]; CheckRuns[node1.runs,0,t1RunsBefore,0,oldSize1] }; IF node2 # NIL THEN { oldSize2 _ ropeI.Size[t2RopeBefore]; IF ropeI.Size[node2.rope] # oldSize2 THEN ERROR; CheckRopes[node2.rope,0,t2RopeBefore,0,oldSize2]; CheckRuns[node2.runs,0,t2RunsBefore,0,oldSize2] }; undoI.Undo[undoEvent]; -- undo the previous undo IF node1 # NIL THEN { newSize1 _ ropeI.Size[t1RopeAfter]; IF ropeI.Size[node1.rope] # newSize1 THEN ERROR; CheckRopes[node1.rope,0,t1RopeAfter,0,newSize1]; CheckRuns[node1.runs,0,t1RunsAfter,0,newSize1] }; IF node2 # NIL THEN { newSize2 _ ropeI.Size[t2RopeAfter]; IF ropeI.Size[node2.rope] # newSize2 THEN ERROR; CheckRopes[node2.rope,0,t2RopeAfter,0,newSize2]; CheckRuns[node2.runs,0,t2RunsAfter,0,newSize2] }}; -- ***** Initialization numEdits: PUBLIC NAT _ 0; PreEdit: PROC [t1, t2: nodeI.Ref, change: REF READONLY notify.Change] = { IF t1 = NIL THEN ERROR; IF t1.kind # text THEN ERROR; IF t2 # NIL AND t2.kind # text THEN ERROR }; PostEdit: PROC [t1, t2: nodeI.Ref, change: REF READONLY notify.Change] = { OPEN checkI; IF t1 # NIL THEN WITH x:t1 SELECT FROM text => CheckTextNode[@x]; ENDCASE => ERROR; IF t2 # NIL THEN WITH x:t2 SELECT FROM text => CheckTextNode[@x]; ENDCASE => ERROR; }; Flatten: PROC [text: Node] = { -- flatten if more than pieceMax pieces pieceMax: Offset = 250; oldRope: Rope _ text.rope; oldRuns: Runs _ text.runs; newRope: Rope; newRuns: Runs; oldSize: Offset _ ropeI.Size[oldRope]; ropePieces: Offset _ ropeI.CountPieces[oldRope]; IF ropePieces > pieceMax THEN { text.rope _ newRope _ ropeI.Flatten[oldRope]; text.runs _ newRuns _ looksI.Flatten[oldRuns]; checkI.CheckTextNode[text]; CheckRopes[newRope,0,oldRope,0,oldSize]; CheckRuns[newRuns,0,oldRuns,0,oldSize]; text.count _ 0 } ELSE { text.count _ text.count/2 }}; InitReaders: PROC = { runrdr1 _ runrdrI.Create[]; runrdr2 _ runrdrI.Create[]; roperdr1 _ roperdrI.Create[]; roperdr2 _ roperdrI.Create[] }; InitNodes: PROC = { alpha _ editI.FromRope[rI.FromString[ "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"]]; beta _ editI.FromRope[rI.FromString[ "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"]]; AdjustLengths[]; }; InitTrees: PROC = { treeFileName: Rope _ rI.FromString["TestTree.Text"]; alphaTree _ GetTree.ReadIndent[treeFileName]; TreeCheck.Verify[alphaTree]; betaTree _ GetTree.ReadIndent[treeFileName]; TreeCheck.Verify[betaTree]; }; brkAt: NAT _ LAST[NAT]; testNum: NAT; Run: PUBLIC PROC [shuffle: NAT _ 0, numberOfTests: NAT _ LAST[NAT], mode: RunMode _ both] = { [] _ randC.InitRandom[]; [] _ randLI.InitRandom[]; notify.AddNotifyProc[PreEdit,before]; notify.AddNotifyProc[PostEdit,after]; InitReaders[]; InitNodes[]; InitTrees[]; FOR i:CARDINAL IN [0..numTests) DO testCountArray[i] _ 0; ENDLOOP; FOR i:CARDINAL IN [0..numNodeTests) DO nodeTestCountArray[i] _ 0; ENDLOOP; THROUGH [0..shuffle) DO [] _ randC.Choose[0,numTests-1]; ENDLOOP; UNTIL numEdits >= numberOfTests DO IF mode=nodesOnly OR (mode=both AND RandomBoolean[]) THEN { -- do a node test this time testNum _ randC.Choose[0,numNodeTests-1]; nodeTestProcArray[testNum][]; nodeTestCountArray[testNum] _ nodeTestCountArray[testNum]+1 } ELSE { -- do a text test testNum _ randC.Choose[0,numTests-1]; testProcArray[testNum][]; testCountArray[testNum] _ testCountArray[testNum]+1; IF alpha.count >= 30 THEN Flatten[alpha]; IF beta.count >= 30 THEN Flatten[beta] }; IF (numEdits _ numEdits+1) >= brkAt THEN { -- break here foo: NAT _ 0; foo _ foo+1 }; ENDLOOP }; END.