1 | # ---------------------------------------------------------------------- |
---|
2 | # COMPONENT: test - run a test and query the results |
---|
3 | # |
---|
4 | # Encapsulates the testing logic, to keep it isolated from the rest of |
---|
5 | # the tester GUI. Constructor requires the location of the tool.xml |
---|
6 | # for the new version, and the test xml file containing the golden set |
---|
7 | # of results. |
---|
8 | # ====================================================================== |
---|
9 | # AUTHOR: Ben Rafferty, Purdue University |
---|
10 | # Copyright (c) 2010 Purdue Research Foundation |
---|
11 | # |
---|
12 | # See the file "license.terms" for information on usage and |
---|
13 | # redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES. |
---|
14 | # ====================================================================== |
---|
15 | |
---|
16 | namespace eval Rappture::Tester::Test { #forward declaration } |
---|
17 | |
---|
18 | itcl::class Rappture::Tester::Test { |
---|
19 | |
---|
20 | constructor {toolxml testxml} { #defined later } |
---|
21 | destructor { #defined later } |
---|
22 | |
---|
23 | private variable _added "" |
---|
24 | private variable _diffs "" |
---|
25 | private variable _missing "" |
---|
26 | private variable _ran no |
---|
27 | private variable _testxml |
---|
28 | private variable _toolxml |
---|
29 | private variable _result "" |
---|
30 | private variable _runfile "" |
---|
31 | private variable _testobj "" |
---|
32 | private variable _toolobj "" |
---|
33 | private variable _runobj "" |
---|
34 | |
---|
35 | public method getAdded {} |
---|
36 | public method getDiffs {} |
---|
37 | public method getInputs {{path input}} |
---|
38 | public method getMissing {} |
---|
39 | public method getOutputs {{path output}} |
---|
40 | public method getResult {} |
---|
41 | public method getRunfile {} |
---|
42 | public method getRunobj {} |
---|
43 | public method getTestobj {} |
---|
44 | public method getTestxml {} |
---|
45 | public method hasRan {} |
---|
46 | public method regoldenize {} |
---|
47 | public method run {} |
---|
48 | |
---|
49 | private method added {lib1 lib2 {path output}} |
---|
50 | private method compareElements {lib1 lib2 path} |
---|
51 | private method diffs {lib1 lib2 {path output}} |
---|
52 | private method makeDriver {} |
---|
53 | private method merge {toolobj golden driver {path input}} |
---|
54 | private method missing {lib1 lib2 {path output}} |
---|
55 | |
---|
56 | } |
---|
57 | |
---|
58 | # ---------------------------------------------------------------------- |
---|
59 | # CONSTRUCTOR |
---|
60 | # ---------------------------------------------------------------------- |
---|
61 | itcl::body Rappture::Tester::Test::constructor {toolxml testxml} { |
---|
62 | set _toolxml $toolxml |
---|
63 | set _testxml $testxml |
---|
64 | set _toolobj [Rappture::library $toolxml] |
---|
65 | if {![Rappture::library isvalid $_toolobj]} { |
---|
66 | error "$toolxml does not represent a valid library object" |
---|
67 | } |
---|
68 | set _testobj [Rappture::library $testxml] |
---|
69 | if {![Rappture::library isvalid $_testobj]} { |
---|
70 | error "$testxml does not represent a valid library object" |
---|
71 | } |
---|
72 | # HACK: Add a new input to differentiate between results |
---|
73 | $_testobj put input.TestRun.current "Golden" |
---|
74 | } |
---|
75 | |
---|
76 | # ---------------------------------------------------------------------- |
---|
77 | # DESTRUCTOR |
---|
78 | # ---------------------------------------------------------------------- |
---|
79 | itcl::body Rappture::Tester::Test::destructor {} { |
---|
80 | itcl::delete object $_toolobj |
---|
81 | itcl::delete object $_testobj |
---|
82 | if {$_ran} { |
---|
83 | itcl::delete object $_runobj |
---|
84 | } |
---|
85 | } |
---|
86 | |
---|
87 | # ---------------------------------------------------------------------- |
---|
88 | # USAGE: getAdded |
---|
89 | # |
---|
90 | # Return a list of paths that have been added that do not exist in the |
---|
91 | # golden results. Throws an error if the test has not been ran. |
---|
92 | # ---------------------------------------------------------------------- |
---|
93 | itcl::body Rappture::Tester::Test::getAdded {} { |
---|
94 | if {!$_ran} { |
---|
95 | error "Test has not yet been ran." |
---|
96 | } |
---|
97 | return $_added |
---|
98 | } |
---|
99 | |
---|
100 | # ---------------------------------------------------------------------- |
---|
101 | # USAGE: getDiffs |
---|
102 | # |
---|
103 | # Returns a list of paths that exist in both the golden and new results, |
---|
104 | # but contain data that does not match according to the compareElements |
---|
105 | # method. Throws an error if the test has not been ran. |
---|
106 | # ---------------------------------------------------------------------- |
---|
107 | itcl::body Rappture::Tester::Test::getDiffs {} { |
---|
108 | if {!$_ran} { |
---|
109 | error "Test has not yet been ran." |
---|
110 | } |
---|
111 | return $_diffs |
---|
112 | } |
---|
113 | |
---|
114 | # ----------------------------------------------------------------------- |
---|
115 | # USAGE: getInputs |
---|
116 | # |
---|
117 | # TODO |
---|
118 | # ----------------------------------------------------------------------- |
---|
119 | itcl::body Rappture::Tester::Test::getInputs {{path input}} { |
---|
120 | set retval [list] |
---|
121 | foreach child [$_testobj children $path] { |
---|
122 | set fullpath $path.$child |
---|
123 | if {$fullpath != "input.TestRun"} { |
---|
124 | set val [$_testobj get $fullpath.current] |
---|
125 | if {$val != ""} { |
---|
126 | lappend retval $fullpath $val |
---|
127 | } |
---|
128 | } |
---|
129 | append retval [getInputs $fullpath] |
---|
130 | } |
---|
131 | return $retval |
---|
132 | } |
---|
133 | |
---|
134 | # ---------------------------------------------------------------------- |
---|
135 | # USAGE: getMissing |
---|
136 | # |
---|
137 | # Return a list of paths that are present in the golden results, but are |
---|
138 | # missing in the new test results. Throws an error if the test has not |
---|
139 | # been ran. |
---|
140 | # ---------------------------------------------------------------------- |
---|
141 | itcl::body Rappture::Tester::Test::getMissing {} { |
---|
142 | if {!$_ran} { |
---|
143 | error "Test has not yet been ran." |
---|
144 | } |
---|
145 | return $_missing |
---|
146 | } |
---|
147 | |
---|
148 | # ---------------------------------------------------------------------- |
---|
149 | # USAGE: getOutputs |
---|
150 | # |
---|
151 | # TODO |
---|
152 | # ---------------------------------------------------------------------- |
---|
153 | itcl::body Rappture::Tester::Test::getOutputs {{path output}} { |
---|
154 | if {!$_ran} { |
---|
155 | error "Test has not yet been ran." |
---|
156 | } |
---|
157 | set retval [list] |
---|
158 | foreach child [$_runobj children $path] { |
---|
159 | set fullpath $path.$child |
---|
160 | if {$fullpath != "output.time" && $fullpath != "output.user" && $fullpath != "output.status"} { |
---|
161 | if {[lsearch $fullpath [getDiffs]] != -1} { |
---|
162 | set status diff |
---|
163 | } elseif {[lsearch $fullpath [getAdded]] != -1} { |
---|
164 | set status added |
---|
165 | #} elseif {[lsearch $fullpath [getMissing]] != -1} { |
---|
166 | # set status missing |
---|
167 | } else { |
---|
168 | if {[$_runobj get $fullpath] != ""} { |
---|
169 | set status ok |
---|
170 | } else { |
---|
171 | set status "" |
---|
172 | } |
---|
173 | } |
---|
174 | lappend retval $fullpath $status |
---|
175 | } |
---|
176 | append retval " [getOutputs $fullpath]" |
---|
177 | } |
---|
178 | # We won't find missing elements by searching through runobj. Instead, |
---|
179 | # tack on all missing items at the end (only do this once) |
---|
180 | if {$path == "output"} { |
---|
181 | foreach item $_missing { |
---|
182 | lappend retval $item missing |
---|
183 | } |
---|
184 | } |
---|
185 | return $retval |
---|
186 | } |
---|
187 | |
---|
188 | # ---------------------------------------------------------------------- |
---|
189 | # USAGE: getResult |
---|
190 | # |
---|
191 | # Returns the result of the test - either Pass, Fail, or Error. Throws |
---|
192 | # an error if the test has not been ran. |
---|
193 | # ---------------------------------------------------------------------- |
---|
194 | itcl::body Rappture::Tester::Test::getResult {} { |
---|
195 | if {!$_ran} { |
---|
196 | error "Test has not yet been ran." |
---|
197 | } |
---|
198 | return $_result |
---|
199 | } |
---|
200 | |
---|
201 | # ---------------------------------------------------------------------- |
---|
202 | # USAGE: getRunfile |
---|
203 | # |
---|
204 | # Returns the location of the runfile generated by the previous run of |
---|
205 | # the test. Throws an error if the test has not been ran. |
---|
206 | # ---------------------------------------------------------------------- |
---|
207 | itcl::body Rappture::Tester::Test::getRunfile {} { |
---|
208 | if {!$_ran} { |
---|
209 | error "Test has not yet been ran." |
---|
210 | } |
---|
211 | return $_runfile |
---|
212 | } |
---|
213 | |
---|
214 | # ----------------------------------------------------------------------- |
---|
215 | # USAGE: getRunobj |
---|
216 | # |
---|
217 | # Returns the library object generated by the previous run of the test. |
---|
218 | # Throws an error if the test has not been ran. |
---|
219 | # ----------------------------------------------------------------------- |
---|
220 | itcl::body Rappture::Tester::Test::getRunobj {} { |
---|
221 | if {!$_ran} { |
---|
222 | error "Test has not yet been ran." |
---|
223 | } |
---|
224 | return $_runobj |
---|
225 | } |
---|
226 | |
---|
227 | # ---------------------------------------------------------------------- |
---|
228 | # USAGE: getTestxml |
---|
229 | # |
---|
230 | # Returns the location of the test xml file containing the set of golden |
---|
231 | # results. |
---|
232 | # ---------------------------------------------------------------------- |
---|
233 | itcl::body Rappture::Tester::Test::getTestxml {} { |
---|
234 | return $_testxml |
---|
235 | } |
---|
236 | |
---|
237 | # ---------------------------------------------------------------------- |
---|
238 | # USAGE: getTestobj |
---|
239 | # |
---|
240 | # Returns the test library object containing the set of golden results. |
---|
241 | # ---------------------------------------------------------------------- |
---|
242 | itcl::body Rappture::Tester::Test::getTestobj {} { |
---|
243 | return $_testobj |
---|
244 | } |
---|
245 | |
---|
246 | # ---------------------------------------------------------------------- |
---|
247 | # USAGE: hasRan |
---|
248 | # |
---|
249 | # Returns yes if the test has been ran (with the run method), returns |
---|
250 | # no otherwise. |
---|
251 | # ---------------------------------------------------------------------- |
---|
252 | itcl::body Rappture::Tester::Test::hasRan {} { |
---|
253 | return $_ran |
---|
254 | } |
---|
255 | |
---|
256 | # ---------------------------------------------------------------------- |
---|
257 | # USAGE: regoldenize |
---|
258 | # |
---|
259 | # Regoldenize the test by overwriting the test xml containin the golden |
---|
260 | # results with the data in the runfile generated by the last run. Copy |
---|
261 | # test label and description into the new file. Update the test's |
---|
262 | # result attributes to reflect the changes. Throws an error if the test |
---|
263 | # has not been ran. |
---|
264 | # ---------------------------------------------------------------------- |
---|
265 | itcl::body Rappture::Tester::Test::regoldenize {} { |
---|
266 | if {!$_ran} { |
---|
267 | error "Test has not yet been ran." |
---|
268 | } |
---|
269 | $_runobj put test.label [$_testobj get test.label] |
---|
270 | $_runobj put test.description [$_testobj get test.description] |
---|
271 | set fid [open $_testxml w] |
---|
272 | puts $fid "<?xml version=\"1.0\"?>" |
---|
273 | puts $fid [$_runobj xml] |
---|
274 | close $fid |
---|
275 | set _testobj $_runobj |
---|
276 | set _result Pass |
---|
277 | set _diffs "" |
---|
278 | set _added "" |
---|
279 | set _missing "" |
---|
280 | } |
---|
281 | |
---|
282 | |
---|
283 | # ---------------------------------------------------------------------- |
---|
284 | # USAGE: run |
---|
285 | # |
---|
286 | # Kicks off a new simulation and checks the results against the golden |
---|
287 | # set of results. Set private attributes accordingly so that they can |
---|
288 | # later be retrieved via the public accessors. |
---|
289 | # ---------------------------------------------------------------------- |
---|
290 | itcl::body Rappture::Tester::Test::run {} { |
---|
291 | # Delete existing library if rerun |
---|
292 | if {$_ran} { |
---|
293 | itcl::delete object $_runobj |
---|
294 | } |
---|
295 | set driver [makeDriver] |
---|
296 | set tool [Rappture::Tool ::#auto $driver [file dirname $_toolxml]] |
---|
297 | foreach {status _runobj} [eval $tool run] break |
---|
298 | set _ran yes |
---|
299 | if {$status == 0 && [Rappture::library isvalid $_runobj]} { |
---|
300 | # HACK: Add a new input to differentiate between results |
---|
301 | $_runobj put input.TestRun.current "Test result" |
---|
302 | set _diffs [diffs $_testobj $_runobj] |
---|
303 | set _missing [missing $_testobj $_runobj] |
---|
304 | set _added [added $_testobj $_runobj] |
---|
305 | set _runfile [$tool getRunFile] |
---|
306 | if {$_diffs == "" && $_missing == "" && $_added == ""} { |
---|
307 | set _result Pass |
---|
308 | } else { |
---|
309 | set _result Fail |
---|
310 | } |
---|
311 | } else { |
---|
312 | set _runobj "" |
---|
313 | set _result Error |
---|
314 | } |
---|
315 | } |
---|
316 | |
---|
317 | # ---------------------------------------------------------------------- |
---|
318 | # USAGE: added lib1 lib2 ?path? |
---|
319 | # |
---|
320 | # Compares two library objects and returns a list of paths that have |
---|
321 | # been added in the second library and do not exist in the first. |
---|
322 | # Return value will contain all differences that occur as descendants of |
---|
323 | # an optional starting path. If the path argument is not given, then |
---|
324 | # only the output sections will be compared. |
---|
325 | # ---------------------------------------------------------------------- |
---|
326 | itcl::body Rappture::Tester::Test::added {lib1 lib2 {path output}} { |
---|
327 | set paths [list] |
---|
328 | foreach child [$lib2 children $path] { |
---|
329 | foreach p [added $lib1 $lib2 $path.$child] { |
---|
330 | lappend paths $p |
---|
331 | } |
---|
332 | } |
---|
333 | if {[$lib1 get $path] == "" && [$lib2 get $path] != ""} { |
---|
334 | lappend paths $path |
---|
335 | } |
---|
336 | return $paths |
---|
337 | } |
---|
338 | |
---|
339 | # ---------------------------------------------------------------------- |
---|
340 | # USAGE: compareElements <lib1> <lib2> <path> |
---|
341 | # |
---|
342 | # Compare data found in two library objects at the given path. Returns |
---|
343 | # 1 if match, 0 if no match. For now, just check if ascii identical. |
---|
344 | # Later, we can do something more sophisticated for different types of |
---|
345 | # elements. |
---|
346 | # ---------------------------------------------------------------------- |
---|
347 | itcl::body Rappture::Tester::Test::compareElements {lib1 lib2 path} { |
---|
348 | set val1 [$lib1 get $path] |
---|
349 | set val2 [$lib2 get $path] |
---|
350 | return [expr {$val1} != {$val2}] |
---|
351 | } |
---|
352 | |
---|
353 | # ---------------------------------------------------------------------- |
---|
354 | # USAGE: diffs <lib1> <lib2> ?path? |
---|
355 | # |
---|
356 | # Compares two library objects and returns a list of paths that do not |
---|
357 | # match. Only paths which exist in both libraries are considered. |
---|
358 | # Return value will contain all differences that occur as descendants of |
---|
359 | # an optional starting path. If the path argument is not given, then |
---|
360 | # only the output sections will be compared. |
---|
361 | # ---------------------------------------------------------------------- |
---|
362 | itcl::body Rappture::Tester::Test::diffs {lib1 lib2 {path output}} { |
---|
363 | set paths [list] |
---|
364 | set clist1 [$lib1 children $path] |
---|
365 | set clist2 [$lib2 children $path] |
---|
366 | foreach child $clist1 { |
---|
367 | # Ignore if not present in both libraries |
---|
368 | if {[lsearch -exact $clist2 $child] != -1} { |
---|
369 | foreach p [diffs $lib1 $lib2 $path.$child] { |
---|
370 | lappend paths $p |
---|
371 | } |
---|
372 | } |
---|
373 | } |
---|
374 | if {[compareElements $lib1 $lib2 $path]} { |
---|
375 | # Ignore output.time and output.user |
---|
376 | if {$path != "output.time" && $path != "output.user"} { |
---|
377 | lappend paths $path |
---|
378 | } |
---|
379 | } |
---|
380 | return $paths |
---|
381 | } |
---|
382 | |
---|
383 | # ---------------------------------------------------------------------- |
---|
384 | # USAGE: makeDriver |
---|
385 | # |
---|
386 | # Builds and returns a driver library object to be used for running the |
---|
387 | # test specified by testxml. Copy current values from test xml into the |
---|
388 | # newly created driver. If any inputs are present in the new tool.xml |
---|
389 | # which do not exist in the test xml, use the default value. |
---|
390 | # ---------------------------------------------------------------------- |
---|
391 | itcl::body Rappture::Tester::Test::makeDriver {} { |
---|
392 | set driver [Rappture::library $_toolxml] |
---|
393 | return [merge $_toolobj $_testobj $driver] |
---|
394 | } |
---|
395 | |
---|
396 | # ---------------------------------------------------------------------- |
---|
397 | # USAGE: merge <toolobj> <golden> <driver> ?path? |
---|
398 | # |
---|
399 | # Used to recursively build up a driver library object for running a |
---|
400 | # test. Should not be called directly - see makeDriver. |
---|
401 | # ---------------------------------------------------------------------- |
---|
402 | itcl::body Rappture::Tester::Test::merge {toolobj golden driver {path input}} { |
---|
403 | foreach child [$toolobj children $path] { |
---|
404 | set val [$golden get $path.$child.current] |
---|
405 | if {$val != ""} { |
---|
406 | $driver put $path.$child.current $val |
---|
407 | } else { |
---|
408 | set def [$toolobj get $path.$child.default] |
---|
409 | if {$def != ""} { |
---|
410 | $driver put $path.$child.current $def |
---|
411 | } |
---|
412 | } |
---|
413 | merge $toolobj $golden $driver $path.$child |
---|
414 | } |
---|
415 | return $driver |
---|
416 | } |
---|
417 | |
---|
418 | # ---------------------------------------------------------------------- |
---|
419 | # USAGE: added lib1 lib2 ?path? |
---|
420 | # |
---|
421 | # Compares two library objects and returns a list of paths that do not |
---|
422 | # exist in the first library and have been added in the second. |
---|
423 | # Return value will contain all differences that occur as descendants of |
---|
424 | # an optional starting path. If the path argument is not given, then |
---|
425 | # only the output sections will be compared. |
---|
426 | # ---------------------------------------------------------------------- |
---|
427 | itcl::body Rappture::Tester::Test::missing {lib1 lib2 {path output}} { |
---|
428 | set paths [list] |
---|
429 | foreach child [$lib1 children $path] { |
---|
430 | foreach p [missing $lib1 $lib2 $path.$child] { |
---|
431 | lappend paths $p |
---|
432 | } |
---|
433 | } |
---|
434 | if {[$lib1 get $path] != "" && [$lib2 get $path] == ""} { |
---|
435 | lappend paths $path |
---|
436 | } |
---|
437 | return $paths |
---|
438 | } |
---|