Skip to content

Commit

Permalink
registry for formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
alexch committed Oct 4, 2010
1 parent cc71d85 commit 4e8fff3
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 104 deletions.
16 changes: 8 additions & 8 deletions README.markdown
Expand Up @@ -27,12 +27,12 @@ which untangles some dependencies.
Wrong provides a simple assert method that takes a block:

require "wrong"

include Wrong

assert { 1 == 1 }
==> nil

assert { 2 == 1 }
==> Expected (2 == 1), but 2 is not equal to 1

Expand Down Expand Up @@ -93,7 +93,7 @@ We also implement the most amazing debugging method ever, `d`, which gives you a
d { x } # => prints "x is 7" to the console
d { x * 2 } # => prints "(x * 2) is 14" to the console

(`d` was originally implemented by Rob Sanheim in LogBuddy; as with Assert2 this is a rewrite and homage.)
(`d` was originally implemented by Rob Sanheim in LogBuddy; as with Assert2 this is a rewrite and homage.) Remember, if you want `d` to work at runtime (e.g. in a webapp) then you must `include 'wrong/d'` inside your app, e.g. for in your `environment.rb` file.

More examples are in the file `examples.rb` <http://github.com/alexch/wrong/blob/master/examples.rb>

Expand Down Expand Up @@ -194,16 +194,16 @@ And if your assertion code isn't self-explanatory, then that's a hint that you m

When a failure occurs, the exception message contains all the details you might need to make sense of it. Here's the breakdown:

Expected [CLAIM], but [PREDICATE]
Expected [CLAIM], but [SUMMARY]
[FORMATTER]
[SUBEXP] is [VALUE]
...

* CLAIM is the code inside your assert block
* PREDICATE is a to-English translation of the claim, via the Predicated library. This tries to be very intelligible; e.g. translating "include?" into "does not include" and so on.
* CLAIM is the code inside your assert block, normalized
* SUMMARY is a to-English translation of the claim, via the Predicated library. This tries to be very intelligible; e.g. translating "include?" into "does not include" and so on.
* If there is a formatter registered for this type of predicate, its output will come next. (See below.)
* SUBEXP is each of the subtrees of the claim, minus duplicates and truisms (e.g. literals).
* The word "is" is a very nice separator since it doesn't look like code.
* The word "is" is a very nice separator since it doesn't look like code, but is short enough to be easily visually parsed.
* VALUE is `eval(SUBEXP).inspect`

We hope this structure lets your eyes focus on the meaningful values and differences in the message, rather than glossing over with stack-trace burnout. If you have any suggestions on how to improve it, please share them.
Expand Down
3 changes: 2 additions & 1 deletion Rakefile
Expand Up @@ -10,6 +10,7 @@ task :test do
"./test/message/test_context_test.rb",
"./test/assert_advanced_test.rb",
]

all_passed = separate.collect do |test_file|
puts "\n>> Separately running #{test_file} under #{ENV['RUBY_VERSION']}..."
clear_bundler_env
Expand All @@ -19,7 +20,7 @@ task :test do
at_exit { exit false }
end

puts "\n>> Running remaining tests under #{ENV['RUBY_VERSION']}..."
puts "\n>> Running most tests under #{ENV['RUBY_VERSION']}..."
Dir["./test/**/*_test.rb"].each do |test_file|
begin
require test_file unless separate.include?(test_file)
Expand Down
72 changes: 34 additions & 38 deletions lib/wrong/assert.rb
Expand Up @@ -4,16 +4,9 @@

require "wrong/chunk"
require "wrong/config"
require "wrong/failure_message"
require "wrong/ruby2ruby_patch" # need to patch it after some other stuff loads

#see http://yehudakatz.com/2009/01/18/other-ways-to-wrap-a-method/
class Module
def overridable(&blk)
mod = Module.new(&blk)
include mod
end
end

module Wrong
module Assert

Expand Down Expand Up @@ -55,10 +48,8 @@ def deny(*args, &block)
end
end

