How to get name of TCL test from the test itself - tcl

I was wondering how you would find the name of the test you're running in tcl from the test itself? I couldn't find this on google.
I'm calling another proc and passing the name of the test that is calling it, as an argument. So I would like to know which tcl command can do that for me.

This isn't an encouraged use case… but you can use info frame 1 to get the information if you use it directly inside the test.
proc example {contextTest} {
puts "Called from $contextTest"
return ok
}
tcltest::test foo-1.1 {testing the foo} {
example [lindex [dict get [info frame 1] cmd] 1]
} ok
This assumes that you're using Tcl 8.5 or later, but Tcl 8.5 is the oldest currently-supported Tcl version so that's a reasonable restriction.

I read your comments ("source ... instade of my test name") as follows: You seem to source the Tcl script file containing the tests (and Donal's instrumented tcltest), rather than batch-running the script from the command line: tclsh /path/to/your/file.tcl In this setting, there will be an extra ("eval") stack frame which distorts introspection.
To turn Donal's instrumentation more robust, I suggest actually walking the Tcl stack and watching out for a valid tcltest frame. This could look as follows:
package req tcltest
proc example {} {
for {set i 1} {$i<=[info frame]} {incr i} {
set frameInfo [info frame $i]
set frameType [dict get $frameInfo type]
set cmd [dict get $frameInfo cmd]
if {$frameType eq "source" && [lindex $cmd 0] eq "tcltest::test"} {
puts "Called from [lindex $cmd 1]"
return ok
}
}
return notok
}
tcltest::test foo-1.1 {testing the foo} {
example
} ok
This will return "Called from foo-1.1" both, when called as:
$ tclsh test.tcl
Called from foo-1.1
and
$ tclsh
% source test.tcl
Called from foo-1.1
% exit
The Tcl version used (8.5, 8.6) is not relevant. However, your are advised to upgrade to 8.6, 8.5 has reached its end of life.

Related

info vars command is not working properly inside a proc

I am tring to do some variable auto-completion using TCL (this is intended for jimtcl)
I have tried the following sequence in both tclsh and jimsh:
% set VAR1 1
1
% set VAR2 2
2
% info vars
.... tcl_pkgPath VAR1 tcl_patchLevel VAR2 argc ...
% set pattern \$V*
$V*
% set vars_pattern [string range $pattern 1 end]
V*
% puts [lsort [info vars $vars_pattern]]
VAR1 VAR2
%
this is fine.
but once I get this into a proc
% proc autocomplete_helper pattern {
# check for variables auto-completion
puts "pattern '$pattern'"
if {[regexp {\$\S+$} $pattern match]} {
set vars_pattern [string range $match 1 end]
puts "pattern '$vars_pattern'"
return [lsort [info vars $vars_pattern]]
}
puts "other stuff to do"
}
% autocomplete_helper zerazer
pattern 'zerazer'
other stuff to do
% autocomplete_helper \$V*
pattern '$V*'
pattern 'V*
%
do you have any idea why this is not working ?
The info vars command is sensitive to what its current context (obviously; it returns the currently-visible variables) and moving things into a procedure changes that. The right fix for this is to use uplevel to run the command in a different context, either uplevel 1 to run in the caller's context or uplevel #0 to run in the global context (the one at the top of the stack).
In this case, we need to be a little careful because the pattern could have metacharacters in it (it'd be weird but legal) and uplevel is eval-like; the list command will ensure we've got a well-formed command. Putting this line into your procedure at the obvious point (everything else unchanged)
# The double quotes around #0 are to fool the highlighter used on Stack Overflow
return [lsort [uplevel "#0" [list info vars $vars_pattern]]]
With that, I can do this:
% autocomplete_helper {$e*}
pattern '$e*'
pattern 'e*'
env errorCode errorInfo
Which looks right to me.
This is a namespace problem.
A proc has its own namespace. When you're running info vars at the tclsh prompt, that's the global :: namespace.
The simplest thing to do in your proc would be to add :: to your argument to info vars
return [lsort [info vars ::$vars_pattern]]
The return values will include the :: namespace prefix, so remove that first if you need to.
Funny that you're seeing this problem with an auto-completion application. I've written a Tcl script to dump out all my procs, commands, namespaces, etc into json files that I read into Vim for a custom auto-completion plugin. I found the very same problems while writing that.

Code that works in 8.4 is causing crash in 8.6, is there a better way to implement the functionality

I have a Tcl utility that makes it easy to ensure a snippet of code run at the time control flow leaves the current scope (of the proc). It crashes in Tcl 8.6.6, so I'm wondering if there is a "better" way to implement the functionality in Tcl 8.6?
An example usage is:
proc test {file} {
set fh [open $file]
::Util::Defer [list close $fh]
# ... do a bunch of stuff
# and even if we hit an error
# [close $fh] will be evaluated as we return
# from the proc
}
It's worked great in Tcl 8.4, and I use it all over my code.
As I'm still coming up to speed on all the functionality available in Tcl 8.6, I'm asking how should the ::Util::Defer proc be written to best take advantage of Tcl 8.6?
Here is the 8.4 implementation:
namespace eval ::Util {}
proc ::Util::Defer_impl {cmd args} {
uplevel 1 $cmd
}
proc ::Util::Defer {cmd} {
set vname _u_defer_var
# look for a unique variable name
while {[uplevel 1 [list info vars $vname]] != ""} {
set vname ${vname}_
}
uplevel 1 [list set $vname $cmd]
# when the variable is unset, trigger a call to the command
uplevel 1 [list trace add variable $vname unset [list ::Util::Defer_impl $cmd]]
# return a chunk of code enabling the user to cancel this if desired
return [list variable $vname unset [list ::Util::Defer_impl $cmd]]
}
Edited to add:
I appreciate the answers. To be honest, I already have other syntactic sugar for a file handle, this:
proc test {file} {
set fh [::Util::LocalFileHandle $file]
# do stuff
}
I was just hoping more for a generic solution to the ::Util::Defer - because I occasionally have two or three uses (at different locations) in the same proc. Yes, I'm leaving out the error handling if the doesn't exist or isn't readable.
Note: I have reported the bug to ActiveState and filed a bug at core.tcl.tk.
Edited to add buggy code: This is the Tcl code that causes a crash for me, it is slightly pared down to the essence (as opposed to being the full-blown ::Util::Defer).
# ---------------begin script-------------------
package require Itcl
proc ::try_uplevel {} {
return [uplevel 1 [list ::info vars _u_defer_var]]
}
itcl::class ::test_class {
constructor {} {}
public proc test_via_proc {} {
::try_uplevel
}
}
::test_class::test_via_proc
# ---------------end script-------------------
The pattern you describe is a supported one; it shouldn't crash (and indeed I can't reproduce the crash with 8.6.3 or the tip of the 8.6 support branch). The only problem it has is that if you have an error during the close (or any other deferred script) it won't report it, as you can see from this snippet (% is prompt):
% apply {{} {
::Util::Defer [list error boo]
puts hi
}}
hi
%
This is part of why I went to quite a bit of effort to provide a try command in 8.6. With that, you can do this:
proc test {filename} {
set f [open $filename]
try {
# Do stuff with $f
} finally {
close $f
}
}
It also takes care of tricky things like stitching errors thrown inside the body and the finally clause together (the body exception info is in the -during option of the finally clause's error exception info) so that if both places error you can find out about both.
% catch {
try {
error a
} finally {
error b
}
} x y
1
% puts $x
b
% puts $y
-errorstack {INNER {returnImm b {}}} -errorcode NONE -errorinfo {b
while executing
"error b"} -errorline 5 -during {-code 1 -level 0 -errorstack {INNER {returnImm a {}}} -errorcode NONE -errorinfo {a
while executing
"error a"} -errorline 3} -code 1 -level 0
Personally, I'd be more inclined to write this:
proc withreadfile {varName filename body} {
upvar 1 $varName f
set f [open $filename]
try {
return [uplevel 1 $body]
} finally {
close $f
}
}
proc test {file} {
withreadfile fh $file {
# Do stuff with $fh
}
}
Your mileage may vary.
Untested code (this exact snippet, I've used this pattern many times):
proc test file {
try {
open $file
} on ok fh {
# do stuff with fh
# more stuff
} finally {
catch {close $fh}
}
}
should be about the same. Regardless of whether you handle errors with the try structure or not, (or whether you get errors or not) the code in the finally clause is run when it ends. If you want to be able to cancel the action, use a simple if inside the clause.
Edit
In case one wants to see any errors generated when the channel is closed, it's a bad idea to just wrap it in a catch, which is necessary if the file couldn't be opened and the channel-id variable wasn't created. Alternatives include:
Checking for existence: if {[info exists fh]} {close $fh}
Propagate the closing error: using the result and options variable name arguments to catch.
Over the weekend this heavyweight solution came to mind. It leverages the itcl::local functionality to achieve the same effect. It does depend on Itcl - but since the problem is an interaction with Itcl, that seems a reasonable solution, even though it is not purely Tcl.
itcl::class Defer_impl {
constructor {cmd} {} {
set _to_eval $cmd
}
destructor {
uplevel 1 $_to_eval
}
private variable _to_eval {}
}
proc ::Util::Defer {cmd} {
uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}

Tcl Output redirection from stdout to a file

I know this question has been asked several times here. I have looked at the responses, but couldn't figure out the way it worked.Can you please help me understand.
Here it goes:
I'm trying to source a tcl script on the tclsh command line, and I want to redirect the output of that script into a file.
$ source my_script.tcl
The script my_script.tcl is something like this:
set output_file final_result
set OUT [open $output_file w]
proc calculate{} {
<commands>
return $result
}
foreach value [calculate] {
puts $output_file "$value"
}
This script still throws out the output onto the stdout, while I expected it to redirect the output into a file specified as "final_result"
Can you please help me understand where I went wrong ?
As you've described here, your program looks OK (apart from minor obvious issues). Your problem is that calculate must not write to stdout, but rather needs to return a value. Or list of values in your case, actually (since you're stuffing them through foreach).
Thus, if you're doing:
proc calculate {} {
set result {}
puts [expr {1 + 2 * 3}]
return $result
}
Then you're going to get output written stdout and an empty final_result (since it's an empty list). If you change that to:
proc calculate {} {
set result {}
lappend result [expr {1 + 2 * 3}]
return $result
}
then your code will do as expected. That is, from puts to lappend result. This is what I recommend you do.
You can capture “stdout” by overriding puts. This is a hack!
rename puts _puts
proc puts {args} {
# Detect where we're running. IMPORTANT!
if {[info level] > 1 && [lindex [info level -1] 0] eq "calculate"} {
upvar 1 result r
lappend r [lindex $args end]
} else {
_puts {*}$args
}
return
}
I'm not convinced that the code to detect whether to capture the value is what it ought to be, but it works in informal testing. (It's also possible to capture stdout itself by a few tricks, but the least horrible — a stacked “transformation” that intercepts the channel — takes a lot more code… and the other alternatives are worse in terms of subtleties.)
Assuming that calculate doesn't write to stdout, and all the other good stuff pointed out and suggested by #DonalFellows has been done...
You need to change the puts in the main script to
puts $OUT "$value"
The script as posted writes to a channel named final_result which almost certainly doesn't exist. I'd expect an error from the puts statement inside the foreach loop.
Don't forget to close the output file - either by exiting from the tclsh interpreter, or preferrably by executing
close $OUT
before you check for anything in it,

