lemon 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/HISTORY.rdoc +15 -0
  2. data/README.rdoc +32 -14
  3. data/bin/lemon +3 -2
  4. data/demo/case_example_fail.rb +15 -0
  5. data/demo/case_example_pass.rb +32 -0
  6. data/demo/case_example_pending.rb +14 -0
  7. data/demo/case_example_untested.rb +10 -0
  8. data/demo/fixture/example-use.rb +5 -0
  9. data/demo/fixture/example.rb +20 -0
  10. data/lib/lemon.rb +2 -2
  11. data/lib/lemon/cli.rb +281 -0
  12. data/lib/lemon/controller/coverage_analyzer.rb +343 -0
  13. data/lib/lemon/controller/scaffold_generator.rb +110 -0
  14. data/lib/lemon/controller/test_runner.rb +284 -0
  15. data/lib/lemon/meta/data.rb +29 -0
  16. data/lib/lemon/meta/gemfile +24 -0
  17. data/{PROFILE → lib/lemon/meta/profile} +6 -5
  18. data/lib/lemon/model/ae.rb +4 -0
  19. data/lib/lemon/model/cover_unit.rb +75 -0
  20. data/lib/lemon/{dsl.rb → model/main.rb} +22 -28
  21. data/lib/lemon/model/pending.rb +10 -0
  22. data/lib/lemon/model/snapshot.rb +203 -0
  23. data/lib/lemon/model/source_parser.rb +198 -0
  24. data/lib/lemon/model/test_case.rb +221 -0
  25. data/lib/lemon/model/test_context.rb +90 -0
  26. data/lib/lemon/model/test_suite.rb +216 -0
  27. data/lib/lemon/{test/unit.rb → model/test_unit.rb} +40 -28
  28. data/lib/lemon/{coversheet → view/cover_reports}/abstract.rb +19 -20
  29. data/lib/lemon/view/cover_reports/compact.rb +37 -0
  30. data/lib/lemon/view/cover_reports/outline.rb +45 -0
  31. data/lib/lemon/view/cover_reports/verbose.rb +51 -0
  32. data/lib/lemon/view/cover_reports/yaml.rb +15 -0
  33. data/lib/lemon/view/test_reports/abstract.rb +149 -0
  34. data/lib/lemon/view/test_reports/dotprogress.rb +73 -0
  35. data/lib/lemon/view/test_reports/html.rb +146 -0
  36. data/lib/lemon/view/test_reports/outline.rb +118 -0
  37. data/lib/lemon/view/test_reports/summary.rb +131 -0
  38. data/lib/lemon/view/test_reports/tap.rb +49 -0
  39. data/lib/lemon/view/test_reports/verbose.rb +197 -0
  40. data/meta/data.rb +29 -0
  41. data/meta/gemfile +24 -0
  42. data/meta/profile +17 -0
  43. data/test/api/applique/fs.rb +18 -0
  44. data/test/api/coverage/complete.rdoc +136 -0
  45. data/test/api/coverage/extensions.rdoc +61 -0
  46. data/test/api/coverage/incomplete.rdoc +97 -0
  47. data/{features → test/cli}/coverage.feature +4 -4
  48. data/{features → test/cli}/generate.feature +2 -2
  49. data/{features → test/cli}/step_definitions/coverage_steps.rb +0 -0
  50. data/{features → test/cli}/support/ae.rb +0 -0
  51. data/{features → test/cli}/support/aruba.rb +0 -0
  52. data/{features → test/cli}/test.feature +0 -0
  53. data/test/fixtures/case_complete.rb +17 -4
  54. data/test/fixtures/case_inclusion.rb +18 -0
  55. data/test/fixtures/case_incomplete.rb +4 -4
  56. data/test/fixtures/example.rb +5 -0
  57. data/test/fixtures/helper.rb +13 -0
  58. data/test/runner +3 -0
  59. data/test/unit/case_coverage_analyzer.rb +25 -0
  60. data/test/unit/case_test_case_dsl.rb +46 -0
  61. metadata +87 -42
  62. data/REQUIRE +0 -9
  63. data/VERSION +0 -6
  64. data/lib/lemon/command.rb +0 -184
  65. data/lib/lemon/coverage.rb +0 -260
  66. data/lib/lemon/coversheet/outline.rb +0 -47
  67. data/lib/lemon/kernel.rb +0 -24
  68. data/lib/lemon/reporter.rb +0 -22
  69. data/lib/lemon/reporter/abstract.rb +0 -97
  70. data/lib/lemon/reporter/dotprogress.rb +0 -68
  71. data/lib/lemon/reporter/outline.rb +0 -105
  72. data/lib/lemon/reporter/verbose.rb +0 -143
  73. data/lib/lemon/runner.rb +0 -308
  74. data/lib/lemon/snapshot.rb +0 -185
  75. data/lib/lemon/test/case.rb +0 -139
  76. data/lib/lemon/test/concern.rb +0 -52
  77. data/lib/lemon/test/suite.rb +0 -229
  78. data/test/case_coverage.rb +0 -26
  79. data/test/case_testcase.rb +0 -58
