grntest 1.0.1 → 1.0.2

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.
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --output-dir doc/reference/en
2
+ --markup markdown
3
+ --markup-provider redcarpet
4
+ -
5
+ doc/text/*
data/README.md CHANGED
@@ -212,6 +212,9 @@ Here are available `NAME` s:
212
212
  * `suggest-create-dataset`
213
213
  * `include`
214
214
  * `copy-path`
215
+ * `long-timeout`
216
+ * `on-error`
217
+ * `omit`
215
218
 
216
219
  `ARGUMENTS...` are depends on directive. A directive doesn't require
217
220
  any arguments but a directive requires arguments.
@@ -245,7 +248,7 @@ select Users --query _key:User29
245
248
 
246
249
  See also: `enable-logging`
247
250
 
248
- ### `enable-logging`
251
+ #### `enable-logging`
249
252
 
250
253
  Usage:
251
254
 
@@ -257,7 +260,7 @@ It enables logging that is disabled by `disable-logging` directive.
257
260
 
258
261
  See also: `disable-logging`
259
262
 
260
- ### suggest-create-dataset
263
+ #### `suggest-create-dataset`
261
264
 
262
265
  Usage:
263
266
 
@@ -284,7 +287,7 @@ load --table event_rurema --each 'suggest_preparer(_id, type, item, sequence, ti
284
287
 
285
288
  See also: `--groonga-suggest-create-dataset` option
286
289
 
287
- ### include
290
+ #### `include`
288
291
 
289
292
  Usage:
290
293
 
@@ -339,9 +342,9 @@ select Users --query _key:Alice
339
342
 
340
343
  See also: `--base-directory` option
341
344
 
342
- ### `copy-path`
345
+ #### `copy-path`
343
346
 
344
- Usage
347
+ Usage:
345
348
 
346
349
  ```
347
350
  #@copy-path SOURCE DESTINATION
@@ -357,6 +360,75 @@ Example:
357
360
  register "query_expanders/tsv"
358
361
  ```
359
362
 
363
+ #### `long-timeout`
364
+
365
+ Usage:
366
+
367
+ ```
368
+ #@long-timeout TIMEOUT
369
+ ```
370
+
371
+ It specifies a timeout for commands that may take long time.
372
+ `TIMEOUT` must be a number or `default`. If you specify `default` as
373
+ `TIMEOUT`, the default timeout is used. The default timeout is 180
374
+ seconds.
375
+
376
+ Here are the commands that may take long time:
377
+
378
+ * `column_create`
379
+ * `register`
380
+
381
+ Example:
382
+
383
+ ```
384
+ # Wait 300 seconds until commands that may take long time output their results
385
+ #@long-timeout 300
386
+ column_create Lexicon users_name COLUMN_INDEX Users name
387
+ # Reset custom timeout for commands that may take long time.
388
+ #@long-timeout default
389
+ ```
390
+
391
+ #### `on-error`
392
+
393
+ Usage:
394
+
395
+ ```
396
+ #@on-error ACTION
397
+ ```
398
+
399
+ It specifies a action on error. `ACTION` must be `default` or
400
+ `omit`. If you specify `default` as `ACTION`, the running test is
401
+ continued. If you specify `omit` as `ACTION`, the running test is
402
+ aborted and the running test is marked as omitted.
403
+
404
+ Example:
405
+
406
+ ```
407
+ # Omit this test if TokenKyTea tokenizer isn't available.
408
+ #@on-error omit
409
+ register tokenizers/kytea
410
+ # Don't omit this test when any error is occurred in the following commands.
411
+ #@on-error default
412
+ ```
413
+
414
+ #### `omit`
415
+
416
+ Usage:
417
+
418
+ ```
419
+ #@omit REASON
420
+ ```
421
+
422
+ Omit the test with `REASON`.
423
+
424
+ Example:
425
+
426
+ ```
427
+ # Omit this test until the feature is implemented.
428
+ #@omit "This feature is not implemented yet."
429
+ new_excelent_command
430
+ ```
431
+
360
432
  ## Options
361
433
 
362
434
  Grntest has many options. You don't need to specify many of them
data/doc/text/news.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # News
2
2
 
3
+ ## 1.0.2: 2012-12-11
4
+
5
+ This is the release that add some directive.
6
+
7
+ ### Improvements
8
+
9
+ * Used long timeout for `column_create` and `register`.
10
+ * Added `long-timeout` directive.
11
+ * Added `on-error` directive.
12
+ * Added "omit" status and `omit` directive.
13
+ * Aborted a test when a command in it can't be parsed by
14
+ Groonga::Command::Parser.
15
+
16
+ ### Fixes
17
+
18
+ * Used stty only when the standard input is tty.
19
+
3
20
  ## 1.0.1: 2012-10-15
4
21
 
5
22
  This has a backward incompatible change. It is directive syntax.
@@ -12,7 +29,7 @@ New:
12
29
 
13
30
  #@NAME ARGUMENT
14
31
 
15
- This change is for easy to debug. Conssider about we have a typo in
32
+ This change is for easy to debug. Consider about we have a typo in
16
33
  `NAME`. It is just ignored in old syntax because it is also a comment
17
34
  line. It is reported in new syntax because it syntax is only for
18
35
  directive. Grntest can know that user want to use a directive. If
data/grntest.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.summary, spec.description, = description.split(/\n\n+/, 3)
36
36
  spec.license = "GPLv3 or later"
37
37
  spec.files = ["README.md", "Rakefile", "Gemfile", "#{spec.name}.gemspec"]
38
+ spec.files += [".yardopts"]
38
39
  spec.files += Dir.glob("lib/**/*.rb")
