grntest 1.0.2 → 1.0.3

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,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