keight 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +263 -0
  4. data/Rakefile +92 -0
  5. data/bench/bench.rb +278 -0
  6. data/bench/benchmarker.rb +502 -0
  7. data/bin/k8rb +496 -0
  8. data/keight.gemspec +36 -0
  9. data/lib/keight/skeleton/.gitignore +10 -0
  10. data/lib/keight/skeleton/app/action.rb +98 -0
  11. data/lib/keight/skeleton/app/api/hello.rb +39 -0
  12. data/lib/keight/skeleton/app/form/.keep +0 -0
  13. data/lib/keight/skeleton/app/helper/.keep +0 -0
  14. data/lib/keight/skeleton/app/model/.keep +0 -0
  15. data/lib/keight/skeleton/app/model.rb +144 -0
  16. data/lib/keight/skeleton/app/page/welcome.rb +17 -0
  17. data/lib/keight/skeleton/app/template/_layout.html.eruby +56 -0
  18. data/lib/keight/skeleton/app/template/welcome.html.eruby +6 -0
  19. data/lib/keight/skeleton/app/usecase/.keep +0 -0
  20. data/lib/keight/skeleton/config/app.rb +29 -0
  21. data/lib/keight/skeleton/config/app_dev.private +11 -0
  22. data/lib/keight/skeleton/config/app_dev.rb +8 -0
  23. data/lib/keight/skeleton/config/app_prod.rb +7 -0
  24. data/lib/keight/skeleton/config/app_stg.rb +5 -0
  25. data/lib/keight/skeleton/config/app_test.private +11 -0
  26. data/lib/keight/skeleton/config/app_test.rb +8 -0
  27. data/lib/keight/skeleton/config/server_puma.rb +22 -0
  28. data/lib/keight/skeleton/config/server_unicorn.rb +21 -0
  29. data/lib/keight/skeleton/config/urlpath_mapping.rb +16 -0
  30. data/lib/keight/skeleton/config.rb +44 -0
  31. data/lib/keight/skeleton/config.ru +21 -0
  32. data/lib/keight/skeleton/index.txt +38 -0
  33. data/lib/keight/skeleton/static/lib/jquery/1.11.3/jquery.min.js +6 -0
  34. data/lib/keight/skeleton/static/lib/jquery/1.11.3/jquery.min.js.gz +0 -0
  35. data/lib/keight/skeleton/static/lib/modernizr/2.8.3/modernizr.min.js +4 -0
  36. data/lib/keight/skeleton/static/lib/modernizr/2.8.3/modernizr.min.js.gz +0 -0
  37. data/lib/keight/skeleton/tmp/upload/.keep +0 -0
  38. data/lib/keight.rb +2017 -0
  39. data/test/data/example1.jpg +0 -0
  40. data/test/data/example1.png +0 -0
  41. data/test/data/multipart.form +0 -0
  42. data/test/data/wabisabi.js +77 -0
  43. data/test/data/wabisabi.js.gz +0 -0
  44. data/test/keight_test.rb +3161 -0
  45. data/test/oktest.rb +1537 -0
  46. metadata +114 -0
