File: tester.tcl

package info (click to toggle)
sqlcipher 4.13.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 119,564 kB
  • sloc: ansic: 290,172; tcl: 24,955; javascript: 13,486; java: 8,153; sh: 7,784; makefile: 2,247; yacc: 1,727; cs: 307; sql: 73
file content (293 lines) | stat: -rw-r--r-- 8,049 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
########################################################################
# 2025 April 5
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#  * May you do good and not evil.
#  * May you find forgiveness for yourself and forgive others.
#  * May you share freely, never taking more than you give.
#
########################################################################
#
# Helper routines for running tests on teaish extensions
#
########################################################################
# ----- @module teaish/tester.tcl -----
#
# @section TEA-ish Testing APIs.
#
# Though these are part of the autosup dir hierarchy, they are not
# intended to be run from autosetup code. Rather, they're for use
# with/via teaish.tester.tcl and target canonical Tcl only, not JimTcl
# (which the autosetup pieces do target).

#
# @test-current-scope ?lvl?
#
# Returns the name of the _calling_ proc from ($lvl + 1) levels up the
# call stack (where the caller's level will be 1 up from _this_
# call). If $lvl would resolve to global scope "global scope" is
# returned and if it would be negative then a string indicating such
# is returned (as opposed to throwing an error).
#
proc test-current-scope {{lvl 0}} {
  #uplevel [expr {$lvl + 1}] {lindex [info level 0] 0}
  set ilvl [info level]
  set offset [expr {$ilvl  - $lvl - 1}]
  if { $offset < 0} {
    return "invalid scope ($offset)"
  } elseif { $offset == 0} {
    return "global scope"
  } else {
    return [lindex [info level $offset] 0]
  }
}

# @test-msg
#
# Emits all arugments to stdout.
#
proc test-msg {args} {
  puts "$args"
}

# @test-warn
#
# Emits all arugments to stderr.
#
proc test-warn {args} {
  puts stderr "WARNING: $args"
}

#
# @test-error msg
#
# Triggers a test-failed error with a string describing the calling
# scope and the provided message.
#
proc test-fail {args} {
  #puts stderr "ERROR: \[[test-current-scope 1]]: $msg"
  #exit 1
  error "FAIL: \[[test-current-scope 1]]: $args"
}

array set ::test__Counters {}
array set ::test__Config {
  verbose-assert 0 verbose-affirm 0
}

