wrong 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +77 -11
- data/lib/wrong.rb +20 -0
- data/lib/wrong/adapters/minitest.rb +6 -18
- data/lib/wrong/adapters/rspec.rb +18 -7
- data/lib/wrong/adapters/test_unit.rb +2 -2
- data/lib/wrong/assert.rb +45 -90
- data/lib/wrong/chunk.rb +89 -27
- data/lib/wrong/close_to.rb +7 -11
- data/lib/wrong/d.rb +42 -0
- data/lib/wrong/failure_message.rb +43 -0
- data/lib/wrong/helpers.rb +66 -0
- data/lib/wrong/irb.rb +1 -1
- data/lib/wrong/message/array_diff.rb +57 -75
- data/lib/wrong/message/string_comparison.rb +88 -0
- data/lib/wrong/message/test_context.rb +2 -0
- data/lib/{predicated/lib/predicated/sexp_patch.rb → wrong/ruby2ruby_patch.rb} +3 -5
- data/lib/wrong/sexp_ext.rb +12 -4
- data/lib/wrong/version.rb +1 -1
- data/test/adapters/rspec1/failing_spec.rb +23 -0
- data/test/adapters/rspec2/failing_spec.rb +26 -0
- data/test/adapters/rspec_test.rb +65 -4
- data/test/adapters/test_unit_test.rb +6 -1
- data/test/assert_advanced_test.rb +51 -0
- data/test/assert_test.rb +4 -48
- data/test/capturing_test.rb +4 -2
- data/test/chunk_test.rb +36 -11
- data/test/close_to_test.rb +2 -2
- data/test/config_test.rb +5 -5
- data/test/d_test.rb +64 -0
- data/test/failure_message_test.rb +40 -0
- data/test/failures_test.rb +6 -7
- data/test/message/array_diff_test.rb +18 -14
- data/test/message/{test_context_text.rb → test_context_test.rb} +2 -1
- data/test/rescuing_test.rb +5 -4
- data/test/separate.rb +4 -0
- data/test/sexp_ext_test.rb +2 -2
- data/test/string_comparison_test.rb +159 -0
- data/test/suite.rb +7 -4
- data/test/test_helper.rb +2 -0
- data/test/wrong_test.rb +60 -0
- metadata +92 -46
- data/lib/wrong/message/string_diff.rb +0 -42
- data/test/message/string_diff_test.rb +0 -89
data/README.markdown
CHANGED
@@ -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
|
-
|
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
|
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
|
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
|
-
*
|
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
|
data/lib/wrong.rb
CHANGED
@@ -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
|
1
|
+
require "wrong"
|
2
2
|
|
3
3
|
class MiniTest::Unit::TestCase
|
4
|
-
include Wrong
|
4
|
+
include Wrong
|
5
|
+
|
5
6
|
def failure_class
|
6
7
|
MiniTest::Assertion
|
7
8
|
end
|
8
9
|
|
9
|
-
def
|
10
|
-
|
11
|
-
|
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
|
data/lib/wrong/adapters/rspec.rb
CHANGED
@@ -1,10 +1,21 @@
|
|
1
|
-
require "
|
2
|
-
require "wrong/assert"
|
1
|
+
require "wrong"
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
data/lib/wrong/assert.rb
CHANGED
@@ -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
|
-
#
|
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
|
51
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
#
|
96
|
-
|
97
|
-
|
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
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
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
|
data/lib/wrong/chunk.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
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
|
-
|
52
|
-
while
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|