data/test/oktest.rb ADDED
@@ -0,0 +1,1537 @@
1
+ ###
2
+ ### $Release: 0.0.1 $
3
+ ### $Copyright: copyright(c) 2014-2015 kuwata-lab.com all rights reserved $
4
+ ### $License: MIT License $
5
+ ###
6
+
7
+ require 'test/unit'
8
+ if defined?(Test::Unit::Runner)
9
+ Test::Unit::Runner.class_variable_set(:@@stop_auto_run, true)
10
+ end
11
+
12
+ #require 'section/tmp'
13
+ #require 'section/recorder'
14
+
15
+
16
+ module Oktest
17
+
18
+
19
+ VERSION = '$Release: 0.0.1 $'.split(/ /)[1]
20
+
21
+
22
+ class SkipException < Exception
23
+ end
24
+
25
+ class TodoException < Exception
26
+ end
27
+
28
+ FAIL_EXCEPTION = defined?(MiniTest) ? MiniTest::Assertion : Test::Unit::AssertionFailedError # :nodoc:
29
+ SKIP_EXCEPTION = SkipException
30
+ TODO_EXCEPTION = TodoException
31
+
32
+
33
+ class AssertionObject
34
+ include Test::Unit::Assertions
35
+
36
+ self.instance_methods.grep(/\?\z/).each do |k|
37
+ undef_method k unless k.to_s == 'equal?' || k.to_s =~ /^assert/
38
+ end
39
+
40
+ NOT_YET = {}
41
+
42
+ def initialize(actual, bool, location)
43
+ @actual = actual
44
+ @bool = bool
45
+ @location = location
46
+ end
47
+
48
+ attr_reader :actual, :bool, :location
49
+
50
+ def _done
51
+ AssertionObject::NOT_YET.delete(self.__id__)
52
+ end
53
+ private :_done
54
+
55
+ def self.report_not_yet
56
+ return if NOT_YET.empty?
57
+ NOT_YET.each_value do |ass|
58
+ $stderr.write "** warning: ok() is called but not tested yet (at #{ass.location})\n"
59
+ end
60
+ end
61
+
62
+ def _not
63
+ return @bool ? '' : 'not '
64
+ end
65
+ private :_not
66
+
67
+ if defined?(MiniTest)
68
+ def __assert result
69
+ if result
70
+ assert true
71
+ else
72
+ #assert_block(yield) { false }
73
+ #flunk yield
74
+ assert false, yield
75
+ end
76
+ end
77
+ else
78
+ def __assert result
79
+ if result
80
+ assert true
81
+ else
82
+ assert_block(yield) { false }
83
+ end
84
+ end
85
+ end
86
+
87
+ def NOT
88
+ @bool = ! @bool
89
+ self
90
+ end
91
+
92
+ def ==(expected)
93
+ _done()
94
+ __assert(@bool == (@actual == expected)) {
95
+ if @bool && ! (@actual == expected) \
96
+ && @actual.is_a?(String) && expected.is_a?(String) \
97
+ && (@actual =~ /\n/ || expected =~ /\n/)
98
+ diff = Util.unified_diff(expected, @actual, "--- $expected\n+++ $actual\n")
99
+ "$actual == $expected: failed.\n#{diff}"
100
+ else
101
+ op = @bool ? '==' : '!='
102
+ "$actual #{op} $expected: failed.\n"\
103
+ " $actual: #{@actual.inspect}\n"\
104
+ " $expected: #{expected.inspect}"
105
+ end
106
+ }
107
+ self
108
+ end
109
+
110
+ if RUBY_VERSION >= '1.9'
111
+ op = @bool ? '==' : '!='
112
+ eval <<-END, binding, __FILE__, __LINE__+1
113
+ def !=(expected)
114
+ _done()
115
+ __assert(@bool == (@actual != expected)) {
116
+ "$actual #{op} $expected: failed.\n"\
117
+ " $actual: \#{@actual.inspect}\n"\
118
+ " $expected: \#{expected.inspect}"
119
+ }
120
+ self
121
+ end
122
+ END
123
+ end
124
+
125
+ #--
126
+ #def > expected; _done(); assert_operator(@actual, @bool ? :> : :<=, expected); self end
127
+ def >= expected; _done(); assert_operator(@actual, @bool ? :>= : :<, expected); self end
128
+ def < expected; _done(); assert_operator(@actual, @bool ? :< : :>=, expected); self end
129
+ def <= expected; _done(); assert_operator(@actual, @bool ? :<= : :>, expected); self end
130
+ #++
131
+
132
+ def >(expected)
133
+ _done()
134
+ __assert(@bool == (@actual > expected)) {
135
+ "#{@actual.inspect} #{@bool ? '>' : '<='} #{expected}: failed."
136
+ }
137
+ self
138
+ end
139
+
140
+ def >=(expected)
141
+ _done()
142
+ __assert(@bool == (@actual >= expected)) {
143
+ "#{@actual.inspect} #{@bool ? '>=' : '<'} #{expected}: failed."
144
+ }
145
+ self
146
+ end
147
+
148
+ def <(expected)
149
+ _done()
150
+ __assert(@bool == (@actual < expected)) {
151
+ "#{@actual.inspect} #{@bool ? '<' : '>='} #{expected}: failed."
152
+ }
153
+ self
154
+ end
155
+
156
+ def <=(expected)
157
+ _done()
158
+ __assert(@bool == (@actual <= expected)) {
159
+ "#{@actual.inspect} #{@bool ? '<=' : '>'} #{expected}: failed."
160
+ }
161
+ self
162
+ end
163
+
164
+ def =~(expected)
165
+ _done()
166
+ #@bool ? assert_match(expected, @actual) : assert_no_match(expected, @actual)
167
+ __assert(@bool == !!(@actual =~ expected)) {
168
+ op = @bool ? '=~' : '!~'
169
+ msg = "$actual #{op} $expected: failed.\n"\
170
+ " $expected: #{expected.inspect}\n"
171
+ if @actual =~ /\n\z/
172
+ msg << " $actual: <<'END'\n#{@actual}END\n"
173
+ else
174
+ msg << " $actual: #{@actual.inspect}\n"
175
+ end
176
+ }
177
+ self
178
+ end
179
+
180
+ if RUBY_VERSION >= "1.9"
181
+ eval <<-'END', binding, __FILE__, __LINE__+1
182
+ def !~(expected)
183
+ _done()
184
+ #@bool ? assert_no_match(expected, @actual) : assert_match(expected, @actual)
185
+ __assert(@bool == !!(@actual !~ expected)) {
186
+ op = @bool ? '!~' : '=~'
187
+ msg = "$actual #{op} $expected: failed.\n"\
188
+ " $expected: #{expected.inspect}\n"
189
+ if @actual =~ /\n\z/
190
+ msg << " $actual: <<'END'\n#{@actual}END\n"
191
+ else
192
+ msg << " $actual: #{@actual.inspect}\n"
193
+ end
194
+ }
195
+ self
196
+ end
197
+ END
198
+ end
199
+
200
+ def in_delta?(expected, delta)
201
+ _done()
202
+ __assert(@bool == !!((@actual - expected).abs < delta)) {
203
+ eq = @bool ? '' : ' == false'
204
+ "($actual - $expected).abs < #{delta}#{eq}: failed.\n"\
205
+ " $actual: #{@actual.inspect}\n"\
206
+ " $expected: #{expected.inspect}\n"\
207
+ " ($actual - $expected).abs: #{(@actual - expected).abs.inspect}"
208
+ }
209
+ self
210
+ end
211
+
212
+ def same?(expected)
213
+ _done()
214
+ #@bool ? assert_same(expected, @actual) \
215
+ # : assert_not_same(expected, @actual)
216
+ __assert(@bool == !! @actual.equal?(expected)) {
217
+ eq = @bool ? '' : ' == false'
218
+ "$actual.equal?($expected)#{eq}: failed.\n"\
219
+ " $actual: #{@actual.inspect}\n"\
220
+ " $expected: #{expected.inspect}\n"
221
+ }
222
+ self
223
+ end
224
+
225
+ def method_missing(method_name, *args)
226
+ _done()
227
+ method_name.to_s =~ /\?\z/ or
228
+ super
229
+ begin
230
+ ret = @actual.__send__(method_name, *args)
231
+ rescue NoMethodError, TypeError => ex
232
+ ex.set_backtrace(caller(1))
233
+ raise ex
234
+ end
235
+ if ret == true || ret == false
236
+ __assert(@bool == ret) {
237
+ args = args.empty? ? '' : "(#{args.collect {|x| x.inspect }.join(', ')})"
238
+ eq = @bool ? '' : ' == false'
239
+ "$actual.#{method_name}#{args}#{eq}: failed.\n"\
240
+ " $actual: #{@actual.inspect}"
241
+ }
242
+ else
243
+ raise TypeError.new("ok(): #{@actual.class}##{method_name}() expected to return true or false, but got #{ret.inspect}.")
244
+ end
245
+ self
246
+ end
247
+
248
+ #--
249
+ #def same? expected ; _done(); assert_same expected, @actual; self; end
250
+ #def not_same? expected ; _done(); assert_not_same expected, @actual; self; end
251
+ #def is_a? expected ; _done(); assert_kind_of expected, @actual; self; end
252
+ #def instance_of? expected ; _done(); assert_instance_of expected, @actual; self; end
253
+ #def respond_to? expected ; _done(); assert_respond_to expected, @actual; self; end
254
+ #def nil? ; _done(); assert_nil @actual; self; end
255
+ #def not_nil? ; _done(); assert_not_nil @actual; self; end
256
+ #++
257
+
258
+ def raise?(expected=Exception, errmsg=nil)
259
+ _done()
260
+ proc_obj = @actual
261
+ if @bool
262
+ ex = nil
263
+ begin
264
+ proc_obj.call
265
+ rescue Exception => ex
266
+ ex.is_a?(expected) or
267
+ __assert(false) { "Expected #{expected.inspect} to be raised but got #{ex.class}." }
268
+ end
269
+ (class << proc_obj; self; end).class_eval { attr_accessor :exception }
270
+ proc_obj.exception = ex
271
+ __assert(! ex.nil?) { "Expected #{expected.inspect} to be raised but nothing raised." }
272
+ case errmsg
273
+ when nil; # do nothing
274
+ when Regexp
275
+ __assert(ex.message =~ errmsg) {
276
+ "$error_message =~ #{errmsg.inspect}: failed.\n"\
277
+ " $error_message: #{ex.message.inspect}"
278
+ }
279
+ else
280
+ __assert(errmsg == ex.message) {
281
+ "$error_message == #{errmsg.inspect}: failed.\n"\
282
+ " $error_message: #{ex.message.inspect}"
283
+ }
284
+ end
285
+ else
286
+ ! errmsg or
287
+ raise ArgumentError.new("#{errmsg.inspect}: NOT.raise?() can't take errmsg.")
288
+ begin
289
+ proc_obj.call
290
+ rescue Exception => ex
291
+ __assert(! ex.is_a?(expected)) {
292
+ "#{expected.inspect} should not be raised but got #{ex.inspect}."
293
+ }
294
+ end
295
+ end
296
+ self
297
+ end
298
+
299
+ #--
300
+ #def not_raise?
301
+ # _done()
302
+ # ! @bool or
303
+ # StandardError.new("ok().not_raise? is not available with '.NOT'.")
304
+ # proc_obj = @actual
305
+ # assert_nothing_raised(&proc_obj)
306
+ # self
307
+ #end
308
+ #++
309
+
310
+ def in?(expected)
311
+ _done()
312
+ __assert(@bool == !! expected.include?(@actual)) {
313
+ eq = @bool ? '' : ' == false'
314
+ "$expected.include?($actual)#{eq}: failed.\n"\
315
+ " $actual: #{@actual.inspect}\n"\
316
+ " $expected: #{expected.inspect}"
317
+ }
318
+ self
319
+ end
320
+
321
+ def include?(expected)
322
+ _done()
323
+ __assert(@bool == !! @actual.include?(expected)) {
324
+ eq = @bool ? '' : ' == false'
325
+ "$actual.include?($expected)#{eq}: failed.\n"\
326
+ " $actual: #{@actual.inspect}\n"\
327
+ " $expected: #{expected.inspect}"
328
+ }
329
+ self
330
+ end
331
+
332
+ def attr(name, val=nil)
333
+ _done()
334
+ d = name.is_a?(Hash) ? name : {name=>val}
335
+ d.each_pair do |k, v|
336
+ attr_val = @actual.__send__(k)
337
+ __assert(@bool == (attr_val == v)) {
338
+ op = @bool ? '==' : '!='
339
+ "$actual.#{k} #{op} $expected: failed.\n"\
340
+ " $actual.#{k}: #{attr_val}\n"\
341
+ " $expected: #{v.inspect}"\
342
+ }
343
+ end
344
+ self
345
+ end
346
+
347
+ def length(n)
348
+ _done()
349
+ __assert(@bool == (@actual.length == n)) {
350
+ op = @bool ? '==' : '!='
351
+ "$actual.length #{op} #{n}: failed.\n"\
352
+ " $actual.length: #{@actual.length}\n"\
353
+ " $actual: #{actual.inspect}"
354
+ }
355
+ self
356
+ end
357
+
358
+ #--
359
+ #def empty?; _done(); @actual.empty? or flunk "#{@actual}.empty?: failed."; self; end
360
+ #def not_empty?; _done(); ! @actual.empty? or flunk "! #{@actual}.empty?: failed."; self; end
361
+ #def truthy?; _done(); !! @actual == true or flunk "!! #{@actual} == true: failed."; self; end
362
+ #def falsy?; _done(); !! @actual == false or flunk "!! #{@actual} == false: failed."; self; end
363
+ #++
364
+
365
+ def truthy?
366
+ _done()
367
+ __assert(@bool == (!!@actual == true)) {
368
+ op = @bool ? '==' : '!='
369
+ "!!$actual #{op} true: failed.\n"\
370
+ " $actual: #{@actual.inspect}"
371
+ }
372
+ self
373
+ end
374
+
375
+ def falsy?
376
+ _done()
377
+ __assert(@bool == (!!@actual == false)) {
378
+ op = @bool ? '==' : '!='
379
+ "!!$actual #{op} false: failed.\n"\
380
+ " $actual: #{@actual.inspect}"
381
+ }
382
+ self
383
+ end
384
+
385
+ def file_exist?
386
+ _done()
387
+ __assert(@bool == File.file?(@actual)) {
388
+ eq = @bool ? '' : ' == false'
389
+ "File.file?($actual)#{eq}: failed.\n"\
390
+ " $actual: #{@actual.inspect}"
391
+ }
392
+ self
393
+ end
394
+
395
+ def dir_exist?
396
+ _done()
397
+ __assert(@bool == File.directory?(@actual)) {
398
+ eq = @bool ? '' : ' == false'
399
+ "File.directory?($actual)#{eq}: failed.\n"\
400
+ " $actual: #{@actual.inspect}"
401
+ }
402
+ self
403
+ end
404
+
405
+ def exist?
406
+ _done()
407
+ __assert(@bool == File.exist?(@actual)) {
408
+ eq = @bool ? '' : ' == false'
409
+ "File.exist?($actual)#{eq}: failed.\n"\
410
+ " $actual: #{@actual.inspect}"
411
+ }
412
+ self
413
+ end
414
+
415
+ end
416
+
417
+
418
+ class ScopeObject
419
+
420
+ def initialize
421
+ @children = []
422
+ @fixtures = {}
423
+ end
424
+
425
+ attr_accessor :name, :parent, :children, :before, :after, :before_all, :after_all, :fixtures
426
+ attr_accessor :_klass, :_prefix #:nodoc:
427
+
428
+ def add_child(child)
429
+ if child.is_a?(ScopeObject)
430
+ child.parent.nil? or
431
+ raise ArgumentError.new("add_child(): can't add child scope which already belongs to other.")
432
+ child.parent = self
433
+ end
434
+ @children << child
435
+ end
436
+
437
+ def get_fixture_info(name)
438
+ return @fixtures[name]
439
+ #@fixtures ? @fixtures[name] : nil # or [nil, nil, nil]?
440
+ end
441
+
442
+ def new_context
443
+ return @_klass.new
444
+ end
445
+
446
+ def accept(runner, *args)
447
+ raise NotImplementedError.new("#{self.class.name}#accept(): not implemented yet.")
448
+ end
449
+
450
+ def _repr(depth=0, buf="")
451
+ indent = " " * depth
452
+ buf << "#{indent}-\n"
453
+ instance_variables().sort.each do |name|
454
+ next if name.to_s == "@children"
455
+ buf << "#{indent} #{name}: #{instance_variable_get(name).inspect}\n"
456
+ end
457
+ @children.each {|child| child._repr(depth+1, buf) }
458
+ return buf
459
+ end
460
+
461
+ def +@
462
+ self
463
+ end
464
+
465
+ end
466
+
467
+
468
+ class FileScopeObject < ScopeObject
469
+
470
+ attr_accessor :filename
471
+
472
+ def initialize(filename=nil)
473
+ super()
474
+ @filename = filename
475
+ end
476
+
477
+ def accept(runner, *args)
478
+ return runner.run_topic(self, *args)
479
+ end
480
+
481
+ end
482
+
483
+
484
+ class TopicObject < ScopeObject
485
+
486
+ def initialize(name=nil)
487
+ super()
488
+ @name = name
489
+ end
490
+
491
+ def accept(runner, *args)
492
+ return runner.run_topic(self, *args)
493
+ end
494
+
495
+ end
496
+
497
+
498
+ module ScopeClassMethods
499
+
500
+ #attr_accessor :_scope
501
+
502
+ def before(&block); @_scope.before = block; end
503
+ def after(&block); @_scope.after = block; end
504
+ def before_all(&block); @_scope.before_all = block; end
505
+ def after_all(&block); @_scope.after_all = block; end
506
+
507
+ def fixture(name, &block)
508
+ location = caller(1).first
509
+ argnames = block.arity > 0 ? Util.block_argnames(block, location) : nil
510
+ @_scope.fixtures[name] = [block, argnames, location]
511
+ end
512
+
513
+ def topic(name, &block)
514
+ topic = TopicObject.new(name)
515
+ @_scope.add_child(topic)
516
+ klass = Class.new(self)
517
+ klass.class_eval do
518
+ extend ScopeClassMethods
519
+ include Test::Unit::Assertions
520
+ include SpecHelper
521
+ @_scope = topic
522
+ end
523
+ klass.class_eval(&block)
524
+ topic._klass = klass
525
+ topic._prefix = '*'
526
+ return topic
527
+ end
528
+
529
+ def case_when(desc, &block)
530
+ return __case_when("When #{desc}", &block)
531
+ end
532
+
533
+ def case_else(&block)
534
+ return __case_when("Else", &block)
535
+ end
536
+
537
+ def __case_when(desc, &block)
538
+ obj = topic(desc, &block)
539
+ obj._prefix = '-'
540
+ return obj
541
+ end
542
+ private :__case_when
543
+
544
+ def spec(desc, &block)
545
+ location = caller(1).first
546
+ if block
547
+ argnames = Util.block_argnames(block, location)
548
+ else
549
+ block = proc { raise TodoException.new("not implemented yet") }
550
+ argnames = []
551
+ end
552
+ spec = SpecObject.new(desc, block, argnames, location)
553
+ @_scope.add_child(spec)
554
+ spec._prefix = '-'
555
+ return spec
556
+ end
557
+
558
+ end
559
+
560
+
561
+ FILESCOPES = []
562
+
563
+
564
+ def self.scope(&block)
565
+ filename = caller(1).first =~ /:\d+/ ? $` : nil
566
+ filename = filename.sub(/\A\.\//, '')
567
+ scope = FileScopeObject.new(filename)
568
+ klass = Class.new
569
+ klass.class_eval do
570
+ extend ScopeClassMethods
571
+ @_scope = scope
572
+ end
573
+ klass.class_eval(&block)
574
+ scope._klass = klass
575
+ FILESCOPES << scope
576
+ return scope
577
+ end
578
+
579
+
580
+ class SpecObject
581
+
582
+ def initialize(desc, block, argnames, location)
583
+ @desc = desc
584
+ @block = block
585
+ @argnames = argnames
586
+ @location = location # necessary when raising fixture not found error
587
+ end
588
+
589
+ attr_reader :desc, :block, :argnames, :location #:nodoc:
590
+ attr_accessor :_prefix #:nodoc:
591
+
592
+ def accept(runner, *args) #:nodoc:
593
+ runner.run_spec(self, *args)
594
+ end
595
+
596
+ def _repr(depth=0, buf="") #:nodoc:
597
+ buf << " " * depth << "- #{@desc}\n"
598
+ return buf
599
+ end
600
+
601
+ def -@
602
+ self
603
+ end
604
+
605
+ end
606
+
607
+
608
+ module SpecHelper
609
+
610
+ attr_accessor :_TODO, :_at_end_blocks
611
+
612
+ def skip_when(condition, reason)
613
+ raise SkipException.new(reason) if condition
614
+ end
615
+
616
+ def TODO
617
+ @_TODO = true
618
+ end
619
+
620
+ def at_end(&block)
621
+ (@_at_end_blocks ||= []) << block
622
+ end
623
+
624
+ def tmp
625
+ unless @_tmp
626
+ require 'section9/tmp' unless defined?(Section9::Tmp)
627
+ @_tmp = Section9::Tmp.new
628
+ at_end { @_tmp.revert }
629
+ end
630
+ return @_tmp
631
+ end
632
+
633
+ def recorder
634
+ require 'section9/recorder' unless defined?(Section9::Recorder)
635
+ return Section9::Recorder.new
636
+ end
637
+
638
+ end
639
+
640
+
641
+ STATUSES = [:PASS, :FAIL, :ERROR, :SKIP, :TODO]
642
+
643
+
644
+ class Runner
645
+
646
+ def initialize(reporter)
647
+ @reporter = reporter
648
+ end
649
+
650
+ def run_topic(topic, depth, parent)
651
+ @reporter.enter_topic(topic, depth)
652
+ call_before_all_block(topic)
653
+ topic.children.each do |child|
654
+ child.accept(self, depth+1, topic)
655
+ end
656
+ call_after_all_block(topic)
657
+ @reporter.exit_topic(topic, depth)
658
+ end
659
+
660
+ def run_spec(spec, depth, parent)
661
+ return if ENV['SPEC'] && ENV['SPEC'] != spec.desc
662
+ @reporter.enter_spec(spec, depth)
663
+ topic = parent
664
+ context = new_context(topic, spec)
665
+ call_before_blocks(topic, context)
666
+ status = :PASS
667
+ ex = nil
668
+ begin
669
+ if spec.argnames.empty?
670
+ call_spec_block(spec, context)
671
+ else
672
+ values = get_fixture_values(spec.argnames, topic, spec, context)
673
+ call_spec_block(spec, context, *values)
674
+ end
675
+ rescue NoMemoryError => ex; raise ex
676
+ rescue SignalException => ex; raise ex
677
+ rescue FAIL_EXCEPTION => ex; status = :FAIL
678
+ rescue SKIP_EXCEPTION => ex; status = :SKIP
679
+ rescue TODO_EXCEPTION => ex; status = :TODO
680
+ rescue Exception => ex; status = :ERROR
681
+ end
682
+ if context._TODO
683
+ if status == :PASS
684
+ status = :FAIL
685
+ ex = FAIL_EXCEPTION.new("spec should be failed (because not implemented yet), but passed unexpectedly.")
686
+ elsif status == :FAIL
687
+ status = :TODO
688
+ ex = TODO_EXCEPTION.new("not implemented yet")
689
+ end
690
+ ex.set_backtrace([spec.location])
691
+ end
692
+ begin
693
+ call_at_end_blocks(context)
694
+ ensure
695
+ call_after_blocks(topic, context)
696
+ end
697
+ @reporter.exit_spec(spec, depth, status, ex, parent)
698
+ end
699
+
700
+ def run_all
701
+ @reporter.enter_all(self)
702
+ while (scope = FILESCOPES.shift)
703
+ run_filescope(scope)
704
+ end
705
+ @reporter.exit_all(self)
706
+ end
707
+
708
+ def run_filescope(filescope)
709
+ @reporter.enter_file(filescope.filename)
710
+ call_before_all_block(filescope)
711
+ filescope.children.each do |child|
712
+ child.accept(self, 0, nil)
713
+ end
714
+ call_after_all_block(filescope)
715
+ @reporter.exit_file(filescope.filename)
716
+ end
717
+
718
+ private
719
+
720
+ def new_context(topic, spec)
721
+ return topic.new_context()
722
+ end
723
+
724
+ def get_fixture_values(names, topic, spec, context)
725
+ return FixtureManager.instance.get_fixture_values(names, topic, spec, context)
726
+ end
727
+
728
+ def _call_blocks_parent_first(topic, name, obj)
729
+ blocks = []
730
+ while topic
731
+ block = topic.__send__(name)
732
+ blocks << block if block
733
+ topic = topic.parent
734
+ end
735
+ blocks.reverse.each {|blk| obj.instance_eval(&blk) }
736
+ blocks.clear
737
+ end
738
+
739
+ def _call_blocks_child_first(topic, name, obj)
740
+ while topic
741
+ block = topic.__send__(name)
742
+ obj.instance_eval(&block) if block
743
+ topic = topic.parent
744
+ end
745
+ end
746
+
747
+ def call_before_blocks(topic, spec)
748
+ _call_blocks_parent_first(topic, :before, spec)
749
+ end
750
+
751
+ def call_after_blocks(topic, spec)
752
+ _call_blocks_child_first(topic, :after, spec)
753
+ end
754
+
755
+ def call_before_all_block(topic)
756
+ block = topic.before_all
757
+ topic.instance_eval(&block) if block
758
+ end
759
+
760
+ def call_after_all_block(topic)
761
+ block = topic.after_all
762
+ topic.instance_eval(&block) if block
763
+ end
764
+
765
+ def call_spec_block(spec, context, *args)
766
+ if args.empty?
767
+ context.instance_eval(&spec.block)
768
+ else
769
+ context.instance_exec(*args, &spec.block)
770
+ end
771
+ end
772
+
773
+ def call_at_end_blocks(context)
774
+ blocks = context._at_end_blocks
775
+ if blocks
776
+ blocks.reverse_each {|block| context.instance_eval(&block) }
777
+ blocks.clear
778
+ end
779
+ end
780
+
781
+ end
782
+
783
+
784
+ RUNNER = Runner
785
+
786
+
787
+ class FixtureManager
788
+
789
+ def self.instance
790
+ return @instance ||= self.new
791
+ end
792
+
793
+ def get_fixture_values(names, topic, spec, context, location=nil, resolved={}, resolving=[])
794
+ location ||= spec.location
795
+ return names.collect {|name|
796
+ ! resolving.include?(name) or
797
+ raise _looped_dependency_error(name, resolving, location)
798
+ get_fixture_value(name, topic, spec, context, location, resolved, resolving)
799
+ }
800
+ end
801
+
802
+ def get_fixture_value(name, topic, spec, context, location=nil, resolved={}, resolving=[])
803
+ return resolved[name] if resolved.key?(name)
804
+ location ||= spec.location
805
+ tuple = topic.get_fixture_info(name)
806
+ if tuple
807
+ block, argnames, location = tuple
808
+ if argnames
809
+ resolving << name
810
+ args = get_fixture_values(argnames, topic, spec, context, location, resolved, resolving)
811
+ (popped = resolving.pop) == name or
812
+ raise "** assertion failed: name=#{name.inspect}, resolvng[-1]=#{popped.inspect}"
813
+ val = context.instance_exec(*args, &block)
814
+ else
815
+ val = context.instance_eval(&block)
816
+ end
817
+ resolved[name] = val
818
+ return val
819
+ elsif topic.parent
820
+ return get_fixture_value(name, topic.parent, spec, context, location, resolved, resolving)
821
+ else
822
+ ex = FixtureNotFoundError.new("#{name}: fixture not found. (spec: #{spec.desc})")
823
+ ex.set_backtrace([location])
824
+ raise ex
825
+ end
826
+ end
827
+
828
+ private
829
+
830
+ def _looped_dependency_error(name, resolving, location)
831
+ resolving << name
832
+ i = resolving.index(name)
833
+ s1 = resolving[0...i].join('->')
834
+ s2 = resolving[i..-1].join('=>')
835
+ loop = s1.empty? ? s2 : "#{s1}->#{s2}"
836
+ #location = $1 if location =~ /(.*:\d+)/
837
+ ex = LoopedDependencyError.new("fixture dependency is looped: #{loop}")
838
+ ex.set_backtrace([location])
839
+ return ex
840
+ end
841
+
842
+ end
843
+
844
+
845
+ class FixtureNotFoundError < StandardError
846
+ end
847
+
848
+ class LoopedDependencyError < StandardError
849
+ end
850
+
851
+
852
+ class Reporter
853
+
854
+ def enter_all(runner); end
855
+ def exit_all(runner); end
856
+ def enter_file(filename); end
857
+ def exit_file(filename); end
858
+ def enter_topic(topic, depth); end
859
+ def exit_topic(topic, depth); end
860
+ def enter_spec(spec, depth); end
861
+ def exit_spec(spec, depth, status, error, parent); end
862
+ #
863
+ def counts; {}; end
864
+
865
+ end
866
+
867
+
868
+ class BaseReporter < Reporter
869
+
870
+ LABELS = { :PASS=>'pass', :FAIL=>'Fail', :ERROR=>'ERROR', :SKIP=>'Skip', :TODO=>'TODO' }
871
+ CHARS = { :PASS=>'.', :FAIL=>'f', :ERROR=>'E', :SKIP=>'s', :TODO=>'t' }
872
+
873
+
874
+ def initialize
875
+ @exceptions = []
876
+ @counts = {}
877
+ end
878
+
879
+ attr_reader :counts
880
+
881
+ def enter_all(runner)
882
+ reset_counts()
883
+ @start_at = Time.now
884
+ end
885
+
886
+ def exit_all(runner)
887
+ elapsed = Time.now - @start_at
888
+ puts footer(elapsed)
889
+ end
890
+
891
+ def enter_file(filename)
892
+ end
893
+
894
+ def exit_file(filename)
895
+ end
896
+
897
+ def enter_topic(topic, depth)
898
+ end
899
+
900
+ def exit_topic(topic, depth)
901
+ end
902
+
903
+ def enter_spec(spec, depth)
904
+ end
905
+
906
+ def exit_spec(spec, depth, status, ex, parent)
907
+ @counts[status] += 1
908
+ @exceptions << [spec, status, ex, parent] if status == :FAIL || status == :ERROR
909
+ end
910
+
911
+ protected
912
+
913
+ def reset_counts
914
+ STATUSES.each {|sym| @counts[sym] = 0 }
915
+ end
916
+
917
+ def print_exceptions
918
+ sep = '-' * 70
919
+ @exceptions.each do |tuple|
920
+ puts sep
921
+ print_exc(*tuple)
922
+ tuple.clear
923
+ end
924
+ puts sep if ! @exceptions.empty?
925
+ @exceptions.clear
926
+ end
927
+
928
+ def print_exc(spec, status, ex, parent)
929
+ label = Color.status(status, LABELS[status])
930
+ topic = parent
931
+ path = Color.topic(spec_path(spec, topic))
932
+ topic = parent
933
+ puts "[#{label}] #{path}"
934
+ print_exc_backtrace(ex, status)
935
+ print_exc_message(ex, status)
936
+ end
937
+
938
+ def print_exc_backtrace(ex, status)
939
+ rexp = FILENAME_FILTER
940
+ prev_file = prev_line = nil
941
+ ex.backtrace.each_with_index do |str, i|
942
+ next if str =~ rexp && ! (i == 0 && status == :ERROR)
943
+ linestr = nil
944
+ if str =~ /:(\d+)/
945
+ file = $` # file path
946
+ line = $1.to_i # line number
947
+ next if file == prev_file && line == prev_line
948
+ linestr = Util.file_line(file, line) if str && File.exist?(file)
949
+ prev_file, prev_line = file, line
950
+ end
951
+ puts " #{str}"
952
+ puts " #{linestr.strip}" if linestr
953
+ end
954
+ end
955
+
956
+ FILENAME_FILTER = %r`/(?:oktest|minitest/unit|test/unit(?:/assertions|/testcase)?)\.rbc?:` #:nodoc:
957
+
958
+ def print_exc_message(ex, status)
959
+ #puts Color.status(status, "#{ex.class.name}: #{ex}")
960
+ if status == :FAIL
961
+ msg = "#{ex}"
962
+ else
963
+ msg = "#{ex.class.name}: #{ex}"
964
+ end
965
+ lines = []
966
+ msg.each_line {|line| lines << line }
967
+ #puts Color.status(status, lines.shift.chomp)
968
+ puts lines.shift.chomp
969
+ puts lines.join.chomp unless lines.empty?
970
+ puts ex.diff if ex.respond_to?(:diff) && ex.diff # for oktest.rb
971
+ end
972
+
973
+ def footer(elapsed)
974
+ total = 0; @counts.each {|k, v| total += v }
975
+ buf = "## total:#{total}"
976
+ STATUSES.each do |st|
977
+ s = "#{st.to_s.downcase}:#{@counts[st]}"
978
+ s = Color.status(st, s) if @counts[st] > 0
979
+ buf << ", " << s
980
+ end
981
+ buf << " (in %.3fs)" % elapsed
982
+ return buf
983
+ end
984
+
985
+ def spec_path(spec, topic)
986
+ arr = []
987
+ while topic
988
+ arr << topic.name.to_s if topic.name
989
+ topic = topic.parent
990
+ end
991
+ arr.reverse!
992
+ arr << spec.desc
993
+ return arr.join(" > ")
994
+ end
995
+
996
+ end
997
+
998
+
999
+ class VerboseReporter < BaseReporter
1000
+
1001
+ LABELS = { :PASS=>'pass', :FAIL=>'Fail', :ERROR=>'ERROR', :SKIP=>'Skip', :TODO=>'TODO' }
1002
+
1003
+ def enter_topic(topic, depth)
1004
+ super
1005
+ puts "#{' ' * depth}#{topic._prefix} #{Color.topic(topic.name)}"
1006
+ end
1007
+
1008
+ def exit_topic(topic, depth)
1009
+ print_exceptions()
1010
+ end
1011
+
1012
+ def enter_spec(spec, depth)
1013
+ if $stdout.tty?
1014
+ str = "#{' ' * depth}#{spec._prefix} [ ] #{spec.desc}"
1015
+ print Util.strfold(str, 79)
1016
+ $stdout.flush
1017
+ end
1018
+ end
1019
+
1020
+ def exit_spec(spec, depth, status, error, parent)
1021
+ super
1022
+ if $stdout.tty?
1023
+ print "\r" # clear line
1024
+ $stdout.flush
1025
+ end
1026
+ label = Color.status(status, LABELS[status] || '???')
1027
+ msg = "#{' ' * depth}- [#{label}] #{spec.desc}"
1028
+ #msg << " (reason: #{error.message})" if error.is_a?(SkipException)
1029
+ msg << " " << Color.reason("(reason: #{error.message})") if error.is_a?(SkipException)
1030
+ puts msg
1031
+ end
1032
+
1033
+ end
1034
+
1035
+
1036
+ class SimpleReporter < BaseReporter
1037
+
1038
+ def enter_file(filename)
1039
+ print "#{filename}: "
1040
+ end
1041
+
1042
+ def exit_file(filename)
1043
+ puts
1044
+ print_exceptions()
1045
+ end
1046
+
1047
+ def exit_spec(spec, depth, status, error, parent)
1048
+ super
1049
+ print Color.status(status, CHARS[status] || '?')
1050
+ $stdout.flush
1051
+ end
1052
+
1053
+ end
1054
+
1055
+
1056
+ class PlainReporter < BaseReporter
1057
+
1058
+ def exit_all(runner)
1059
+ elapsed = Time.now - @start_at
1060
+ puts
1061
+ print_exceptions()
1062
+ puts footer(elapsed)
1063
+ end
1064
+
1065
+ def exit_spec(spec, depth, status, error, parent)
1066
+ super
1067
+ print Color.status(status, CHARS[status] || '?')
1068
+ $stdout.flush
1069
+ end
1070
+
1071
+ end
1072
+
1073
+
1074
+ REPORTER = VerboseReporter
1075
+
1076
+
1077
+ REPORTER_CLASSES = {
1078
+ 'verbose' => VerboseReporter, 'v' => VerboseReporter,
1079
+ 'simple' => SimpleReporter, 's' => SimpleReporter,
1080
+ 'plain' => PlainReporter, 'p' => PlainReporter,
1081
+ }
1082
+
1083
+
1084
+ #def self.run(*topics)
1085
+ # opts = topics[-1].is_a?(Hash) ? topics.pop() : {}
1086
+ # topics = TOPICS if topics.empty?
1087
+ # return if topics.empty?
1088
+ # klass = (opts[:style] ? REPORTER_CLASSES[opts[:style]] : REPORTER) or
1089
+ # raise ArgumentError.new("#{opts[:style].inspect}: unknown style.")
1090
+ # reporter = klass.new
1091
+ # runner = Runner.new(reporter)
1092
+ # runner.run_all(*topics)
1093
+ # counts = reporter.counts
1094
+ # return counts[:FAIL] + counts[:ERROR]
1095
+ #end
1096
+ def self.run(opts={})
1097
+ return if FILESCOPES.empty?
1098
+ klass = (opts[:style] ? REPORTER_CLASSES[opts[:style]] : REPORTER) or
1099
+ raise ArgumentError.new("#{opts[:style].inspect}: unknown style.")
1100
+ reporter = klass.new
1101
+ runner = Runner.new(reporter)
1102
+ runner.run_all()
1103
+ FILESCOPES.clear
1104
+ counts = reporter.counts
1105
+ return counts[:FAIL] + counts[:ERROR]
1106
+ end
1107
+
1108
+
1109
+ module Util
1110
+
1111
+ module_function
1112
+
1113
+ def file_line(filename, linenum)
1114
+ return nil unless File.file?(filename)
1115
+ @cache ||= [nil, []]
1116
+ if @cache[0] != filename
1117
+ @cache[0] = filename
1118
+ @cache[1].clear
1119
+ @cache[1] = lines = File.open(filename, 'rb') {|f| f.to_a }
1120
+ else
1121
+ lines = @cache[1]
1122
+ end
1123
+ return lines[linenum-1]
1124
+ end
1125
+
1126
+ def block_argnames(block, location)
1127
+ return nil unless block
1128
+ return [] if block.arity <= 0
1129
+ if block.respond_to?(:parameters)
1130
+ argnames = block.parameters.collect {|pair| pair.last }
1131
+ else
1132
+ location =~ /:(\d+)/
1133
+ filename = $`
1134
+ linenum = $1.to_i
1135
+ File.file?(filename) or
1136
+ raise ArgumentError.new("block_argnames(): #{filename.inspect}: source file not found.")
1137
+ linestr = file_line(filename, linenum) || ""
1138
+ linestr =~ /(?:\bdo|\{) *\|(.*)\|/ or
1139
+ raise ArgumentError.new("spec(): can't detect block parameters at #{filename}:#{linenum}")
1140
+ argnames = $1.split(/,/).collect {|var| var.strip.intern }
1141
+ end
1142
+ return argnames
1143
+ end
1144
+
1145
+ def strfold(str, width=80, mark='...')
1146
+ return str if str.length <= width
1147
+ return str[0, width - mark.length] + mark if str =~ /\A[\x00-\x7f]*\z/
1148
+ limit = width - mark.length
1149
+ w = len = 0
1150
+ str.split(//u).each do |ch|
1151
+ n = ch.length
1152
+ w += n == 1 ? 1 : 2
1153
+ break if w >= limit
1154
+ len += n
1155
+ end
1156
+ str = str[0, len] + mark if w >= limit
1157
+ return str
1158
+ end
1159
+ def strfold(str, width=80, mark='...')
1160
+ return str if str.bytesize <= width
1161
+ return str[0, width - mark.length] + mark if str.ascii_only?
1162
+ limit = width - mark.length
1163
+ w = len = 0
1164
+ str.each_char do |ch|
1165
+ w += ch.bytesize == 1 ? 1 : 2
1166
+ break if w >= limit
1167
+ len += 1
1168
+ end
1169
+ str = str[0, len] + mark if w >= limit
1170
+ return str
1171
+ end if RUBY_VERSION >= '1.9'
1172
+
1173
+
1174
+ def chain_errors(*errors)
1175
+ end
1176
+
1177
+
1178
+ def _text2lines(text, no_newline_msg=nil)
1179
+ lines = []
1180
+ text.each_line {|line| line.chomp!; lines << line }
1181
+ lines[-1] << no_newline_msg if no_newline_msg && text[-1] && text[-1] != ?\n
1182
+ return lines
1183
+ end
1184
+ private :_text2lines
1185
+
1186
+ ## platform independent, but requires 'diff-lcs' gem
1187
+ def unified_diff(text_old, text_new, label="--- old\n+++ new\n", context=3)
1188
+ msg = "\\ No newline at end of string"
1189
+ lines_old = _text2lines(text_old, msg)
1190
+ lines_new = _text2lines(text_new, msg)
1191
+ #
1192
+ buf = "#{label}"
1193
+ len = 0
1194
+ prevhunk = hunk = nil
1195
+ diffs = Diff::LCS.diff(lines_old, lines_new)
1196
+ diffs.each do |diff|
1197
+ hunk = Diff::LCS::Hunk.new(lines_old, lines_new, diff, context, len)
1198
+ if hunk.overlaps?(prevhunk)
1199
+ hunk.unshift(prevhunk)
1200
+ else
1201
+ buf << prevhunk.diff(:unified) << "\n"
1202
+ end if prevhunk
1203
+ len = hunk.file_length_difference
1204
+ prevhunk = hunk
1205
+ end
1206
+ buf << prevhunk.diff(:unified) << "\n" if prevhunk
1207
+ return buf
1208
+ end
1209
+
1210
+ ## platform depend, but not require extra library
1211
+ def diff_unified(text_old, text_new, label="--- old\n+++ new\n", context=3)
1212
+ tmp_old = "_tmp.old.#{rand()}"
1213
+ tmp_new = "_tmp.new.#{rand()}"
1214
+ File.open(tmp_old, 'w') {|f| f.write(text_old) }
1215
+ File.open(tmp_new, 'w') {|f| f.write(text_new) }
1216
+ begin
1217
+ #diff = `diff -u #{tmp_old} #{tmp_new}`
1218
+ diff = `diff --unified=#{context} #{tmp_old} #{tmp_new}`
1219
+ ensure
1220
+ File.unlink(tmp_old)
1221
+ File.unlink(tmp_new)
1222
+ end
1223
+ diff.sub!(/\A\-\-\-.*\n\+\+\+.*\n/, label.to_s)
1224
+ return diff
1225
+ end
1226
+
1227
+ ## when diff-lcs is not installed then use diff command
1228
+ begin
1229
+ require 'diff/lcs'
1230
+ #require 'diff/lcs/string'
1231
+ require 'diff/lcs/hunk'
1232
+ rescue LoadError
1233
+ alias _unified_diff unified_diff
1234
+ alias unified_diff diff_unified
1235
+ class << self
1236
+ alias _unified_diff unified_diff
1237
+ alias unified_diff diff_unified
1238
+ end
1239
+ end
1240
+
1241
+ end
1242
+
1243
+
1244
+ class Config
1245
+
1246
+ @os_windows = RUBY_PLATFORM =~ /mswin|mingw/i
1247
+ @auto_run = true
1248
+ @color_available = ! @os_windows
1249
+ @color_enabled = @color_available
1250
+ @diff_command = @os_windows ? "diff.exe -u" : "diff -u"
1251
+ @system_exit = true # exit() on Oktest.main()
1252
+
1253
+ class << self
1254
+ attr_accessor :auto_run, :color_available, :color_enabled, :system_exit
1255
+ end
1256
+
1257
+ end
1258
+
1259
+
1260
+ module Color
1261
+
1262
+ module_function
1263
+
1264
+ def normal (s); return s; end
1265
+ def bold (s); return "\x1b[0;1m#{s}\x1b[22m"; end
1266
+ def black (s); return "\x1b[1;30m#{s}\x1b[0m"; end
1267
+ def red (s); return "\x1b[1;31m#{s}\x1b[0m"; end
1268
+ def green (s); return "\x1b[1;32m#{s}\x1b[0m"; end
1269
+ def yellow (s); return "\x1b[1;33m#{s}\x1b[0m"; end
1270
+ def blue (s); return "\x1b[1;34m#{s}\x1b[0m"; end
1271
+ def magenta(s); return "\x1b[1;35m#{s}\x1b[0m"; end
1272
+ def cyan (s); return "\x1b[1;36m#{s}\x1b[0m"; end
1273
+ def white (s); return "\x1b[1;37m#{s}\x1b[0m"; end
1274
+
1275
+ def topic(s); Config.color_enabled ? bold(s) : s; end
1276
+ def spec (s); Config.color_enabled ? normal(s) : s; end
1277
+ def pass (s); Config.color_enabled ? green(s) : s; end
1278
+ def fail (s); Config.color_enabled ? red(s) : s; end
1279
+ def error(s); Config.color_enabled ? red(s) : s; end
1280
+ def skip (s); Config.color_enabled ? yellow(s) : s; end
1281
+ def todo (s); Config.color_enabled ? yellow(s) : s; end
1282
+ def reason(s); Config.color_enabled ? yellow(s) : s; end
1283
+ def status(status, s); __send__(status.to_s.downcase, s); end
1284
+
1285
+ end
1286
+
1287
+
1288
+ class TestGenerator
1289
+
1290
+ def parse(io)
1291
+ tree = _parse(io, [], nil)
1292
+ return tree
1293
+ end
1294
+
1295
+ def _parse(io, tree, end_indent)
1296
+ while (line = io.gets())
1297
+ case line
1298
+ when /^([ \t]*)end\b/
1299
+ return tree if $1 == end_indent
1300
+ when /^([ \t]*)(module|class|def) +(\w+[.:\w]*)/
1301
+ indent, keyword, topic = $1, $2, $3
1302
+ next if line =~ /\bend$/
1303
+ if keyword == 'def'
1304
+ topic = topic =~ /^self\./ ? ".#{$'}" : "\##{topic}"
1305
+ end
1306
+ newtree = []
1307
+ _parse(io, newtree, indent)
1308
+ tree << [indent, keyword, topic, newtree]
1309
+ when /^([ \t]*)\#[:;] (.*)/
1310
+ indent, keyword, spec = $1, 'spec', $2
1311
+ tree << [indent, keyword, spec]
1312
+ end
1313
+ end
1314
+ end_indent == nil or
1315
+ raise "parse error: end_indent=#{end_indent.inspect}"
1316
+ return tree
1317
+ end
1318
+ private :_parse
1319
+
1320
+ def transform(tree, depth=0)
1321
+ buf = ''
1322
+ tree.each do |tuple|
1323
+ _transform(tuple, depth, buf)
1324
+ end
1325
+ return buf
1326
+ end
1327
+
1328
+ def _transform(tuple, depth, buf)
1329
+ indent = ' ' * depth
1330
+ keyword = tuple[1]
1331
+ if keyword == 'spec'
1332
+ _, _, spec = tuple
1333
+ #buf << "\n"
1334
+ #buf << "#{indent}spec \"#{spec}\" do\n"
1335
+ #buf << "#{indent}end\n"
1336
+ escaped = spec.gsub(/"/, '\\\"')
1337
+ buf << "#{indent}spec \"#{escaped}\"\n"
1338
+ #buf << "#{indent}spec \"#{spec.gsub(/"/, '\\\"')}\"\n" #
1339
+ else
1340
+ _, _, topic, children = tuple
1341
+ buf << "\n"
1342
+ buf << "#{indent}topic '#{topic}' do\n" if keyword == 'def'
1343
+ buf << "#{indent}topic #{topic} do\n" if keyword != 'def'
1344
+ buf << "\n" unless keyword == 'def'
1345
+ children.each do |child_tuple|
1346
+ _transform(child_tuple, depth+1, buf)
1347
+ end
1348
+ buf << "\n"
1349
+ buf << "#{indent}end\n" if keyword == 'def'
1350
+ buf << "#{indent}end # #{topic}\n" if keyword != 'def'
1351
+ buf << "\n"
1352
+ end
1353
+ end
1354
+ private :_transform
1355
+
1356
+ def generate(io)
1357
+ tree = parse(io)
1358
+ return <<END
1359
+ # -*- coding: utf-8 -*-
1360
+
1361
+ require 'oktest'
1362
+
1363
+ Oktest.scope do
1364
+
1365
+ #{transform(tree)}
1366
+ end
1367
+ END
1368
+ end
1369
+
1370
+ end
1371
+
1372
+
1373
+ class MainApp
1374
+
1375
+ def main(args=nil)
1376
+ require 'optparse'
1377
+ begin
1378
+ status = run(args)
1379
+ exit(status) if Config.system_exit && status
1380
+ return status || 0
1381
+ rescue OptionParser::InvalidOption => ex
1382
+ command ||= File.basename($0)
1383
+ $stderr.write("#{command}: #{ex}\n")
1384
+ exit(1) if Config.system_exit
1385
+ return 1
1386
+ end
1387
+ end
1388
+
1389
+ def run(args=nil)
1390
+ ## parse command-line options
1391
+ args = ARGV.dup if args.nil?
1392
+ #opts = Options.new
1393
+ #parser = option_parser(opts)
1394
+ #filenames = parser.parse(args)
1395
+ cmdopt = option_parser()
1396
+ opts = cmdopt.parse(args)
1397
+ filenames = args
1398
+ ## help, version
1399
+ if opts.help
1400
+ puts help_message()
1401
+ return 0
1402
+ end
1403
+ if opts.version
1404
+ puts VERSION
1405
+ return 0
1406
+ end
1407
+ ## fix not to load this file twice.
1408
+ $" << __FILE__ unless $".include?(__FILE__)
1409
+ ##
1410
+ if opts.generate
1411
+ ## generate test code from source code
1412
+ generate(filenames)
1413
+ exit()
1414
+ else
1415
+ ## load and run
1416
+ load_files(filenames)
1417
+ Oktest::Config.auto_run = false
1418
+ n_errors = Oktest.run(:style=>opts.style)
1419
+ AssertionObject.report_not_yet()
1420
+ return n_errors
1421
+ end
1422
+ end
1423
+
1424
+ private
1425
+
1426
+ class Options #:nodoc:
1427
+ attr_accessor :help, :version, :style
1428
+ end
1429
+
1430
+ #def option_parser(opts)
1431
+ # require 'optparse'
1432
+ # parser = OptionParser.new
1433
+ # parser.on('-h', '--help') {|val| opts.help = val }
1434
+ # parser.on('-v', '--version') {|val| opts.version = val }
1435
+ # parser.on('-s STYLE') {|val|
1436
+ # REPORTER_CLASSES.key?(val) or
1437
+ # raise OptionParser::InvalidOption.new("-s #{val}: unknown style.")
1438
+ # opts.style = val
1439
+ # }
1440
+ # return parser
1441
+ #end
1442
+ def option_parser()
1443
+ require 'section9/cmdopt'
1444
+ cmdopt = Section9::Cmdopt.new
1445
+ cmdopt.option("-h, --help", "show help")
1446
+ cmdopt.option("-v, --version", "print version")
1447
+ cmdopt.option("-s STYLE #style", "report style (verbose/simple/plain, or v/s/p)")\
1448
+ .validation {|val| "unknown style." unless REPORTER_CLASSES.key?(val) }
1449
+ cmdopt.option("-g, --generate", "genearte test code from source file")
1450
+ return cmdopt
1451
+ end
1452
+
1453
+ def help_message(command=nil)
1454
+ command ||= File.basename($0)
1455
+ buf = "Usage: #{command} [options] [file or directory...]\n"
1456
+ buf << " -h, --help : show help\n"
1457
+ buf << " -v, --version : print version\n"
1458
+ buf << " -s STYLE : report style (verbose/simple/plain, or v/s/p)\n"
1459
+ return buf
1460
+ end
1461
+
1462
+ def load_files(filenames)
1463
+ ## file exists?
1464
+ filenames.each do |fname|
1465
+ File.exist?(fname) or
1466
+ raise OptionParser::InvalidOption.new("#{fname}: not found.")
1467
+ end
1468
+ ## load files
1469
+ filenames.each do |fname|
1470
+ File.directory?(fname) ? load_dir(fname) : load(fname)
1471
+ end
1472
+ end
1473
+
1474
+ def load_dir(dir, pattern=/^(test_.*|.*_test)\.rb$/)
1475
+ Dir.glob("#{dir}/*").each do |path|
1476
+ if File.directory?(path)
1477
+ load_dir(path)
1478
+ elsif File.file?(path)
1479
+ load(path) if File.basename(path) =~ pattern
1480
+ else
1481
+ raise ArgumentError.new("#{path}: not a file nor directory.")
1482
+ end
1483
+ end
1484
+ end
1485
+
1486
+ def generate(filenames)
1487
+ filenames.each do |fname|
1488
+ generator = TestGenerator.new
1489
+ File.open(fname) do |f|
1490
+ print generator.generate(f)
1491
+ end
1492
+ end
1493
+ end
1494
+
1495
+ end
1496
+
1497
+
1498
+ def self.main(args=nil)
1499
+ return MainApp.new.main(args)
1500
+ end
1501
+
1502
+
1503
+ end
1504
+
1505
+
1506
+ Test::Unit::Assertions.module_eval do
1507
+ def ok
1508
+ location = caller(1).first
1509
+ actual = yield
1510
+ ass = Oktest::AssertionObject.new(actual, true, location)
1511
+ Oktest::AssertionObject::NOT_YET[ass.__id__] = ass
1512
+ return ass
1513
+ end
1514
+ end
1515
+
1516
+
1517
+ at_exit do
1518
+ if Oktest::FILESCOPES.empty?
1519
+ if defined?(Test::Unit::Runner)
1520
+ Test::Unit::Runner.class_variable_set(:@@stop_auto_run, false)
1521
+ end
1522
+ else
1523
+ ex = $!
1524
+ if (! ex || ex.is_a?(SystemExit)) && Oktest::Config.auto_run
1525
+ Oktest.main()
1526
+ raise ex if ex
1527
+ end
1528
+ end
1529
+ end
1530
+
1531
+
1532
+ if __FILE__ == $0
1533
+ ## prevent to load oktest.rb twice
1534
+ $LOADED_FEATURES << File.expand_path(__FILE__)
1535
+ ## run test scripts
1536
+ Oktest.main()
1537
+ end