wrong 0.4.0-java
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.
- data/README.markdown +300 -0
- data/lib/wrong.rb +27 -0
- data/lib/wrong/adapters/minitest.rb +14 -0
- data/lib/wrong/adapters/rspec.rb +21 -0
- data/lib/wrong/adapters/test_unit.rb +9 -0
- data/lib/wrong/assert.rb +105 -0
- data/lib/wrong/chunk.rb +233 -0
- data/lib/wrong/close_to.rb +9 -0
- data/lib/wrong/config.rb +29 -0
- 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 +16 -0
- data/lib/wrong/message/array_diff.rb +69 -0
- data/lib/wrong/message/string_comparison.rb +88 -0
- data/lib/wrong/message/test_context.rb +28 -0
- data/lib/wrong/rainbow.rb +127 -0
- data/lib/wrong/ruby2ruby_patch.rb +37 -0
- data/lib/wrong/sexp_ext.rb +49 -0
- data/lib/wrong/version.rb +3 -0
- data/test/adapters/minitest_test.rb +97 -0
- data/test/adapters/rspec1/failing_spec.rb +23 -0
- data/test/adapters/rspec2/failing_spec.rb +26 -0
- data/test/adapters/rspec_test.rb +104 -0
- data/test/adapters/test_unit_test.rb +59 -0
- data/test/assert_advanced_test.rb +51 -0
- data/test/assert_test.rb +76 -0
- data/test/capturing_test.rb +59 -0
- data/test/chunk_test.rb +264 -0
- data/test/close_to_test.rb +39 -0
- data/test/config_test.rb +89 -0
- data/test/d_test.rb +64 -0
- data/test/failure_message_test.rb +40 -0
- data/test/failures_test.rb +157 -0
- data/test/message/array_diff_test.rb +79 -0
- data/test/message/test_context_test.rb +69 -0
- data/test/rescuing_test.rb +17 -0
- data/test/separate.rb +4 -0
- data/test/sexp_ext_test.rb +80 -0
- data/test/string_comparison_test.rb +159 -0
- data/test/suite.rb +7 -0
- data/test/test_helper.rb +64 -0
- data/test/wrong_test.rb +60 -0
- metadata +215 -0
data/lib/wrong/chunk.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'ruby_parser'
|
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
|
+
|
16
|
+
require "wrong/config"
|
17
|
+
require "wrong/sexp_ext"
|
18
|
+
|
19
|
+
module Wrong
|
20
|
+
class Chunk
|
21
|
+
def self.from_block(block, depth = 0)
|
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
|
+
|
33
|
+
new(file, line, block)
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :file, :line_number, :block
|
37
|
+
|
38
|
+
# line parameter is 1-based
|
39
|
+
def initialize(file, line_number, block = nil)
|
40
|
+
@file = file
|
41
|
+
@line_number = line_number.to_i
|
42
|
+
@block = block
|
43
|
+
end
|
44
|
+
|
45
|
+
def line_index
|
46
|
+
@line_number - 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def location
|
50
|
+
"#{@file}:#{@line_number}"
|
51
|
+
end
|
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
|
+
|
88
|
+
# Algorithm:
|
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)
|
94
|
+
lines = source.split("\n")
|
95
|
+
@parser ||= RubyParser.new
|
96
|
+
@chunk = nil
|
97
|
+
c = 0
|
98
|
+
sexp = nil
|
99
|
+
while sexp.nil? && line_index + c < lines.size
|
100
|
+
begin
|
101
|
+
@chunk = lines[line_index..line_index+c].join("\n")
|
102
|
+
sexp = @parser.parse(@chunk)
|
103
|
+
rescue Racc::ParseError => e
|
104
|
+
# loop and try again
|
105
|
+
c += 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
sexp
|
109
|
+
end
|
110
|
+
|
111
|
+
# The claim is the part of the assertion inside the curly braces.
|
112
|
+
# E.g. for "assert { x == 5 }" the claim is "x == 5"
|
113
|
+
def claim
|
114
|
+
sexp()
|
115
|
+
|
116
|
+
if @sexp.nil?
|
117
|
+
raise "Could not parse #{location}"
|
118
|
+
else
|
119
|
+
assertion = @sexp.assertion
|
120
|
+
statement = assertion && assertion[3]
|
121
|
+
if statement.nil?
|
122
|
+
@sexp
|
123
|
+
# raise "Could not find assertion in #{location}\n\t#{@chunk.strip}\n\t#{@sexp}"
|
124
|
+
else
|
125
|
+
statement
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def code
|
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)
|
136
|
+
end
|
137
|
+
|
138
|
+
def parts(sexp = nil)
|
139
|
+
if sexp.nil?
|
140
|
+
parts(self.claim).compact.uniq
|
141
|
+
else
|
142
|
+
# todo: extract some of this into Sexp
|
143
|
+
parts_list = []
|
144
|
+
begin
|
145
|
+
unless sexp.first == :arglist
|
146
|
+
code = sexp.to_ruby.strip
|
147
|
+
parts_list << code unless code == "" || parts_list.include?(code)
|
148
|
+
end
|
149
|
+
rescue => e
|
150
|
+
puts "#{e.class}: #{e.message}"
|
151
|
+
puts e.backtrace.join("\n")
|
152
|
+
end
|
153
|
+
|
154
|
+
if sexp.first == :iter
|
155
|
+
sexp.delete_at(1) # remove the method-call-sans-block subnode
|
156
|
+
end
|
157
|
+
|
158
|
+
sexp.each do |sub|
|
159
|
+
if sub.is_a?(Sexp)
|
160
|
+
parts_list += parts(sub)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
parts_list
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def details
|
169
|
+
require "wrong/rainbow" if Wrong.config[:color]
|
170
|
+
s = ""
|
171
|
+
parts = self.parts
|
172
|
+
parts.shift # remove the first part, since it's the same as the code
|
173
|
+
|
174
|
+
details = []
|
175
|
+
|
176
|
+
if parts.size > 0
|
177
|
+
parts.each do |part|
|
178
|
+
begin
|
179
|
+
value = eval(part, block.binding)
|
180
|
+
unless part == value.inspect # this skips literals or tautologies
|
181
|
+
if part =~ /\n/m
|
182
|
+
part.gsub!(/\n/, newline(2))
|
183
|
+
part += newline(3)
|
184
|
+
end
|
185
|
+
value = indent_all(3, value.inspect)
|
186
|
+
if Wrong.config[:color]
|
187
|
+
part = part.color(:blue)
|
188
|
+
value = value.color(:magenta)
|
189
|
+
end
|
190
|
+
details << indent(2, part, " is ", value)
|
191
|
+
end
|
192
|
+
rescue Exception => e
|
193
|
+
raises = "raises #{e.class}"
|
194
|
+
if Wrong.config[:color]
|
195
|
+
part = part.color(:blue)
|
196
|
+
raises = raises.bold.color(:red)
|
197
|
+
end
|
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
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
details.uniq!
|
209
|
+
if details.empty?
|
210
|
+
""
|
211
|
+
else
|
212
|
+
"\n" + details.join("\n") + "\n"
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
|
219
|
+
def indent(indent, *s)
|
220
|
+
"#{" " * indent}#{s.join('')}"
|
221
|
+
end
|
222
|
+
|
223
|
+
def newline(indent)
|
224
|
+
"\n" + self.indent(indent)
|
225
|
+
end
|
226
|
+
|
227
|
+
def indent_all(amount, s)
|
228
|
+
s.gsub("\n", "\n#{indent(amount)}")
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
data/lib/wrong/config.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Wrong
|
2
|
+
def self.config
|
3
|
+
@config ||= Config.new
|
4
|
+
end
|
5
|
+
|
6
|
+
class Config < Hash
|
7
|
+
def alias_assert(method_name)
|
8
|
+
Wrong::Assert.send(:alias_method, method_name, :assert)
|
9
|
+
self.assert_method_names << method_name.to_sym unless self.assert_method_names.include?(method_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
def alias_deny(method_name)
|
13
|
+
Wrong::Assert.send(:alias_method, method_name, :deny)
|
14
|
+
self.deny_method_names << method_name.to_sym unless self.deny_method_names.include?(method_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert_method_names
|
18
|
+
(self[:assert_method] ||= [:assert])
|
19
|
+
end
|
20
|
+
|
21
|
+
def deny_method_names
|
22
|
+
(self[:deny_method] ||= [:deny])
|
23
|
+
end
|
24
|
+
|
25
|
+
def assert_methods
|
26
|
+
assert_method_names + deny_method_names
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/wrong/d.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "sexp"
|
2
|
+
require "wrong/chunk"
|
3
|
+
|
4
|
+
class ::Sexp < ::Array
|
5
|
+
def d?
|
6
|
+
is_a?(Sexp) &&
|
7
|
+
(self[0] == :iter) &&
|
8
|
+
(self[1][0] == :call) &&
|
9
|
+
(self[1][2] == :d)
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
module Wrong
|
15
|
+
module D
|
16
|
+
def d(*args, &block)
|
17
|
+
called_from = caller.first.split(':')
|
18
|
+
chunk = Chunk.from_block(block, 1)
|
19
|
+
sexp = chunk.sexp
|
20
|
+
|
21
|
+
# look for a "d" inside the block
|
22
|
+
sexp.each_subexp do |subexp|
|
23
|
+
if subexp.d?
|
24
|
+
sexp = subexp[3] # swap in the block part of the nested d call
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
code = sexp.to_ruby
|
29
|
+
value = eval(code, block.binding, called_from[0], called_from[1].to_i).inspect
|
30
|
+
|
31
|
+
if Wrong.config[:color]
|
32
|
+
require "wrong/rainbow"
|
33
|
+
code = code.color(:blue)
|
34
|
+
value = value.color(:magenta)
|
35
|
+
end
|
36
|
+
|
37
|
+
puts [code, "is", value].join(" ")
|
38
|
+
end
|
39
|
+
|
40
|
+
extend D # this allows you to call Wrong::D.d if you like
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Wrong
|
2
|
+
class FailureMessage
|
3
|
+
@@formatters = []
|
4
|
+
|
5
|
+
def self.register_formatter(formatter)
|
6
|
+
@@formatters << formatter
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.formatters
|
10
|
+
@@formatters
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.formatter_for(predicate)
|
14
|
+
@@formatters.each do |formatter_class|
|
15
|
+
formatter = formatter_class.new(predicate)
|
16
|
+
if formatter.match?
|
17
|
+
return formatter
|
18
|
+
end
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
class Formatter
|
24
|
+
def self.register
|
25
|
+
Wrong::FailureMessage.register_formatter(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :predicate
|
29
|
+
|
30
|
+
def initialize(predicate)
|
31
|
+
@predicate = predicate
|
32
|
+
end
|
33
|
+
|
34
|
+
def describe(valence)
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def match?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Wrong
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# Executes a block that is expected to raise an exception. Returns that exception, or nil if none was raised.
|
5
|
+
def rescuing
|
6
|
+
error = nil
|
7
|
+
begin
|
8
|
+
yield
|
9
|
+
rescue Exception, RuntimeError => e
|
10
|
+
error = e
|
11
|
+
end
|
12
|
+
error
|
13
|
+
end
|
14
|
+
|
15
|
+
# Usage:
|
16
|
+
# capturing { puts "hi" } => "hi\n"
|
17
|
+
# capturing(:stderr) { $stderr.puts "hi" } => "hi\n"
|
18
|
+
# out, err = capturing(:stdout, :stderr) { ... }
|
19
|
+
#
|
20
|
+
# see http://www.justskins.com/forums/closing-stderr-105096.html for more explanation
|
21
|
+
def capturing(*streams)
|
22
|
+
streams = [:stdout] if streams.empty?
|
23
|
+
original = {}
|
24
|
+
captured = {}
|
25
|
+
|
26
|
+
# reassign the $ variable (which is used by well-behaved code e.g. puts)
|
27
|
+
streams.each do |stream|
|
28
|
+
original[stream] = (stream == :stdout ? $stdout : $stderr)
|
29
|
+
captured[stream] = StringIO.new
|
30
|
+
case stream
|
31
|
+
when :stdout
|
32
|
+
$stdout = captured[stream]
|
33
|
+
when :stderr
|
34
|
+
$stderr = captured[stream]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
yield
|
39
|
+
|
40
|
+
# return either one string, or an array of two strings
|
41
|
+
if streams.size == 1
|
42
|
+
captured[streams.first].string
|
43
|
+
else
|
44
|
+
[captured[streams[0]].string, captured[streams[1]].string]
|
45
|
+
end
|
46
|
+
|
47
|
+
ensure
|
48
|
+
|
49
|
+
streams.each do |stream|
|
50
|
+
# bail if stream was reassigned inside the block
|
51
|
+
if (stream == :stdout ? $stdout : $stderr) != captured[stream]
|
52
|
+
raise "#{stream} was reassigned while being captured"
|
53
|
+
end
|
54
|
+
# support nested calls to capturing
|
55
|
+
original[stream] << captured[stream].string if original[stream].is_a? StringIO
|
56
|
+
case stream
|
57
|
+
when :stdout
|
58
|
+
$stdout = original[stream]
|
59
|
+
when :stderr
|
60
|
+
$stderr = original[stream]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
data/lib/wrong/irb.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
if defined? IRB
|
2
|
+
module IRB
|
3
|
+
class Context
|
4
|
+
alias :original_evaluate :evaluate
|
5
|
+
attr_reader :all_lines
|
6
|
+
|
7
|
+
def evaluate(line, line_no)
|
8
|
+
(@all_lines ||= "") <<line
|
9
|
+
original_evaluate line, line_no
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# include it in the top level too, since if you're using Wrong inside IRB that's probably what you want
|
15
|
+
include Wrong
|
16
|
+
end
|