lemon 0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|