qed 2.6.3 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.ruby +4 -3
  2. data/.yardopts +3 -0
  3. data/HISTORY.rdoc +71 -35
  4. data/README.rdoc +9 -10
  5. data/bin/qed +1 -1
  6. data/bin/qedoc +2 -1
  7. data/lib/qed.rb +2 -5
  8. data/lib/qed.yml +4 -3
  9. data/lib/qed/applique.rb +57 -24
  10. data/lib/qed/cli.rb +8 -0
  11. data/lib/qed/cli/qed.rb +124 -0
  12. data/lib/qed/demo.rb +35 -39
  13. data/lib/qed/document.rb +5 -3
  14. data/lib/qed/document/template.rhtml +1 -0
  15. data/lib/qed/evaluator.rb +227 -199
  16. data/lib/qed/parser.rb +60 -282
  17. data/lib/qed/reporter/abstract.rb +54 -58
  18. data/lib/qed/reporter/dotprogress.rb +6 -4
  19. data/lib/qed/reporter/html.rb +112 -31
  20. data/lib/qed/reporter/tapy.rb +95 -125
  21. data/lib/qed/reporter/verbatim.rb +80 -38
  22. data/lib/qed/scope.rb +35 -48
  23. data/lib/qed/session.rb +35 -140
  24. data/lib/qed/settings.rb +104 -67
  25. data/lib/qed/step.rb +237 -0
  26. data/{spec → qed}/01_demos.rdoc +0 -0
  27. data/{spec → qed}/02_advice.rdoc +18 -7
  28. data/qed/03_helpers.rdoc +44 -0
  29. data/{spec → qed}/04_samples.rdoc +4 -4
  30. data/{spec → qed}/05_quote.rdoc +3 -3
  31. data/{spec → qed}/07_toplevel.rdoc +0 -0
  32. data/{spec → qed}/08_cross_script.rdoc +0 -0
  33. data/{spec → qed}/09_cross_script.rdoc +0 -0
  34. data/{spec → qed}/10_constant_lookup.rdoc +2 -2
  35. data/qed/11_embedded_rules.rdoc +46 -0
  36. data/{test/integration/topcode.rdoc → qed/99_issues/02_topcode.rdoc} +0 -0
  37. data/{spec → qed}/applique/constant.rb +0 -0
  38. data/{spec → qed}/applique/env.rb +0 -0
  39. data/{spec → qed}/applique/fileutils.rb +0 -0
  40. data/{spec → qed}/applique/markup.rb +0 -0
  41. data/{spec → qed}/applique/toplevel.rb +0 -0
  42. data/{spec → qed}/helpers/advice.rb +6 -7
  43. data/{spec → qed}/helpers/toplevel.rb +0 -0
  44. data/{spec → qed}/samples/data.txt +0 -0
  45. data/{spec → qed}/samples/table.yml +0 -0
  46. metadata +44 -39
  47. data/LICENSE.rdoc +0 -31
  48. data/SPECSHEET.rdoc +0 -456
  49. data/lib/qed/advice.rb +0 -158
  50. data/lib/qed/reporter/bullet.rb +0 -91
  51. data/lib/qed/reporter/dtrace.rb +0 -67
  52. data/lib/yard-qed.rb +0 -1
  53. data/spec/03_helpers.rdoc +0 -43
  54. data/spec/applique/quote.rb +0 -4
  55. data/spec/helpers/sample.rb +0 -4
@@ -10,78 +10,120 @@ module Reporter #:nodoc:
10
10
  #
11
11
  def before_session(session)
12
12
  @start_time = Time.now
13
+
14
+ trap "INFO" do
15
+ print_time
16
+ print_tally
17
+ end if INFO_SIGNAL
13
18
  end
14
19
 
15
- #
16
- def head(step)
17
- io.print step.text.ansi(:bold)
20
+ def step(step)
21
+ @_explain = step.explain.dup
18
22
  end
19
23
 
20
24
  #
21
- def desc(step)
25
+ def match(step, md)
26
+ unless md[0].empty?
27
+ @_explain.sub!(md[0], md[0].ansi(:bold))
28
+ end
22
29
  end
23
30
 
24
31
  #
