graham 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -0,0 +1,3 @@
1
+ module Graham
2
+ VERSION = '0.0.0'
3
+ end
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
@@ -0,0 +1,10 @@
1
+ class Graham::Cases
2
+ def NinetyNine; 99 end
3
+ def NameError; raise Asdf.qwer.ty / 0 end
4
+ end
5
+
6
+ Graham.pp {|that|
7
+ that.NinetyNine.returns_a(Fixnum).such_that {self==99}.and_that_is 99
8
+ that.NameError.raises.and_raises_a NameError
9
+ }
10
+
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