39
40
  spec.files += Dir.glob("doc/text/*")
40
41
  spec.test_files += Dir.glob("test/**/*")
@@ -42,12 +43,14 @@ Gem::Specification.new do |spec|
42
43
  spec.executables = Dir.glob("*")
43
44
  end
44
45
 
45
- spec.add_dependency("json")
46
- spec.add_dependency("msgpack")
46
+ spec.add_runtime_dependency("json")
47
+ spec.add_runtime_dependency("msgpack")
48
+ spec.add_runtime_dependency("groonga-command")
47
49
 
48
50
  spec.add_development_dependency("bundler")
49
51
  spec.add_development_dependency("rake")
50
52
  spec.add_development_dependency("test-unit")
53
+ spec.add_development_dependency("test-unit-rr")
51
54
  spec.add_development_dependency("packnga")
52
55
  spec.add_development_dependency("redcarpet")
53
56
  end
@@ -22,11 +22,12 @@ require "fileutils"
22
22
  require "tempfile"
23
23
  require "shellwords"
24
24
  require "open-uri"
25
- require "cgi/util"
26
25
 
27
26
  require "json"
28
27
  require "msgpack"
29
28
 
29
+ require "groonga/command"
30
+
30
31
  require "grntest/version"
31
32
 
32
33
  module Grntest
@@ -407,13 +408,14 @@ module Grntest
407
408
 
408
409
  class WorkerResult < Result
409
410
  attr_reader :n_tests, :n_passed_tests, :n_leaked_tests
410
- attr_reader :n_not_checked_tests
411
+ attr_reader :n_omitted_tests, :n_not_checked_tests
411
412
  attr_reader :failed_tests
412
413
  def initialize
413
414
  super
414
415
  @n_tests = 0
415
416
  @n_passed_tests = 0
416
417
  @n_leaked_tests = 0
418
+ @n_omitted_tests = 0
417
419
  @n_not_checked_tests = 0
418
420
  @failed_tests = []
419
421
  end
@@ -422,23 +424,27 @@ module Grntest
422
424
  @failed_tests.size
423
425
  end
424
426
 
425
- def test_finished
427
+ def on_test_finish
426
428
  @n_tests += 1
427
429
  end
428
430
 
429
- def test_passed
431
+ def on_test_success
430
432
  @n_passed_tests += 1
431
433
  end
432
434
 
433
- def test_failed(name)
435
+ def on_test_failure(name)
434
436
  @failed_tests << name
435
437
  end
436
438
 
437
- def test_leaked(name)
439
+ def on_test_leak(name)
438
440
  @n_leaked_tests += 1
439
441
  end
440
442
 
441
- def test_not_checked
443
+ def on_test_omission
444
+ @n_omitted_tests += 1
445
+ end
446
+
447
+ def on_test_no_check
442
448
  @n_not_checked_tests += 1
443
449
  end
444
450
  end
@@ -471,16 +477,16 @@ module Grntest
471
477
  succeeded = true
472
478
 
473
479
  @result.measure do
474
- @reporter.start_worker(self)
480
+ @reporter.on_worker_start(self)
475
481
  catch do |tag|
476
482
  loop do
477
483
  suite_name, test_script_path, test_name = queue.pop
478
484
  break if test_script_path.nil?
479
485
 
480
486
  unless @suite_name == suite_name
481
- @reporter.finish_suite(self) if @suite_name
487
+ @reporter.on_suite_finish(self) if @suite_name
482
488
  @suite_name = suite_name
483
- @reporter.start_suite(self)
489
+ @reporter.on_suite_start(self)
484
490
  end
485
491
  @test_script_path = test_script_path
486
492
  @test_name = test_name
@@ -490,48 +496,54 @@ module Grntest
490
496
  break if interruptted?
491
497
  end
492
498
  @status = "finished"
