migr8 0.4.0

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.
@@ -0,0 +1,93 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+
4
+ class Dummy
5
+
6
+ def stdout
7
+ bkup = $stdout
8
+ $stdout = stdout = StringIO.new
9
+ begin
10
+ yield
11
+ ensure
12
+ $stdout = bkup
13
+ end
14
+ stdout.rewind
15
+ return stdout.read()
16
+ end
17
+
18
+ def stderr
19
+ bkup = $stderr
20
+ $stderr = stderr = StringIO.new
21
+ begin
22
+ yield
23
+ ensure
24
+ $stderr = bkup
25
+ end
26
+ stderr.rewind
27
+ return stderr.read()
28
+ end
29
+
30
+ def stdouterr
31
+ bkup = [$stdout, $stderr]
32
+ $stdout = stdout = StringIO.new
33
+ $stderr = stderr = StringIO.new
34
+ begin
35
+ yield
36
+ ensure
37
+ $stdout, $stderr = bkup
38
+ end
39
+ stdout.rewind
40
+ stderr.rewind
41
+ return [stdout.read(), stderr.read()]
42
+ end
43
+
44
+ end
45
+
46
+
47
+ Dummy.class_eval do
48
+
49
+ def self.partial_regexp(string, pattern=/\[==(.*)==\]/)
50
+ pat = '\A'
51
+ pos = 0
52
+ string.scan(pattern) do
53
+ m = Regexp.last_match
54
+ text = string[pos...m.begin(0)]
55
+ pos = m.end(0)
56
+ pat << Regexp.escape(text)
57
+ pat << m[1]
58
+ end
59
+ rest = pos == 0 ? string : string[pos..-1]
60
+ pat << Regexp.escape(rest)
61
+ pat << '\z'
62
+ $stderr.puts "\033[0;31m*** debug: pat=\n#{pat}\033[0m"
63
+ return Regexp.compile(pat)
64
+ end
65
+
66
+ end
67
+
68
+
69
+ if __FILE__ == $0
70
+
71
+ text = <<'END'
72
+ Usage: skeema.rb [global-options] [action [options] [...]]
73
+ -h, --help : show help
74
+ -v, --version : show version
75
+
76
+ Actions (default: [==(navi|status)==]):
77
+ navi : !!RUN THIS ACTION AT FIRST!!
78
+ help [action] : show help message of action, or list action names
79
+ init : create necessary files and a table
80
+ hist : list history of versions
81
+ new : create new migration file and open it by $SKEEMA_EDITOR
82
+ edit [version] : open migration file by $SKEEMA_EDITOR
83
+ status : show status
84
+ up : apply a next migration
85
+ down : unapply current migration
86
+ redo : do migration down, and up it again
87
+ apply version ... : apply specified migrations
88
+ unapply version ... : unapply specified migrations
89
+ END
90
+
91
+ Dummy.pattern_text(text)
92
+
93
+ end
@@ -0,0 +1,1537 @@
1
+ ###
2
+ ### $Release: $
3
+ ### $Copyright: copyright(c) 2011 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.0 $'.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