lemon 0.6 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,185 @@
1
+ module Lemon
2
+
3
+ #
4
+ class Snapshot
5
+ include Enumerable
6
+
7
+ #
8
+ def self.capture
9
+ o = new
10
+ o.capture
11
+ o
12
+ end
13
+
14
+ #
15
+ attr :modules
16
+
17
+ #
18
+ def initialize
19
+ @modules = {}
20
+ end
21
+
22
+ #
23
+ def each(&block)
24
+ modules.values.each(&block)
25
+ end
26
+
27
+ #
28
+ def size
29
+ modules.size
30
+ end
31
+
32
+ #
33
+ def [](mod)
34
+ @modules[mod]
35
+ end
36
+
37
+ #
38
+ def []=(mod, ofmod)
39
+ @modules[mod] = ofmod
40
+ end
41
+
42
+ #
43
+ def capture
44
+ @modules = {}
45
+ ObjectSpace.each_object(Module) do |mod|
46
+ next if mod.name.empty?
47
+ @modules[mod] = OfModule.new(mod)
48
+ end
49
+ end
50
+
51
+ #
52
+ def to_a(public_only=true)
53
+ modules.values.map{ |m| m.to_a(public_only) }.flatten
54
+ end
55
+
56
+ # Produce a hash based checklist thap mod #.namet Coverage uses
57
+ # to compare against tests and create a coverage report.
58
+ def checklist(public_only=true)
59
+ h = Hash.new{|h,k|h[k]={}}
60
+ modules.values.each do |mod|
61
+ mod.class_methods(public_only).each do |meth|
62
+ h[mod.name]["::#{meth}"] = false
63
+ end
64
+ mod.instance_methods(public_only).each do |meth|
65
+ h[mod.name]["#{meth}"] = false
66
+ end
67
+ end
68
+ h
69
+ end
70
+
71
+ #
72
+ def -(other)
73
+ c = Snapshot.new
74
+ modules.each do |mod, ofmod|
75
+ c[mod] = ofmod.dup
76
+ end
77
+ other.modules.each do |mod, ofmod|
78
+ if c[mod]
79
+ c[mod].public_instance_methods -= ofmod.public_instance_methods
80
+ c[mod].private_instance_methods -= ofmod.private_instance_methods
81
+ c[mod].protected_instance_methods -= ofmod.protected_instance_methods
82
+
83
+ c[mod].public_class_methods -= ofmod.public_class_methods
84
+ c[mod].private_class_methods -= ofmod.private_class_methods
85
+ c[mod].protected_class_methods -= ofmod.protected_class_methods
86
+ end
87
+ end
88
+ c.clean!
89
+ return c
90
+ end
91
+
92
+ #
93
+ def <<(other)
94
+ other.modules.each do |mod, ofmod|
95
+ if self[mod]
96
+ self[mod].public_instance_methods += ofmod.public_instance_methods
97
+ self[mod].private_instance_methods += ofmod.private_instance_methods
98
+ self[mod].protected_instance_methods += ofmod.protected_instance_methods
99
+
100
+ self[mod].public_class_methods += ofmod.public_class_methods
101
+ self[mod].private_class_methods += ofmod.private_class_methods
102
+ self[mod].protected_class_methods += ofmod.protected_class_methods
103
+ else
104
+ self[mod] = ofmod.dup
105
+ end
106
+ end
107
+ end
108
+
109
+ #
110
+ def clean!
111
+ modules.each do |mod, ofmod|
112
+ if ofmod.class_methods(false).empty? && ofmod.instance_methods(false).empty?
113
+ modules.delete(mod)
114
+ end
115
+ end
116
+ end
117
+
118
+ #
119
+ def filter(&block)
120
+ c = Snapshot.new
121
+ modules.each do |mod, ofmod|
122
+ if block.call(ofmod)
123
+ c[mod] = ofmod
124
+ end
125
+ end
126
+ c
127
+ end
128
+
129
+ #
130
+ class OfModule
131
+ attr :base
132
+
133
+ attr_accessor :public_instance_methods
134
+ attr_accessor :protected_instance_methods
135
+ attr_accessor :private_instance_methods
136
+
137
+ attr_accessor :public_class_methods
138
+ attr_accessor :protected_class_methods
139
+ attr_accessor :private_class_methods
140
+
141
+ #
142
+ def initialize(base)
143
+ @base = base
144
+
145
+ @public_instance_methods = base.public_instance_methods(false)
146
+ @protected_instance_methods = base.protected_instance_methods(false)
147
+ @private_instance_methods = base.private_instance_methods(false)
148
+
149
+ @public_class_methods = base.methods(false)
150
+ @protected_class_methods = base.protected_methods(false)
151
+ @private_class_methods = base.private_methods(false)
152
+ end
153
+
154
+ #
155
+ def name
156
+ @base.name
157
+ end
158
+
159
+ #
160
+ def instance_methods(public_only=true)
161
+ if public_only
162
+ public_instance_methods
163
+ else
164
+ public_instance_methods + private_instance_methods + protected_instance_methods
165
+ end
166
+ end
167
+
168
+ #
169
+ def class_methods(public_only=true)
170
+ if public_only
171
+ public_class_methods
172
+ else
173
+ public_class_methods + private_class_methods + protected_class_methods
174
+ end
175
+ end
176
+
177
+ #
178
+ def to_a(public_only=true)
179
+ class_methods(public_only).map{ |m| "#{name}.#{m}" } + instance_methods(public_only).map{ |m| "#{name}##{m}" }
180
+ end
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -50,8 +50,9 @@ module Lemon::Test
50
50
  end