493
- @reporter.finish_suite(@suite_name) if @suite_name
499
+ @reporter.on_suite_finish(@suite_name) if @suite_name
494
500
  @suite_name = nil
495
501
  end
496
502
  end
497
- @reporter.finish_worker(self)
503
+ @reporter.on_worker_finish(self)
498
504
 
499
505
  succeeded
500
506
  end
501
507
 
502
- def start_test
508
+ def on_test_start
503
509
  @status = "running"
504
510
  @test_result = nil
505
- @reporter.start_test(self)
511
+ @reporter.on_test_start(self)
506
512
  end
507
513
 
508
- def pass_test(result)
514
+ def on_test_success(result)
509
515
  @status = "passed"
510
- @result.test_passed
511
- @reporter.pass_test(self, result)
516
+ @result.on_test_success
517
+ @reporter.on_test_success(self, result)
512
518
  end
513
519
 
514
- def fail_test(result)
520
+ def on_test_failure(result)
515
521
  @status = "failed"
516
- @result.test_failed(test_name)
517
- @reporter.fail_test(self, result)
522
+ @result.on_test_failure(test_name)
523
+ @reporter.on_test_failure(self, result)
518
524
  end
519
525
 
520
- def leaked_test(result)
526
+ def on_test_leak(result)
521
527
  @status = "leaked(#{result.n_leaked_objects})"
522
- @result.test_leaked(test_name)
523
- @reporter.leaked_test(self, result)
528
+ @result.on_test_leak(test_name)
529
+ @reporter.on_test_leak(self, result)
524
530
  end
525
531
 
526
- def not_checked_test(result)
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)
527
539
  @status = "not checked"
528
- @result.test_not_checked
529
- @reporter.not_checked_test(self, result)
540
+ @result.on_test_no_check
541
+ @reporter.on_test_no_check(self, result)
530
542
  end
531
543
 
532
- def finish_test(result)
533
- @result.test_finished
534
- @reporter.finish_test(self, result)
544
+ def on_test_finish(result)
545
+ @result.on_test_finish
546
+ @reporter.on_test_finish(self, result)
535
547
  @test_script_path = nil
536
548
  @test_name = nil
537
549
  end
@@ -571,6 +583,10 @@ module Grntest
571
583
  collect_count(:n_leaked_tests)
572
584
  end
573
585
 
586
+ def n_omitted_tests
587
+ collect_count(:n_omitted_tests)
588
+ end
589
+
574
590
  def n_not_checked_tests
575
591
  collect_count(:n_not_checked_tests)
576
592
  end
@@ -597,7 +613,7 @@ module Grntest
597
613
  @result.measure do
598
614
  succeeded = run_test_suites(test_suites)
599
615
  end
600
- @reporter.finish(@result)
616
+ @reporter.on_finish(@result)
601
617
 
602
618
  succeeded
603
619
  end
@@ -623,7 +639,7 @@ module Grntest
623
639
  workers << Worker.new(i, @tester, @result, @reporter)
624
640
  end
625
641
  @result.workers = workers
626
- @reporter.start(@result)
642
+ @reporter.on_start(@result)
627
643
 
628
644
  succeeded = true
629
645
  worker_threads = []
@@ -660,6 +676,7 @@ module Grntest
660
676
  class TestResult < Result
661
677
  attr_accessor :worker_id, :test_name
662
678
  attr_accessor :expected, :actual, :n_leaked_objects
679
+ attr_writer :omitted
663
680
  def initialize(worker)
664
681
  super()
665
682
  @worker_id = worker.id
@@ -667,9 +684,12 @@ module Grntest
667
684
  @actual = nil
668
685
  @expected = nil
669
686
  @n_leaked_objects = 0
687
+ @omitted = false
670
688
  end
671
689
 
672
690
  def status
691
+ return :omitted if omitted?
692
+
673
693
  if @expected
674
694
  if @actual == @expected
675
695
  if @n_leaked_objects.zero?
@@ -688,6 +708,51 @@ module Grntest
688
708
  end
689
709
  end
690
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
691
756
  end
692
757
 
693
758
  class TestRunner
@@ -703,35 +768,37 @@ module Grntest
703
768
  def run
704
769
  succeeded = true
705
770
 
706
- @worker.start_test
771
+ @worker.on_test_start
707
772
  result = TestResult.new(@worker)
708
773
  result.measure do
709
- result.actual = execute_groonga_script
774
+ execute_groonga_script(result)
710
775
  end
711
776
  normalize_actual_result(result)
712
777
  result.expected = read_expected_result
713
778
  case result.status
714
779
  when :success
715
- @worker.pass_test(result)
780
+ @worker.on_test_success(result)
716
781
  remove_reject_file
717
782
  when :failure
718
- @worker.fail_test(result)
783
+ @worker.on_test_failure(result)
719
784
  output_reject_file(result.actual)
