wrong 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +114 -25
- data/lib/predicated/Gemfile +15 -0
- data/lib/predicated/LICENSE +20 -0
- data/lib/predicated/README.markdown +191 -0
- data/lib/predicated/Rakefile +51 -0
- data/lib/predicated/lib/predicated.rb +4 -0
- data/lib/predicated/lib/predicated/autogen_call.rb +37 -0
- data/lib/predicated/lib/predicated/constrain.rb +66 -0
- data/lib/predicated/lib/predicated/evaluate.rb +94 -0
- data/lib/predicated/lib/predicated/from/callable_object.rb +108 -0
- data/lib/predicated/lib/predicated/from/json.rb +59 -0
- data/lib/predicated/lib/predicated/from/ruby_code_string.rb +73 -0
- data/lib/predicated/lib/predicated/from/url_part.rb +104 -0
- data/lib/predicated/lib/predicated/from/xml.rb +61 -0
- data/lib/predicated/lib/predicated/gem_check.rb +34 -0
- data/lib/predicated/lib/predicated/predicate.rb +111 -0
- data/lib/predicated/lib/predicated/print.rb +62 -0
- data/lib/predicated/lib/predicated/selectable.rb +102 -0
- data/lib/predicated/lib/predicated/simple_templated_predicate.rb +79 -0
- data/lib/predicated/lib/predicated/string_utils.rb +20 -0
- data/lib/predicated/lib/predicated/to/arel.rb +41 -0
- data/lib/predicated/lib/predicated/to/json.rb +48 -0
- data/lib/predicated/lib/predicated/to/sentence.rb +94 -0
- data/lib/predicated/lib/predicated/to/solr.rb +15 -0
- data/lib/predicated/lib/predicated/to/xml.rb +67 -0
- data/lib/predicated/lib/predicated/version.rb +3 -0
- data/lib/predicated/predicated.gemspec +22 -0
- data/lib/predicated/test/autogen_call_test.rb +40 -0
- data/lib/predicated/test/canonical_transform_cases.rb +63 -0
- data/lib/predicated/test/constrain_test.rb +86 -0
- data/lib/predicated/test/enumerable_test.rb +32 -0
- data/lib/predicated/test/equality_test.rb +32 -0
- data/lib/predicated/test/evaluate_test.rb +149 -0
- data/lib/predicated/test/from/callable_object_canonical_test.rb +43 -0
- data/lib/predicated/test/from/callable_object_test.rb +78 -0
- data/lib/predicated/test/from/json_test.rb +83 -0
- data/lib/predicated/test/from/ruby_code_string_canonical_test.rb +37 -0
- data/lib/predicated/test/from/ruby_code_string_test.rb +103 -0
- data/lib/predicated/test/from/url_part_parser_test.rb +123 -0
- data/lib/predicated/test/from/url_part_test.rb +48 -0
- data/lib/predicated/test/from/xml_test.rb +57 -0
- data/lib/predicated/test/json_conversion_test.rb +33 -0
- data/lib/predicated/test/print_test.rb +66 -0
- data/lib/predicated/test/selectable_test.rb +123 -0
- data/lib/predicated/test/simple_templated_predicate_test.rb +39 -0
- data/lib/predicated/test/suite.rb +2 -0
- data/lib/predicated/test/test_helper.rb +64 -0
- data/lib/predicated/test/test_helper_with_wrong.rb +6 -0
- data/lib/predicated/test/to/arel_test.rb +85 -0
- data/lib/predicated/test/to/json_test.rb +74 -0
- data/lib/predicated/test/to/sentence_test.rb +90 -0
- data/lib/predicated/test/to/solr_test.rb +39 -0
- data/lib/predicated/test/to/xml_test.rb +72 -0
- data/lib/predicated/test/xml_conversion_test.rb +34 -0
- data/lib/predicated/test_integration/arel_integration_test.rb +52 -0
- data/lib/predicated/test_integration/canonical_integration_cases.rb +66 -0
- data/lib/predicated/test_integration/schema.xml +83 -0
- data/lib/predicated/test_integration/solr_integration_test.rb +71 -0
- data/lib/predicated/test_integration/sqlite_db +0 -0
- data/lib/predicated/test_integration/suite.rb +2 -0
- data/lib/predicated/test_integration/usage_test.rb +252 -0
- data/lib/wrong.rb +3 -1
- data/lib/wrong/adapters/test_unit.rb +1 -3
- data/lib/wrong/assert.rb +81 -24
- data/lib/wrong/chunk.rb +145 -0
- data/lib/wrong/message/string_diff.rb +2 -4
- data/lib/wrong/message/test_context.rb +2 -2
- data/lib/wrong/version.rb +2 -2
- data/test/adapters/minitest_test.rb +16 -9
- data/test/adapters/test_unit_test.rb +1 -1
- data/test/assert_test.rb +90 -0
- data/test/catch_raise_test.rb +2 -2
- data/test/chunk_test.rb +236 -0
- data/test/failures_test.rb +109 -74
- data/test/message/array_diff_test.rb +35 -19
- data/test/message/string_diff_test.rb +39 -15
- data/test/message/test_context_text.rb +2 -2
- data/test/test_helper.rb +25 -7
- metadata +86 -33
- data/test/basic_assert_test.rb +0 -38
data/lib/wrong.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require "wrong/assert"
|
2
2
|
|
3
3
|
class Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
6
4
|
include Wrong::Assert
|
7
5
|
|
8
6
|
Wrong::Assert.disable_existing_assert_methods(self)
|
@@ -10,4 +8,4 @@ class Test::Unit::TestCase
|
|
10
8
|
def failure_class
|
11
9
|
Test::Unit::AssertionFailedError
|
12
10
|
end
|
13
|
-
end
|
11
|
+
end
|
data/lib/wrong/assert.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "predicated/predicate"
|
2
2
|
require "predicated/from/callable_object"
|
3
3
|
require "predicated/to/sentence"
|
4
|
+
require "wrong/chunk"
|
4
5
|
|
5
6
|
#see http://yehudakatz.com/2009/01/18/other-ways-to-wrap-a-method/
|
6
7
|
class Module
|
@@ -12,28 +13,20 @@ end
|
|
12
13
|
|
13
14
|
module Wrong
|
14
15
|
module Assert
|
15
|
-
|
16
|
-
class AssertionFailedError < RuntimeError;
|
17
|
-
|
16
|
+
|
17
|
+
class AssertionFailedError < RuntimeError;
|
18
|
+
end
|
19
|
+
|
18
20
|
def failure_class
|
19
21
|
AssertionFailedError
|
20
22
|
end
|
21
|
-
|
22
|
-
def assert(&block)
|
23
|
-
unless block.call
|
24
|
-
raise failure_class.new(
|
25
|
-
failure_message(:assert, block, Predicated::Predicate.from_callable_object(block))
|
26
|
-
)
|
27
|
-
end
|
28
|
-
end
|
29
23
|
|
24
|
+
def assert(explanation = nil, depth = 0, &block)
|
25
|
+
aver(:assert, explanation, depth, &block)
|
26
|
+
end
|
30
27
|
|
31
|
-
def deny(&block)
|
32
|
-
|
33
|
-
raise failure_class.new(
|
34
|
-
failure_message(:deny, block, Predicated::Predicate.from_callable_object(block))
|
35
|
-
)
|
36
|
-
end
|
28
|
+
def deny(explanation = nil, depth = 0, &block)
|
29
|
+
aver(:deny, explanation, depth, &block)
|
37
30
|
end
|
38
31
|
|
39
32
|
def catch_raise
|
@@ -43,25 +36,89 @@ module Wrong
|
|
43
36
|
rescue Exception, RuntimeError => e
|
44
37
|
error = e
|
45
38
|
end
|
46
|
-
|
39
|
+
error
|
47
40
|
end
|
48
|
-
|
41
|
+
|
49
42
|
overridable do
|
50
43
|
def failure_message(method_sym, block, predicate)
|
51
44
|
method_sym == :deny ? predicate.to_sentence : predicate.to_negative_sentence
|
52
45
|
end
|
53
46
|
end
|
54
|
-
|
47
|
+
|
55
48
|
def self.disable_existing_assert_methods(the_class)
|
56
49
|
(the_class.public_instance_methods.
|
57
|
-
|
50
|
+
map { |m| m.to_s }.
|
51
|
+
select { |m| m =~ /^assert/ } - ["assert"]).each do |old_assert_method|
|
58
52
|
the_class.class_eval(%{
|
59
53
|
def #{old_assert_method}(*args)
|
60
54
|
raise "#{old_assert_method} has been disabled. When you use Wrong, it overrides 'assert', which most test frameworks have defined, and use internally."
|
61
55
|
end
|
62
56
|
})
|
63
57
|
end
|
64
|
-
end
|
65
|
-
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def indent(indent)
|
63
|
+
(" " * indent)
|
64
|
+
end
|
65
|
+
|
66
|
+
def newline(indent)
|
67
|
+
"\n" + self.indent(indent)
|
68
|
+
end
|
69
|
+
|
70
|
+
# todo: test
|
71
|
+
def details(block, chunk)
|
72
|
+
# p chunk.claim
|
73
|
+
details = ""
|
74
|
+
parts = chunk.parts
|
75
|
+
parts.shift # remove the first part, since it's the same as the code
|
76
|
+
if parts.size > 0
|
77
|
+
details = "\n"
|
78
|
+
parts.each do |part|
|
79
|
+
begin
|
80
|
+
value = eval(part, block.binding)
|
81
|
+
unless part == value.inspect
|
82
|
+
if part =~ /\n/m
|
83
|
+
part.gsub!(/\n/, newline(2))
|
84
|
+
part += newline(3)
|
85
|
+
end
|
86
|
+
value = value.inspect.gsub("\n", "\n#{indent(3)}")
|
87
|
+
details << indent(2) << "#{part} is #{value}\n"
|
88
|
+
end
|
89
|
+
rescue Exception => e
|
90
|
+
details << indent(2) << "#{part} raises #{e.class}: #{e.message.gsub("\n", "\n#{indent(3)}")}\n"
|
91
|
+
if false
|
92
|
+
puts "#{e.class}: #{e.message} evaluating #{part.inspect}"
|
93
|
+
puts "\t" + e.backtrace.join("\n\t")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
details
|
99
|
+
end
|
100
|
+
|
101
|
+
def aver(valence, explanation = nil, depth = 0, &block)
|
102
|
+
value = block.call
|
103
|
+
value = !value if valence == :deny
|
104
|
+
unless value
|
105
|
+
chunk = Wrong::Chunk.from_block(block, depth + 2)
|
106
|
+
code = chunk.code
|
107
|
+
predicate = begin
|
108
|
+
Predicated::Predicate.from_ruby_code_string(code, block.binding)
|
109
|
+
rescue Predicated::Predicate::DontKnowWhatToDoWithThisSexpError
|
110
|
+
nil
|
111
|
+
rescue Exception
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
message = ""
|
115
|
+
message << "#{explanation}: " if explanation
|
116
|
+
message << "#{valence == :deny ? "Didn't expect" : "Expected"} #{code}, but"
|
117
|
+
message << " #{failure_message(valence, block, predicate)}" if predicate
|
118
|
+
message << details(block, chunk)
|
119
|
+
raise failure_class.new(message)
|
120
|
+
end
|
121
|
+
end
|
66
122
|
end
|
67
|
-
|
123
|
+
|
124
|
+
end
|
data/lib/wrong/chunk.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'ruby_parser'
|
2
|
+
require 'ruby2ruby'
|
3
|
+
|
4
|
+
module Wrong
|
5
|
+
class Chunk
|
6
|
+
def self.from_block(block, depth = 0)
|
7
|
+
file, line = if block.to_proc.respond_to? :source_location
|
8
|
+
# in Ruby 1.9, it reads the source location from the block
|
9
|
+
block.to_proc.source_location
|
10
|
+
else
|
11
|
+
# in Ruby 1.8, it reads the source location from the call stack
|
12
|
+
caller[depth].split(":")
|
13
|
+
end
|
14
|
+
new(file, line)
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :file, :line_number
|
18
|
+
|
19
|
+
# line parameter is 1-based
|
20
|
+
def initialize(file, line_number)
|
21
|
+
@file = file
|
22
|
+
@line_number = line_number.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def line_index
|
26
|
+
@line_number - 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def location
|
30
|
+
"#{@file}:#{@line_number}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Algorithm:
|
34
|
+
# try to parse the starting line
|
35
|
+
# if it parses OK, then we're done!
|
36
|
+
# if not, then glom the next line and try again
|
37
|
+
# repeat until it parses or we're out of lines
|
38
|
+
def parse
|
39
|
+
lines = File.read(@file).split("\n")
|
40
|
+
@parser ||= RubyParser.new
|
41
|
+
@chunk = nil
|
42
|
+
c = 0
|
43
|
+
@sexp = nil
|
44
|
+
while @sexp.nil? && line_index + c < lines.size
|
45
|
+
begin
|
46
|
+
@chunk = lines[line_index..line_index+c].join("\n")
|
47
|
+
@sexp = @parser.parse(@chunk)
|
48
|
+
rescue Racc::ParseError => e
|
49
|
+
# loop and try again
|
50
|
+
c += 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@sexp
|
54
|
+
end
|
55
|
+
|
56
|
+
# The claim is the part of the assertion inside the curly braces.
|
57
|
+
# E.g. for "assert { x == 5 }" the claim is "x == 5"
|
58
|
+
def claim
|
59
|
+
parse()
|
60
|
+
|
61
|
+
if @sexp.nil?
|
62
|
+
raise "Could not parse #{location}"
|
63
|
+
else
|
64
|
+
assertion = @sexp.assertion
|
65
|
+
statement = assertion && assertion[3]
|
66
|
+
if statement.nil?
|
67
|
+
@sexp
|
68
|
+
# raise "Could not find assertion in #{location}\n\t#{@chunk.strip}\n\t#{@sexp}"
|
69
|
+
else
|
70
|
+
statement
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def code
|
76
|
+
self.claim.to_ruby
|
77
|
+
end
|
78
|
+
|
79
|
+
def parts(sexp = nil)
|
80
|
+
if sexp.nil?
|
81
|
+
parts(self.claim).compact.uniq
|
82
|
+
else
|
83
|
+
# todo: extract into Sexp, once I have more unit tests
|
84
|
+
parts_list = []
|
85
|
+
begin
|
86
|
+
unless sexp.first == :arglist
|
87
|
+
code = sexp.to_ruby.strip
|
88
|
+
parts_list << code unless code == "" || parts_list.include?(code)
|
89
|
+
end
|
90
|
+
rescue => e
|
91
|
+
puts "#{e.class}: #{e.message}"
|
92
|
+
puts e.backtrace.join("\n")
|
93
|
+
end
|
94
|
+
sexp.each do |sub|
|
95
|
+
if sub.is_a?(Sexp)
|
96
|
+
parts_list += parts(sub)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
parts_list
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
# todo: move to separate monkey patch file
|
108
|
+
class Sexp < Array
|
109
|
+
def doop
|
110
|
+
Marshal.load(Marshal.dump(self))
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_ruby
|
114
|
+
d = self.doop
|
115
|
+
x = Ruby2Ruby.new.process(d)
|
116
|
+
x
|
117
|
+
end
|
118
|
+
|
119
|
+
def assertion?
|
120
|
+
self.is_a? Sexp and
|
121
|
+
self[0] == :iter and
|
122
|
+
self[1].is_a? Sexp and
|
123
|
+
self[1][0] == :call and
|
124
|
+
[:assert, :deny].include? self[1][2] # todo: allow aliases for assert (e.g. "is")
|
125
|
+
end
|
126
|
+
|
127
|
+
def assertion
|
128
|
+
sexp = self
|
129
|
+
assertion = if sexp.assertion?
|
130
|
+
sexp
|
131
|
+
else
|
132
|
+
# todo: extract into sexp
|
133
|
+
nested_assertions.first
|
134
|
+
end
|
135
|
+
assertion
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
def nested_assertions
|
140
|
+
assertions = []
|
141
|
+
self.each_of_type(:iter) { |sexp| assertions << sexp if sexp.assertion? }
|
142
|
+
assertions
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/lib/wrong/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Wrong
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.2.0" unless defined?(Wrong::VERSION)
|
3
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require "test/test_helper"
|
1
|
+
require "./test/test_helper"
|
2
2
|
|
3
3
|
require "minitest/spec"
|
4
4
|
require "minitest/unit"
|
@@ -6,18 +6,25 @@ require "minitest/unit"
|
|
6
6
|
require "wrong/assert"
|
7
7
|
require "wrong/adapters/minitest"
|
8
8
|
|
9
|
-
|
9
|
+
regarding "basic assert features" do
|
10
10
|
|
11
|
-
|
11
|
+
regarding "pass/fail basics" do
|
12
12
|
test "disables other assert methods" do
|
13
13
|
test_case_instance = Class.new(MiniTest::Unit::TestCase).new("x")
|
14
|
-
assert{
|
15
|
-
|
14
|
+
assert{
|
15
|
+
catch_raise{
|
16
|
+
test_case_instance.assert_equal(1,1)
|
17
|
+
}.message.include?("has been disabled")
|
18
|
+
}
|
16
19
|
end
|
17
20
|
|
18
21
|
test "raises minitest assertion failures" do
|
19
22
|
test_case_instance = Class.new(MiniTest::Unit::TestCase).new("x")
|
20
|
-
assert{
|
23
|
+
assert{
|
24
|
+
catch_raise{
|
25
|
+
test_case_instance.assert{1==2}
|
26
|
+
}.is_a?(MiniTest::Assertion)
|
27
|
+
}
|
21
28
|
end
|
22
29
|
|
23
30
|
test "assert and deny are available to minitest tests" do
|
@@ -42,12 +49,12 @@ apropos "basic assert features" do
|
|
42
49
|
end
|
43
50
|
|
44
51
|
msg = catch_raise{MyFailingAssertTest.new.test_fail}.message
|
45
|
-
assert{ "1 is not equal to 2"
|
52
|
+
assert{ msg.include?("1 is not equal to 2") }
|
46
53
|
|
47
54
|
msg = catch_raise{MyFailingDenyTest.new.test_fail}.message
|
48
|
-
assert{ "1 is equal to 1"
|
55
|
+
assert{ msg.include?("1 is equal to 1") }
|
49
56
|
end
|
50
57
|
|
51
58
|
end
|
52
59
|
|
53
|
-
end
|
60
|
+
end
|
data/test/assert_test.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require "./test/test_helper"
|
2
|
+
require "wrong/assert"
|
3
|
+
|
4
|
+
regarding "basic assert features" do
|
5
|
+
|
6
|
+
before do
|
7
|
+
@m = Module.new do
|
8
|
+
extend Wrong::Assert
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
regarding "pass/fail basics" do
|
13
|
+
test "passes when the result is true. deny does the reverse" do
|
14
|
+
@m.assert { true }
|
15
|
+
@m.assert { 1==1 }
|
16
|
+
|
17
|
+
@m.deny { false }
|
18
|
+
@m.deny { 1==2 }
|
19
|
+
end
|
20
|
+
|
21
|
+
test "fails when result is false. deny does the reverse" do
|
22
|
+
get_error {
|
23
|
+
@m.assert { false }
|
24
|
+
} || fail
|
25
|
+
get_error {
|
26
|
+
@m.assert { 1==2 }
|
27
|
+
} || fail
|
28
|
+
|
29
|
+
get_error {
|
30
|
+
@m.deny { true }
|
31
|
+
} || fail
|
32
|
+
get_error {
|
33
|
+
@m.deny { 1==1 }
|
34
|
+
} || fail
|
35
|
+
end
|
36
|
+
|
37
|
+
class MyError < StandardError;
|
38
|
+
end
|
39
|
+
|
40
|
+
test "both deny and assert fail when an error is thrown. bubbles up the error." do
|
41
|
+
assert_raises(MyError) { @m.assert { raise MyError.new } }
|
42
|
+
assert_raises(MyError) { @m.deny { raise MyError.new } }
|
43
|
+
end
|
44
|
+
|
45
|
+
test "assert takes an optional explanation" do
|
46
|
+
e = get_error {
|
47
|
+
sky = "green"
|
48
|
+
@m.assert("the sky should be blue") { sky == "blue" }
|
49
|
+
}
|
50
|
+
assert e.message =~ /^the sky should be blue: /
|
51
|
+
end
|
52
|
+
|
53
|
+
test "deny takes an optional explanation" do
|
54
|
+
e = get_error {
|
55
|
+
sky = "blue"
|
56
|
+
@m.deny("the sky should not be blue") { sky == "blue" }
|
57
|
+
}
|
58
|
+
assert e.message =~ /^the sky should not be blue: /
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
regarding "advanced assert features" do
|
65
|
+
include Wrong::Assert
|
66
|
+
|
67
|
+
def assert_many(*procs)
|
68
|
+
failures = []
|
69
|
+
procs.each do |proc|
|
70
|
+
begin
|
71
|
+
assert(nil, 3, &proc)
|
72
|
+
rescue => e
|
73
|
+
failures << e
|
74
|
+
end
|
75
|
+
end
|
76
|
+
assert { failures.empty? }
|
77
|
+
end
|
78
|
+
|
79
|
+
test "it's possible (but not advisable) to define procs in different places from the assert call" do
|
80
|
+
x = 10
|
81
|
+
e = catch_raise do
|
82
|
+
assert_many(lambda { x == 10 })
|
83
|
+
assert_many(lambda { x > 10 })
|
84
|
+
end
|
85
|
+
|
86
|
+
assert { e.message =~ /^Expected failures.empty\?/ }
|
87
|
+
assert { e.message =~ /x is 10/ }
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|