wrong 0.6.3 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.markdown +10 -7
  2. data/lib/wrong.rb +1 -0
  3. data/lib/wrong/adapters/rspec.rb +4 -0
  4. data/lib/wrong/assert.rb +1 -2
  5. data/lib/wrong/chunk.rb +5 -170
  6. data/lib/wrong/close_to.rb +7 -1
  7. data/lib/wrong/config.rb +3 -3
  8. data/lib/wrong/d.rb +2 -1
  9. data/lib/wrong/failure_message.rb +109 -7
  10. data/lib/wrong/message/string_comparison.rb +1 -1
  11. data/lib/wrong/sexp_ext.rb +2 -1
  12. data/lib/wrong/terminal.rb +43 -0
  13. data/lib/wrong/version.rb +1 -1
  14. data/test/adapters/railsapp/app/controllers/application_controller.rb +3 -0
  15. data/test/adapters/railsapp/app/helpers/application_helper.rb +2 -0
  16. data/test/adapters/railsapp/config/application.rb +54 -0
  17. data/test/adapters/railsapp/config/boot.rb +6 -0
  18. data/test/adapters/railsapp/config/environment.rb +5 -0
  19. data/test/adapters/railsapp/config/environments/development.rb +30 -0
  20. data/test/adapters/railsapp/config/environments/production.rb +60 -0
  21. data/test/adapters/railsapp/config/environments/test.rb +42 -0
  22. data/test/adapters/railsapp/config/initializers/backtrace_silencers.rb +7 -0
  23. data/test/adapters/railsapp/config/initializers/inflections.rb +10 -0
  24. data/test/adapters/railsapp/config/initializers/mime_types.rb +5 -0
  25. data/test/adapters/railsapp/config/initializers/secret_token.rb +7 -0
  26. data/test/adapters/railsapp/config/initializers/session_store.rb +8 -0
  27. data/test/adapters/railsapp/config/initializers/wrap_parameters.rb +14 -0
  28. data/test/adapters/railsapp/config/routes.rb +58 -0
  29. data/test/adapters/railsapp/db/seeds.rb +7 -0
  30. data/test/adapters/railsapp/spec/spec_helper.rb +33 -0
  31. data/test/adapters/railsapp/spec/wrong_spec.rb +8 -0
  32. data/test/adapters/rspec_rails_test.rb +4 -4
  33. data/test/adapters/rspec_test.rb +2 -2
  34. data/test/assert_advanced_test.rb +9 -1
  35. data/test/chunk_test.rb +0 -200
  36. data/test/close_to_test.rb +34 -2
  37. data/test/d_test.rb +2 -2
  38. data/test/eventually_test.rb +27 -34
  39. data/test/failure_message_test.rb +212 -13
  40. data/test/test_helper.rb +2 -2
  41. metadata +47 -56
  42. data/lib/wrong/ruby2ruby_patch.rb +0 -37
@@ -100,6 +100,8 @@ If you want to compare floats, try this:
100
100
 
101
101
  If you don't want `close_to?` cluttering up `Float` in your test runs then use `include Wrong::Assert` instead of `include Wrong`.
102
102
 
103
+ `close_to?` also works on `Time`s, `Date`s, and `DateTime`s. (The default tolerance of 1 msec may be too small for you.)
104
+
103
105
  ### d
104
106
 
105
107
  We also implement the most amazing debugging method ever, `d`, which gives you a sort of mini-wrong wherever you want it
@@ -251,29 +253,30 @@ Wrong is compatible with RSpec and MiniTest::Spec, and probably Cucumber too, so
251
253
  Here's an RSpec example:
252
254
 
253
255
  require "wrong/adapters/rspec"
254
- Wrong.config.alias_assert :expect_that
256
+ Wrong.config.alias_assert :expect, override: true
255
257
 
256
258
  describe BleuCheese do
257
259
  it "stinks" do
258
- expect_that { BleuCheese.new.smell > 9000 }
260
+ cheese = BleuCheese.new
261
+ expect { cheese.smell > 9000 }
259
262
  end
260
263
  end
261
264
 
262
265
  This makes your code read like a BDD-style DSL, without RSpec's "should" syntax (which is, let's face it, pretty weird the first few hundred times you have to use it). Compare
263
266
 
264
- expect_that { BleuCheese.new.smell > 9000 }
267
+ expect { cheese.smell > 9000 }
265
268
 
266
269
  to
267
270
 
268
- BleuCheese.new.smell.should > 9000
271
+ cheese.smell.should be > 9000
269
272
 
