lemon 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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
-