tcl proc using upvar resulting in history(nextid)

I'm getting this weird issue.
i'm using tcl 8.3
after i define this proc in a tcl shell
% proc incr { varName {amount 1}} {
puts $varName
upvar #0 $varName var
puts $varName
if {[info exists var]} {
set var [expr $var + $amount]
} else {
set var $amount
}
return $var
}
i keep getting
%
history(nextid)
history(nextid)
history(oldest)
history(oldest)
%
Everytime i hit newline "Enter" after that
any one has any idea why this is happening?
Because the history managment is written in Tcl itself, and that uses incr.
Your incr is almost equal to Tcl 8.3's incr with some differences:
The name of the variable is always printed
If the variable does not exist, it will be created.
So if you remove the first difference (the puts) everything will work as expected, just that some library commands may call your incr instead the standard incr.
The second difference is now in the core, IIRC starting with Tcl 8.5 it is not nessencary that a variable already exists pior to calling incr.
In short: What you did is fine. But don't expect to be the only one who calls an standard command.

Way to list all procedures in a Tcl file

Is there any way to list all the procedures(proc) in a myFile.tcl using another tcl file or in the same file.
You can use [info procs] before and after sourcing the file in question and compare the results to determine which procs were added. For example:
proc diff {before after} {
set result [list]
foreach name $before {
set procs($name) 1
}
foreach name $after {
if { ![info exists procs($name)] } {
lappend result $name
}
}
return [lsort $result]
}
set __before [info procs]
source myFile.tcl
set __after [info procs]
puts "Added procs: [diff $__before $__after]"
One thing I like about this solution is that the diff procedure is really just a generic set differencing utility -- it's not specific to comparing lists of defined procedures.
The cheapest way is to just open the file and use regexp to pick out the names. It's not perfectly accurate, but it does a reasonably good job.
set f [open "sourcefile.tcl"]
set data [read $f]
close $f
foreach {dummy procName} [regexp -all -inline -line {^[\s:]*proc (\S+)} $data] {
puts "Found procedure $procName"
}
Does it deal with all cases? No. Does it deal with a useful subset? Yes. Is the subset large enough for you? Quite possibly.
Yes it is, although not that easy. The basic idea is to source the file in a modified slave interp that only executes some commands:
proc proc_handler {name arguments body} {
puts $name
}
set i [interp create -safe]
interp eval $i {proc unknown args {}}
interp alias $i proc {} proc_handler
interp invokehidden source yourfile.tcl
This approach will fail if the file requires other packages (package require will not work), relies on the result of some usually auto_load'ed commands etc..
It also does not take namespaces into account. (namespace eval ::foo {proc bar a {}} creates a proc with the name ::foo::bar
For a more complex implementation you could look into auto.tcl's auto_mkindex, which has a similar goal.
Here is a different approach:
Create a temporary namespace
Source (include) the script in question, then
Use the info procs command to get a list of procs
Delete the temporary namespace upon finish
Here is my script, *list_procs.tcl*:
#!/usr/bin/env tclsh
# Script to scan a Tcl script and list all the procs
proc listProcsFromFile {fileName} {
namespace eval TempNamespace {
source $fileName
set procsList [info procs]
}
set result $::TempNamespace::procsList
namespace delete TempNamespace
return $result
}
set fileName [lindex $::argv 0]
set procsList [listProcsFromFile $fileName]
puts "File $fileName contains the following procs: $procsList"
For example, if you have the following script, procs.tcl:
proc foo {a b c} {}
proc bar {a} {}
Then running the script will produce:
$ tclsh list_procs.tcl procs.tcl
File procs.tcl contains the following procs: foo bar