270
- and consider which one more clearly describes the desired behavior. The object under test doesn't really have a `should` method, so why should it magically get one during a test? And in what human language is "should greater than" a valid phrase?
273
+ and consider which one more clearly describes the desired behavior. The object under test doesn't really have a `should` method, so why should it magically get one during a test?
271
274
 
272
275
  Warning: currently the use of `alias_assert :expect` is **not** compatible with RSpec, since RSpec also defines `expect` as a synonym for `lambda`. If you really want to use `expect` as an alias form `assert`, then use `Wrong.config.alias_assert :expect, :override => true`. See [issue #6](https://github.com/sconover/wrong/issues/6) for more details.
273
276
 
274
277
  ## Algorithm ##
275
278
 
276
- So wait a second. How do we do it? Doesn't Ruby have [poor support for AST introspection](http://blog.zenspider.com/2009/04/parsetree-eol.html)? Well, yes, it does, so we cheat: we figure out what file and line the assert block is defined in, then open the file, read the code, and parse it directly using Ryan Davis' amazing [RubyParser](http://parsetree.rubyforge.org/ruby_parser/) and [Ruby2Ruby](http://seattlerb.rubyforge.org/ruby2ruby/). You can bask in the kludge by examining `chunk.rb` and `assert.rb`. If you find some code it can't parse, please send it our way. As a failsafe we also use Sourcify, which has yet another home baked RACC parser, so we have many chances to parse your code.
279
+ So wait a second. How do we do it? Doesn't Ruby have [poor support for AST introspection](http://blog.zenspider.com/2009/04/parsetree-eol.html)? Well, yes, it does, so we cheat: we figure out what file and line the assert block is defined in, then open the file, read the code, and parse it directly using Ryan Davis' amazing [RubyParser](http://parsetree.rubyforge.org/ruby_parser/) and [Ruby2Ruby](http://seattlerb.rubyforge.org/ruby2ruby/). You can bask in the kludge by examining `chunk.rb` and `assert.rb`. If you find some code it can't parse, please send it our way.
277
280
 
278
281
  Before you get your knickers in a twist about how this is totally unacceptable because it doesn't support this or that use case, here are our caveats and excuses:
279
282
 
@@ -462,7 +465,7 @@ If you're in Ruby 1.8, you **really** shouldn't do it! But if you do, you can us
462
465
 
463
466
  ## Bugs ##
464
467
 
465
- * assert doesn't work (can't find the source code) from inside a "Dir.chdir" block
468
+ * see Github Issues <http://github.com/sconover/wrong/issues>
466
469
 
467
470
  ## todo ##
468
471
 
@@ -5,6 +5,7 @@ require "predicated"
5
5
  require "wrong/assert"
6
6
  require "wrong/helpers"
7
7
  require "wrong/chunk"
8
+ require "wrong/terminal"
8
9
  require "wrong/sexp_ext"
9
10
  require "wrong/version"
10
11
  require "wrong/config"
@@ -1,4 +1,5 @@
1
1
  require "wrong"
2
+ require "wrong/terminal"
2
3
 
3
4
  if Object.const_defined? :RSpec
4
5
  # RSpec 2
@@ -58,6 +59,9 @@ if Object.const_defined? :RSpec
58
59
  end
59
60
  end
60
61
 
62
+ # tweak terminal width so exceptions wrap properly inside RSpec output
63
+ Wrong::Terminal.width -= 7
64
+
61
65
  elsif Object.const_defined? :Spec
62
66
  # RSpec 1
63
67
  Spec::Runner.configure do |config|
@@ -5,7 +5,6 @@ require "predicated/to/sentence"
5
5
  require "wrong/chunk"
6
6
  require "wrong/config"
7
7
  require "wrong/failure_message"
8
- require "wrong/ruby2ruby_patch" # need to patch it after some other stuff loads
9
8
  require "wrong/rainbow"
10
9
 
11
10
  module Wrong
@@ -68,7 +67,7 @@ module Wrong
68
67
  value = !value if valence == :deny
69
68
  if value
70
69
  if Wrong.config[:verbose]
71
- code = Wrong::Chunk.from_block(block, depth + 2).code
70
+ code = Wrong::Chunk.from_block(block, depth + 2).code
72
71
  if Wrong.config[:color]
73
72
  explanation = explanation.color(:blue) if explanation
74
73
  code = code.color(:green)
@@ -2,18 +2,6 @@ require 'ruby_parser'
2
2
  require 'ruby2ruby'
3
3
  require 'pp'
4
4
 
5
- def require_optionally(library)
6
- begin
7
- require library
8
- rescue LoadError => e
9
- raise e unless e.message == "no such file to load -- #{library}" or
10
- e.message == "cannot load such file -- #{library}" # 1.9.3 changed the error message
11
- end
12
- end
13
-
14
- require_optionally "ParseTree"
15
- require_optionally "sourcify"
16
-
17
5
  require "wrong/config"
18
6
  require "wrong/sexp_ext"
19
7
  require "wrong/capturing"
@@ -60,21 +48,11 @@ module Wrong
60
48
  end
61
49
 
62
50
  def build_sexp
63
- sexp = begin
64
- unless @block.nil? or @block.is_a?(String) or !Object.const_defined?(:Sourcify)
65
- # first try sourcify
66
- @block.to_sexp[3] # the [3] is to strip out the "proc {" sourcify adds to everything
67
- end
68
- rescue Exception => e
69
- # sourcify failed, so fall through
70
- end
71
-
72
- # next try glomming
73
- sexp ||= glom(if @file == "(irb)"
74
- IRB.CurrentContext.all_lines
75
- else
76
- read_source_file(@file)
77
- end)
51
+ glom(if @file == "(irb)"
52
+ IRB.CurrentContext.all_lines
53
+ else
54
+ read_source_file(@file)
55
+ end)
78
56
  end
79
57
 
80
58
  def read_source_file(file)
@@ -95,7 +73,6 @@ module Wrong
95
73
  while sexp.nil? && line_index + c < lines.size
96
74
  begin
97
75
  @chunk = lines[line_index..line_index+c].join("\n")
98
-
99
76
  capturing(:stderr) do # new RubyParser is too loud
100
77
  sexp = @parser.parse(@chunk)
101
78
  end
@@ -164,148 +141,6 @@ module Wrong
164
141
  end
165
142
  end
166
143
 
167
- def details
168
- @details ||= build_details
169
- end
170
-
171
- def pretty_value(value, starting_col = 0, indent_wrapped_lines = 6, width = Chunk.terminal_width)
172
- # inspected = value.inspect
173
-
174
- # note that if the first line overflows due to the starting column then pp won't wrap it right
175
- inspected = PP.pp(value, "", width - starting_col).chomp
176
-
177
- # this bit might be redundant with the pp call now
178
- indented = indent_all(6, inspected)
179
- if width
180
- wrap_and_indent(indented, starting_col, indent_wrapped_lines, width)
181
- else
182
- indented
183
- end
184
- end
185
-
186
- private
187
-
188
- # todo: move to FailureMessage?
189
- def build_details
190
- require "wrong/rainbow" if Wrong.config[:color]
191
- s = ""
192
- parts = self.parts
193
- parts.shift # remove the first part, since it's the same as the code
194
-
195
- details = []
196
-
197
- if parts.size > 0
198
- parts.each do |part|
199
- begin
200
- value = eval(part, block.binding)
201
- unless part == value.inspect # this skips literals or tautologies
202
- if part =~ /\n/m
203
- part.gsub!(/\n/, newline(2))
204
- part += newline(3)
205
- end
206
- value = pretty_value(value, (4 + part.length + 4))
207
- if Wrong.config[:color]
208
- part = part.color(:blue)
209
- value = value.color(:magenta)
210
- end
211
- details << indent(4, part, " is ", value)
212
- end
213
- rescue Exception => e
214
- raises = "raises #{e.class}"
215
- if Wrong.config[:color]
216
- part = part.color(:blue)
217
- raises = raises.bold.color(:red)
218
- end
219
- formatted_exeption = if e.message and e.message != e.class.to_s
220
- indent(4, part, " ", raises, ": ", indent_all(6, e.message))
221
- else
222
- indent(4, part, " ", raises)
223
- end
224
- details << formatted_exeption
225
- end
226
- end
227
- end
228
-
229
- details.uniq!
230
- if details.empty?
231
- ""
232
- else
233
- "\n" + details.join("\n") + "\n"
234
- end
235
-
236
- end
237
-
238
- public # don't know exactly why this needs to be public but eval'ed code can't find it otherwise
239
- def indent(indent, *s)
240
- "#{" " * indent}#{s.join('')}"
241
- end
242
-
243
- def newline(indent)
244
- "\n" + self.indent(indent)
245
- end
246
-
247
- def indent_all(amount, s)
248
- s.gsub("\n", "\n#{indent(amount)}")
249
- end
250
-
251
- def wrap_and_indent(indented, starting_col, indent_wrapped_lines, full_width)
252
- first_line = true
253
- width = full_width - starting_col # the first line is essentially shorter
254
- indented.split("\n").map do |line|
255
- s = ""
256
- while line.length > width
257
- s << line[0...width]
258
- s << newline(indent_wrapped_lines)
259
- line = line[width..-1]
260
- if first_line
261
- width += starting_col - indent_wrapped_lines
262
- first_line = false
263
- end
264
- end
265
- s << line
266
- s
267
- end.join("\n")
268
- end
269
-
270
- # Returns [width, height] of terminal when detected, nil if not detected.
271
- # Think of this as a simpler version of Highline's Highline::SystemExtensions.terminal_size()
272
- # Lifted from https://github.com/cldwalker/hirb/blob/master/lib/hirb/util.rb#L59
273
- #
274
- # See also http://stackoverflow.com/questions/2068859/how-to-get-the-width-of-terminal-window-in-ruby
275
- # https://github.com/genki/ruby-terminfo/blob/master/lib/terminfo.rb
276
- # http://www.mkssoftware.com/docs/man1/stty.1.asp
277
-
278
-
279
- def self.terminal_size
280
- @@terminal_size ||= begin
281
- if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
282
- [ENV['COLUMNS'].to_i, ENV['LINES'].to_i]
283
- elsif (RUBY_PLATFORM =~ /java/ || (!STDIN.tty? && ENV['TERM'])) && command_exists?('tput')
284
- [`tput cols`.to_i, `tput lines`.to_i]
285
- elsif STDIN.tty? && command_exists?('stty')
286
- `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse
287
- else
288
- nil
289
- end
290
- rescue
291
- nil
292
- end
293
- end
294
-
295
- def self.terminal_width
296
- (@terminal_width ||= nil) || (terminal_size && terminal_size.first) || 80
297
- end
298
-
299
- def self.terminal_width= forced_with
300
- @terminal_width = forced_with
301
- end
302
-
303
- # Determines if a shell command exists by searching for it in ENV['PATH'].
304
- def self.command_exists?(command)
305
- ENV['PATH'].split(File::PATH_SEPARATOR).any? {|d| File.exists? File.join(d, command) }
306
- end
307
-
308
-
309
144
  end
310
145
 
311
146
  end
@@ -1,8 +1,14 @@
1
1
  module Wrong
2
2
  module CloseTo
3
3
  def close_to?(other, tolerance = 0.001)
4
- (self.to_f - other.to_f).abs < tolerance
4
+ if respond_to? :to_f
5
+ (self.to_f - other.to_f).abs < tolerance
6
+ elsif respond_to? :to_time
7
+ self.to_time.close_to?( other.to_time, tolerance)
8
+ end
5
9
  end
6
10
  end
7
11
  Numeric.send :include, CloseTo
12
+ Date.send :include, CloseTo
13
+ Time.send :include, CloseTo
8
14
  end
@@ -10,7 +10,7 @@ module Wrong
10
10
  end
11
11
  Config.new settings
12
12
  end
13
-
13
+
14
14
  def self.config
15
15
  @config ||= load_config
16
16
  end
@@ -72,8 +72,8 @@ module Wrong
72
72
  self[:aliases][:deny]
73
73
  end
74
74
 
75
- def assert_methods
76
- assert_method_names + deny_method_names
75
+ def hidden_methods
76
+ assert_method_names + deny_method_names + [:eventually]
77
77
  end
78
78
  end
79
79
  end
@@ -21,6 +21,7 @@ module Wrong
21
21
 
22
22
  # look for a "d" inside the block
23
23
  sexp.each_subexp do |subexp|
24
+ #sexp.deep_each do |subexp| # todo: try to use deep_each
24
25
  if subexp.d?
25
26
  sexp = subexp[3] # swap in the block part of the nested d call
26
27
  end
@@ -28,7 +29,7 @@ module Wrong
28
29
 
29
30
  code = sexp.to_ruby
30
31
  value = eval(code, block.binding, called_from[0], called_from[1].to_i)
31
- width = Chunk.terminal_width
32
+ width = Terminal.width
32
33
  value = PP.pp(value, "", width - (code.size + 3)).chomp
33
34
 
34
35
  if Wrong.config[:color]
@@ -1,3 +1,6 @@
1
+ require "wrong/chunk"
2
+ require "wrong/terminal"
3
+
1
4
  module Wrong
2
5
  class FailureMessage
3
6
  @@formatters = []
@@ -40,7 +43,6 @@ module Wrong
40
43
  end
41
44
  end
42
45
 
43
-
44
46
  attr_accessor :chunk, :valence, :explanation
45
47
 
46
48
  def initialize(chunk, valence, explanation)
@@ -57,20 +59,89 @@ module Wrong
57
59
  message << basic
58
60
 
59
61
  formatted_message = if predicate && !(predicate.is_a? Predicated::Conjunction)
60
- if formatter = FailureMessage.formatter_for(predicate)
61
- colored(:bold, formatter.describe)
62
- end
63
- end
62
+ if formatter = FailureMessage.formatter_for(predicate)
63
+ colored(:bold, formatter.describe)
64
+ end
65
+ end
64
66
 
65
- unless chunk.details.empty? and formatted_message.nil?
67
+ unless details.empty? and formatted_message.nil?
66
68
  message << ", but"
67
69
  end
68
70
 
69
71
  message << formatted_message if formatted_message
70
- message << chunk.details unless chunk.details.empty?
72
+ message << details unless details.empty?
71
73
  message
72
74
  end
73
75
 
76
+ def details
77
+ @details ||= begin
78
+ require "wrong/rainbow" if Wrong.config[:color]
79
+ s = ""
80
+ parts = chunk.parts
81
+
82
+ parts.shift while parts.first == "()" # the parser adds this sometimes
83
+ parts.shift # remove the first part, since it's the same as the code
84
+
85
+ details = []
86
+
87
+ if parts.size > 0
88
+ parts.each do |part|
89
+ begin
90
+ value = eval(part, chunk.block.binding)
91
+ unless part == value.inspect # this skips literals or tautologies
92
+ if part =~ /\n/m
93
+ part.gsub!(/\n/, newline(2))
94
+ part += newline(3)
95
+ end
96
+ value = pretty_value(value, (4 + part.length + 4))
97
+ if Wrong.config[:color]
98
+ part = part.color(:blue)
99
+ value = value.color(:magenta)
100
+ end
101
+ details << indent(4, part, " is ", value)
102
+ end
103
+ rescue Exception => e
104
+ raises = "raises #{e.class}"
105
+ if Wrong.config[:color]
106
+ part = part.color(:blue)
107
+ raises = raises.bold.color(:red)
108
+ end
109
+ formatted_exeption = if e.message and e.message != e.class.to_s
110
+ indent(4, part, " ", raises, ": ", indent_all(6, e.message))
111
+ else
112
+ indent(4, part, " ", raises)
113
+ end
114
+ details << formatted_exeption
115
+ end
116
+ end
117
+ end
118
+
119
+ details.uniq!
120
+ if details.empty?
121
+ ""
122
+ else
123
+ "\n" + details.join("\n") + "\n"
124
+ end
125
+ end
126
+
127
+ end
128
+
129
+ # todo: use awesome_print
130
+ def pretty_value(value, starting_col = 0, indent_wrapped_lines = 6, width = Terminal.width)
131
+ # inspected = value.inspect
132
+
133
+ # note that if the first line overflows due to the starting column then pp won't wrap it right
134
+ inspected = PP.pp(value, "", width - starting_col).chomp
135
+
136
+ # this bit might be redundant with the pp call now
137
+ indented = indent_all(6, inspected)
138
+ if width
139
+ wrap_and_indent(indented, starting_col, indent_wrapped_lines, width)
140
+ else
141
+ indented
142
+ end
143
+ end
144
+
74
145
  protected
75
146
  def code
76
147
  @code ||= chunk.code
@@ -98,5 +169,36 @@ module Wrong
98
169
  end
99
170
  end
100
171
 
172
+ def indent(indent, *s)
173
+ "#{" " * indent}#{s.join('')}"
174
+ end
175
+
176
+ def newline(indent)
177
+ "\n" + self.indent(indent)
178
+ end
179
+
180
+ def indent_all(amount, s)
181
+ s.gsub("\n", "\n#{indent(amount)}")
182
+ end
183
+
184
+ def wrap_and_indent(indented, starting_col, indent_wrapped_lines, full_width)
185
+ first_line = true
186
+ width = full_width - starting_col # the first line is essentially shorter
187
+ indented.split("\n").map do |line|
188
+ s = ""
189
+ while line.length > width
190
+ s << line[0...width]
191
+ s << newline(indent_wrapped_lines)
192
+ line = line[width..-1]
193
+ if first_line
194
+ width += starting_col - indent_wrapped_lines
195
+ first_line = false
196
+ end
197
+ end
198
+ s << line
199
+ s
200
+ end.join("\n")
201
+ end
202
+
101
203
  end
102
204
  end