lemon 0.9.0 → 0.9.1

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 (107) hide show
  1. data/.ruby +23 -14
  2. data/.yardopts +6 -0
  3. data/Config.rb +14 -0
  4. data/{HISTORY.rdoc → HISTORY.md} +26 -11
  5. data/LICENSE.txt +27 -0
  6. data/README.md +42 -28
  7. data/SPECSHEET.md +314 -0
  8. data/bin/{lemonade → lemons} +0 -0
  9. data/lib/lemon.yml +23 -14
  10. data/lib/lemon/cli.rb +19 -8
  11. data/lib/lemon/cli/base.rb +50 -20
  12. data/lib/lemon/cli/generate.rb +51 -16
  13. data/lib/lemon/cli/lemon.ascii +84 -0
  14. data/lib/lemon/cli/obrother.rb +35 -0
  15. data/lib/lemon/cli/scaffold.rb +116 -0
  16. data/lib/lemon/core_ext.rb +2 -2
  17. data/lib/lemon/core_ext/module.rb +9 -0
  18. data/lib/lemon/coverage/analyzer.rb +76 -5
  19. data/lib/lemon/coverage/cover_unit.rb +38 -14
  20. data/lib/lemon/coverage/formats/verbose.rb +1 -1
  21. data/lib/lemon/coverage/generator.rb +196 -0
  22. data/lib/lemon/coverage/snapshot.rb +16 -16
  23. data/lib/lemon/coverage/source_parser.rb +103 -37
  24. data/lib/lemon/ignore_callers.rb +19 -0
  25. data/lib/lemon/test_case.rb +135 -26
  26. data/lib/lemon/test_class.rb +16 -3
  27. data/lib/lemon/test_class_method.rb +58 -0
  28. data/lib/lemon/test_method.rb +57 -68
  29. data/lib/lemon/test_module.rb +47 -44
  30. data/lib/lemon/test_proc.rb +28 -2
  31. data/lib/lemon/test_scope.rb +14 -0
  32. data/lib/lemon/test_setup.rb +1 -1
  33. data/lib/lemon/test_world.rb +7 -0
  34. data/{work/deprecated/features/support → spec/applique}/ae.rb +0 -0
  35. data/spec/coverage/{01_complete.rdoc → 01_complete.md} +3 -3
  36. data/spec/coverage/{02_incomplete.rdoc → 02_incomplete.md} +2 -2
  37. data/spec/coverage/{03_extensions.rdoc → 03_extensions.md} +2 -2
  38. data/try/case_scope.rb +19 -0
  39. metadata +50 -102
  40. data/.gemspec +0 -152
  41. data/.gitignore +0 -8
  42. data/.reap/digest +0 -678
  43. data/.reap/test.reap +0 -7
  44. data/Assembly +0 -37
  45. data/COPYING.rdoc +0 -33
  46. data/MANIFEST +0 -55
  47. data/PROFILE +0 -30
  48. data/Rakefile +0 -23
  49. data/VERSION +0 -1
  50. data/lib/lemon/core_ext/omission.rb +0 -18
  51. data/lib/lemon/generator.rb +0 -149
  52. data/notes/2010-05-05-coverage.rdoc +0 -47
  53. data/notes/2010-05-06-files-not-classes.rdoc +0 -19
  54. data/notes/2010-07-11-acid-testing.rdoc +0 -52
  55. data/notes/2010-08-02-enforcing-the-unit.md +0 -68
  56. data/notes/2010-08-03-new-api.md +0 -37
  57. data/notes/2011-07-07-nailing-down-the-nomenclature.md +0 -6
  58. data/site/.rsync-filter +0 -8
  59. data/site/assets/images/cut-lemon.png +0 -0
  60. data/site/assets/images/forkme.png +0 -0
  61. data/site/assets/images/github-logo.png +0 -0
  62. data/site/assets/images/lemon.jpg +0 -0
  63. data/site/assets/images/lemon.svg +0 -39
  64. data/site/assets/images/lemons-are-good.png +0 -0
  65. data/site/assets/images/opensource.png +0 -0
  66. data/site/assets/images/ruby-logo.png +0 -0
  67. data/site/assets/images/skin.jpg +0 -0
  68. data/site/assets/images/skin1.jpg +0 -0
  69. data/site/assets/images/tap.png +0 -0
  70. data/site/assets/images/title.png +0 -0
  71. data/site/assets/styles/class.css +0 -6
  72. data/site/assets/styles/reset.css +0 -17
  73. data/site/assets/styles/site.css +0 -33
  74. data/site/index.html +0 -218
  75. data/work/deprecated/command/abstract.rb +0 -29
  76. data/work/deprecated/command/coverage.rb +0 -115
  77. data/work/deprecated/command/generate.rb +0 -124
  78. data/work/deprecated/command/test.rb +0 -112
  79. data/work/deprecated/cucumber.yml +0 -3
  80. data/work/deprecated/features/coverage.feature +0 -65
  81. data/work/deprecated/features/generate.feature +0 -66
  82. data/work/deprecated/features/step_definitions/coverage_steps.rb +0 -1
  83. data/work/deprecated/features/support/aruba.rb +0 -1
  84. data/work/deprecated/features/test.feature +0 -67
  85. data/work/deprecated/model/dsl/advice.rb +0 -78
  86. data/work/deprecated/model/dsl/subject.rb +0 -40
  87. data/work/deprecated/model/main.rb +0 -87
  88. data/work/deprecated/model/test.rb +0 -54
  89. data/work/deprecated/model/test_base_dsl.rb +0 -88
  90. data/work/deprecated/model/test_clause.rb +0 -112
  91. data/work/deprecated/model/test_context.rb +0 -90
  92. data/work/deprecated/model/test_feature.rb +0 -128
  93. data/work/deprecated/model/test_scenario.rb +0 -137
  94. data/work/deprecated/model/test_suite.rb +0 -297
  95. data/work/deprecated/rake.rb +0 -103
  96. data/work/deprecated/test/case_coverage_analyzer.rb +0 -25
  97. data/work/deprecated/test/case_test_case_dsl.rb +0 -46
  98. data/work/deprecated/test/fixtures/case_complete.rb +0 -25
  99. data/work/deprecated/test/fixtures/case_inclusion.rb +0 -18
  100. data/work/deprecated/test/fixtures/case_incomplete.rb +0 -12
  101. data/work/deprecated/test/fixtures/example.rb +0 -13
  102. data/work/deprecated/test/fixtures/helper.rb +0 -13
  103. data/work/deprecated/test/runner +0 -2
  104. data/work/old-tests/case_example.rb +0 -15
  105. data/work/old-tests/feature_example.rb +0 -40
  106. data/work/reference/dsl2.rb +0 -140
  107. data/work/reference/dynamic_constant_lookup.rb +0 -76
