diff options
author | apnadkarni <apnmbx-wits@yahoo.com> | 2022-07-17 17:00:52 (GMT) |
---|---|---|
committer | apnadkarni <apnmbx-wits@yahoo.com> | 2022-07-17 17:00:52 (GMT) |
commit | be522119abc546c0f97b40ce6396cb9b73f35041 (patch) | |
tree | 9f31a63262e680a5c242b1ad72009c6cf67bf5a7 | |
parent | a034ecec4fd9513f2d8df0636bdbca1118fc7938 (diff) | |
download | tcl-be522119abc546c0f97b40ce6396cb9b73f35041.zip tcl-be522119abc546c0f97b40ce6396cb9b73f35041.tar.gz tcl-be522119abc546c0f97b40ce6396cb9b73f35041.tar.bz2 |
Another batch of white box listrep tests
-rw-r--r-- | generic/tclListObj.c | 66 | ||||
-rw-r--r-- | tests/listRep.test | 288 |
2 files changed, 312 insertions, 42 deletions
diff --git a/generic/tclListObj.c b/generic/tclListObj.c index 3a3c531..8419b4e 100644 --- a/generic/tclListObj.c +++ b/generic/tclListObj.c @@ -1447,7 +1447,7 @@ ListRepRange( /* Take the opportunity to garbage collect */ /* TODO - we probably do not need the preserveSrcRep here unlike later */ if (!preserveSrcRep) { - /* T:listrep-1.{4,5,8,9} */ + /* T:listrep-1.{4,5,8,9},2.{4,5,6,7} */ ListRepFreeUnreferenced(srcRepPtr); } @@ -1505,9 +1505,7 @@ ListRepRange( } else if (ListSpanMerited(rangeLen, srcRepPtr->storePtr->numUsed, srcRepPtr->storePtr->numAllocated)) { - /* - * Option 2 - because span would be most efficient - */ + /* Option 2 - because span would be most efficient */ ListSizeT spanStart = ListRepStart(srcRepPtr) + rangeStart; if (!preserveSrcRep && srcRepPtr->spanPtr && srcRepPtr->spanPtr->refCount <= 1) { @@ -1516,7 +1514,8 @@ ListRepRange( srcRepPtr->spanPtr->spanLength = rangeLen; *rangeRepPtr = *srcRepPtr; } else { - /* Span not present or is shared. T:listrep-1.5 */ + /* Span not present or is shared. */ + /* T:listrep-1.5,2.{5,7} */ rangeRepPtr->storePtr = srcRepPtr->storePtr; rangeRepPtr->spanPtr = ListSpanNew(spanStart, rangeLen); } @@ -1527,10 +1526,12 @@ ListRepRange( * is mandated. */ if (!preserveSrcRep) { + /* T:listrep-2.{5,7} */ ListRepFreeUnreferenced(rangeRepPtr); } } else if (preserveSrcRep || ListRepIsShared(srcRepPtr)) { /* Option 3 - span or modification in place not allowed/desired */ + /* T:listrep-2.{4,6} */ ListRepElements(srcRepPtr, numSrcElems, srcElems); /* TODO - allocate extra space? */ ListRepInit(rangeLen, @@ -1858,7 +1859,7 @@ Tcl_ListObjAppendList( LIST_ASSERT(listRep.storePtr->numAllocated >= finalLen); if (toLen) { - /* T:listrep-2.2 */ + /* T:listrep-2.{2,9} */ ObjArrayCopy(ListRepSlotPtr(&listRep, 0), toLen, toObjv); } ObjArrayCopy(ListRepSlotPtr(&listRep, toLen), elemCount, elemObjv); @@ -2129,21 +2130,24 @@ Tcl_ListObjReplace( /* Check Case (1) - Treat pure deletes from front or back as range ops */ if (numToInsert == 0) { if (numToDelete == 0) { - /* Should force canonical even for no-op */ - /* T:listrep-1.10 */ + /* + * Should force canonical even for no-op. Remember Tcl_Obj unshared + * so OK to invalidate string rep + */ + /* T:listrep-1.10,2.8 */ TclInvalidateStringRep(listObj); return TCL_OK; } if (first == 0) { /* Delete from front, so return tail. */ - /* T:listrep-1.{4,5} */ + /* T:listrep-1.{4,5},2.{4,5} */ ListRep tailRep; ListRepRange(&listRep, numToDelete, origListLen-1, 0, &tailRep); ListObjReplaceRepAndInvalidate(listObj, &tailRep); return TCL_OK; } else if ((first+numToDelete) >= origListLen) { /* Delete from tail, so return head */ - /* T:listrep-1.{8,9} */ + /* T:listrep-1.{8,9},2.{6,7} */ ListRep headRep; ListRepRange(&listRep, 0, first-1, 0, &headRep); ListObjReplaceRepAndInvalidate(listObj, &headRep); @@ -2161,7 +2165,7 @@ Tcl_ListObjReplace( if (numToDelete == 0) { /* Case (2a) - Append to list. */ if (first == origListLen) { - /* T:listrep-1.11 */ + /* T:listrep-1.11,2.9 */ return TclListObjAppendElements( interp, listObj, numToInsert, insertObjs); } @@ -2217,7 +2221,7 @@ Tcl_ListObjReplace( * ListObjReplaceAndInvalidate below. */ if (numFreeSlots < lenChange && !ListRepIsShared(&listRep)) { - /* T:listrep-1.{1,3} */ + /* T:listrep-1.{1,3,14,18} */ ListStore *newStorePtr = ListStoreReallocate(listRep.storePtr, origListLen + lenChange); if (newStorePtr == NULL) { @@ -2255,24 +2259,24 @@ Tcl_ListObjReplace( &newRep); toObjs = ListRepSlotPtr(&newRep, 0); if (leadSegmentLen > 0) { - /* T:listrep-2.{2,3} */ + /* T:listrep-2.{2,3,13,14,15} */ ObjArrayCopy(toObjs, leadSegmentLen, listObjs); } if (numToInsert > 0) { - /* T:listrep-2.{1,2,3} */ + /* T:listrep-2.{1,2,3,10,11,12,13,14,15} */ ObjArrayCopy(&toObjs[leadSegmentLen], numToInsert, insertObjs); } if (tailSegmentLen > 0) { - /* T:listrep-2.{1,2,3} */ + /* T:listrep-2.{1,2,3,10,11,12,13,14,15} */ ObjArrayCopy(&toObjs[leadSegmentLen + numToInsert], tailSegmentLen, &listObjs[leadSegmentLen+numToDelete]); } newRep.storePtr->numUsed = origListLen + lenChange; if (newRep.spanPtr) { - /* T:listrep-2.{1,2,3} */ + /* T:listrep-2.{1,2,3,10,11,12,13,14,15} */ newRep.spanPtr->spanLength = newRep.storePtr->numUsed; } LISTREP_CHECK(&newRep); @@ -2305,15 +2309,21 @@ Tcl_ListObjReplace( * for objects to be inserted in case there is overlap. T:listobj-11.1 */ if (numToInsert) { - /* T:listrep-1.{1,3} */ + /* T:listrep-1.{1,3,12,13,14,15,16,17,18} */ ObjArrayIncrRefs(insertObjs, 0, numToInsert); } if (numToDelete) { - /* T:listrep-1.{6,7} */ + /* T:listrep-1.{6,7,12,13,14,15,16,17,18} */ ObjArrayDecrRefs(listObjs, first, numToDelete); } /* + * TODO - below the moves are optimized but this may result in needing a + * span allocation. Perhaps for small lists, it may be more efficient to + * just move everything up front and save on allocating a span. + */ + + /* * Calculate shifts if necessary to accomodate insertions. * NOTE: all indices are relative to listObjs which is not necessarily the * start of the ListStore storage area. @@ -2324,7 +2334,7 @@ Tcl_ListObjReplace( */ if (lenChange == 0) { - /* Exact fit */ + /* T:listrep-1.{12,15}. Exact fit */ leadShift = 0; tailShift = 0; } else if (lenChange < 0) { @@ -2334,12 +2344,12 @@ Tcl_ListObjReplace( */ if (leadSegmentLen > tailSegmentLen) { /* Tail segment smaller. Insert after lead, move tail down */ - /* T:listrep-1.7 */ + /* T:listrep-1.{7,17} */ leadShift = 0; tailShift = lenChange; } else { /* Lead segment smaller. Insert before tail, move lead up */ - /* T:listrep-1.6 */ + /* T:listrep-1.{6,13,16} */ leadShift = -lenChange; tailShift = 0; } @@ -2388,7 +2398,7 @@ Tcl_ListObjReplace( if (finalFreeSpace > 1 && (leadSpace == 0 || leadSegmentLen == 0)) { ListSizeT postShiftTailSpace = tailSpace - lenChange; if (postShiftTailSpace > (finalFreeSpace/2)) { - /* T:listrep-1.{1,3} */ + /* T:listrep-1.{1,3,14,18} */ ListSizeT extraShift = postShiftTailSpace - (finalFreeSpace / 2); tailShift += extraShift; leadShift = extraShift; /* Move head to the back as well */ @@ -2432,14 +2442,14 @@ Tcl_ListObjReplace( if (leadShift > 0) { /* Will happen when we have to make room at bottom */ if (tailShift != 0 && tailSegmentLen != 0) { - /* T:listrep-1.{1,3} */ + /* T:listrep-1.{1,3,14,18} */ ListSizeT tailStart = leadSegmentLen + numToDelete; memmove(&listObjs[tailStart + tailShift], &listObjs[tailStart], tailSegmentLen * sizeof(Tcl_Obj *)); } if (leadSegmentLen != 0) { - /* T:listrep-1.{3,6} */ + /* T:listrep-1.{3,6,16,18} */ memmove(&listObjs[leadShift], &listObjs[0], leadSegmentLen * sizeof(Tcl_Obj *)); @@ -2451,7 +2461,7 @@ Tcl_ListObjReplace( leadSegmentLen * sizeof(Tcl_Obj *)); } if (tailShift != 0 && tailSegmentLen != 0) { - /* T:listrep-1.7 */ + /* T:listrep-1.{7,17} */ ListSizeT tailStart = leadSegmentLen + numToDelete; memmove(&listObjs[tailStart + tailShift], &listObjs[tailStart], @@ -2460,7 +2470,7 @@ Tcl_ListObjReplace( } if (numToInsert) { /* Do NOT use ObjArrayCopy here since we have already incr'ed ref counts */ - /* T:listrep-1.{1,3} */ + /* T:listrep-1.{1,3,12,13,14,15,16,17,18} */ memmove(&listObjs[leadSegmentLen + leadShift], insertObjs, numToInsert * sizeof(Tcl_Obj *)); @@ -2477,10 +2487,10 @@ Tcl_ListObjReplace( } else { /* Need a new span record */ if (listRep.storePtr->firstUsed == 0) { - /* T:listrep-1.7 */ + /* T:listrep-1.{7,12,15,17} */ listRep.spanPtr = NULL; } else { - /* T:listrep-1.{1,3,6} */ + /* T:listrep-1.{1,3,13,14,16,18} */ listRep.spanPtr = ListSpanNew(listRep.storePtr->firstUsed, listRep.storePtr->numUsed); } diff --git a/tests/listRep.test b/tests/listRep.test index 654ae5d..203bb39 100644 --- a/tests/listRep.test +++ b/tests/listRep.test @@ -63,14 +63,22 @@ proc validate {l} { testlistrep validate $l } proc leadSpaceMore {l} { - expr {[leadSpace $l] >= 2*[tailSpace $l]} + set leadSpace [leadSpace $l] + expr {$leadSpace > 0 && $leadSpace >= 2*[tailSpace $l]} } proc tailSpaceMore {l} { - expr {[tailSpace $l] >= 2*[leadSpace $l]} + set tailSpace [tailSpace $l] + expr {$tailSpace > 0 && $tailSpace >= 2*[leadSpace $l]} } proc spaceEqual {l} { - # 1 if lead and tail space shared (diff of 1 at most) - set diff [expr {[leadSpace $l] - [tailSpace $l]}] + # 1 if lead and tail space shared (diff of 1 at most) and more than 0 + set leadSpace [leadSpace $l] + set tailSpace [tailSpace $l] + if {$leadSpace == 0 && $tailSpace == 0} { + # At least one must be positive + return 0 + } + set diff [expr {$leadSpace - $tailSpace}] return [expr {$diff >= -1 && $diff <= 1}] } proc hasSpan {l args} { @@ -153,6 +161,7 @@ if {[testConstraint testlistrep]} { # operations completely in byte code if indices are literals set zero 0 set one 1 +set two 2 set four 4 set end end @@ -238,7 +247,7 @@ test listrep-1.9 { } -result [list [irange 0 994] 0 5 0] test listrep-1.10 { - lreplace no-op should force a canonical list representation + lreplace no-op on unshared list should force a canonical list representation } -body { lreplace { 1 2 3 4 } $zero -1 } -result {1 2 3 4} @@ -248,22 +257,102 @@ test listrep-1.11 { so no free space in front } -body { # Note $end, not end else byte code compiler short-cuts - set l [lreplace [freeSpaceNone 1000] $end+1 $end+1 99] + set l [lreplace [freeSpaceNone 1000] $end+1 $end+1 1000] list $l [leadSpace $l] [expr {[tailSpace $l] > 0}] [hasSpan $l] -} -result [list [linsert [irange 0 999] end+1 99] 0 1 0] +} -result [list [irange 0 1000] 0 1 0] + +test listrep-1.12 { + Replacement of elements at front with same number elements in unshared list + is in-place +} -body { + set l [lreplace [freeSpaceNone] $zero $one 10 11] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {10 11 2 3 4 5 6 7} 0 0] + +test listrep-1.13 { + Replacement of elements at front with fewer elements in unshared list + results in a spanned list with space only in front +} -body { + set l [lreplace [freeSpaceNone] $zero $four 10] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {10 5 6 7} 4 0] + +test listrep-1.14 { + Replacement of elements at front with more elements in unshared list + results in a reallocated spanned list with space at front and back +} -body { + set l [lreplace [freeSpaceNone] $zero $one 10 11 12] + list $l [spaceEqual $l] +} -result [list {10 11 12 2 3 4 5 6 7} 1] + +test listrep-1.15 { + Replacement of elements in middle with same number elements in unshared list + is in-place +} -body { + set l [lreplace [freeSpaceNone] $one $two 10 11] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {0 10 11 3 4 5 6 7} 0 0] + +test listrep-1.16 { + Replacement of elements in front half with fewer elements in unshared list + results in a spanned list with space only in front since smaller segment moved +} -body { + set l [lreplace [freeSpaceNone] $one $four 10] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {0 10 5 6 7} 3 0] + +test listrep-1.17 { + Replacement of elements in back half with fewer elements in unshared list + results in a spanned list with space only at back +} -body { + set l [lreplace [freeSpaceNone] end-$four end-$one 10] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {0 1 2 10 7} 0 3] + +test listrep-1.18 { + Replacement of elements in middle more elements in unshared list + results in a reallocated spanned list with space at front and back +} -body { + set l [lreplace [freeSpaceNone] $one $two 10 11 12] + list $l [spaceEqual $l] +} -result [list {0 10 11 12 3 4 5 6 7} 1] + +test listrep-1.19 { + Replacement of elements at back with same number elements in unshared list + is in-place +} -body { + set l [lreplace [freeSpaceNone] $end-1 $end 10 11] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {0 1 2 3 4 5 10 11} 0 0] + +test listrep-1.20 { + Replacement of elements at back with fewer elements in unshared list + is in-place with space only at the back +} -body { + set l [lreplace [freeSpaceNone] $end-2 $end 10] + list $l [leadSpace $l] [tailSpace $l] +} -result [list {0 1 2 3 4 10} 0 2] + +test listrep-1.21 { + Replacement of elements at back with more elements in unshared list + allocates new representation with equal space at front and back +} -body { + set l [lreplace [freeSpaceNone] $end-1 $end 10 11 12] + list $l [spaceEqual $l] +} -result [list {0 1 2 3 4 5 10 11 12} 1] # -# listrep-2.* tests all operate on shared lists with no free space -# The lrange construct on an variable's value will result in a listrep -# that is shared (it's not enough that the Tcl_Obj is shared so just -# assigning to another variable does not suffice) +# listrep-2.* tests all operate on shared list reps with no free space. Note the +# *list internal rep* must be shared, not only the Tcl_Obj so just assigning to +# another variable does not suffice. The lrange construct on an variable's value +# will do the needful. test listrep-2.1 { Inserts in front of shared list with no free space should reallocate with more leading space in front } -constraints testlistrep -body { set a [freeSpaceNone] - set b [lrange $a 0 end] + set b [lrange $a 0 end]; # Ensure shared listrep set l [linsert $b $zero 99] validate $l list [repStoreRefCount $b] $l [leadSpaceMore $l] [repStoreRefCount $l] @@ -274,7 +363,7 @@ test listrep-2.2 { more leading space in back } -constraints testlistrep -body { set a [freeSpaceNone] - set b [lrange $a 0 end] + set b [lrange $a 0 end]; # Ensure shared listrep set l [linsert $b $end 99] validate $l list [repStoreRefCount $b] $l [tailSpaceMore $l] [repStoreRefCount $l] @@ -285,12 +374,183 @@ test listrep-2.3 { equal spacing } -constraints testlistrep -body { set a [freeSpaceNone] - set b [lrange $a 0 end] + set b [lrange $a 0 end]; # Ensure shared listrep set l [linsert $b $four 99] validate $l list [repStoreRefCount $b] $l [spaceEqual $l] [repStoreRefCount $l] } -result [list 2 {0 1 2 3 99 4 5 6 7} 1 1] +test listrep-2.4 { + Deletes from front of small shared list with no free space should + allocate new list of exact size +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $zero $zero] + validate $l + list [repStoreRefCount $b] $l [leadSpace $l] [tailSpace $l] [repStoreRefCount $l] +} -result [list 2 {1 2 3 4 5 6 7} 0 0 1] + +test listrep-2.5 { + Deletes from front of large shared list with no free space should + create span +} -constraints testlistrep -body { + set a [freeSpaceNone 1000] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $zero $zero] + validate $l + # The listrep store should be shared among a, b, l (3 refs) + list [repStoreRefCount $b] $l [hasSpan $l] [leadSpace $l] [tailSpace $l] [repStoreRefCount $l] +} -result [list 3 [irange 1 999] 1 0 0 3] + +test listrep-2.6 { + Deletes from back of small shared list with no free space should + allocate new list of exact size +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $end $end] + validate $l + list [repStoreRefCount $b] $l [leadSpace $l] [tailSpace $l] [repStoreRefCount $l] +} -result [list 2 {0 1 2 3 4 5 6} 0 0 1] + +test listrep-2.7 { + Deletes from back of large shared list with no free space should + use a span +} -constraints testlistrep -body { + set a [freeSpaceNone 1000] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $end $end] + validate $l + # Note lead and tail space is 0 because original list store in a,b is used + list [repStoreRefCount $b] $l [leadSpace $l] [tailSpace $l] [repStoreRefCount $l] +} -result [list 3 [irange 0 998] 0 0 3] + +test listrep-2.8 { + lreplace no-op on shared list should force a canonical list representation + with original unchanged +} -body { + set l { 1 2 3 4 } + list [lreplace $l $zero -1] $l +} -result [list {1 2 3 4} { 1 2 3 4 }] + +test listrep-2.9 { + Appends to back of large shared list with no free space allocates new + list with space only at the back. +} -constraints testlistrep -body { + set a [freeSpaceNone 1000] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $end+1 $end+1 1000] + validate $l + list [repStoreRefCount $b] $l [leadSpace $l] [expr {[tailSpace $l]>0}] [repStoreRefCount $l] +} -result [list 2 [irange 0 1000] 0 1 1] + +test listrep-2.10 { + Replacement of elements at front with same number elements in shared list + results in a new list store with more space in front than back +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $zero $one 10 11] + validate $l + list [repStoreRefCount $b] $l [leadSpaceMore $l] [repStoreRefCount $l] +} -result [list 2 {10 11 2 3 4 5 6 7} 1 1] + +test listrep-2.11 { + Replacement of elements at front with fewer elements in shared list + results in a new list store with more space in front than back +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $zero $four 10] + validate $l + list [repStoreRefCount $b] $l [leadSpaceMore $l] [repStoreRefCount $l] +} -result [list 2 {10 5 6 7} 1 1] + +test listrep-2.12 { + Replacement of elements at front with more elements in shared list + results in a new spanned list with more space in front +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $zero $one 10 11 12] + validate $l + list [repStoreRefCount $b] $l [leadSpaceMore $l] [repStoreRefCount $l] +} -result [list 2 {10 11 12 2 3 4 5 6 7} 1 1] + +test listrep-2.13 { + Replacement of elements in middle with same number elements in shared list + results in a new list store with equal space in front and back +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $one $two 10 11] + validate $l + list [repStoreRefCount $b] $l [spaceEqual $l] [repStoreRefCount $l] +} -result [list 2 {0 10 11 3 4 5 6 7} 1 1] + +test listrep-2.14 { + Replacement of elements in middle with fewer elements in shared list + results in a new list store with equal space +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $one 5 10] + validate $l + list [repStoreRefCount $b] $l [spaceEqual $l] [repStoreRefCount $l] +} -result [list 2 {0 10 6 7} 1 1] + +test listrep-2.15 { + Replacement of elements in middle with more elements in shared list + results in a new spanned list with space in front and back +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b $one $two 10 11 12] + validate $l + list [repStoreRefCount $b] $l [spaceEqual $l] [repStoreRefCount $l] +} -result [list 2 {0 10 11 12 3 4 5 6 7} 1 1] + +test listrep-2.16 { + Replacement of elements at back with same number elements in shared list + results in a new list store with more space in back than front +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b end-$one $end 10 11] + validate $l + list [repStoreRefCount $b] $l [tailSpaceMore $l] [repStoreRefCount $l] +} -result [list 2 {0 1 2 3 4 5 10 11} 1 1] + +test listrep-2.17 { + Replacement of elements at back with fewer elements in shared list + results in a new list store with more space in back than front +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b end-$four $end 10] + validate $l + list [repStoreRefCount $b] $l [tailSpaceMore $l] [repStoreRefCount $l] +} -result [list 2 {0 1 2 10} 1 1] + +test listrep-2.18 { + Replacement of elements at back with more elements in shared list + results in a new list store with more space in back than front +} -constraints testlistrep -body { + set a [freeSpaceNone] + set b [lrange $a 0 end]; # Ensure shared listrep + set l [lreplace $b end-$four $end 10] + validate $l + list [repStoreRefCount $b] $l [tailSpaceMore $l] [repStoreRefCount $l] +} -result [list 2 {0 1 2 10} 1 1] + + +# TBD - tests on spanned lists +# TBD - tests when tcl-obj is shared but listrep is not (lappend, lset etc.) +# TBD - range and subrange tests +# - spanned and unspanned # +# Special case - nested lremove (does seem tested even in 8.6) + ::tcltest::cleanupTests return |