graffle 0.1.9 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.txt +9 -0
  2. data/Manifest.txt +15 -8
  3. data/Rakefile.hoe +1 -2
  4. data/bin/interpret-intentions.rb +51 -0
  5. data/examples/objects with notes.rb +1 -0
  6. data/examples/old-style-rails-workflow-test.expected +11 -0
  7. data/examples/old-style-rails-workflow-test.rb +43 -0
  8. data/examples/rails-workflow-test.rb +7 -6
  9. data/examples/sheet with key.expected +5 -0
  10. data/examples/sheet with key.graffle +0 -0
  11. data/examples/sheet with key.rb +38 -0
  12. data/graffle.tmproj +242 -75
  13. data/lib/graffle.rb +11 -2
  14. data/lib/graffle/nodoc/hacks.rb +1 -0
  15. data/lib/graffle/point.rb +1 -0
  16. data/lib/graffle/stereotypes.rb +34 -3
  17. data/lib/graffle/version.rb +3 -1
  18. data/lib/graphical_tests_for_rails.rb +11 -9
  19. data/lib/graphical_tests_for_rails/jumbled-string-canonicalizer.rb +117 -0
  20. data/lib/graphical_tests_for_rails/orderings.rb +1 -2
  21. data/lib/graphical_tests_for_rails/picture-applier.rb +257 -0
  22. data/lib/graphical_tests_for_rails/stereotype-extensions.rb +111 -0
  23. data/lib/graphical_tests_for_rails/text-appliers.rb +10 -84
  24. data/lib/graphical_tests_for_rails/user-intention.rb +250 -0
  25. data/test/abstract-graphic-tests.rb +10 -0
  26. data/test/container-tests.rb +29 -0
  27. data/test/graphical_tests_for_rails/jumbled-string-canonicalizer-tests.rb +174 -0
  28. data/test/graphical_tests_for_rails/new-style-picture-applier-tests.rb +286 -0
  29. data/test/graphical_tests_for_rails/old-style-picture-applier-tests.rb +129 -0
  30. data/test/graphical_tests_for_rails/user-intention-tests.rb +389 -0
  31. data/test/graphical_tests_for_rails/util.rb +9 -0
  32. data/test/sheet-tests.rb +21 -0
  33. data/test/text-tests.rb +6 -0
  34. metadata +25 -26
  35. data/design-notes/graphical-tests-for-rails-objects.graffle +0 -644
  36. data/lib/graphical_tests_for_rails/graphic-volunteers.rb +0 -75
  37. data/lib/graphical_tests_for_rails/picture-appliers.rb +0 -225
  38. data/lib/graphical_tests_for_rails/volunteer-pool.rb +0 -115
  39. data/test/graphical_tests_for_rails/deprecated-graphic-interpreter-tests.rb +0 -121
  40. data/test/graphical_tests_for_rails/graphic-volunteer-tests.rb +0 -218
  41. data/test/graphical_tests_for_rails/picture-applier-tests.rb +0 -215
  42. data/test/graphical_tests_for_rails/text-applier-tests.rb +0 -111
@@ -38,7 +38,7 @@ module Graffle
38
38
  filename = File.join(filename, "data.plist")
39
39
  end
40
40
 
41
- parse_xml(File.read(filename))
41
+ parse_xml(data_from(filename))
42
42
  end
43
43
 
44
44
  def self.parse_xml(content)
@@ -46,7 +46,16 @@ module Graffle
46
46
  Document.takes_on(doc)
47
47
  end
48
48
  end
49
-
49
+
50
+ def self.data_from(filename)
51
+ possible_gzip_header = File.read(filename, 2)
52
+ if possible_gzip_header.unpack('CC') == [0x1f, 0x8b]
53
+ `gunzip < '#{filename}'`
54
+ else
55
+ File.read(filename)
56
+ end
57
+ end
58
+
50
59
  def self.stereotype(o) # :nodoc:
51
60
  return true if ShapedGraphic.takes_on(o)
52
61
  return true if LineGraphic.takes_on(o)
@@ -3,6 +3,7 @@
3
3
  # Created by Brian Marick on 2007-07-06.
4
4
  # Copyright (c) 2007. All rights reserved.
5
5
 
6
+
6
7
  require 'pp'
7
8
 