@@ -41,7 +41,7 @@ module Lemon::CoverReports
41
41
 
42
42
  #
43
43
  def unit_line(unit, color)
44
- data = [unit.to_s.ansi(color), unit.access.to_s, unit.function? ? 'function' : 'instance method']
44
+ data = [unit.to_s.ansi(color), unit.access.to_s, unit.singleton? ? 'class method' : 'instance method']
45
45
  "* %s %s %s" % data
46
46
  end
47
47
 
@@ -0,0 +1,196 @@
1
+ module Lemon
2
+
3
+ require 'lemon/coverage/analyzer'
4
+ require 'lemon/coverage/source_parser'
5
+
6
+ # TODO: Add option to add `require <file>` for each test file genetated.
7
+
8
+ # Test Scaffold Generator.
9
+ #
10
+ class Generator
11
+
12
+ # New Scaffold Generator.
13
+ #
14
+ # @option options [Array] :files
15
+ # Ruby scripts for which to generate tests.
16
+ #
17
+ # @option options [Array] :tests
18
+ # Test files that already exist.
19
+ #
20
+ # @option options [Array] :namespaces
21
+ # List of class/module names to limit scaffolding.
22
+ #
23
+ # @option options [Boolean] :private
24
+ # Include private methods in scaffolding.
25
+ #
26
+ # @option options [Symbol] :group
27
+ # Group by `:case` or by `:file`.
28
+ #
29
+ def initialize(options={})
30
+ @files = options[:files] || []
31
+ @tests = options[:tests] || []
32
+ @group = options[:group] || :case
33
+
34
+ @namespaces = options[:namespaces]
35
+ #@coverage = options[:coverage]
36
+ @private = options[:private]
37
+ @caps = options[:caps]
38
+
39
+ #if @namespaces
40
+ # @coverage ||= :all
41
+ #else
42
+ # @coverage ||= :uncovered
43
+ #end
44
+
45
+ #@snapshot = Snapshot.capture
46
+
47
+ @analyzer = CoverageAnalyzer.new(files + tests, options)
48
+ @suite = @analyzer.suite
49
+ end
50
+
51
+ #
52
+ attr :files
53
+
54
+ #
55
+ attr :tests
56
+
57
+ # Group tests by `:case` or `:file`.
58
+ attr :group
59
+
60
+ # List of class and module namespaces to limit scaffolding.
61
+ attr :namespaces
62
+
63
+ # Target coverage `:all`, `:uncovered` or `:covered`.
64
+ #def coverage
65
+ # @coverage
66
+ #end
67
+
68
+ # Include private and protected methods.
69
+ def private?
70
+ @private
71
+ end
72
+
73
+ # Returns CoverageAnalyzer instance.
74
+ def analyzer
75
+ @analyzer
76
+ end
77
+
78
+ # Units in groups, by file or by case.
79
+ def grouped_units
80
+ case group
81
+ when :case
82
+ units_by_case
83
+ when :file
84
+ units_by_file
85
+ else
86
+ units_by_case # default ?
87
+ end
88
+ end
89
+
90
+ # Generate test template(s).
91
+ def generate
92
+ render_map = {}
93
+
94
+ if tests.empty?
95
+ grouped_units.each do |group, units|
96
+ units = filter(units)
97
+ render_map[group] = render(units)
98
+ end
99
+ else
100
+ uncovered_units = analyzer.uncovered
101
+ grouped_units.each do |group, units|
102
+ units = filter(units)
103
+ units = units & uncovered_units
104
+ render_map[group] = render(units)
105
+ end
106
+ #when :covered
107
+ # covered_units = analyzer.target.units
108
+ # grouped_units.each do |group, units|
109
+ # units = filter(units)
110
+ # units = units & covered_units
111
+ # map[group] = render(units)
112
+ # end
113
+ #else
114
+ # #units = Snapshot.capture(namespaces).units
115
+ # #units = (units - @snapshot.units)
116
+ end
117
+
118
+ render_map
119
+ end
120
+
121
+ # TODO: If units knew which file they came from that would make
122
+ # the code more efficient b/c then we could group after all
123
+ # filtering instead of before.
124
+
125
+ #
126
+ def units_by_case
127
+ units = []
128
+ files.each do |file|
129
+ units.concat SourceParser.parse_units(File.read(file))
130
+ end
131
+ units.group_by{ |u| u.namespace }
132
+ end
133
+
134
+ #
135
+ def units_by_file
136
+ map = {}
137
+ files.each do |file|
138
+ units = SourceParser.parse_units(File.read(file))
139
+ map[file] = units
140
+ end
141
+ map
142
+ end
143
+
144
+ # Filter targets to include only specified namespaces.
145
+ def filter(units)
146
+ return units if namespaces.nil? or namespaces.empty?
147
+ units.select do |u|
148
+ namespaces.any? do |ns|
149
+ /^#{ns}/ =~ u.namespace.to_s
150
+ end
151
+ end
152
+ end
153
+
154
+ # Generate code template.
155
+ def render(units)
156
+ code = []
157
+ mods = units.group_by{ |u| u.namespace }
158
+ mods.each do |mod, units|
159
+ code << "#{term_case} #{mod} do"
160
+ units.each do |unit|
161
+ next unless private? or unit.public?
162
+ if unit.singleton?
163
+ code << "\n #{term_class_method} :#{unit.method} do"
164
+ code << "\n test '' do"
165
+ code << "\n end"
166
+ code << "\n end"
167
+ else
168
+ code << "\n #{term_method} :#{unit.method} do"
169
+ code << "\n test '' do"
170
+ code << "\n end"
171
+ code << "\n end"
172
+ end
173
+ end
174
+ code << "\nend\n"
175
+ end
176
+ code.join("\n")
177
+ end
178
+
179
+ #
180
+ def term_case
181
+ @caps ? 'TestCase' : 'test_case'
182
+ end
183
+
184
+ #
185
+ def term_class_method
186
+ @caps ? 'ClassMethod' : 'class_method'
187
+ end
188
+
189
+ #
190
+ def term_method
191
+ @caps ? 'Method' : 'method'
192
+ end
193
+
194
+ end
195
+
196
+ end
@@ -64,7 +64,7 @@ module Lemon
64
64
  methods = mod.__send__("#{access}methods", false)