25
- def data(step)
26
- #io.puts step.clean_text.ansi(:blue)
27
- #io.puts
32
+ def applique(step)
33
+ io.print "#{@_explain}".ansi(:cyan)
34
+ io.print "#{step.example}" #.ansi(:blue)
28
35
  end
29
36
 
30
37
  #
31
38
  def pass(step)
32
39
  super(step)
33
- if step.code?
34
- io.print "#{step.text}".ansi(:green)
35
- elsif step.header?
36
- io.print "#{step.text}".ansi(:bold)
37
- elsif step.data?
38
- io.print "#{step.text}".ansi(:blue)
40
+ if step.heading?
41
+ if step.code?
42
+ io.print "#{@_explain}".ansi(:bold, :cyan)
43
+ else
44
+ io.print "#{@_explain}".ansi(:bold)
45
+ end
39
46
  else
40
- io.print "#{step.text}"
47
+ io.print "#{@_explain}".ansi(:cyan)
48
+ end
49
+
50
+ if step.has_example?
51
+ if step.data?
52
+ io.print "#{step.example}" #.ansi(:magenta)
53
+ else
54
+ io.print "#{step.example}".ansi(:green)
55
+ end
41
56
  end
42
57
  end
43
58
 
44
59
  #
45
60
  def fail(step, error)
46
61
  super(step, error)
47
- txt = step.text.rstrip #.sub("\n",'')
62
+
48
63
  tab = step.text.index(/\S/)
49
- io.print "#{txt}\n\n".ansi(:red)
64
+
65
+ if step.heading?
66
+ if step.code?
67
+ io.print "#{@_explain}".ansi(:bold, :magenta)
68
+ else
69
+ io.print "#{@_explain}".ansi(:bold)
70
+ end
71
+ else
72
+ io.print "#{@_explain}".ansi(:magenta)
73
+ end
74
+
75
+ if step.has_example?
76
+ if step.data?
77
+ io.print "#{step.example}".ansi(:red)
78
+ else
79
+ io.print "#{step.example}".ansi(:red)
80
+ end
81
+ end
82
+
50
83
  msg = []
51
- #msg << ANSI::Code.bold(ANSI::Code.red("FAIL: ")) + error.message
52
- #msg << ANSI::Code.bold(clean_backtrace(error.backtrace[0]))
53
84
  msg << "FAIL: ".ansi(:bold, :red) + error.message.to_s #to_str
54
- msg << sane_backtrace(error).first.ansi(:bold)
55
- io.puts msg.join("\n").tabto(tab||2)
85
+ msg << sane_backtrace(error).join("\n").ansi(:bold)
86
+ msg = msg.join("\n")
87
+
88
+ io.puts msg.tabto(tab||2)
56
89
  io.puts
57
90
  end
58
91
 
59
92
  #
60
93
  def error(step, error)
61
94
  super(step, error)
62
- raise error if $DEBUG
63
- txt = step.text.rstrip #.sub("\n",'')
95
+
96
+ raise error if $DEBUG # TODO: Should this be here?
97
+
64
98
  tab = step.text.index(/\S/)
65
- io.print "#{txt}\n\n".ansi(:red)
99
+
100
+ if step.heading?
101
+ if step.code?
102
+ io.print "#{@_explain}".ansi(:bold, :magenta)
103
+ else
104
+ io.print "#{@_explain}".ansi(:bold)
105
+ end
106
+ else
107
+ io.print "#{@_explain}".ansi(:magenta)
108
+ end
109
+
110
+ if step.has_example?
111
+ if step.data?
112
+ io.print "#{step.example}".ansi(:red)
113
+ else
114
+ io.print "#{step.example}".ansi(:red)
115
+ end
116
+ end
117
+
66
118
  msg = []
67
119
  msg << "ERROR: #{error.class} ".ansi(:bold,:red) + error.message #.sub(/for QED::Context.*?$/,'')
68
- msg << sane_backtrace(error).first.ansi(:bold)
69
- #msg = msg.ansi(:red)
70
- io.puts msg.join("\n").tabto(tab||2)
120
+ msg << sane_backtrace(error).join("\n").ansi(:bold)
121
+ msg = msg.join("\n") #.ansi(:red)
122
+
123
+ io.puts msg.tabto(tab||2)
71
124
  io.puts
72
125
  end
73
126
 
