lax 0.2.1 → 0.2.3
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.
- 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
|
-
|