graffle 0.1.9 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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