source [file dirname [info script]]/testing.tcl
needs cmd namespace

test namespace-1.1 {usage for "namespace" command} -body {
    namespace
} -returnCodes error -match glob -result {wrong # args: should be *}

test namespace-1.2 {global namespace's name is "::" or {}} {
    list [namespace current] [namespace eval {} {namespace current}] [namespace eval :: {namespace current}]
} {:: :: ::}

test namespace-1.3 {usage for "namespace eval"} -body {
    namespace eval
} -returnCodes error -match glob -result {wrong # args: should be "namespace eval *"}

test namespace-1.5 {access a new namespace} {
    namespace eval ns1 { namespace current }
} {::ns1}

test namespace-1.7 {usage for "namespace eval"} -body {
    namespace eval ns1
} -returnCodes error -match glob -result {wrong # args: should be "namespace eval *"}

test namespace-1.8 {command "namespace eval" concatenates args} {
    namespace eval ns1 namespace current
} {::ns1}

test namespace-1.9 {simple namespace elements} {
    namespace eval ns1 {
        variable v1 1
        proc p1 {a} {variable v1; list $a $v1}
        p1 3
    }
} {3 1}

test namespace-1.10 {commands in a namespace} {
    namespace eval ns1 {
        info commands [namespace current]::*
    }
} {::ns1::p1}

test namespace-1.11 {variables in a namespace} {
    namespace eval ns1 {
        info vars [namespace current]::*
    }
} {::ns1::v1}

test namespace-1.12 {global vars are separate from locals vars} {
    set v1 2
    list [ns1::p1 123] [set ns1::v1] [set ::v1]
} {{123 1} 1 2}

test namespace-1.13 {add to an existing namespace} {
    namespace eval ns1 {
        variable v2 22
        proc p2 {script} {variable v2; eval $script}
        p2 {return $v2}
    }
} 22

test namespace-1.14 {commands in a namespace} {
    lsort [namespace eval ns1 {info commands [namespace current]::*}]
} {::ns1::p1 ::ns1::p2}

test namespace-1.15 {variables in a namespace} {
    lsort [namespace eval ns1 {info vars [namespace current]::*}]
} {::ns1::v1 ::ns1::v2}

# Tcl produces fully scoped names here
test namespace-1.16 {variables in a namespace} jim {
    lsort [info vars ns1::*]
} {ns1::v1 ns1::v2}

test namespace-1.17 {commands in a namespace are hidden} -body {
    v2 {return 3}
} -returnCodes error -result {invalid command name "v2"}

test namespace-1.18 {using namespace qualifiers} {
    ns1::p2 {return 44}
} 44

test namespace-1.19 {using absolute namespace qualifiers} {
    ::ns1::p2 {return 55}
} 55

test namespace-1.20 {variables in a namespace are hidden} -body {
    set v2
}  -returnCodes error -result {can't read "v2": no such variable}

test namespace-1.21 {using namespace qualifiers} {
    list $ns1::v1 $ns1::v2
} {1 22}

test namespace-1.22 {using absolute namespace qualifiers} {
    list $::ns1::v1 $::ns1::v2
} {1 22}

test namespace-1.23 {variables can be accessed within a namespace} {
    ns1::p2 {
        variable v1
        variable v2
        list $v1 $v2
    }
} {1 22}

test namespace-1.24 {setting global variables} {
    ns1::p2 {
		variable v1
        set v1 new
    }
    namespace eval ns1 {
        variable v1
        variable v2
        list $v1 $v2
    }
} {new 22}

test namespace-1.25 {qualified variables don't need a global declaration} {
    namespace eval ns2 { variable x 456 }
    set cmd {set ::ns2::x}
    ns1::p2 "$cmd some-value"
	set ::ns2::x
} {some-value}

test namespace-1.26 {namespace qualifiers are okay after $'s} {
    namespace eval ns1 { variable x; variable y; set x 12; set y 34 }
    set cmd {list $::ns1::x $::ns1::y}
    list [ns1::p2 $cmd] [eval $cmd]
} {{12 34} {12 34}}

test namespace-1.27 {can create commands with null names} {
    proc ns1:: {args} {return $args}
	ns1:: x
} {x}

test namespace-1.28 {namespace variable with array element syntax} -body {
	namespace eval ns1 {
		variable x(3) y
	}
} -returnCodes error -result {can't define "x(3)": name refers to an element in an array}

unset -nocomplain ns1::x ns1::y

# -----------------------------------------------------------------------
# TEST: using "info" in namespace contexts
# -----------------------------------------------------------------------
test namespace-2.1 {querying:  info commands} {
    lsort [ns1::p2 {info commands [namespace current]::*}]
} {::ns1:: ::ns1::p1 ::ns1::p2}

test namespace-2.2 {querying:  info procs} {
    lsort [ns1::p2 {info procs}]
} {{} p1 p2}

# Tcl produces fully scoped names here
test namespace-2.3 {querying:  info vars} jim {
    lsort [info vars ns1::*]
} {ns1::v1 ns1::v2}

test namespace-2.4 {querying:  info vars} {
    lsort [ns1::p2 {info vars [namespace current]::*}]
} {::ns1::v1 ::ns1::v2}

test namespace-2.5 {querying:  info locals} {
    lsort [ns1::p2 {info locals}]
} {script}

test namespace-2.6 {querying:  info exists} {
    ns1::p2 {info exists v1}
} {0}

test namespace-2.7 {querying:  info exists} {
    ns1::p2 {info exists v2}
} {1}

test namespace-2.8 {querying:  info args} {
    info args ns1::p2
} {script}

test namespace-2.9 {querying:  info body} {
    string trim [info body ns1::p1]
} {variable v1; list $a $v1}

# -----------------------------------------------------------------------
# TEST: namespace qualifiers, namespace tail
# -----------------------------------------------------------------------
test namespace-3.1 {usage for "namespace qualifiers"} {
    list [catch "namespace qualifiers" msg] $msg
} {1 {wrong # args: should be "namespace qualifiers string"}}

test namespace-3.2 {querying:  namespace qualifiers} {
    list [namespace qualifiers ""] \
         [namespace qualifiers ::] \
         [namespace qualifiers x] \
         [namespace qualifiers ::x] \
         [namespace qualifiers foo::x] \
         [namespace qualifiers ::foo::bar::xyz]
} {{} {} {} {} foo ::foo::bar}

test namespace-3.3 {usage for "namespace tail"} {
    list [catch "namespace tail" msg] $msg
} {1 {wrong # args: should be "namespace tail string"}}

test namespace-3.4 {querying:  namespace tail} {
    list [namespace tail ""] \
         [namespace tail ::] \
         [namespace tail x] \
         [namespace tail ::x] \
         [namespace tail foo::x] \
         [namespace tail ::foo::bar::xyz]
} {{} {} x x x xyz}

# -----------------------------------------------------------------------
# TEST: namespace hierarchy
# -----------------------------------------------------------------------
test namespace-5.1 {define nested namespaces} {
    set test_ns_var_global "var in ::"
    proc test_ns_cmd_global {} {return "cmd in ::"}
    namespace eval nsh1 {
        set test_ns_var_hier1 "particular to hier1"
        proc test_ns_cmd_hier1 {} {return "particular to hier1"}
        proc test_ns_show {} {return "[namespace current]: 1"}
        namespace eval nsh2 {
            set test_ns_var_hier2 "particular to hier2"
            proc test_ns_cmd_hier2 {} {return "particular to hier2"}
            proc test_ns_show {} {return "[namespace current]: 2"}
            namespace eval nsh3a {}
            namespace eval nsh3b {}
        }
        namespace eval nsh2a {}
        namespace eval nsh2b {}
    }
} {}

test namespace-5.2 {namespaces can be nested} {
    list [namespace eval nsh1 {namespace current}] \
         [namespace eval nsh1 {
              namespace eval nsh2 {namespace current}
          }]
} {::nsh1 ::nsh1::nsh2}

test namespace-5.3 {namespace qualifiers work in namespace command} {
    list [namespace eval ::nsh1 {namespace current}] \
         [namespace eval nsh1::nsh2 {namespace current}] \
         [namespace eval ::nsh1::nsh2 {namespace current}]
} {::nsh1 ::nsh1::nsh2 ::nsh1::nsh2}

test namespace-5.4 {nested namespaces can access global namespace} {
    list [namespace eval nsh1 {set ::test_ns_var_global}] \
         [namespace eval nsh1 {test_ns_cmd_global}] \
         [namespace eval nsh1::nsh2 {set ::test_ns_var_global}] \
         [namespace eval nsh1::nsh2 {test_ns_cmd_global}]
} {{var in ::} {cmd in ::} {var in ::} {cmd in ::}}

test namespace-5.6 {commands in different namespaces don't conflict} {
    list [nsh1::test_ns_show] \
         [nsh1::nsh2::test_ns_show]
} {{::nsh1: 1} {::nsh1::nsh2: 2}}
test namespace-5.7 {nested namespaces don't see variables in parent} {
    set cmd {
        namespace eval nsh1::nsh2 {set test_ns_var_hier1}
    }
    list [catch $cmd msg] $msg
} {1 {can't read "test_ns_var_hier1": no such variable}}
test namespace-5.8 {nested namespaces don't see commands in parent} {
    set cmd {
        namespace eval nsh1::nsh2 {test_ns_cmd_hier1}
    }
    list [catch $cmd msg] $msg
} {1 {invalid command name "test_ns_cmd_hier1"}}

test namespace-5.18 {usage for "namespace parent"} {
    list [catch {namespace parent x y} msg] $msg
} {1 {wrong # args: should be "namespace parent ?name?"}}

test namespace-5.20 {querying namespace parent} {
    list [namespace eval :: {namespace parent}] \
        [namespace eval nsh1 {namespace parent}] \
        [namespace eval nsh1::nsh2 {namespace parent}] \
        [namespace eval nsh1::nsh2::nsh3a {namespace parent}] \
} {{} :: ::nsh1 ::nsh1::nsh2}

test namespace-5.21 {querying namespace parent for explicit namespace} {
    list [namespace parent ::] \
         [namespace parent nsh1] \
         [namespace parent nsh1::nsh2] \
         [namespace parent nsh1::nsh2::nsh3a]
} {{} :: ::nsh1 ::nsh1::nsh2}

# -----------------------------------------------------------------------
# TEST: name resolution and caching
# -----------------------------------------------------------------------
test namespace-6.1 {relative ns names only looked up in current ns} {
    namespace eval tns1 {}
    namespace eval tns2 {}
    namespace eval tns2::test_ns_cache3 {}
    set trigger {
        namespace eval tns2 {namespace current}
    }
    set trigger2 {
        namespace eval tns2::test_ns_cache3 {namespace current}
    }
    list [namespace eval tns1 $trigger] \
         [namespace eval tns1 $trigger2]
} {::tns1::tns2 ::tns1::tns2::test_ns_cache3}
test namespace-6.2 {relative ns names only looked up in current ns} {
    namespace eval tns1::tns2 {}
    list [namespace eval tns1 $trigger] \
         [namespace eval tns1 $trigger2]
} {::tns1::tns2 ::tns1::tns2::test_ns_cache3}
test namespace-6.3 {relative ns names only looked up in current ns} {
    namespace eval tns1::tns2::test_ns_cache3 {}
    list [namespace eval tns1 $trigger] \
         [namespace eval tns1 $trigger2]
} {::tns1::tns2 ::tns1::tns2::test_ns_cache3}
test namespace-6.4 {relative ns names only looked up in current ns} {
    namespace delete tns1::tns2
    list [namespace eval tns1 $trigger] \
         [namespace eval tns1 $trigger2]
} {::tns1::tns2 ::tns1::tns2::test_ns_cache3}

test namespace-6.5 {define test commands} {
    proc testcmd {} {
        return "global version"
    }
    namespace eval tns1 {
        proc trigger {} {
            testcmd
        }
    }
    tns1::trigger
} {global version}

test namespace-6.6 {one-level check for command shadowing} {
    proc tns1::testcmd {} {
        return "cache1 version"
    }
    tns1::trigger
} {cache1 version}

test namespace-6.7 {renaming commands changes command epoch} {
    namespace eval tns1 {
        rename testcmd testcmd_new
    }
    tns1::trigger
} {global version}
test namespace-6.8 {renaming back handles shadowing} {
    namespace eval tns1 {
        rename testcmd_new testcmd
    }
    tns1::trigger
} {cache1 version}
test namespace-6.9 {deleting commands changes command epoch} {
    namespace eval tns1 {
        rename testcmd ""
    }
    tns1::trigger
} {global version}
test namespace-6.10 {define test namespaces} {
    namespace eval tns2 {
        proc testcmd {} {
            return "global cache2 version"
        }
    }
    namespace eval tns1 {
        proc trigger {} {
            tns2::testcmd
        }
    }
    namespace eval tns1::tns2 {
        proc trigger {} {
            testcmd
        }
    }
    list [tns1::trigger] [tns1::tns2::trigger]
} {{global cache2 version} {global version}}

test namespace-6.11 {commands affect all parent namespaces} {
    proc tns1::tns2::testcmd {} {
        return "cache2 version"
    }
    list [tns1::trigger] [tns1::tns2::trigger]
} {{cache2 version} {cache2 version}}

# -----------------------------------------------------------------------
# TEST: uplevel/upvar across namespace boundaries
# -----------------------------------------------------------------------
# Note that Tcl behaves a little differently for uplevel and upvar

test namespace-7.1 {uplevel in namespace eval} jim {
	set x 66
    namespace eval uns1 {
		variable y 55
		set x 33
        uplevel 1 set x
    }
} {66}

test namespace-7.2 {upvar in ns proc} jim {
	proc uns1::getvar {v} {
		variable y
		upvar $v var
		list $var $y
	}
	uns1::getvar x
} {66 55}

# -----------------------------------------------------------------------
# TEST: scoped values
# -----------------------------------------------------------------------
test namespace-10.1 {define namespace for scope test} {
    namespace eval ins1 {
        variable x "x-value"
        proc show {args} {
            return "show: $args"
        }
        proc do {args} {
            return [eval $args]
        }
        list [set x] [show test]
    }
} {x-value {show: test}}

test namespace-10.2 {command "namespace code" requires one argument} {
    list [catch {namespace code} msg] $msg
} {1 {wrong # args: should be "namespace code arg"}}

test namespace-10.3 {command "namespace code" requires one argument} {
    list [catch {namespace code first "second arg" third} msg] $msg
} {1 {wrong # args: should be "namespace code arg"}}

test namespace-10.4 {command "namespace code" gets current namesp context} {
    namespace eval ins1 {
        namespace code {"1 2 3" "4 5" 6}
    }
} {::namespace inscope ::ins1 {"1 2 3" "4 5" 6}}

test namespace-10.5 {with one arg, first "scope" sticks} {
    set sval [namespace eval ins1 {namespace code {one two}}]
    namespace code $sval
} {::namespace inscope ::ins1 {one two}}

test namespace-10.6 {with many args, each "scope" adds new args} {
    set sval [namespace eval ins1 {namespace code {one two}}]
    namespace code "$sval three"
} {::namespace inscope ::ins1 {one two} three}

test namespace-10.7 {scoped commands work with eval} {
    set cref [namespace eval ins1 {namespace code show}]
    list [eval $cref "a" "b c" "d e f"]
} {{show: a b c d e f}}

test namespace-10.8 {scoped commands execute in namespace context} {
    set cref [namespace eval ins1 {
        namespace code {variable x; set x "some new value"}
    }]
    list [set ins1::x] [eval $cref] [set ins1::x]
} {x-value {some new value} {some new value}}

test namespace-11.1 {command caching} {
	proc cmd1 {} { return global }
	set result {}
	namespace eval ns1 {
		proc cmd1 {} { return ns1 }
		proc cmd2 {} {
			uplevel 1 cmd1
		}
		lappend ::result [cmd2]
	}
	lappend result [ns1::cmd2]
} {ns1 global}

test namespace-12.1 {namespace import} {
	namespace eval test_ns_scope1 {
		proc a {} { return a }
		namespace export a
	}
	namespace eval test_ns_scope2 {
		namespace import ::test_ns_scope1::a
		a
	}
} {a}

test namespace-12.2 {namespace import recursive} -body {
	namespace eval test_ns_scope1 {
            namespace import [namespace current]::*
        }
} -returnCodes error -match glob -result {import pattern "*" tries to import from namespace "*" into itself}

test namespace-12.3 {namespace import loop} -setup {
    namespace eval one {
	namespace export cmd
	proc cmd {} {}
    }
    namespace eval two namespace export cmd
    namespace eval two \
	    [list namespace import [namespace current]::one::cmd]
    namespace eval three namespace export cmd
    namespace eval three \
	    [list namespace import [namespace current]::two::cmd]
} -body {
    namespace eval two [list namespace import -force \
	    [namespace current]::three::cmd]
    namespace origin two::cmd
} -cleanup {
    namespace delete one two three
} -returnCodes error -match glob -result {import pattern * would create a loop*}

foreach cmd [info commands test_ns_*] {
    rename $cmd ""
}

catch {rename cmd {}}
catch {rename cmd1 {}}
catch {rename cmd2 {}}
catch {rename ncmd {}}
catch {rename ncmd1 {}}
catch {rename ncmd2 {}}
catch {unset cref}
catch {unset trigger}
catch {unset trigger2}
catch {unset sval}
catch {unset msg}
catch {unset x}
catch {unset test_ns_var_global}
catch {unset cmd}
catch {eval namespace delete [namespace children :: test_ns_*]}

# cleanup
::tcltest::cleanupTests
return

# Local Variables:
# mode: tcl
# End:
