rdx 0.9.0.pre
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.
- checksums.yaml +7 -0
- data/.rdx +20 -0
- data/README +19 -0
- data/bin/rdx +7 -0
- data/examples/minimal/.rdx +8 -0
- data/examples/minimal/README +10 -0
- data/examples/minimal/lib/other_conventions.rb +64 -0
- data/examples/minimal/lib/the_basics.rb +94 -0
- data/examples/minimal/lib/using_directives.rb +66 -0
- data/examples/minimal/rakefile +27 -0
- data/examples/ruby-2.0.0-p0/README +7 -0
- data/examples/ruby-2.0.0-p0/install/core/.rdx +6 -0
- data/examples/ruby-2.0.0-p0/install/core/README +19 -0
- data/examples/ruby-2.0.0-p0/install/core/Rakefile +61 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/array.c.diff +166 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/bignum.c.diff +11 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/class.c.diff +36 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/compar.c.diff +11 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/complex.c.diff +301 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/cont.c.diff +65 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/dir.c.diff +147 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/re.rdoc.diff +328 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/security.rdoc.diff +8 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/standard_library.rdoc.diff +0 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax.rdoc.diff +0 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/assignment.rdoc.diff +160 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/calling_methods.rdoc.diff +130 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/control_expressions.rdoc.diff +254 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/exceptions.rdoc.diff +0 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/literals.rdoc.diff +54 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/methods.rdoc.diff +157 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/miscellaneous.rdoc.diff +91 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/modules_and_classes.rdoc.diff +161 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/precedence.rdoc.diff +8 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/doc/syntax/refinements.rdoc.diff +146 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/encoding.c.diff +276 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/enum.c.diff +281 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/enumerator.c.diff +479 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/error.c.diff +143 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/eval.c.diff +47 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/eval_jump.c.diff +23 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/file.c.diff +752 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/gc.c.diff +195 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/hash.c.diff +84 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/iseq.c.diff +354 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/load.c.diff +53 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/marshal.c.diff +98 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/math.c.diff +110 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/numeric.c.diff +103 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/object.c.diff +295 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/pack.c.diff +18 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/parse.y.diff +23 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/proc.c.diff +155 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/random.c.diff +126 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/range.c.diff +49 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/rational.c.diff +312 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/re.c.diff +207 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/ruby.c.diff +21 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/signal.c.diff +67 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/sprintf.c.diff +29 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/string.c.diff +73 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/struct.c.diff +20 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/time.c.diff +691 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/transcode.c.diff +435 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/variable.c.diff +62 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_backtrace.c.diff +164 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_eval.c.diff +99 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_method.c.diff +17 -0
- data/examples/ruby-2.0.0-p0/install/core/diffs/vm_trace.c.diff +393 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/.rdx +6 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/README +19 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/Rakefile +53 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/abbrev.rb.diff +77 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/base64.rb.diff +42 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/benchmark.rb.diff +144 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/cmath.rb.diff +52 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/forwardable.rb.diff +150 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/mathn.rb.diff +58 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/matrix.rb.diff +657 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/observer.rb.diff +31 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/optparse.rb.diff +147 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/ostruct.rb.diff +78 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/prime.rb.diff +52 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/pstore.rb.diff +110 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/scanf.rb.diff +100 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/securerandom.rb.diff +144 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/set.rb.diff +637 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/shellwords.rb.diff +66 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/singleton.rb.diff +37 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/tempfile.rb.diff +104 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/thread.rb.diff +38 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/time.rb.diff +140 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/tmpdir.rb.diff +52 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/uri.rb.diff +39 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/uri/common.rb.diff +237 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/weakref.rb.diff +36 -0
- data/examples/ruby-2.0.0-p0/install/stdlib/diffs/lib/yaml/store.rb.diff +27 -0
- data/examples/ruby-2.0.0-p0/rakefile +165 -0
- data/lib/rdx.rb +331 -0
- data/lib/rdx/assertions.rb +484 -0
- data/lib/rdx/binding.rb +151 -0
- data/lib/rdx/code_object.rb +598 -0
- data/lib/rdx/comment.rb +338 -0
- data/lib/rdx/convention.rb +1174 -0
- data/lib/rdx/directive.rb +1432 -0
- data/lib/rdx/example.rb +679 -0
- data/lib/rdx/generator.rb +112 -0
- data/lib/rdx/generator/rdoc.rb +1006 -0
- data/lib/rdx/options.rb +359 -0
- data/lib/rdx/plain_text.rb +65 -0
- data/lib/rdx/reporter.rb +421 -0
- data/lib/rdx/ruby_lex.rb +324 -0
- data/lib/rdx/runner.rb +309 -0
- data/lib/rdx/source_file.rb +94 -0
- data/lib/rdx/specification.rb +194 -0
- data/lib/rdx/statement.rb +248 -0
- data/lib/rdx/store.rb +119 -0
- data/lib/rdx/task.rb +361 -0
- data/lib/rdx/text.rb +688 -0
- data/lib/rdx/version.rb +15 -0
- data/rakefile +64 -0
- metadata +203 -0
data/lib/rdx.rb
ADDED
@@ -0,0 +1,331 @@
|
|
1
|
+
#
|
2
|
+
# = RDX: Ruby Documentation example eXecutor
|
3
|
+
#
|
4
|
+
# The aim of RDX is to join the worlds of documentation and testing, whose overlap in examples.
|
5
|
+
# Relying on the RDoc documentation generator tool and Minitest RDX parses source files,
|
6
|
+
# generates documentation, extracts examples from comments and executes them as tests.
|
7
|
+
#
|
8
|
+
# == Why RDX?
|
9
|
+
#
|
10
|
+
# The whole application is built on these four cornerstones:
|
11
|
+
# * Document Well and Right!
|
12
|
+
#
|
13
|
+
# The library should be well documented. The examples can be the most direct way
|
14
|
+
# to explain and show the use of the library - if these have been unit tested and work fine.
|
15
|
+
# * DRY (Don't Repeat Yourself)!
|
16
|
+
#
|
17
|
+
# We want to avoid - honestly, we should hate - writing the same things more than once.
|
18
|
+
# It's too tedious to translate back and forth all the examples between documentation and testing.
|
19
|
+
# * Join coupled parts!
|
20
|
+
#
|
21
|
+
# Source, documentation and tests are different sides of the same thing - the programmer's idea -.
|
22
|
+
# When some parts are tightly coupled, the more these are close the more straightforward the eventual
|
23
|
+
# fix is. Documentation tools unify documentation with the source, RDX tries to take the next step.
|
24
|
+
# * Be Consistent!
|
25
|
+
#
|
26
|
+
# Most scripts of the Ruby Core and many of the Standard Library already have the examples
|
27
|
+
# written in a conformed and accepted way. It was this coherency that allowed (and induced)
|
28
|
+
# me to write RDX: a tool can easily automate tasks if they fit well into a general scheme.
|
29
|
+
#
|
30
|
+
# == How RDX works
|
31
|
+
#
|
32
|
+
# In order to achieve its goal it first deals with the documentation tool
|
33
|
+
# (RDoc is the most commonly used, so currently only its support is built-in) asking to keep
|
34
|
+
# track of examples. These are parsed and subdivided into statements: the comment at
|
35
|
+
# their end can eventually become an expectation. The tests are build according to
|
36
|
+
# conventions and directives.
|
37
|
+
#
|
38
|
+
# The *conventions*, heart of RDX, define the rules to accept the ending comments and process the code
|
39
|
+
# (for example the historical hash-rocket notation, <tt>#=></tt>, is implemented in Convention::result_eval:
|
40
|
+
# it evaluates both the statement and the expectation, then compares those results through +assert_equal+).
|
41
|
+
# See more details in the RDX::Convention class.
|
42
|
+
#
|
43
|
+
# While conventions affect single statements, the *directives* operate on a larger scale.
|
44
|
+
# Perhaps we may want to not execute an example, to interpret one as the output of the
|
45
|
+
# previous, to locally change some of the assertions methods, to run it in a temporary directory,
|
46
|
+
# to simulate a bash environment, and so on...
|
47
|
+
# See the RDX::Directive class for more information about them.
|
48
|
+
#
|
49
|
+
# Both the conventions and directives can be either built-in because of their popularity in the
|
50
|
+
# Ruby Community or defined for convenience by the user.
|
51
|
+
#
|
52
|
+
# Once these tests are built, the only thing left to do is to simply run them.
|
53
|
+
# As a bonus, if everything is correct, RDX's signature is added to those made by the generator
|
54
|
+
# tool: the user of our library now can know that the examples showed enjoy a high level of trust.
|
55
|
+
# All this with a single run, either from the command line or through rake.
|
56
|
+
#
|
57
|
+
# == Conclusions
|
58
|
+
#
|
59
|
+
# When RDX has finished we can see the report of our tests, as if we had manually written all of
|
60
|
+
# them refactoring the comments. In fact, it's even better: if an example has something wrong - if it
|
61
|
+
# doesn't work as declared - the error points us to its location in the comment, which is just above
|
62
|
+
# the source code. This makes the tracking and fixing steps inceadibly easy.
|
63
|
+
#
|
64
|
+
# Through execution, <b>RDX gives formalism to the documentation examples</b>,
|
65
|
+
# greatly increasing their strength. It gives all the section of a source file the deserved emphasis,
|
66
|
+
# transforming fictitious examples into living ones!
|
67
|
+
#
|
68
|
+
# A great point in its favour is that a developer familiar with the Ruby documentation
|
69
|
+
# (of course most of them) doesn't have to learn a new DSL or syntax, but just
|
70
|
+
# to code and document in the "standard" way, with very few alteration.
|
71
|
+
#
|
72
|
+
# == Security
|
73
|
+
#
|
74
|
+
# The point that +RDX+ evaluates the comments leads to the topic of security.
|
75
|
+
# Never run blindly RDX on libraries that haven't been written for that, since it's never
|
76
|
+
# a good idea to evaluate unknown code.
|
77
|
+
# If you want, fisrt you have to scan the files and find the examples
|
78
|
+
# (the <tt>--dry-run</tt> option may help you, though it isn't fully secure).
|
79
|
+
# Once you have found that the code is safe you can give RDX a try.
|
80
|
+
#
|
81
|
+
# However, if a library has been built RDX-compatible, you can run it
|
82
|
+
# from your machine - if you _trust_ the producer. If you don't
|
83
|
+
# you must be very careful: even a plain +require+ can be dangerous,
|
84
|
+
# with RDX you have to pay an extra attention to the comments.
|
85
|
+
#
|
86
|
+
# == Roadmap
|
87
|
+
#
|
88
|
+
# We have already talked about conventions, directives and their relative classes.
|
89
|
+
# These are probably the most intresting topics.
|
90
|
+
#
|
91
|
+
# If you want to define your own conventions and directives, see the Specification.
|
92
|
+
#
|
93
|
+
# If you want to know the available command line options, see Options.
|
94
|
+
#
|
95
|
+
# If you want to run RDX through +rake+, see Task.
|
96
|
+
#
|
97
|
+
# The role of other classes is explained in these sections.
|
98
|
+
#
|
99
|
+
# == Credits
|
100
|
+
#
|
101
|
+
# I have ideated RDX and I am developing it. <br>
|
102
|
+
# I think this is the best way I have to thank Yukihiro "Matz" Matsumoto
|
103
|
+
# for the beatiful language he has designed.
|
104
|
+
#
|
105
|
+
# Maurizio Destefanis
|
106
|
+
#
|
107
|
+
module RDX
|
108
|
+
|
109
|
+
# :stopdoc:
|
110
|
+
|
111
|
+
require 'stringio'
|
112
|
+
require 'tempfile'
|
113
|
+
require 'pathname'
|
114
|
+
|
115
|
+
ROOT = File.dirname(__FILE__).freeze
|
116
|
+
|
117
|
+
require 'rdx/version'
|
118
|
+
|
119
|
+
VERSION_HEADER = "RDX version: #{VERSION}".freeze
|
120
|
+
|
121
|
+
HOMEPAGE = 'https://rubygems.org/gems/rdx'.freeze
|
122
|
+
|
123
|
+
SIGNATURE = "Examples tested with <a href='#{HOMEPAGE}'>RDX</a> #{VERSION}".freeze
|
124
|
+
|
125
|
+
|
126
|
+
Error = Class.new RuntimeError
|
127
|
+
|
128
|
+
# Raised when invalid code is processed.
|
129
|
+
class ParseError < RuntimeError
|
130
|
+
attr_accessor :line_no
|
131
|
+
def initialize msg=nil, line_no=nil
|
132
|
+
super msg
|
133
|
+
@line_no = line_no
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class Assertion < Exception
|
138
|
+
end
|
139
|
+
|
140
|
+
class << self
|
141
|
+
attr_accessor :backtrace_filter
|
142
|
+
end
|
143
|
+
class BacktraceFilter # :nodoc:
|
144
|
+
def filter bt
|
145
|
+
return ['No backtrace'] if bt.empty?
|
146
|
+
return bt.take_while{ |line| !line.start_with?(RDX::ROOT) } unless RDX.options.debug
|
147
|
+
# We are not debugging launching tools (eg: rake)
|
148
|
+
last_index = bt.rindex{ |line| line.start_with?(RDX::ROOT) } || bt.size-1
|
149
|
+
bt.take last_index+1
|
150
|
+
end
|
151
|
+
end
|
152
|
+
self.backtrace_filter = BacktraceFilter.new
|
153
|
+
def self.filter_backtrace bt
|
154
|
+
backtrace_filter.filter bt
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.runner
|
158
|
+
Runner.current
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.options
|
162
|
+
Options.current
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.generator
|
166
|
+
Generator.current
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.active?
|
170
|
+
Runner.active?
|
171
|
+
end
|
172
|
+
|
173
|
+
def self.passed?
|
174
|
+
Runner.passed?
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.run(argv)
|
178
|
+
Runner.run(argv)
|
179
|
+
end
|
180
|
+
|
181
|
+
# :startdoc:
|
182
|
+
|
183
|
+
#
|
184
|
+
# Contains various useful stuff.
|
185
|
+
#
|
186
|
+
module Util
|
187
|
+
|
188
|
+
module_function
|
189
|
+
|
190
|
+
# :stopdoc:
|
191
|
+
|
192
|
+
def runner
|
193
|
+
RDX.runner
|
194
|
+
end
|
195
|
+
|
196
|
+
def options
|
197
|
+
RDX.options
|
198
|
+
end
|
199
|
+
|
200
|
+
def generator
|
201
|
+
RDX.generator
|
202
|
+
end
|
203
|
+
|
204
|
+
# :startdoc:
|
205
|
+
|
206
|
+
#
|
207
|
+
# Returns the number of seconds elapsed during the execution of the given block.
|
208
|
+
#
|
209
|
+
def measure_time
|
210
|
+
t0 = Time.now
|
211
|
+
yield
|
212
|
+
return Time.now - t0
|
213
|
+
end
|
214
|
+
|
215
|
+
#
|
216
|
+
# Run the given block with the current directory set to a temporary one.
|
217
|
+
#
|
218
|
+
def in_tmpdir *args
|
219
|
+
tmpdir = Dir.mktmpdir *args
|
220
|
+
begin
|
221
|
+
result = nil
|
222
|
+
Dir.chdir tmpdir do
|
223
|
+
result = yield(tmpdir)
|
224
|
+
end
|
225
|
+
result
|
226
|
+
ensure
|
227
|
+
FileUtils.remove_entry tmpdir, true
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Changes <tt>$stdout</tt> and <tt>$stderr</tt> to the given +IO+ streams _new_out_ and _new_err_
|
233
|
+
# during the execution of the block.
|
234
|
+
# If +nil+ is provided that stream will be discarded;
|
235
|
+
# if +false+ is given that stream will not be changed.
|
236
|
+
#
|
237
|
+
def change_ostreams new_out, new_err
|
238
|
+
new_out = StringIO.new if new_out.nil?
|
239
|
+
new_err = StringIO.new if new_err.nil?
|
240
|
+
begin
|
241
|
+
orig_stdout,$stdout = $stdout,new_out if new_out
|
242
|
+
orig_stderr,$stderr = $stderr,new_err if new_err
|
243
|
+
yield
|
244
|
+
ensure
|
245
|
+
$stdout = orig_stdout if new_out
|
246
|
+
$stderr = orig_stderr if new_err
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
#
|
251
|
+
# Captures <tt>$stdout</tt> and <tt>$stderr</tt> produced by the block into strings and returns them.
|
252
|
+
# If _stdout_ or _stderr_ is +nil+ or +false+ that stream will not be captured.
|
253
|
+
#
|
254
|
+
def capture_ostreams stdout=true, stderr=true, &blk
|
255
|
+
stdout = stdout ? StringIO.new : false
|
256
|
+
stderr = stderr ? StringIO.new : false
|
257
|
+
change_ostreams(stdout,stderr,&blk)
|
258
|
+
return [stdout && stdout.string, stderr && stderr.string]
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# Changes <tt>$stdin</tt> to the given +String+ _in_str_
|
263
|
+
# during the execution of the block.
|
264
|
+
#
|
265
|
+
def change_stdin in_str
|
266
|
+
stdin = StringIO.new(in_str.to_str)
|
267
|
+
begin
|
268
|
+
orig_stdin,$stdin = $stdin,stdin
|
269
|
+
yield
|
270
|
+
ensure
|
271
|
+
$stdin = orig_stdin
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def restore_instance_method klass, meth # :nodoc:
|
276
|
+
name = meth.name
|
277
|
+
owner = meth.owner
|
278
|
+
unless owner == klass
|
279
|
+
klass.__send__ :remove_method, name
|
280
|
+
else
|
281
|
+
klass.__send__ :define_method, name, meth
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def restore_singleton_method receiver, orig_singleton_method # :nodoc:
|
286
|
+
receiver_singleton_class = class<<receiver; self; end
|
287
|
+
restore_instance_method receiver_singleton_class, orig_singleton_method.unbind
|
288
|
+
end
|
289
|
+
|
290
|
+
def patch_singleton_method receiver, name, new_method # :nodoc:
|
291
|
+
name = name.to_sym
|
292
|
+
new_method = new_method.to_proc
|
293
|
+
orig_method = receiver.method(name)
|
294
|
+
receiver.define_singleton_method name do |*args,&blk|
|
295
|
+
new_method.call(orig_method,*args,&blk)
|
296
|
+
end
|
297
|
+
begin
|
298
|
+
yield
|
299
|
+
ensure
|
300
|
+
restore_singleton_method receiver, orig_method
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
305
|
+
|
306
|
+
gem 'minitest'
|
307
|
+
gem 'rdoc', '= 4.0.0'
|
308
|
+
|
309
|
+
require 'rdx/runner'
|
310
|
+
|
311
|
+
autoload :Options, 'rdx/options'
|
312
|
+
autoload :Store, 'rdx/store'
|
313
|
+
autoload :Generator, 'rdx/generator'
|
314
|
+
autoload :Task, 'rdx/task'
|
315
|
+
|
316
|
+
autoload :Convention, 'rdx/convention'
|
317
|
+
autoload :Directive, 'rdx/directive'
|
318
|
+
autoload :Specification, 'rdx/specification'
|
319
|
+
|
320
|
+
autoload :Assertions, 'rdx/assertions'
|
321
|
+
autoload :Binding, 'rdx/binding'
|
322
|
+
autoload :SourceFile, 'rdx/source_file'
|
323
|
+
autoload :Comment, 'rdx/comment'
|
324
|
+
autoload :PlainText, 'rdx/plain_text'
|
325
|
+
autoload :Example, 'rdx/example'
|
326
|
+
autoload :Statement, 'rdx/statement'
|
327
|
+
|
328
|
+
autoload :Text, 'rdx/text'
|
329
|
+
autoload :RubyLex, 'rdx/ruby_lex'
|
330
|
+
|
331
|
+
end
|
@@ -0,0 +1,484 @@
|
|
1
|
+
|
2
|
+
module RDX
|
3
|
+
|
4
|
+
#
|
5
|
+
# RDX uses the assertions of the standard testing framework of ruby, +Minitest+.
|
6
|
+
# This module adapt its functionality to RDX's needs.
|
7
|
+
# The most important new features are:
|
8
|
+
# * #assert_equal_numeric: more powerful than +assert_equal+ if +Numeric+ are involved;
|
9
|
+
# * #assert_string_match: perfect when comparing an actual +String+ with an expected +String+ or +Regexp+;
|
10
|
+
# * #assert_msg_raised: checks the exception message in alternative of or along to the exception class;
|
11
|
+
# * #assert_same_type: expected and actual should be "similar";
|
12
|
+
# * #assert_*_but family: it's possible to do some normalization before the assertion.
|
13
|
+
#
|
14
|
+
#-- rdx
|
15
|
+
# :rdx: setup
|
16
|
+
# include RDX::Assertions
|
17
|
+
#
|
18
|
+
module Assertions
|
19
|
+
|
20
|
+
begin
|
21
|
+
gem 'minitest', '>= 5.0'
|
22
|
+
require 'minitest/assertions'
|
23
|
+
rescue Gem::LoadError
|
24
|
+
gem 'minitest'
|
25
|
+
require 'minitest/unit'
|
26
|
+
::Minitest = ::MiniTest unless Object.const_defined?(:Minitest) # For minitest < 2.12
|
27
|
+
end
|
28
|
+
|
29
|
+
include Minitest::Assertions
|
30
|
+
|
31
|
+
extend self
|
32
|
+
|
33
|
+
PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, SystemExit] # :nodoc:
|
34
|
+
|
35
|
+
# Doesn't count assertions (done in CodeObject::Runnable#trace_assertion).
|
36
|
+
def assert test, msg = nil
|
37
|
+
return true if test
|
38
|
+
msg ||= "Failed assertion, no message given."
|
39
|
+
msg = msg.call if Proc === msg
|
40
|
+
raise Assertion, msg
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# Same as +assert_equal+, but uses #normalize_values (passing _normalize_.+to_proc+) before the comparison.
|
45
|
+
#
|
46
|
+
# act = "a string"
|
47
|
+
# exp = "A string"
|
48
|
+
# assert_equal exp, act # fails!
|
49
|
+
# assert_equal_but :capitalize, exp, act # passes
|
50
|
+
#
|
51
|
+
def assert_equal_but normalize, exp, act, msg=nil
|
52
|
+
exp,act = normalize_values(exp,act,&normalize)
|
53
|
+
msg = message(msg,''){ diff exp, act }
|
54
|
+
assert exp==act, msg
|
55
|
+
end
|
56
|
+
|
57
|
+
# Overrided to implicitly call #normalize_values.
|
58
|
+
def assert_equal *args, &blk
|
59
|
+
assert_equal_but nil, *args, &blk
|
60
|
+
end
|
61
|
+
|
62
|
+
# This should be used in libraries, because +assert_equal+ can get patched.
|
63
|
+
alias rdx_assert_equal assert_equal # :nodoc:
|
64
|
+
private :rdx_assert_equal
|
65
|
+
|
66
|
+
#
|
67
|
+
# Fails unless _comparison_ <tt>===</tt> _target_.
|
68
|
+
#
|
69
|
+
# assert_relation 'a string', String # passes
|
70
|
+
# assert_relation 'a string', /str/ # passes
|
71
|
+
# assert_relation 3, 1..4 # passes
|
72
|
+
#
|
73
|
+
def assert_relation target, comparison, msg=nil
|
74
|
+
msg = message msg do
|
75
|
+
"Expected #{mu_pp(comparison)} === #{mu_pp(target)} to be true"
|
76
|
+
end
|
77
|
+
assert comparison===target, msg
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Fails unless _exp_ and _act_ aren't instances of the same class.
|
82
|
+
#
|
83
|
+
# assert_same_class "A string", "Another string" # passes
|
84
|
+
# assert_same_class 1, 2**64 # fails! # Fixnum and Bignum
|
85
|
+
# assert_same_class Complex(1,1), Complex(1.0,1) # passes
|
86
|
+
# assert_same_class [1], ['A'] # passes
|
87
|
+
#
|
88
|
+
def assert_same_class exp, act, msg=nil
|
89
|
+
msg = message msg do
|
90
|
+
"Expected #{mu_pp(exp)} and #{mu_pp(act)} to be instances of the same class, not #{exp.class} and #{act.class}"
|
91
|
+
end
|
92
|
+
assert exp.class==act.class, msg
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
# Resembles #assert_same_class but with a few differences:
|
97
|
+
# * It's more permissive with similar objects:
|
98
|
+
# assert_same_type Object.new, nil # passes # nil is the wildcard, because it's quite common
|
99
|
+
# assert_same_type 1, 2**64 # passes # Fixnum and Bignum are Integer
|
100
|
+
# assert_same_type true, false # passes # both are booleans
|
101
|
+
# * It's more strict with +Enumerable+ objects, where +assert_same_type+
|
102
|
+
# is called again with some of their elements:
|
103
|
+
# assert_same_type [1], ['A'] # fails! # the first element for non-empty arrays
|
104
|
+
# assert_same_type( {:a=>1}, {:b=>:c} ) # fails! # the first pair for non-empty hashes
|
105
|
+
# assert_same_type 1..2, 1..2.0 # fails! # begin and end for ranges
|
106
|
+
# assert_same_type [1].each, [:a].each # fails! # the first element for other enumerables
|
107
|
+
#
|
108
|
+
def assert_same_type exp, act, msg=nil
|
109
|
+
return pass if nil==exp || nil==act
|
110
|
+
if ([true,false] & [exp,act]).size==2
|
111
|
+
return pass
|
112
|
+
end
|
113
|
+
exp_class = exp.class
|
114
|
+
act_class = act.class
|
115
|
+
if ([Fixnum,Bignum] & [exp_class,act_class]).size==2
|
116
|
+
return pass
|
117
|
+
end
|
118
|
+
assert_same_class exp, act, msg
|
119
|
+
return unless Enumerable > exp_class
|
120
|
+
if Array==exp_class && !exp.empty? && !act.empty?
|
121
|
+
assert_same_type exp.first, act.first, message(msg,''){ 'First element of Array' }
|
122
|
+
elsif Hash==exp_class && !exp.empty? && !act.empty?
|
123
|
+
assert_same_type exp.first[0], act.first[0], message(msg,''){ 'First key of Hash' }
|
124
|
+
assert_same_type exp.first[1], act.first[1], message(msg,''){ 'First value of Hash' }
|
125
|
+
elsif Range==exp_class
|
126
|
+
assert_same_type exp.begin, act.begin, message(msg,''){ 'Begin of Range' }
|
127
|
+
assert_same_type exp.end, act.end, message(msg,''){ 'End of Range' }
|
128
|
+
else
|
129
|
+
assert_same_type exp.first, act.first, message(msg,''){ 'First element of Enumerable' }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# More suitable for +Numeric+ in contrast to +assert_equal+ because:
|
135
|
+
# * type checking is enforced according to #assert_same_type
|
136
|
+
# assert_equal 1, 1.0 # passes
|
137
|
+
# assert_equal_numeric 1, 1.0 # fails!
|
138
|
+
# assert_equal Complex(1,1), Complex(1,1.0) # passes
|
139
|
+
# assert_equal_numeric Complex(1,1), Complex(1,1.0) # fails!
|
140
|
+
# * accuracy is reduced in float comparison: #assert_in_epsilon is used
|
141
|
+
# assert_equal 1.0, 1.0+1e-10 # fails!
|
142
|
+
# assert_equal_numeric 1.0, 1.0+1e-10 # passes
|
143
|
+
# assert_equal_numeric 1.0, 1.0+1e-10, 0 # fails!
|
144
|
+
#
|
145
|
+
def assert_equal_numeric exp, act, epsilon=1e-6, msg=nil
|
146
|
+
assert_same_type exp, act, msg
|
147
|
+
case exp
|
148
|
+
when Float
|
149
|
+
assert_in_epsilon exp, act, epsilon, msg
|
150
|
+
when Complex
|
151
|
+
assert_same_type exp.real, act.real, message(msg,''){ 'Real part of Complex' }
|
152
|
+
assert_same_type exp.imag, act.imag, message(msg,''){ 'Imag part of Complex' }
|
153
|
+
assert_equal_numeric exp.real, act.real, epsilon, msg
|
154
|
+
assert_equal_numeric exp.imag, act.imag, epsilon, msg
|
155
|
+
else
|
156
|
+
rdx_assert_equal exp, act, msg
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Same as +assert_match+, but uses #normalize_values (passing _normalize_.+to_proc+)
|
162
|
+
# before the comparison.
|
163
|
+
#
|
164
|
+
# normalize = lambda do |arg|
|
165
|
+
# arg.respond_to?(:to_str) ? arg.to_str.strip : arg
|
166
|
+
# end
|
167
|
+
# re = /\A\w+\z/
|
168
|
+
# str = " a_word "
|
169
|
+
# assert_match re, str # fails!
|
170
|
+
# assert_match_but normalize, re, str # passes
|
171
|
+
#
|
172
|
+
def assert_match_but normalize, matcher, obj, msg=nil
|
173
|
+
matcher,obj = normalize_values matcher, obj, &normalize
|
174
|
+
msg = message msg do
|
175
|
+
"Expected #{mu_pp matcher} to match #{mu_pp obj}"
|
176
|
+
end
|
177
|
+
assert matcher =~ obj, msg
|
178
|
+
end
|
179
|
+
|
180
|
+
# Overrided to implicitly call #normalize_values.
|
181
|
+
def assert_match *args, &blk
|
182
|
+
assert_match_but nil, *args, &blk
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Same as #assert_string_match, but uses #normalize_values (passing _normalize_.+to_proc+)
|
187
|
+
# before the comparison.
|
188
|
+
#
|
189
|
+
# act = "a string"
|
190
|
+
# exp = /str$/
|
191
|
+
# normalize = lambda do |arg|
|
192
|
+
# arg.is_a?(String) ? arg.chomp('ing') : arg
|
193
|
+
# end
|
194
|
+
# assert_string_match exp, act # fails!
|
195
|
+
# assert_string_match_but normalize, exp, act # passes
|
196
|
+
#
|
197
|
+
def assert_string_match_but normalize, matcher, str, msg=nil
|
198
|
+
if matcher.respond_to?(:to_str)
|
199
|
+
assert_equal_but normalize, matcher.to_str, str.to_str, msg
|
200
|
+
else
|
201
|
+
assert_match_but normalize, matcher, str.to_str, msg
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
#
|
206
|
+
# Usable when _act_ is a +String+.
|
207
|
+
# If _exp_ is a +Regexp+ uses #assert_match, otherwise (including a +String+) uses #assert_equal.
|
208
|
+
#
|
209
|
+
# assert_string_match 'string', 'string' # passes
|
210
|
+
# assert_string_match 'ing', 'string' # fails!
|
211
|
+
# assert_string_match /ing/, 'string' # passes
|
212
|
+
# assert_string_match 'string', /rgxp/ # raises!
|
213
|
+
#
|
214
|
+
def assert_string_match exp, act, msg=nil
|
215
|
+
assert_string_match_but nil, exp, act, msg
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# Same as +assert_output+, but uses #normalize_values (passing _normalize_.+to_proc+)
|
220
|
+
# before the comparison.
|
221
|
+
#
|
222
|
+
# assert_output 'Hey!' do puts "Hey!" end # fails! # Does the trailing newline really matter?
|
223
|
+
# assert_output_but :chomp, 'Hey!' do puts "Hey!" end # passes
|
224
|
+
# strip_and_capitalize = lambda{ |str| str.strip.capitalize }
|
225
|
+
# assert_output_but strip_and_capitalize, "A word" do puts " a word " end # passes
|
226
|
+
#
|
227
|
+
def assert_output_but normalize, exp_out=nil, exp_err=nil, out_msg='In stdout', err_msg='In stderr', &blk
|
228
|
+
act_out,act_err = capture_io(exp_out,exp_err,&blk)
|
229
|
+
assert_string_match_but normalize, exp_out, act_out, out_msg if exp_out
|
230
|
+
assert_string_match_but normalize, exp_err, act_err, err_msg if exp_err
|
231
|
+
end
|
232
|
+
|
233
|
+
# Overrided to implicitly call #normalize_values.
|
234
|
+
def assert_output *args, &blk
|
235
|
+
assert_output_but nil, *args, &blk
|
236
|
+
end
|
237
|
+
|
238
|
+
#
|
239
|
+
# Same as +assert_silent+, but uses #normalize_values (passing _normalize_.+to_proc+)
|
240
|
+
# before the comparison.
|
241
|
+
#
|
242
|
+
# assert_silent do print " " end # fails!
|
243
|
+
# assert_silent_but :strip do print " " end # passes
|
244
|
+
#
|
245
|
+
def assert_silent_but normalize, out_msg=nil, err_msg=nil, &blk
|
246
|
+
assert_output_but normalize, '', '', out_msg, err_msg, &blk
|
247
|
+
end
|
248
|
+
|
249
|
+
# Overrided to implicitly call #normalize_values.
|
250
|
+
def assert_silent *args, &blk
|
251
|
+
assert_silent_but nil, *args, &blk
|
252
|
+
end
|
253
|
+
|
254
|
+
#
|
255
|
+
# The block should raise an exception listed in _excs_, or a generic +StandardError+ if no
|
256
|
+
# exceptions are given. The last argument given can be a message containing additional failure details.
|
257
|
+
# If the raised exception isn't expected two things may happen:
|
258
|
+
# * if it's a +StandardError+ a failure will be reported:
|
259
|
+
# assert_raises(ArgumentError){ raise NameError } # fails!
|
260
|
+
# * otherwise it's re-raised, eventually reporting an error:
|
261
|
+
# assert_raises(NameError){ raise LoadError } # raises LoadError
|
262
|
+
#
|
263
|
+
# Returns the exception matched so you can check the message, attributes, etc..
|
264
|
+
#
|
265
|
+
def assert_raises *excs
|
266
|
+
msg = "#{excs.pop.to_str}.\n" if excs.last.respond_to?(:to_str)
|
267
|
+
excs << StandardError if excs.empty?
|
268
|
+
begin
|
269
|
+
yield
|
270
|
+
rescue Exception => raised
|
271
|
+
if excs.any?{ |ex| raised.kind_of? ex }
|
272
|
+
pass
|
273
|
+
return raised
|
274
|
+
elsif raised.is_a?(StandardError)
|
275
|
+
flunk exception_details(raised, "#{msg}#{mu_pp(excs)} exception expected, not")
|
276
|
+
else # Unhandled generic Exception
|
277
|
+
raise raised
|
278
|
+
end
|
279
|
+
else
|
280
|
+
flunk "#{msg}#{mu_pp(excs)} expected but nothing was raised."
|
281
|
+
end
|
282
|
+
end
|
283
|
+
alias assert_raise assert_raises
|
284
|
+
|
285
|
+
#
|
286
|
+
# The block should not raise an exception listed in _excs_, or a generic +StandardError+ if no
|
287
|
+
# exceptions are given. The last argument given can be a message containing additional failure details.
|
288
|
+
# If the raised exception isn't expected two things may happen:
|
289
|
+
# * if it's a +StandardError+ a failure will be reported:
|
290
|
+
# assert_nothing_raised(ArgumentError){ raise NameError } # fails!
|
291
|
+
# * otherwise it's re-raised, eventually reporting an error:
|
292
|
+
# assert_nothing_raised(NameError){ raise LoadError } # raises LoadError
|
293
|
+
#
|
294
|
+
def assert_nothing_raised *excs
|
295
|
+
msg = "#{excs.pop.to_str}.\n" if excs.last.respond_to?(:to_str)
|
296
|
+
excs << StandardError if excs.empty?
|
297
|
+
begin
|
298
|
+
yield
|
299
|
+
rescue Exception => raised
|
300
|
+
specified = excs.any?{ |ex| raised.kind_of? ex }
|
301
|
+
if specified || raised.is_a?(StandardError)
|
302
|
+
msg = message msg do
|
303
|
+
"Unexpected exception raised:\n#{exception_details raised}"
|
304
|
+
end
|
305
|
+
flunk msg
|
306
|
+
else # Unhandled generic Exception
|
307
|
+
raise raised
|
308
|
+
end
|
309
|
+
else
|
310
|
+
pass
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
#
|
315
|
+
# Same as +assert_msg_raised+, but uses #normalize_values (passing _normalize_.+to_proc+)
|
316
|
+
# before the comparison.
|
317
|
+
#
|
318
|
+
# exp_msg = "Can't modify frozen String"
|
319
|
+
# assert_msg_raised( exp_msg){ "".freeze << 'a' } # fails!
|
320
|
+
# assert_msg_raised_but(:capitalize,exp_msg){ "".freeze << 'a' } # passes
|
321
|
+
#
|
322
|
+
def assert_msg_raised_but normalize, exp_msg, *excs, &blk
|
323
|
+
whole = [false,true].include?(excs.first) ? excs.shift : true
|
324
|
+
raised = assert_raises(*excs,&blk)
|
325
|
+
if exp_msg
|
326
|
+
act_msg = raised.message
|
327
|
+
if exp_msg.respond_to?(:to_str)
|
328
|
+
exp_msg,act_msg = normalize_values(exp_msg.to_str,act_msg,&normalize)
|
329
|
+
if whole
|
330
|
+
assert_equal exp_msg, act_msg, 'Wrong exception message'
|
331
|
+
else
|
332
|
+
# Better than the message of assert_match implicitly called
|
333
|
+
msg = message 'Wrong exception message (expected should be included in actual)' do
|
334
|
+
diff exp_msg, act_msg
|
335
|
+
end
|
336
|
+
assert act_msg=~/(?<!\w)#{Regexp.escape(exp_msg)}(?!\w)/, msg if exp_msg=~/\S/
|
337
|
+
end
|
338
|
+
else
|
339
|
+
assert_match_but normalize, exp_msg, act_msg, 'Wrong message for exception'
|
340
|
+
end
|
341
|
+
end
|
342
|
+
return raised
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
# First uses #assert_raises passing the exceptions _excs_ and the given block.
|
347
|
+
#
|
348
|
+
# If _exp_msg_ isn't +nil+ the check of the exception message is also performed:
|
349
|
+
# this can be a +String+ (which is interpreted literally) or a +Regexp+ (which should match).
|
350
|
+
#
|
351
|
+
# If after the string message is sent the parameter +false+ the message given (expected)
|
352
|
+
# doesn't have to be exact: it can be only a part of the actual one.
|
353
|
+
#
|
354
|
+
# assert_raises{ [].freeze << 1 } # passes # but we may need more information...
|
355
|
+
# assert_msg_raised("frozen Array"){ [].freeze << 1 } # fails! # the message isn't complete
|
356
|
+
# assert_msg_raised("frozen Array",false){ [].freeze << 1 }
|
357
|
+
# # passes # the message can be only a part
|
358
|
+
# re = /number of arguments \(\d for \d\)/
|
359
|
+
# assert_msg_raised(re,ArgumentError){ 12.floor(2) } # passes
|
360
|
+
#
|
361
|
+
def assert_msg_raised exp_msg, *excs, &blk
|
362
|
+
assert_msg_raised_but nil, exp_msg, *excs, &blk
|
363
|
+
end
|
364
|
+
|
365
|
+
#
|
366
|
+
# :section: Helper methods
|
367
|
+
#
|
368
|
+
|
369
|
+
##
|
370
|
+
# :method:
|
371
|
+
#
|
372
|
+
# Proxy to RDX::Util.capture_ostreams.
|
373
|
+
#
|
374
|
+
define_method :capture_io, RDX::Util.method(:capture_ostreams).to_proc
|
375
|
+
|
376
|
+
#
|
377
|
+
# Returns _exp_ and _act_ in an +Array+ normalized through the block given.
|
378
|
+
# This method can be overridded to modify the normalization.
|
379
|
+
#
|
380
|
+
def normalize_values exp, act, &normalize
|
381
|
+
if normalize
|
382
|
+
exp = normalize.call(exp)
|
383
|
+
act = normalize.call(act)
|
384
|
+
end
|
385
|
+
[exp,act]
|
386
|
+
end
|
387
|
+
|
388
|
+
#
|
389
|
+
# This differs from the original implementation in that hexadecimal values are not
|
390
|
+
# generalized in this step.
|
391
|
+
#--
|
392
|
+
# really escaped newlines are expanded (and not a simple backslash followed by an 'n')
|
393
|
+
#
|
394
|
+
def mu_pp_for_diff obj
|
395
|
+
mu_pp(obj).
|
396
|
+
gsub(/\\(?:n()|.)/){ |seq| $1 ? "\n" : seq }
|
397
|
+
end
|
398
|
+
|
399
|
+
# Returns details for exception _exc_.
|
400
|
+
def exception_details exc, msg=nil
|
401
|
+
[
|
402
|
+
msg.to_s,
|
403
|
+
"Class: #{exc.class}",
|
404
|
+
"Message: #{exc.message}",
|
405
|
+
"---Backtrace---",
|
406
|
+
*exc.backtrace,
|
407
|
+
"---------------",
|
408
|
+
""
|
409
|
+
].join "\n"
|
410
|
+
end
|
411
|
+
|
412
|
+
class << self
|
413
|
+
# The system +diff+ command used in +assert_equal+.
|
414
|
+
attr_accessor :diff
|
415
|
+
end
|
416
|
+
def self.use_default_diff
|
417
|
+
self.diff = if system("diff", __FILE__, __FILE__)
|
418
|
+
'diff -u'
|
419
|
+
elsif system("gdiff", __FILE__, __FILE__)
|
420
|
+
'gdiff -u'
|
421
|
+
else
|
422
|
+
nil
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
# This no more relies on Minitest
|
427
|
+
def diff exp, act
|
428
|
+
expect = mu_pp_for_diff exp
|
429
|
+
butwas = mu_pp_for_diff act
|
430
|
+
result = nil
|
431
|
+
|
432
|
+
need_to_diff =
|
433
|
+
Assertions.diff &&
|
434
|
+
(expect.include?("\n") ||
|
435
|
+
butwas.include?("\n") ||
|
436
|
+
expect.size > 30 ||
|
437
|
+
butwas.size > 30 ||
|
438
|
+
expect == butwas)
|
439
|
+
|
440
|
+
return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
|
441
|
+
need_to_diff
|
442
|
+
|
443
|
+
if exp.is_a?(String) && act.is_a?(String)
|
444
|
+
# leave the quotes on their own lines, so that diff compares the real content of the string
|
445
|
+
expect.insert(1,"\n").insert(-2,"\n")
|
446
|
+
butwas.insert(1,"\n").insert(-2,"\n")
|
447
|
+
end
|
448
|
+
|
449
|
+
Tempfile.open("expect") do |a|
|
450
|
+
a.puts expect
|
451
|
+
a.flush
|
452
|
+
|
453
|
+
Tempfile.open("butwas") do |b|
|
454
|
+
b.puts butwas
|
455
|
+
b.flush
|
456
|
+
|
457
|
+
result = `#{Assertions.diff} "#{a.path}" "#{b.path}"`
|
458
|
+
result.sub!(/^\-\-\- .+/, "--- expected")
|
459
|
+
result.sub!(/^\+\+\+ .+/, "+++ actual")
|
460
|
+
|
461
|
+
if result.empty? then
|
462
|
+
klass = exp.class
|
463
|
+
result = [
|
464
|
+
"No visible difference in the #{klass}#inspect output.\n",
|
465
|
+
"You should look at the implementation of #== on ",
|
466
|
+
"#{klass} or its members.\n",
|
467
|
+
expect,
|
468
|
+
].join
|
469
|
+
end
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
result
|
474
|
+
end
|
475
|
+
|
476
|
+
# No need, since parallelization does not occurr...
|
477
|
+
def synchronize
|
478
|
+
yield
|
479
|
+
end
|
480
|
+
alias _synchronize synchronize
|
481
|
+
|
482
|
+
end
|
483
|
+
|
484
|
+
end
|