summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--library/safe.tcl127
-rw-r--r--tests/safe.test339
2 files changed, 444 insertions, 22 deletions
diff --git a/library/safe.tcl b/library/safe.tcl
index 3429b9e..dcf3c82 100644
--- a/library/safe.tcl
+++ b/library/safe.tcl
@@ -249,10 +249,11 @@ proc ::safe::interpConfigure {args} {
# Optional Arguments :
# + slave name : if empty, generated name will be used
# + access_path: path list controlling where load/source can occur,
-# if empty: the master auto_path will be used.
+# if empty: the master auto_path and its subdirectories will be
+# used.
# + staticsok : flag, if 0 :no static package can be loaded (load {} Xxx)
# if 1 :static packages are ok.
-# + nestedok: flag, if 0 :no loading to sub-sub interps (load xx xx sub)
+# + nestedok : flag, if 0 :no loading to sub-sub interps (load xx xx sub)
# if 1 : multiple levels are ok.
# use the full name and no indent so auto_mkIndex can find us
@@ -278,12 +279,16 @@ proc ::safe::InterpCreate {
#
# InterpSetConfig (was setAccessPath) :
-# Sets up slave virtual auto_path and corresponding structure within
+# Sets up slave virtual access path and corresponding structure within
# the master. Also sets the tcl_library in the slave to be the first
# directory in the path.
# NB: If you change the path after the slave has been initialized you
# probably need to call "auto_reset" in the slave in order that it gets
# the right auto_index() array values.
+#
+# It is the caller's responsibility, if it supplies a non-empty value for
+# access_path, to make the first directory in the path suitable for use as
+# tcl_library, and (if ![SetAutoPathSync]), to set the slave's ::auto_path.
proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
global auto_path
@@ -309,10 +314,14 @@ proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
moved it to front of slave's access_path" NOTICE
}
+ set raw_auto_path $access_path
+
# Add 1st level sub dirs (will searched by auto loading from tcl
# code in the slave using glob and thus fail, so we add them here
# so by default it works the same).
set access_path [AddSubDirs $access_path]
+ } else {
+ set raw_auto_path {}
}
Log $slave "Setting accessPath=($access_path) staticsok=$staticsok\
@@ -343,7 +352,20 @@ proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
incr i
}
+ # Set the slave auto_path.
+ # If [SetAutoPathSync], SyncAccessPath will overwrite this value with the
+ # full access path.
+ # If ![SetAutoPathSync], Safe Base code will not change this value.
+ set tokens_auto_path {}
+ foreach dir $raw_auto_path {
+ if {[dict exists $remap_access_path $dir]} {
+ lappend tokens_auto_path [dict get $remap_access_path $dir]
+ }
+ }
+ ::interp eval $slave [list set auto_path $tokens_auto_path]
+
set morepaths [::tcl::tm::list]
+ set firstpass 1
while {[llength $morepaths]} {
set addpaths $morepaths
set morepaths {}
@@ -361,7 +383,12 @@ proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
lappend map_access_path $token $dir
lappend remap_access_path $dir $token
lappend norm_access_path [file normalize $dir]
- lappend slave_tm_path $token
+ if {$firstpass} {
+ # $dir is in [::tcl::tm::list] and belongs in the slave_tm_path.
+ # Later passes handle subdirectories, which belong in the
+ # access path but not in the module path.
+ lappend slave_tm_path $token
+ }
incr i
# [Bug 2854929]
@@ -372,6 +399,7 @@ proc ::safe::InterpSetConfig {slave access_path staticsok nestedok deletehook} {
# subdirectories.
lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
}
+ set firstpass 0
}
set state(access_path) $access_path
@@ -547,6 +575,15 @@ proc ::safe::interpDelete {slave} {
namespace upvar ::safe S$slave state
+ # Sub interpreters would be deleted automatically, but if they are managed
+ # by the Safe Base we also need to clean up, and this needs to be done
+ # independently of the cleanupHook.
+ foreach sub [interp slaves $slave] {
+ if {[info exists ::safe::S[list $slave $sub]]} {
+ ::safe::interpDelete [list $slave $sub]
+ }
+ }
+
# If the slave has a cleanup hook registered, call it. Check the
# existance because we might be called to delete an interp which has
# not been registered with us at all
@@ -613,20 +650,23 @@ proc ::safe::setLogCmd {args} {
# ------------------- END OF PUBLIC METHODS ------------
#
-# Sets the slave auto_path to the master recorded value. Also sets
-# tcl_library to the first token of the virtual path.
+# Sets the slave auto_path to its recorded access path. Also sets
+# tcl_library to the first token of the access path.
#
proc ::safe::SyncAccessPath {slave} {
+ variable AutoPathSync
namespace upvar ::safe S$slave state
set slave_access_path $state(access_path,slave)
- ::interp eval $slave [list set auto_path $slave_access_path]
+ if {$AutoPathSync} {
+ ::interp eval $slave [list set auto_path $slave_access_path]
- Log $slave "auto_path in $slave has been set to $slave_access_path"\
- NOTICE
+ Log $slave "auto_path in $slave has been set to $slave_access_path"\
+ NOTICE
+ }
# This code assumes that info library is the first element in the
- # list of auto_path's. See -> InterpSetConfig for the code which
+ # list of access path's. See -> InterpSetConfig for the code which
# ensures this condition.
::interp eval $slave [list \
@@ -690,6 +730,7 @@ proc ::safe::AliasFileSubcommand {slave subcommand name} {
# AliasGlob is the target of the "glob" alias in safe interpreters.
proc ::safe::AliasGlob {slave args} {
+ variable AutoPathSync
Log $slave "GLOB ! $args" NOTICE
set cmd {}
set at 0
@@ -712,11 +753,15 @@ proc ::safe::AliasGlob {slave args} {
while {$at < [llength $args]} {
switch -glob -- [set opt [lindex $args $at]] {
- -nocomplain - -- - -join - -tails {
+ -nocomplain - -- - -tails {
lappend cmd $opt
set got($opt) 1
incr at
}
+ -join {
+ set got($opt) 1
+ incr at
+ }
-types - -type {
lappend cmd -types [lindex $args [incr at]]
incr at
@@ -731,15 +776,20 @@ proc ::safe::AliasGlob {slave args} {
incr at
}
pkgIndex.tcl {
- # Oops, this is globbing a subdirectory in regular package
- # search. That is not wanted. Abort, handler does catch
- # already (because glob was not defined before). See
- # package.tcl, lines 484ff in tclPkgUnknown.
- return -code error "unknown command glob"
+ if {$AutoPathSync} {
+ # Oops, this is globbing a subdirectory in regular package
+ # search. That is not wanted. Abort, handler does catch
+ # already (because glob was not defined before). See
+ # package.tcl, lines 484ff in tclPkgUnknown.
+ return -code error "unknown command glob"
+ } else {
+ break
+ }
}
-* {
Log $slave "Safe base rejecting glob option '$opt'"
return -code error "Safe base rejecting glob option '$opt'"
+ # unsafe/unnecessary options rejected: -path
}
default {
break
@@ -763,7 +813,7 @@ proc ::safe::AliasGlob {slave args} {
lappend cmd -directory $dir
}
- # Apply the -join semantics ourselves
+ # Apply the -join semantics ourselves (hence -join not copied to $cmd)
if {$got(-join)} {
set args [lreplace $args $at end [join [lrange $args $at end] "/"]]
}
@@ -1105,8 +1155,49 @@ proc ::safe::Setup {} {
return
}
+# Accessor method for ::safe::SetAutoPathSync
+# Usage: ::safe::SetAutoPathSync ?newValue?
+
+proc ::safe::SetAutoPathSync {args} {
+ variable AutoPathSync
+
+ if {[llength $args] == 1} {
+ set newValue [lindex $args 0]
+ if {![string is boolean -strict $newValue]} {
+ return -code error "new value must be a valid boolean"
+ }
+ set args [expr {$newValue && $newValue}]
+ if {([info vars ::safe::S*] ne {}) && ($args != $AutoPathSync)} {
+ return -code error \
+ "cannot change AutoPathSync while Safe Base slaves exist"
+ }
+ }
+
+ set AutoPathSync {*}$args
+}
+
namespace eval ::safe {
- # internal variables
+ # internal variables (must not begin with "S")
+
+ # AutoPathSync
+ #
+ # Set AutoPathSync to 0 to give a slave's ::auto_path the same meaning as
+ # for an unsafe interpreter: the package command will search its directories
+ # and first-level subdirectories for pkgIndex.tcl files; the auto-loader
+ # will search its directories for tclIndex files. The access path and
+ # module path will be maintained as separate values, and ::auto_path will
+ # not be updated when the user calls ::safe::interpAddToAccessPath to add to
+ # the access path. If the user specifies an access path when calling
+ # interpCreate, interpInit or interpConfigure, it is the user's
+ # responsibility to define the slave's auto_path. If these commands are
+ # called with no (or empty) access path, the slave's auto_path will be set
+ # to a tokenized form of the master's auto_path, and these directories and
+ # their first-level subdirectories will be added to the access path.
+ #
+ # Set to 1 for "traditional" behavior: a slave's entire access path and
+ # module path are copied to its ::auto_path, which is updated whenever
+ # the user calls ::safe::interpAddToAccessPath to add to the access path.
+ variable AutoPathSync 1
# Log command, set via 'setLogCmd'. Logging is disabled when empty.
variable Log {}
diff --git a/tests/safe.test b/tests/safe.test
index 11ad2a9..fac52f1 100644
--- a/tests/safe.test
+++ b/tests/safe.test
@@ -17,6 +17,8 @@ if {[lsearch [namespace children] ::tcltest] == -1} {
namespace import -force ::tcltest::*
}
+testConstraint AutoSyncDefined 1
+
foreach i [interp slaves] {
interp delete $i
}
@@ -180,22 +182,46 @@ test safe-6.3 {test safe interpreters knowledge of the world} {
# leaking infos, but they still do...
# high level general test
-test safe-7.1 {tests that everything works at high level} -body {
+test safe-7.1 {tests that everything works at high level with conventional AutoPathSync} -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 1
+ }
+
set i [safe::interpCreate]
+
+} -body {
# no error shall occur:
# (because the default access_path shall include 1st level sub dirs so
# package require in a slave works like in the master)
set v [interp eval $i {package require http 2}]
# no error shall occur:
interp eval $i {http::config}
- safe::interpDelete $i
set v
+} -cleanup {
+ safe::interpDelete $i
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
} -match glob -result 2.*
-test safe-7.2 {tests specific path and interpFind/AddToAccessPath} -body {
+test safe-7.2 {tests specific path and interpFind/AddToAccessPath with conventional AutoPathSync} -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 1
+ } else {
+ set SyncVal_TMP 1
+ }
+} -body {
set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]]
# should not add anything (p0)
set token1 [safe::interpAddToAccessPath $i [info library]]
- # should add as p1
+ # should add as p* (not p1 if master has a module path)
set token2 [safe::interpAddToAccessPath $i "/dummy/unixlike/test/path"]
# an error shall occur (http is not anymore in the secure 0-level
# provided deep path)
@@ -203,6 +229,10 @@ test safe-7.2 {tests specific path and interpFind/AddToAccessPath} -body {
[catch {interp eval $i {package require http 1}} msg] $msg \
[safe::interpConfigure $i]\
[safe::interpDelete $i]
+} -cleanup {
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
} -match glob -result "{\$p(:0:)} {\$p(:*:)} 1 {can't find package http 1} {-accessPath {[list $tcl_library */dummy/unixlike/test/path]} -statics 0 -nested 1 -deleteHook {}} {}"
test safe-7.3 {check that safe subinterpreters work} {
set i [safe::interpCreate]
@@ -210,6 +240,64 @@ test safe-7.3 {check that safe subinterpreters work} {
list [interp eval $j {join {o k} ""}] [safe::interpDelete $i] [interp exists $j]
} {ok {} 0}
+test safe-7.4 {tests specific path and positive search with conventional AutoPathSync} -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 1
+ } else {
+ set SyncVal_TMP 1
+ }
+} -body {
+ set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]]
+ # should not add anything (p0)
+ set token1 [safe::interpAddToAccessPath $i [info library]]
+ # should add as p* (not p1 if master has a module path)
+ set token2 [safe::interpAddToAccessPath $i [file join [info library] http1.0]]
+ # this time, unlike test safe-7.2, http 1.0 should be found
+ list $token1 $token2 \
+ [catch {interp eval $i {package require http 1}} msg] $msg \
+ [safe::interpConfigure $i]\
+ [safe::interpDelete $i]
+ # Note that the glob match elides directories (those from the module path)
+ # other than the first and last in the access path.
+} -cleanup {
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -match glob -result "{\$p(:0:)} {\$p(:*:)} 0 1.0 {-accessPath {[list $tcl_library *$tcl_library/http1.0]} -statics 0 -nested 1 -deleteHook {}} {}"
+
+test safe-7.5 {tests positive and negative module loading with conventional AutoPathSync} -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 1
+ }
+
+ set i [safe::interpCreate]
+
+ interp eval $i {
+ package forget platform::shell
+ package forget platform
+ catch {namespace delete ::platform}
+ }
+} -body {
+ # Should raise an error (module ancestor directory issue)
+ set code1 [catch {interp eval $i {package require shell}} msg1]
+ # Should not raise an error
+ set code2 [catch {interp eval $i {package require platform::shell}} msg2]
+ return [list $code1 $msg1 $code2]
+} -cleanup {
+ safe::interpDelete $i
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -result {1 {can't find package shell} 0}
+
# test source control on file name
set i "a"
test safe-8.1 {safe source control on file} -setup {
@@ -403,6 +491,8 @@ test safe-9.6 {interpConfigure widget like behaviour} -body {
safe::interpConfigure $i]\
[safe::interpConfigure $i -deleteHook toto -nosta -nested 0
safe::interpConfigure $i]
+} -cleanup {
+ safe::interpDelete $i
} -match glob -result {{-accessPath * -statics 0 -nested 1 -deleteHook {foo bar}} {-accessPath *} {-nested 1} {-statics 0} {-deleteHook {foo bar}} {-accessPath * -statics 1 -nested 1 -deleteHook {foo bar}} {-accessPath * -statics 0 -nested 0 -deleteHook toto}}
catch {teststaticpkg Safepkg1 0 0}
@@ -831,6 +921,247 @@ test safe-16.4 {Bug 3529949: defang ~user in globs} -setup {
} -cleanup {
safe::interpDelete $i
} -result {}
+
+### 17. The first element in a slave's ::auto_path and access path must be [info library].
+
+test safe-17.1 {Check that first element of slave auto_path (and access path) is Tcl Library} -setup {
+ set lib1 [info library]
+ set lib2 [file dirname $lib1]
+ set ::auto_TMP $::auto_path
+ set ::auto_path [list $lib1 $lib2]
+
+ set i [safe::interpCreate]
+} -body {
+ set autoList {}
+ set token [lindex [$i eval set ::auto_path] 0]
+ set auto0 [dict get [set ::safe::S${i}(access_path,map)] $token]
+ set accessList [lindex [safe::interpConfigure $i -accessPath] 1]
+ return [list [lindex $accessList 0] $auto0]
+} -cleanup {
+ set ::auto_path $::auto_TMP
+ safe::interpDelete $i
+} -result [list [info library] [info library]]
+
+test safe-17.2 {Check that first element of slave auto_path (and access path) is Tcl Library, even if not true for master} -setup {
+ set lib1 [info library]
+ set lib2 [file dirname $lib1]
+ set ::auto_TMP $::auto_path
+ set ::auto_path [list $lib2 $lib1]
+ # Unexpected order, should be reversed in the slave
+
+ set i [safe::interpCreate]
+} -body {
+ set autoList {}
+ set token [lindex [$i eval set ::auto_path] 0]
+ set auto0 [dict get [set ::safe::S${i}(access_path,map)] $token]
+ set accessList [lindex [safe::interpConfigure $i -accessPath] 1]
+
+ return [list [lindex $accessList 0] $auto0]
+} -cleanup {
+ set ::auto_path $::auto_TMP
+ safe::interpDelete $i
+} -result [list [info library] [info library]]
+
+### 18. Tests for AutoSyncDefined without conventional AutoPathSync, i.e. with AutoPathSync off.
+
+test safe-18.1 {cf. safe-7.1 - tests that everything works at high level without conventional AutoPathSync} -constraints AutoSyncDefined -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 0
+ } else {
+ error {This test is meaningful only if the command ::safe::SetAutoPathSync is defined}
+ }
+
+ # Without AutoPathSync, we need a more complete auto_path, because the slave will use the same value.
+ set lib1 [info library]
+ set lib2 [file dirname $lib1]
+ set ::auto_TMP $::auto_path
+ set ::auto_path [list $lib1 $lib2]
+
+ set i [safe::interpCreate]
+} -body {
+ # no error shall occur:
+ # (because the default access_path shall include 1st level sub dirs
+ # so package require in a slave works like in the master)
+ set v [interp eval $i {package require http 1}]
+ # no error shall occur:
+ interp eval $i {http_config}
+ set v
+} -cleanup {
+ set ::auto_path $::auto_TMP
+ safe::interpDelete $i
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -result 1.0
+
+test safe-18.2 {cf. safe-7.2 - tests specific path and interpFind/AddToAccessPath without conventional AutoPathSync} -constraints AutoSyncDefined -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 0
+ } else {
+ error {This test is meaningful only if the command ::safe::SetAutoPathSync is defined}
+ }
+} -body {
+ set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]]
+ set auto1 [interp eval $i {set ::auto_path}]
+ interp eval $i {set ::auto_path [list {$p(:0:)}]}
+ # should not add anything (p0)
+ set token1 [safe::interpAddToAccessPath $i [info library]]
+ # should add as p1
+ set token2 [safe::interpAddToAccessPath $i "/dummy/unixlike/test/path"]
+ # an error shall occur (http is not anymore in the secure 0-level
+ # provided deep path)
+ list $auto1 $token1 $token2 \
+ [catch {interp eval $i {package require http 1}} msg] $msg \
+ [safe::interpConfigure $i]\
+ [safe::interpDelete $i]
+} -cleanup {
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -match glob -result "{} {\$p(:0:)} {\$p(:*:)} 1 {can't find package http 1} {-accessPath {[list $tcl_library */dummy/unixlike/test/path]} -statics 0 -nested 1 -deleteHook {}} {}"
+
+test safe-18.3 {Check that default auto_path is the same as in the master interpreter without conventional AutoPathSync} -constraints AutoSyncDefined -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 0
+ } else {
+ error {This test is meaningful only if the command ::safe::SetAutoPathSync is defined}
+ }
+
+ set i [safe::interpCreate]
+
+} -body {
+ # This file's header sets auto_path to a single directory [info library],
+ # which is the one required by Safe Base to be present & first in the list.
+
+ set ap {}
+ foreach token [$i eval set ::auto_path] {
+ lappend ap [dict get [set ::safe::S${i}(access_path,map)] $token]
+ }
+ return $ap
+} -cleanup {
+ safe::interpDelete $i
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -result [set ::auto_path]
+
+test safe-18.4 {cf. safe-7.4 - tests specific path and positive search and auto_path without conventional AutoPathSync} -constraints AutoSyncDefined -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 0
+ } else {
+ error {This test is meaningful only if the command ::safe::SetAutoPathSync is defined}
+ }
+} -body {
+ set i [safe::interpCreate -nostat -nested 1 -accessPath [list [info library]]]
+
+ # should not have been set by Safe Base:
+ set auto1 [interp eval $i {set ::auto_path}]
+
+ interp eval $i {set ::auto_path [list {$p(:0:)}]}
+
+ # should not add anything (p0)
+ set token1 [safe::interpAddToAccessPath $i [info library]]
+
+ # should add as p* (not p1 if master has a module path)
+ set token2 [safe::interpAddToAccessPath $i [file join [info library] http1.0]]
+
+ # should not have been changed by Safe Base:
+ set auto2 [interp eval $i {set ::auto_path}]
+
+ # This time, unlike test safe-18.2 and the try above, http 1.0 should be found:
+ list $auto1 $auto2 $token1 $token2 \
+ [catch {interp eval $i {package require http 1}} msg] $msg \
+ [safe::interpConfigure $i]\
+ [safe::interpDelete $i]
+} -cleanup {
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -match glob -result "{} {{\$p(:0:)}} {\$p(:0:)} {\$p(:*:)} 0 1.0 {-accessPath {[list $tcl_library *$tcl_library/http1.0]} -statics 0 -nested 1 -deleteHook {}} {}"
+
+test safe-18.5 {cf. safe-7.5 - tests positive and negative module loading without conventional AutoPathSync} -setup {
+ # All ::safe commands are loaded at start of file.
+ set SyncExists [expr {[info commands ::safe::SetAutoPathSync] ne {}}]
+
+ if {$SyncExists} {
+ set SyncVal_TMP [safe::SetAutoPathSync]
+ safe::SetAutoPathSync 0
+ } else {
+ error {This test is meaningful only if the command ::safe::SetAutoPathSync is defined}
+ }
+
+ set i [safe::interpCreate]
+
+ interp eval $i {
+ package forget platform::shell
+ package forget platform
+ catch {namespace delete ::platform}
+ }
+} -body {
+ # Should raise an error (tests module ancestor directory rule)
+ set code1 [catch {interp eval $i {package require shell}} msg1]
+ # Should not raise an error
+ set code2 [catch {interp eval $i {package require platform::shell}} msg2]
+ return [list $code1 $msg1 $code2]
+} -cleanup {
+ safe::interpDelete $i
+ if {$SyncExists} {
+ safe::SetAutoPathSync $SyncVal_TMP
+ }
+} -result {1 {can't find package shell} 0}
+
+### 19. Test tokenization of directories available to a slave.
+
+test safe-19.1 {Check that each directory of the default auto_path is a valid token} -setup {
+ set i [safe::interpCreate]
+} -body {
+ set badTokens {}
+ foreach dir [$i eval {set ::auto_path}] {
+ if {[regexp {^\$p\(:[0-9]+:\)$} $dir]} {
+ # Match - OK - token has expected form
+ } else {
+ # No match - possibly an ordinary path has not been tokenized
+ lappend badTokens $dir
+ }
+ }
+ set badTokens
+} -cleanup {
+ safe::interpDelete $i
+} -result {}
+
+test safe-19.2 {Check that each directory of the module path is a valid token} -setup {
+ set i [safe::interpCreate]
+} -body {
+ set badTokens {}
+ foreach dir [$i eval {::tcl::tm::path list}] {
+ if {[regexp {^\$p\(:[0-9]+:\)$} $dir]} {
+ # Match - OK - token has expected form
+ } else {
+ # No match - possibly an ordinary path has not been tokenized
+ lappend badTokens $dir
+ }
+ }
+ set badTokens
+} -cleanup {
+ safe::interpDelete $i
+} -result {}
+
set ::auto_path $saveAutoPath
# cleanup