lemon 0.8.1 → 0.8.2

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.
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