# -*- Tcl -*- package require nx; namespace import ::nx::* ::nx::configure defaultMethodCallProtection false package require nx::test Test parameter count 10 ::nsf::method::alias ::nx::Object set -frame object ::set Class create O -superclass Object { :method init {} { set ::ObjectDestroy 0 set ::firstDestroy 0 } :method destroy {} { incr ::ObjectDestroy #[:info class] dealloc [current] next } } # # classical simple case # set case "simple destroy (1)" Test case simple-destroy-1 Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" :destroy puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 ? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBBB" ? {::nsf::object::exists c1} 1 "$::case object still exists in proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" } C create c1 c1 foo ? {::nsf::object::exists c1} 0 "$::case object deleted" ? "set ::firstDestroy" 1 "firstDestroy called" # # simple case, destroy does not propagate, c1 survives # set case "simple destroy (2), destroy blocks" Test case simple-destroy-2 Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy block"} C method foo {} { puts stderr "==== $::case [current]" :destroy puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 ? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBBB" ? {::nsf::object::exists c1} 1 "$::case object still exists in proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" } C create c1 c1 foo ? {::nsf::object::exists c1} 1 "$::case object deleted" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" # # simple object recreate # set case "recreate" Test case recreate Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" [:info class] create [current] puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 ? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBBB" ? {::nsf::object::exists c1} 1 "$::case object still exists in proc" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" } C create c1 c1 foo ? {::nsf::object::exists c1} 1 "$::case object deleted" ? "set ::firstDestroy" 0 "firstDestroy called" # # cmd rename to empty, xotcl provides its own rename and calls destroy # .. like simple case above # set case "cmd rename empty (1)" Test case rename-empty-1 Object create o Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" rename [current] "" puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 ? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" ? {::nsf::object::exists c1} 1 "$::case object still exists in proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" } C create c1 c1 foo ? {::nsf::object::exists c1} 0 "$::case object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" # # cmd rename to empty, xotcl provides its own rename and calls # destroy, but destroy does not propagate, c1 survives rename, since # this is the situation like above, as long xotcl's rename is used. # set case "cmd rename empty (2)" Test case rename-empty-2 Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy block"} C method foo {} { puts stderr "==== $::case [current]" rename [current] "" puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 ? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" ? {::nsf::object::exists c1} 1 "$::case object still exists in proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" } C create c1 c1 foo #puts stderr ======[c1 set x] ? {::nsf::object::exists c1} 1 "$::case object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" # # cmd rename other xotcl object to current, # xotcl's rename invokes a move # set case "cmd rename object to current" Test case rename-to-current Object create o Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" rename o [current] puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 ? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" ? {::nsf::object::exists c1} 1 "$::case object still exists in proc" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" } C create c1 c1 foo ? {::nsf::object::exists c1} 1 "$::case object still exists after proc" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" # # cmd rename other proc to current object, # xotcl's rename invokes a move # set case "cmd rename proc to current" Test case rename-proc-to-current proc o args {} Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" set x [catch {rename o [current]}] ? "set _ $x" 1 "$::case tcl refuses to rename into an existing command" } C create c1 c1 foo ? {::nsf::object::exists c1} 1 "$::case object still exists after proc" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" # # namespace delete: tcl delays delete until the namespace is not # active anymore. destroy is called after BBBB. Hypothesis: destroy is # called only when we are lucky, since C might be destroyed before c1 # by the namespace delete # set case "delete parent namespace (1)" Test case delete-parent-namespace namespace eval ::test { Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" namespace delete ::test puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 # # If the following line is commented in, the namespace is deleted # here. Is there a bug with nsPtr->activationCount # #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" puts stderr "???? [current] exists [::nsf::object::exists [current]]" ? "::nsf::object::exists [current]" 0 ;# WHY? puts stderr "???? [current] exists [::nsf::object::exists [current]]" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "$::case destroy not yet called" } } test::C create test::c1 test::c1 foo ? {::nsf::object::exists test::c1} 0 "object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "destroy was called when poping stack frame" ? {::nsf::object::exists ::test::C} 0 "class still exists after proc" ? {namespace exists ::test::C} 0 "namespace ::test::C still exists after proc" ? {namespace exists ::test} 1 "parent ::test namespace still exists after proc" ? {namespace exists ::xotcl::classes::test::C} 0 "namespace ::xotcl::classes::test::C still exists after proc" # # namespace delete: tcl delays delete until the namespace is not # active anymore. destroy is called after BBBB, but does not # propagate. # set case "delete parent namespace (2)" Test case delete-parent-namespace-2 namespace eval ::test { ? {namespace exists test::C} 0 "exists test::C" Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy block"} C method foo {} { puts stderr "==== $::case [current]" namespace delete ::test puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 # # If the following line is commented in, the namespace is deleted # here. Is there a bug with nsPtr->activationCount # #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBBB" puts stderr "???? [current] exists [::nsf::object::exists [current]]" ? "::nsf::object::exists [current]" 0 "$::case object still exists in proc";# WHY? puts stderr "???? [current] exists [::nsf::object::exists [current]]" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called"; # NOT YET CALLED } } test::C create test::c1 test::c1 foo ? {::nsf::object::exists test::c1} 0 "$::case object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" ;# toplevel destroy was blocked # # controlled namespace delete: xotcl has its own namespace cleanup, # topological order should be always ok. however, the object o::c1 is # already deleted, while a method of it is excuted # set case "delete parent object (1)" Test case delete-parent-object Object create o Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" o destroy puts stderr "AAAA" # the following object::exists call has a problem in Tcl_GetCommandFromObj(), # which tries to access invalid memory puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBBB" ? {::nsf::object::exists ::o::c1} 0 "$::case object still exists in proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" } C create o::c1 o::c1 foo ? {::nsf::object::exists ::o::c1} 0 "$::case object o::c1 still exists after proc" ? {::nsf::object::exists o} 0 "$::case object o still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" # # controlled namespace delete: xotcl has its own namespace cleanup. # destroy does not delegate, but still o::c1 does not survive, since o # is deleted. # set case "delete parent object (2)" Test case delete-parent-object-2 Object create o Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy block"} C method foo {} { puts stderr "==== $::case [current]" o destroy puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" ? {::nsf::object::exists ::o::c1} 0 "$::case object still exists in proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" } C create o::c1 o::c1 foo ? {::nsf::object::exists ::o::c1} 0 "$::case object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" # # create an other cmd with the current object's name. # xotcl 1.6 crashed on this test # set case "redefined current object as proc" Test case redefined-current-object-as-proc Object create o Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" proc [current] {args} {puts HELLO} puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" ? {::nsf::object::exists c1} 0 "$::case object still exists in proc" } C create c1 c1 foo ? {::nsf::object::exists c1} 0 "$::case object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" # # delete the active class # set case "delete active class" Test case delete-active-class Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" C destroy puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" #? [:info class] ::xotcl::Object "object reclassed" ? [:info class] ::C "object reclassed?" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" ? {::nsf::object::exists c1} 1 "object still exists in proc" #? {::nsf::is class ::C} 0 "class still exists in proc" ? {::nsf::is class ::C} 1 "class still exists in proc" } C create c1 c1 foo ? {::nsf::object::exists c1} 1 "object still exists after proc" ? [c1 info class] ::nx::Object "after proc: object reclassed?" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "ObjectDestroy called" # # delete active object nested in class # set case "delete active object nested in class" Test case delete-active-object-nested-in-class Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" C destroy puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" #? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::firstDestroy" 1 "firstDestroy called" #? "set ::ObjectDestroy" 0 "ObjectDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" ? [:info class] ::C "object reclassed" #? [:info class] ::xotcl::Object "object reclassed" ? {::nsf::object::exists ::C::c1} 1 "object still exists in proc" ? {::nsf::is class ::C} 1 "class still exists in proc" } C create ::C::c1 C::c1 foo #puts stderr ======[::nsf::object::exists ::C::c1] ? {::nsf::object::exists ::C::c1} 0 "object still exists after proc" ? {::nsf::is class ::C} 0 "class still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "ObjectDestroy called" # Test case nesting-destroy { Object create x Object create x::y x destroy ? {::nsf::object::exists x} 0 "parent object gone" ? {::nsf::object::exists x::y} 0 "child object gone" } Test case deleting-aliased-object { Object create o Object create o2 ::nsf::method::alias o a o2 ? {o a} ::o2 "call object via alias" ? {o info method type a} alias ## the ensemble-object needs per-object methods o2 method info args {:info {*}$args} o2 method set args {:set {*}$args} ? {o a info vars} "" "call info on aliased object" ? {o set x 10} 10 "set variable on object" ? {o info vars} x "query vars" ? {o a info vars} x "query vars via alias" ? {o a set x} 10 "set var via alias" o2 destroy ? {o a info vars} "Trying to dispatch deleted object via method 'a'" "1st call on deleted object" ? {o a info vars} "::o: unable to dispatch method 'a'" "2nd call on deleted object" } set case "deleting object with alias to object" Test case deleting-object-with-alias-to-object Object create o Object create o3 ::nsf::method::alias o x o3 o destroy ? {::nsf::object::exists o} 0 "parent object gone" ? {::nsf::object::exists o3} 1 "aliased object still here" o3 destroy ? {::nsf::object::exists o3} 0 "aliased object destroyed" set case "create an alias, and delete cmd via aggregation" Test case create-alias-delete-via-aggregation Object create o Object create o3 ::nsf::method::alias o x o3 o::x destroy ? {::nsf::object::exists o3} 0 "aliased object destroyed" o destroy # # create an alias, and recreate obj # Test case create-alias-and-recreate-obj { Object create o Object create o3 o alias x o3 Object create o3 o3 method set args {:set {*}$args} o set a 13 ? {o x set a} 13 "aliased object works after recreate" } # # create an alias on the class level, double aliasing, delete aliased # object # Test case create-alias-on-class-delete-aliased-obj { Class create C Object create o Object create o3 o alias a o3 C alias b o o3 method set args {:set {*}$args} o method set args {:set {*}$args} C create c1 ? {c1 b set B 2} 2 "call 1st level" ? {c1 b a set A 3} 3 "call 2nd level" ? {c1 set B} 2 "call 1st level ok" ? {c1 set A} 3 "call 2nd level ok" o destroy ? {c1 b} "Trying to dispatch deleted object via method 'b'" "call via alias to deleted object" } # # create an alias on the class level, double aliasing, destroy class # Test case create-alias-on-class-destroy-class { Class create C Object create o Object create o3 o alias a o3 C alias b o C create c1 C destroy ? {::nsf::object::exists o} 1 "object o still here" ? {::nsf::object::exists o3} 1 "object o3 still here" } # # test cases where preexisting namespaces are re-used # Test case module { # create a namespace with an object/class in it namespace eval ::module { Object create foo } # reuse the namespace for a class/object Class create ::module ? {::nsf::is class ::module} 1 # delete the object/class ... and namespace ::module destroy ? {::nsf::is class ::module} 0 } Test case namespace-import { namespace eval ::module { Class create Foo { :create foo } namespace export Foo foo } Class create ::module { :create mod1 } ? {::nsf::is class ::module::Foo} 1 ? {::nsf::is class ::module::foo} 0 ? {::nsf::object::exists ::module::foo} 1 ? {::nsf::is class ::module} 1 Object create ::o { :require namespace } namespace eval ::o {namespace import ::module::*} ? {::nsf::is class ::o::Foo} 1 ? {::nsf::object::exists ::o::foo} 1 # do not destroy namespace imported objects/classes ::o destroy ? {::nsf::is class ::o::Foo} 0 ? {::nsf::object::exists ::o::foo} 0 ? {::nsf::is class ::module::Foo} 1 ? {::nsf::object::exists ::module::foo} 1 ::module destroy } # to avoid CallDirectly, we could activate this line ::nx::Class create M {:method dealloc args {next}} Test case delete-parent-namespace-dealloc namespace eval ::test { Class create C -superclass O C method destroy {} {incr ::firstDestroy; puts stderr " *** [current] destroy"; next} C method foo {} { puts stderr "==== $::case [current]" namespace delete ::test puts stderr "AAAA [current] exists [::nsf::object::exists [current]]" :set x 1 # # If the following line is commented in, the namespace is deleted # here. Is there a bug with nsPtr->activationCount # #? "[current] set x" 1 "$::case can still access [current]" puts stderr "BBB" puts stderr "???? [current] exists [::nsf::object::exists [current]]" ? "::nsf::object::exists [current]" 0 ;# WHY? puts stderr "???? [current] exists [::nsf::object::exists [current]]" ? "set ::firstDestroy" 0 "firstDestroy called" ? "set ::ObjectDestroy" 0 "$::case destroy not yet called" } } test::C create test::c1 test::c1 foo ? {::nsf::object::exists test::c1} 0 "object still exists after proc" ? "set ::firstDestroy" 1 "firstDestroy called" ? "set ::ObjectDestroy" 1 "destroy was called when poping stack frame" ? {::nsf::object::exists ::test::C} 0 "class still exists after proc" ? {namespace exists ::test::C} 0 "namespace ::test::C still exists after proc" ? {namespace exists ::test} 1 "parent ::test namespace still exists after proc" ? {namespace exists ::xotcl::classes::test::C} 0 "namespace ::xotcl::classes::test::C still exists after proc" Test case destroy-during-init { # create class nx::Class create Foo { :public method bar {} {return 1} :public method baz {} {return 2} } # create object Foo create f1 { :bar; :baz; :destroy } ? {info command f1} "" "explicit destroy of object" set c [nx::Class new { :public method bar {} {return 1} :public method baz {} {return 2} :new { :bar; :baz; :destroy } :destroy }] ? [list info command $c] "" "explicit destroy of class" # create new class and object and cleanup everything set x [nx::Class new { :volatile :public method bar {} {return 1} :public method baz {} {return 2} :new { :volatile; :bar; :baz } }] ? [list info command $x] "" "destroy via volatile" set x [nx::Class new -volatile { :public method bar {} {return 1} :public method baz {} {return 2} :new { :volatile; :bar; :baz } }] ? [list info command $x] $x "destroy via volatile method" # create new class and object and cleanup everything + 2 filters ::nx::Object public method f1 args {next} ::nx::Object public method f2 args {next} ::nx::Object filter {f1 f2} set x [nx::Class new { :volatile :public method bar {} {return 1} :public method baz {} {return 2} :new { :volatile; :bar; :baz } }] ? [list info command $x] "" "destroy via volatile + 2 filters" set x [nx::Class new -volatile { :public method bar {} {return 1} :public method baz {} {return 2} :new { :volatile; :bar; :baz } }] ? [list info command $x] $x "destroy via volatile method + 2 filters" ::nx::Object filter "" } Test case nested-ordered-composite { # The following test case an explicit deletion/redefinition of an # toplevel object (o1) will cause the implicit deletion of a nested # object o1::o2. The object o2 has as well several included objects, # containing an "ordered composite". The deletion of the ordered # compostite causes the (explicit) deletion of its siblings (all # children of o1::o2). This is actually a stress test for the deletion # of o2's namespace, since the loop over its children will be # confronted with the deletion of indirectly deleted items (deleted by # the deletion of the ordered composite). Class create C { :attribute os :public method destroy {} { #puts stderr "[self] destroy ${:os}" foreach o ${:os} { if {[::nsf::object::exists $o]} { #puts stderr "--D $o destroy" $o destroy } next } } } # # 10 siblings of oc1: # deletion order in bucket: 8 4 10 9 5 1 6 2 oc1 7 3 # oc1 deletes 7 and 3, fine # ... loop might run into an epoched cmd -> might crash # set c 0 for {set i 0} {$i < 10} {incr i} { set os [list] for {set j 0} {$j < 10} {incr j} {lappend os ::o1::o2::[incr c]} Object create ::o1 Object create ::o1::o2 foreach o $os {Object create $o} C create ::o1::o2::oc1 -os $os ? {llength [o1 info children]} 1 ? {llength [o1::o2 info children]} 11 } ### 20 siblings of oc1 (has to be >12): # deletion order in bucket: 17 18 1 20 19 2 3 4 5 6 7 8 9 19 11 oc1 12 13 14 15 16 # oc1 deletes 12 13 14 15 16 # after destroy of oc1 # a) NextHashEntry(hSearch) returns valid looking hPtr # b) Tcl_GetHashValue(hPtr) returns garbage (uninitialized memory?) instead of cmd # --> might crash # set c 0 for {set i 0} {$i < 10} {incr i} { set os [list] for {set j 0} {$j < 20} {incr j} {lappend os ::o1::o2::[incr c]} Object create ::o1 Object create ::o1::o2 foreach o $os {Object create $o} C create ::o1::o2::oc1 -os $os ? {llength [o1 info children]} 1 ? {llength [o1::o2 info children]} 21 } # similar to above, but this time partial deletes occur set c 0 for {set i 0} {$i < 10} {incr i} { set os [list] for {set j 0} {$j < 20} {incr j} {lappend os ::o1::o2::[incr c]} Object create ::o1 Object create ::o1::o2 foreach o $os {Object create $o} C create ::o1::o2::ocX -os {} C create ::o1::o2::ocY -os $os ? {llength [o1 info children]} 1 ? {llength [o1::o2 info children]} 22 } } #puts stderr "==== EXIT ===="