lemon 0.5
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/COPYING +621 -0
- data/COPYING.LESSER +166 -0
- data/HISTORY +13 -0
- data/MANIFEST +37 -0
- data/README.rdoc +108 -0
- data/Syckfile +68 -0
- data/bin/lemon +3 -0
- data/lib/lemon.rb +1 -0
- data/lib/lemon/command.rb +125 -0
- data/lib/lemon/coverage.rb +118 -0
- data/lib/lemon/kernel.rb +20 -0
- data/lib/lemon/reporter.rb +58 -0
- data/lib/lemon/reporters.rb +3 -0
- data/lib/lemon/reporters/dotprogress.rb +62 -0
- data/lib/lemon/reporters/verbose.rb +59 -0
- data/lib/lemon/runner.rb +117 -0
- data/lib/lemon/test/case.rb +109 -0
- data/lib/lemon/test/concern.rb +43 -0
- data/lib/lemon/test/suite.rb +126 -0
- data/lib/lemon/test/unit.rb +56 -0
- data/meta/author +1 -0
- data/meta/contact +1 -0
- data/meta/copyright +1 -0
- data/meta/description +2 -0
- data/meta/homepage +1 -0
- data/meta/license +1 -0
- data/meta/name +1 -0
- data/meta/repository +1 -0
- data/meta/suite +1 -0
- data/meta/summary +1 -0
- data/meta/title +1 -0
- data/meta/version +1 -0
- data/test/cases/coverage_case.rb +26 -0
- data/test/cases/testcase_case.rb +58 -0
- metadata +96 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
module Lemon
|
2
|
+
|
3
|
+
#
|
4
|
+
class Coverage
|
5
|
+
|
6
|
+
# Paths of ruby scripts to be covered.
|
7
|
+
attr :paths
|
8
|
+
|
9
|
+
# Conical snapshot of system (before loading libraries to be covered).
|
10
|
+
attr :conical
|
11
|
+
|
12
|
+
# New Coverage object.
|
13
|
+
#
|
14
|
+
# Coverage.new('lib/', :public => true)
|
15
|
+
#
|
16
|
+
def initialize(paths, options={})
|
17
|
+
@public = options[:public]
|
18
|
+
|
19
|
+
@paths = paths
|
20
|
+
@conical = snapshot
|
21
|
+
|
22
|
+
load_system
|
23
|
+
end
|
24
|
+
|
25
|
+
# Over use public methods for coverage.
|
26
|
+
def public_only?
|
27
|
+
@public
|
28
|
+
end
|
29
|
+
|
30
|
+
# Produce a coverage map.
|
31
|
+
def coverage(suite)
|
32
|
+
checklist = cover()
|
33
|
+
suite.each do |testcase|
|
34
|
+
testcase.testunits.each do |testunit|
|
35
|
+
checklist[testcase.target.name][testunit.target.to_s] = true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
checklist
|
39
|
+
end
|
40
|
+
|
41
|
+
# Coverage template.
|
42
|
+
def cover
|
43
|
+
cover = Hash.new{|h,k|h[k]={}}
|
44
|
+
system.each do |base|
|
45
|
+
next if base.is_a?(Lemon::Test::Suite)
|
46
|
+
cover[base.name] = {}
|
47
|
+
base.public_instance_methods(false).each do |meth|
|
48
|
+
cover[base.name][meth.to_s] = false
|
49
|
+
end
|
50
|
+
unless public_only?
|
51
|
+
base.private_instance_methods(false).each do |meth|
|
52
|
+
cover[base.name][meth.to_s] = false
|
53
|
+
end
|
54
|
+
base.protected_instance_methods(false).each do |meth|
|
55
|
+
cover[base.name][meth.to_s] = false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
cover
|
60
|
+
end
|
61
|
+
|
62
|
+
# Iterate over +paths+ and use #load to bring in all +.rb+ scripts.
|
63
|
+
def load_system
|
64
|
+
files = []
|
65
|
+
paths.map do |path|
|
66
|
+
if File.directory?(path)
|
67
|
+
files.concat(Dir[File.join(path, '**', '*.rb')])
|
68
|
+
else
|
69
|
+
files.concat(Dir[path])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
files.each{ |file| load(file) }
|
73
|
+
end
|
74
|
+
|
75
|
+
# System to be covered. This takes a sanpshot of the system
|
76
|
+
# and then removes the conical snapshot.
|
77
|
+
def system
|
78
|
+
snapshot - conical
|
79
|
+
end
|
80
|
+
|
81
|
+
# Produces a list of all existent Modules and Classes.
|
82
|
+
def snapshot
|
83
|
+
sys = []
|
84
|
+
#ObjectSpace.each_object(Class) do |c|
|
85
|
+
# sys << c
|
86
|
+
#end
|
87
|
+
ObjectSpace.each_object(Module) do |m|
|
88
|
+
sys << m
|
89
|
+
end
|
90
|
+
sys
|
91
|
+
end
|
92
|
+
|
93
|
+
# TODO: option to do only do what hasn't been covered thus far
|
94
|
+
def generate(opts={})
|
95
|
+
code = []
|
96
|
+
system.each do |base|
|
97
|
+
next if base.is_a?(Lemon::Test::Suite)
|
98
|
+
code << "testcase #{base}"
|
99
|
+
base.public_instance_methods(false).each do |meth|
|
100
|
+
code << "\n unit :#{meth} => '' do\n pending\n end"
|
101
|
+
end
|
102
|
+
unless public_only?
|
103
|
+
base.private_instance_methods(false).each do |meth|
|
104
|
+
code << "\n unit :#{meth} => '' do\n pending\n end"
|
105
|
+
end
|
106
|
+
base.protected_instance_methods(false).each do |meth|
|
107
|
+
code << "\n unit :#{meth} => '' do\n pending\n end"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
code << "\nend\n"
|
111
|
+
end
|
112
|
+
code.join("\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
end#class Coverage
|
116
|
+
|
117
|
+
end#module Lemon
|
118
|
+
|
data/lib/lemon/kernel.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'facets/functor'
|
2
|
+
|
3
|
+
$PRY_TABLE = {} #Hash.new{|h,k| h[k]=nil}
|
4
|
+
|
5
|
+
# Pry allows you to test private and protected methods,
|
6
|
+
# via a public-only interface.
|
7
|
+
#
|
8
|
+
# Generally one should avoid testing private and protected
|
9
|
+
# method directly, instead relying on tests of public methods to
|
10
|
+
# indirectly test them, because private and protected methods are
|
11
|
+
# considered implementation details. But sometimes is necessary
|
12
|
+
# to test them directly, or if you wish to achieve *absolute
|
13
|
+
# coverage*, say in mission critical systems.
|
14
|
+
|
15
|
+
def pry
|
16
|
+
$PRY_TABLE[self] ||= Functor.new do |op, *a, &b|
|
17
|
+
__send__(op, *a, &b)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Lemon
|
2
|
+
|
3
|
+
# Generic Reporter
|
4
|
+
class Reporter
|
5
|
+
|
6
|
+
#
|
7
|
+
def self.factory(format)
|
8
|
+
format = format.to_sym if format
|
9
|
+
case format
|
10
|
+
when :verbose
|
11
|
+
Reporters::Verbose.new
|
12
|
+
else
|
13
|
+
Reporters::DotProgress.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def report_start(suite)
|
18
|
+
end
|
19
|
+
|
20
|
+
def report_concern(concern)
|
21
|
+
end
|
22
|
+
|
23
|
+
def report_success(testunit)
|
24
|
+
print "."
|
25
|
+
end
|
26
|
+
|
27
|
+
def report_failure(testunit, exception)
|
28
|
+
#puts exception
|
29
|
+
print "F"
|
30
|
+
end
|
31
|
+
|
32
|
+
def report_error(testunit, exception)
|
33
|
+
#puts exception
|
34
|
+
print "E"
|
35
|
+
end
|
36
|
+
|
37
|
+
def report_finish(successes, failures, errors)
|
38
|
+
puts; puts
|
39
|
+
|
40
|
+
failures.each do |testunit, exception|
|
41
|
+
puts " #{testunit}"
|
42
|
+
puts " #{exception}"
|
43
|
+
puts
|
44
|
+
end
|
45
|
+
|
46
|
+
errors.each do |testunit, exception|
|
47
|
+
puts " #{testunit}"
|
48
|
+
puts " #{exception}"
|
49
|
+
puts
|
50
|
+
end
|
51
|
+
|
52
|
+
total = successes.size + failures.size + errors.size
|
53
|
+
puts "#{total} tests, #{failures.size} failures, #{errors.size} errors"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Lemon
|
2
|
+
module Reporters
|
3
|
+
|
4
|
+
require 'lemon/reporter'
|
5
|
+
|
6
|
+
# Generic Reporter
|
7
|
+
class DotProgress < Reporter
|
8
|
+
|
9
|
+
#
|
10
|
+
def self.factory(format)
|
11
|
+
case format.to_sym
|
12
|
+
when :verbose
|
13
|
+
VerboseReporter.new
|
14
|
+
else
|
15
|
+
new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def report_start(suite)
|
20
|
+
end
|
21
|
+
|
22
|
+
def report_concern(concern)
|
23
|
+
end
|
24
|
+
|
25
|
+
def report_success(testunit)
|
26
|
+
print "."
|
27
|
+
end
|
28
|
+
|
29
|
+
def report_failure(testunit, exception)
|
30
|
+
#puts exception
|
31
|
+
print "F"
|
32
|
+
end
|
33
|
+
|
34
|
+
def report_error(testunit, exception)
|
35
|
+
#puts exception
|
36
|
+
print "E"
|
37
|
+
end
|
38
|
+
|
39
|
+
def report_finish(successes, failures, errors)
|
40
|
+
puts; puts
|
41
|
+
|
42
|
+
failures.each do |testunit, exception|
|
43
|
+
puts " #{testunit}"
|
44
|
+
puts " #{exception}"
|
45
|
+
puts
|
46
|
+
end
|
47
|
+
|
48
|
+
errors.each do |testunit, exception|
|
49
|
+
puts " #{testunit}"
|
50
|
+
puts " #{exception}"
|
51
|
+
puts
|
52
|
+
end
|
53
|
+
|
54
|
+
total = successes.size + failures.size + errors.size
|
55
|
+
puts "#{total} tests, #{failures.size} failures, #{errors.size} errors"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Lemon
|
2
|
+
module Reporters
|
3
|
+
|
4
|
+
require 'lemon/reporter'
|
5
|
+
|
6
|
+
# Generic Reporter
|
7
|
+
class Verbose < Reporter
|
8
|
+
|
9
|
+
#
|
10
|
+
def report_start(suite)
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
def report_concern(concern)
|
15
|
+
puts
|
16
|
+
puts concern.description
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
def report_success(testunit)
|
21
|
+
puts "* [PASS] #{testunit}"
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
def report_failure(testunit, exception)
|
26
|
+
puts "* [FAIL] #{testunit.target}"
|
27
|
+
#puts exception
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
def report_error(testunit, exception)
|
32
|
+
puts "* [ERROR] #{testunit.target}"
|
33
|
+
#puts exception
|
34
|
+
end
|
35
|
+
|
36
|
+
def report_finish(successes, failures, errors)
|
37
|
+
puts; puts
|
38
|
+
|
39
|
+
failures.each do |testunit, exception|
|
40
|
+
puts " #{testunit}"
|
41
|
+
puts " #{exception}"
|
42
|
+
puts
|
43
|
+
end
|
44
|
+
|
45
|
+
errors.each do |testunit, exception|
|
46
|
+
puts " #{testunit}"
|
47
|
+
puts " #{exception}"
|
48
|
+
puts
|
49
|
+
end
|
50
|
+
|
51
|
+
total = successes.size + failures.size + errors.size
|
52
|
+
puts "#{total} tests, #{failures.size} failures, #{errors.size} errors"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/lib/lemon/runner.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
module Lemon
|
2
|
+
|
3
|
+
require 'ae'
|
4
|
+
require 'ae/expect'
|
5
|
+
require 'ae/should'
|
6
|
+
|
7
|
+
require 'lemon/kernel'
|
8
|
+
require 'lemon/test/suite'
|
9
|
+
require 'lemon/reporters'
|
10
|
+
|
11
|
+
#
|
12
|
+
class Runner
|
13
|
+
|
14
|
+
# Test suite to run.
|
15
|
+
attr :suite
|
16
|
+
|
17
|
+
# Record of successful tests.
|
18
|
+
attr :successes
|
19
|
+
|
20
|
+
# Record of failed tests.
|
21
|
+
attr :failures
|
22
|
+
|
23
|
+
# Record of errors.
|
24
|
+
attr :errors
|
25
|
+
|
26
|
+
# Report format.
|
27
|
+
attr :format
|
28
|
+
|
29
|
+
# New Runner.
|
30
|
+
def initialize(suite, format)
|
31
|
+
@suite = suite
|
32
|
+
@format = format
|
33
|
+
@successes = []
|
34
|
+
@failures = []
|
35
|
+
@errors = []
|
36
|
+
end
|
37
|
+
|
38
|
+
# Run tests.
|
39
|
+
def run
|
40
|
+
reporter.report_start(suite)
|
41
|
+
suite.each do |testcase|
|
42
|
+
testcase.each do |concern|
|
43
|
+
reporter.report_concern(concern)
|
44
|
+
run_concern_procedures(concern, suite, testcase)
|
45
|
+
concern.each do |testunit|
|
46
|
+
run_pretest_procedures(testunit, suite, testcase)
|
47
|
+
begin
|
48
|
+
testunit.call
|
49
|
+
reporter.report_success(testunit)
|
50
|
+
successes << testunit
|
51
|
+
rescue Assertion => exception
|
52
|
+
reporter.report_failure(testunit, exception)
|
53
|
+
failures << [testunit, exception]
|
54
|
+
rescue Exception => exception
|
55
|
+
reporter.report_error(testunit, exception)
|
56
|
+
errors << [testunit, exception]
|
57
|
+
end
|
58
|
+
run_postest_procedures(testunit, suite, testcase)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
reporter.report_finish(successes, failures, errors)
|
63
|
+
end
|
64
|
+
|
65
|
+
# All output is handled by a reporter.
|
66
|
+
def reporter
|
67
|
+
@reporter ||= Reporter.factory(format)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
#
|
73
|
+
def run_concern_procedures(concern, suite, testcase)
|
74
|
+
suite.when_clauses.each do |match, block|
|
75
|
+
if match.nil? or match === concern.to_s
|
76
|
+
block.call(testcase)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
testcase.when_clauses.each do |match, block|
|
80
|
+
if match.nil? or match === concern.to_s
|
81
|
+
block.call(testcase) if match === concern.to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
def run_pretest_procedures(testunit, suite, testcase)
|
88
|
+
suite.before_clauses.each do |match, block|
|
89
|
+
if match.nil? or match === testunit.aspect
|
90
|
+
block.call(testunit)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
testcase.before_clauses.each do |match, block|
|
94
|
+
if match.nil? or match === testunit.aspect
|
95
|
+
block.call(testunit)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
def run_postest_procedures(testunit, suite, testcase)
|
102
|
+
testcase.after_clauses.each do |match, block|
|
103
|
+
if match.nil? or match === testunit.aspect
|
104
|
+
block.call(testunit)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
suite.after_clauses.each do |match, block|
|
108
|
+
if match.nil? or match === testunit.aspect
|
109
|
+
block.call(testunit)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Lemon::Test
|
2
|
+
|
3
|
+
require 'lemon/test/concern'
|
4
|
+
require 'lemon/test/unit'
|
5
|
+
|
6
|
+
# Test Case encapsulates a collection of
|
7
|
+
# unit tests organized into groups of concern.
|
8
|
+
class Case
|
9
|
+
|
10
|
+
# The test suite to which this testcase belongs.
|
11
|
+
attr :suite
|
12
|
+
|
13
|
+
# A testcase +target+ is a class or module.
|
14
|
+
attr :target
|
15
|
+
|
16
|
+
#
|
17
|
+
attr :testunits
|
18
|
+
|
19
|
+
# List of before procedures that apply case-wide.
|
20
|
+
attr :before_clauses
|
21
|
+
|
22
|
+
# List of after procedures that apply case-wide.
|
23
|
+
attr :after_clauses
|
24
|
+
|
25
|
+
# List of concern procedures that apply case-wide.
|
26
|
+
attr :when_clauses
|
27
|
+
|
28
|
+
# A test case +target+ is a class or module.
|
29
|
+
def initialize(suite, target, &block)
|
30
|
+
@suite = suite
|
31
|
+
@target = target
|
32
|
+
@testunits = []
|
33
|
+
@concerns = []
|
34
|
+
@before_clauses = {}
|
35
|
+
@after_clauses = {}
|
36
|
+
@when_clauses = {}
|
37
|
+
instance_eval(&block)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Load a helper script applicable to this test case.
|
41
|
+
def helper(file)
|
42
|
+
instance_eval(File.read(file), file)
|
43
|
+
end
|
44
|
+
|
45
|
+
# NOTE: Due to a limitation in Ruby this does not
|
46
|
+
# provived access to submodules. A hack has been used
|
47
|
+
# to circumvent. See Suite.const_missing.
|
48
|
+
def include(*mods)
|
49
|
+
extend *mods
|
50
|
+
end
|
51
|
+
|
52
|
+
# Define a new test concern for this case.
|
53
|
+
def Concern(*description)
|
54
|
+
concern = Concern.new(self, description)
|
55
|
+
@concerns << concern
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :concern, :Concern
|
59
|
+
|
60
|
+
# The last defined concern. Used to assign new unit tests.
|
61
|
+
def current_concern
|
62
|
+
if @concerns.empty?
|
63
|
+
@concerns << Concern.new(self)
|
64
|
+
end
|
65
|
+
@concerns.last
|
66
|
+
end
|
67
|
+
|
68
|
+
# Iterate over each test concern.
|
69
|
+
def each(&block)
|
70
|
+
@concerns.each(&block)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Define a unit test for this case.
|
74
|
+
def Unit(*targets, &block)
|
75
|
+
targets_hash = Hash===targets.last ? targets.pop : {}
|
76
|
+
targets_hash.each do |target_method, target_concern|
|
77
|
+
@testunits << Unit.new(current_concern, target_method, target_concern, &block)
|
78
|
+
end
|
79
|
+
targets.each do |target_method|
|
80
|
+
@testunits << Unit.new(current_concern, target_method, &block)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
alias_method :unit, :Unit
|
84
|
+
|
85
|
+
# Define a before procedure for this case.
|
86
|
+
def Before(match=nil, &block)
|
87
|
+
@before_clauses[match] = block #<< Advice.new(match, &block)
|
88
|
+
end
|
89
|
+
alias_method :before, :Before
|
90
|
+
|
91
|
+
# Define an after procedure for this case.
|
92
|
+
def After(match=nil, &block)
|
93
|
+
@after_clauses[match] = block #<< Advice.new(match, &block)
|
94
|
+
end
|
95
|
+
alias_method :after, :After
|
96
|
+
|
97
|
+
# Define a concern procedure to apply case-wide.
|
98
|
+
def When(match=nil, &block)
|
99
|
+
@when_clauses[match] = block #<< Advice.new(match, &block)
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
def to_s
|
104
|
+
target.to_s.sub(/^\#\<.*?\>::/, '')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|