51
51
 
52
52
  # Define a new test concern for this case.
53
- def Concern(*description)
54
- concern = Concern.new(self, description)
53
+ # TODO: Probably will deprecate the &setup procedure (YAGNI).
54
+ def Concern(*description, &setup)
55
+ concern = Concern.new(self, description, &setup)
55
56
  @concerns << concern
56
57
  end
57
58
 
@@ -74,7 +75,7 @@ module Lemon::Test
74
75
  def Unit(*targets, &block)
75
76
  targets_hash = Hash===targets.last ? targets.pop : {}
76
77
  targets_hash.each do |target_method, target_concern|
77
- @testunits << Unit.new(current_concern, target_method, target_concern, &block)
78
+ @testunits << Unit.new(current_concern, target_method, :aspect=>target_concern, &block)
78
79
  end
79
80
  targets.each do |target_method|
80
81
  @testunits << Unit.new(current_concern, target_method, &block)
@@ -82,16 +83,36 @@ module Lemon::Test
82
83
  end
83
84
  alias_method :unit, :Unit
84
85
 
86
+ # Define a meta-method unit test for this case.
87
+ def MetaUnit(*targets, &block)
88
+ targets_hash = Hash===targets.last ? targets.pop : {}
89
+ targets_hash.each do |target_method, target_concern|
90
+ @testunits << Unit.new(current_concern, target_method, :aspect=>target_concern, :metaclass=>true, &block)
91
+ end
92
+ targets.each do |target_method|
93
+ @testunits << Unit.new(current_concern, target_method, :metaclass=>true, &block)
94
+ end
95
+ end
96
+ alias_method :metaunit, :MetaUnit
97
+
85
98
  # Define a before procedure for this case.
86
- def Before(match=nil, &block)
87
- @before_clauses[match] = block #<< Advice.new(match, &block)
99
+ def Before(*matches, &block)
100
+ matches == [nil] if matches.empty?
101
+ matches.each do |match|
102
+ @before_clauses[match] = block #<< Advice.new(match, &block)
103
+ end
88
104
  end
105
+
89
106
  alias_method :before, :Before
90
107
 
91
108
  # Define an after procedure for this case.
