graham 0.0.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.
- data/README.md +36 -0
- data/Rakefile +17 -0
- data/graham.gemspec +17 -0
- data/lib/graham/pp.rb +34 -0
- data/lib/graham/rake_task.rb +65 -0
- data/lib/graham/version.rb +3 -0
- data/lib/graham.rb +178 -0
- data/test/case/control.rb +10 -0
- data/test/case/test.rb +57 -0
- metadata +72 -0
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Graham #
|
2
|
+
|
3
|
+
Graham is a tiny-yet-useful testing library based on Mallow - in fact it _is_ Mallow, with a tweaked DSL and some extra exception handling, bundled with a trivial pretty printer and a helper for Rake tasks. It was written to handle Mallow's unit tests because:
|
4
|
+
* Test::Unit was too ugly
|
5
|
+
* TestRocket was too minimal
|
6
|
+
* RSpec was too verbose (not to mention ridiculous overkill)
|
7
|
+
|
8
|
+
## How i graham ##
|
9
|
+
|
10
|
+
Graham test cases are instance methods on classes defined in Graham's namespace:
|
11
|
+
```ruby
|
12
|
+
class Graham::TestCases
|
13
|
+
def initialize
|
14
|
+
@number = 1
|
15
|
+
end
|
16
|
+
def Case1
|
17
|
+
@number ** 2
|
18
|
+
end
|
19
|
+
def Case2
|
20
|
+
@number / 0
|
21
|
+
end
|
22
|
+
def Case3
|
23
|
+
Graham.this_is_not_a_method
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
Then test your cases:
|
28
|
+
```ruby
|
29
|
+
Graham.test(:TestCases) do |that|
|
30
|
+
that.Case1.is_such_that { self == 1 }
|
31
|
+
that.Case2.is_a(Fixnum).such_that {self > 1}
|
32
|
+
that.Case3.does_not_raise_an_exception
|
33
|
+
end #=> {:Case1=>true, :Case2=>#<ZeroDivisionError>, :Case3=>false}
|
34
|
+
```
|
35
|
+
Calling Graham#pp instead will call Graham#test and run the output through Graham's built-in pretty printer.
|
36
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__)
|
2
|
+
require 'rake'
|
3
|
+
require 'graham/rake_task'
|
4
|
+
|
5
|
+
Graham::RakeTask.new
|
6
|
+
task default: :test
|
7
|
+
|
8
|
+
namespace :gem do
|
9
|
+
task :build do
|
10
|
+
sh "gem b graham.gemspec"
|
11
|
+
end
|
12
|
+
task :install do
|
13
|
+
sh "gem i #{Dir.glob('graham-*.gem').sort.last}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
task gem: %w{ gem:build gem:install }
|
17
|
+
|
data/graham.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH << File.expand_path('../lib', __FILE__)
|
2
|
+
require 'rake'
|
3
|
+
require 'graham/version'
|
4
|
+
|
5
|
+
graham = Gem::Specification.new do |spec|
|
6
|
+
spec.add_dependency 'mallow'
|
7
|
+
spec.name = 'graham'
|
8
|
+
spec.version = Graham::VERSION
|
9
|
+
spec.author = 'feivel jellyfish'
|
10
|
+
spec.email = 'feivel@sdf.org'
|
11
|
+
spec.files = FileList['README.md','graham.gemspec', 'lib/**/*.rb']
|
12
|
+
spec.test_files = FileList['Rakefile','test/**/*.rb']
|
13
|
+
spec.homepage = 'http://github.com/gwentacle/graham'
|
14
|
+
spec.summary = 'Miniature test engine based on Mallow'
|
15
|
+
spec.description = 'Miniature test engine based on Mallow'
|
16
|
+
end
|
17
|
+
|
data/lib/graham/pp.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# A trivial pretty printer for test output.
|
2
|
+
# TODO: replace with something better
|
3
|
+
module Graham
|
4
|
+
class PP
|
5
|
+
attr_reader :results
|
6
|
+
attr_accessor :bt, :out, :color
|
7
|
+
BOLD = "\x1b[1m"
|
8
|
+
PLAIN = "\x1b[0m"
|
9
|
+
GREEN = "\x1b[32m"
|
10
|
+
RED = "\x1b[31m"
|
11
|
+
def initialize(results, bt=0, out=$stdout, color=true)
|
12
|
+
@results, @bt, @out, @color = results, bt, out, color
|
13
|
+
end
|
14
|
+
def pp
|
15
|
+
results.each do |name, result|
|
16
|
+
out.puts case result
|
17
|
+
when true
|
18
|
+
[hi('PASS', GREEN), lo(name)]
|
19
|
+
when false
|
20
|
+
[hi('FAIL', RED), lo(name)]
|
21
|
+
else
|
22
|
+
[hi('XPTN', RED), lo(name), result.class.name, result.message] + backtrace(result, bt)
|
23
|
+
end.join ' :: '
|
24
|
+
end
|
25
|
+
end
|
26
|
+
private
|
27
|
+
def backtrace(e,n)
|
28
|
+
n>0 ? ["\n" << e.backtrace.first(n).map {|s| " "+s}.join("\n")] : []
|
29
|
+
end
|
30
|
+
|
31
|
+
def hi(str, c); (color ? BOLD+c : '')+str.to_s end
|
32
|
+
def lo(str); (color ? PLAIN : '')+str.to_s end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'graham'
|
2
|
+
# Rake task generator for Graham. Usage is just:
|
3
|
+
# # in Rakefile
|
4
|
+
# require 'graham/rake_task'
|
5
|
+
# Graham::RakeTask.new( options )
|
6
|
+
# Options (all optional) are:
|
7
|
+
# :dir:: The directory (relative to the Rakefile) where Graham should look for
|
8
|
+
# tests. Default is 'test'
|
9
|
+
# :name:: The namespace for generated Rake tasks. Default is 'test'
|
10
|
+
# :ignore:: Directories under the test directory that will be ignored for the
|
11
|
+
# purpose of generating namespaced Rake tasks.
|
12
|
+
# Let _name_ be the chosen namespace for tests. Then this will
|
13
|
+
# 1. Create the task _name_:_folder_ for each subfolder _folder_ under the
|
14
|
+
# testing directory, which (non-recursively) loads each file in _folder_;
|
15
|
+
# 2. Create the task _name_, which pulls in all tasks created by (1);
|
16
|
+
# 3. For each task _t_ created by (1) and (2), create the task _t_:timed that
|
17
|
+
# outputs the running time of _t_ once it finishes.
|
18
|
+
#
|
19
|
+
class Graham::RakeTask
|
20
|
+
include Rake::DSL
|
21
|
+
def initialize(opts = {})
|
22
|
+
@dir = opts[:dir] || :test
|
23
|
+
@ignore = opts[:ignore] || []
|
24
|
+
@name = opts[:name] || @dir
|
25
|
+
make_task
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def make_task
|
30
|
+
tests = []
|
31
|
+
namespace @name do
|
32
|
+
namespace :timed do
|
33
|
+
task :start do
|
34
|
+
@start = Time.now
|
35
|
+
end
|
36
|
+
task :stop do
|
37
|
+
puts "Finished in #{Time.now - @start} seconds."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
test_groups.each do |group|
|
41
|
+
tests << name = group.sub(/\//,?:).sub(@dir.to_s,@name.to_s)
|
42
|
+
desc "[Graham] load files in #{group}"
|
43
|
+
task name.sub(/^#{@name}:/,'') do
|
44
|
+
Dir["#{group}/*.rb"].each do |file|
|
45
|
+
puts "\x1b[4m #{file.sub(/\.rb$/,'')}\x1b[0m"
|
46
|
+
load file
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
task timed: [ "#{@name}:timed:start", @name, "#{@name}:timed:stop" ]
|
51
|
+
end
|
52
|
+
desc "[Graham] load all test files"
|
53
|
+
task @name => tests
|
54
|
+
tests.each do |test|
|
55
|
+
task "#{test}:timed" => [ "#{@name}:timed:start", test, "#{@name}:timed:stop" ]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_groups
|
60
|
+
ignore = [*@ignore].map {|d| %r|^#{@dir}/#{d}|}
|
61
|
+
FileList["#{@dir}/**/*"].select {|f| File.directory? f}
|
62
|
+
.reject {|d| ignore.any? {|i| d=~i}}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
data/lib/graham.rb
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'mallow'
|
3
|
+
require 'graham/version'
|
4
|
+
# == A miniature test engine powered by Mallow
|
5
|
+
# ---
|
6
|
+
# Test cases are instance methods on classes defined in Graham's namespace,
|
7
|
+
# and expectations on their return values are enumerated using a very slightly
|
8
|
+
# modified Mallow::DSL.
|
9
|
+
#
|
10
|
+
# class Graham::Cases
|
11
|
+
# def test1; 4 + 5 end
|
12
|
+
# def test2; 'test'.upcase end
|
13
|
+
# def test3; 1/0 end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# Graham.test { |that|
|
17
|
+
# that.test1.returns_a(Fixnum).such_that {self < 100}
|
18
|
+
# that.test2.returns 'TeST'
|
19
|
+
# that.test3.returns_a Numeric
|
20
|
+
# } #=> {:test1=>true, :test2=>false, :test3=>#<ZeroDivisionError>}
|
21
|
+
#
|
22
|
+
# A side effect of this approach is that you can use RDoc to document your
|
23
|
+
# tests, gauge coverage, etc.
|
24
|
+
#
|
25
|
+
# === N.B.
|
26
|
+
# Since a Graham test is basically a Mallow pattern matcher, only the first
|
27
|
+
# test on a case will actually be executed, as subsequent invocations of the
|
28
|
+
# test case will be matched by the first rule. This can lead to confusing
|
29
|
+
# results:
|
30
|
+
#
|
31
|
+
# Graham.test { |that|
|
32
|
+
# that.test1.returns_a(Fixnum)
|
33
|
+
# that.test1.returns_a(String)
|
34
|
+
# } #=> {:test1=>true}
|
35
|
+
#
|
36
|
+
# To avoid this issue, either run separate tests:
|
37
|
+
#
|
38
|
+
# Graham.test {|that| that.test1.returns_a Fixnum} #=> {:test1=>true}
|
39
|
+
# Graham.test {|that| that.test1.returns_a String} #=> {:test1=>false}
|
40
|
+
#
|
41
|
+
# Or (better) chain the tests you want to run:
|
42
|
+
#
|
43
|
+
# Graham.test { |that|
|
44
|
+
# that.test1.returns_a(Fixnum).and_returns_a(String)
|
45
|
+
# } #=> {:test1=>false}
|
46
|
+
#
|
47
|
+
module Graham
|
48
|
+
autoload :PP, 'graham/pp'
|
49
|
+
autoload :RakeTask, 'graham/rake_task'
|
50
|
+
# Namespace for test cases; see documentation for Graham
|
51
|
+
class Cases; end
|
52
|
+
class << self
|
53
|
+
# A convenience method that builds and executes a Graham::Core
|
54
|
+
# in the given namespace (defaults to Cases). See documentation for
|
55
|
+
# Graham for more on usage.
|
56
|
+
def test(ns=self::Cases, &b)
|
57
|
+
ns=const_get(ns) if ns.is_a? Symbol
|
58
|
+
Graham::Core.build(ns, &b).test
|
59
|
+
end
|
60
|
+
# A convenience methods that calls ::test and passes the output to a
|
61
|
+
# pretty printer.
|
62
|
+
def pp(ns=self::Cases, &b)
|
63
|
+
PP.new(test ns,&b).pp
|
64
|
+
end
|
65
|
+
|
66
|
+
def compose(ns=nil, *test_groups)
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
alias test_in test
|
71
|
+
|
72
|
+
private
|
73
|
+
def ns_compose(test_groups)
|
74
|
+
test_groups.map do |ns, tests|
|
75
|
+
[ ns, tests.map {|test| test ns, &test} ]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class Core < Mallow::Core
|
82
|
+
attr_accessor :cases
|
83
|
+
def initialize(*args)
|
84
|
+
@cases = []
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
def _fluff1(e)
|
89
|
+
[e.name] << begin
|
90
|
+
super e
|
91
|
+
true
|
92
|
+
rescue Mallow::DeserializationException
|
93
|
+
false
|
94
|
+
rescue => err
|
95
|
+
err
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def test; Hash[_fluff @cases] end
|
100
|
+
def self.build(ns, &b); DSL.build ns, &b end
|
101
|
+
end
|
102
|
+
|
103
|
+
class DSL < Mallow::DSL
|
104
|
+
|
105
|
+
def self.build(ns)
|
106
|
+
yield(dsl = new(ns))
|
107
|
+
dsl.finish!
|
108
|
+
end
|
109
|
+
|
110
|
+
def initialize(ns)
|
111
|
+
@core, @cases = Core.new, ns.new
|
112
|
+
reset!
|
113
|
+
end
|
114
|
+
|
115
|
+
def method_missing(msg, *args, &b)
|
116
|
+
case msg.to_s
|
117
|
+
when /^((and|that)_)+(.+)$/
|
118
|
+
respond_to?($3)? send($3, *args, &b) : super
|
119
|
+
else
|
120
|
+
core.cases << (_case = @cases.method msg) rescue super
|
121
|
+
rule!._this _case
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def _where(&b); push b, :conditions end
|
126
|
+
def _this(o); _where {|e|e==o} end
|
127
|
+
def where(&b); _where {|e| preproc(b)[e.call] } end
|
128
|
+
|
129
|
+
def raises(x=nil)
|
130
|
+
_where {
|
131
|
+
begin
|
132
|
+
call
|
133
|
+
false
|
134
|
+
rescue x => e
|
135
|
+
true
|
136
|
+
rescue => e
|
137
|
+
x ? raise(e) : true
|
138
|
+
end
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
def does_not_raise(x=nil)
|
143
|
+
_where {
|
144
|
+
begin
|
145
|
+
call
|
146
|
+
true
|
147
|
+
rescue x => e
|
148
|
+
false
|
149
|
+
rescue => e
|
150
|
+
x ? raise(e) : false
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
alias is this
|
156
|
+
alias returns this
|
157
|
+
|
158
|
+
alias is_such_that where
|
159
|
+
alias such_that where
|
160
|
+
alias and where
|
161
|
+
alias that where
|
162
|
+
|
163
|
+
alias raises_an raises
|
164
|
+
alias raises_a raises
|
165
|
+
alias raises_an_exception raises
|
166
|
+
|
167
|
+
alias returns_ does_not_raise
|
168
|
+
alias does_not_raise_a does_not_raise
|
169
|
+
alias does_not_raise_an does_not_raise
|
170
|
+
alias does_not_raise_an_exception does_not_raise
|
171
|
+
|
172
|
+
alias is_a a
|
173
|
+
alias is_an a
|
174
|
+
alias returns_a a
|
175
|
+
alias returns_an a
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
data/test/case/test.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
class Graham::Cases
|
2
|
+
def DocTest1; 4 + 5 end
|
3
|
+
def DocTest2; 'test'.upcase end
|
4
|
+
def DocTest3; 1/0 end
|
5
|
+
def RDocExample
|
6
|
+
Graham.test { |that|
|
7
|
+
that.DocTest1.returns_a(Fixnum).such_that {self < 100}
|
8
|
+
that.DocTest2.returns 'TeST'
|
9
|
+
that.DocTest3.returns_a Numeric
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@number = 1
|
15
|
+
end
|
16
|
+
def ReadmeCase1
|
17
|
+
@number ** 2
|
18
|
+
end
|
19
|
+
def ReadmeCase2
|
20
|
+
@number / 0
|
21
|
+
end
|
22
|
+
def ReadmeCase3
|
23
|
+
Graham.this_is_not_a_method
|
24
|
+
end
|
25
|
+
def ReadmeExample
|
26
|
+
Graham.test do |that|
|
27
|
+
that.ReadmeCase1.is_such_that { self == 1 }
|
28
|
+
that.ReadmeCase2.is_a(Fixnum).such_that {self > 1}
|
29
|
+
that.ReadmeCase3.does_not_raise_an_exception
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Graham::Namespace
|
35
|
+
def Namespacing; self end
|
36
|
+
end
|
37
|
+
|
38
|
+
Graham.pp do |that|
|
39
|
+
that.RDocExample.returns_a(Hash).of_size(3).such_that {
|
40
|
+
self[:DocTest1] == true and
|
41
|
+
self[:DocTest2] == false and
|
42
|
+
self[:DocTest3].is_a? ZeroDivisionError
|
43
|
+
}
|
44
|
+
that.ReadmeExample.returns_a(Hash).of_size(3).such_that {
|
45
|
+
self[:ReadmeCase1] == true and
|
46
|
+
self[:ReadmeCase2].is_a? ZeroDivisionError and
|
47
|
+
self[:ReadmeCase3] == false
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
Graham.pp(:Namespace) do |that|
|
52
|
+
that.Namespacing.is_such_that {
|
53
|
+
self.class == Graham::Namespace
|
54
|
+
}.and {!respond_to? :DocExample
|
55
|
+
}.and { respond_to? :Namespacing}
|
56
|
+
end
|
57
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graham
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- feivel jellyfish
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: mallow
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
description: Miniature test engine based on Mallow
|
31
|
+
email: feivel@sdf.org
|
32
|
+
executables: []
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- README.md
|
37
|
+
- graham.gemspec
|
38
|
+
- lib/graham.rb
|
39
|
+
- lib/graham/rake_task.rb
|
40
|
+
- lib/graham/pp.rb
|
41
|
+
- lib/graham/version.rb
|
42
|
+
- Rakefile
|
43
|
+
- test/case/control.rb
|
44
|
+
- test/case/test.rb
|
45
|
+
homepage: http://github.com/gwentacle/graham
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.23
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Miniature test engine based on Mallow
|
69
|
+
test_files:
|
70
|
+
- Rakefile
|
71
|
+
- test/case/control.rb
|
72
|
+
- test/case/test.rb
|