grntest 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,141 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require "thread"
19
+
20
+ require "grntest/reporters"
21
+ require "grntest/worker"
22
+ require "grntest/base-result"
23
+
24
+ module Grntest
25
+ class TestSuitesResult < BaseResult
26
+ attr_accessor :workers
27
+ attr_accessor :n_total_tests
28
+ def initialize
29
+ super
30
+ @workers = []
31
+ @n_total_tests = 0
32
+ end
33
+
34
+ def pass_ratio
35
+ n_target_tests = n_tests - n_not_checked_tests
36
+ if n_target_tests.zero?
37
+ 0
38
+ else
39
+ (n_passed_tests / n_target_tests.to_f) * 100
40
+ end
41
+ end
42
+
43
+ def n_tests
44
+ collect_count(:n_tests)
45
+ end
46
+
47
+ def n_passed_tests
48
+ collect_count(:n_passed_tests)
49
+ end
50
+
51
+ def n_failed_tests
52
+ collect_count(:n_failed_tests)
53
+ end
54
+
55
+ def n_leaked_tests
56
+ collect_count(:n_leaked_tests)
57
+ end
58
+
59
+ def n_omitted_tests
60
+ collect_count(:n_omitted_tests)
61
+ end
62
+
63
+ def n_not_checked_tests
64
+ collect_count(:n_not_checked_tests)
65
+ end
66
+
67
+ private
68
+ def collect_count(item)
69
+ counts = @workers.collect do |worker|
70
+ worker.result.send(item)
71
+ end
72
+ counts.inject(&:+)
73
+ end
74
+ end
75
+
76
+ class TestSuitesRunner
77
+ def initialize(tester)
78
+ @tester = tester
79
+ @reporter = create_reporter
80
+ @result = TestSuitesResult.new
81
+ end
82
+
83
+ def run(test_suites)
84
+ succeeded = true
85
+
86
+ @result.measure do
87
+ succeeded = run_test_suites(test_suites)
88
+ end
89
+ @reporter.on_finish(@result)
90
+
91
+ succeeded
92
+ end
93
+
94
+ private
95
+ def run_test_suites(test_suites)
96
+ queue = Queue.new
97
+ test_suites.each do |suite_name, test_script_paths|
98
+ next unless @tester.target_test_suite?(suite_name)
99
+ test_script_paths.each do |test_script_path|
100
+ test_name = test_script_path.basename(".*").to_s
101
+ next unless @tester.target_test?(test_name)
102
+ queue << [suite_name, test_script_path, test_name]
103
+ @result.n_total_tests += 1
104
+ end
105
+ end
106
+ @tester.n_workers.times do
107
+ queue << nil
108
+ end
109
+
110
+ workers = []
111
+ @tester.n_workers.times do |i|
112
+ workers << Worker.new(i, @tester, @result, @reporter)
113
+ end
114
+ @result.workers = workers
115
+ @reporter.on_start(@result)
116
+
117
+ succeeded = true
118
+ worker_threads = []
119
+ @tester.n_workers.times do |i|
120
+ worker = workers[i]
121
+ worker_threads << Thread.new do
122
+ succeeded = false unless worker.run(queue)
123
+ end
124
+ end
125
+
126
+ begin
127
+ worker_threads.each(&:join)
128
+ rescue Interrupt
129
+ workers.each do |worker|
130
+ worker.interrupt
131
+ end
132
+ end
133
+
134
+ succeeded
135
+ end
136
+
137
+ def create_reporter
138
+ Reporters.create_repoter(@tester)
139
+ end
140
+ end
141
+ end
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2012 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This program is free software: you can redistribute it and/or modify
6
6
  # it under the terms of the GNU General Public License as published by
@@ -15,44 +15,14 @@
15
15
  # You should have received a copy of the GNU General Public License
16
16
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
17
 
18
- require "English"
19
18
  require "optparse"
20
19
  require "pathname"
21
- require "fileutils"
22
- require "tempfile"
23
- require "shellwords"
24
- require "open-uri"
25
-
26
- require "json"
27
- require "msgpack"
28
-
29
- require "groonga/command"
30
20
 
31
21
  require "grntest/version"
22
+ require "grntest/test-suites-runner"
32
23
 
33
24
  module Grntest
34
25
  class Tester
35
- class Error < StandardError
36
- end
37
-
38
- class NotExist < Error
39
- attr_reader :path
40
- def initialize(path)
41
- @path = path
42
- super("<#{path}> doesn't exist.")
43
- end
44
- end
45
-
46
- class ParseError < Error
47
- attr_reader :type, :content, :reason
48
- def initialize(type, content, reason)
49
- @type = type
50
- @content = content
51
- @reason = reason
52
- super("failed to parse <#{@type}> content: #{reason}: <#{content}>")
53
- end
54
- end
55
-
56
26
  class << self
57
27
  def run(argv=nil)
58
28
  argv ||= ARGV.dup
@@ -391,1948 +361,5 @@ module Grntest
391
361
  false
392
362
  end
393
363
  end