92
- def After(match=nil, &block)
93
- @after_clauses[match] = block #<< Advice.new(match, &block)
109
+ def After(*matches, &block)
110
+ matches == [nil] if matches.empty?
111
+ matches.each do |match|
112
+ @after_clauses[match] = block #<< Advice.new(match, &block)
113
+ end
94
114
  end
115
+
95
116
  alias_method :after, :After
96
117
 
97
118
  # Define a concern procedure to apply case-wide.
@@ -100,9 +121,9 @@ module Lemon::Test
100
121
  end
101
122
 
102
123
  #
103
- def pending
104
- raise PendingAssertion
105
- end
124
+ #def pending
125
+ # raise Pending
126
+ #end
106
127
 
107
128
  #
108
129
  def to_s
@@ -112,6 +133,7 @@ module Lemon::Test
112
133
 
113
134
  end
114
135
 
115
- class PendingAssertion < Assertion
136
+ class Pending < Assertion
137
+ def self.to_proc; lambda{ raise self }; end
116
138
  end
117
139
 
@@ -15,10 +15,14 @@ module Lemon::Test
15
15
  # Unit tests that belong to this concern.
16
16
  attr :testunits
17
17
 
18
+ # Setup procedure for concern.
19
+ attr :setup
20
+
18
21
  # New concern.
19
- def initialize(testcase, *description)
22
+ def initialize(testcase, *description, &setup)
20
23
  @testcase = testcase
21
24
  @description = description.join("\n")
25
+ @setup = setup
22
26
  @testunits = []
23
27
  end
24
28
 
@@ -38,6 +42,11 @@ module Lemon::Test
38
42
  description.gsub(/\n/, ' ')
39
43
  end
40
44
 
45
+ #
46
+ def call
47
+ setup.call if setup
48
+ end
49
+
41
50
  end
42
51
 
43
52
  end
@@ -8,6 +8,9 @@ module Test
8
8
  #
9
9
  class Suite
10
10
 
11
+ # Files from which the suite is loaded.
12
+ attr :files
13
+
11
14
  # Test cases in this suite.
12
15
  attr :testcases
13
16
 
@@ -20,46 +23,120 @@ module Test
20
23
  # List of post-test procedures that apply suite-wide.
21
24
  attr :after_clauses
22
25
 
26
+ ## A snapshot of the system before the suite is loaded.
27
+ #attr :canonical
28
+
29
+ # List of files to be covered. This primarily serves
30
+ # as a means for allowing one test to load another
31
+ # and ensuring converage remains accurate.
32
+ #attr :subtest
33
+
34
+ def coverage
35
+ @final_coveage ||= @coverage - @canonical
36
+ end
37
+
23
38
  #
24
- def initialize(*files)
39
+ #attr :options
40
+
41
+ #
42
+ def initialize(files, options={})
43
+ @files = files.flatten
44
+ @options = options
45
+
46
+ #@subtest = []
25
47
  @testcases = []
26
48
  @before_clauses = {}
27
49
  @after_clauses = {}
28
50
  @when_clauses = {}
29
51
 
30
- loadfiles(*files)
52
+ #load_helpers
53
+
54
+ @coverage = Snapshot.new
55
+ @canonical = Snapshot.capture
56
+
57
+ load_files
58
+ #load_subtest_helpers
31
59
  end
32
60
 
33
61
  #
34
- def loadfiles(*files)
35
- Lemon.suite = self
62
+ def cover?
63
+ @options[:cover]
64
+ end
36
65
 
37
- # directories glob *.rb files
38
- files = files.flatten.map do |file|
39
- if File.directory?(file)
40
- Dir[File.join(file, '**', '*.rb')]
41
- else
42
- file
43
- end
44
- end.flatten.uniq
66
+ #
67
+ #def load_helpers(*files)
68
+ # helpers = []
69
+ # filelist.each do |file|
70
+ # dir = File.dirname(file)
71
+ # hlp = Dir[File.join(dir, '{test_,}helper.rb')]
72
+ # helpers.concat(hlp)
73
+ # end
74
+ #
75
+ # helpers.each do |hlp|
76
+ # require hlp
77
+ # end
78
+ #end
45
79
 