65
65
  #methods -= Object.__send__("#{access}methods", true)
66
66
  methods.each do |method|
67
- @units << Unit.new(mod, method, :access=>access, :function=>true)
67
+ @units << Unit.new(mod, method, :access=>access, :singleton=>true)
68
68
  end
69
69
  end
70
70
  return @units
@@ -105,19 +105,19 @@ module Lemon
105
105
  attr :method
106
106
 
107
107
  # Is the method a "class method", rather than an instance method.
108
- attr :function
108
+ attr :singleton
109
109
 
110
110
  def initialize(target, method, props={})
111
- @target = target
112
- @method = method.to_sym
113
- @function = props[:function] ? true : false
114
- @covered = props[:covered]
111
+ @target = target
112
+ @method = method.to_sym
113
+ @singleton = props[:singleton] ? true : false
114
+ @covered = props[:covered]
115
115
 
116
- if @function
116
+ if @singleton
117
117
  @private = @target.private_methods.find{ |m| m.to_sym == @method }
118
118
  @protected = @target.protected_methods.find{ |m| m.to_sym == @method }
119
119
  else
120
- @private = @target.private_instance_methods.find{ |m| m.to_sym == @method }
120
+ @private = @target.private_instance_methods.find{ |m| m.to_sym == @method }
121
121
  @protected = @target.protected_instance_methods.find{ |m| m.to_sym == @method }