720
785
  succeeded = false
721
786
  when :leaked
722
- @worker.leaked_test(result)
787
+ @worker.on_test_leak(result)
723
788
  succeeded = false
789
+ when :omitted
790
+ @worker.on_test_omission(result)
724
791
  else
725
- @worker.not_checked_test(result)
792
+ @worker.on_test_no_check(result)
726
793
  output_actual_file(result.actual)
727
794
  end
728
- @worker.finish_test(result)
795
+ @worker.on_test_finish(result)
729
796
 
730
797
  succeeded
731
798
  end
732
799
 
733
800
  private
734
- def execute_groonga_script
801
+ def execute_groonga_script(result)
735
802
  create_temporary_directory do |directory_path|
736
803
  if @tester.database_path
737
804
  db_path = Pathname(@tester.database_path).expand_path
@@ -751,7 +818,8 @@ module Grntest
751
818
  executor.execute(test_script_path)
752
819
  end
753
820
  check_memory_leak(context)
754
- context.result
821
+ result.omitted = context.omitted?
822
+ result.actual = context.result
755
823
  end
756
824
  end
757
825
 
@@ -781,11 +849,14 @@ module Grntest
781
849
  create_empty_database(context.db_path.to_s)
782
850
  end
783
851
 
784
- case @tester.interface
785
- when :stdio
786
- run_groonga_stdio(context, &block)
787
- when :http
788
- run_groonga_http(context, &block)
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
789
860
  end
790
861
  end
791
862
 
@@ -1022,7 +1093,7 @@ EOF
1022
1093
  status = nil
1023
1094
  values = nil
1024
1095
  begin
1025
- status, *values = parse_result(content.chomp, type)
1096
+ status, *values = ResponseParser.parse(content.chomp, type)
1026
1097
  rescue ParseError
1027
1098
  return $!.message
1028
1099
  end
@@ -1038,25 +1109,6 @@ EOF
1038
1109
  end
1039
1110
  end
1040
1111
 
1041
- def parse_result(result, type)
1042
- case type
1043
- when "json"
1044
- begin
1045
- JSON.parse(result)
1046
- rescue JSON::ParserError
1047
- raise ParseError.new(type, result, $!.message)
1048
- end
1049
- when "msgpack"
1050
- begin
1051
- MessagePack.unpack(result.chomp)
1052
- rescue MessagePack::UnpackError, NoMemoryError
1053
- raise ParseError.new(type, result, $!.message)
1054
- end
1055
- else
1056
- raise ParseError.new(type, result, "unknown type")
1057
- end
1058
- end
1059
-
1060
1112
  def normalize_status(status)
1061
1113
  return_code, started_time, elapsed_time, *rest = status
1062
1114
  _ = started_time = elapsed_time # for suppress warnings
@@ -1135,6 +1187,8 @@ EOF
1135
1187
  attr_accessor :groonga_suggest_create_dataset
1136
1188
  attr_accessor :result
1137
1189
  attr_accessor :output_type
1190
+ attr_accessor :on_error
1191
+ attr_accessor :abort_tag
1138
1192
  def initialize
1139
1193
  @logging = true
1140
1194
  @base_directory = Pathname(".")
@@ -1145,6 +1199,9 @@ EOF
1145
1199
  @result = []
1146
1200
  @output_type = "json"
1147
1201
  @log = nil
1202
+ @on_error = :default
1203
+ @abort_tag = nil
1204
+ @omitted = false
1148
1205
  end
1149
1206
 
1150
1207
  def logging?
@@ -1173,6 +1230,30 @@ EOF
1173
1230
  def relative_db_path
1174
1231
  @db_path.relative_path_from(@temporary_directory_path)
1175
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
1176
1257
  end
1177
1258
 
1178
1259
  attr_reader :context
@@ -1182,6 +1263,7 @@ EOF
1182
1263
  @pending_load_command = nil
1183
1264
  @current_command_name = nil
1184
1265
  @output_type = nil
1266
+ @long_timeout = default_long_timeout
1185
1267
  @context = context || Context.new
1186
1268
  end
1187
1269
 
@@ -1192,17 +1274,19 @@ EOF
1192
1274
 
1193
1275
  @context.execute do
1194
1276
  script_path.open("r:ascii-8bit") do |script_file|
1277
+ parser = create_parser
1195
1278
  script_file.each_line do |line|
1196
1279
  begin
1197
- if @loading
1198
- execute_line_on_loading(line)
1199
- else
1200
- execute_line_with_continuation_line_support(line)
1201
- end
1202
- rescue Error
1280
+ parser << line
1281
+ rescue Error, Groonga::Command::ParseError
1203
1282
  line_info = "#{script_path}:#{script_file.lineno}:#{line.chomp}"
