lemon 0.6 → 0.7.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/HISTORY +32 -1
- data/LICENSE +22 -0
- data/README.rdoc +30 -34
- data/lib/lemon/command.rb +16 -39
- data/lib/lemon/command/abstract.rb +29 -0
- data/lib/lemon/{commands → command}/coverage.rb +13 -14
- data/lib/lemon/{commands → command}/generate.rb +35 -19
- data/lib/lemon/{commands → command}/test.rb +34 -13
- data/lib/lemon/coverage.rb +175 -65
- data/lib/lemon/dsl.rb +15 -2
- data/lib/lemon/kernel.rb +16 -12
- data/lib/lemon/reporter.rb +17 -47
- data/lib/lemon/reporter/abstract.rb +92 -0
- data/lib/lemon/{reporters → reporter}/dotprogress.rb +5 -7
- data/lib/lemon/reporter/outline.rb +105 -0
- data/lib/lemon/reporter/verbose.rb +127 -0
- data/lib/lemon/runner.rb +195 -12
- data/lib/lemon/snapshot.rb +185 -0
- data/lib/lemon/test/case.rb +33 -11
- data/lib/lemon/test/concern.rb +10 -1
- data/lib/lemon/test/suite.rb +128 -22
- data/lib/lemon/test/unit.rb +43 -7
- data/meta/{author → authors} +0 -0
- data/meta/version +1 -1
- data/test/{cases/coverage_case.rb → coverage_case.rb} +4 -6
- data/test/{cases/testcase_case.rb → testcase_case.rb} +1 -1
- metadata +33 -31
- data/COPYING +0 -621
- data/COPYING.LESSER +0 -166
- data/MANIFEST +0 -43
- data/Syckfile +0 -68
- data/lib/lemon/reporters.rb +0 -3
- data/lib/lemon/reporters/verbose.rb +0 -92
@@ -1,12 +1,13 @@
|
|
1
1
|
module Lemon
|
2
|
-
module
|
2
|
+
module Command
|
3
|
+
require 'lemon/command/abstract'
|
3
4
|
|
4
5
|
# Lemon Test Command-line tool.
|
5
|
-
class Test <
|
6
|
+
class Test < Abstract
|
6
7
|
require 'lemon/runner'
|
7
8
|
|
8
|
-
def self.
|
9
|
-
|
9
|
+
def self.subcommand
|
10
|
+
'test'
|
10
11
|
end
|
11
12
|
|
12
13
|
# Initialize and run.
|
@@ -17,13 +18,18 @@ module Commands
|
|
17
18
|
# New Command instance.
|
18
19
|
def initialize
|
19
20
|
@format = nil
|
21
|
+
@cover = false
|
20
22
|
@requires = []
|
21
23
|
@includes = []
|
24
|
+
@namespaces = []
|
22
25
|
end
|
23
26
|
|
24
27
|
#
|
25
28
|
attr_accessor :format
|
26
29
|
|
30
|
+
#
|
31
|
+
attr_accessor :cover
|
32
|
+
|
27
33
|
# Get or set librarires to pre-require.
|
28
34
|
def requires(*paths)
|
29
35
|
@requires.concat(paths) unless paths.empty?
|
@@ -36,16 +42,33 @@ module Commands
|
|
36
42
|
@includes
|
37
43
|
end
|
38
44
|
|
45
|
+
#
|
46
|
+
def namespaces(*names)
|
47
|
+
@namespaces.concat(names) unless names.empty?
|
48
|
+
@namespaces
|
49
|
+
end
|
50
|
+
|
39
51
|
# Instance of OptionParser.
|
40
52
|
def parser
|
41
53
|
@parser ||= OptionParser.new do |opt|
|
54
|
+
opt.banner = "lemon [options] [test-files ...]"
|
42
55
|
opt.separator("Run unit tests.")
|
56
|
+
opt.separator("OPTIONS:")
|
57
|
+
opt.on('--coverage', '-c', "include coverage informarton") do
|
58
|
+
self.cover = true
|
59
|
+
end
|
43
60
|
opt.on('--verbose', '-v', "select verbose report format") do |type|
|
44
61
|
self.format = :verbose
|
45
62
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
63
|
+
opt.on('--outline', '-o', "select outline report format") do |type|
|
64
|
+
self.format = :outline
|
65
|
+
end
|
66
|
+
opt.on('--format', '-f [TYPE]', "select report format") do |type|
|
67
|
+
self.format = type
|
68
|
+
end
|
69
|
+
opt.on("--namespace", "-n [NAME]", "limit testing to this namespace") do |name|
|
70
|
+
namespaces(name)
|
71
|
+
end
|
49
72
|
opt.on("-r [FILES]" , 'library files to require') do |files|
|
50
73
|
files = files.split(/[:;]/)
|
51
74
|
requires(*files)
|
@@ -70,14 +93,12 @@ module Commands
|
|
70
93
|
|
71
94
|
files = ARGV.dup
|
72
95
|
|
73
|
-
includes.each
|
74
|
-
$LOAD_PATHS.unshift(path)
|
75
|
-
end
|
76
|
-
|
96
|
+
includes.each{ |path| $LOAD_PATH.unshift(path) }
|
77
97
|
requires.each{ |path| require(path) }
|
78
98
|
|
79
|
-
suite = Lemon::Test::Suite.new(
|
80
|
-
runner = Lemon::Runner.new(suite, format)
|
99
|
+
suite = Lemon::Test::Suite.new(files, :cover=>cover)
|
100
|
+
runner = Lemon::Runner.new(suite, :format=>format, :cover=>cover, :namespaces=>namespaces)
|
101
|
+
|
81
102
|
runner.run
|
82
103
|
end
|
83
104
|
|
data/lib/lemon/coverage.rb
CHANGED
@@ -1,33 +1,63 @@
|
|
1
|
+
require 'lemon/snapshot'
|
2
|
+
|
1
3
|
module Lemon
|
2
4
|
|
3
5
|
#
|
4
6
|
class Coverage
|
5
7
|
|
8
|
+
#
|
9
|
+
attr :suite
|
10
|
+
|
6
11
|
# Paths of lemon tests and/or ruby scripts to be compared and covered.
|
7
12
|
# This can include directories too, in which case all .rb scripts below
|
8
13
|
# then directory will be included.
|
9
14
|
attr :files
|
10
15
|
|
11
|
-
|
12
|
-
attr :
|
16
|
+
## Conical snapshot of system (before loading libraries to be covered).
|
17
|
+
#attr :canonical
|
13
18
|
|
14
19
|
#
|
15
20
|
attr :namespaces
|
16
21
|
|
22
|
+
## New Coverage object.
|
23
|
+
##
|
24
|
+
## Coverage.new('lib/', :MyApp, :public => true)
|
25
|
+
##
|
26
|
+
#def initialize(suite_or_files, namespaces=nil, options={})
|
27
|
+
# @namespaces = namespaces || []
|
28
|
+
# case suite_or_files
|
29
|
+
# when Test::Suite
|
30
|
+
# @suite = suite_or_files
|
31
|
+
# @files = suite_or_files.files
|
32
|
+
# else
|
33
|
+
# @suite = Test::Suite.new(suite_or_files)
|
34
|
+
# @files = suite_or_files
|
35
|
+
# end
|
36
|
+
# #@canonical = @suite.canonical
|
37
|
+
# @public = options[:public]
|
38
|
+
#end
|
39
|
+
|
17
40
|
# New Coverage object.
|
18
41
|
#
|
19
42
|
# Coverage.new('lib/', :MyApp, :public => true)
|
20
43
|
#
|
21
|
-
def initialize(
|
22
|
-
@namespaces = namespaces
|
23
|
-
|
24
|
-
@files
|
25
|
-
|
44
|
+
def initialize(suite, namespaces=nil, options={})
|
45
|
+
@namespaces = [namespaces].flatten.compact
|
46
|
+
@suite = suite
|
47
|
+
@files = suite.files
|
48
|
+
#@canonical = @suite.canonical
|
49
|
+
@public = options[:public]
|
50
|
+
end
|
26
51
|
|
27
|
-
|
52
|
+
##
|
53
|
+
#def canonical!
|
54
|
+
# @canonical = Snapshot.capture
|
55
|
+
#end
|
28
56
|
|
29
|
-
|
30
|
-
|
57
|
+
#
|
58
|
+
def suite=(suite)
|
59
|
+
raise ArgumentError unless Test::Suite === suite
|
60
|
+
@suite = suite
|
31
61
|
end
|
32
62
|
|
33
63
|
# Over use public methods for coverage.
|
@@ -35,37 +65,37 @@ module Lemon
|
|
35
65
|
@public
|
36
66
|
end
|
37
67
|
|
68
|
+
#
|
69
|
+
def each(&block)
|
70
|
+
checklist.each(&block)
|
71
|
+
end
|
72
|
+
|
38
73
|
# Produce a coverage map.
|
39
|
-
def
|
40
|
-
|
74
|
+
#def checklist
|
75
|
+
# list = system.checklist
|
76
|
+
# suite.each do |testcase|
|
77
|
+
# testcase.testunits.each do |testunit|
|
78
|
+
# list[testcase.target.name][testunit.key] = true
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
# list
|
82
|
+
#end
|
83
|
+
|
84
|
+
# Produce a coverage checklist.
|
85
|
+
def checklist
|
86
|
+
list = system.checklist
|
41
87
|
suite.each do |testcase|
|
42
88
|
testcase.testunits.each do |testunit|
|
43
|
-
|
89
|
+
list[testcase.target.name][testunit.key] = true
|
44
90
|
end
|
45
91
|
end
|
46
|
-
|
92
|
+
list
|
47
93
|
end
|
48
94
|
|
49
|
-
#
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
next if base.is_a?(Lemon::Test::Suite)
|
54
|
-
cover[base.name] = {}
|
55
|
-
base.public_instance_methods(false).each do |meth|
|
56
|
-
cover[base.name][meth.to_s] = false
|
57
|
-
end
|
58
|
-
unless public_only?
|
59
|
-
base.private_instance_methods(false).each do |meth|
|
60
|
-
cover[base.name][meth.to_s] = false
|
61
|
-
end
|
62
|
-
base.protected_instance_methods(false).each do |meth|
|
63
|
-
cover[base.name][meth.to_s] = false
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
cover
|
68
|
-
end
|
95
|
+
# #
|
96
|
+
# def load_covered_files
|
97
|
+
# suite.load_covered_files
|
98
|
+
# end
|
69
99
|
|
70
100
|
# Iterate over +paths+ and use #load to bring in all +.rb+ scripts.
|
71
101
|
#def load_system
|
@@ -80,57 +110,137 @@ module Lemon
|
|
80
110
|
# files.each{ |file| load(file) }
|
81
111
|
#end
|
82
112
|
|
83
|
-
# System to be covered. This takes a
|
84
|
-
# and
|
85
|
-
# the namespace.
|
113
|
+
# # Snapshot of System to be covered. This takes a current snapshot
|
114
|
+
# # of the system and removes the canonical snapshot or filters out
|
115
|
+
# # everything but the selected namespace.
|
116
|
+
# def system
|
117
|
+
# if namespaces.empty?
|
118
|
+
# snapshot - canonical
|
119
|
+
# else
|
120
|
+
# (snapshot - canonical).filter do |ofmod|
|
121
|
+
# namespaces.any?{ |n| ofmod.name.start_with?(n.to_s) }
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
# end
|
125
|
+
|
126
|
+
# List of modules/classes not covered.
|
127
|
+
def uncovered_cases
|
128
|
+
c = suite.coverage.map{ |ofmod| ofmod.base }
|
129
|
+
a = suite.testcases.map{ |tc| tc.target }
|
130
|
+
c - a
|
131
|
+
end
|
132
|
+
|
133
|
+
# List of methods not covered by covered cases.
|
134
|
+
def uncovered_units
|
135
|
+
@calculated ||= calculate_coverage
|
136
|
+
@uncovered_units
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
def undefined_units
|
141
|
+
@calculated ||= calculate_coverage
|
142
|
+
@undefined_units
|
143
|
+
end
|
144
|
+
|
145
|
+
#
|
146
|
+
def calculate_coverage
|
147
|
+
clist = []
|
148
|
+
tlist = []
|
149
|
+
suite.testcases.each do |tc|
|
150
|
+
mod = tc.target
|
151
|
+
|
152
|
+
metaunits, units = *tc.testunits.partition{ |u| u.meta? }
|
153
|
+
|
154
|
+
units.map!{ |u| u.fullname }
|
155
|
+
metaunits.map!{ |u| u.fullname }
|
156
|
+
|
157
|
+
tlist = tlist | units
|
158
|
+
tlist = tlist | metaunits
|
159
|
+
|
160
|
+
if system[mod]
|
161
|
+
meths = system[mod].instance_methods.map{ |m| "#{mod}##{m}" }
|
162
|
+
metameths = system[mod].class_methods.map{ |m| "#{mod}.#{m}" }
|
163
|
+
|
164
|
+
clist = clist | meths
|
165
|
+
clist = clist | metameths
|
166
|
+
end
|
167
|
+
end
|
168
|
+
@uncovered_units = clist - tlist
|
169
|
+
@undefined_units = tlist - clist
|
170
|
+
end
|
171
|
+
|
86
172
|
#
|
87
|
-
# TODO: Perhaps get rid of the conical subtraction and require a namespace?
|
88
173
|
def system
|
89
174
|
if namespaces.empty?
|
90
|
-
|
175
|
+
suite.coverage
|
91
176
|
else
|
92
|
-
|
93
|
-
namespaces.any?{ |n|
|
177
|
+
suite.coverage.filter do |ofmod|
|
178
|
+
namespaces.any?{ |n| ofmod.name.start_with?(n.to_s) }
|
94
179
|
end
|
95
180
|
end
|
96
181
|
end
|
97
182
|
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
183
|
+
# Generate code template.
|
184
|
+
#
|
185
|
+
# TODO: support output
|
186
|
+
def generate(output=nil)
|
187
|
+
code = []
|
188
|
+
|
189
|
+
system.each do |ofmod|
|
190
|
+
next if ofmod.base.is_a?(Lemon::Test::Suite)
|
191
|
+
|
192
|
+
code << "TestCase #{ofmod.base} do"
|
193
|
+
|
194
|
+
ofmod.class_methods(public_only?).each do |meth|
|
195
|
+
code << "\n MetaUnit :#{meth} => '' do\n raise Pending\n end"
|
196
|
+
end
|
197
|
+
|
198
|
+
ofmod.instance_methods(public_only?).each do |meth|
|
199
|
+
code << "\n Unit :#{meth} => '' do\n raise Pending\n end"
|
200
|
+
end
|
201
|
+
|
202
|
+
code << "\nend\n"
|
106
203
|
end
|
107
|
-
sys
|
108
|
-
end
|
109
204
|
|
110
|
-
|
111
|
-
|
205
|
+
code.join("\n")
|
206
|
+
end
|
112
207
|
|
113
|
-
|
208
|
+
#
|
209
|
+
def generate_uncovered(output=nil)
|
114
210
|
code = []
|
115
|
-
|
116
|
-
next if
|
211
|
+
checklist.each do |base, methods|
|
212
|
+
next if /Lemon::Test::Suite/ =~ base.to_s
|
117
213
|
code << "TestCase #{base} do"
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
base.protected_instance_methods(false).each do |meth|
|
126
|
-
code << "\n Unit :#{meth} => '' do\n pending\n end"
|
214
|
+
methods.each do |meth, covered|
|
215
|
+
next if covered
|
216
|
+
if meth.to_s =~ /^\:\:/
|
217
|
+
meth = meth.sub('::','')
|
218
|
+
code << "\n MetaUnit :#{meth} => '' do\n raise Pending\n end"
|
219
|
+
else
|
220
|
+
code << "\n Unit :#{meth} => '' do\n raise Pending\n end"
|
127
221
|
end
|
128
222
|
end
|
223
|
+
#base.public_instance_methods(false).each do |meth|
|
224
|
+
# code << "\n Unit :#{meth} => '' do\n Pending\n end"
|
225
|
+
#end
|
226
|
+
#unless public_only?
|
227
|
+
# base.private_instance_methods(false).each do |meth|
|
228
|
+
# code << "\n Unit :#{meth} => '' do\n Pending\n end"
|
229
|
+
# end
|
230
|
+
# base.protected_instance_methods(false).each do |meth|
|
231
|
+
# code << "\n Unit :#{meth} => '' do\n Pending\n end"
|
232
|
+
# end
|
233
|
+
#end
|
129
234
|
code << "\nend\n"
|
130
235
|
end
|
131
236
|
code.join("\n")
|
132
237
|
end
|
133
238
|
|
239
|
+
# Get a snapshot of the system.
|
240
|
+
def snapshot
|
241
|
+
Snapshot.capture
|
242
|
+
end
|
243
|
+
|
134
244
|
end#class Coverage
|
135
245
|
|
136
246
|
end#module Lemon
|
data/lib/lemon/dsl.rb
CHANGED
@@ -28,12 +28,25 @@ end
|
|
28
28
|
def Case(target_class, &block)
|
29
29
|
Lemon.suite.Case(target_class, &block)
|
30
30
|
end
|
31
|
-
|
32
31
|
alias :TestCase :Case
|
33
32
|
|
33
|
+
#
|
34
|
+
def Covers(script)
|
35
|
+
Lemon.suite.Covers(script)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
def Helper(script)
|
40
|
+
Lemon.suite.Helper(script)
|
41
|
+
end
|
42
|
+
|
43
|
+
#def Subtest(script)
|
44
|
+
# Lemon.suite.Subtest(script)
|
45
|
+
#end
|
46
|
+
|
34
47
|
# FIXME: This is a BIG FAT HACK! For the life of me I cannot find
|
35
48
|
# a way to resolve module constants included in the test cases.
|
36
|
-
#
|
49
|
+
# Because of closure, the constant lookup goes through here, and not
|
37
50
|
# the Case singleton class. So to work around we must note each test
|
38
51
|
# before it is run, and reroute the missing constants.
|
39
52
|
#
|
data/lib/lemon/kernel.rb
CHANGED
@@ -2,19 +2,23 @@ require 'facets/functor'
|
|
2
2
|
|
3
3
|
$PRY_TABLE = {} #Hash.new{|h,k| h[k]=nil}
|
4
4
|
|
5
|
-
|
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.
|
5
|
+
module Kernel
|
14
6
|
|
15
|
-
|
16
|
-
|
17
|
-
|
7
|
+
# Pry allows you to test private and protected methods,
|
8
|
+
# via a public-only interface.
|
9
|
+
#
|
10
|
+
# Generally one should avoid testing private and protected
|
11
|
+
# method directly, instead relying on tests of public methods to
|
12
|
+
# indirectly test them, because private and protected methods are
|
13
|
+
# considered implementation details. But sometimes is necessary
|
14
|
+
# to test them directly, or if you wish to achieve *absolute
|
15
|
+
# coverage*, say in mission critical systems.
|
16
|
+
|
17
|
+
def pry
|
18
|
+
$PRY_TABLE[self] ||= Functor.new do |op, *a, &b|
|
19
|
+
__send__(op, *a, &b)
|
20
|
+
end
|
18
21
|
end
|
22
|
+
|
19
23
|
end
|
20
24
|
|