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 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