8
9
  class Object
@@ -2,6 +2,7 @@
2
2
  #
3
3
  # Created by Brian Marick on 2007-07-06.
4
4
  # Copyright (c) 2007. All rights reserved.
5
+ "prevent the above from infecting rdoc"
5
6
 
6
7
 
7
8
  module Graffle
@@ -2,6 +2,8 @@
2
2
  #
3
3
  # Created by Brian Marick on 2007-07-06.
4
4
  # Copyright (c) 2007. All rights reserved.
5
+ "prevent the above from infecting rdoc"
6
+
5
7
 
6
8
  module Graffle
7
9
  module Builders # :nodoc:
@@ -132,8 +134,16 @@ module Graffle
132
134
  # Pro feature)
133
135
  def has_note?; self.has_key?('Notes'); end
134
136
  alias_method :has_notes?, :has_note?
137
+
138
+ # Delete this graphic from inside its Container (a Sheet or a
139
+ # Group). The name is delete_yourself to avoid overriding the
140
+ # delete method on the underlying class.
141
+ def delete_yourself
142
+ container.delete_graphic(self)
143
+ end
135
144
 
136
-
145
+
146
+
137
147
  end
138
148
 
139
149
 
@@ -338,11 +348,11 @@ module Graffle
338
348
  if label
339
349
  label.content.as_lines
340
350
  else
341
- label || []
351
+ label || [] # TODO: Remove unnecessary first condition.
342
352
  end
343
353
  end
344
354
 
345
-
355
+
346
356
  private
347
357
  def points_at(*points)
348
358
  self['Points'] = points.collect { |p| "{#{p[0]}, #{p[1]}}" }
@@ -379,6 +389,16 @@ module Graffle
379
389
  o.container = self # works because instance_evaled.
380
390
  end
381
391
  end
392
+
393
+ # Delete a graphic from inside this Container
394
+ # The name is delete_graphic to avoid overriding the
395
+ # delete method on the underlying class.
396
+ def delete_graphic(graphic)
397
+ graphic.container = nil
398
+ graphics.delete(graphic)
399
+ end
400
+
401
+
382
402
 
383
403
  # Find the AbstractGraphic matching the integer id. Returns nil
384
404
  # if not found.
@@ -396,6 +416,15 @@ module Graffle
396
416
  end
397
417
  result
398
418
  end
419
+
420
+ # Find a graphic whose ShapedGraphic#content matches the given
421
+ # _string_.
422
+ def find_by_content(string)
423
+ result = graphics.find do | elt |
424
+ elt.respond_to?(:content) &&
425
+ elt.content.as_plain_text.downcase === string.downcase
426
+ end
427
+ end
399
428
 
400
429
  def self.stereotype_children_of(parent) # :nodoc:
401
430
  parent.graphics.each do | child |
@@ -409,6 +438,7 @@ module Graffle
409
438
  end
410
439
  true
411
440
  end
441
+
412
442
 
413
443
  end
414
444
 
@@ -498,6 +528,7 @@ module Graffle
498
528
  # Null object for text
499
529
  class Null # :nodoc:
500
530
  def as_lines; []; end
531
+ def as_plain_text; ''; end
501
532
  end
502
533
 
503
534
  end
@@ -2,7 +2,9 @@
2
2
  #
3
3
  # Created by Brian Marick on 2007-07-06.
4
4
  # Copyright (c) 2007. All rights reserved.
5
+ "prevent the above from infecting rdoc"
6
+
5
7
 
6
8
  module Graffle
7
- Version = '0.1.9'
9
+ Version = '0.2.0'
8
10
  end
@@ -5,23 +5,25 @@
5
5
 
6
6
 
7
7
  require 'graffle'
8
+ require 'graphical_tests_for_rails/jumbled-string-canonicalizer'
8
9
  require 'graphical_tests_for_rails/orderings'
10
+ require 'graphical_tests_for_rails/picture-applier'
11
+ require 'graphical_tests_for_rails/stereotype-extensions'
9
12
  require 'graphical_tests_for_rails/text-appliers'
10
- require 'graphical_tests_for_rails/picture-appliers'
11
- require 'graphical_tests_for_rails/graphic-volunteers'
12
- require 'graphical_tests_for_rails/volunteer-pool'
13
+ require 'graphical_tests_for_rails/user-intention'
13
14
 