1204
1283
  log_error("#{line_info}: #{$!.message}")
1205
- raise unless @context.top_level?
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
1206
1290
  end
1207
1291
  end
1208
1292
  end
@@ -1212,42 +1296,21 @@ EOF
1212
1296
  end
1213
1297
 
1214
1298
  private
1215
- def execute_line_on_loading(line)
1216
- log_input(line)
1217
- @pending_load_command << line
1218
- if line == "]\n"
1219
- execute_command(@pending_load_command)
1220
- @pending_load_command = nil
1221
- @loading = false
1222
- end
1223
- end
1224
-
1225
- def execute_line_with_continuation_line_support(line)
1226
- if /\\$/ =~ line
1227
- @pending_command << $PREMATCH
1228
- else
1229
- if @pending_command.empty?
1230
- execute_line(line)
1231
- else
1232
- @pending_command << line
1233
- execute_line(@pending_command)
1234
- @pending_command = ""
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)
1235
1311
  end
1236
1312
  end
1237
- end
1238
-
1239
- def execute_line(line)
1240
- case line
1241
- when /\A\#@/
1242
- directive_content = $POSTMATCH
1243
- execute_directive(line, directive_content)
1244
- when /\A\s*\z/
1245
- # do nothing
1246
- when /\A\s*\#/
1247
- # ignore comment
1248
- else
1249
- execute_command_line(line)
1250
- end
1313
+ parser
1251
1314
  end
1252
1315
 
1253
1316
  def resolve_path(path)
@@ -1295,6 +1358,54 @@ EOF
1295
1358
  FileUtils.cp_r(source.to_s, destination.to_s)
1296
1359
  end
1297
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
+
1298
1409
  def execute_directive(line, content)
1299
1410
  command, *options = Shellwords.split(content)
1300
1411
  case command
@@ -1308,6 +1419,12 @@ EOF
1308
1419
  execute_directive_include(line, content, options)
1309
1420
  when "copy-path"
1310
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)
1311
1428
  else
1312
1429
  log_input(line)
1313
1430
  log_error("#|e| unknown directive: <#{command}>")
@@ -1335,46 +1452,24 @@ EOF
1335
1452
  executor.execute(resolve_path(script_path))
1336
1453
  end
1337
1454
 
1338
- def execute_command_line(command_line)
1339
- extract_command_info(command_line)
1340
- log_input(command_line)
1341
- if multiline_load_command?
1342
- @loading = true
1343
- @pending_load_command = command_line.dup
1344
- else
1345
- execute_command(command_line)
1346
- end
1347
- end
1348
-
1349
- def extract_command_info(command_line)
1350
- @current_command, *@current_arguments = Shellwords.split(command_line)
1351
- if @current_command == "dump"
1455
+ def extract_command_info(command)
1456
+ @current_command = command
1457
+ if @current_command.name == "dump"
1352
1458
  @output_type = "groonga-command"
1353
1459
  else
1354
- @output_type = @context.output_type
1355
- @current_arguments.each_with_index do |word, i|
1356
- if /\A--output_type(?:=(.+))?\z/ =~ word
1357
- @output_type = $1 || words[i + 1]
1358
- break
1359
- end
1360
- end
1361
- end
1362
- end
1363
-
1364
- def have_output_type_argument?
1365
- @current_arguments.any? do |argument|
1366
- /\A--output_type(?:=.+)?\z/ =~ argument
1460
+ @output_type = @current_command[:output_type] || @context.output_type
1367
1461
  end
1368
1462
  end
1369
1463
 
1370
- def multiline_load_command?
1371
- @current_command == "load" and
1372
- not @current_arguments.include?("--values")
1373
- end
1374
-
1375
1464
  def execute_command(command)
1376
- log_output(send_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)
1377
1470
  log_error(read_error_log)
1471
+
1472
+ @context.error if error_response?(response, type)
1378
1473
  end
1379
1474
 
1380
1475
  def read_error_log
@@ -1396,8 +1491,10 @@ EOF
1396
1491
  timeout = first_timeout
1397
1492
  while IO.select([output], [], [], timeout)
1398
1493
  break if output.eof?
1399
- content << output.readpartial(65535)
1400
- timeout = 0
1494
+ request_bytes = 1024
1495
+ read_content = output.readpartial(request_bytes)
1496
+ content << read_content
1497
+ timeout = 0 if read_content.bytesize < request_bytes
1401
1498
  end
1402
1499
  content
1403
1500
  end
@@ -1410,6 +1507,18 @@ EOF
1410
1507
  message.start_with?("/")
1411
1508
  end
1412
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
+
1413
1522
  def log(tag, content, options={})
1414
1523
  return unless @context.logging?
1415
1524
  log_force(tag, content, options)
