matest 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +65 -1
- data/lib/matest.rb +44 -13
- data/lib/matest/example_block.rb +50 -0
- data/lib/matest/spec_printer.rb +78 -5
- data/lib/matest/version.rb +1 -1
- data/matest.gemspec +2 -0
- data/spec/matest_specs/aliases_spec.rb +18 -18
- data/spec/matest_specs/printing_assertion_spec.rb +35 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92cbe87cbc8b53490086b363cba0466107e906a5
|
4
|
+
data.tar.gz: 545125006ca70b8c13ca8b5632183e3832731898
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d743771c606770155e344f1bdc5c13118a386bdbc1e28ced772ff97108bbc27482772e5e3a69fec9d9c764d2ed637915cf48974aa48ca45f29d74b22dd6567ed
|
7
|
+
data.tar.gz: 686cbd8a5ef598ee08564e4c27ce575895b5f30ed6a044f2dfdb79199ef76876cff2da3b133f168a16a5f6f9c48fc9729f04f98cbe2c5aa70d666b3f8e84e7c8
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# Matest
|
2
|
+
[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/iachettifederico/matest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
2
3
|
|
3
4
|
Tests Gasoleros (Very cheap tests)
|
4
5
|
|
@@ -59,6 +60,29 @@ scope "a description" do
|
|
59
60
|
end
|
60
61
|
```
|
61
62
|
|
63
|
+
## Constraints
|
64
|
+
|
65
|
+
A couple of constraints must b taken into account, and this is extremely important.
|
66
|
+
|
67
|
+
### The assertion MUST return a boolean
|
68
|
+
|
69
|
+
The assertion is the last statement of the block and it must return either true or false.
|
70
|
+
This is important, because the assertion will be evaluated and it will provide the status o the test.
|
71
|
+
|
72
|
+
### Assertion expression
|
73
|
+
|
74
|
+
The assertion can be any expression that returns a boolean value, BUT IT CANNOT CONTAIN LOCAL VARIABLES.
|
75
|
+
If the assertion contains a local variable and it fails, the code that explains it bit by bit will throw an error.
|
76
|
+
|
77
|
+
### The assertion MUST be idempotent
|
78
|
+
|
79
|
+
Once the code is run, the state of the block gets saved.
|
80
|
+
If the test fails, the assertion will be re-run a couple of times.
|
81
|
+
So if you are popping the last element of an array inside the assertion, each time it gets run, you'll be popping the next element on the chain.
|
82
|
+
The reason for this is that Matest will show you the result of each part of the expression and for that, it needs to re-run each part.
|
83
|
+
|
84
|
+
Removing this constraint is a high priority on my todo list. But I'm considering the threadoffs of doing it. I'll be happy to pair or discuss on this matter.
|
85
|
+
|
62
86
|
## Raising Errors
|
63
87
|
|
64
88
|
If your test raises an error during the run, youll get an `ERROR` status and you'll see the backtrace.
|
@@ -133,7 +157,47 @@ end
|
|
133
157
|
|
134
158
|
## The output
|
135
159
|
|
136
|
-
In case the test fails,
|
160
|
+
In case the test fails, you'll get an extensive explanation about why.
|
161
|
+
|
162
|
+
To show a trivial example:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
scope "hola" do
|
166
|
+
let(:three) { 3 }
|
167
|
+
spec "chau" do
|
168
|
+
one = 2
|
169
|
+
two = 2
|
170
|
+
@one_plus_two_plus_three = one + two + three
|
171
|
+
@res = 3
|
172
|
+
|
173
|
+
@one_plus_two_plus_three.to_i == @res
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
It fails and the output will be
|
179
|
+
|
180
|
+
```
|
181
|
+
F
|
182
|
+
|
183
|
+
### Messages ###
|
184
|
+
|
185
|
+
[FAILING] chau
|
186
|
+
Location:
|
187
|
+
spec/matest_specs/printing_assertion_spec.rb:3:
|
188
|
+
Assertion:
|
189
|
+
@one_plus_two_plus_three.to_i == @res
|
190
|
+
Variables:
|
191
|
+
@one_plus_two_plus_three: 7
|
192
|
+
@res: 3
|
193
|
+
Lets:
|
194
|
+
three: 3
|
195
|
+
Explanation:
|
196
|
+
"@one_plus_two_plus_three.to_i == @res" =>
|
197
|
+
false
|
198
|
+
"@one_plus_two_plus_three.to_i" =>
|
199
|
+
7
|
200
|
+
```
|
137
201
|
|
138
202
|
## Matchers
|
139
203
|
|
data/lib/matest.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "matest/version"
|
2
|
+
require "matest/example_block"
|
2
3
|
require "matest/spec_status"
|
3
4
|
require "matest/spec_printer"
|
4
5
|
|
@@ -38,35 +39,65 @@ module Matest
|
|
38
39
|
class SkipMe; end
|
39
40
|
|
40
41
|
class Example
|
41
|
-
def example_block
|
42
|
-
@__example_block
|
43
|
-
end
|
44
|
-
def description
|
45
|
-
@__description
|
46
|
-
end
|
47
|
-
|
48
42
|
def initialize(example_block, description, lets)
|
49
|
-
@
|
50
|
-
@
|
43
|
+
@example_block__for_internal_use = ExampleBlock.new(example_block)
|
44
|
+
@description__for_internal_use = description
|
51
45
|
lets.each do |let|
|
52
46
|
self.class.let(let.var_name, &let.block)
|
53
47
|
send(let.var_name) if let.bang
|
54
48
|
end
|
55
49
|
end
|
56
50
|
|
51
|
+
def example_block
|
52
|
+
@example_block__for_internal_use
|
53
|
+
end
|
54
|
+
|
55
|
+
def description
|
56
|
+
@description__for_internal_use
|
57
|
+
end
|
58
|
+
|
57
59
|
def call
|
58
|
-
instance_eval(&example_block)
|
60
|
+
instance_eval(&example_block.block)
|
59
61
|
end
|
60
62
|
|
61
63
|
def self.let(var_name, &block)
|
62
64
|
define_method(var_name) do
|
63
|
-
instance_variable_set(:"@#{var_name}", block.call)
|
65
|
+
instance_variable_set(:"@#{var_name}__from_let", block.call)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.local_var(var_name)
|
70
|
+
define_method(var_name) do
|
71
|
+
instance_variable_get(:"@#{var_name}")
|
72
|
+
end
|
73
|
+
define_method("#{var_name}=") do |value|
|
74
|
+
instance_variable_set(:"@#{var_name}", value)
|
64
75
|
end
|
65
76
|
end
|
66
77
|
|
67
|
-
def
|
68
|
-
instance_variables.reject {|
|
78
|
+
def track_variables
|
79
|
+
instance_variables.reject {|var|
|
80
|
+
var.to_s =~ /__for_internal_use\Z/ || var.to_s =~ /__from_let\Z/
|
81
|
+
}.map {|var| [var, instance_variable_get(var)] }
|
69
82
|
end
|
83
|
+
|
84
|
+
def track_lets
|
85
|
+
instance_variables.select {|var|
|
86
|
+
var.to_s =~ /__from_let\Z/
|
87
|
+
}.map {|var|
|
88
|
+
name = var.to_s
|
89
|
+
name["__from_let"] = ""
|
90
|
+
name[0] = ""
|
91
|
+
[name, instance_variable_get(var)]
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def without_block
|
96
|
+
the_new = self.clone
|
97
|
+
the_new.instance_variable_set(:@example_block__for_internal_use, nil)
|
98
|
+
the_new
|
99
|
+
end
|
100
|
+
|
70
101
|
end
|
71
102
|
|
72
103
|
class Let
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "ripper"
|
2
|
+
require "sorcerer"
|
3
|
+
|
4
|
+
class ExampleBlock
|
5
|
+
attr_reader :block
|
6
|
+
attr_reader :code
|
7
|
+
attr_reader :sexp
|
8
|
+
attr_reader :assertion
|
9
|
+
attr_reader :assertion_sexp
|
10
|
+
|
11
|
+
def initialize(block)
|
12
|
+
@block = block
|
13
|
+
|
14
|
+
@code = generate_code
|
15
|
+
|
16
|
+
@sexp = Ripper::SexpBuilder.new(code).parse.last
|
17
|
+
@assertion_sexp = @sexp.last
|
18
|
+
@assertion = Sorcerer.source(assertion_sexp)
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
block.call
|
23
|
+
end
|
24
|
+
|
25
|
+
def source_location
|
26
|
+
block.source_location
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def generate_code
|
32
|
+
file = File.open(block.source_location.first)
|
33
|
+
source = file.read
|
34
|
+
lines = source.each_line.to_a
|
35
|
+
|
36
|
+
lineno = block.source_location.last
|
37
|
+
|
38
|
+
current_line = lineno-1
|
39
|
+
valid_lines = [lines[current_line]]
|
40
|
+
|
41
|
+
valid_lines
|
42
|
+
|
43
|
+
until Ripper::SexpBuilder.new(valid_lines.join("\n")).parse
|
44
|
+
current_line += 1
|
45
|
+
valid_lines << lines[current_line]
|
46
|
+
end
|
47
|
+
code_array = valid_lines[1..-2]
|
48
|
+
code_array.join
|
49
|
+
end
|
50
|
+
end
|
data/lib/matest/spec_printer.rb
CHANGED
@@ -1,7 +1,52 @@
|
|
1
1
|
module Matest
|
2
|
+
class EvalErr
|
3
|
+
def initialize(str)
|
4
|
+
@string = str
|
5
|
+
end
|
6
|
+
def size
|
7
|
+
inspect.size
|
8
|
+
end
|
9
|
+
def to_s
|
10
|
+
@string
|
11
|
+
end
|
12
|
+
def inspect
|
13
|
+
@string
|
14
|
+
end
|
15
|
+
end
|
2
16
|
|
3
|
-
class
|
17
|
+
class Evaluator
|
18
|
+
def initialize(example, block)
|
19
|
+
# @example = Marshal.load( Marshal.dump(example) )
|
20
|
+
@example = example
|
21
|
+
@block = block
|
22
|
+
end
|
23
|
+
|
24
|
+
def eval_string(exp_string)
|
25
|
+
limit_length(eval_in_context(exp_string).inspect)
|
26
|
+
rescue StandardError => ex
|
27
|
+
EvalErr.new("#{ex.class}: #{ex.message}")
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
MAX_INSPECT_SIZE = 2000
|
33
|
+
|
34
|
+
def limit_length(string)
|
35
|
+
if string.size > MAX_INSPECT_SIZE
|
36
|
+
string[0..MAX_INSPECT_SIZE] + " (...truncated...)"
|
37
|
+
else
|
38
|
+
string
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def eval_in_context(exp_string)
|
43
|
+
exp_proc = "proc { #{exp_string} }"
|
44
|
+
blk = eval(exp_proc, @block.binding)
|
45
|
+
@example.instance_eval(&blk)
|
46
|
+
end
|
47
|
+
end
|
4
48
|
|
49
|
+
class SpecPrinter
|
5
50
|
def print(runner)
|
6
51
|
puts "\n\n### Messages ###"
|
7
52
|
|
@@ -16,15 +61,27 @@ module Matest
|
|
16
61
|
runner.info[:num_specs][status.name] ||= 0
|
17
62
|
runner.info[:num_specs][status.name] += 1
|
18
63
|
|
19
|
-
if status.is_a?(Matest::SpecPassed)
|
20
|
-
else
|
64
|
+
if !status.is_a?(Matest::SpecPassed)
|
21
65
|
puts "\n[#{status.name}] #{status.description}"
|
66
|
+
puts "Location:\n #{status.location}:"
|
67
|
+
|
22
68
|
if status.is_a?(Matest::SpecFailed)
|
23
69
|
runner.info[:success] = false
|
70
|
+
puts "Assertion: \n #{status.example.example_block.assertion}"
|
24
71
|
puts "Variables: "
|
25
|
-
status.example.
|
72
|
+
status.example.track_variables.each do |var, val|
|
26
73
|
puts " #{var}: #{val.inspect}"
|
27
74
|
end
|
75
|
+
puts "Lets: "
|
76
|
+
status.example.track_lets.each do |var, val|
|
77
|
+
puts " #{var}: #{val.inspect}"
|
78
|
+
end
|
79
|
+
|
80
|
+
puts "Explanation:"
|
81
|
+
subexpressions = Sorcerer.subexpressions(status.example.example_block.assertion_sexp).reverse.uniq.reverse
|
82
|
+
subexpressions.each do |code|
|
83
|
+
print_subexpression(code, status)
|
84
|
+
end
|
28
85
|
end
|
29
86
|
if status.is_a?(Matest::NotANaturalAssertion)
|
30
87
|
runner.info[:success] = false
|
@@ -38,10 +95,26 @@ module Matest
|
|
38
95
|
end
|
39
96
|
|
40
97
|
end
|
41
|
-
puts " #{status.location}:"
|
42
98
|
end
|
43
99
|
end
|
44
100
|
end
|
45
101
|
end
|
102
|
+
|
103
|
+
def print_subexpression(code, status)
|
104
|
+
result = Evaluator.new(status.example, status.example.example_block.block).eval_string(code)
|
105
|
+
if result.class != Matest::EvalErr
|
106
|
+
puts <<-CODE
|
107
|
+
"#{code}" =>
|
108
|
+
#{result}
|
109
|
+
CODE
|
110
|
+
else
|
111
|
+
puts <<-CODE
|
112
|
+
The assertion couldn't be explained.
|
113
|
+
The error message was:
|
114
|
+
#{result}
|
115
|
+
Make sure you are not calling any local vaiables on your code assertion.
|
116
|
+
CODE
|
117
|
+
end
|
118
|
+
end
|
46
119
|
end
|
47
120
|
end
|
data/lib/matest/version.rb
CHANGED
data/matest.gemspec
CHANGED
@@ -1,65 +1,65 @@
|
|
1
1
|
scope do
|
2
2
|
spec { true }
|
3
|
-
xspec {
|
3
|
+
xspec { false }
|
4
4
|
|
5
5
|
example { true }
|
6
|
-
xexample {
|
6
|
+
xexample { false }
|
7
7
|
|
8
8
|
it { true }
|
9
|
-
xit {
|
9
|
+
xit { false }
|
10
10
|
end
|
11
11
|
|
12
12
|
describe do
|
13
13
|
spec { true }
|
14
|
-
xspec {
|
14
|
+
xspec { false }
|
15
15
|
|
16
16
|
example { true }
|
17
|
-
xexample {
|
17
|
+
xexample { false }
|
18
18
|
|
19
19
|
it { true }
|
20
|
-
xit {
|
20
|
+
xit { false }
|
21
21
|
end
|
22
22
|
|
23
23
|
context do
|
24
24
|
spec { true }
|
25
|
-
xspec {
|
25
|
+
xspec { false }
|
26
26
|
|
27
27
|
example { true }
|
28
|
-
xexample {
|
28
|
+
xexample { false }
|
29
29
|
|
30
30
|
it { true }
|
31
|
-
xit {
|
31
|
+
xit { false }
|
32
32
|
end
|
33
33
|
|
34
34
|
xscope do
|
35
35
|
spec { true }
|
36
|
-
xspec {
|
36
|
+
xspec { false }
|
37
37
|
|
38
38
|
example { true }
|
39
|
-
xexample {
|
39
|
+
xexample { false }
|
40
40
|
|
41
41
|
it { true }
|
42
|
-
xit {
|
42
|
+
xit { false }
|
43
43
|
end
|
44
44
|
|
45
45
|
xdescribe do
|
46
46
|
spec { true }
|
47
|
-
xspec {
|
47
|
+
xspec { false }
|
48
48
|
|
49
49
|
example { true }
|
50
|
-
xexample {
|
50
|
+
xexample { false }
|
51
51
|
|
52
52
|
it { true }
|
53
|
-
xit {
|
53
|
+
xit { false }
|
54
54
|
end
|
55
55
|
|
56
56
|
xcontext do
|
57
57
|
spec { true }
|
58
|
-
xspec {
|
58
|
+
xspec { false }
|
59
59
|
|
60
60
|
example { true }
|
61
|
-
xexample {
|
61
|
+
xexample { false }
|
62
62
|
|
63
63
|
it { true }
|
64
|
-
xit {
|
64
|
+
xit { false }
|
65
65
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
scope "hola" do
|
2
|
+
let(:three) { 3 }
|
3
|
+
xspec "chau" do
|
4
|
+
one = 2
|
5
|
+
two = 2
|
6
|
+
|
7
|
+
@one_plus_two_plus_three = one + two + three
|
8
|
+
@res = 3
|
9
|
+
@one_plus_two_plus_three.to_i == @res
|
10
|
+
end
|
11
|
+
|
12
|
+
xspec "again?" do
|
13
|
+
@arr = %w[a b c d e]
|
14
|
+
|
15
|
+
@arr.pop == "k"
|
16
|
+
end
|
17
|
+
|
18
|
+
spec do
|
19
|
+
a = 4
|
20
|
+
|
21
|
+
a == 5
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @one_plus_two == @res.to_i => false
|
26
|
+
# @one_plus_two => 4
|
27
|
+
# @res => "3"
|
28
|
+
# @res.to_i => 3
|
29
|
+
|
30
|
+
|
31
|
+
## ON EXAMPLE
|
32
|
+
# on call:
|
33
|
+
# - run the spec without the assertion
|
34
|
+
# - save the state
|
35
|
+
# - and then run the assertion.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: matest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Federico Iachetti
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sorcerer
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.0.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.2
|
41
55
|
description: Natural assertions test suite.
|
42
56
|
email:
|
43
57
|
- iachetti.federico@gmail.com
|
@@ -53,6 +67,7 @@ files:
|
|
53
67
|
- Rakefile
|
54
68
|
- bin/matest
|
55
69
|
- lib/matest.rb
|
70
|
+
- lib/matest/example_block.rb
|
56
71
|
- lib/matest/spec_printer.rb
|
57
72
|
- lib/matest/spec_status.rb
|
58
73
|
- lib/matest/version.rb
|
@@ -63,6 +78,7 @@ files:
|
|
63
78
|
- spec/matest_specs/matchers_spec.rb
|
64
79
|
- spec/matest_specs/nested_scopes_spec.rb
|
65
80
|
- spec/matest_specs/passing_spec.rb
|
81
|
+
- spec/matest_specs/printing_assertion_spec.rb
|
66
82
|
- spec/matest_specs/scope_spec.rb
|
67
83
|
- spec/matest_specs/scoping_stuff_spec.rb
|
68
84
|
- spec/matest_specs/spec_helper.rb
|
@@ -87,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
87
103
|
version: '0'
|
88
104
|
requirements: []
|
89
105
|
rubyforge_project:
|
90
|
-
rubygems_version: 2.4.
|
106
|
+
rubygems_version: 2.4.5
|
91
107
|
signing_key:
|
92
108
|
specification_version: 4
|
93
109
|
summary: Tests gasoleros (cheap tests).
|
@@ -98,6 +114,7 @@ test_files:
|
|
98
114
|
- spec/matest_specs/matchers_spec.rb
|
99
115
|
- spec/matest_specs/nested_scopes_spec.rb
|
100
116
|
- spec/matest_specs/passing_spec.rb
|
117
|
+
- spec/matest_specs/printing_assertion_spec.rb
|
101
118
|
- spec/matest_specs/scope_spec.rb
|
102
119
|
- spec/matest_specs/scoping_stuff_spec.rb
|
103
120
|
- spec/matest_specs/spec_helper.rb
|