14
15
  # Classes within this module help you build up Rails integration tests
15
16
  # from OmniGraffle files. The important classes are these:
16
17
  #
17
- # * GraphicInterpreter: These objects are 'programmed' with
18
- # AbstractTextApplier objects who know how to convert lines of text
19
- # into messages and arguments to send to a _target_.
20
- # * GraphicOrderer: These objects take a list of graphics in no
18
+ # * PictureApplier: These objects are 'programmed' with unstructured
19
+ # descriptions of how they should handle particular lines in particular
20
+ # kinds of Graffle objects. Once programmed, they are handed Graffle
21
+ # objects, turn them into messages (and arguments), and send those messages
22
+ # to some _target_.
23
+ # * GraphicArrayOrderings::GraphicOrderer: These objects take a list of graphics in no
21
24
  # particular order and put it in the right order for a particular
22
- # style of test. For example, an InWorkflowOrder represents an
25
+ # style of test. For example, an GraphicArrayOrderings::InWorkflowOrder represents an
23
26
  # order created by tracing through shapes connected by lines.
24
-
25
27
  module GraphicalTestsForRails
26
28
  include GraphicArrayOrderings
27
29
  include TextAppliers
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-07-29.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+ require 'ostruct'
7
+ require 's4t-utils'
8
+
9
+ module GraphicalTestsForRails
10
+ # A JumbledStringCanonicalizer takes a string of words and matches it
11
+ # against a set of patterns, returning strings that represent each match.
12
+ # All of the regular expressions in the array must match for the corresponding
13
+ # string to be returned.
14
+ class JumbledStringCanonicalizer
15
+
16
+ # The _patterns_ are a hash mapping arrays of regular expressions to strings.
17
+ # The block can be used to change the default. Messages within the block are
18
+ # sent to this JumbledStringCanonicalizer. (The block is instance_eval'd.)
19
+ def initialize(patterns = {}, &block) # :yields:
20
+ @patterns = patterns
21
+ @no_match_message = proc {|source| "Cannot recognize anything in '#{source}'"}
22
+ @multiple_match_message = proc do |source, pretty_reasons, raw_reasons|
23
+ "Multiple matches in '#{source}':\n" +
24
+ pretty_reasons.join("\n")
25
+ end
26
+ instance_eval(&block) if block
27
+ end
28
+
29
+ # Set a default value to return when there's no match. The default
30
+ # behavior is to raise a StandardError exception.
31
+ def default(value)
32
+ @default_given = true
33
+ @default = value
34
+ end
35
+
36
+ # If no match was found and there's no default value, the _block_ is
37
+ # called with the failing string as its only argument. That block should
38
+ # produce a string that's set as the raised StandardError's message.
39
+ def no_match_message(&block) # :yields: failing_string
40
+ @no_match_message = block
41
+ end
42
+
43
+ # If more than one of the arrays matches a string, and there's no disambiguator,
44
+ # the _block_ is called with the failing string as its only argument. That block should
45
+ # produce a string that's set as the raised StandardError's message.
46
+ def multiple_match_message(&block) # :yields: failing_string
47
+ @multiple_match_message = block
48
+ end
49
+
50
+ # This method takes a _block_ like the ones passed to Array#sort (two arguments,
51
+ # a return value that's 1, 0, or -1). If there are multiple matches for a given
52
+ # string, they are sorted and the first one is chosen. If there is no
53
+ # disambiguator, StandardError is raised.
54
+ def disambiguate(&block) # :yields: match1, match2
55
+ @disambiguator = block
56
+ end
57
+
58
+ # Take a string, match it against one of the keys in the _patterns_ given to
59
+ # JumbledStringCanonicalizer.new, and return an informative object. That object
60
+ # responds to these messages:
61
+ # * value: the string value corresponding to the match.
62
+ # * source: the original _string_ (unchanged)
63
+ # * raw_reason: an array. Each element is a list of the groups captured
64
+ # by one of the regular expressions in the match, but with any nil
65
+ # groups compacted out.
66
+ # * pretty_reason: the raw_reason converted into a tolerably understandable
67
+ # string.
68
+ # If there
69
+ # are zero matches or more than one match, the default behavior is to raise StandardError.
70
+ def canonicalize(string)
71
+ raw_reasons = []
72
+ matches = @patterns.keys.collect do |k|
73
+ raw_reasons = []
74
+ if k.all? { |r| r =~ string && raw_reasons << $~.captures.compact }
75
+ OpenStruct.new(:value => @patterns[k], :source => string,
76
+ :raw_reason => raw_reasons,
77
+ :pretty_reason => prettify(raw_reasons))
78
+ end
79
+ end.compact
80
+ case matches.length
81
+ when 0: error_or_default(string)
82
+ when 1: matches.first
83
+ else error_or_disambiguation(string, matches)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def prettify(raw_reasons)
90
+ clauses = raw_reasons.collect do |partial_list|
91
+ S4tUtils.friendly_list("and", partial_list)
92
+ end
93
+ clauses.join("; and also ")
94
+ end
95
+
96
+ def quote(text)
97
+ %Q{"#{text}"}
98
+ end
99
+
100
+ def error_or_default(string)
101
+ if @default_given
102
+ OpenStruct.new(:value => @default, :source => string,
103
+ :raw_reason => [],
104
+ :pretty_reason => "no matching words")
105
+ else
106
+ user_is_bewildered(@no_match_message.call(string))
107
+ end
108
+ end
109
+
110
+ def error_or_disambiguation(string, matches)
111
+ return matches.sort(&@disambiguator).first if @disambiguator
112
+ user_is_bewildered(@multiple_match_message.call(
113
+ string, matches.collect { |m| m.pretty_reason },
114
+ matches.collect { |m| m.raw_reason }))
115
+ end
116
+ end
117
+ end
@@ -2,8 +2,7 @@
2
2
  #