74
- #def report(str)
75
- # count[-1] += 1 unless count.empty?
76
- # str = str.chomp('.') + '.'
77
- # str = count.join('.') + ' ' + str
78
- # puts str.strip
79
- #end
80
-
81
- #def report_table(set)
82
- # puts set.to_yaml.tabto(2).ansi(:magenta)
83
- #end
84
-
85
127
  #
86
128
  #def macro(step)
87
129
  # #io.puts
@@ -92,6 +134,7 @@ module Reporter #:nodoc:
92
134
 
93
135
  #
94
136
  def after_session(session)
137
+ trap 'INFO', 'DEFAULT' if INFO_SIGNAL
95
138
  print_time
96
139
  print_tally
97
140
  end
@@ -100,4 +143,3 @@ module Reporter #:nodoc:
100
143
 
101
144
  end #module Reporter
102
145
  end #module QED
103
-
@@ -9,29 +9,18 @@ module QED
9
9
  # Location of `qed/scope.rb`.
10
10
  DIRECTORY = File.dirname(__FILE__)
11
11
 
12
- #
13
- # def self.new(applique, file)
14
- # @_applique = applique
15
- # super(applique, file)
16
- # end
12
+ # Setup new Scope instance.
13
+ def initialize(demo, options={})
14
+ super()
17
15
 
18
- # #
19
- # def self.const_missing(name)
20
- # @_applique.const_get(name)
21
- # end
16
+ @_applique = demo.applique_prime
22
17
 
23
- #
24
- def initialize(applique, cwd, file=nil)
25
- super()
26
- @_applique = applique
27
- @_cwd = cwd
28
- @_file = file
29
- #@loadlist = []
18
+ @_file = demo.file
19
+ @_root = options[:root] || $ROOT # FIXME
30
20
 
31
- include *applique
32
- #extend self
33
- #extend applique # TODO: extend or include applique or none ?
34
- #extend DSLi
21
+ @_features = []
22
+
23
+ include *demo.applique
35
24
 
36
25
  # TODO: custom extends?
37
26
 
@@ -40,9 +29,6 @@ module QED
40
29
 
41
30
  # This turns out to be the key to proper scoping.
42
31
  def __create_clean_binding_method__
