wrong 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/README.markdown +77 -11
  2. data/lib/wrong.rb +20 -0
  3. data/lib/wrong/adapters/minitest.rb +6 -18
  4. data/lib/wrong/adapters/rspec.rb +18 -7
  5. data/lib/wrong/adapters/test_unit.rb +2 -2
  6. data/lib/wrong/assert.rb +45 -90
  7. data/lib/wrong/chunk.rb +89 -27
  8. data/lib/wrong/close_to.rb +7 -11
  9. data/lib/wrong/d.rb +42 -0
  10. data/lib/wrong/failure_message.rb +43 -0
  11. data/lib/wrong/helpers.rb +66 -0
  12. data/lib/wrong/irb.rb +1 -1
  13. data/lib/wrong/message/array_diff.rb +57 -75
  14. data/lib/wrong/message/string_comparison.rb +88 -0
  15. data/lib/wrong/message/test_context.rb +2 -0
  16. data/lib/{predicated/lib/predicated/sexp_patch.rb → wrong/ruby2ruby_patch.rb} +3 -5
  17. data/lib/wrong/sexp_ext.rb +12 -4
  18. data/lib/wrong/version.rb +1 -1
  19. data/test/adapters/rspec1/failing_spec.rb +23 -0
  20. data/test/adapters/rspec2/failing_spec.rb +26 -0
  21. data/test/adapters/rspec_test.rb +65 -4
  22. data/test/adapters/test_unit_test.rb +6 -1
  23. data/test/assert_advanced_test.rb +51 -0
  24. data/test/assert_test.rb +4 -48
  25. data/test/capturing_test.rb +4 -2
  26. data/test/chunk_test.rb +36 -11
  27. data/test/close_to_test.rb +2 -2
  28. data/test/config_test.rb +5 -5
  29. data/test/d_test.rb +64 -0
  30. data/test/failure_message_test.rb +40 -0
  31. data/test/failures_test.rb +6 -7
  32. data/test/message/array_diff_test.rb +18 -14
  33. data/test/message/{test_context_text.rb → test_context_test.rb} +2 -1
  34. data/test/rescuing_test.rb +5 -4
  35. data/test/separate.rb +4 -0
  36. data/test/sexp_ext_test.rb +2 -2
  37. data/test/string_comparison_test.rb +159 -0
  38. data/test/suite.rb +7 -4
  39. data/test/test_helper.rb +2 -0
  40. data/test/wrong_test.rb +60 -0
  41. metadata +92 -46
  42. data/lib/wrong/message/string_diff.rb +0 -42
  43. data/test/message/string_diff_test.rb +0 -89
@@ -6,23 +6,33 @@
6
6
 
7
7
  Wrong provides a general assert method that takes a predicate block. Assertion failure messages are rich in detail. The Wrong idea is to replace all those countless assert\_this, assert\_that, should\_something library methods which only exist to give a more useful failure message than "assertion failed". Wrong replaces all of them in one fell swoop, since if you can write it in Ruby, Wrong can make a sensible failure message out of it.
8
8
 
9
- Wrong is alpha-quality. We'd very much appreciate feedback and bug reports. There are plenty of things left to be done to make the results look uniformly clean and beautiful. We want your feedback, and especially to give us cases where either it blows up or the output is ugly or uninformative.
9
+ We'd very much appreciate feedback and bug reports. There are plenty of things left to be done to make the results look uniformly clean and beautiful. We want your feedback, and especially to give us cases where either it blows up or the output is ugly or uninformative.
10
10
 