122
122
  end
123
123
  end
@@ -131,8 +131,8 @@ module Lemon
131
131
  end
132
132
 
133
133
  #
134
- def function?
135
- @function
134
+ def singleton?
135
+ @singleton
136
136
  end
137
137
 
138
138
  # Method access is public?
@@ -164,12 +164,12 @@ module Lemon
164
164
 
165
165
  #
166
166
  def hash
167
- @target.hash ^ @method.hash ^ @function.hash
167
+ @target.hash ^ @method.hash ^ @singleton.hash
168
168
  end
169
169
 
170
170
  #
171
171
  def to_s
172
- if @function
172
+ if @singleton
173
173
  "#{@target}.#{@method}"
174
174
  else
175
175
  "#{@target}##{@method}"
@@ -181,19 +181,19 @@ module Lemon
181
181
  return false unless Unit === other
182
182
  return false unless target == other.target
183
183
  return false unless method == other.method
184
- return false unless function == other.function
184
+ return false unless singleton == other.singleton
185
185
  return true
186
186
  end
187
187
 
188
188
  def inspect
189
- "#{target}#{function ? '.' : '#'}#{method}"
189
+ "#{target}#{singleton ? '.' : '#'}#{method}"
190
190
  end
191
191
 
192
192
  def <=>(other)
193
193
  c = (target.name <=> other.target.name)
194
194
  return c unless c == 0
195
- return -1 if function && !other.function
196
- return 1 if !function && other.function
195
+ return -1 if singleton && !other.singleton
196
+ return 1 if !singleton && other.singleton
197
197
  method.to_s <=> other.method.to_s
198
198
  end
199
199
  end
@@ -1,6 +1,7 @@
1
- module Lemon
1
+ require 'ripper'
2
+ require 'lemon/coverage/snapshot'
2
3
 