overridable do
def failure_message(method_sym, block, predicate)
method_sym == :deny ? predicate.to_sentence : predicate.to_negative_sentence
end
def summary(method_sym, predicate)
method_sym == :deny ? predicate.to_sentence : predicate.to_negative_sentence
end

protected
Expand All @@ -68,39 +59,44 @@ def self.last_predicated_error
@@last_predicated_error ||= nil
end

# todo: move some/all of this into FailureMessage
def full_message(chunk, block, valence, explanation)
code = chunk.code

predicate = begin
Predicated::Predicate.from_ruby_code_string(code, block.binding)
rescue Predicated::Predicate::DontKnowWhatToDoWithThisSexpError, Exception => e
# save it off for debugging
@@last_predicated_error = e
nil
end

code = code.color(:blue) if Wrong.config[:color]
message = ""
message << "#{explanation}: " if explanation
message << "#{valence == :deny ? "Didn't expect" : "Expected"} #{code}, but "
if predicate && !(predicate.is_a? Predicated::Conjunction)
message << summary(valence, predicate)
if formatter = FailureMessage.formatter_for(predicate)
failure = formatter.describe
failure = failure.bold if Wrong.config[:color]
message << failure
end
end
message << chunk.details
message
end

def aver(valence, explanation = nil, depth = 0, &block)
require "wrong/rainbow" if Wrong.config[:color]

value = block.call
value = !value if valence == :deny
unless value
chunk = Wrong::Chunk.from_block(block, depth + 2)
begin
code = chunk.code
rescue => e
# note: this is untested; it's to recover from when we can't locate the code
message = "Failed assertion at #{caller[depth + 2]} [couldn't retrieve source code due to #{e.inspect}]"
raise failure_class.new(message)
end

predicate = begin
Predicated::Predicate.from_ruby_code_string(code, block.binding)
rescue Predicated::Predicate::DontKnowWhatToDoWithThisSexpError, Exception => e
# save it off for debugging
@@last_predicated_error = e
nil
end
chunk = Wrong::Chunk.from_block(block, depth + 2)

code = code.color(:blue) if Wrong.config[:color]
message = ""
message << "#{explanation}: " if explanation
message << "#{valence == :deny ? "Didn't expect" : "Expected"} #{code}, but "
if predicate && !(predicate.is_a? Predicated::Conjunction)
failure = failure_message(valence, block, predicate)
failure = failure.bold if Wrong.config[:color]
message << failure
end
message << chunk.details
message = full_message(chunk, block, valence, explanation)
raise failure_class.new(message)
end
end
Expand Down
8 changes: 6 additions & 2 deletions lib/wrong/chunk.rb
Expand Up @@ -76,7 +76,7 @@ def read_source_file(file, dir = ".")
File.read "#{dir}/#{file}"

rescue Errno::ENOENT, Errno::EACCES => e
# we may be in a chdir underneath where the file is, so move up one level and try again
# we may be in a chdir underneath where the file is, so move up one level and try again
parent = "#{dir}/..".gsub(/^(\.\/)*/, '')
if File.expand_path(dir) == File.expand_path(parent)
raise Errno::ENOENT, "couldn't find #{file}"
Expand Down Expand Up @@ -129,6 +129,10 @@ def claim

def code
self.claim.to_ruby
rescue => e
# note: this is untested; it's to recover from when we can't locate the code
message = "Failed assertion at #{caller[depth + 2]} [couldn't retrieve source code due to #{e.inspect}]"
raise failure_class.new(message)
end

def parts(sexp = nil)
Expand All @@ -150,7 +154,7 @@ def parts(sexp = nil)
if sexp.first == :iter
sexp.delete_at(1) # remove the method-call-sans-block subnode
end

sexp.each do |sub|
if sub.is_a?(Sexp)
parts_list += parts(sub)
Expand Down
43 changes: 43 additions & 0 deletions lib/wrong/failure_message.rb
@@ -0,0 +1,43 @@
module Wrong
class FailureMessage
@@formatters = []

def self.register_formatter(formatter)
@@formatters << formatter
end