394
-
395
- class Result
396
- attr_accessor :elapsed_time
397
- def initialize
398
- @elapsed_time = 0
399
- end
400
-
401
- def measure
402
- start_time = Time.now
403
- yield
404
- ensure
405
- @elapsed_time = Time.now - start_time
406
- end
407
- end
408
-
409
- class WorkerResult < Result
410
- attr_reader :n_tests, :n_passed_tests, :n_leaked_tests
411
- attr_reader :n_omitted_tests, :n_not_checked_tests
412
- attr_reader :failed_tests
413
- def initialize
414
- super
415
- @n_tests = 0
416
- @n_passed_tests = 0
417
- @n_leaked_tests = 0
418
- @n_omitted_tests = 0
419
- @n_not_checked_tests = 0
420
- @failed_tests = []
421
- end
422
-
423
- def n_failed_tests
424
- @failed_tests.size
425
- end
426
-
427
- def on_test_finish
428
- @n_tests += 1
429
- end
430
-
431
- def on_test_success
432
- @n_passed_tests += 1
433
- end
434
-
435
- def on_test_failure(name)
436
- @failed_tests << name
437
- end
438
-
439
- def on_test_leak(name)
440
- @n_leaked_tests += 1
441
- end
442
-
443
- def on_test_omission
444
- @n_omitted_tests += 1
445
- end
446
-
447
- def on_test_no_check
448
- @n_not_checked_tests += 1
449
- end
450
- end
451
-
452
- class Worker
453
- attr_reader :id, :tester, :test_suites_rusult, :reporter
454
- attr_reader :suite_name, :test_script_path, :test_name, :status, :result
455
- def initialize(id, tester, test_suites_result, reporter)
456
- @id = id
457
- @tester = tester
458
- @test_suites_result = test_suites_result
459
- @reporter = reporter
460
- @suite_name = nil
461
- @test_script_path = nil
462
- @test_name = nil
463
- @interruptted = false
464
- @status = "not running"
465
- @result = WorkerResult.new
466
- end
467
-
468
- def interrupt
469
- @interruptted = true
470
- end
471
-
472
- def interruptted?
473
- @interruptted
474
- end
475
-
476
- def run(queue)
477
- succeeded = true
478
-
479
- @result.measure do
480
- @reporter.on_worker_start(self)
481
- catch do |tag|
482
- loop do
483
- suite_name, test_script_path, test_name = queue.pop
484
- break if test_script_path.nil?
485
-
486
- unless @suite_name == suite_name
487
- @reporter.on_suite_finish(self) if @suite_name
488
- @suite_name = suite_name
489
- @reporter.on_suite_start(self)
490
- end
491
- @test_script_path = test_script_path
492
- @test_name = test_name
493
- runner = TestRunner.new(@tester, self)
494
- succeeded = false unless runner.run
495
-
496
- break if interruptted?
497
- end
498
- @status = "finished"
499
- @reporter.on_suite_finish(@suite_name) if @suite_name
500
- @suite_name = nil
501
- end
502
- end
503
- @reporter.on_worker_finish(self)
504
-
505
- succeeded
506
- end
507
-
508
- def on_test_start
509
- @status = "running"
510
- @test_result = nil
511
- @reporter.on_test_start(self)
512
- end
513
-
514
- def on_test_success(result)
515
- @status = "passed"
516
- @result.on_test_success
517
- @reporter.on_test_success(self, result)
518
- end
519
-
520
- def on_test_failure(result)
521
- @status = "failed"
522
- @result.on_test_failure(test_name)
523
- @reporter.on_test_failure(self, result)
524
- end
525
-
526
- def on_test_leak(result)
527
- @status = "leaked(#{result.n_leaked_objects})"
528
- @result.on_test_leak(test_name)
529
- @reporter.on_test_leak(self, result)
530
- end
531
-
532
- def on_test_omission(result)
533
- @status = "omitted"
534
- @result.on_test_omission
535
- @reporter.on_test_omission(self, result)
536
- end
537
-
538
- def on_test_no_check(result)
539
- @status = "not checked"
540
- @result.on_test_no_check
541
- @reporter.on_test_no_check(self, result)
542
- end
543
-
544
- def on_test_finish(result)
545
- @result.on_test_finish
546
- @reporter.on_test_finish(self, result)
547
- @test_script_path = nil
548
- @test_name = nil
549
- end
550
- end
551
-
552
- class TestSuitesResult < Result
553
- attr_accessor :workers
554
- attr_accessor :n_total_tests
555
- def initialize
556
- super
557
- @workers = []
558
- @n_total_tests = 0
559
- end
560
-
561
- def pass_ratio
562
- n_target_tests = n_tests - n_not_checked_tests
563
- if n_target_tests.zero?
564
- 0
565
- else
566
- (n_passed_tests / n_target_tests.to_f) * 100
567
- end
568
- end
569
-
570
- def n_tests
571
- collect_count(:n_tests)
572
- end
573
-
574
- def n_passed_tests
575
- collect_count(:n_passed_tests)
576
- end
577
-
578
- def n_failed_tests
579
- collect_count(:n_failed_tests)
580
- end
581
-
582
- def n_leaked_tests
583
- collect_count(:n_leaked_tests)
584
- end
585
-
586
- def n_omitted_tests
587
- collect_count(:n_omitted_tests)
588
- end
589
-
590
- def n_not_checked_tests
591
- collect_count(:n_not_checked_tests)
592
- end
593
-
594
- private
595
- def collect_count(item)
596
- counts = @workers.collect do |worker|
597
- worker.result.send(item)
598
- end
599
- counts.inject(&:+)
600
- end
601
- end
602
-
603
- class TestSuitesRunner
604
- def initialize(tester)
605
- @tester = tester
606
- @reporter = create_reporter
607
- @result = TestSuitesResult.new
608
- end
609
-
610
- def run(test_suites)
611
- succeeded = true
612
-
613
- @result.measure do
614
- succeeded = run_test_suites(test_suites)
615
- end
616
- @reporter.on_finish(@result)
617
-
618
- succeeded
619
- end
620
-
621
- private
622
- def run_test_suites(test_suites)
623
- queue = Queue.new
624
- test_suites.each do |suite_name, test_script_paths|
625
- next unless @tester.target_test_suite?(suite_name)
626
- test_script_paths.each do |test_script_path|
627
- test_name = test_script_path.basename(".*").to_s
628
- next unless @tester.target_test?(test_name)
629
- queue << [suite_name, test_script_path, test_name]
630
- @result.n_total_tests += 1
631
- end
632
- end
633
- @tester.n_workers.times do
634
- queue << nil
635
- end
636
-
637
- workers = []
638
- @tester.n_workers.times do |i|
639
- workers << Worker.new(i, @tester, @result, @reporter)
640
- end
641
- @result.workers = workers
642
- @reporter.on_start(@result)
643
-
644
- succeeded = true
645
- worker_threads = []
646
- @tester.n_workers.times do |i|
647
- worker = workers[i]
648
- worker_threads << Thread.new do
649
- succeeded = false unless worker.run(queue)
650
- end
651
- end
652
-
653
- begin
654
- worker_threads.each(&:join)
655
- rescue Interrupt
656
- workers.each do |worker|
657
- worker.interrupt
658
- end
659
- end
660
-
661
- succeeded
662
- end
663
-
664
- def create_reporter
665
- case @tester.reporter
666
- when :mark
667
- MarkReporter.new(@tester)
668
- when :stream
669
- StreamReporter.new(@tester)
670
- when :inplace
671
- InplaceReporter.new(@tester)
672
- end
673
- end
674
- end
675
-
676
- class TestResult < Result
677
- attr_accessor :worker_id, :test_name
678
- attr_accessor :expected, :actual, :n_leaked_objects
679
- attr_writer :omitted
680
- def initialize(worker)
681
- super()
682
- @worker_id = worker.id
683
- @test_name = worker.test_name
684
- @actual = nil
685
- @expected = nil
686
- @n_leaked_objects = 0
687
- @omitted = false
688
- end
689
-
690
- def status
691
- return :omitted if omitted?
692
-
693
- if @expected
694
- if @actual == @expected
695
- if @n_leaked_objects.zero?
696
- :success
697
- else
698
- :leaked
699
- end
700
- else
701
- :failure
702
- end
703
- else
704
- if @n_leaked_objects.zero?
705
- :not_checked
706
- else
707
- :leaked
708
- end
709
- end
710
- end
711
-
712
- def omitted?
713
- @omitted
714
- end
715
- end
716
-
717
- class ResponseParser
718
- class << self
719
- def parse(content, type)
720
- parser = new(type)
721
- parser.parse(content)
722
- end
723
- end
724
-
725
- def initialize(type)
726
- @type = type
727
- end
728
-
729
- def parse(content)
730
- case @type
731
- when "json", "msgpack"
732
- parse_result(content.chomp)
733
- else
734
- content
735
- end
736
- end
737
-
738
- def parse_result(result)
739
- case @type
740
- when "json"
741
- begin
742
- JSON.parse(result)
743
- rescue JSON::ParserError
744
- raise ParseError.new(@type, result, $!.message)
745
- end
746
- when "msgpack"
747
- begin
748
- MessagePack.unpack(result.chomp)
749
- rescue MessagePack::UnpackError, NoMemoryError
750
- raise ParseError.new(@type, result, $!.message)
751
- end
752
- else
753
- raise ParseError.new(@type, result, "unknown type")
754
- end
755
- end
756
- end
757
-
758
- class TestRunner
759
- MAX_N_COLUMNS = 79
760
-
761
- def initialize(tester, worker)
762
- @tester = tester
763
- @worker = worker
764
- @max_n_columns = MAX_N_COLUMNS
765
- @id = nil
766
- end
767
-
768
- def run
769
- succeeded = true
770
-
771
- @worker.on_test_start
772
- result = TestResult.new(@worker)
773
- result.measure do
774
- execute_groonga_script(result)
775
- end
776
- normalize_actual_result(result)
777
- result.expected = read_expected_result
778
- case result.status
779
- when :success
780
- @worker.on_test_success(result)
781
- remove_reject_file
782
- when :failure
783
- @worker.on_test_failure(result)
784
- output_reject_file(result.actual)
785
- succeeded = false
786
- when :leaked
787
- @worker.on_test_leak(result)
788
- succeeded = false
789
- when :omitted
790
- @worker.on_test_omission(result)
791
- else
792
- @worker.on_test_no_check(result)
793
- output_actual_file(result.actual)
794
- end
795
- @worker.on_test_finish(result)
796
-
797
- succeeded
798
- end
799
-
800
- private
801
- def execute_groonga_script(result)
802
- create_temporary_directory do |directory_path|
803
- if @tester.database_path
804
- db_path = Pathname(@tester.database_path).expand_path
805
- else
806
- db_dir = directory_path + "db"
807
- FileUtils.mkdir_p(db_dir.to_s)
808
- db_path = db_dir + "db"
809
- end
810
- context = Executor::Context.new
811
- context.temporary_directory_path = directory_path
812
- context.db_path = db_path
813
- context.base_directory = @tester.base_directory.expand_path
814
- context.groonga_suggest_create_dataset =
815
- @tester.groonga_suggest_create_dataset
816
- context.output_type = @tester.output_type
817
- run_groonga(context) do |executor|
818
- executor.execute(test_script_path)
819
- end
820
- check_memory_leak(context)
821
- result.omitted = context.omitted?
822
- result.actual = context.result
823
- end
824
- end
825
-
826
- def create_temporary_directory
827
- path = "tmp/grntest"
828
- path << ".#{@worker.id}" if @tester.n_workers > 1
829
- FileUtils.rm_rf(path, :secure => true)
830
- FileUtils.mkdir_p(path)
831
- begin
832
- yield(Pathname(path).expand_path)
833
- ensure
834
- if @tester.keep_database? and File.exist?(path)
835
- FileUtils.rm_rf(keep_database_path, :secure => true)
836
- FileUtils.mv(path, keep_database_path)
837
- else
838
- FileUtils.rm_rf(path, :secure => true)
839
- end
840
- end
841
- end
842
-
843
- def keep_database_path
844
- test_script_path.to_s.gsub(/\//, ".")
845
- end
846
-
847
- def run_groonga(context, &block)
848
- unless @tester.database_path
849
- create_empty_database(context.db_path.to_s)
850
- end
851
-
852
- catch do |tag|
853
- context.abort_tag = tag
854
- case @tester.interface
855
- when :stdio
856
- run_groonga_stdio(context, &block)
857
- when :http
858
- run_groonga_http(context, &block)
859
- end
860
- end
861
- end
862
-
863
- def run_groonga_stdio(context)
864
- pid = nil
865
- begin
866
- open_pipe do |input_read, input_write, output_read, output_write|
867
- groonga_input = input_write
868
- groonga_output = output_read
869
-
870
- input_fd = input_read.to_i
871
- output_fd = output_write.to_i
872
- env = {}
873
- spawn_options = {
874
- input_fd => input_fd,
875
- output_fd => output_fd
876
- }
877
- command_line = groonga_command_line(context, spawn_options)
878
- command_line += [
879
- "--input-fd", input_fd.to_s,
880
- "--output-fd", output_fd.to_s,
881
- context.relative_db_path.to_s,
882
- ]
883
- pid = Process.spawn(env, *command_line, spawn_options)
884
- executor = StandardIOExecutor.new(groonga_input,
885
- groonga_output,
886
- context)
887
- executor.ensure_groonga_ready
888
- yield(executor)
889
- end
890
- ensure
891
- Process.waitpid(pid) if pid
892
- end
893
- end
894
-
895
- def open_pipe
896
- IO.pipe("ASCII-8BIT") do |input_read, input_write|
897
- IO.pipe("ASCII-8BIT") do |output_read, output_write|
898
- yield(input_read, input_write, output_read, output_write)
899
- end
900
- end
901
- end
902
-
903
- def command_command_line(command, context, spawn_options)
904
- command_line = []
905
- if @tester.gdb
906
- if libtool_wrapper?(command)
907
- command_line << find_libtool(command)
908
- command_line << "--mode=execute"
909
- end
910
- command_line << @tester.gdb
911
- gdb_command_path = context.temporary_directory_path + "groonga.gdb"
912
- File.open(gdb_command_path, "w") do |gdb_command|
913
- gdb_command.puts(<<-EOC)
914
- break main
915
- run
916
- print chdir("#{context.temporary_directory_path}")
917
- EOC
918
- end
919
- command_line << "--command=#{gdb_command_path}"
920
- command_line << "--quiet"
921
- command_line << "--args"
922
- else
923
- spawn_options[:chdir] = context.temporary_directory_path.to_s
924
- end
925
- command_line << command
926
- command_line
927
- end
928
-
929
- def groonga_command_line(context, spawn_options)
930
- command_line = command_command_line(@tester.groonga, context,
931
- spawn_options)
932
- command_line << "--log-path=#{context.log_path}"
933
- command_line << "--working-directory=#{context.temporary_directory_path}"
934
- command_line
935
- end
936
-
937
- def libtool_wrapper?(command)
938
- return false unless File.exist?(command)
939
- File.open(command, "r") do |command_file|
940
- first_line = command_file.gets
941
- first_line.start_with?("#!")
942
- end
943
- end
944
-
945
- def find_libtool(command)
946
- command_path = Pathname.new(command)
947
- directory = command_path.dirname
948
- until directory.root?
949
- libtool = directory + "libtool"
950
- return libtool.to_s if libtool.executable?
951
- directory = directory.parent
952
- end
953
- "libtool"
954
- end
955
-
956
- def run_groonga_http(context)
957
- host = "127.0.0.1"
958
- port = 50041 + @worker.id
959
- pid_file_path = context.temporary_directory_path + "groonga.pid"
960
-
961
- env = {}
962
- spawn_options = {}
963
- command_line = groonga_http_command(host, port, pid_file_path, context,
964
- spawn_options)
965
- pid = nil
966
- begin
967
- pid = Process.spawn(env, *command_line, spawn_options)
968
- begin
969
- executor = HTTPExecutor.new(host, port, context)
970
- begin
971
- executor.ensure_groonga_ready
972
- rescue
973
- if Process.waitpid(pid, Process::WNOHANG)
974
- pid = nil
975
- raise
976
- end
977
- raise unless @tester.gdb
978
- retry
979
- end
980
- yield(executor)
981
- ensure
982
- executor.send_command("shutdown")
983
- wait_groonga_http_shutdown(pid_file_path)
984
- end
985
- ensure
986
- Process.waitpid(pid) if pid
987
- end
988
- end
989
-
990
- def wait_groonga_http_shutdown(pid_file_path)
991
- total_sleep_time = 0
992
- sleep_time = 0.1
993
- while pid_file_path.exist?
994
- sleep(sleep_time)
995
- total_sleep_time += sleep_time
996
- break if total_sleep_time > 1.0
997
- end
998
- end
999
-
1000
- def groonga_http_command(host, port, pid_file_path, context, spawn_options)
1001
- case @tester.testee
1002
- when "groonga"
1003
- command_line = groonga_command_line(context, spawn_options)
1004
- command_line += [
1005
- "--pid-path", pid_file_path.to_s,
1006
- "--bind-address", host,
1007
- "--port", port.to_s,
1008
- "--protocol", "http",
1009
- "-s",
1010
- context.relative_db_path.to_s,
1011
- ]
1012
- when "groonga-httpd"
1013
- command_line = command_command_line(@tester.groonga_httpd, context,
1014
- spawn_options)
1015
- config_file_path = create_config_file(context, host, port,
1016
- pid_file_path)
1017
- command_line += [
1018
- "-c", config_file_path.to_s,
1019
- "-p", "#{context.temporary_directory_path}/",
1020
- ]
1021
- end
1022
- command_line
1023
- end
1024
-
1025
- def create_config_file(context, host, port, pid_file_path)
1026
- config_file_path =
1027
- context.temporary_directory_path + "groonga-httpd.conf"
1028
- config_file_path.open("w") do |config_file|
1029
- config_file.puts(<<EOF)
1030
- daemon off;
1031
- master_process off;
1032
- worker_processes 1;
1033
- working_directory #{context.temporary_directory_path};
1034
- error_log groonga-httpd-access.log;
1035
- pid #{pid_file_path};
1036
- events {
1037
- worker_connections 1024;
1038
- }
1039
-
1040
- http {
1041
- server {
1042
- access_log groonga-httpd-access.log;
1043
- listen #{port};
1044
- server_name #{host};
1045
- location /d/ {
1046
- groonga_database #{context.relative_db_path};
1047
- groonga on;
1048
- }
1049
- }
1050
- }
1051
- EOF
1052
- end
1053
- config_file_path
1054
- end
1055
-
1056
- def create_empty_database(db_path)
1057
- output_fd = Tempfile.new("create-empty-database")
1058
- create_database_command = [
1059
- @tester.groonga,
1060
- "--output-fd", output_fd.to_i.to_s,
1061
- "-n", db_path,
1062
- "shutdown"
1063
- ]
1064
- system(*create_database_command)
1065
- output_fd.close(true)
1066
- end
1067
-
1068
- def normalize_actual_result(result)
1069
- normalized_result = ""
1070
- result.actual.each do |tag, content, options|
1071
- case tag
1072
- when :input
1073
- normalized_result << content
1074
- when :output
1075
- normalized_result << normalize_output(content, options)
1076
- when :error
1077
- normalized_result << normalize_raw_content(content)
1078
- when :n_leaked_objects
1079
- result.n_leaked_objects = content
1080
- end
1081
- end
1082
- result.actual = normalized_result
1083
- end
1084
-
1085
- def normalize_raw_content(content)
1086
- "#{content}\n".force_encoding("ASCII-8BIT")
1087
- end
1088
-
1089
- def normalize_output(content, options)
1090
- type = options[:type]
1091
- case type
1092
- when "json", "msgpack"
1093
- status = nil
1094
- values = nil
1095
- begin
1096
- status, *values = ResponseParser.parse(content.chomp, type)
1097
- rescue ParseError
1098
- return $!.message
1099
- end
1100
- normalized_status = normalize_status(status)
1101
- normalized_output_content = [normalized_status, *values]
1102
- normalized_output = JSON.generate(normalized_output_content)
1103
- if normalized_output.bytesize > @max_n_columns
1104
- normalized_output = JSON.pretty_generate(normalized_output_content)
1105
- end
1106
- normalize_raw_content(normalized_output)
1107
- else
1108
- normalize_raw_content(content)
1109
- end
1110
- end
1111
-
1112
- def normalize_status(status)
1113
- return_code, started_time, elapsed_time, *rest = status
1114
- _ = started_time = elapsed_time # for suppress warnings
1115
- if return_code.zero?
1116
- [0, 0.0, 0.0]
1117
- else
1118
- message, backtrace = rest
1119
- _ = backtrace # for suppress warnings
1120
- [[return_code, 0.0, 0.0], message]
1121
- end
1122
- end
1123
-
1124
- def test_script_path
1125
- @worker.test_script_path
1126
- end
1127
-
1128
- def have_extension?
1129
- not test_script_path.extname.empty?
1130
- end
1131
-
1132
- def related_file_path(extension)
1133
- path = Pathname(test_script_path.to_s.gsub(/\.[^.]+\z/, ".#{extension}"))
1134
- return nil if test_script_path == path
1135
- path
1136
- end
1137
-
1138
- def read_expected_result
1139
- return nil unless have_extension?
1140
- result_path = related_file_path("expected")
1141
- return nil if result_path.nil?
1142
- return nil unless result_path.exist?
1143
- result_path.open("r:ascii-8bit") do |result_file|
1144
- result_file.read
1145
- end
1146
- end
1147
-
1148
- def remove_reject_file
1149
- return unless have_extension?
1150
- reject_path = related_file_path("reject")
1151
- return if reject_path.nil?
1152
- FileUtils.rm_rf(reject_path.to_s, :secure => true)
1153
- end
1154
-
1155
- def output_reject_file(actual_result)
1156
- output_actual_result(actual_result, "reject")
1157
- end
1158
-
1159
- def output_actual_file(actual_result)
1160
- output_actual_result(actual_result, "actual")
1161
- end
1162
-
1163
- def output_actual_result(actual_result, suffix)
1164
- result_path = related_file_path(suffix)
1165
- return if result_path.nil?
1166
- result_path.open("w:ascii-8bit") do |result_file|
1167
- result_file.print(actual_result)
1168
- end
1169
- end
1170
-
1171
- def check_memory_leak(context)
1172
- context.log.each_line do |line|
1173
- timestamp, log_level, message = line.split(/\|\s*/, 3)
1174
- _ = timestamp # suppress warning
1175
- next unless /^grn_fin \((\d+)\)$/ =~ message
1176
- n_leaked_objects = $1.to_i
1177
- next if n_leaked_objects.zero?
1178
- context.result << [:n_leaked_objects, n_leaked_objects, {}]
1179
- end
1180
- end
1181
- end
1182
-
1183
- class Executor
1184
- class Context
1185
- attr_writer :logging
1186
- attr_accessor :base_directory, :temporary_directory_path, :db_path
1187
- attr_accessor :groonga_suggest_create_dataset
1188
- attr_accessor :result
1189
- attr_accessor :output_type
1190
- attr_accessor :on_error
1191
- attr_accessor :abort_tag
1192
- def initialize
1193
- @logging = true
1194
- @base_directory = Pathname(".")
1195
- @temporary_directory_path = Pathname("tmp")
1196
- @db_path = Pathname("db")
1197
- @groonga_suggest_create_dataset = "groonga-suggest-create-dataset"
1198
- @n_nested = 0
1199
- @result = []
1200
- @output_type = "json"
1201
- @log = nil
1202
- @on_error = :default
1203
- @abort_tag = nil
1204
- @omitted = false
1205
- end
1206
-
1207
- def logging?
1208
- @logging
1209
- end
1210
-
1211
- def execute
1212
- @n_nested += 1
1213
- yield
1214
- ensure
1215
- @n_nested -= 1
1216
- end
1217
-
1218
- def top_level?
1219
- @n_nested == 1
1220
- end
1221
-
1222
- def log_path
1223
- @temporary_directory_path + "groonga.log"
1224
- end
1225
-
1226
- def log
1227
- @log ||= File.open(log_path.to_s, "a+")
1228
- end
1229
-
1230
- def relative_db_path
1231
- @db_path.relative_path_from(@temporary_directory_path)
1232
- end
1233
-
1234
- def omitted?
1235
- @omitted
1236
- end
1237
-
1238
- def error
1239
- case @on_error
1240
- when :omit
1241
- omit
1242
- end
1243
- end
1244
-
1245
- def omit
1246
- @omitted = true
1247
- abort
1248
- end
1249
-
1250
- def abort
1251
- throw @abort_tag
1252
- end
1253
- end
1254
-
1255
- module ReturnCode
1256
- SUCCESS = 0
1257
- end
1258
-
1259
- attr_reader :context
1260
- def initialize(context=nil)
1261
- @loading = false
1262
- @pending_command = ""
1263
- @pending_load_command = nil
1264
- @current_command_name = nil
1265
- @output_type = nil
1266
- @long_timeout = default_long_timeout
1267
- @context = context || Context.new
1268
- end
1269
-
1270
- def execute(script_path)
1271
- unless script_path.exist?
1272
- raise NotExist.new(script_path)
1273
- end
1274
-
1275
- @context.execute do
1276
- script_path.open("r:ascii-8bit") do |script_file|
1277
- parser = create_parser
1278
- script_file.each_line do |line|
1279
- begin
1280
- parser << line
1281
- rescue Error, Groonga::Command::ParseError
1282
- line_info = "#{script_path}:#{script_file.lineno}:#{line.chomp}"
1283
- log_error("#{line_info}: #{$!.message}")
1284
- if $!.is_a?(Groonga::Command::ParseError)
1285
- @context.abort
1286
- else
1287
- log_error("#{line_info}: #{$!.message}")
1288
- raise unless @context.top_level?
1289
- end
1290
- end
1291
- end
1292
- end
1293
- end
1294
-
1295
- @context.result
1296
- end
1297
-
1298
- private
1299
- def create_parser
1300
- parser = Groonga::Command::Parser.new
1301
- parser.on_command do |command|
1302
- execute_command(command)
1303
- end
1304
- parser.on_load_complete do |command|
1305
- execute_command(command)
1306
- end
1307
- parser.on_comment do |comment|
1308
- if /\A@/ =~ comment
1309
- directive_content = $POSTMATCH
1310
- execute_directive("\##{comment}", directive_content)
1311
- end
1312
- end
1313
- parser
1314
- end
1315
-
1316
- def resolve_path(path)
1317
- if path.relative?
1318
- @context.base_directory + path
1319
- else
1320
- path
1321
- end
1322
- end
1323
-
1324
- def execute_directive_suggest_create_dataset(line, content, options)
1325
- dataset_name = options.first
1326
- if dataset_name.nil?
1327
- log_input(line)
1328
- log_error("#|e| [suggest-create-dataset] dataset name is missing")
1329
- return
1330
- end
1331
- execute_suggest_create_dataset(dataset_name)
1332
- end
1333
-
1334
- def execute_directive_include(line, content, options)
1335
- path = options.first
1336
- if path.nil?
1337
- log_input(line)
1338
- log_error("#|e| [include] path is missing")
1339
- return
1340
- end
1341
- execute_script(Pathname(path))
1342
- end
1343
-
1344
- def execute_directive_copy_path(line, content, options)
1345
- source, destination, = options
1346
- if source.nil? or destination.nil?
1347
- log_input(line)
1348
- if source.nil?
1349
- log_error("#|e| [copy-path] source is missing")
1350
- end
1351
- if destiantion.nil?
1352
- log_error("#|e| [copy-path] destination is missing")
1353
- end
1354
- return
1355
- end
1356
- source = resolve_path(Pathname(source))
1357
- destination = resolve_path(Pathname(destination))
1358
- FileUtils.cp_r(source.to_s, destination.to_s)
1359
- end
1360
-
1361
- def execute_directive_long_timeout(line, content, options)
1362
- long_timeout, = options
1363
- invalid_value_p = false
1364
- case long_timeout
1365
- when "default"
1366
- @long_timeout = default_long_timeout
1367
- when nil
1368
- invalid_value_p = true
1369
- else
1370
- begin
1371
- @long_timeout = Float(long_timeout)
1372
- rescue ArgumentError
1373
- invalid_value_p = true
1374
- end
1375
- end
1376
-
1377
- if invalid_value_p
1378
- log_input(line)
1379
- message = "long-timeout must be number or 'default': <#{long_timeout}>"
1380
- log_error("#|e| [long-timeout] #{message}")
1381
- end
1382
- end
1383
-
1384
- def execute_directive_on_error(line, content, options)
1385
- action, = options
1386
- invalid_value_p = false
1387
- valid_actions = ["default", "omit"]
1388
- if valid_actions.include?(action)
1389
- @context.on_error = action.to_sym
1390
- else
1391
- invalid_value_p = true
1392
- end
1393
-
1394
- if invalid_value_p
1395
- log_input(line)
1396
- valid_actions_label = "[#{valid_actions.join(', ')}]"
1397
- message = "on-error must be one of #{valid_actions_label}"
1398
- log_error("#|e| [on-error] #{message}: <#{action}>")
1399
- end
1400
- end
1401
-
1402
- def execute_directive_omit(line, content, options)
1403
- reason, = options
1404
- @output_type = "raw"
1405
- log_output("omit: #{reason}")
1406
- @context.omit
1407
- end
1408
-
1409
- def execute_directive(line, content)
1410
- command, *options = Shellwords.split(content)
1411
- case command
1412
- when "disable-logging"
1413
- @context.logging = false
1414
- when "enable-logging"
1415
- @context.logging = true
1416
- when "suggest-create-dataset"
1417
- execute_directive_suggest_create_dataset(line, content, options)
1418
- when "include"
1419
- execute_directive_include(line, content, options)
1420
- when "copy-path"
1421
- execute_directive_copy_path(line, content, options)
1422
- when "long-timeout"
1423
- execute_directive_long_timeout(line, content, options)
1424
- when "on-error"
1425
- execute_directive_on_error(line, content, options)
1426
- when "omit"
1427
- execute_directive_omit(line, content, options)
1428
- else
1429
- log_input(line)
1430
- log_error("#|e| unknown directive: <#{command}>")
1431
- end
1432
- end
1433
-
1434
- def execute_suggest_create_dataset(dataset_name)
1435
- command_line = [@context.groonga_suggest_create_dataset,
1436
- @context.db_path.to_s,
1437
- dataset_name]
1438
- packed_command_line = command_line.join(" ")
1439
- log_input("#{packed_command_line}\n")
1440
- begin
1441
- IO.popen(command_line, "r:ascii-8bit") do |io|
1442
- log_output(io.read)
1443
- end
1444
- rescue SystemCallError
1445
- raise Error.new("failed to run groonga-suggest-create-dataset: " +
1446
- "<#{packed_command_line}>: #{$!}")
1447
- end
1448
- end
1449
-
1450
- def execute_script(script_path)
1451
- executor = create_sub_executor(@context)
1452
- executor.execute(resolve_path(script_path))
1453
- end
1454
-
1455
- def extract_command_info(command)
1456
- @current_command = command
1457
- if @current_command.name == "dump"
1458
- @output_type = "groonga-command"
1459
- else
1460
- @output_type = @current_command[:output_type] || @context.output_type
1461
- end
1462
- end
1463
-
1464
- def execute_command(command)
1465
- extract_command_info(command)
1466
- log_input("#{command.original_source}\n")
1467
- response = send_command(command)
1468
- type = @output_type
1469
- log_output(response)
1470
- log_error(read_error_log)
1471
-
1472
- @context.error if error_response?(response, type)
1473
- end
1474
-
1475
- def read_error_log
1476
- log = read_all_readable_content(context.log, :first_timeout => 0)
1477
- normalized_error_log = ""
1478
- log.each_line do |line|
1479
- timestamp, log_level, message = line.split(/\|\s*/, 3)
1480
- _ = timestamp # suppress warning
1481
- next unless error_log_level?(log_level)
1482
- next if backtrace_log_message?(message)
1483
- normalized_error_log << "\#|#{log_level}| #{message}"
1484
- end
1485
- normalized_error_log.chomp
1486
- end
1487
-
1488
- def read_all_readable_content(output, options={})
1489
- content = ""
1490
- first_timeout = options[:first_timeout] || 1
1491
- timeout = first_timeout
1492
- while IO.select([output], [], [], timeout)
1493
- break if output.eof?
1494
- request_bytes = 1024
1495
- read_content = output.readpartial(request_bytes)
1496
- content << read_content
1497
- timeout = 0 if read_content.bytesize < request_bytes
1498
- end
1499
- content
1500
- end
1501
-
1502
- def error_log_level?(log_level)
1503
- ["E", "A", "C", "e"].include?(log_level)
1504
- end
1505
-
1506
- def backtrace_log_message?(message)
1507
- message.start_with?("/")
1508
- end
1509
-
1510
- def error_response?(response, type)
1511
- status = nil
1512
- begin
1513
- status, = ResponseParser.parse(response, type)
1514
- rescue ParseError
1515
- return false
1516
- end
1517
-
1518
- return_code, = status
1519
- return_code != ReturnCode::SUCCESS
1520
- end
1521
-
1522
- def log(tag, content, options={})
1523
- return unless @context.logging?
1524
- log_force(tag, content, options)
1525
- end
1526
-
1527
- def log_force(tag, content, options)
1528
- return if content.empty?
1529
- @context.result << [tag, content, options]
1530
- end
1531
-
1532
- def log_input(content)
1533
- log(:input, content)
1534
- end
1535
-
1536
- def log_output(content)
1537
- log(:output, content,
1538
- :command => @current_command,
1539
- :type => @output_type)
1540
- @current_command = nil
1541
- @output_type = nil
1542
- end
1543
-
1544
- def log_error(content)
1545
- log_force(:error, content, {})
1546
- end
1547
-
1548
- def default_long_timeout
1549
- 180
1550
- end
1551
- end
1552
-
1553
- class StandardIOExecutor < Executor
1554
- def initialize(input, output, context=nil)
1555
- super(context)
1556
- @input = input
1557
- @output = output
1558
- end
1559
-
1560
- def send_command(command)
1561
- command_line = @current_command.original_source
1562
- unless @current_command.has_key?(:output_type)
1563
- command_line = command_line.sub(/$/, " --output_type #{@output_type}")
1564
- end
1565
- begin
1566
- @input.print(command_line)
1567
- @input.print("\n")
1568
- @input.flush
1569
- rescue SystemCallError
1570
- message = "failed to write to groonga: <#{command_line}>: #{$!}"
1571
- raise Error.new(message)
1572
- end
1573
- read_output
1574
- end
1575
-
1576
- def ensure_groonga_ready
1577
- @input.print("status\n")
1578
- @input.flush
1579
- @output.gets
1580
- end
1581
-
1582
- def create_sub_executor(context)
1583
- self.class.new(@input, @output, context)
1584
- end
1585
-
1586
- private
1587
- def read_output
1588
- options = {}
1589
- options[:first_timeout] = @long_timeout if may_slow_command?
1590
- read_all_readable_content(@output, options)
1591
- end
1592
-
1593
- MAY_SLOW_COMMANDS = [
1594
- "column_create",
1595
- "register",
1596
- ]
1597
- def may_slow_command?
1598
- MAY_SLOW_COMMANDS.include?(@current_command)
1599
- end
1600
- end
1601
-
1602
- class HTTPExecutor < Executor
1603
- def initialize(host, port, context=nil)
1604
- super(context)
1605
- @host = host
1606
- @port = port
1607
- end
1608
-
1609
- def send_command(command_line)
1610
- converter = CommandFormatConverter.new(command_line)
1611
- url = "http://#{@host}:#{@port}#{converter.to_url}"
1612
- begin
1613
- open(url) do |response|
1614
- "#{response.read}\n"
1615
- end
1616
- rescue OpenURI::HTTPError
1617
- message = "Failed to get response from groonga: #{$!}: <#{url}>"
1618
- raise Error.new(message)
1619
- end
1620
- end
1621
-
1622
- def ensure_groonga_ready
1623
- n_retried = 0
1624
- begin
1625
- send_command("status")
1626
- rescue SystemCallError
1627
- n_retried += 1
1628
- sleep(0.1)
1629
- retry if n_retried < 10
1630
- raise
1631
- end
1632
- end
1633
-
1634
- def create_sub_executor(context)
1635
- self.class.new(@host, @port, context)
1636
- end
1637
- end
1638
-
1639
- class CommandFormatConverter
1640
- def initialize(gqtp_command)
1641
- @gqtp_command = gqtp_command
1642
- end
1643
-
1644
- def to_url
1645
- command = Groonga::Command::Parser.parse(@gqtp_command)
1646
- command.to_uri_format
1647
- end
1648
- end
1649
-
1650
- class BaseReporter
1651
- def initialize(tester)
1652
- @tester = tester
1653
- @term_width = guess_term_width
1654
- @output = @tester.output
1655
- @mutex = Mutex.new
1656
- reset_current_column
1657
- end
1658
-
1659
- private
1660
- def synchronize
1661
- @mutex.synchronize do
1662
- yield
1663
- end
1664
- end
1665
-
1666
- def report_summary(result)
1667
- puts(statistics_header)
1668
- puts(colorize(statistics(result), result))
1669
- pass_ratio = result.pass_ratio
1670
- elapsed_time = result.elapsed_time
1671
- summary = "%.4g%% passed in %.4fs." % [pass_ratio, elapsed_time]
1672
- puts(colorize(summary, result))
1673
- end
1674
-
1675
- def columns
1676
- [
1677
- # label, format value
1678
- ["tests/sec", lambda {|result| "%9.2f" % throughput(result)}],
1679
- [" tests", lambda {|result| "%8d" % result.n_tests}],
1680
- [" passes", lambda {|result| "%8d" % result.n_passed_tests}],
1681
- ["failures", lambda {|result| "%8d" % result.n_failed_tests}],
1682
- [" leaked", lambda {|result| "%8d" % result.n_leaked_tests}],
1683
- [" omitted", lambda {|result| "%8d" % result.n_omitted_tests}],
1684
- ["!checked", lambda {|result| "%8d" % result.n_not_checked_tests}],
1685
- ]
1686
- end
1687
-
1688
- def statistics_header
1689
- labels = columns.collect do |label, format_value|
1690
- label
1691
- end
1692
- " " + labels.join(" | ") + " |"
1693
- end
1694
-
1695
- def statistics(result)
1696
- items = columns.collect do |label, format_value|
1697
- format_value.call(result)
1698
- end
1699
- " " + items.join(" | ") + " |"
1700
- end
1701
-
1702
- def throughput(result)
1703
- if result.elapsed_time.zero?
1704
- tests_per_second = 0
1705
- else
1706
- tests_per_second = result.n_tests / result.elapsed_time
1707
- end
1708
- tests_per_second
1709
- end
1710
-
1711
- def report_failure(result)
1712
- report_marker(result)
1713
- report_diff(result.expected, result.actual)
1714
- report_marker(result)
1715
- end
1716
-
1717
- def report_actual(result)
1718
- report_marker(result)
1719
- puts(result.actual)
1720
- report_marker(result)
1721
- end
1722
-
1723
- def report_marker(result)
1724
- puts(colorize("=" * @term_width, result))
1725
- end
1726
-
1727
- def report_diff(expected, actual)
1728
- create_temporary_file("expected", expected) do |expected_file|
1729
- create_temporary_file("actual", actual) do |actual_file|
1730
- diff_options = @tester.diff_options.dup
1731
- diff_options.concat(["--label", "(expected)", expected_file.path,
1732
- "--label", "(actual)", actual_file.path])
1733
- system(@tester.diff, *diff_options)
1734
- end
1735
- end
1736
- end
1737
-
1738
- def report_test(worker, result)
1739
- report_marker(result)
1740
- print("[#{worker.id}] ") if @tester.n_workers > 1
1741
- puts(worker.suite_name)
1742
- print(" #{worker.test_name}")
1743
- report_test_result(result, worker.status)
1744
- end
1745
-
1746
- def report_test_result(result, label)
1747
- message = test_result_message(result, label)
1748
- message_width = string_width(message)
1749
- rest_width = @term_width - @current_column
1750
- if rest_width > message_width
1751
- print(" " * (rest_width - message_width))
1752
- end
1753
- puts(message)
1754
- end
1755
-
1756
- def test_result_message(result, label)
1757
- elapsed_time = result.elapsed_time
1758
- formatted_elapsed_time = "%.4fs" % elapsed_time
1759
- formatted_elapsed_time = colorize(formatted_elapsed_time,
1760
- elapsed_time_status(elapsed_time))
1761
- " #{formatted_elapsed_time} [#{colorize(label, result)}]"
1762
- end
1763
-
1764
- LONG_ELAPSED_TIME = 1.0
1765
- def long_elapsed_time?(elapsed_time)
1766
- elapsed_time >= LONG_ELAPSED_TIME
1767
- end
1768
-
1769
- def elapsed_time_status(elapsed_time)
1770
- if long_elapsed_time?(elapsed_time)
1771
- elapsed_time_status = :failure
1772
- else
1773
- elapsed_time_status = :not_checked
1774
- end
1775
- end
1776
-
1777
- def justify(message, width)
1778
- return " " * width if message.nil?
1779
- return message.ljust(width) if message.bytesize <= width
1780
- half_width = width / 2.0
1781
- elision_mark = "..."
1782
- left = message[0, half_width.ceil - elision_mark.size]
1783
- right = message[(message.size - half_width.floor)..-1]
1784
- "#{left}#{elision_mark}#{right}"
1785
- end
1786
-
1787
- def print(message)
1788
- @current_column += string_width(message.to_s)
1789
- @output.print(message)
1790
- end
1791
-
1792
- def puts(*messages)
1793
- reset_current_column
1794
- @output.puts(*messages)
1795
- end
1796
-
1797
- def reset_current_column
1798
- @current_column = 0
1799
- end
1800
-
1801
- def create_temporary_file(key, content)
1802
- file = Tempfile.new("groonga-test-#{key}")
1803
- file.print(content)
1804
- file.close
1805
- yield(file)
1806
- end
1807
-
1808
- def guess_term_width
1809
- Integer(guess_term_width_from_env || guess_term_width_from_stty || 79)
1810
- rescue ArgumentError
1811
- 0
1812
- end
1813
-
1814
- def guess_term_width_from_env
1815
- ENV["COLUMNS"] || ENV["TERM_WIDTH"]
1816
- end
1817
-
1818
- def guess_term_width_from_stty
1819
- return nil unless STDIN.tty?
1820
-
1821
- case tty_info
1822
- when /(\d+) columns/
1823
- $1
1824
- when /columns (\d+)/
1825
- $1
1826
- else
1827
- nil
1828
- end
1829
- end
1830
-
1831
- def tty_info
1832
- begin
1833
- `stty -a`
1834
- rescue SystemCallError
1835
- nil
1836
- end
1837
- end
1838
-
1839
- def string_width(string)
1840
- string.gsub(/\e\[[0-9;]+m/, "").size
1841
- end
1842
-
1843
- def result_status(result)
1844
- if result.respond_to?(:status)
1845
- result.status
1846
- else
1847
- if result.n_failed_tests > 0
1848
- :failure
1849
- elsif result.n_leaked_tests > 0
1850
- :leaked
1851
- elsif result.n_omitted_tests > 0
1852
- :omitted
1853
- elsif result.n_not_checked_tests > 0
1854
- :not_checked
1855
- else
1856
- :success
1857
- end
1858
- end
1859
- end
1860
-
1861
- def colorize(message, result_or_status)
1862
- return message unless @tester.use_color?
1863
- if result_or_status.is_a?(Symbol)
1864
- status = result_or_status
1865
- else
1866
- status = result_status(result_or_status)
1867
- end
1868
- case status
1869
- when :success
1870
- "%s%s%s" % [success_color, message, reset_color]
1871
- when :failure
1872
- "%s%s%s" % [failure_color, message, reset_color]
1873
- when :leaked
1874
- "%s%s%s" % [leaked_color, message, reset_color]
1875
- when :omitted
1876
- "%s%s%s" % [omitted_color, message, reset_color]
1877
- when :not_checked
1878
- "%s%s%s" % [not_checked_color, message, reset_color]
1879
- else
1880
- message
1881
- end
1882
- end
1883
-
1884
- def success_color
1885
- escape_sequence({
1886
- :color => :green,
1887
- :color_256 => [0, 3, 0],
1888
- :background => true,
1889
- },
1890
- {
1891
- :color => :white,
1892
- :color_256 => [5, 5, 5],
1893
- :bold => true,
1894
- })
1895
- end
1896
-
1897
- def failure_color
1898
- escape_sequence({
1899
- :color => :red,
1900
- :color_256 => [3, 0, 0],
1901
- :background => true,
1902
- },
1903
- {
1904
- :color => :white,
1905
- :color_256 => [5, 5, 5],
1906
- :bold => true,
1907
- })
1908
- end
1909
-
1910
- def leaked_color
1911
- escape_sequence({
1912
- :color => :magenta,
1913
- :color_256 => [3, 0, 3],
1914
- :background => true,
1915
- },
1916
- {
1917
- :color => :white,
1918
- :color_256 => [5, 5, 5],
1919
- :bold => true,
1920
- })
1921
- end
1922
-
1923
- def omitted_color
1924
- escape_sequence({
1925
- :color => :blue,
1926
- :color_256 => [0, 0, 1],
1927
- :background => true,
1928
- },
1929
- {
1930
- :color => :white,
1931
- :color_256 => [5, 5, 5],
1932
- :bold => true,
1933
- })
1934
- end
1935
-
1936
- def not_checked_color
1937
- escape_sequence({
1938
- :color => :cyan,
1939
- :color_256 => [0, 1, 1],
1940
- :background => true,
1941
- },
1942
- {
1943
- :color => :white,
1944
- :color_256 => [5, 5, 5],
1945
- :bold => true,
1946
- })
1947
- end
1948
-
1949
- def reset_color
1950
- escape_sequence(:reset)
1951
- end
1952
-
1953
- COLOR_NAMES = [
1954
- :black, :red, :green, :yellow,
1955
- :blue, :magenta, :cyan, :white,
1956
- ]
1957
- def escape_sequence(*commands)
1958
- sequence = []
1959
- commands.each do |command|
1960
- case command
1961
- when :reset
1962
- sequence << "0"
1963
- when :bold
1964
- sequence << "1"
1965
- when :italic
1966
- sequence << "3"
1967
- when :underline
1968
- sequence << "4"
1969
- when Hash
1970
- foreground_p = !command[:background]
1971
- if available_colors == 256
1972
- sequence << (foreground_p ? "38" : "48")
1973
- sequence << "5"
1974
- sequence << pack_256_color(*command[:color_256])
1975
- else
1976
- color_parameter = foreground_p ? 3 : 4
1977
- color_parameter += 6 if command[:intensity]
1978
- color = COLOR_NAMES.index(command[:color])
1979
- sequence << "#{color_parameter}#{color}"
1980
- end
1981
- end
1982
- end
1983
- "\e[#{sequence.join(';')}m"
1984
- end
1985
-
1986
- def pack_256_color(red, green, blue)
1987
- red * 36 + green * 6 + blue + 16
1988
- end
1989
-
1990
- def available_colors
1991
- case ENV["COLORTERM"]
1992
- when "gnome-terminal"
1993
- 256
1994
- else
1995
- case ENV["TERM"]
1996
- when /-256color\z/
1997
- 256
1998
- else
1999
- 8
2000
- end
2001
- end
2002
- end
2003
- end
2004
-
2005
- class MarkReporter < BaseReporter
2006
- def initialize(tester)
2007
- super
2008
- end
2009
-
2010
- def on_start(result)
2011
- end
2012
-
2013
- def on_worker_start(worker)
2014
- end
2015
-
2016
- def on_suite_start(worker)
2017
- end
2018
-
2019
- def on_test_start(worker)
2020
- end
2021
-
2022
- def on_test_success(worker, result)
2023
- synchronize do
2024
- report_test_result_mark(".", result)
2025
- end
2026
- end
2027
-
2028
- def on_test_failure(worker, result)
2029
- synchronize do
2030
- report_test_result_mark("F", result)
2031
- puts
2032
- report_test(worker, result)
2033
- report_failure(result)
2034
- end
2035
- end
2036
-
2037
- def on_test_leak(worker, result)
2038
- synchronize do
2039
- report_test_result_mark("L(#{result.n_leaked_objects})", result)
2040
- end
2041
- end
2042
-
2043
- def on_test_omission(worker, result)
2044
- synchronize do
2045
- report_test_result_mark("O", result)
2046
- puts
2047
- report_test(worker, result)
2048
- report_actual(result)
2049
- end
2050
- end
2051
-
2052
- def on_test_no_check(worker, result)
2053
- synchronize do
2054
- report_test_result_mark("N", result)
2055
- puts
2056
- report_test(worker, result)
2057
- report_actual(result)
2058
- end
2059
- end
2060
-
2061
- def on_test_finish(worker, result)
2062
- end
2063
-
2064
- def on_suite_finish(worker)
2065
- end
2066
-
2067
- def on_worker_finish(worker_id)
2068
- end
2069
-
2070
- def on_finish(result)
2071
- puts
2072
- puts
2073
- report_summary(result)
2074
- end
2075
-
2076
- private
2077
- def report_test_result_mark(mark, result)
2078
- if @term_width < @current_column + mark.bytesize
2079
- puts
2080
- end
2081
- print(colorize(mark, result))
2082
- if @term_width <= @current_column
2083
- puts
2084
- else
2085
- @output.flush
2086
- end
2087
- end
2088
- end
2089
-
2090
- class StreamReporter < BaseReporter
2091
- def initialize(tester)
2092
- super
2093
- end
2094
-
2095
- def on_start(result)
2096
- end
2097
-
2098
- def on_worker_start(worker)
2099
- end
2100
-
2101
- def on_suite_start(worker)
2102
- if worker.suite_name.bytesize <= @term_width
2103
- puts(worker.suite_name)
2104
- else
2105
- puts(justify(worker.suite_name, @term_width))
2106
- end
2107
- @output.flush
2108
- end
2109
-
2110
- def on_test_start(worker)
2111
- print(" #{worker.test_name}")
2112
- @output.flush
2113
- end
2114
-
2115
- def on_test_success(worker, result)
2116
- report_test_result(result, worker.status)
2117
- end
2118
-
2119
- def on_test_failure(worker, result)
2120
- report_test_result(result, worker.status)
2121
- report_failure(result)
2122
- end
2123
-
2124
- def on_test_leak(worker, result)
2125
- report_test_result(result, worker.status)
2126
- end
2127
-
2128
- def on_test_omission(worker, result)
2129
- report_test_result(result, worker.status)
2130
- report_actual(result)
2131
- end
2132
-
2133
- def on_test_no_check(worker, result)
2134
- report_test_result(result, worker.status)
2135
- report_actual(result)
2136
- end
2137
-
2138
- def on_test_finish(worker, result)
2139
- end
2140
-
2141
- def on_suite_finish(worker)
2142
- end
2143
-
2144
- def on_worker_finish(worker_id)
2145
- end
2146
-
2147
- def on_finish(result)
2148
- puts
2149
- report_summary(result)
2150
- end
2151
- end
2152
-
2153
- class InplaceReporter < BaseReporter
2154
- def initialize(tester)
2155
- super
2156
- @last_redraw_time = Time.now
2157
- @minimum_redraw_interval = 0.1
2158
- end
2159
-
2160
- def on_start(result)
2161
- @test_suites_result = result
2162
- end
2163
-
2164
- def on_worker_start(worker)
2165
- end
2166
-
2167
- def on_suite_start(worker)
2168
- redraw
2169
- end
2170
-
2171
- def on_test_start(worker)
2172
- redraw
2173
- end
2174
-
2175
- def on_test_success(worker, result)
2176
- redraw
2177
- end
2178
-
2179
- def on_test_failure(worker, result)
2180
- redraw do
2181
- report_test(worker, result)
2182
- report_failure(result)
2183
- end
2184
- end
2185
-
2186
- def on_test_leak(worker, result)
2187
- redraw do
2188
- report_test(worker, result)
2189
- report_marker(result)
2190
- end
2191
- end
2192
-
2193
- def on_test_omission(worker, result)
2194
- redraw do
2195
- report_test(worker, result)
2196
- report_actual(result)
2197
- end
2198
- end
2199
-
2200
- def on_test_no_check(worker, result)
2201
- redraw do
2202
- report_test(worker, result)
2203
- report_actual(result)
2204
- end
2205
- end
2206
-
2207
- def on_test_finish(worker, result)
2208
- redraw
2209
- end
2210
-
2211
- def on_suite_finish(worker)
2212
- redraw
2213
- end
2214
-
2215
- def on_worker_finish(worker)
2216
- redraw
2217
- end
2218
-
2219
- def on_finish(result)
2220
- draw
2221
- puts
2222
- report_summary(result)
2223
- end
2224
-
2225
- private
2226
- def draw
2227
- draw_statistics_header_line
2228
- @test_suites_result.workers.each do |worker|
2229
- draw_status_line(worker)
2230
- draw_test_line(worker)
2231
- end
2232
- draw_progress_line
2233
- end
2234
-
2235
- def draw_statistics_header_line
2236
- puts(statistics_header)
2237
- end
2238
-
2239
- def draw_status_line(worker)
2240
- clear_line
2241
- left = "[#{colorize(worker.id, worker.result)}] "
2242
- right = " [#{worker.status}]"
2243
- rest_width = @term_width - @current_column
2244
- center_width = rest_width - string_width(left) - string_width(right)
2245
- center = justify(worker.suite_name, center_width)
2246
- puts("#{left}#{center}#{right}")
2247
- end
2248
-
2249
- def draw_test_line(worker)
2250
- clear_line
2251
- if worker.test_name
2252
- label = " #{worker.test_name}"
2253
- else
2254
- label = statistics(worker.result)
2255
- end
2256
- puts(justify(label, @term_width))
2257
- end
2258
-
2259
- def draw_progress_line
2260
- n_done_tests = @test_suites_result.n_tests
2261
- n_total_tests = @test_suites_result.n_total_tests
2262
- if n_total_tests.zero?
2263
- finished_test_ratio = 0.0
2264
- else
2265
- finished_test_ratio = n_done_tests.to_f / n_total_tests
2266
- end
2267
-
2268
- start_mark = "|"
2269
- finish_mark = "|"
2270
- statistics = " [%3d%%]" % (finished_test_ratio * 100)
2271
-
2272
- progress_width = @term_width
2273
- progress_width -= start_mark.bytesize
2274
- progress_width -= finish_mark.bytesize
2275
- progress_width -= statistics.bytesize
2276
- finished_mark = "-"
2277
- if n_done_tests == n_total_tests
2278
- progress = colorize(finished_mark * progress_width,
2279
- @test_suites_result)
2280
- else
2281
- current_mark = ">"
2282
- finished_marks_width = (progress_width * finished_test_ratio).ceil
2283
- finished_marks_width -= current_mark.bytesize
2284
- finished_marks_width = [0, finished_marks_width].max
2285
- progress = finished_mark * finished_marks_width + current_mark
2286
- progress = colorize(progress, @test_suites_result)
2287
- progress << " " * (progress_width - string_width(progress))
2288
- end
2289
- puts("#{start_mark}#{progress}#{finish_mark}#{statistics}")
2290
- end
2291
-
2292
- def redraw
2293
- synchronize do
2294
- unless block_given?
2295
- return if Time.now - @last_redraw_time < @minimum_redraw_interval
2296
- end
2297
- draw
2298
- if block_given?
2299
- yield
2300
- else
2301
- up_n_lines(n_using_lines)
2302
- end
2303
- @last_redraw_time = Time.now
2304
- end
2305
- end
2306
-
2307
- def up_n_lines(n)
2308
- print("\e[1A" * n)
2309
- end
2310
-
2311
- def clear_line
2312
- print(" " * @term_width)
2313
- print("\r")
2314
- reset_current_column
2315
- end
2316
-
2317
- def n_using_lines
2318
- n_statistics_header_line + n_worker_lines * n_workers + n_progress_lines
2319
- end
2320
-
2321
- def n_statistics_header_line
2322
- 1
2323
- end
2324
-
2325
- def n_worker_lines
2326
- 2
2327
- end
2328
-
2329
- def n_progress_lines
2330
- 1
2331
- end
2332
-
2333
- def n_workers
2334
- @tester.n_workers
2335
- end
2336
- end
2337
364
  end
2338
365
  end