@@ -0,0 +1,10 @@
1
+ require 'lemon/model/ae'
2
+
3
+ class Pending < Assertion
4
+ def self.to_proc; lambda{ raise self }; end
5
+ end
6
+
7
+ class Untested < Pending
8
+ def self.to_proc; lambda{ raise self }; end
9
+ end
10
+
@@ -0,0 +1,203 @@
1
+ module Lemon
2
+
3
+ # Snapshot is used to record the "unit picture" of the system
4
+ # at a given moment.
5
+ class Snapshot
6
+
7
+ include Enumerable
8
+
9
+ #
10
+ def self.capture(namespaces=nil)
11
+ o = new
12
+ o.capture(namespaces)
13
+ o
14
+ end
15
+
16
+ #
17
+ attr :units
18
+
19
+ #
20
+ def initialize(units=[])
21
+ @units = units
22
+ end
23
+
24
+ #
25
+ def each(&block)
26
+ units.each(&block)
27
+ end
28
+
29
+ #
30
+ def size
31
+ @units.size
32
+ end
33
+
34
+ #
35
+ def reset
36
+ each{ |u| u.covered = false }
37
+ end
38
+
39
+ # Select units by namespace (i.e. module or class).
40
+ #def [](mod)
41
+ # @units.select{ |u| u.namespace == mod }
42
+ #end
43
+
44
+ #
45
+ def capture(namespaces=nil)
46
+ @units = []
47
+ ObjectSpace.each_object(Module) do |mod|
48
+ next if mod.name.empty?
49
+ #next if namespaces and !namespaces.any?{ |ns| /^#{ns}(::|$)/ =~ mod.to_s }
50
+ next if namespaces and !namespaces.any?{ |ns| ns.to_s == mod.to_s }
51
+ capture_namespace(mod)
52
+ end
53
+ end
54
+
55
+ #
56
+ def capture_namespace(mod)
57
+ ['', 'protected_', 'private_'].each do |access|
58
+ methods = mod.__send__("#{access}instance_methods", false)
59
+ #methods -= Object.__send__("#{access}instance_methods", true)
60
+ methods.each do |method|
61
+ @units << Unit.new(mod, method, :access=>access)
62
+ end
63
+
64
+ methods = mod.__send__("#{access}methods", false)
65
+ #methods -= Object.__send__("#{access}methods", true)
66
+ methods.each do |method|
67
+ @units << Unit.new(mod, method, :access=>access, :function=>true)
68
+ end
69
+ end
70
+ return @units
71
+ end
72
+
73
+ #
74
+ def to_a
75
+ @units
76
+ end
77
+
78
+ #
79
+ def public_units
80
+ @units.select{ |u| u.public? }
81
+ end
82
+
83
+ #
84
+ def -(other)
85
+ Snapshot.new(units - other.units)
86
+ end
87
+
88
+ #
89
+ def +(other)
90
+ Snapshot.new(units + other.units)
91
+ end
92
+
93
+ #
94
+ def <<(other)
95
+ @units.concat(other.units)
96
+ end
97
+
98
+ # Snapshot Unit encapsulates a method and it's various characteristics.
99
+ class Unit
100
+
101
+ # Clsss or module.
102
+ attr :target
103
+
104
+ # Method name.
105
+ attr :method
106
+
107
+ # Is the method a "class method", rather than an instance method.
108
+ attr :function
109
+
110
+ def initialize(target, method, props={})
111
+ @target = target
112
+ @method = method.to_sym
113
+ @function = props[:function] ? true : false
114
+ @covered = props[:covered]
115
+
116
+ if @function
117
+ @private = @target.private_methods.find{ |m| m.to_sym == @method }
118
+ @protected = @target.protected_methods.find{ |m| m.to_sym == @method }
119
+ else
120
+ @private = @target.private_instance_methods.find{ |m| m.to_sym == @method }
121
+ @protected = @target.protected_instance_methods.find{ |m| m.to_sym == @method }
122
+ end
123
+ end
124
+
125
+ # Can be used to flag a unit as covered.
126
+ attr_accessor :covered
127
+
128
+ # Alternate name for target.
129
+ def namespace
130
+ @target
131
+ end
132
+
133
+ #
134
+ def function?
135
+ @function
136
+ end
137
+
138
+ # Method access is public?
139
+ def public?
140
+ !(@private or @protected)
141
+ end
142
+
143
+ # Method access is public?
144
+ def private?
145
+ @private
146
+ end
147
+
148
+ #
149
+ def protected?
150
+ @protected
151
+ end
152
+
153
+ #
154
+ def access
155
+ return :private if private?
156
+ return :protected if protected?
157
+ :public
158
+ end
159
+
160
+ # Marked as covered?
161
+ def covered?
162
+ @covered
163
+ end
164
+
165
+ #
166
+ def hash
167
+ @target.hash ^ @method.hash ^ @function.hash
168
+ end
169
+
170
+ #
171
+ def to_s
172
+ if @function
173
+ "#{@target}.#{@method}"
174
+ else
175
+ "#{@target}##{@method}"
176
+ end
177
+ end
178
+ alias to_str to_s
179
+
180
+ def eql?(other)
181
+ return false unless Unit === other
182
+ return false unless target == other.target
183
+ return false unless method == other.method
184
+ return false unless function == other.function
185
+ return true
186
+ end
187
+
188
+ def inspect
189
+ "#{target}#{function ? '.' : '#'}#{method}"
190
+ end
191
+
192
+ def <=>(other)
193
+ c = (target.name <=> other.target.name)
194
+ return c unless c == 0
195
+ return -1 if function && !other.function
196
+ return 1 if !function && other.function
197
+ method.to_s <=> other.method.to_s
198
+ end
199
+ end
200
+
201
+ end
202
+
203
+ end
@@ -0,0 +1,198 @@
1
+ module Lemon
2
+
3
+ require 'ruby_parser'
4
+
5
+ #
6
+ class SourceParser
7
+ # Converts Ruby code into a data structure.
8
+ #
9
+ # text - A String of Ruby code.
10
+ #
11
+ # Returns a Hash with each key a namespace and each value another
12
+ # Hash or a TomDoc::Scope.
13
+ def self.parse(text)
14
+ new.parse(text)
15
+ end
16
+
17
+ attr_accessor :parser, :scopes, :options
18
+
19
+ # Each instance of SourceParser accumulates scopes with each
20
+ # parse, making it easy to parse an entire project in chunks but
21
+ # more difficult to parse disparate files in one go. Create
22
+ # separate instances for separate global scopes.
23
+ #
24
+ # Returns an instance of TomDoc::SourceParser
25
+ def initialize(options = {})
26
+ @options = {}
27
+ @parser = RubyParser.new
28
+ @scopes = {}
29
+ end
30
+
31
+ # Resets the state of the parser to a pristine one. Maintains options.
32
+ #
33
+ # Returns nothing.
34
+ def reset
35
+ initialize(@options)
36
+ end
37
+
38
+ # Converts Ruby code into a data structure. Note that at the
39
+ # instance level scopes accumulate, which makes it easy to parse
40
+ # multiple files in a single project but harder to parse files
41
+ # that have no connection.
42
+ #
43
+ # text - A String of Ruby code.
44
+ #
45
+ # Examples
46
+ # @parser = TomDoc::SourceParser.new
47
+ # files.each do |file|
48
+ # @parser.parse(File.read(file))
49
+ # end
50
+ # pp @parser.scopes
51
+ #
52
+ # Returns a Hash with each key a namespace and each value another
53
+ # Hash or a TomDoc::Scope.
54
+ def parse(text)
55
+ process(tokenize(sexp(text)))
56
+ @scopes
57
+ end
58
+
59
+ # Converts Ruby sourcecode into an AST.
60
+ #
61
+ # text - A String of Ruby source.
62
+ #
63
+ # Returns a Sexp representing the AST.
64
+ def sexp(text)
65
+ @parser.parse(text)
66
+ end
67
+
68
+ # Converts a tokenized Array of classes, modules, and methods into
69
+ # Scopes and Methods, adding them to the @scopes instance variable
70
+ # as it works.
71
+ #
72
+ # ast - Tokenized Array produced by calling `tokenize`.
73
+ # scope - An optional Scope object for nested classes or modules.
74
+ #
75
+ # Returns nothing.
76
+ def process(ast, scope = nil)
77
+ case Array(ast)[0]
78
+ when :module, :class
79
+ name = ast[1]
80
+ new_scope = Scope.new(name, ast[2])
81
+
82
+ if scope
83
+ scope.scopes[name] = new_scope
84
+ elsif @scopes[name]
85
+ new_scope = @scopes[name]
86
+ else
87
+ @scopes[name] = new_scope
88
+ end
89
+
90
+ process(ast[3], new_scope)
91
+ when :imethod
92
+ ast.shift
93
+ scope.instance_methods << Method.new(*ast)
94
+ when :cmethod
95
+ ast.shift
96
+ scope.class_methods << Method.new(*ast)
97
+ when Array
98
+ ast.map { |a| process(a, scope) }
99
+ end
100
+ end
101
+
102
+ # Converts a Ruby AST-style Sexp into an Array of more useful tokens.
103
+ #
104
+ # node - A Ruby AST Sexp or Array
105
+ #
106
+ # Examples
107
+ #
108
+ # [:module, :Math, "",
109
+ # [:class, :Multiplexer, "# Class Comment",
110
+ # [:cmethod,
111
+ # :multiplex, "# Class Method Comment", [:text]],
112
+ # [:imethod,
113
+ # :multiplex, "# Instance Method Comment", [:text, :count]]]]
114
+ #
115
+ # # In others words:
116
+ # # [ :type, :name, :comment, other ]
117
+ #
118
+ # Returns an Array in the above format.
119
+ def tokenize(node)
120
+ case Array(node)[0]
121
+ when :module
122
+ name = node[1]
123
+ [ :module, name, node.comments, tokenize(node[2]) ]
124
+ when :class
125
+ name = node[1]
126
+ [ :class, name, node.comments, tokenize(node[3]) ]
127
+ when :defn
128
+ name = node[1]
129
+ args = args_for_node(node[2])
130
+ [ :imethod, name, node.comments, args ]
131
+ when :defs
132
+ name = node[2]
133
+ args = args_for_node(node[3])
134
+ [ :cmethod, name, node.comments, args ]
135
+ when :block
136
+ tokenize(node[1..-1])
137
+ when :scope
138
+ tokenize(node[1])
139
+ when Array
140
+ node.map { |n| tokenize(n) }.compact
141
+ end
142
+ end
143
+
144
+ # Given a method sexp, returns an array of the args.
145
+ def args_for_node(node)
146
+ Array(node)[1..-1].select { |arg| arg.is_a? Symbol }
147
+ end
148
+
149
+ # A Scope is a Module or Class.
150
+ # It may contain other scopes.
151
+ class Scope
152
+ include Enumerable
153
+
154
+ attr_accessor :name, :comment, :instance_methods, :class_methods
155
+ attr_accessor :scopes
156
+
157
+ def initialize(name, comment = '', instance_methods = [], class_methods = [])
158
+ @name = name
159
+ @comment = comment
160
+ @instance_methods = instance_methods
161
+ @class_methods = class_methods
162
+ @scopes = {}
163
+ end
164
+
165
+ def tomdoc
166
+ @tomdoc ||= TomDoc.new(@comment)
167
+ end
168
+
169
+ def [](scope)
170
+ @scopes[scope]
171
+ end
172
+
173
+ def keys
174
+ @scopes.keys
175
+ end
176
+
177
+ def each(&block)
178
+ @scopes.each(&block)
179
+ end
180
+
181
+ def to_s
182
+ inspect
183
+ end
184
+
185
+ def inspect
186
+ scopes = @scopes.keys.join(', ')
187
+ imethods = @instance_methods.inspect
188
+ cmethods = @class_methods.inspect
189
+
190
+ "<#{name} scopes:[#{scopes}] :#{cmethods}: ##{imethods}#>"
191
+ end
192
+
193
+ end
194
+
195
+ end
196
+
197
+ end
198
+
@@ -0,0 +1,221 @@
1
+ require 'lemon/model/pending'
2
+ require 'lemon/model/test_context'
3
+ require 'lemon/model/test_unit'
4
+
5
+ module Lemon
6
+
7
+ # Test Case encapsulates a collection of
8
+ # unit tests organized into groups of contexts.
9
+ class TestCase
10
+
11
+ # The test suite to which this testcase belongs.
12
+ attr :suite
13
+
14
+ # A testcase +target+ is a class or module.
15
+ attr :target
16
+
17
+ # Description of the aspect of the test class/module
18
+ # to be testd.
19
+ attr :aspect
20
+
21
+ # Ordered list of testunits.
22
+ attr :units
23
+
24
+ # Before any test case units are run.
25
+ attr :before
26
+ #attr_accessor :prepare
27
+
28
+ # After all test case units are run.
29
+ attr :after
30
+ #attr_accessor :cleanup
31
+
32
+ # Module for parsing test case scripts.
33
+ attr :dsl
34
+
35
+ # A test case +target+ is a class or module.
36
+ def initialize(suite, target, aspect=nil, &block)
37
+ @suite = suite
38
+ @target = target
39
+ @aspect = aspect
40
+
41
+ #@steps = []
42
+ @units = []
43
+
44
+ #@prepare = nil
45
+ #@cleanup = nil
46
+
47
+ @before = {}
48
+ @after = {}
49
+
50
+ @dsl = DSL.new(self, &block)
51
+ end
52
+
53
+ # DEPRECATE
54
+ alias_method :testunits, :units
55
+
56
+ # Iterate over each test unit.
57
+ def each(&block)
58
+ units.each(&block)
59
+ end
60
+
61
+ #
62
+ def size
63
+ testunits.size
64
+ end
65
+
66
+ #
67
+ def to_s
68
+ target.to_s.sub(/^\#\<.*?\>::/, '')
69
+ end
70
+
71
+ #
72
+ def prepare
73
+ @before[[]]
74
+ end
75
+
76
+ #
77
+ def cleanup
78
+ @after[[]]
79
+ end
80
+
81
+ #
82
+ class DSL < Module
83
+ #
84
+ def initialize(testcase, &casecode)
85
+ @testcase = testcase
86
+ @context = nil #Instance.new(self)
87
+ module_eval(&casecode)
88
+ end
89
+
90
+ # Define a unit test for this case.
91
+ def unit(*target, &block)
92
+ target = target.map{ |x| Hash === x ? x.to_a : x }.flatten
93
+ method, aspect = *target
94
+ unit = TestUnit.new(
95
+ @testcase, method,
96
+ :function => false,
97
+ :aspect => aspect,
98
+ :context => @context,
99
+ &block
100
+ )
101
+ #@testcase.steps << unit
102
+ @testcase.units << unit
103
+ unit
104
+ end
105
+ alias_method :TestUnit, :unit
106
+ alias_method :testunit, :unit
107
+ alias_method :Unit, :unit
108
+
109
+ # Define a meta-method unit test for this case.
110
+ def meta(*target, &block)
111
+ target = target.map{ |x| Hash === x ? x.to_a : x }.flatten
112
+ method, aspect = *target
113
+ unit = TestUnit.new(
114
+ @testcase, method,
115
+ :function => true,
116
+ :aspect => aspect,
117
+ :context => @context,
118
+ &block
119
+ )
120
+ #@testcase.steps << unit
121
+ @testcase.units << unit
122
+ unit
123
+ end
124
+ alias_method :MetaUnit, :meta
125
+ alias_method :metaunit, :meta
126
+ alias_method :Meta, :meta
127
+
128
+ # Omit a unit from testing.
129
+ #
130
+ # omit unit :foo do
131
+ # # ...
132
+ # end
133
+ #
134
+ def Omit(unit)
135
+ unit.omit = true
136
+ end
137
+ alias_method :omit, :Omit
138
+
139
+ #
140
+ def setup(description=nil, &block)
141
+ if block
142
+ context = TestContext.new(@testcase, description, &block)
143
+ @context = context
144
+ #@function = false
145
+ #@testcase.steps << context
146
+ end
147
+ end
148
+ alias_method :Setup, :setup
149
+ alias_method :Concern, :setup
150
+ alias_method :concern, :setup
151
+ alias_method :Context, :setup
152
+ alias_method :context, :setup
153
+
154
+ ## Define a new test instance for this case.
155
+ #def instance(description=nil, &block)
156
+ # context = TestInstance.new(@testcase, description, &block)
157
+ # @context = context
158
+ # @function = false
159
+ # #@testcase.steps << context
160
+ #end
161
+ #alias_method :Instance, :instance
162
+
163
+ # Define a new test singleton for this case.
164
+ #def Singleton(description=nil, &block)
165
+ # context = TestSingleton.new(@testcase, description, &block)
166
+ # @context = context
167
+ # @function = true
168
+ # #@testcase.steps << context
169
+ #end
170
+ #alias_method :singleton, :Singleton
171
+
172
+ def teardown(&block)
173
+ @context.teardown = block
174
+ end
175
+ alias_method :Teardown, :teardown
176
+
177
+ # TODO: Make Before and After more generic to handle before and after
178
+ # units, contexts/concerns, etc.
179
+
180
+ # Define a before procedure for this case.
181
+ def before(*matches, &block)
182
+ @testcase.before[matches] = block
183
+ end
184
+ alias_method :Before, :before
185
+
186
+ # Define an after procedure for this case.
187
+ def after(*matches, &block)
188
+ @testcase.after[matches] = block
189
+ end
190
+ alias_method :After, :after
191
+
192
+ #
193
+ #def prepare(&block)
194
+ # @testcase.prepare = block
195
+ #end
196
+ #alias_method :Prepare, :prepare
197
+
198
+ #
199
+ #def cleanup(&block)
200
+ # @testcase.cleanup = block
201
+ #end
202
+ #alias_method :Cleanup, :cleanup
203
+
204
+ # Load a helper script applicable to this test case.
205
+ def helper(file)
206
+ instance_eval(File.read(file), file)
207
+ end
208
+ alias_method :Helper, :helper
209
+
210
+ #def include(*mods)
211
+ # extend *mods
212
+ #end
213
+
214
+ #def pending(message=nil)
215
+ # raise Pending.new(message)
216
+ #end
217
+ end
218
+
219
+ end
220
+
221
+ end