@@ -1435,6 +1544,10 @@ EOF
1435
1544
  def log_error(content)
1436
1545
  log_force(:error, content, {})
1437
1546
  end
1547
+
1548
+ def default_long_timeout
1549
+ 180
1550
+ end
1438
1551
  end
1439
1552
 
1440
1553
  class StandardIOExecutor < Executor
@@ -1444,12 +1557,14 @@ EOF
1444
1557
  @output = output
1445
1558
  end
1446
1559
 
1447
- def send_command(command_line)
1448
- unless have_output_type_argument?
1560
+ def send_command(command)
1561
+ command_line = @current_command.original_source
1562
+ unless @current_command.has_key?(:output_type)
1449
1563
  command_line = command_line.sub(/$/, " --output_type #{@output_type}")
1450
1564
  end
1451
1565
  begin
1452
1566
  @input.print(command_line)
1567
+ @input.print("\n")
1453
1568
  @input.flush
1454
1569
  rescue SystemCallError
1455
1570
  message = "failed to write to groonga: <#{command_line}>: #{$!}"
@@ -1470,7 +1585,17 @@ EOF
1470
1585
 
1471
1586
  private
1472
1587
  def read_output
1473
- read_all_readable_content(@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)
1474
1599
  end
1475
1600
  end
1476
1601
 
@@ -1517,84 +1642,8 @@ EOF
1517
1642
  end
1518
1643
 
1519
1644
  def to_url
1520
- command = nil
1521
- arguments = nil
1522
- load_values = ""
1523
- @gqtp_command.each_line.with_index do |line, i|
1524
- if i.zero?
1525
- command, *arguments = Shellwords.split(line)
1526
- else
1527
- load_values << line
1528
- end
1529
- end
1530
- arguments.concat(["--values", load_values]) unless load_values.empty?
1531
-
1532
- named_arguments = convert_to_named_arguments(command, arguments)
1533
- build_url(command, named_arguments)
1534
- end
1535
-
1536
- private
1537
- def convert_to_named_arguments(command, arguments)
1538
- named_arguments = {}
1539
-
1540
- last_argument_name = nil
1541
- n_non_named_arguments = 0
1542
- arguments.each do |argument|
1543
- if /\A--/ =~ argument
1544
- last_argument_name = $POSTMATCH
1545
- next
1546
- end
1547
-
1548
- if last_argument_name.nil?
1549
- argument_name = arguments_name(command)[n_non_named_arguments]
1550
- n_non_named_arguments += 1
1551
- else
1552
- argument_name = last_argument_name
1553
- last_argument_name = nil
1554
- end
1555
-
1556
- named_arguments[argument_name] = argument
1557
- end
1558
-
1559
- named_arguments
1560
- end
1561
-
1562
- def arguments_name(command)
1563
- case command
1564
- when "table_create"
1565
- ["name", "flags", "key_type", "value_type", "default_tokenizer"]
1566
- when "column_create"
1567
- ["table", "name", "flags", "type", "source"]
1568
- when "load"
1569
- ["values", "table", "columns", "ifexists", "input_type"]
1570
- when "select"
1571
- ["table"]
1572
- when "suggest"
1573
- [
1574
- "types", "table", "column", "query", "sortby",
1575
- "output_columns", "offset", "limit", "frequency_threshold",
1576
- "conditional_probability_threshold", "prefix_search"
1577
- ]
1578
- when "truncate"
1579
- ["table"]
1580
- when "get"
1581
- ["table", "key", "output_columns", "id"]
1582
- else
1583
- nil
1584
- end
1585
- end
1586
-
1587
- def build_url(command, named_arguments)
1588
- url = "/d/#{command}"
1589
- query_parameters = []
1590
- named_arguments.each do |name, argument|
1591
- query_parameters << "#{CGI.escape(name)}=#{CGI.escape(argument)}"
1592
- end
1593
- unless query_parameters.empty?
1594
- url << "?"
1595
- url << query_parameters.join("&")
1596
- end
1597
- url
1645
+ command = Groonga::Command::Parser.parse(@gqtp_command)
1646
+ command.to_uri_format
1598
1647
  end
1599
1648
  end
1600
1649
 
@@ -1623,27 +1672,30 @@ EOF
1623
1672
  puts(colorize(summary, result))
1624
1673
  end
1625
1674
 
1626
- def statistics_header
1627
- items = [
1628
- "tests/sec",
1629
- "tests",
1630
- "passes",
1631
- "failures",
1632
- "leaked",
1633
- "!checked",
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}],
1634
1685
  ]
1635
- " " + ((["%-9s"] * items.size).join(" | ") % items) + " |"
1686
+ end
1687
+
1688
+ def statistics_header
1689
+ labels = columns.collect do |label, format_value|
1690
+ label
1691
+ end
1692
+ " " + labels.join(" | ") + " |"
1636
1693
  end
