matest 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|