3
- require 'ruby_parser'
4
+ module Lemon
4
5
 
5
6
  #
6
7
  class SourceParser
@@ -8,24 +9,38 @@ module Lemon
8
9
  #
9
10
  # text - A String of Ruby code.
10
11
  #
11
- # Returns a Hash with each key a namespace and each value another
12
- # Hash or a TomDoc::Scope.
12
+ # Returns a Hash with each key a namespace and each value another Hash or Scope.
13
13
  def self.parse(text)
14
14
  new.parse(text)
15
15
  end
16
16
 
17
- attr_accessor :parser, :scopes, :options
17
+ # Converts Ruby code into a data structure.
18
+ #
19
+ # text - A String of Ruby code.
20
+ #
21
+ # Returns an Array of Snapshot::Unit objects.
22
+ def self.parse_units(text)
23
+ sp = new
24
+ sp.parse(text)
25
+ sp.units
26
+ end
27
+
28
+ attr_accessor :parser
29
+
30
+ attr_accessor :scopes
31
+
32
+ attr_accessor :options
18
33
 
19
34
  # Each instance of SourceParser accumulates scopes with each
20
35
  # parse, making it easy to parse an entire project in chunks but
21
36
  # more difficult to parse disparate files in one go. Create
22
37
  # separate instances for separate global scopes.
23
38
  #
24
- # Returns an instance of TomDoc::SourceParser
39
+ # Returns an instance of SourceParser.
25
40
  def initialize(options = {})
26
41
  @options = {}
27
- @parser = RubyParser.new
28
42
  @scopes = {}
43
+ #@parser = RubyParser.new
29
44
  end
30
45
 
31
46
  # Resets the state of the parser to a pristine one. Maintains options.
@@ -43,14 +58,13 @@ module Lemon
43
58
  # text - A String of Ruby code.
44
59
  #
45
60
  # Examples
46
- # @parser = TomDoc::SourceParser.new
61
+ # @parser = SourceParser.new
47
62
  # files.each do |file|
48
63
  # @parser.parse(File.read(file))
49
64
  # end
50
65
  # pp @parser.scopes
51
66
  #
52
- # Returns a Hash with each key a namespace and each value another
53
- # Hash or a TomDoc::Scope.
67
+ # Returns a Hash with each key a namespace and each value another Hash or Scope.
54
68
  def parse(text)
55
69
  process(tokenize(sexp(text)))
56
70
  @scopes
@@ -62,7 +76,8 @@ module Lemon
62
76
  #
63
77
  # Returns a Sexp representing the AST.
64
78
  def sexp(text)
65
- @parser.parse(text)
79
+ Ripper.sexp(text)
80
+ #@parser.parse(text)
66
81
  end
67
82
 
68
83
  # Converts a tokenized Array of classes, modules, and methods into
@@ -73,13 +88,14 @@ module Lemon
73
88
  # scope - An optional Scope object for nested classes or modules.
74
89
  #
75
90
  # Returns nothing.
76
- def process(ast, scope = nil)
91
+ def process(ast, scope=nil)
77
92
  case Array(ast)[0]
78
93
  when :module, :class
79
94
  name = ast[1]
80
95
  new_scope = Scope.new(name, ast[2])
81
96
 
82
97
  if scope
98
+ new_scope.parent = scope
83
99
  scope.scopes[name] = new_scope
84
100
  elsif @scopes[name]
85
101
  new_scope = @scopes[name]
@@ -119,22 +135,22 @@ module Lemon
119
135
  def tokenize(node)
120
136
  case Array(node)[0]
121
137
  when :module
122
- name = node[1]
123
- [ :module, name, node.comments, tokenize(node[2]) ]
138
+ name = node[1][1][1]
139
+ [ :module, name, '', tokenize(node[2]) ]
124
140
  when :class
125
- name = node[1]
126
- [ :class, name, node.comments, tokenize(node[3]) ]
127
- when :defn
128
- name = node[1]
141
+ name = node[1][1][1]
142
+ [ :class, name, '', tokenize(node[3]) ]
143
+ when :def
144
+ name = node[1][1]
129
145
  args = args_for_node(node[2])