1637
1694
 
1638
1695
  def statistics(result)
1639
- items = [
1640
- "%9.2f" % throughput(result),
1641
- "%9d" % result.n_tests,
1642
- "%9d" % result.n_passed_tests,
1643
- "%9d" % result.n_failed_tests,
1644
- "%9d" % result.n_leaked_tests,
1645
- "%9d" % result.n_not_checked_tests,
1646
- ]
1696
+ items = columns.collect do |label, format_value|
1697
+ format_value.call(result)
1698
+ end
1647
1699
  " " + items.join(" | ") + " |"
1648
1700
  end
1649
1701
 
@@ -1764,7 +1816,9 @@ EOF
1764
1816
  end
1765
1817
 
1766
1818
  def guess_term_width_from_stty
1767
- case `stty -a`
1819
+ return nil unless STDIN.tty?
1820
+
1821
+ case tty_info
1768
1822
  when /(\d+) columns/
1769
1823
  $1
1770
1824
  when /columns (\d+)/
@@ -1772,8 +1826,14 @@ EOF
1772
1826
  else
1773
1827
  nil
1774
1828
  end
1775
- rescue SystemCallError
1776
- nil
1829
+ end
1830
+
1831
+ def tty_info
1832
+ begin
1833
+ `stty -a`
1834
+ rescue SystemCallError
1835
+ nil
1836
+ end
1777
1837
  end
1778
1838
 
1779
1839
  def string_width(string)
@@ -1788,6 +1848,8 @@ EOF
1788
1848
  :failure
1789
1849
  elsif result.n_leaked_tests > 0
1790
1850
  :leaked
1851
+ elsif result.n_omitted_tests > 0
1852
+ :omitted
1791
1853
  elsif result.n_not_checked_tests > 0
1792
1854
  :not_checked
1793
1855
  else
@@ -1810,6 +1872,8 @@ EOF
1810
1872
  "%s%s%s" % [failure_color, message, reset_color]
1811
1873
  when :leaked
1812
1874
  "%s%s%s" % [leaked_color, message, reset_color]
1875
+ when :omitted
1876
+ "%s%s%s" % [omitted_color, message, reset_color]
1813
1877
  when :not_checked
1814
1878
  "%s%s%s" % [not_checked_color, message, reset_color]
1815
1879
  else
@@ -1856,6 +1920,19 @@ EOF
1856
1920
  })
1857
1921
  end
1858
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
+
1859
1936
  def not_checked_color