43
- #define_method(:__binding__) do
44
- # @__binding__ ||= binding
45
- #end
46
32
  module_eval %{
47
33
  def __binding__
48
34
  @__binding__ ||= binding
@@ -53,7 +39,7 @@ module QED
53
39
  #
54
40
  def include(*modules)
55
41
  super(*modules)
56
- extend self
42
+ extend self # overcome dynamic inclusion problem
57
43
  end
58
44
 
59
45
  # Expanded dirname of +file+.
@@ -62,43 +48,39 @@ module QED
62
48
  end
63
49
 
64
50
  # Evaluate code in the context of the scope's special binding.
65
- # The return result of the evaluation is stored in `@_`.
51
+ # The return value of the evaluation is stored in `@_`.
66
52
  #
67
- # TODO: rename to avoid conflict with Kernel method.
68
- def eval(code, file=nil, line=nil)
53
+ def evaluate(code, file=nil, line=nil)
69
54
  if file
70
- @_ = super(code, __binding__, file.to_s, line.to_i)
55
+ @_ = eval(code, __binding__, file.to_s, line.to_i)
71
56
  else
72
- @_ = super(code, __binding__)
57
+ @_ = eval(code, __binding__)
73
58
  end
74
59
  end
75
60
 
61
+ # TODO: Alternative to Plugin gem? If not improve and make standard requirement.
62
+
76
63
  # Utilize is like #require, but will evaluate the script in the context
77
64
  # of the current scope.
78
- #--
79
- # TODO: Alternative to Plugin gem?
80
65
  #
81
- # TODO: Should work like require so same file isn't loaded twice.
82
- #++
83
66
  def utilize(file)
84
67
  file = Dir[DIRECTORY + "/helpers/#{file}"].first
85
68
  if !file
86
69
  require 'plugin'
87
70
  file = Plugin.find("#{file}{,.rb}", :directory=>nil)
88
71
  end
89
- if file
72
+ if file && !@_features.include?(file)
90
73
  code = File.read(file)
91
- eval(code, nil, file)
74
+ evaluate(code, nil, file)
92
75
  else
93
76
  raise LoadError, "no such file -- #{file}"
94
77
  end
95
78
  end
96
79
 
97
-
98
80
  # Define "when" advice.
99
81
  def When(*patterns, &procedure)
100
- patterns = patterns.map{ |pat| pat == :text ? :desc : pat }
101
- @_applique.first.When(*patterns, &procedure)
82
+ #patterns = patterns.map{ |pat| pat == :text ? :desc : pat }
83
+ @_applique.When(*patterns, &procedure)
102
84
  end
103
85
 
104
86
  # Define "before" advice. Default type is :each, which
@@ -106,7 +88,7 @@ module QED
106
88
  def Before(type=:each, &procedure)
107
89
  type = :step if type == :each
108
90
  type = :demo if type == :all
109
- @_applique.first.Before(type, &procedure)
91
+ @_applique.Before(type, &procedure)
110
92
  end
111
93
 
112
94
  # Define "after" advice. Default type is :each, which
@@ -114,7 +96,7 @@ module QED
114
96
  def After(type=:each, &procedure)
115
97
  type = :step if type == :each
116
98
  type = :demo if type == :all
117
- @_applique.first.After(type, &procedure)
99
+ @_applique.After(type, &procedure)
118
100
  end
119
101
 
120
102
  # Directory of current document.
@@ -126,13 +108,14 @@ module QED
126
108
  end
127
109
  end
128
110
 
129
- # TODO: Should Table and Data be extensions that can be loaded if desired?
111
+ # TODO: Should Table and Data be extensions that can be optionally loaded?
112
+
113
+ # TODO: Cache Table and Data for speed ?
130
114
 
131
115
  # Use sample table to run steps. The table file is located relative to
132
116
  # the demo, failing that it will be looked for relative to the working
133
117
  # directory.
134
118
  #
135
- # TODO: Cache data for speed ?
136
119
  def Table(file=nil, options={}) #:yield:
137
120
  if file
138
121
  file = Dir.glob(File.join(File.dirname(@_file), file)).first || file
@@ -166,9 +149,6 @@ module QED
166
149
  # Read a static data file and yield contents to block if given.
167
150
  #
168
151
  # This method no longer automatically uses YAML.load.
169
- #--
170
- # TODO: Cache data for speed ?
171
- #++
172
152
  def Data(file) #:yield:
173
153
  file = Dir.glob(File.join(File.dirname(@_file), file)).first || file
174
154
  #case File.extname(file)
@@ -184,9 +164,10 @@ module QED
184
164
  end
185
165
  end
186
166
 
187
- # Clear temporary work directory.
167
+ # Helper method to clear temporary work directory.
168
+ #
188
169
  def clear_working_directory!
189
- dir = @_cwd
170
+ dir = @_root
190
171
  dir = File.expand_path(dir)
191
172
 
192
173
  if dir == '/' or dir == File.expand_path('~')
@@ -201,7 +182,13 @@ module QED
201
182
  dirs.each { |dir| FileUtils.rmdir(dir) }
202
183
  end
203
184
 
204
- #
185
+ # TODO: Project's root directory.
186
+ #def rootdir
187
+ # @_root
188
+ #end
189
+
190
+ # Redirect constant missing to toplevel (i.e. Object). This is
191
+ # to allow the evaluation scope to emulate the toplevel.
205
192
  def const_missing(const)
206
193
  Object.const_get(const)
207
194
  end
@@ -58,8 +58,9 @@ module QED
58
58
 
59
59
  @files = [files].flatten.compact
60
60
  @files = [DEFAULT_FILES.find{ |d| File.directory?(d) }] if @files.empty?
61
+ @files = @files.compact
61
62
 
62
- @format = options[:format] || :dotprogress
63
+ @format = options[:format] || :dot
63
64
  @trace = options[:trace] || false
64
65
  @mode = options[:mode] || nil
65
66
  @profile = options[:profile] || :default
@@ -93,29 +94,28 @@ module QED
93
94
  # Instance of selected Reporter subclass.
94
95
  def reporter
95
96
  @reporter ||= (
96
- name = Reporter.constants.find{ |c| /#{format}/ =~ c.downcase }
97
+ name = Reporter.constants.find{ |c| /#{format}/ =~ c.to_s.downcase }
97
98
  Reporter.const_get(name).new(:trace => trace)
98
99
  )
99
100
  end
100
101
 
101
- # Returns an Array of Demo instances.
102
- #--
103
102
  # TODO: Pass settings to demo, so we can get temporary_directory.
104
- #++
103
+
104
+ # Returns an Array of Demo instances.
105
105
  def demos
106
106
  @demos ||= demo_files.map{ |file| Demo.new(file, :mode=>mode, :at=>directory) }
107
107
  end
108
108
 
109
+ # List of observers to pass to the evaluator. Only includes the reporter
110
+ # instance, by default.
109
111
  #
110
112
  def observers
111
113
  [reporter]
112
114
  end
113
115
 
114
- # Run session.
115
- #--
116
116
  # TODO: remove loadpath additions when done
117
- #++
118
- # COMMIT: Pre-parse demos before running them.
117
+
118
+ # Run session.
119
119
  def run
120
120
  abort "No documents." if demo_files.empty?
121
121
 
@@ -128,19 +128,19 @@ module QED
128
128
 
129
129
  Dir.chdir(directory) do
130
130
  # pre-parse demos
131
- demos.each do |demo|
132
- demo.steps
133
- end
134
-
135
- #profile.before_session(self)
136
- reporter.before_session(self)
137
- demos.each do |demo|
138
- demo.run(*observers)
139
- #pid = fork { demo.run(*observers) }
140
- #Process.detach(pid)
131
+ demos.each{ |demo| demo.steps }
132
+
133
+ # Let's do it.
134
+ observers.each{ |o| o.before_session(self) }
135
+ begin
136
+ demos.each do |demo|
137
+ Evaluator.run(demo, :observers=>observers, :settings=>settings) #demo.run(*observers)
138
+ #pid = fork { demo.run(*observers) }
139
+ #Process.detach(pid)
140
+ end
141
+ ensure
142
+ observers.each{ |o| o.after_session(self) }
141
143
  end
142
- reporter.after_session(self)
143
- #profile.after_session(self)
144
144
  end
145
145
 
146
146
  reporter.success?
@@ -163,7 +163,7 @@ module QED
163
163
 
164
164
  #
165
165
  def require_profile
166
- settings.require_profile(profile)
166
+ settings.load_profile(profile)
167
167
  end
168
168
 
169
169
  # Returns a list of demo files. The files returned depends on the
@@ -171,22 +171,22 @@ module QED
171
171
  def demo_files
172
172
  @demo_files ||= (
173
173
  if mode == :comment
174
- demos_in_comment_mode
174
+ demo_files_in_comment_mode
175
175
  else
176
- demos_in_normal_mode
176
+ demo_files_in_normal_mode
177
177
  end
178
178
  )
179
179
  end
180
180
 
181
181
  # Collect default files to process in normal demo mode.
182
- def demos_in_normal_mode
183
- demos_gather(DEMO_TYPES)
182
+ def demo_files_in_normal_mode
183
+ demos_gather #(DEMO_TYPES)
184
184
  end
185
185
 
186
186
  # Collect default files to process in code comment mode.
187
187
  #
188
188
  # TODO: Sure removing applique files is the best approach here?
189
- def demos_in_comment_mode
189
+ def demo_files_in_comment_mode
190
190
  files = demos_gather(CODE_TYPES)
191
191
  files = files.reject{ |f| f.index('applique/') } # don't include applique files ???
192
192
  files
@@ -206,7 +206,8 @@ module QED
206
206
  end
207
207
  end
208
208
  files = files.flatten.uniq
209
- files = files.reject{ |f| f =~ Regexp.new('\/'+omit.join('|')+'\/') }
209
+ #files = files.reject{ |f| f =~ Regexp.new("/\/(#{omit.join('|')})\//") }
210
+ files = files.reject{ |f| omit.any?{ |o| f.index("/#{o}/") } }
210
211
  files.map{|f| File.expand_path(f) }.uniq.sort
211
212
  end
212
213
 
@@ -217,123 +218,17 @@ module QED
217
218
  # end
218
219
  #end
219
220
 
221
+ # Get the total test count. This method tallies up the number of
222
+ # _assertive_ steps.
220
223
  #
221
224
  def total_step_count
222
225
  count = 0
223
- QED.all_steps.each do |step|
224
- count += 1 unless step.header?
225
- end
226
- count
227
- end
228
-
229
- #
230
- def self.cli(*argv)
231
- require 'optparse'
232
- require 'shellwords'
233
-
234
- files, options = cli_parse(argv)
235
-
236
- #if files.empty?
237
- # puts "No files."
238
- # exit -1
239
- #end
240
-
241
- session = new(files, options)
242
- success = session.run
243
-
244
- exit -1 unless success
245
- end
246
-
247
- # Instance of OptionParser
248
- def self.cli_parse(argv)
249
- options = {}
250
- options_parser = OptionParser.new do |opt|
251
- opt.banner = "Usage: qed [options] <files...>"
252
-
253
- opt.separator("Custom Profiles:") unless settings.profiles.empty?
254
-
255
- settings.profiles.each do |name, value|
256
- o = "--#{name}"
257
- opt.on(o, "#{name} custom profile") do
258
- options[:profile] = name.to_sym
259
- end
260
- end
261
-
262
- opt.separator("Report Formats (pick one):")
263
- opt.on('--dotprogress', '-d', "use dot-progress reporter [default]") do
264
- options[:format] = :dotprogress
265
- end
266
- opt.on('--verbatim', '-v', "use verbatim reporter") do
267
- options[:format] = :verbatim
268
- end
269
- opt.on('--tapy', '-y', "use TAP-Y reporter") do
270
- options[:format] = :tapy
271
- end
272
- opt.on('--bullet', '-b', "use bullet-point reporter") do
273
- options[:format] = :bullet
274
- end
275
- opt.on('--html', '-h', "use underlying HTML reporter") do
276
- options[:format] = :html
277
- end
278
- #opt.on('--script', "psuedo-reporter") do
279
- # options[:format] = :script # psuedo-reporter
280
- #end
281
- opt.on('--format', '-f FORMAT', "use custom reporter") do |format|
282
- options[:format] = format.to_sym
283
- end
284
-
285
- opt.separator("Control Options:")
286
- opt.on('--comment', '-c', "run comment code") do
287
- options[:mode] = :comment
288
- end
289
- opt.on('--profile', '-p NAME', "load runtime profile") do |name|
290
- options[:profile] = name
291
- end
292
- opt.on('--loadpath', "-I PATH", "add paths to $LOAD_PATH") do |paths|
293
- options[:loadpath] = paths.split(/[:;]/).map{|d| File.expand_path(d)}
294
- end
295
- opt.on('--require', "-r LIB", "require library") do |paths|
296
- options[:requires] = paths.split(/[:;]/)
297
- end
298
- opt.on('--rooted', '-R', "run from project root instead of temporary directory") do
299
- options[:rooted] = true
300
- end
301
- # COMMIT:
302
- # The qed command --trace option takes a count.
303
- # Use 0 to mean all.
304
- opt.on('--trace', '-t [COUNT]', "show full backtraces for exceptions") do |cnt|
305
- #options[:trace] = true
306
- ENV['trace'] = cnt
307
- end
308
- opt.on('--warn', "run with warnings turned on") do
309
- $VERBOSE = true # wish this were called $WARN!
310
- end
311
- opt.on('--debug', "exit immediately upon raised exception") do
312
- $DEBUG = true
313
- end
314
-
315
- opt.separator("Optional Commands:")
316
- opt.on_tail('--version', "display version") do
317
- puts "QED #{QED::VERSION}"
318
- exit
319
- end
320
- opt.on_tail('--copyright', "display copyrights") do
321
- puts "Copyright (c) 2008 Thomas Sawyer, Apache 2.0 License"
322
- exit
323
- end
324
- opt.on_tail('--help', '-h', "display this help message") do
325
- puts opt
326
- exit
226
+ demos.each do |demo|
227
+ demo.steps.each do |step|
228
+ count += 1 if step.assertive?
327
229
  end
328
230
  end
329
- options_parser.parse!(argv)
330
- return argv, options
331
- end
332
-
333
- # TODO: Pass to Session class, instead of acting global.
334
- # It is used at the class level to get profiles for the cli.
335
- def self.settings
336
- @settings ||= Settings.new
231
+ count
337
232
  end
338
233
 
339
234
  end#class Session