# Internal impl for affirm and assert.
#
# $args = ?-v? script {msg-on-fail ""}
proc test__affert {failMode args} {
  if {$failMode} {
    set what assert
  } else {
    set what affirm
  }
  set verbose $::test__Config(verbose-$what)
  if {"-v" eq [lindex $args 0]} {
    lassign $args - script msg
    if {1 == [llength $args]} {
      # If -v is the only arg, toggle default verbose mode
      set ::test__Config(verbose-$what) [expr {!$::test__Config(verbose-$what)}]
      return
    }
    incr verbose
  } else {
    lassign $args script msg
  }
  incr ::test__Counters($what)
  if {![uplevel 1 expr [list $script]]} {
    if {"" eq $msg} {
      set msg $script
    }
    set txt [join [list $what # $::test__Counters($what) "failed:" $msg]]
    if {$failMode} {
      puts stderr $txt
      exit 1
    } else {
      error $txt
    }
  } elseif {$verbose} {
    puts stderr [join [list $what # $::test__Counters($what) "passed:" $script]]
  }
}

#
# @affirm ?-v? script ?msg?
#
# Works like a conventional assert method does, but reports failures
# using [error] instead of [exit]. If -v is used, it reports passing
# assertions to stderr. $script is evaluated in the caller's scope as
# an argument to [expr].
#
proc affirm {args} {
  tailcall test__affert 0 {*}$args
}

#
# @assert ?-v? script ?msg?
#
# Works like [affirm] but exits on error.
#
proc assert {args} {
  tailcall test__affert 1 {*}$args
}

#
# @assert-matches ?-e? pattern ?-e? rhs ?msg?
#
# Equivalent to assert {[string match $pattern $rhs]} except that
# if either of those are prefixed with an -e flag, they are eval'd
# and their results are used.
#
proc assert-matches {args} {
  set evalLhs 0
  set evalRhs 0
  if {"-e" eq [lindex $args 0]} {
    incr evalLhs
    set args [lassign $args -]
  }
  set args [lassign $args pattern]
  if {"-e" eq [lindex $args 0]} {
    incr evalRhs
    set args [lassign $args -]
  }
  set args [lassign $args rhs msg]

  if {$evalLhs} {
    set pattern [uplevel 1 $pattern]
  }
  if {$evalRhs} {
    set rhs [uplevel 1 $rhs]
  }
  #puts "***pattern=$pattern\n***rhs=$rhs"
  tailcall test__affert 1 \
    [join [list \[ string match [list $pattern] [list $rhs] \]]] $msg
  # why does this not work? [list \[ string match [list $pattern] [list $rhs] \]] $msg
  # "\[string match [list $pattern] [list $rhs]\]"
}

#
# @test-assert testId script ?msg?
#
# Works like [assert] but emits $testId to stdout first.
#
proc test-assert {testId script {msg ""}} {
  puts "test $testId"
  tailcall test__affert 1 $script $msg
}

#
# @test-expect testId script result
#
# Runs $script in the calling scope and compares its result to
# $result, minus any leading or trailing whitespace.  If they differ,
# it triggers an [assert].
#
proc test-expect {testId script result} {
  puts "test $testId"
  set x [string trim [uplevel 1 $script]]
  set result [string trim $result]
  tailcall test__affert 0 [list "{$x}" eq "{$result}"] \
    "\nEXPECTED: <<$result>>\nGOT:      <<$x>>"
}

#
# @test-catch cmd ?...args?
#
# Runs [cmd ...args], repressing any exception except to possibly log
# the failure. Returns 1 if it caught anything, 0 if it didn't.
#
proc test-catch {cmd args} {
  if {[catch {
    uplevel 1 $cmd {*}$args
  } rc xopts]} {
    puts "[test-current-scope] ignoring failure of: $cmd [lindex $args 0]: $rc"
    return 1
  }
  return 0
}

#
# @test-catch-matching pattern (script|cmd args...)
#
# Works like test-catch, but it expects its argument(s) to to throw an
# error matching the given string (checked with [string match]).  If
# they do not throw, or the error does not match $pattern, this
# function throws, else it returns 1.
#
# If there is no second argument, the $cmd is assumed to be a script,
# and will be eval'd in the caller's scope.
#
# TODO: add -glob and -regex flags to control matching flavor.
#
proc test-catch-matching {pattern cmd args} {
  if {[catch {
    #puts "**** catch-matching cmd=$cmd args=$args"
    if {0 == [llength $args]} {
      uplevel 1 $cmd {*}$args
    } else {
      $cmd {*}$args
    }
  } rc xopts]} {
    if {[string match $pattern $rc]} {
      return 1
    } else {
      error "[test-current-scope] exception does not match {$pattern}: {$rc}"
    }
  }
  error "[test-current-scope] expecting to see an error matching {$pattern}"
}

if {![array exists ::teaish__BuildFlags]} {
  array set ::teaish__BuildFlags {}
}

#
# @teaish-build-flag3 flag tgtVar ?dflt?
#
# If the current build has the configure-time flag named $flag set
# then tgtVar is assigned its value and 1 is returned, else tgtVal is
# assigned $dflt and 0 is returned.
#
# Caveat #1: only valid when called in the context of teaish's default
# "make test" recipe, e.g. from teaish.test.tcl. It is not valid from
# a teaish.tcl configure script because (A) the state it relies on
# doesn't fully exist at that point and (B) that level of the API has
# more direct access to the build state. This function requires that
# an external script have populated its internal state, which is
# normally handled via teaish.tester.tcl.in.
#
# Caveat #2: defines in the style of HAVE_FEATURENAME with a value of
# 0 are, by long-standing configure script conventions, treated as
# _undefined_ here.
#
proc teaish-build-flag3 {flag tgtVar {dflt ""}} {
  upvar $tgtVar tgt
  if {[info exists ::teaish__BuildFlags($flag)]} {
    set tgt $::teaish__BuildFlags($flag)
    return 1;
  } elseif {0==[array size ::teaish__BuildFlags]} {
    test-warn \
      "\[[test-current-scope]] was called from " \
      "[test-current-scope 1] without the build flags imported."
  }
  set tgt $dflt
  return 0
}

#
# @teaish-build-flag flag ?dflt?
#
# Convenience form of teaish-build-flag3 which returns the
# configure-time-defined value of $flag or "" if it's not defined (or
# if it's an empty string).
#
proc teaish-build-flag {flag {dflt ""}} {
  set tgt ""
  teaish-build-flag3 $flag tgt $dflt
  return $tgt
}