1860
1937
  escape_sequence({
1861
1938
  :color => :cyan,
@@ -1930,25 +2007,25 @@ EOF
1930
2007
  super
1931
2008
  end
1932
2009
 
1933
- def start(result)
2010
+ def on_start(result)
1934
2011
  end
1935
2012
 
1936
- def start_worker(worker)
2013
+ def on_worker_start(worker)
1937
2014
  end
1938
2015
 
1939
- def start_suite(worker)
2016
+ def on_suite_start(worker)
1940
2017
  end
1941
2018
 
1942
- def start_test(worker)
2019
+ def on_test_start(worker)
1943
2020
  end
1944
2021
 
1945
- def pass_test(worker, result)
2022
+ def on_test_success(worker, result)
1946
2023
  synchronize do
1947
2024
  report_test_result_mark(".", result)
1948
2025
  end
1949
2026
  end
1950
2027
 
1951
- def fail_test(worker, result)
2028
+ def on_test_failure(worker, result)
1952
2029
  synchronize do
1953
2030
  report_test_result_mark("F", result)
1954
2031
  puts
@@ -1957,13 +2034,22 @@ EOF
1957
2034
  end
1958
2035
  end
1959
2036
 
1960
- def leaked_test(worker, result)
2037
+ def on_test_leak(worker, result)
1961
2038
  synchronize do
1962
2039
  report_test_result_mark("L(#{result.n_leaked_objects})", result)
1963
2040
  end
1964
2041
  end
1965
2042
 
1966
- def not_checked_test(worker, result)
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)
1967
2053
  synchronize do
1968
2054
  report_test_result_mark("N", result)
1969
2055
  puts
@@ -1972,16 +2058,16 @@ EOF
1972
2058
  end
1973
2059
  end
1974
2060
 
1975
- def finish_test(worker, result)
2061
+ def on_test_finish(worker, result)
1976
2062
  end
1977
2063
 
1978
- def finish_suite(worker)
2064
+ def on_suite_finish(worker)
1979
2065
  end
1980
2066
 
1981
- def finish_worker(worker_id)
2067
+ def on_worker_finish(worker_id)
1982
2068
  end
1983
2069
 
1984
- def finish(result)
2070
+ def on_finish(result)
1985
2071
  puts
1986
2072
  puts
1987
2073
  report_summary(result)
@@ -2006,13 +2092,13 @@ EOF
2006
2092
  super
2007
2093
  end
2008
2094
 
2009
- def start(result)
2095
+ def on_start(result)
2010
2096
  end
2011
2097
 
2012
- def start_worker(worker)
2098
+ def on_worker_start(worker)
2013
2099
  end
2014
2100
 
2015
- def start_suite(worker)
2101
+ def on_suite_start(worker)
2016
2102
  if worker.suite_name.bytesize <= @term_width
2017
2103
  puts(worker.suite_name)
2018
2104
  else
@@ -2021,39 +2107,44 @@ EOF
2021
2107
  @output.flush
2022
2108
  end
2023
2109
 
2024
- def start_test(worker)
2110
+ def on_test_start(worker)
2025
2111
  print(" #{worker.test_name}")
2026
2112
  @output.flush
2027
2113
  end
2028
2114
 
2029
- def pass_test(worker, result)
2115
+ def on_test_success(worker, result)
2030
2116
  report_test_result(result, worker.status)
2031
2117
  end
2032
2118
 
2033
- def fail_test(worker, result)
2119
+ def on_test_failure(worker, result)
2034
2120
  report_test_result(result, worker.status)
2035
2121
  report_failure(result)
2036
2122
  end
2037
2123
 
2038
- def leaked_test(worker, result)
2124
+ def on_test_leak(worker, result)
2125
+ report_test_result(result, worker.status)
2126
+ end
2127
+
2128
+ def on_test_omission(worker, result)
2039
2129
  report_test_result(result, worker.status)
2130
+ report_actual(result)
2040
2131
  end
2041
2132
 
2042
- def not_checked_test(worker, result)
2133
+ def on_test_no_check(worker, result)
2043
2134
  report_test_result(result, worker.status)
2044
2135
  report_actual(result)
2045
2136
  end
2046
2137
 
2047
- def finish_test(worker, result)
2138
+ def on_test_finish(worker, result)
2048
2139
  end
2049
2140
 
2050
- def finish_suite(worker)
2141
+ def on_suite_finish(worker)
2051
2142
  end
2052
2143
 
2053
- def finish_worker(worker_id)
2144
+ def on_worker_finish(worker_id)
2054
2145
  end
2055
2146
 
2056
- def finish(result)
2147
+ def on_finish(result)
2057
2148
  puts
2058
2149
  report_summary(result)
2059
2150
  end
@@ -2066,59 +2157,66 @@ EOF
2066
2157
  @minimum_redraw_interval = 0.1
2067
2158
  end
2068
2159
 
2069
- def start(result)
2160
+ def on_start(result)
2070
2161
  @test_suites_result = result
2071
2162
  end
2072
2163
 
2073
- def start_worker(worker)
2164
+ def on_worker_start(worker)
2074
2165
  end
2075
2166
 
2076
- def start_suite(worker)
2167
+ def on_suite_start(worker)
2077
2168
  redraw
2078
2169
  end
2079
2170
 
2080
- def start_test(worker)
2171
+ def on_test_start(worker)
2081
2172
  redraw
2082
2173
  end
2083
2174
 
2084
- def pass_test(worker, result)
2175
+ def on_test_success(worker, result)
2085
2176
  redraw
2086
2177
  end
2087
2178
 
2088
- def fail_test(worker, result)
2179
+ def on_test_failure(worker, result)
2089
2180
  redraw do
2090
2181
  report_test(worker, result)
2091
2182
  report_failure(result)
2092
2183
  end
2093
2184
  end
2094
2185
 
2095
- def leaked_test(worker, result)
2186
+ def on_test_leak(worker, result)
2096
2187
  redraw do
2097
2188
  report_test(worker, result)
2098
2189
  report_marker(result)
2099
2190
  end
2100
2191
  end
2101
2192
 
2102
- def not_checked_test(worker, result)
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)
2103
2201
  redraw do
2104
2202
  report_test(worker, result)
2105
2203
  report_actual(result)
2106
2204
  end
2107
2205
  end
2108
2206
 
2109
- def finish_test(worker, result)
2207
+ def on_test_finish(worker, result)
2110
2208
  redraw
2111
2209
  end
2112
2210
 
2113
- def finish_suite(worker)
2211
+ def on_suite_finish(worker)
2114
2212
  redraw
2115
2213
  end
2116
2214
 
2117
- def finish_worker(worker)
2215
+ def on_worker_finish(worker)
2118
2216
  redraw
2119
2217
  end
2120
2218
 
2121
- def finish(result)
2219
+ def on_finish(result)
2122
2220
  draw
2123
2221
  puts
2124
2222
  report_summary(result)