lax 0.2.1 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +52 -17
- data/lib/lax.rb +137 -3
- data/spec/acceptance/acceptance_spec.rb +32 -15
- data/spec/spec_helper.rb +0 -1
- data/spec/unit/lax_spec.rb +26 -8
- data/test/control.rb +67 -18
- metadata +2 -10
- data/lib/lax/rake_task.rb +0 -20
- data/lib/lax/source.rb +0 -288
- data/spec/unit/fixture/hashable_spec.rb +0 -27
- data/spec/unit/hook_spec.rb +0 -40
- data/spec/unit/target_spec.rb +0 -25
data/README.md
CHANGED
@@ -1,30 +1,65 @@
|
|
1
1
|
lax
|
2
2
|
===
|
3
|
-
Lax is an insouciant smidgen of a testing framework that tries to be an invisible wrapper around your ideas about how your code
|
3
|
+
Lax is an insouciant smidgen of a testing framework that tries hard to be an invisible wrapper around your ideas about how your code works.
|
4
4
|
```ruby
|
5
|
-
Lax.
|
6
|
-
let number: 1,
|
7
|
-
string: 'Hi There',
|
8
|
-
regexp:
|
9
|
-
|
10
|
-
number + 1 == 2
|
11
|
-
string.downcase =~ regexp
|
5
|
+
Lax.scope do
|
6
|
+
let number: 1, # let defines targets that are appropriately scoped and
|
7
|
+
string: 'Hi There', # re-instantiated for each assertion block.
|
8
|
+
regexp: lazy{ /the/ } # <- lazy evaluation
|
12
9
|
|
13
10
|
assert do
|
14
|
-
|
15
|
-
|
11
|
+
that number + 1 == 2, # these assertions can pass or fail independently
|
12
|
+
string.downcase =~ regexp
|
13
|
+
|
14
|
+
that(regexp.hash).satisfies {|obj| obj.is_a? Fixnum} # you can also easily define your own conditions
|
16
15
|
end
|
17
|
-
end
|
18
16
|
|
19
|
-
|
17
|
+
string { upcase.strip == 'HI THERE' } # you can also make assertions like this
|
18
|
+
|
19
|
+
before { puts "hiii. i am a callback. i will be run once for each assertion block in my scope." }
|
20
|
+
|
21
|
+
scope do
|
22
|
+
before { puts "i will be run after the before callback in my enclosing scope." }
|
23
|
+
before { @this_ivar = 'is visible in assertion blocks' }
|
24
|
+
after { puts 'after callbacks also are a thing' }
|
25
|
+
|
26
|
+
|
27
|
+
def self.number_is_even # rspec-like 'shared examples' can be defined like this.
|
28
|
+
number { even? }
|
29
|
+
end
|
30
|
+
|
31
|
+
let number: 2,
|
32
|
+
nothing: regexp.match('ffff') # compound target
|
33
|
+
bool: true
|
34
|
+
|
35
|
+
number_is_even
|
36
|
+
|
37
|
+
assert 'documented tests' do # docstrings can optionally be attached to assertion groups.
|
38
|
+
that number - 1 == 1,
|
39
|
+
string.upcase == 'HI THERE', # string is still in scope
|
40
|
+
nothing == nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
scope do
|
45
|
+
let lax: self,
|
46
|
+
open_file: fix(read: "data\nof\nimmediate\ninterest ") # fixtures are also a thing
|
47
|
+
assert do
|
48
|
+
that lax.respond_to?(:bool) == false, # bool is out of scope
|
49
|
+
open_file.read.lines.map(&:strip).size == 4
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
20
53
|
|
54
|
+
Lax::Run[ Lax ] #=> green dots aww yeah
|
21
55
|
```
|
22
56
|
how come lax is neat
|
23
57
|
--------------------
|
24
|
-
* Minimal
|
25
|
-
* Easy-to-define custom matchers
|
26
|
-
*
|
27
|
-
*
|
58
|
+
* Minimal legalese.
|
59
|
+
* Easy-to-define custom matchers.
|
60
|
+
* Built-in Rake task generator for quick setup.
|
61
|
+
* Small & hackable is a design goal (< 150 SLOC with plenty of hooks for your code)
|
62
|
+
* Does not work by infecting the entire object system with its code - neighbourly!
|
28
63
|
|
29
64
|
how to make it do it
|
30
65
|
--------------------
|
@@ -32,7 +67,7 @@ how to make it do it
|
|
32
67
|
gem install lax
|
33
68
|
cd my/project/root
|
34
69
|
echo "require 'lax/rake_task'; Lax::RakeTask.new" >> Rakefile
|
35
|
-
# write
|
70
|
+
# write tests in yr test directory (defaults to 'test')
|
36
71
|
rake lax
|
37
72
|
```
|
38
73
|
|
data/lib/lax.rb
CHANGED
@@ -1,4 +1,138 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
class Lax < Array
|
2
|
+
VERSION = '0.2.3'
|
3
|
+
|
4
|
+
Lazy = Class.new Proc
|
5
|
+
Run = ->(lax=Lax, fin=->(n){n}) do
|
6
|
+
fin.call lax.select {|l| l.included_modules.include? AssertionGroup}.map(&:new).flatten
|
7
|
+
end
|
8
|
+
Assertion = Struct.new(:pass, :source, :doc, :exception)
|
9
|
+
|
10
|
+
@lings = []
|
11
|
+
extend Enumerable
|
12
|
+
def self.inherited(ling)
|
13
|
+
@lings << ling
|
14
|
+
ling.lings = []
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :lings, :src, :doc
|
19
|
+
|
20
|
+
def each(&b)
|
21
|
+
yield self
|
22
|
+
lings.each {|c| c.each(&b)}
|
23
|
+
end
|
24
|
+
|
25
|
+
def let(h)
|
26
|
+
h.each do |key, value|
|
27
|
+
val = (Lazy===value) ? value : lazy{value}
|
28
|
+
define_singleton_method(key) do |&b|
|
29
|
+
b ? assert { that val.call.instance_exec(&b) } : val.call
|
30
|
+
end
|
31
|
+
define_method(key) do
|
32
|
+
(@_memo||={}).has_key?(key)? @_memo[key] : @_memo[key] = val.call
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def lazy
|
38
|
+
Lazy.new
|
39
|
+
end
|
40
|
+
|
41
|
+
def fix(hash)
|
42
|
+
Struct.new(*hash.keys).new *hash.values
|
43
|
+
end
|
44
|
+
|
45
|
+
def before(&bef)
|
46
|
+
->(m) { define_method(:before) do |*a|
|
47
|
+
m.bind(self).call *a
|
48
|
+
instance_exec(*a, &bef)
|
49
|
+
end }.call instance_method :before
|
50
|
+
end
|
51
|
+
|
52
|
+
def assert(doc=nil,&spec)
|
53
|
+
scope do
|
54
|
+
@doc, @src = doc, spec.source_location
|
55
|
+
include AssertionGroup
|
56
|
+
before(&spec)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def scope(&b)
|
61
|
+
Class.new(self, &b)
|
62
|
+
end
|
63
|
+
|
64
|
+
def after(&aft)
|
65
|
+
->(m) { define_method(:after) do |*a|
|
66
|
+
instance_exec(*a, &aft)
|
67
|
+
m.bind(self).call *a
|
68
|
+
end }.call instance_method :after
|
69
|
+
end
|
70
|
+
|
71
|
+
def matcher(sym,&p)
|
72
|
+
define_method(sym) { satisfies &p}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def before(*a); end
|
77
|
+
def after(*a); end
|
78
|
+
|
79
|
+
module AssertionGroup
|
80
|
+
def fix(hash)
|
81
|
+
self.class.fix hash
|
82
|
+
end
|
83
|
+
|
84
|
+
def that(*as)
|
85
|
+
concat as.map {|a| assert a}
|
86
|
+
end
|
87
|
+
|
88
|
+
def satisfies
|
89
|
+
push assert yield pop
|
90
|
+
end
|
91
|
+
|
92
|
+
def assert(v,x=nil)
|
93
|
+
Assertion.new !!v, self.class.src, self.class.doc, x
|
94
|
+
end
|
95
|
+
|
96
|
+
def initialize
|
97
|
+
before
|
98
|
+
rescue => e
|
99
|
+
push assert(false, e)
|
100
|
+
ensure
|
101
|
+
after self
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
module RakeTask
|
106
|
+
def self.new(opts = {})
|
107
|
+
require 'rake'
|
108
|
+
extend Rake::DSL
|
109
|
+
o = {dir: :test, name: :lax}.merge(opts)
|
110
|
+
namespace o[:name] do
|
111
|
+
task(:load) { Dir["./#{o[:dir]}/**/*.rb"].each {|f| load f} }
|
112
|
+
task(:run) do
|
113
|
+
Lax.after &Output::DOTS
|
114
|
+
Run[ Lax, ->(n){Output::FAILURES[n]; Output::SUMMARY[n]} ]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
task o[:name] => ["#{o[:name]}:load", "#{o[:name]}:run"]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
module Output
|
122
|
+
DOTS = ->(tc) {
|
123
|
+
tc.each {|c| print c.pass ? "\x1b[32m.\x1b[0m" : "\x1b[31mX\x1b[0m"}
|
124
|
+
}
|
125
|
+
|
126
|
+
SUMMARY = ->(cs) {
|
127
|
+
puts "pass: #{cs.select(&:pass).size}\nfail: #{cs.reject(&:pass).size}"
|
128
|
+
}
|
129
|
+
FAILURES = ->(cs) {
|
130
|
+
puts
|
131
|
+
cs.reject(&:pass).each do |f|
|
132
|
+
puts " failure in #{f.doc || 'an undocumended node'} at #{f.source*?:}"
|
133
|
+
puts " raised #{f.exception.class} : #{f.exception.message}" if f.exception
|
134
|
+
end
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
4
138
|
|
@@ -2,25 +2,42 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Lax do
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
context 'a simple case' do
|
6
|
+
let :simple_case do
|
7
|
+
Lax.scope do
|
8
|
+
let number: 19,
|
9
|
+
string: 'asdf',
|
10
|
+
symbol: :symbol
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
assert do
|
13
|
+
that number.odd? == true,
|
14
|
+
number == 19,
|
15
|
+
string == 'asdf',
|
16
|
+
number == 20,
|
17
|
+
string.upcase == 'ASDF',
|
18
|
+
symbol.to_s == 'symbol'
|
19
|
+
end
|
20
|
+
end
|
17
21
|
end
|
22
|
+
|
23
|
+
subject { simple_case.lings.last.new }
|
24
|
+
it { should have(6).things }
|
25
|
+
specify { subject.select(&:pass).should have(5).things }
|
26
|
+
specify { subject.reject(&:pass).should have(1).things }
|
18
27
|
end
|
19
28
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
29
|
+
context 'compound targets' do
|
30
|
+
let :comp do
|
31
|
+
Lax.scope do
|
32
|
+
let number: 21
|
33
|
+
let thirty: number + 9
|
34
|
+
assert { that thirty == 30 }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
subject { comp.lings.last.new }
|
38
|
+
it { should have(1).thing }
|
39
|
+
specify { subject.first.pass.should == true }
|
40
|
+
end
|
24
41
|
|
25
42
|
end
|
26
43
|
|
data/spec/spec_helper.rb
CHANGED
data/spec/unit/lax_spec.rb
CHANGED
@@ -1,20 +1,38 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Lax do
|
4
|
-
|
4
|
+
let(:lax) { Class.new(Lax) }
|
5
5
|
|
6
6
|
describe '::let' do
|
7
|
-
before
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
it { should have(1).thing }
|
7
|
+
before { lax.let number: 1 }
|
8
|
+
specify { lax.methods.should include :number }
|
9
|
+
specify { lax.instance_methods.should include :number }
|
10
|
+
specify { lax.number.should == 1 }
|
11
|
+
specify { lax.new.number.should == 1 }
|
13
12
|
end
|
14
13
|
|
15
14
|
describe '#initialize' do
|
16
|
-
subject {
|
15
|
+
subject { lax.new }
|
17
16
|
it { should be_empty }
|
18
17
|
end
|
18
|
+
|
19
|
+
describe '::scope' do
|
20
|
+
subject { Lax.scope }
|
21
|
+
specify { Lax.lings.should include subject }
|
22
|
+
its(:superclass) { should == Lax }
|
23
|
+
its(:new) { should be_empty }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '::assert' do
|
27
|
+
subject do
|
28
|
+
Lax.scope do
|
29
|
+
let number: 22
|
30
|
+
assert('hahawow') { that number == 22 }
|
31
|
+
end.lings.last
|
32
|
+
end
|
33
|
+
specify { subject.superclass.superclass.should be Lax }
|
34
|
+
its(:doc) { should == 'hahawow' }
|
35
|
+
its(:new) { should have(1).thing }
|
36
|
+
end
|
19
37
|
end
|
20
38
|
|
data/test/control.rb
CHANGED
@@ -1,28 +1,77 @@
|
|
1
|
-
Lax.
|
1
|
+
Lax.scope do
|
2
2
|
let number: 1,
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
string: 'asdf',
|
4
|
+
symbol: :a_sym,
|
5
|
+
regexp: lazy{/asd/}
|
6
6
|
|
7
|
-
|
8
|
-
number == 1
|
9
|
-
|
7
|
+
scope do
|
8
|
+
assert { that number == 1 }
|
9
|
+
scope do
|
10
10
|
let number: 2
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
assert do
|
12
|
+
that number == 2,
|
13
|
+
number.even?,
|
14
|
+
string.upcase.downcase == 'asdf',
|
15
|
+
string =~ regexp
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
end
|
18
|
-
|
20
|
+
|
21
|
+
Lax.scope do
|
19
22
|
let number: 1,
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
string
|
23
|
+
string: 'Hi There',
|
24
|
+
regexp: lazy{ /the/ } # lazy evaluation
|
25
|
+
|
26
|
+
string {upcase == 'HI THERE'}
|
27
|
+
|
24
28
|
assert do
|
25
|
-
|
26
|
-
|
29
|
+
that number + 1 == 2,
|
30
|
+
string.downcase =~ regexp
|
31
|
+
end
|
32
|
+
|
33
|
+
# before { puts "i will be run once for each assert block in my scope" }
|
34
|
+
# after { puts "are stackable" }
|
35
|
+
|
36
|
+
scope do
|
37
|
+
|
38
|
+
def self.number_is_even
|
39
|
+
number { even? }
|
40
|
+
end
|
41
|
+
|
42
|
+
let number: 2,
|
43
|
+
nothing: regexp.match('ffff'),
|
44
|
+
bool: true
|
45
|
+
before { @qqq=9}
|
46
|
+
|
47
|
+
number_is_even
|
48
|
+
|
49
|
+
assert 'documented tests' do
|
50
|
+
that number + @qqq == 11,
|
51
|
+
number - 1 == 1,
|
52
|
+
string.upcase == 'HI THERE', # string is in scope
|
53
|
+
nothing == nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
scope do
|
58
|
+
let lax: self,
|
59
|
+
open_file: fix(read: "data\nof\nimmediate\ninterest ") # fixtures
|
60
|
+
assert do
|
61
|
+
that lax.respond_to?(:bool) == false # bool is out of scope
|
62
|
+
that open_file.read.lines.map(&:strip).size == 4
|
63
|
+
end
|
27
64
|
end
|
28
65
|
end
|
66
|
+
|
67
|
+
Lax.scope do
|
68
|
+
let lax: Lax
|
69
|
+
assert do
|
70
|
+
group = lax.scope do
|
71
|
+
let altitude: 10000
|
72
|
+
assert { that altitude > 1000 }
|
73
|
+
end.lings.first.new
|
74
|
+
that group.size==1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lax
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -35,17 +35,12 @@ extra_rdoc_files: []
|
|
35
35
|
files:
|
36
36
|
- lax.gemspec
|
37
37
|
- lib/lax.rb
|
38
|
-
- lib/lax/rake_task.rb
|
39
|
-
- lib/lax/source.rb
|
40
38
|
- README.md
|
41
39
|
- LICENSE
|
42
40
|
- test/control.rb
|
43
41
|
- spec/spec_helper.rb
|
44
42
|
- spec/acceptance/acceptance_spec.rb
|
45
|
-
- spec/unit/hook_spec.rb
|
46
|
-
- spec/unit/target_spec.rb
|
47
43
|
- spec/unit/lax_spec.rb
|
48
|
-
- spec/unit/fixture/hashable_spec.rb
|
49
44
|
homepage: http://github.com/gwentacle/lax
|
50
45
|
licenses:
|
51
46
|
- MIT/X11
|
@@ -75,7 +70,4 @@ test_files:
|
|
75
70
|
- test/control.rb
|
76
71
|
- spec/spec_helper.rb
|
77
72
|
- spec/acceptance/acceptance_spec.rb
|
78
|
-
- spec/unit/hook_spec.rb
|
79
|
-
- spec/unit/target_spec.rb
|
80
73
|
- spec/unit/lax_spec.rb
|
81
|
-
- spec/unit/fixture/hashable_spec.rb
|
data/lib/lax/rake_task.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'rake'
|
2
|
-
require 'lax'
|
3
|
-
class Lax
|
4
|
-
module RakeTask
|
5
|
-
class << self
|
6
|
-
include Rake::DSL
|
7
|
-
def new(opts = {})
|
8
|
-
o = Lax.config.task.merge opts
|
9
|
-
namespace o[:name] do
|
10
|
-
task(:load) { Dir["./#{o[:dir]}/**/*.rb"].each {|f| load f} }
|
11
|
-
task(:run) do
|
12
|
-
Lax::Run[ Lax ]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
task o[:name] => ["#{o[:name]}:load", "#{o[:name]}:run"]
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
data/lib/lax/source.rb
DELETED
@@ -1,288 +0,0 @@
|
|
1
|
-
VERSION = '0.2.1'
|
2
|
-
|
3
|
-
class Assertion < Struct.new :name, :subject, :condition, :src, :matcher, :args, :hooks
|
4
|
-
def pass?
|
5
|
-
memoize(:pass) { condition.call value }
|
6
|
-
end
|
7
|
-
|
8
|
-
def value
|
9
|
-
memoize(:value) { subject.call }
|
10
|
-
end
|
11
|
-
|
12
|
-
def validate
|
13
|
-
memoize(:validate) do
|
14
|
-
hooks.before.call self
|
15
|
-
pass?
|
16
|
-
self.tap { hooks.after.call self }
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
def memoize(key)
|
22
|
-
@memo ||= {}
|
23
|
-
@memo.has_key?(key) ? @memo[key] : @memo[key] = yield
|
24
|
-
end
|
25
|
-
|
26
|
-
class Xptn < Struct.new :assertion, :exception
|
27
|
-
attr_accessor :name, :src, :matcher, :args
|
28
|
-
|
29
|
-
def pass?
|
30
|
-
false
|
31
|
-
end
|
32
|
-
|
33
|
-
def value
|
34
|
-
nil
|
35
|
-
end
|
36
|
-
|
37
|
-
def initialize(a, x)
|
38
|
-
super
|
39
|
-
%w{name src matcher args}.each {|m| send "#{m}=", a.send(m)}
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
module Fixture
|
45
|
-
def self.new(hash)
|
46
|
-
klass = Struct.new(*hash.keys)
|
47
|
-
klass.send :include, self
|
48
|
-
klass.new *hash.values
|
49
|
-
end
|
50
|
-
|
51
|
-
module Hashable
|
52
|
-
def self.new(hashable)
|
53
|
-
hash = hashable.to_hash
|
54
|
-
klass = Struct.new(*hash.keys)
|
55
|
-
klass.send :include, self, Fixture
|
56
|
-
klass.new(*hash.values.map do |val|
|
57
|
-
(Hash===val) ? new(val) : val
|
58
|
-
end)
|
59
|
-
end
|
60
|
-
|
61
|
-
def to_hash
|
62
|
-
Hash[
|
63
|
-
members.zip entries.map {|e| e.kind_of?(Hashable) ? e.to_hash : e }
|
64
|
-
]
|
65
|
-
end
|
66
|
-
|
67
|
-
def merge(hashable)
|
68
|
-
Hashable.new to_hash.merge hashable
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class Target < BasicObject
|
74
|
-
def self.define_matcher(sym, &p)
|
75
|
-
define_method(sym) do |*a,&b|
|
76
|
-
p ?
|
77
|
-
satisfies(sym) do |o|
|
78
|
-
p[*a.map {|v| resolve v},&b][o]
|
79
|
-
end :
|
80
|
-
satisfies(sym,*a) do |o|
|
81
|
-
o.__send__ sym,*a.map {|v| resolve v},&b
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.define_predicate(sym)
|
87
|
-
if sym =~ /(.*)(\?|_?p)$/
|
88
|
-
define_method($1) { satisfies($1) {|o| o.__send__ sym} }
|
89
|
-
else
|
90
|
-
raise ArgumentError, "#{sym} does not appear to be a predicate"
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
%w{== === != =~ !~ < > <= >=}.each {|m| define_matcher m}
|
95
|
-
%w{odd? even? is_a? kind_of? include?}.each {|m| define_predicate m}
|
96
|
-
|
97
|
-
def initialize(node, subj, name, src)
|
98
|
-
@node, @subj, @name, @src = node, subj, name, src
|
99
|
-
end
|
100
|
-
|
101
|
-
def satisfies(matcher=nil, *args, &cond)
|
102
|
-
assert!(cond, *[ matcher, args ])
|
103
|
-
end
|
104
|
-
|
105
|
-
def method_missing(sym, *args, &blk)
|
106
|
-
Target.new @node, ->{@subj.call.__send__(sym, *args, &blk)}, @name, @src
|
107
|
-
end
|
108
|
-
|
109
|
-
def __val__
|
110
|
-
@subj.call
|
111
|
-
end
|
112
|
-
|
113
|
-
private
|
114
|
-
def assert!(cond, matcher=nil, args=nil)
|
115
|
-
name, subj, src, hooks = @name, @subj, @src, @node.hooks
|
116
|
-
ord = @node.instance_methods.size.to_s
|
117
|
-
@node.send(:include, ::Module.new do
|
118
|
-
define_method(ord) do
|
119
|
-
::Lax::Assertion.new name, subj, cond, src, matcher, args, hooks
|
120
|
-
end
|
121
|
-
end)
|
122
|
-
end
|
123
|
-
|
124
|
-
def resolve(v)
|
125
|
-
::Lax::Target === v ? v.__val__ : v
|
126
|
-
end
|
127
|
-
|
128
|
-
end
|
129
|
-
|
130
|
-
module Run
|
131
|
-
def self.[](lax)
|
132
|
-
hook = lax.config.run.hooks
|
133
|
-
hook.start[ as = lax.map(&:new).flatten ]
|
134
|
-
as.map do |assertion|
|
135
|
-
hook.before[ assertion ]
|
136
|
-
validate_protect(assertion).tap {|v| hook.after[v]}
|
137
|
-
end.tap {|vs| hook.finish[vs]}
|
138
|
-
end
|
139
|
-
|
140
|
-
private
|
141
|
-
def self.validate_protect(a)
|
142
|
-
begin
|
143
|
-
a.validate
|
144
|
-
rescue => e
|
145
|
-
Assertion::Xptn.new(a, e)
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
class Hook < Proc
|
151
|
-
class << self
|
152
|
-
def _resolve(hook)
|
153
|
-
if hook.is_a? Hook
|
154
|
-
hook
|
155
|
-
elsif hook.is_a? Proc
|
156
|
-
new &hook
|
157
|
-
elsif hook.is_a?(Symbol) and self.respond_to?(hook)
|
158
|
-
send hook
|
159
|
-
else
|
160
|
-
raise NameError, "Unable to resolve hook `#{hook}'"
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
def noop
|
165
|
-
new {|*a|}
|
166
|
-
end
|
167
|
-
|
168
|
-
def output
|
169
|
-
new {|tc| print tc.pass? ? "\x1b[32m.\x1b[0m" : "\x1b[31mX\x1b[0m"}
|
170
|
-
end
|
171
|
-
|
172
|
-
# Returns a hook for generating terminal output from test cases.
|
173
|
-
def summary
|
174
|
-
new {|cs| puts "\nFinished #{cs.size} tests with #{cs.reject(&:pass?).size} failures"}
|
175
|
-
end
|
176
|
-
|
177
|
-
# Returns a hook for generating terminal output from test cases.
|
178
|
-
def failures
|
179
|
-
new do |cs|
|
180
|
-
cs.reject(&:pass?).each do |f|
|
181
|
-
puts " #{f.src}\n " <<
|
182
|
-
"#{f.exception ?
|
183
|
-
"(raised an unhandled #{f.exception.class})" :
|
184
|
-
"(got #{f.subject})"}"
|
185
|
-
end
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
def define(sym, &p)
|
190
|
-
define_singleton_method(sym) {new &p}
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def <<(hook)
|
195
|
-
Hook.new {|*a,&b| call(Hook._resolve(hook)[*a,&b])}
|
196
|
-
end
|
197
|
-
|
198
|
-
def +(hook)
|
199
|
-
Hook.new {|*a,&b| call(*a,&b); Hook._resolve(hook)[*a,&b]}
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
CONFIG = Fixture::Hashable.new(
|
204
|
-
task: { dir: :test, name: :lax },
|
205
|
-
node: {
|
206
|
-
hooks: {
|
207
|
-
before: Hook.noop,
|
208
|
-
after: Hook.noop
|
209
|
-
}
|
210
|
-
},
|
211
|
-
run: {
|
212
|
-
hooks: {
|
213
|
-
start: Hook.noop,
|
214
|
-
before: Hook.noop,
|
215
|
-
after: Hook.output,
|
216
|
-
finish: Hook.summary
|
217
|
-
}
|
218
|
-
}
|
219
|
-
)
|
220
|
-
|
221
|
-
@hooks = CONFIG.node.hooks
|
222
|
-
@children = []
|
223
|
-
|
224
|
-
def self.inherited(child)
|
225
|
-
@children << child
|
226
|
-
child.hooks = @hooks.dup
|
227
|
-
child.children = []
|
228
|
-
end
|
229
|
-
|
230
|
-
extend Enumerable
|
231
|
-
|
232
|
-
class << self
|
233
|
-
attr_accessor :hooks, :children
|
234
|
-
|
235
|
-
def reboot(suppress_warning = true)
|
236
|
-
(stderr, $stderr = $stderr, StringIO.new) if suppress_warning
|
237
|
-
Object.const_set :Lax, Class.new(Array)
|
238
|
-
Lax.const_set :SOURCE, SOURCE
|
239
|
-
Lax.class_eval SOURCE
|
240
|
-
($stderr = stderr) if suppress_warning
|
241
|
-
Lax
|
242
|
-
end
|
243
|
-
|
244
|
-
def config
|
245
|
-
block_given? ? yield(CONFIG) : CONFIG
|
246
|
-
end
|
247
|
-
|
248
|
-
def matcher(sym, &b)
|
249
|
-
Target.define_matcher(sym, &b)
|
250
|
-
end
|
251
|
-
|
252
|
-
def hook(sym, &b)
|
253
|
-
Hook.define(sym, &b)
|
254
|
-
end
|
255
|
-
|
256
|
-
def each(&b)
|
257
|
-
yield self
|
258
|
-
children.each {|c| c.each(&b)}
|
259
|
-
end
|
260
|
-
|
261
|
-
def let(h)
|
262
|
-
h.each do |key, value|
|
263
|
-
val = value.is_a?(Hook) ? value : ->{value}
|
264
|
-
define_singleton_method(key) do
|
265
|
-
Target.new(self, val, key, caller[0])
|
266
|
-
end
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
def defer(&v)
|
271
|
-
Hook.new(&v)
|
272
|
-
end
|
273
|
-
alias _ defer
|
274
|
-
|
275
|
-
def fix(hash)
|
276
|
-
Fixture.new(hash)
|
277
|
-
end
|
278
|
-
|
279
|
-
def assert(*vals, &b)
|
280
|
-
Class.new(self).tap {|node| node.class_eval(&b)}
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def initialize
|
285
|
-
ks = methods - self.class.superclass.instance_methods
|
286
|
-
ks.map {|k| send k}.each {|k| self << k}
|
287
|
-
end
|
288
|
-
|
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Lax::Fixture::Hashable do
|
4
|
-
let(:hash) { { name: 'phyllis', fears: { mild: ['spiders'], severe: ['manatees'] } } }
|
5
|
-
let(:config) { Lax::Fixture::Hashable.new hash }
|
6
|
-
|
7
|
-
describe '::new' do
|
8
|
-
specify { ->{Lax::Fixture::Hashable.new}.should raise_error ArgumentError }
|
9
|
-
specify { [:name, :fears].each {|msg| config.should respond_to msg } }
|
10
|
-
specify { config.name.should == 'phyllis' }
|
11
|
-
specify { config.fears.should be_a_kind_of Lax::Fixture::Hashable }
|
12
|
-
end
|
13
|
-
|
14
|
-
describe '#merge' do
|
15
|
-
subject { config.merge manners: 'impeccable' }
|
16
|
-
it { should be_a_kind_of Lax::Fixture::Hashable }
|
17
|
-
specify { subject.name.should == 'phyllis' }
|
18
|
-
specify { subject.manners.should == 'impeccable' }
|
19
|
-
specify { subject.fears.should be_a_kind_of Lax::Fixture::Hashable }
|
20
|
-
end
|
21
|
-
|
22
|
-
describe '#to_hash' do
|
23
|
-
subject { config.to_hash }
|
24
|
-
it { should == hash }
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
data/spec/unit/hook_spec.rb
DELETED
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Lax::Hook do
|
4
|
-
let(:one) { Lax::Hook.new {1} }
|
5
|
-
specify { one.should be_a_kind_of Proc }
|
6
|
-
|
7
|
-
describe '#<<' do
|
8
|
-
let(:doubler) { Lax::Hook.new {|n| 2*n} }
|
9
|
-
subject { doubler << one }
|
10
|
-
its(:call) { should == 2 }
|
11
|
-
end
|
12
|
-
|
13
|
-
describe '#+' do
|
14
|
-
let(:thing) { Object.new }
|
15
|
-
let(:hash) { Lax::Hook.new { thing.hash } }
|
16
|
-
subject { hash + one }
|
17
|
-
its(:call) { should == 1 }
|
18
|
-
specify do
|
19
|
-
thing.should_receive :hash
|
20
|
-
subject.call
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe '::_resolve' do
|
25
|
-
let(:resolve) { Lax::Hook.method :_resolve }
|
26
|
-
context 'given a hook' do
|
27
|
-
let(:hook) { Lax::Hook.new {:surprise!} }
|
28
|
-
subject { resolve[hook] }
|
29
|
-
it { should == hook }
|
30
|
-
end
|
31
|
-
|
32
|
-
context 'given a symbol' do
|
33
|
-
let(:a_hook) { :noop }
|
34
|
-
let(:random_sym) { :asdfqwer }
|
35
|
-
specify { resolve[a_hook].call.should == nil }
|
36
|
-
specify { -> {resolve[random_sym]}.should raise_error NameError }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
data/spec/unit/target_spec.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Lax::Target do
|
4
|
-
let(:node) { Lax.assert { let num: 1 } }
|
5
|
-
|
6
|
-
describe 'establishing a target' do
|
7
|
-
subject { node.num }
|
8
|
-
specify { (Lax::Target === subject).should == true }
|
9
|
-
specify { subject.__val__.should == 1 }
|
10
|
-
end
|
11
|
-
|
12
|
-
context 'when specifying a condition' do
|
13
|
-
before { node.num == 1234 }
|
14
|
-
specify { node.new.should have(1).thing }
|
15
|
-
describe 'the entailed assertion' do
|
16
|
-
subject { node.new.first }
|
17
|
-
it { should be_an_instance_of Lax::Assertion }
|
18
|
-
its(:subject) { should be_an_instance_of Proc }
|
19
|
-
its(:name) { should == :num }
|
20
|
-
its(:matcher) { should == '==' }
|
21
|
-
its(:pass?) { should == false }
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|