130
- [ :imethod, name, node.comments, args ]
146
+ [ :imethod, name, '', args ]
131
147
  when :defs
132
- name = node[2]
133
- args = args_for_node(node[3])
134
- [ :cmethod, name, node.comments, args ]
148
+ name = node[3][1]
149
+ args = args_for_node(node[4])
150
+ [ :cmethod, name, '', args ]
135
151
  when :block
136
152
  tokenize(node[1..-1])
137
- when :scope
153
+ when :program, :bodystmt, :scope
138
154
  tokenize(node[1])
139
155
  when Array
140
156
  node.map { |n| tokenize(n) }.compact
@@ -143,27 +159,34 @@ module Lemon
143
159
 
144
160
  # Given a method sexp, returns an array of the args.
145
161
  def args_for_node(node)
146
- Array(node)[1..-1].select { |arg| arg.is_a? Symbol }
162
+ Array(node)[1..-1].select{ |arg| arg.is_a? Symbol }
147
163
  end
148
164
 
149
- # A Scope is a Module or Class.
150
- # It may contain other scopes.
165
+ #
166
+ def units
167
+ list = []
168
+ @scopes.each do |name, scope|
169
+ list.concat(scope.to_units)
170
+ end
171
+ list
172
+ end
173
+
174
+ # A Scope is a Module or Class, and may contain other scopes.
151
175
  class Scope
152
176
  include Enumerable
153
177
 
154
178
  attr_accessor :name, :comment, :instance_methods, :class_methods
179
+
180
+ attr_accessor :parent
181
+
155
182
  attr_accessor :scopes
156
183
 
157
- def initialize(name, comment = '', instance_methods = [], class_methods = [])
158
- @name = name
159
- @comment = comment
184
+ def initialize(name, comment='', instance_methods=[], class_methods=[])
185
+ @name = name
186
+ @comment = comment
160
187
  @instance_methods = instance_methods
161
- @class_methods = class_methods
162
- @scopes = {}
163
- end
164
-
165
- def tomdoc
166
- @tomdoc ||= TomDoc.new(@comment)
188
+ @class_methods = class_methods
189
+ @scopes = {}
167
190
  end
168
191
 
169
192
  def [](scope)
@@ -183,16 +206,59 @@ module Lemon
183
206
  end
184
207
 
185
208
  def inspect
186
- scopes = @scopes.keys.join(', ')
209
+ scopes = @scopes.keys.join(', ')
187
210
  imethods = @instance_methods.inspect
188
211
  cmethods = @class_methods.inspect
189
212
 
190
213
  "<#{name} scopes:[#{scopes}] :#{cmethods}: ##{imethods}#>"
191
214
  end
192
215
 
216
+ #
217
+ def target
218
+ if parent
219
+ parent.target.const_get(name)
220
+ else
221
+ Object.const_get(name)
222
+ end
223
+ end
224
+
225
+ #
226
+ def to_units
227
+ units = []
228
+ @instance_methods.each do |imethod|
229
+ units << Snapshot::Unit.new(target, imethod, :singleton=>false)
230
+ end
231
+ @class_methods.each do |imethod|
232
+ units << Snapshot::Unit.new(target, imethod, :singleton=>true)
233
+ end
234
+ @scopes.each do |name, scope|
235
+ units.concat(scope.to_units)
236
+ end
237
+ units
238
+ end
239
+
240
+ end
241
+
242
+ # A Method can be instance or class level.
243
+ class Method
244
+ attr_accessor :name, :comment, :args
245
+
246
+ def initialize(name, comment='', args=[])
247
+ @name = name
248
+ @comment = comment
249
+ @args = args || []
250
+ end
251
+ alias_method :to_s, :name
252
+
253
+ def to_sym
254
+ name.to_sym
255
+ end
256
+
257
+ def inspect
258
+ "#{name}(#{args.join(', ')})"
259
+ end
193
260
  end
194
261
 
195
262
  end
196
263
 
197
264
  end
198
-