11
11
  It relies on [Predicated](http://github.com/sconover/predicated) for its main failure message.
12
12
 
13
13
  Inspired by [assert { 2.0 }](http://assert2.rubyforge.org/) but rewritten from scratch. Compatible with Ruby (MRI) 1.8, 1.9, and JRuby 1.5.
14
14
 
15
+ ## Installation
16
+
17
+ gem install wrong
18
+
19
+ Under JRuby, the above may cause errors; if so, then try
20
+
21
+ gem install wrong-jruby
22
+
23
+ which untangles some dependencies.
24
+
15
25
  ## Usage ##
16
26
 
17
27
  Wrong provides a simple assert method that takes a block:
18
28
 
19
29
  require "wrong"
20
-
21
- include Wrong::Assert
22
-
30
+
31
+ include Wrong
32
+
23
33
  assert { 1 == 1 }
24
34
  ==> nil
25
-
35
+
26
36
  assert { 2 == 1 }
27
37
  ==> Expected (2 == 1), but 2 is not equal to 1
28
38
 
@@ -63,21 +73,55 @@ And one for capturing output streams:
63
73
 
64
74
  assert { capturing { puts "hi" } == "hi\n" }
65
75
  assert { capturing(:stderr) { $stderr.puts "hi" } == "hi\n" }
76
+
66
77
  out, err = capturing(:stdout, :stderr) { ... }
78
+ assert { out == "something standard\n" }
79
+ assert { err =~ /something erroneous/ }
67
80
 
68
81
  If you want to compare floats, try this:
69
82
 
70
- require "wrong/close_to"
71
-
72
83
  assert { 5.0.close_to?(5.0001) } # default tolerance = 0.001
73
84
  assert { 5.0.close_to?(5.1, 0.5) } # optional tolerance parameter
74
85
 
86
+ (If you don't want `close_to?` cluttering up `Float` in your test runs then use `include Wrong::Assert` instead of `include Wrong`.)
87
+
88
+ We also implement the most amazing debugging method ever, `d`, which gives you a sort of mini-wrong wherever you want it
89
+ , even in production code at runtime:
90
+
91
+ require 'wrong'
92
+ x = 7
93
+ d { x } # => prints "x is 7" to the console
94
+ d { x * 2 } # => prints "(x * 2) is 14" to the console
95
+
96
+ (`d` was originally implemented by Rob Sanheim in LogBuddy; as with Assert2 this is a rewrite and homage.) Remember, if you want `d` to work at runtime (e.g. in a webapp) then you must `include 'wrong/d'` inside your app, e.g. for in your `environment.rb` file.
97
+
75
98
  More examples are in the file `examples.rb` <http://github.com/alexch/wrong/blob/master/examples.rb>
76
99
 
77
100
  There's also a spreadsheet showing a translation from Test::Unit and RSpec to Wrong, with notes, at [this Google Doc](https://spreadsheets.google.com/pub?key=0AouPn6oLrimWdE0tZDVOWnFGMzVPZy0tWHZwdnhFYkE&hl=en&output=html). (Ask <alexch@gmail.com> if you want editing privileges.)
78
101
 
79
102
  And don't miss the [slideshare presentation](http://www.slideshare.net/alexchaffee/wrong-5069976).
80
103
 
104
+ ## Piecemeal Usage ##
105
+
106
+ We know that sometimes you don't want all the little doodads from a library cluttering up your namespace. If you **don't** do
107
+
108
+ require 'wrong'
109
+ include Wrong
110
+
111
+ then you can instead `require` and `include` just the bits you really want. For example:
112
+
113
+ require 'wrong/assert'
114
+ include Wrong::Assert
115
+
116
+ will give you the `assert` and `deny` methods but not the formatters or `rescuing` or `d` or `close_to?`. And if all you want is `d` then do:
117
+
118
+ require 'wrong/d'
119
+ include Wrong::D
120
+
121
+ To summarize: if you do `require 'wrong'` and `include Wrong` then you will get the whole ball of wax. Most people will probably want this since it's easier, but there is an alternative, whici is to `require` and `include` only what you want.
122
+
123
+ And beware: if you don't `require 'wrong'`, then `include Wrong` will not do anything at all.
124
+
81
125
  ## Apology ##
82
126
 
83
127
  So does the world need another assertion framework? In fact, it does not! We actually believe the world needs **fewer** assert methods.
@@ -146,6 +190,26 @@ The failure message of the above would be something like "`Expected sky.blue? bu
146
190
 
147
191
  And if your assertion code isn't self-explanatory, then that's a hint that you might need to do some refactoring until it is. (Yes, even test code should be clean as a whistle. **Especially** test code.)
148
192
 
193
+ ## Details ##
194
+
195
+ When a failure occurs, the exception message contains all the details you might need to make sense of it. Here's the breakdown:
196
+
197
+ Expected [CLAIM], but [SUMMARY]
198
+ [FORMATTER]
199
+ [SUBEXP] is [VALUE]
200
+ ...
201
+
202
+ * CLAIM is the code inside your assert block, normalized
203
+ * SUMMARY is a to-English translation of the claim, via the Predicated library. This tries to be very intelligible; e.g. translating "include?" into "does not include" and so on.
204
+ * If there is a formatter registered for this type of predicate, its output will come next. (See below.)
205
+ * SUBEXP is each of the subtrees of the claim, minus duplicates and truisms (e.g. literals).
206
+ * The word "is" is a very nice separator since it doesn't look like code, but is short enough to be easily visually parsed.
207
+ * VALUE is `eval(SUBEXP).inspect`
208
+
209
+ We hope this structure lets your eyes focus on the meaningful values and differences in the message, rather than glossing over with stack-trace burnout. If you have any suggestions on how to improve it, please share them.
210
+
211
+ (Why does VALUE use `inspect` and not `to_s`? Because `inspect` on standard objects like String and Array are sure to show all relevant details, such as white space, in a console-safe way, and we hope other libraries follow suit. Also, `to_s` often inserts line breaks and that messes up formatting and legibility.)
212
+
149
213
  ## Formatters ##
150
214
 
151
215
  Enhancements for error messages sit under wrong/message.
@@ -190,7 +254,7 @@ Apparently, no test framework is successful unless and until it supports console
190
254
 
191
255
  Wrong.config[:color] = true
192
256
 
193
- in your test helper or rakefile or wherever and get ready to be **bedazzled**.
257
+ in your test helper or rakefile or wherever and get ready to be **bedazzled**. If you need custom colors, let us know.
194
258
 
195
259
  ## Aliases ##
196
260
 
@@ -217,7 +281,7 @@ Just don't use "`aver`" since we took that one for an internal method in `Wrong:
217
281
 
218
282
  ## Helper Assert Methods ##
219
283
 
220
- If you really want to, you can define your procs in one method, pass it in to another method, and have that method assert it. This is very bizarre and you probably shouldn't do it. Wrong will do its best to figure out where the actual assertion code is but it might not succeed.
284
+ If you really want to, you can define your proc in one method, pass it in to another method, and have that method assert it. This is a challenge for Wrong and you probably shouldn't do it. Wrong will do its best to figure out where the actual assertion code is but it might not succeed.
221
285
 
222
286
  If you're in Ruby 1.8, you **really** shouldn't do it! But if you do, you can use the "depth" parameter to give Wrong a better hint about how far up the stack it should crawl to find the code. See `assert_test.rb` for more details, if you dare.
223
287
 
@@ -225,10 +289,12 @@ If you're in Ruby 1.8, you **really** shouldn't do it! But if you do, you can us
225
289
 
226
290
  * Steve Conover - <sconover@gmail.com>
227
291
  * Alex Chaffee - <alex@stinky.com> - <http://alexch.github.com>
292
+ * John Firebaugh
293
+ * Thierry Henrio
228
294
 
229
295
  ## Etc ##
230
296
 
231
297
  * Github projects: <http://github.com/alexch/wrong>, <http://github.com/sconover/wrong>
232
298
  * Tracker project: <http://www.pivotaltracker.com/projects/109993>
233
- * [Wrong way translation table (from RSpec and Test::Unit)](https://spreadsheets.google.com/pub?key=0AouPn6oLrimWdE0tZDVOWnFGMzVPZy0tWHZwdnhFYkE&hl=en&output=html). (Ask <alexch@gmail.com> if you want editing privileges.)
234
- * [the Wrong slides I presented at Carbon Five](http://www.slideshare.net/alexchaffee/wrong-5069976)
299
+ * the [Wrong way translation table (from RSpec and Test::Unit)](https://spreadsheets.google.com/pub?key=0AouPn6oLrimWdE0tZDVOWnFGMzVPZy0tWHZwdnhFYkE&hl=en&output=html). (Ask <alexch@gmail.com> if you want editing privileges.)
300
+ * the [Wrong slides](http://www.slideshare.net/alexchaffee/wrong-5069976) that Alex presented at Carbon Five and GoGaRuCo
@@ -1,7 +1,27 @@
1
+ dir = File.expand_path(File.dirname(__FILE__))
2
+ $: << dir unless $:.include?(dir) # should we really have to do this? It's necessary to run examples.rb
3
+
1
4
  require "predicated"
2
5
  require "wrong/assert"
6
+ require "wrong/helpers"
3
7
  require "wrong/chunk"
4
8
  require "wrong/sexp_ext"
5
9
  require "wrong/version"
6
10
  require "wrong/config"
7
11
  require "wrong/irb"
12
+ require "wrong/d"
13
+ require "wrong/message/array_diff"
14
+ require "wrong/message/string_comparison"
15
+
16
+ module Wrong
17
+ include Wrong::Assert
18
+ extend Wrong::Assert
19
+ include Wrong::Helpers
20
+ extend Wrong::Helpers
21
+ end
22
+
23
+ # this does some magic; if you don't like it, `require 'wrong/assert'` et al. individually and don't `require 'wrong/close_to'` or `require 'wrong'`
24
+ require "wrong/close_to"
25
+
26
+ # this does some magic; if you don't like it, `require 'wrong/assert'` et al. individually, don't `require 'wrong'`, and `include Wrong::D` only in the modules you want to call `d` from
27
+ Object.send :include, Wrong::D
@@ -1,26 +1,14 @@
1
- require "wrong/assert"
1
+ require "wrong"
2
2
 
3
3
  class MiniTest::Unit::TestCase
4
- include Wrong::Assert
4
+ include Wrong
5
+
5
6
  def failure_class
6
7
  MiniTest::Assertion
7
8
  end
8
9
 
9
- def assert(*args)
10
- if block_given?
11
- self._assertions += 1
12
- super(explanation=args.first, depth=1)
13
- else
14
- super
15
- end
16
- end
17
-
18
- def deny(*args)
19
- if block_given?
20
- self._assertions += 1
21
- super(explanation=args.first, depth=1)
22
- else
23
- super
24
- end
10
+ def aver(valence, explanation = nil, depth = 0)
11
+ self._assertions += 1 # increment minitest's assert count
12
+ super(valence, explanation, depth + 1) # apparently this passes along the default block
25
13
  end
26
14
  end
@@ -1,10 +1,21 @@
1
- require "spec"
2
- require "wrong/assert"
1
+ require "wrong"
3
2
 
4
- Spec::Runner.configure do |config|
5
- include Wrong::Assert
6
-
7
- def failure_class
8
- Spec::Expectations::ExpectationNotMetError
3
+ if Object.const_defined? :Spec
4
+ Spec::Runner.configure do |config|
5
+ include Wrong
6
+
7
+ def failure_class
8
+ Spec::Expectations::ExpectationNotMetError
9
+ end
10
+ end
11
+ elsif Object.const_defined? :RSpec
12
+ RSpec.configure do |config|
13
+ include Wrong
14
+
15
+ def failure_class
16
+ RSpec::Expectations::ExpectationNotMetError
17
+ end
9
18
  end
19
+ else
20
+ raise "Wrong's RSpec adapter can't find RSpec. Please require 'spec' or 'rspec' first."
10
21
  end
@@ -1,7 +1,7 @@
1
- require "wrong/assert"
1
+ require "wrong"
2
2
 
3
3
  class Test::Unit::TestCase
4
- include Wrong::Assert
4
+ include Wrong
5
5
 
6
6
  def failure_class
7
7
  Test::Unit::AssertionFailedError
@@ -1,32 +1,31 @@
1
1
  require "predicated/predicate"
2
2
  require "predicated/from/ruby_code_string"
3
3
  require "predicated/to/sentence"
4
+
4
5
  require "wrong/chunk"
5
6
  require "wrong/config"
6
-
7
- #see http://yehudakatz.com/2009/01/18/other-ways-to-wrap-a-method/
8
- class Module
9
- def overridable(&blk)
10
- mod = Module.new(&blk)
11
- include mod
12
- end
13
- end
7
+ require "wrong/failure_message"
8
+ require "wrong/ruby2ruby_patch" # need to patch it after some other stuff loads
14
9
 
15
10
  module Wrong
16
11
  module Assert
17
12
 
18
- class AssertionFailedError < RuntimeError;
13
+ class AssertionFailedError < RuntimeError
19
14
  end
20
15
 
21
16
  def failure_class
22
17
  AssertionFailedError
23
18
  end
24
19
 
25
- # Actual signature: assert(explanation = nil, depth = 0, block)
20
+ # Actual signature: assert(explanation = nil, depth = 0, &block)
26
21
  def assert(*args, &block)
22
+ # to notice (and fail fast from) odd recursion problem
23
+ raise "Reentry bug while trying to assert(#{args.join(', ')})" if @_inside_wrong_assert
24
+ @_inside_wrong_assert = true
25
+
27
26
  if block.nil?
28
27
  begin
29
- super
28
+ super(*args) # if there's a framework assert method (sans block), then call it
30
29
  rescue NoMethodError => e
31
30
  # note: we're not raising an AssertionFailedError because this is a programmer error, not a failed assertion
32
31
  raise "You must pass a block to Wrong's assert and deny methods"
@@ -34,9 +33,11 @@ module Wrong
34
33
  else
35
34
  aver(:assert, *args, &block)
36
35
  end
36
+ ensure
37
+ @_inside_wrong_assert = false
37
38
  end
38
39
 
39
- # Actual signature: deny(explanation = nil, depth = 0, block)
40
+ # Actual signature: deny(explanation = nil, depth = 0, &block)
40
41
  def deny(*args, &block)
41
42
  if block.nil?
42
43
  test = args.first
@@ -47,101 +48,55 @@ module Wrong
47
48
  end
48
49
  end
49
50
 
50
- def rescuing
51
- error = nil
52
- begin
53
- yield
54
- rescue Exception, RuntimeError => e
55
- error = e
56
- end
57
- error
51
+ def summary(method_sym, predicate)
52
+ method_sym == :deny ? predicate.to_sentence : predicate.to_negative_sentence
58
53
  end
59
54
 
60
- # Usage:
61
- # capturing { puts "hi" } => "hi\n"
62
- # capturing(:stderr) { $stderr.puts "hi" } => "hi\n"
63
- # out, err = capturing(:stdout, :stderr) { ... }
64
- #
65
- # see http://www.justskins.com/forums/closing-stderr-105096.html for more explanation
66
- def capturing(*streams)
67
- streams = [:stdout] if streams.empty?
68
- original = {}
69
- captured = {}
70
-
71
- # reassign the $ variable (which is used by well-behaved code e.g. puts)
72
- streams.each do |stream|
73
- original[stream] = (stream == :stdout ? $stdout : $stderr)
74
- captured[stream] = StringIO.new
75
- case stream
76
- when :stdout
77
- $stdout = captured[stream]
78
- when :stderr
79
- $stderr = captured[stream]
80
- end
81
- end
55
+ protected
82
56
 
83
- yield
57
+ # for debugging -- if we couldn't make a predicate out of the code block, then this was why
58
+ def self.last_predicated_error
59
+ @@last_predicated_error ||= nil
60
+ end
84
61
 
85
- # return either one string, or an array of two strings
86
- if streams.size == 1
87
- captured[streams.first].string
88
- else
89
- [captured[streams[0]].string, captured[streams[1]].string]
90
- end
62
+ # todo: move some/all of this into FailureMessage
63
+ def full_message(chunk, block, valence, explanation)
64
+ code = chunk.code
91
65
 
92
- ensure
93
-
94
- streams.each do |stream|
95
- # bail if stream was reassigned inside the block
96
- if (stream == :stdout ? $stdout : $stderr) != captured[stream]
97
- raise "#{stream} was reassigned while being captured"
98
- end
99
- # support nested calls to capturing
100
- original[stream] << captured[stream].string if original[stream].is_a? StringIO
101
- case stream
102
- when :stdout
103
- $stdout = original[stream]
104
- when :stderr
105
- $stderr = original[stream]
106
- end
66
+ predicate = begin
67
+ Predicated::Predicate.from_ruby_code_string(code, block.binding)
68
+ rescue Predicated::Predicate::DontKnowWhatToDoWithThisSexpError, Exception => e
69
+ # save it off for debugging
70
+ @@last_predicated_error = e
71
+ nil
107
72
  end
108
- end
109
73
 
110
- overridable do
111
- def failure_message(method_sym, block, predicate)
112
- method_sym == :deny ? predicate.to_sentence : predicate.to_negative_sentence
74
+ code = code.color(:blue) if Wrong.config[:color]
75
+ message = ""
76
+ message << "#{explanation}: " if explanation
77
+ message << "#{valence == :deny ? "Didn't expect" : "Expected"} #{code}, but "
78
+ if predicate && !(predicate.is_a? Predicated::Conjunction)
79
+ message << summary(valence, predicate)
80
+ if formatter = FailureMessage.formatter_for(predicate)
81
+ failure = formatter.describe
82
+ failure = failure.bold if Wrong.config[:color]
83
+ message << failure
84
+ end
113
85
  end
86
+ message << chunk.details
87
+ message
114
88
  end
115
89
 
116
- private
117
-
118
90
  def aver(valence, explanation = nil, depth = 0, &block)
119
91
  require "wrong/rainbow" if Wrong.config[:color]
120
-
92
+
121
93
  value = block.call
122
94
  value = !value if valence == :deny
123
95
  unless value
96
+
124
97
  chunk = Wrong::Chunk.from_block(block, depth + 2)
125
- code = chunk.code
126
-
127
- predicate = begin
128
- Predicated::Predicate.from_ruby_code_string(code, block.binding)
129
- rescue Predicated::Predicate::DontKnowWhatToDoWithThisSexpError
130
- nil
131
- rescue Exception
132
- nil
133
- end
134
98
 
135
- code = code.color(:blue) if Wrong.config[:color]
136
- message = ""
137
- message << "#{explanation}: " if explanation
138
- message << "#{valence == :deny ? "Didn't expect" : "Expected"} #{code}, but "
139
- if predicate && !(predicate.is_a? Predicated::Conjunction)
140
- failure = failure_message(valence, block, predicate)
141
- failure = failure.bold if Wrong.config[:color]
142
- message << failure
143
- end
144
- message << chunk.details
99
+ message = full_message(chunk, block, valence, explanation)
145
100
  raise failure_class.new(message)
146
101
  end
147
102
  end
@@ -1,18 +1,35 @@
1
1
  require 'ruby_parser'
2
2
  require 'ruby2ruby'
3
+
4
+ begin
5
+ require "ParseTree"
6
+ rescue LoadError => e
7
+ raise e unless e.message == "no such file to load -- ParseTree"
8
+ end
9
+
10
+ begin
11
+ require "sourcify"
12
+ rescue LoadError => e
13
+ raise e unless e.message == "no such file to load -- sourcify"
14
+ end
15
+
3
16
  require "wrong/config"
4
17
  require "wrong/sexp_ext"
5
18
 
6
19
  module Wrong
7
20
  class Chunk
8
21
  def self.from_block(block, depth = 0)
9
- file, line = if block.to_proc.respond_to? :source_location
10
- # in Ruby 1.9, it reads the source location from the block
11
- block.to_proc.source_location
12
- else
13
- # in Ruby 1.8, it reads the source location from the call stack
14
- caller[depth].split(":")
15
- end
22
+
23
+ as_proc = block.to_proc
24
+ file, line =
25
+ if as_proc.respond_to? :source_location
26
+ # in Ruby 1.9, or with Sourcify, it reads the source location from the block
27
+ as_proc.source_location
28
+ else
29
+ # in Ruby 1.8, it reads the source location from the call stack
30
+ caller[depth].split(":")
31
+ end
32
+
16
33
  new(file, line, block)
17
34
  end
18
35
 
@@ -33,38 +50,68 @@ module Wrong
33
50
  "#{@file}:#{@line_number}"
34
51
  end
35
52
 
53
+ def sexp
54
+ @sexp ||= build_sexp
55
+ end
56
+
57
+ def build_sexp
58
+ sexp = begin
59
+ unless @block.nil? or @block.is_a?(String) or !Object.const_defined?(:Sourcify)
60
+ # first try sourcify
61
+ @block.to_sexp[3] # the [3] is to strip out the "proc {" sourcify adds to everything
62
+ end
63
+ rescue ::Sourcify::MultipleMatchingProcsPerLineError, Racc::ParseError, Errno::ENOENT => e
64
+ # fall through
65
+ end
66
+
67
+ # next try glomming
68
+ sexp ||= glom(if @file == "(irb)"
69
+ IRB.CurrentContext.all_lines
70
+ else
71
+ read_source_file(@file)
72
+ end)
73
+ end
74
+
75
+ def read_source_file(file, dir = ".")
76
+ File.read "#{dir}/#{file}"
77
+
78
+ rescue Errno::ENOENT, Errno::EACCES => e
79
+ # we may be in a chdir underneath where the file is, so move up one level and try again
80
+ parent = "#{dir}/..".gsub(/^(\.\/)*/, '')
81
+ if File.expand_path(dir) == File.expand_path(parent)
82
+ raise Errno::ENOENT, "couldn't find #{file}"
83
+ end
84
+ read_source_file(file, parent)
85
+
86
+ end
87
+
36
88
  # Algorithm:
37
- # try to parse the starting line
38
- # if it parses OK, then we're done!
39
- # if not, then glom the next line and try again
40
- # repeat until it parses or we're out of lines
41
- def parse
42
- source = if @file == "(irb)"
43
- IRB.CurrentContext.all_lines
44
- else
45
- File.read(@file)
46
- end
89
+ # * try to parse the starting line
90
+ # * if it parses OK, then we're done!
91
+ # * if not, then glom the next line and try again
92
+ # * repeat until it parses or we're out of lines
93
+ def glom(source)
47
94
  lines = source.split("\n")
48
95
  @parser ||= RubyParser.new
49
96
  @chunk = nil
50
97
  c = 0
51
- @sexp = nil
52
- while @sexp.nil? && line_index + c < lines.size
98
+ sexp = nil
99
+ while sexp.nil? && line_index + c < lines.size
53
100
  begin
54
101
  @chunk = lines[line_index..line_index+c].join("\n")
55
- @sexp = @parser.parse(@chunk)
102
+ sexp = @parser.parse(@chunk)
56
103
  rescue Racc::ParseError => e
57
104
  # loop and try again
58
105
  c += 1
59
106
  end
60
107
  end
61
- @sexp
108
+ sexp
62
109
  end
63
-
110
+
64
111
  # The claim is the part of the assertion inside the curly braces.
65
112
  # E.g. for "assert { x == 5 }" the claim is "x == 5"
66
113
  def claim
67
- parse()
114
+ sexp()
68
115
 
69
116
  if @sexp.nil?
70
117
  raise "Could not parse #{location}"
@@ -82,6 +129,10 @@ module Wrong
82
129
 
83
130
  def code
84
131
  self.claim.to_ruby
132
+ rescue => e
133
+ # note: this is untested; it's to recover from when we can't locate the code
134
+ message = "Failed assertion at #{caller[depth + 2]} [couldn't retrieve source code due to #{e.inspect}]"
135
+ raise failure_class.new(message)
85
136
  end
86
137
 
87
138
  def parts(sexp = nil)
@@ -99,11 +150,17 @@ module Wrong
99
150
  puts "#{e.class}: #{e.message}"
100
151
  puts e.backtrace.join("\n")
101
152
  end
153
+
154
+ if sexp.first == :iter
155
+ sexp.delete_at(1) # remove the method-call-sans-block subnode
156
+ end
157
+
102
158
  sexp.each do |sub|
103
159
  if sub.is_a?(Sexp)
104
160
  parts_list += parts(sub)
105
161
  end
106
162
  end
163
+
107
164
  parts_list
108
165
  end
109
166
  end
@@ -120,7 +177,7 @@ module Wrong
120
177
  parts.each do |part|
121
178
  begin
122
179
  value = eval(part, block.binding)
123
- unless part == value.inspect # this skips literals or tautologies
180
+ unless part == value.inspect # this skips literals or tautologies
124
181
  if part =~ /\n/m
125
182
  part.gsub!(/\n/, newline(2))
126
183
  part += newline(3)
@@ -138,7 +195,12 @@ module Wrong
138
195
  part = part.color(:blue)
139
196
  raises = raises.bold.color(:red)
140
197
  end
141
- details << indent(2, part, " ", raises, ": ", indent_all(3, e.message))
198
+ formatted_exeption = if e.message and e.message != e.class.to_s
199
+ indent(2, part, " ", raises, ": ", indent_all(3, e.message))
200
+ else
201
+ indent(2, part, " ", raises)
202
+ end
203
+ details << formatted_exeption
142
204
  end
143
205
  end
144
206
  end
@@ -149,11 +211,11 @@ module Wrong
149
211
  else
150
212
  "\n" + details.join("\n") + "\n"
151
213
  end
152
-
214
+
153
215
  end
154
216
 
155
217
  private
156
-
218
+
157
219
  def indent(indent, *s)
158
220
  "#{" " * indent}#{s.join('')}"
159
221
  end