46
- files.each do |file|
80
+ #def load_subtest_helpers
81
+ # helpers = []
82
+ # @subtest.each do |file|
83
+ # dir = File.dirname(file)
84
+ # hlp = Dir[File.join(dir, '{test_,}helper.rb')]
85
+ # helpers.concat(hlp)
86
+ # end
87
+ #
88
+ # #s = Snapshot.capture
89
+ # helpers.each do |hlp|
90
+ # require hlp
91
+ # end
92
+ # #z = Snapshot.capture
93
+ # #d = z - s
94
+ # #@canonical << d
95
+ #end
96
+
97
+ #
98
+ def load_files #(*files)
99
+ #if cover?
100
+ # $stdout << "Load: "
101
+ #end
102
+
103
+ Lemon.suite = self
104
+ filelist.each do |file|
105
+ #@current_file = file
47
106
  #file = File.expand_path(file)
48
107
  #instance_eval(File.read(file), file)
49
- load(file)
108
+ require(file) #load(file)
50
109
  end
51
110
 
111
+ #if cover?
112
+ # $stdout << "\n"
113
+ # $stdout.flush
114
+ #end
115
+
52
116
  return Lemon.suite
53
117
  end
54
118
 
55
- # Load a helper. This method must be used when loading local
56
- # suite support. The usual #require or #load can only be used
57
- # for extenal support libraries (such as a test mock framework).
58
- # This is so because suite code is not evaluated at the toplevel.
59
- def helper(file)
60
- instance_eval(File.read(file), file)
119
+ # Directories glob *.rb files.
120
+ def filelist
121
+ @filelist ||= (
122
+ @files.flatten.map do |file|
123
+ if File.directory?(file)
124
+ Dir[File.join(file, '**', '*.rb')]
125
+ else
126
+ file
127
+ end
128
+ end.flatten.uniq
129
+ )
61
130
  end
62
131
 
132
+ ## Load a helper. This method must be used when loading local
133
+ ## suite support. The usual #require or #load can only be used
134
+ ## for extenal support libraries (such as a test mock framework).
135
+ ## This is so because suite code is not evaluated at the toplevel.
136
+ #def helper(file)
137
+ # instance_eval(File.read(file), file)
138
+ #end
139
+
63
140
  #
64
141
  #def load(file)
65
142
  # instance_eval(File.read(file), file)
@@ -74,13 +151,12 @@ module Test
74
151
 
75
152
  # Define a test case belonging to this suite.
76
153
  def Case(target_class, &block)
154
+ raise "lemon: case target must be a class or module" unless Module === target_class
77
155
  testcases << Case.new(self, target_class, &block)
78
156
  end
79
157
 
80
158
  #
81
159
  alias_method :TestCase, :Case
82
-
83
- #
84
160
  #alias_method :testcase, :Case
85
161
 
86
162
  # Define a pre-test procedure to apply suite-wide.
@@ -102,6 +178,36 @@ module Test
102
178
  @when_clauses[match] = block #<< Advice.new(match, &block)
103
179
  end
104
180
 
181
+ # TODO: need require_find() to avoid first snapshot
182
+ def Covers(file)
183
+ if cover?
184
+ #return if $".include?(file)
185
+ s = Snapshot.capture
186
+ if require(file)
187
+ z = Snapshot.capture
188
+ @coverage << (z - s)
189
+ end
190
+ else
191
+ require file
192
+ end
193
+ end
194
+
195
+ #
196
+ def Helper(file)
197
+ local = File.join(File.dirname(caller[1]), file.to_str + '.rb')
198
+ if File.exist?(local)
199
+ require local
200
+ else
201
+ require file
202
+ end
203
+ end
204
+
205
+ #
206
+ #def Subtest(file)
207
+ # @subtest << file
208
+ # require file
209
+ #end
210
+
105
211
  # Iterate through this suite's test cases.
106
212
  def each(&block)
107
213
  @testcases.each(&block)