assert_value 1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *~
2
+ *.gem
3
+ pkg
4
+ .kdev4
5
+ Gemfile.lock
6
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'rspec'
7
+ end
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010-2011 Pluron, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # assert_value
2
+
3
+ Checks that two values are same and "magically" replaces expected value
4
+ with the actual in case the new behavior (and new actual value) is correct.
5
+ Support two kind of arguments: string and code block.
6
+
7
+ ## String Example:
8
+
9
+ It is better to start with no expected value
10
+
11
+ assert_value "foo"
12
+
13
+ Then run tests as usual with "rake test". As a result you will see
14
+ diff between expected and actual values:
15
+
16
+ Failure:
17
+ @@ -1,0, +1,1 @@
18
+ +foo
19
+ Accept the new value: yes to all, no to all, yes, no? [Y/N/y/n] (y):
20
+
21
+ If you accept the new value your test will be automatically modified to
22
+
23
+ assert_value "foo", <<-END
24
+ foo
25
+ END
26
+
27
+ ## Block Example:
28
+
29
+ assert_value supports code block as argument. If executed block raises exception then
30
+ exception message is returned as actual value:
31
+
32
+ assert_value do
33
+ nil+1
34
+ end
35
+
36
+ Run tests
37
+
38
+ Failure:
39
+ @@ -1,0, +1,1 @@
40
+ +Exception NoMethodError: undefined method `+' for nil:NilClass
41
+ Accept the new value: yes to all, no to all, yes, no? [Y/N/y/n] (y):
42
+
43
+ After the new value is accepted you get
44
+
45
+ assert_value(<<-END) do
46
+ Exception NoMethodError: undefined method `+' for nil:NilClass
47
+ END
48
+ nil + 1
49
+ end
50
+
51
+ ## Options:
52
+
53
+ --no-interactive skips all questions and just reports failures
54
+ --autoaccept prints diffs and automatically accepts all new actual values
55
+ --no-canonicalize turns off expected and actual value canonicalization (see below for details)
56
+
57
+ Additional options can be passed during both single test file run and rake test run:
58
+
59
+ In Ruby 1.8:
60
+
61
+ ruby test/unit/foo_test.rb -- --autoaccept
62
+ rake test TESTOPTS="-- --autoaccept"
63
+
64
+ In Ruby 1.9:
65
+
66
+ ruby test/unit/foo_test.rb --autoaccept
67
+ rake test TESTOPTS="--autoaccept"
68
+
69
+ ## Canonicalization:
70
+
71
+ Before comparing expected and actual strings, assert_value canonicalizes both using these rules:
72
+
73
+ - indentation is ignored (except for indentation relative to the first line of the expected/actual string)
74
+ - ruby-style comments after "#" are ignored
75
+ - empty lines are ignored
76
+ - trailing whitespaces are ignored
77
+
78
+ You can turn canonicalization off with --no-canonicalize option. This is useful
79
+ when you need to regenerate expected test strings.
80
+ To regenerate the whole test suite, run:
81
+
82
+ In Ruby 1.8:
83
+
84
+ rake test TESTOPTS="-- --no-canonicalize --autoaccept"
85
+
86
+ In Ruby 1.9:
87
+
88
+ rake test TESTOPTS="--no-canonicalize --autoaccept"
89
+
90
+
91
+ ## Changelog
92
+
93
+ - 1.0: Rename to assert_value
94
+ - 0.7: Support Ruby 1.9's MiniTest
95
+ - 0.6: Support test execution on Mac
96
+ - 0.5: Support code blocks to assert_same
97
+ - 0.4: Added support for code blocks as argument
98
+ - 0.3: Ruby 1.9 is supported
99
+ - 0.2: Make assert_same useful as a standalone gem. Bugfixes
100
+ - 0.1: Initial release
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+ require 'bundler/setup'
5
+ require 'rspec/core/rake_task'
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ desc 'Default: run unit tests.'
10
+ task :default => :test
11
+
12
+ desc 'Test assert_value.'
13
+ Rake::TestTask.new(:test) do |t|
14
+ t.libs << 'lib'
15
+ t.pattern = 'test/**/*_test.rb'
16
+ t.verbose = true
17
+ end
18
+
19
+ desc "Run the specs."
20
+ RSpec::Core::RakeTask.new do |t|
21
+ t.pattern = "test/**/*_spec.rb"
22
+ end
23
+
24
+ desc 'Generate documentation for assert_value.'
25
+ Rake::RDocTask.new(:rdoc) do |rdoc|
26
+ rdoc.rdoc_dir = 'rdoc'
27
+ rdoc.title = 'assert_value'
28
+ rdoc.options << '--line-numbers' << '--inline-source'
29
+ rdoc.rdoc_files.include('README')
30
+ rdoc.rdoc_files.include('lib/**/*.rb')
31
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+
3
+ SPEC = Gem::Specification.new do |s|
4
+ s.name = "assert_value"
5
+ s.version = "1.0"
6
+ s.author = "Pluron, Inc."
7
+ s.email = "support@pluron.com"
8
+ s.homepage = "http://github.com/acunote/assert_value"
9
+ s.platform = Gem::Platform::RUBY
10
+ s.license = 'MIT'
11
+ s.description = "assert_value assertion"
12
+ s.summary = "Assert that checks that two values (strings, expected and actual) are same and which can magically replace expected value with the actual in case the new behavior (and new actual value) is correct"
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- test/*`.split("\n")
16
+
17
+ s.require_path = "lib"
18
+ s.has_rdoc = true
19
+ end
@@ -0,0 +1,3 @@
1
+ [Project]
2
+ Manager=KDevGenericManager
3
+ Name=assert_value
data/lib/array_diff.rb ADDED
@@ -0,0 +1,331 @@
1
+ class ArrayDiff
2
+
3
+ VERSION = 0.3
4
+
5
+ def ArrayDiff.lcs(a, b)
6
+ astart = 0
7
+ bstart = 0
8
+ afinish = a.length-1
9
+ bfinish = b.length-1
10
+ mvector = []
11
+
12
+ # First we prune off any common elements at the beginning
13
+ while (astart <= afinish && bstart <= afinish && a[astart] == b[bstart])
14
+ mvector[astart] = bstart
15
+ astart += 1
16
+ bstart += 1
17
+ end
18
+
19
+ # now the end
20
+ while (astart <= afinish && bstart <= bfinish && a[afinish] == b[bfinish])
21
+ mvector[afinish] = bfinish
22
+ afinish -= 1
23
+ bfinish -= 1
24
+ end
25
+
26
+ bmatches = b.reverse_hash(bstart..bfinish)
27
+ thresh = []
28
+ links = []
29
+
30
+ (astart..afinish).each { |aindex|
31
+ aelem = a[aindex]
32
+ next unless bmatches.has_key? aelem
33
+ k = nil
34
+ bmatches[aelem].reverse.each { |bindex|
35
+ if k && (thresh[k] > bindex) && (thresh[k-1] < bindex)
36
+ thresh[k] = bindex
37
+ else
38
+ k = thresh.replacenextlarger(bindex, k)
39
+ end
40
+ links[k] = [ (k==0) ? nil : links[k-1], aindex, bindex ] if k
41
+ }
42
+ }
43
+
44
+ if !thresh.empty?
45
+ link = links[thresh.length-1]
46
+ while link
47
+ mvector[link[1]] = link[2]
48
+ link = link[0]
49
+ end
50
+ end
51
+
52
+ return mvector
53
+ end
54
+
55
+ def makediff(a, b)
56
+ mvector = ArrayDiff.lcs(a, b)
57
+ ai = bi = 0
58
+ while ai < mvector.length
59
+ bline = mvector[ai]
60
+ if bline
61
+ while bi < bline
62
+ discardb(bi, b[bi])
63
+ bi += 1
64
+ end
65
+ match(ai, bi)
66
+ bi += 1
67
+ else
68
+ discarda(ai, a[ai])
69
+ end
70
+ ai += 1
71
+ end
72
+ while ai < a.length
73
+ discarda(ai, a[ai])
74
+ ai += 1
75
+ end
76
+ while bi < b.length
77
+ discardb(bi, b[bi])
78
+ bi += 1
79
+ end
80
+ match(ai, bi)
81
+ 1
82
+ end
83
+
84
+ def compactdiffs
85
+ diffs = []
86
+ @diffs.each { |df|
87
+ i = 0
88
+ curdiff = []
89
+ while i < df.length
90
+ whot = df[i][0]
91
+ s = @isstring ? df[i][2].chr : [df[i][2]]
92
+ p = df[i][1]
93
+ last = df[i][1]
94
+ i += 1
95
+ while df[i] && df[i][0] == whot && df[i][1] == last+1
96
+ s << df[i][2]
97
+ last = df[i][1]
98
+ i += 1
99
+ end
100
+ curdiff.push [whot, p, s]
101
+ end
102
+ diffs.push curdiff
103
+ }
104
+ return diffs
105
+ end
106
+
107
+ attr_reader :diffs, :difftype
108
+
109
+ def initialize(diffs_or_a, b = nil, isstring = nil)
110
+ if b.nil?
111
+ @diffs = diffs_or_a
112
+ @isstring = isstring
113
+ else
114
+ @diffs = []
115
+ @curdiffs = []
116
+ makediff(diffs_or_a, b)
117
+ @difftype = diffs_or_a.class
118
+ end
119
+ end
120
+
121
+ def match(ai, bi)
122
+ @diffs.push @curdiffs unless @curdiffs.empty?
123
+ @curdiffs = []
124
+ end
125
+
126
+ def discarda(i, elem)
127
+ @curdiffs.push ['-', i, elem]
128
+ end
129
+
130
+ def discardb(i, elem)
131
+ @curdiffs.push ['+', i, elem]
132
+ end
133
+
134
+ def compact
135
+ return Diff.new(compactdiffs)
136
+ end
137
+
138
+ def compact!
139
+ @diffs = compactdiffs
140
+ end
141
+
142
+ def inspect
143
+ @diffs.inspect
144
+ end
145
+
146
+ def diffrange(a, b)
147
+ if (a == b)
148
+ "#{a}"
149
+ else
150
+ "#{a},#{b}"
151
+ end
152
+ end
153
+
154
+ def to_diff
155
+ offset = 0
156
+ @diffs.each { |b|
157
+ first = b[0][1]
158
+ length = b.length
159
+ action = b[0][0]
160
+ addcount = 0
161
+ remcount = 0
162
+ b.each { |l|
163
+ if l[0] == "+"
164
+ addcount += 1
165
+ elsif l[0] == "-"
166
+ remcount += 1
167
+ end
168
+ }
169
+ if addcount == 0
170
+ puts "#{diffrange(first+1, first+remcount)}d#{first+offset}"
171
+ elsif remcount == 0
172
+ puts "#{first-offset}a#{diffrange(first+1, first+addcount)}"
173
+ else
174
+ puts "#{diffrange(first+1, first+remcount)}c#{diffrange(first+offset+1, first+offset+addcount)}"
175
+ end
176
+ lastdel = (b[0][0] == "-")
177
+ b.each { |l|
178
+ if l[0] == "-"
179
+ offset -= 1
180
+ print "< "
181
+ elsif l[0] == "+"
182
+ offset += 1
183
+ if lastdel
184
+ lastdel = false
185
+ puts "---"
186
+ end
187
+ print "> "
188
+ end
189
+ print l[2]
190
+ print "\n"
191
+ }
192
+ }
193
+ end
194
+
195
+
196
+ end
197
+
198
+ module Diffable
199
+ def diff(b)
200
+ ArrayDiff.new(self, b)
201
+ end
202
+
203
+ # Create a hash that maps elements of the array to arrays of indices
204
+ # where the elements are found.
205
+
206
+ def reverse_hash(range = (0...self.length))
207
+ revmap = {}
208
+ range.each { |i|
209
+ elem = self[i]
210
+ if revmap.has_key? elem
211
+ revmap[elem].push i
212
+ else
213
+ revmap[elem] = [i]
214
+ end
215
+ }
216
+ return revmap
217
+ end
218
+
219
+ def replacenextlarger(value, high = nil)
220
+ high ||= self.length
221
+ if self.empty? || value > self[-1]
222
+ push value
223
+ return high
224
+ end
225
+ # binary search for replacement point
226
+ low = 0
227
+ while low < high
228
+ index = (high+low)/2
229
+ found = self[index]
230
+ return nil if value == found
231
+ if value > found
232
+ low = index + 1
233
+ else
234
+ high = index
235
+ end
236
+ end
237
+
238
+ self[low] = value
239
+ # $stderr << "replace #{value} : 0/#{low}/#{init_high} (#{steps} steps) (#{init_high-low} off )\n"
240
+ # $stderr.puts self.inspect
241
+ #gets
242
+ #p length - low
243
+ return low
244
+ end
245
+
246
+ def patch(diff)
247
+ newary = nil
248
+ if diff.difftype == String
249
+ newary = diff.difftype.new('')
250
+ else
251
+ newary = diff.difftype.new
252
+ end
253
+ ai = 0
254
+ bi = 0
255
+ diff.diffs.each { |d|
256
+ d.each { |mod|
257
+ case mod[0]
258
+ when '-'
259
+ while ai < mod[1]
260
+ newary << self[ai]
261
+ ai += 1
262
+ bi += 1
263
+ end
264
+ ai += 1
265
+ when '+'
266
+ while bi < mod[1]
267
+ newary << self[ai]
268
+ ai += 1
269
+ bi += 1
270
+ end
271
+ newary << mod[2]
272
+ bi += 1
273
+ else
274
+ raise "Unknown diff action"
275
+ end
276
+ }
277
+ }
278
+ while ai < self.length
279
+ newary << self[ai]
280
+ ai += 1
281
+ bi += 1
282
+ end
283
+ return newary
284
+ end
285
+ end
286
+
287
+ class Array
288
+ include Diffable
289
+ end
290
+
291
+ class String
292
+ include Diffable
293
+ end
294
+
295
+ =begin
296
+ = ArrayDiff
297
+ (({diff.rb})) - computes the differences between two arrays or
298
+ strings. Copyright (C) 2001 Lars Christensen
299
+ http://users.cybercity.dk/~dsl8950/ruby/diff.html
300
+
301
+ == Synopsis
302
+
303
+ diff = ArrayDiff.new(a, b)
304
+ b = a.patch(diff)
305
+
306
+ == Class ArrayDiff
307
+ === Class Methods
308
+ --- ArrayDiff.new(a, b)
309
+ --- a.diff(b)
310
+ Creates a Diff object which represent the differences between
311
+ ((|a|)) and ((|b|)). ((|a|)) and ((|b|)) can be either be arrays
312
+ of any objects, strings, or object of any class that include
313
+ module ((|Diffable|))
314
+
315
+ == Module Diffable
316
+ The module ((|Diffable|)) is intended to be included in any class for
317
+ which differences are to be computed. Diffable is included into String
318
+ and Array when (({diff.rb})) is (({require}))'d.
319
+
320
+ Classes including Diffable should implement (({[]})) to get element at
321
+ integer indices, (({<<})) to append elements to the object and
322
+ (({ClassName#new})) should accept 0 arguments to create a new empty
323
+ object.
324
+
325
+ === Instance Methods
326
+ --- Diffable#patch(diff)
327
+ Applies the differences from ((|diff|)) to the object ((|obj|))
328
+ and return the result. ((|obj|)) is not changed. ((|obj|)) and
329
+ can be either an array or a string, but must match the object
330
+ from which the ((|diff|)) was created.
331
+ =end
@@ -0,0 +1,414 @@
1
+ # Copyright (c) 2010-2011 Pluron, Inc.
2
+ require 'test/unit/testcase'
3
+ require 'text_diff'
4
+ require 'pathname'
5
+
6
+ $assert_value_options = []
7
+
8
+ if RUBY_VERSION >= "1.9.0"
9
+
10
+ # Test/Unit from Ruby 1.9 can't accept additional options like it did in 1.8:
11
+ # ruby test.rb -- --foo
12
+ # Now it has a strict option parser that considers all additional options as files.
13
+ # The right way to have an option now is to explicitly add it to Test/Unit parser.
14
+ module Test::Unit
15
+
16
+ module AssertValueOption
17
+ def setup_options(parser, options)
18
+ super
19
+ parser.on '--no-interactive', 'assert_value: non-interactive mode' do |flag|
20
+ $assert_value_options << "--no-interactive"
21
+ end
22
+ parser.on '--no-canonicalize', 'assert_value: turn off canonicalization' do |flag|
23
+ $assert_value_options << "--no-canonicalize"
24
+ end
25
+ parser.on '--autoaccept', 'assert_value: automatically accept new actual values' do |flag|
26
+ $assert_value_options << "--autoaccept"
27
+ end
28
+ end
29
+
30
+ def non_options(files, options)
31
+ super
32
+ end
33
+ end
34
+
35
+ class Runner < MiniTest::Unit
36
+ include Test::Unit::AssertValueOption
37
+ end
38
+
39
+ end
40
+
41
+ else
42
+
43
+ # In Ruby 1.8 additional test options are simply passed via ARGV
44
+ $assert_value_options << "--no-interactive" if ARGV.include?("--no-interactive")
45
+ $assert_value_options << "--no-canonicalize" if ARGV.include?("--no-canonicalize")
46
+ $assert_value_options << "--autoaccept" if ARGV.include?("--autoaccept")
47
+
48
+ end
49
+
50
+
51
+ #Use this to raise internal error with a given message
52
+ #You can define your own method for your application
53
+ unless defined? internal_error
54
+ def internal_error(message = 'internal error')
55
+ raise message
56
+ end
57
+ end
58
+
59
+
60
+ module AssertValueAssertion
61
+
62
+ def file_offsets
63
+ @file_offsets ||= Hash.new { |hash, key| hash[key] = {} }
64
+ end
65
+
66
+ # assert_value: assert which checks that two strings (expected and actual) are same
67
+ # and which can "magically" replace expected value with the actual in case
68
+ # the new behavior (and new actual value) is correct
69
+ #
70
+ # == Usage ==
71
+ #
72
+ # Write this in the test source:
73
+ # assert_value something, <<-END
74
+ # foo
75
+ # bar
76
+ # zee
77
+ # END
78
+ #
79
+ # You can also use assert_value for blocks. Then you can assert block result or raised exception
80
+ # assert_value(<<-END) do
81
+ # Exception NoMethodError: undefined method `+' for nil:NilClass
82
+ # END
83
+ # # Code block starts here
84
+ # c = nil + 1
85
+ # end
86
+ #
87
+ # Then run tests as usual:
88
+ # rake test:units
89
+ # ruby test/unit/foo_test.rb
90
+ # ...
91
+ #
92
+ # When assert_value fails, you'll be able to:
93
+ # - review diff
94
+ # - (optionally) accept new actual value (this modifies the test source file)
95
+ #
96
+ # Additional options for test runs:
97
+ # --no-interactive skips all questions and just reports failures
98
+ # --autoaccept prints diffs and automatically accepts all new actual values
99
+ # --no-canonicalize turns off expected and actual value canonicalization (see below for details)
100
+ #
101
+ # Additional options can be passed during both single test file run and rake test run:
102
+ # In Ruby 1.8:
103
+ # ruby test/unit/foo_test.rb -- --autoaccept
104
+ # ruby test/unit/foo_test.rb -- --no-interactive
105
+ #
106
+ # rake test TESTOPTS="-- --autoaccept"
107
+ # rake test:units TESTOPTS="-- --no-canonicalize --autoaccept"
108
+ #
109
+ # In Ruby 1.9:
110
+ # ruby test/unit/foo_test.rb --autoaccept
111
+ # ruby test/unit/foo_test.rb --no-interactive
112
+ #
113
+ # rake test TESTOPTS="--autoaccept"
114
+ # rake test:units TESTOPTS="--no-canonicalize --autoaccept"
115
+ #
116
+ #
117
+ # == Canonicalization ==
118
+ #
119
+ # Before comparing expected and actual strings, assert_value canonicalizes both using these rules:
120
+ # - indentation is ignored (except for indentation
121
+ # relative to the first line of the expected/actual string)
122
+ # - ruby-style comments after "#" are ignored:
123
+ # both whole-line and end-of-line comments are supported
124
+ # - empty lines are ignored
125
+ # - trailing whitespaces are ignored
126
+ #
127
+ # You can turn canonicalization off with --no-canonicalize option. This is useful
128
+ # when you need to regenerate expected test strings.
129
+ # To regenerate the whole test suite, run:
130
+ # In Ruby 1.8:
131
+ # rake test TESTOPTS="-- --no-canonicalize --autoaccept"
132
+ # In Ruby 1.9:
133
+ # rake test TESTOPTS="--no-canonicalize --autoaccept"
134
+ #
135
+ # Example of assert_value with comments:
136
+ # assert_value something, <<-END
137
+ # # some tree
138
+ # foo 1
139
+ # foo 1.1
140
+ # foo 1.2 # some node
141
+ # bar 2
142
+ # bar 2.1
143
+ # END
144
+ #
145
+ #
146
+ #
147
+ # == Umportant Usage Rules ==
148
+ #
149
+ # Restrictions:
150
+ # - only END and EOS are supported as end of string sequence
151
+ # - it's a requirement that you have <<-END at the same line as assert_value
152
+ # - assert_value can't be within a block
153
+ #
154
+ # Storing expected output in files:
155
+ # - assert_value something, :log => <path_to_file>
156
+ # - path to file is relative to:
157
+ # - RAILS_ROOT (if that is defined)
158
+ # - current dir (if no RAILS_ROOT is defined)
159
+ # - file doesn't have to exist, it will be created if necessary
160
+ #
161
+ # Misc:
162
+ # - it's ok to have several assert_value's in the same test method, assert_value.
163
+ # correctly updates all assert_value's in the test file
164
+ # - it's ok to omit expected string, like this:
165
+ # assert_value something
166
+ # in fact, this is the preferred way to create assert_value tests - you write empty
167
+ # assert_value, run tests and they will fill expected values for you automatically
168
+ def assert_value(*args)
169
+ if block_given?
170
+ mode = :block
171
+ expected = args[0]
172
+ actual = ""
173
+ begin
174
+ actual = yield.to_s
175
+ rescue Exception => e
176
+ actual = "Exception #{e.class}: #{e.message}"
177
+ end
178
+ else
179
+ mode = :scalar
180
+ expected = args[1]
181
+ actual = args[0]
182
+ end
183
+
184
+ if expected.nil?
185
+ expected = ""
186
+ change = :create_expected_string
187
+ elsif expected.class == String
188
+ change = :update_expected_string
189
+ elsif expected.class == Hash
190
+ raise ":log key is missing" unless expected.has_key? :log
191
+ log_file = expected[:log]
192
+ if defined? RAILS_ROOT
193
+ log_file = File.expand_path(log_file, RAILS_ROOT)
194
+ else
195
+ log_file = File.expand_path(log_file, Dir.pwd)
196
+ end
197
+ expected = File.exists?(log_file) ? File.read(log_file) : ""
198
+ change = :update_file_with_expected_string
199
+ else
200
+ internal_error("Invalid expected class #{excepted.class}")
201
+ end
202
+
203
+ # interactive mode is turned on by default, except when
204
+ # - --no-interactive is given
205
+ # - CIRCLECI is set (CircleCI captures test output, but doesn't interact with user)
206
+ # - STDIN is not a terminal device (i.e. we can't ask any questions)
207
+ interactive = !$assert_value_options.include?("--no-interactive") && !ENV["CIRCLECI"] && STDIN.tty?
208
+ canonicalize = !$assert_value_options.include?("--no-canonicalize")
209
+ autoaccept = $assert_value_options.include?("--autoaccept")
210
+
211
+ is_same_canonicalized, is_same, diff_canonicalized, diff = compare_for_assert_value(expected, actual)
212
+
213
+ if (canonicalize and !is_same_canonicalized) or (!canonicalize and !is_same)
214
+ diff_to_report = canonicalize ? diff_canonicalized : diff
215
+ if interactive
216
+ # print method name and short backtrace
217
+ soft_fail(diff_to_report)
218
+
219
+ if autoaccept
220
+ accept = true
221
+ else
222
+ print "Accept the new value: yes to all, no to all, yes, no? [Y/N/y/n] (y): "
223
+ STDOUT.flush
224
+ response = STDIN.gets.strip
225
+ accept = ["", "y", "Y"].include? response
226
+ $assert_value_options << "--autoaccept" if response == "Y"
227
+ $assert_value_options << "--no-interactive" if response == "N"
228
+ end
229
+
230
+ if accept
231
+ if [:create_expected_string, :update_expected_string].include? change
232
+ accept_string(actual, change, mode)
233
+ elsif change == :update_file_with_expected_string
234
+ accept_file(actual, log_file)
235
+ else
236
+ internal_error("Invalid change #{change}")
237
+ end
238
+ end
239
+ end
240
+ if accept
241
+ # when change is accepted, we should not report it as a failure because
242
+ # we want the test method to continue executing (in case there're more
243
+ # assert_value's in the method)
244
+ succeed
245
+ else
246
+ fail(diff)
247
+ end
248
+ else
249
+ succeed
250
+ end
251
+ end
252
+
253
+ private
254
+
255
+ def succeed
256
+ if RUBY_VERSION < "1.9.0"
257
+ add_assertion
258
+ else
259
+ true
260
+ end
261
+ end
262
+
263
+ def soft_fail(diff)
264
+ if RUBY_VERSION < "1.9.0"
265
+ failure = Test::Unit::Failure.new(name, filter_backtrace(caller(0)), diff)
266
+ puts "\n#{failure}"
267
+ else
268
+ failure = MiniTest::Assertion.new(diff)
269
+ puts "\n#{failure}"
270
+ end
271
+ end
272
+
273
+ def fail(diff)
274
+ if RUBY_VERSION < "1.9.0"
275
+ raise Test::Unit::AssertionFailedError.new(diff)
276
+ else
277
+ raise MiniTest::Assertion.new(diff)
278
+ end
279
+ end
280
+
281
+ # actual - actual value of the scalar or result of the executed block
282
+ # change - what to do with expected value (:create_expected_string or :update_expected_string)
283
+ # mode - describes signature of assert_value call by type of main argument (:block or :scalar)
284
+ def accept_string(actual, change, mode)
285
+ file, method, line = get_caller_location(:depth => 3)
286
+
287
+ # read source file, construct the new source, replacing everything
288
+ # between "do" and "end" in assert_value's block
289
+ # using File::expand_path here because "file" can be either
290
+ # absolute path (when test is run with "rake test" runs)
291
+ # or relative path (when test is run via ruby <path_to_test_file>)
292
+ source = File.readlines(File::expand_path(file))
293
+
294
+ # file may be changed by previous accepted assert_value's, adjust line numbers
295
+ offset = file_offsets[file].keys.inject(0) do |sum, i|
296
+ line.to_i >= i ? sum + file_offsets[file][i] : sum
297
+ end
298
+
299
+ expected_text_end_line = expected_text_start_line = line.to_i + offset
300
+ if change == :update_expected_string
301
+ #search for the end of expected value in code
302
+ expected_text_end_line += 1 while !["END", "EOS"].include?(source[expected_text_end_line].strip)
303
+ elsif change == :create_expected_string
304
+ # The is no expected value yet. expected_text_end_line is unknown
305
+ else
306
+ internal_error("Invalid change #{change}")
307
+ end
308
+
309
+ expected_length = expected_text_end_line - expected_text_start_line
310
+
311
+ # indentation is the indentation of assert_value call + 4
312
+ indentation = source[expected_text_start_line-1] =~ /^(\s+)/ ? $1.length : 0
313
+ indentation += 4
314
+
315
+ if change == :create_expected_string
316
+ if mode == :scalar
317
+ # add second argument to assert_value if it's omitted
318
+ source[expected_text_start_line-1] = "#{source[expected_text_start_line-1].chop}, <<-END\n"
319
+ elsif mode == :block
320
+ # add expected value as argument to assert_value before block call
321
+ source[expected_text_start_line-1] = source[expected_text_start_line-1].sub(/assert_value(\(.*?\))*/, "assert_value(<<-END)")
322
+ else
323
+ internal_error("Invalid mode #{mode}")
324
+ end
325
+ end
326
+ source = source[0, expected_text_start_line] +
327
+ actual.split("\n").map { |l| "#{" "*(indentation)}#{l}\n"} +
328
+ (change == :create_expected_string ? ["#{" "*(indentation-4)}END\n"] : [])+
329
+ source[expected_text_end_line, source.length]
330
+
331
+ # recalculate line number adjustments
332
+ actual_length = actual.split("\n").length
333
+ actual_length += 1 if change == :create_expected_string # END marker after expected value
334
+ file_offsets[file][line.to_i] = actual_length - expected_length
335
+
336
+ source_file = File.open(file, "w+")
337
+ source_file.write(source.join(''))
338
+ source_file.fsync
339
+ source_file.close
340
+ end
341
+
342
+ def accept_file(actual, log_file)
343
+ log = File.open(log_file, "w+")
344
+ log.write(actual)
345
+ log.fsync
346
+ log.close
347
+ end
348
+
349
+ def compare_for_assert_value(expected_verbatim, actual_verbatim)
350
+ expected_canonicalized, expected = canonicalize_for_assert_value(expected_verbatim)
351
+ actual_canonicalized, actual = canonicalize_for_assert_value(actual_verbatim)
352
+ diff_canonicalized = AssertValue::TextDiff.array_diff(expected_canonicalized, actual_canonicalized)
353
+ diff = AssertValue::TextDiff.array_diff(expected, actual)
354
+ [expected_canonicalized == actual_canonicalized, expected == actual, diff_canonicalized, diff]
355
+ end
356
+
357
+ def canonicalize_for_assert_value(text)
358
+ # make array of lines out of the text
359
+ result = text.split("\n")
360
+
361
+ # ignore leading newlines if any (trailing will be automatically ignored by split())
362
+ result.delete_at(0) if result[0] == ""
363
+
364
+ indentation = $1.length if result[0] and result[0] =~ /^(\s+)/
365
+
366
+ result.map! do |line|
367
+ # ignore indentation: we assume that the first line defines indentation
368
+ line.gsub!(/^\s{#{indentation}}/, '') if indentation
369
+ # ignore trailing spaces
370
+ line.gsub(/\s*$/, '')
371
+ end
372
+
373
+ # ignore comments
374
+ result_canonicalized= result.map do |line|
375
+ line.gsub(/\s*(#.*)?$/, '')
376
+ end
377
+ # ignore blank lines (usually they are lines with comments only)
378
+ result_canonicalized.delete_if { |line| line.nil? or line.empty? }
379
+
380
+ [result_canonicalized, result]
381
+ end
382
+
383
+ def get_caller_location(options = {:depth => 2})
384
+ caller_method = caller(options[:depth])[0]
385
+
386
+ #Sample output is:
387
+ #either full path when run as "rake test"
388
+ # /home/user/assert_value/test/unit/assure_test.rb:9:in `test_assure
389
+ #or relative path when run as ruby test/unit/assure_test.rb
390
+ # test/unit/assure_test.rb:9:in `test_assure
391
+ caller_method =~ /([^:]+):([0-9]+):in `(.+)'/
392
+ file = $1
393
+ line = $2
394
+ method = $3
395
+ [file, method, line]
396
+ end
397
+
398
+ end
399
+
400
+ if RUBY_VERSION >= "1.9.0"
401
+ class MiniTest::Unit::TestCase
402
+ include AssertValueAssertion
403
+ end
404
+ else
405
+ class Test::Unit::TestCase
406
+ include AssertValueAssertion
407
+ end
408
+ end
409
+
410
+ if defined?(RSpec)
411
+ RSpec.configure do |c|
412
+ c.include AssertValueAssertion
413
+ end
414
+ end
data/lib/text_diff.rb ADDED
@@ -0,0 +1,109 @@
1
+ # Copyright (c) 2008 Pluron, Inc.
2
+
3
+ require 'array_diff'
4
+
5
+ module AssertValue
6
+
7
+ class TextDiff
8
+
9
+ def self.mapped_chunk(arr, from, to)
10
+ return [] if to < from
11
+ arr[from, to-from+1].map{|x| [' ', x]}
12
+ end
13
+
14
+ def self.chunk(arr, from, to)
15
+ result = []
16
+ if to - from > 2
17
+ result += self.mapped_chunk(arr, from, from+2) if from > 0
18
+ result << ['@', to-2]
19
+ result += self.mapped_chunk(arr, to-2, to)
20
+ else
21
+ result << ['@', from] if from == 0
22
+ result += self.mapped_chunk(arr, from, to)
23
+ end
24
+ end
25
+
26
+ def self.record_stat(arr, at, remcount, addcount, linecount, offset)
27
+ index = arr[at][1]+1
28
+ ['', "@@ -#{index},#{linecount-addcount}, +#{index+offset},#{linecount-remcount} @@"]
29
+ end
30
+
31
+ def self.diff(a_text, b_text)
32
+ a = a_text.split("\n")
33
+ b = b_text.split("\n")
34
+ self.array_diff(a, b)
35
+ end
36
+
37
+ def self.array_diff(a, b)
38
+ diff = ArrayDiff.new(a, b)
39
+
40
+ return nil if diff.diffs.empty?
41
+
42
+ result = []
43
+
44
+ from = to = nextfrom = 0
45
+ offset = 0
46
+ diff.diffs.each do |tuple|
47
+ first = tuple[0][1]
48
+ length = tuple.length
49
+ action = tuple[0][0]
50
+ addcount = 0
51
+ remcount = 0
52
+ tuple.each do |l|
53
+ if l[0] == "+"
54
+ addcount += 1
55
+ elsif l[0] == "-"
56
+ remcount += 1
57
+ end
58
+ end
59
+ if remcount == 0
60
+ to = first-offset-1
61
+ nextfrom = to+1
62
+ else
63
+ to = first-1
64
+ nextfrom = to+remcount+1
65
+ end
66
+ result += self.chunk(a, from, to)
67
+ from = nextfrom
68
+ lastdel = (tuple[0][0] == "-")
69
+ tuple.each do |l|
70
+ if l[0] == "-"
71
+ offset -= 1
72
+ else
73
+ offset += 1
74
+ end
75
+ result << [l[0], l[2]]
76
+ end
77
+ end
78
+ if (a.length - from) > 2
79
+ result += self.chunk(a, from, from+2)
80
+ elsif a.length > 0
81
+ result += self.chunk(a, from, a.length-1)
82
+ end
83
+ linecount = addcount = remcount = offset = current_offset = 0
84
+ info_index = nil
85
+ result.each_with_index do |l, i|
86
+ if l[0] == '@'
87
+ result[info_index] = self.record_stat(result, info_index, remcount, addcount,
88
+ linecount, offset) if info_index
89
+ info_index = i
90
+ offset += addcount - remcount
91
+ linecount = 0
92
+ addcount = remcount = 0
93
+ elsif l[0] == '-'
94
+ remcount += 1
95
+ linecount += 1
96
+ elsif l[0] == '+'
97
+ addcount += 1
98
+ linecount += 1
99
+ else
100
+ linecount += 1
101
+ end
102
+ end
103
+ result[info_index] = self.record_stat(result, info_index, remcount, addcount, linecount, offset) if info_index
104
+ return result.map{|x| x.join('')}.join("\n")
105
+ end
106
+
107
+ end
108
+
109
+ end
@@ -0,0 +1,28 @@
1
+ require 'assert_value'
2
+
3
+ describe "Assert Value" do
4
+
5
+ it "compares with value in file" do
6
+ assert_value("foo", :log => 'test/logs/assert_value_with_files.log.ref')
7
+ expect(assert_value("foo", :log => 'test/logs/assert_value_with_files.log.ref')).to eq(true)
8
+ end
9
+
10
+ it "compares with value inlined in source file" do
11
+ assert_value "foo", <<-END
12
+ foo
13
+ END
14
+
15
+ expect(
16
+ assert_value "foo", <<-END
17
+ foo
18
+ END
19
+ ).to eq(true)
20
+
21
+ expect(assert_value(<<-END) do
22
+ foo
23
+ END
24
+ "foo"
25
+ end).to eq(true)
26
+ end
27
+
28
+ end
@@ -0,0 +1,101 @@
1
+ # Copyright (c) 2011 Pluron, Inc.
2
+
3
+ require 'test/unit'
4
+ require 'assert_value'
5
+
6
+ class AssertValueTest < Test::Unit::TestCase
7
+
8
+ def test_basic_assert_value
9
+ assert_value "foo", <<-END
10
+ foo
11
+ END
12
+ end
13
+
14
+ def test_assert_value_with_files
15
+ assert_value "foo", :log => 'test/logs/assert_value_with_files.log.ref'
16
+ end
17
+
18
+ def test_assert_value_empty_string
19
+ # These two calls are the same
20
+ assert_value ""
21
+ assert_value "", <<-END
22
+ END
23
+ end
24
+
25
+ def test_assert_value_exception
26
+ assert_value(<<-END) do
27
+ Exception NoMethodError: undefined method `+' for nil:NilClass
28
+ END
29
+ nil + 1
30
+ end
31
+ end
32
+
33
+ def test_assert_value_for_block_value
34
+ assert_value(<<-END) do
35
+ All Ok!
36
+ END
37
+ a = "All Ok!"
38
+ end
39
+ end
40
+
41
+ def test_assert_value_exception_with_files
42
+ assert_value(:log => 'test/logs/assert_value_exception_with_files.log.ref') do
43
+ nil + 1
44
+ end
45
+ end
46
+
47
+ def test_assert_value_block_alternative_syntax
48
+ assert_value(<<-END) {
49
+ Exception NoMethodError: undefined method `+' for nil:NilClass
50
+ END
51
+ nil + 1
52
+ }
53
+ end
54
+
55
+ def test_assert_value_block_alternative_syntax_one_liner
56
+ assert_value(<<-END) { nil + 1 }
57
+ Exception NoMethodError: undefined method `+' for nil:NilClass
58
+ END
59
+ end
60
+
61
+ def test_assert_value_for_empty_block
62
+ # These calls are the same
63
+ assert_value { }
64
+
65
+ assert_value do end
66
+
67
+ assert_value(<<-END) { }
68
+ END
69
+
70
+ assert_value(<<-END) do end
71
+ END
72
+ end
73
+
74
+ def test_assert_value_for_nil_block_result
75
+ # These calls are the same
76
+ assert_value { nil }
77
+
78
+ assert_value do nil end
79
+
80
+ assert_value(<<-END) { nil }
81
+ END
82
+
83
+ assert_value(<<-END) do nil end
84
+ END
85
+ end
86
+
87
+ end
88
+
89
+ if RUBY_VERSION >= "1.9.0"
90
+
91
+ class AssertValueMiniTest < MiniTest::Unit::TestCase
92
+
93
+ def test_basic_assert_value
94
+ assert_value "foo", <<-END
95
+ foo
96
+ END
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1 @@
1
+ Exception NoMethodError: undefined method `+' for nil:NilClass
@@ -0,0 +1 @@
1
+ foo
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: assert_value
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Pluron, Inc.
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-24 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: assert_value assertion
15
+ email: support@pluron.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .gitignore
21
+ - Gemfile
22
+ - LICENSE
23
+ - README.md
24
+ - Rakefile
25
+ - assert_value.gemspec
26
+ - assert_value.kdev4
27
+ - lib/array_diff.rb
28
+ - lib/assert_value.rb
29
+ - lib/text_diff.rb
30
+ - test/assert_value_spec.rb
31
+ - test/assert_value_test.rb
32
+ - test/logs/assert_value_exception_with_files.log.ref
33
+ - test/logs/assert_value_with_files.log.ref
34
+ homepage: http://github.com/acunote/assert_value
35
+ licenses:
36
+ - MIT
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 1.8.23
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Assert that checks that two values (strings, expected and actual) are same
59
+ and which can magically replace expected value with the actual in case the new behavior
60
+ (and new actual value) is correct
61
+ test_files:
62
+ - test/assert_value_spec.rb
63
+ - test/assert_value_test.rb
64
+ - test/logs/assert_value_exception_with_files.log.ref
65
+ - test/logs/assert_value_with_files.log.ref