def self.formatters
@@formatters
end

def self.formatter_for(predicate)
@@formatters.each do |formatter_class|
formatter = formatter_class.new(predicate)
if formatter.match?
return formatter
end
end
nil
end

class Formatter
def self.register
Wrong::FailureMessage.register_formatter(self)
end

attr_reader :predicate

def initialize(predicate)
@predicate = predicate
end

def describe(valence)

end

def match?
false
end
end
end
end
49 changes: 23 additions & 26 deletions lib/wrong/message/array_diff.rb
@@ -1,36 +1,33 @@
require "diff/lcs"
require "wrong/failure_message"

module Wrong
module Assert
class ArrayDiff < FailureMessage::Formatter
register # tell FailureMessage::Formatter about us

def is_arrayish?(object)
# in some Rubies, String is Enumerable
object.is_a?(Enumerable) && !object.is_a?(String)
def match?
predicate.is_a?(Predicated::Equal) &&
arrayish?(predicate.left) &&
arrayish?(predicate.right)
end

overridable do
def failure_message(method_sym, block, predicate)
message = super

if predicate.is_a?(Predicated::Equal) &&
is_arrayish?(predicate.left) &&
is_arrayish?(predicate.right)
def arrayish?(object)
# in some Rubies, String is Enumerable
object.is_a?(Enumerable) && !object.is_a?(String)
end

left_str, right_str, diff_str = Wrong::ArrayDiff.compute_and_format(predicate.left, predicate.right)
def describe
left_str, right_str, diff_str = compute_and_format(predicate.left, predicate.right)

message << "\n\narray diff:\n"
message << left_str + "\n"
message << right_str + "\n"
message << diff_str + "\n"
end
message = "\n"
message << left_str + "\n"
message << right_str + "\n"
message << diff_str + "\n"
message

message
end
end
end

module ArrayDiff
def self.compute_and_format(left, right)
def compute_and_format(left, right)
diffs = Diff::LCS.sdiff(left, right)

left_arr = []
Expand All @@ -48,12 +45,12 @@ def self.compute_and_format(left, right)
end


[format(left_arr),
format(right_arr),
" " + diff_arr.join(" ") + " "]
diff_str = " " + diff_arr.join(" ") + " "

[format(left_arr), format(right_arr), diff_str]
end

def self.format(thing)
def format(thing)
str = ""
if thing.is_a?(Array)
str << "["
Expand Down
29 changes: 13 additions & 16 deletions lib/wrong/message/string_comparison.rb
@@ -1,3 +1,5 @@
require "wrong/failure_message"

module Wrong
class StringComparison
@@window = 64
Expand Down Expand Up @@ -68,24 +70,19 @@ def chunk(s)
end
end

module Assert
overridable do

def failure_message(method_sym, block, predicate)
message = super

if predicate.is_a?(Predicated::Equal) &&
predicate.left.is_a?(String) &&
predicate.right.is_a?(String)

comparison = Wrong::StringComparison.new(predicate.left, predicate.right)
message << "\n"
message << comparison.message
end
class StringComparisonFormatter < FailureMessage::Formatter
register # tell FailureMessage::Formatter about us

message
end
def match?
predicate.is_a?(Predicated::Equal) &&
predicate.left.is_a?(String) &&
predicate.right.is_a?(String)
end

def describe
comparison = Wrong::StringComparison.new(predicate.left, predicate.right)
"\n" + comparison.message
end
end

end
4 changes: 2 additions & 2 deletions test/assert_test.rb
Expand Up @@ -47,14 +47,14 @@ class MyError < StandardError;
sky = "green"
@m.assert("the sky should be blue") { sky == "blue" }
}
assert e.message =~ /^the sky should be blue: /
assert e.message =~ /^the sky should be blue: /, e.message
end

it "gives a meaningful error when passed no block" do
e = get_error {
@m.assert(2+2 == 5)
}
assert e.message =~ /a block/
assert e.message =~ /a block/, e.message
end
end

Expand Down

0 comments on commit 4e8fff3

Please sign in to comment.