3
3
  # Created by Brian Marick on 2007-07-11.
4
4
  # Copyright (c) 2007. All rights reserved.
5
-
6
- require 'graffle'
5
+ "prevent the above from infecting rdoc"
7
6
 
8
7
 
9
8
  module GraphicalTestsForRails
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-07-22.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+
7
+ require 'graphical_tests_for_rails/text-appliers'
8
+ require 'graphical_tests_for_rails/user-intention'
9
+
10
+ require 'enumerator'
11
+ require 'test/unit/assertionfailederror'
12
+ require 'ostruct'
13
+
14
+
15
+ module GraphicalTestsForRails
16
+
17
+ # A PictureApplier takes a list of strings and deduces what those strings
18
+ # say about how to generate message sends from graphics. It can then be
19
+ # given a list of AbstractGraphic objects to send those messages to some
20
+ # target object.
21
+ #
22
+ # Here are typical strings:
23
+ # "ignore shape content"
24
+ # "line label content is user action with arguments in quotes"
25
+ # "annotations with ellipses are page names"
26
+ # "in shape content with ellipses, ignore lines with one word"
27
+ #
28
+ # Here's what strings can describe. Unless noted, words don't have to be
29
+ # in any particular order. You can generally use either singular or
30
+ # plural forms.
31
+ # * kind of graphic. It can be a _line_, a <i>line label</i>, or a
32
+ # _shape_ (alt. <i>shaped graphic</i>). Groups are not currently
33
+ # allowed. The kind of graphic can be omitted, in which case
34
+ # the string matches all kinds.
35
+ # * where the text of interest lives. It can be _note_ (alt. _annotation_)
36
+ # or _content_. (The content is the text that appears on the normal
37
+ # OmniGraffle screen. The note appears if you hover your cursor above
38
+ # it or look at Notes in the Inspector.) One of these words must be present.
39
+ # * An action to take:
40
+ # * _skip_ (alt. _ignore_). Do not turn text into message sends.
41
+ # * <i>user action</i> (in that order) followed by <i>quoted</i> and
42
+ # <i>arg</i> (in any order). Variations on _quoted_ and _arg_ are
43
+ # allowed. For example, "quote all arguments" matches. This argument
44
+ # means that <i>Fred controls the "widget"</i> is turned into:
45
+ # target.controls_the("fred", "widget")
46
+ # Strings to match are downcased
47
+ # before the match and non-word-characters are stripped, so "John runs AWAY!"
48
+ # turns into runs_away("john").
49
+ # * _claim_ followed by _quoted_ and _arg_, as above. In this case,
50
+ # <i>Fred controls the widget</i> is turned into:
51
+ # target.fred_controls_the("widget")
52
+ # * _name_ and _pages_ in any order. _HOME_ is converted into
53
+ # target.assert_on_page("home").
54
+ # The string must describe an action.
55
+ # * A description of which lines within a body of text should have the
56
+ # action applied to it.
57
+ # * <i>with one word</i> (in that order, possibly with other words
58
+ # interspersed). The action only happens on lines of text that contain
59
+ # a single word.
60
+ # * <i>text...</i> (alt., _ellipses_). The action only happens on lines
61
+ # of text that end with ellipses.
62
+ # * <i>otherwise</i> (alt <i>other kinds of</i>, <i>other kind of</i>).
63
+ # The action happens for all lines. (Only useful when it follows one
64
+ # of the above.)
65
+ #
66
+ # These descriptions are checked in the order given:
67
+ #
68
+ # one word lines in shape content name pages.
69
+ # shape content lines with ellipses are claims with quoted args.
70
+ # otherwise, ignore shape content.
71
+ #
72
+ # There's one special case to handle a common desire: turning off
73
+ # all interpretation of a graphic. Typically that's used for shape
74
+ # contents or line labels that are just there for the human reader, not
75
+ # for the test. To ignore them, add a note containing the word "ignore"
76
+ # and this text as the first description:
77
+ #
78
+ # skip when note contains 'ignore'
79
+ #
80
+
81
+ class PictureApplier
82
+
83
+ attr_reader :intentions
84
+
85
+ def initialize(intentions = [], &block) #:nodoc:
86
+ @intentions = intentions
87
+ @log = []
88
+
89
+ if block
90
+ STDERR.puts "Warning: old-style PictureApplier initialization is deprecated."
91
+ instance_eval(&block)
92
+ end
93
+ end
94
+
95
+ # Apply messages derived from the _graphics_ to the _target_, adding
96
+ # descriptions of each message to the _log_.
97
+ def apply(graphics, target, log=[])
98
+ return old_style_apply(graphics, target, log) if @intentions.empty?
99
+
100
+ while_logging_for_test_errors do
101
+ graphics.each { |g| apply_one(g, target) }
102
+ end
103
+ end
104
+
105
+ # Use the Key graphic in the _sheet_ to program the PictureApplier's
106
+ # behavior. The Key graphic is then discarded (not used in the actual
107
+ # test.)
108
+ def self.from_sheet(sheet, *default_written_intentions)
109
+ key = sheet.find_by_content("key")
110
+
111
+ if key.nil?
112
+ user_denies(default_written_intentions.empty?) {
113
+ "The sheet has no key for the tests. It needs a graphic whose visible content is 'key'."
114
+ }
115
+ from_written_instructions(*default_written_intentions)
116
+ else
117
+ user_claims(key.has_notes?) {
118
+ "The key graphic is supposed to have an annotation describing how to interpret the rest of the sheet."
119
+ }
120
+ key.delete_yourself
121
+ from_graphic(key)
122
+ end
123
+ end
124
+
125
+ # The _graphic_ should have an annotation with a list of written
126
+ # instructions of the form accepted by from_written_instructions.
127
+ def self.from_graphic(graphic)
128
+ written_intentions = graphic.notes.as_lines.find_all { |l| l.strip != "" }
129
+ from_written_intentions(*written_intentions)
130
+ end
131
+
132
+ # Accept a list of written instructions that are later obeyed by
133
+ # apply.
134
+ def self.from_written_intentions(*written_intentions)
135
+ intentions = written_intentions.collect do |line|
136
+ UserIntention.understand(line)
137
+ end
138
+ new(intentions)
139
+ end
140
+
141
+ # Synonym for from_written_intentions.
142
+ def self.from_written_instructions(*args)
143
+ self.from_written_intentions(*args)
144
+ end
145
+
146
+ include GraphicalTestsForRails::TextAppliers
147
+
148
+ # Claim that the PictureApplier matches graphics matching
149
+ # the _form_description_, a string. Here are the form descriptions
150
+ # currently supported:
151
+ #
152
+ # * 'shaped graphic content': A shaped graphic is a rectangle, oval,
153
+ # embedded picture, etc.: anything that is neither a line nor a
154
+ # group. The content is the text you type in when you double-click
155
+ # on the object. When given the form description, the PictureApplier
156
+ # will match any shaped graphic that contains text.
157
+ # * 'line label content': This matches any line that has a label.
158
+ # (Note: lines can have more than one label. That's not handled.)
159
+ # * 'note': Matches any object that contains an annotation. (Annotations are
160
+ # only available in OmniGraffle Pro. They're arbitrary text
161
+ # associated with a line or shaped graphic. You can see the
162
+ # annotations if you hover over the object.)
163
+ #
164
+ # The _form_description_ describes the first of two matching steps.
165
+ # If it matches, it extracts the appropriate text (content or
166
+ # annotation) and hands it to the next step, described by given.
167
+ #
168
+ # This method returns self so that other methods can be chained onto it.
169
+ def given(form_description) # :nodoc:
170
+ @new_style_place_implied = form_description
171
+ @new_style_text_implied = ""
172
+ @new_style_action_implied = ""
173
+ self
174
+ end
175
+
176
+ # Claim that the PictureApplier matches particular text extracted from
177
+ # a graphical object. These _text_descriptions_ are currently supported:
178
+ #
179
+ # * 'text...': match if the text ends in ellipses (ignoring whitespace).
180
+ # * 'ignore': match if the text ends in the word "ignore" (ignoring whitespace).
181
+ #
182
+ # This method returns self so that other methods can be chained onto it.
183
+ def matching(text_description) # :nodoc:
184
+ @new_style_text_implied = text_description
185
+ self
186
+ end
187
+
188
+ # When a PictureApplier matches an object, the extracted text is
189
+ # passed to the _text_applier_ to be turned into messages.
190
+ def use(text_applier) # :nodoc:
191
+ new_style_action_implied =
192
+ case text_applier
193
+ when TextApplierThatDoesNothing: 'skip'
194
+ when PrefixNameAndArgsFromQuotedText: 'user action with args from quoted text'
195
+ when ArgsFromQuotedText: "claim with args from quoted text"
196
+ when TextAsNameOfPage: 'name of page'
197
+ else user_is_bewildered("Unknown class: #{text_applier.class}")
198
+ end
199
+ parts = [new_style_action_implied, @new_style_place_implied, @new_style_text_implied]
200
+ @intentions << UserIntention.understand(parts.join(" "))
201
+ end
202
+
203
+ # After a PictureApplier matches a graphical object, it's ignored.
204
+ # This is used to prevent text associated with the object from being
205
+ # turned into messages.
206
+ def skip # :nodoc:
207
+ use(TextApplierThatDoesNothing.new)
208
+ end
209
+
210
+ def intentions_that_apply_to(graphic) # :nodoc:
211
+ @intentions.find_all do |i|
212
+ # pi i, '#'
213
+ # if (pi i.applies_to_graphic?(graphic), 'applies to graphic?')
214
+ # pi i.lines_from(graphic), "graphic contains"
215
+ # i.applies_to_text?(i.lines_from(graphic))
216
+ # end
217
+ i.applies_to_graphic?(graphic) &&
218
+ i.applies_to_text?(i.lines_from(graphic))
219
+ end
220
+ end
221
+
222
+
223
+
224
+ private
225
+
226
+
227
+ def apply_one(graphic, target)
228
+ relevant_intentions = intentions_that_apply_to(graphic)
229
+ return if relevant_intentions.empty?
230
+
231
+ lines_chosen = relevant_intentions.first.lines_from(graphic)
232
+ lines_chosen.each do |line|
233
+ intention_for_line = relevant_intentions.find do |i|
234
+ i.applies_to_line?(line)
235
+ end
236
+ intention_for_line.apply(line, target, @log) if intention_for_line
237
+ end
238
+ end
239
+
240
+
241
+
242
+ def while_logging_for_test_errors
243
+ begin
244
+ yield
245
+ rescue Test::Unit::AssertionFailedError, StandardError, ScriptError => e
246
+ new_message = %Q{#{e.message}. That happened during or after the last of these commands:\n#{@log.join("\n")}}
247
+ new_e = e.exception(new_message)
248
+ new_e.set_backtrace(e.backtrace)
249
+ raise new_e
250
+ end
251
+ end
252
+
253
+
254
+
255
+
256
+ end
257
+ end