seeing_is_believing 3.1.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/Rakefile +43 -16
  4. data/Readme.md +27 -242
  5. data/appveyor.yml +29 -0
  6. data/bin/seeing_is_believing +2 -1
  7. data/features/deprecated-flags.feature +19 -0
  8. data/features/errors.feature +57 -0
  9. data/features/examples.feature +4 -2
  10. data/features/flags.feature +35 -2
  11. data/features/regression.feature +107 -10
  12. data/features/support/env.rb +61 -2
  13. data/features/xmpfilter-style.feature +3 -2
  14. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +11 -9
  15. data/lib/seeing_is_believing/binary/annotate_every_line.rb +9 -8
  16. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +9 -8
  17. data/lib/seeing_is_believing/binary/config.rb +19 -3
  18. data/lib/seeing_is_believing/binary/engine.rb +5 -10
  19. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +60 -45
  20. data/lib/seeing_is_believing/event_stream/consumer.rb +6 -1
  21. data/lib/seeing_is_believing/event_stream/handlers/debug.rb +5 -1
  22. data/lib/seeing_is_believing/event_stream/producer.rb +1 -1
  23. data/lib/seeing_is_believing/hard_core_ensure.rb +6 -0
  24. data/lib/seeing_is_believing/result.rb +26 -14
  25. data/lib/seeing_is_believing/safe.rb +6 -1
  26. data/lib/seeing_is_believing/the_matrix.rb +16 -4
  27. data/lib/seeing_is_believing/version.rb +1 -1
  28. data/lib/seeing_is_believing/wrap_expressions.rb +1 -1
  29. data/lib/seeing_is_believing.rb +7 -10
  30. data/seeing_is_believing.gemspec +9 -8
  31. data/spec/binary/config_spec.rb +65 -4
  32. data/spec/binary/engine_spec.rb +1 -1
  33. data/spec/evaluate_by_moving_files_spec.rb +31 -5
  34. data/spec/event_stream_spec.rb +14 -6
  35. data/spec/hard_core_ensure_spec.rb +70 -44
  36. data/spec/seeing_is_believing_spec.rb +136 -42
  37. data/spec/spec_helper.rb +8 -0
  38. data/spec/wrap_expressions_spec.rb +15 -0
  39. metadata +21 -6
@@ -1,6 +1,10 @@
1
1
  class SeeingIsBelieving
2
2
  module EventStream
3
3
  module Handlers
4
+ # Even though the debugger can be disabled, which would push the decision of
5
+ # whether to report or not into the debugger where it belongs, you should still
6
+ # avoid using this class if you don't need it since it is expensive and there
7
+ # could be tens of millions of events, eg https://github.com/JoshCheek/seeing_is_believing/issues/12
4
8
  class Debug
5
9
  def initialize(debugger, handler)
6
10
  @debugger = debugger
@@ -22,7 +26,7 @@ class SeeingIsBelieving
22
26
  attr_reader :debugger, :handler
23
27
 
24
28
  def finish
25
- @debugger.context("EVENTS:") { @seen }
29
+ @debugger.context("EVENTS") { @seen }
26
30
  end
27
31
 
28
32
  def observe(event)
@@ -9,7 +9,7 @@ class SeeingIsBelieving
9
9
  class Producer
10
10
  module NullQueue
11
11
  extend self
12
- Queue.instance_methods.each do |name|
12
+ Queue.instance_methods(false).each do |name|
13
13
  define_method(name) { |*| }
14
14
  end
15
15
  end
@@ -25,6 +25,7 @@ class SeeingIsBelieving
25
25
  invoke_ensure
26
26
  Process.kill 'INT', $$
27
27
  end
28
+ trap 'INT', old_handler if ignore_interrupt? old_handler
28
29
  end
29
30
 
30
31
  def invoke_code
@@ -48,5 +49,10 @@ class SeeingIsBelieving
48
49
  raise ArgumentError, "Unknown keys: #{unknown_keys.map(&:inspect).join(', ')}"
49
50
  end
50
51
  end
52
+
53
+ def ignore_interrupt?(interrupt_handler)
54
+ # any handler that ignores gets normalized to IGNORE
55
+ interrupt_handler == 'IGNORE'
56
+ end
51
57
  end
52
58
  end
@@ -3,14 +3,17 @@ class SeeingIsBelieving
3
3
  include Enumerable
4
4
  RecordedException = Struct.new :line_number, :class_name, :message, :backtrace
5
5
 
6
- attr_accessor :stdout, :stderr, :exitstatus, :max_line_captures, :exception, :num_lines, :sib_version, :ruby_version, :filename, :timeout_seconds
6
+ attr_accessor :stdout, :stderr, :exitstatus, :max_line_captures, :exceptions, :num_lines, :sib_version, :ruby_version, :filename, :timeout_seconds
7
7
 
8
- def initialize
9
- self.stdout = ''
10
- self.stderr = ''
8
+ def has_exception?
9
+ exceptions.any?
11
10
  end
12
11
 
13
- alias has_exception? exception
12
+ def initialize
13
+ self.stdout = ''
14
+ self.stderr = ''
15
+ self.exceptions = []
16
+ end
14
17
 
15
18
  def has_stdout?
16
19
  stdout && !stdout.empty?
@@ -30,7 +33,11 @@ class SeeingIsBelieving
30
33
  end
31
34
 
32
35
  def record_exception(line_number, exception_class, exception_message, exception_backtrace)
33
- self.exception = RecordedException.new line_number, exception_class, exception_message, exception_backtrace
36
+ self.exceptions << RecordedException.new(line_number, exception_class, exception_message, exception_backtrace)
37
+ end
38
+
39
+ def exception
40
+ exceptions.first
34
41
  end
35
42
 
36
43
  def [](line_number, type=:inspect)
@@ -47,17 +54,13 @@ class SeeingIsBelieving
47
54
  end
48
55
 
49
56
  def as_json
50
- ex = has_exception? && {
51
- line_number_in_this_file: exception.line_number,
52
- class_name: exception.class_name,
53
- message: exception.message,
54
- backtrace: exception.backtrace,
55
- }
56
-
57
+ # We have both an exception and a list of exceptions because multiple exceptions
58
+ # weren't added until #85, and I don't want to break backwards compatibility right now.
57
59
  { stdout: stdout,
58
60
  stderr: stderr,
59
61
  exitstatus: exitstatus,
60
- exception: ex,
62
+ exception: exception_json(exception),
63
+ exceptions: exceptions.map { |e| exception_json e },
61
64
  lines: each.with_object(Hash.new)
62
65
  .with_index(1) { |(result, hash), line_number| hash[line_number] = result },
63
66
  }
@@ -73,5 +76,14 @@ class SeeingIsBelieving
73
76
  def results
74
77
  @results ||= Hash.new
75
78
  end
79
+
80
+ def exception_json(exception)
81
+ return nil unless exception
82
+ { line_number_in_this_file: exception.line_number,
83
+ class_name: exception.class_name,
84
+ message: exception.message,
85
+ backtrace: exception.backtrace,
86
+ }
87
+ end
76
88
  end
77
89
  end
@@ -21,7 +21,6 @@ class SeeingIsBelieving
21
21
  refine Symbol do
22
22
  alias == ==
23
23
  alias to_s to_s
24
- alias inspect inspect
25
24
  end
26
25
 
27
26
  refine Symbol.singleton_class do
@@ -35,6 +34,12 @@ class SeeingIsBelieving
35
34
  alias to_str to_str
36
35
  end
37
36
 
37
+ # in 2.4 we should use Integer instead, but it's not obvious to me how
38
+ # to detect this. eg defined?(Fixnum) returns "constant". Accessing it
39
+ # leads to a warning, but SiB turns warnings off so you don't see it.
40
+ # So.... for now, it incidentally doesn't do anything annoying, but would
41
+ # be good to figure out something better (eg if we ever wanted to allow the
42
+ # user to decide whether warnings should display or not)
38
43
  refine Fixnum do
39
44
  alias to_s to_s
40
45
  alias next next
@@ -1,11 +1,21 @@
1
1
  require_relative 'safe'
2
2
  require_relative 'version'
3
3
  require_relative 'event_stream/producer'
4
+ require 'socket'
5
+ require 'timeout'
4
6
 
5
7
  using SeeingIsBelieving::Safe
6
8
 
7
9
  sib_vars = Marshal.load ENV["SIB_VARIABLES.MARSHAL.B64"].unpack('m0').first
8
- event_stream = IO.open sib_vars.fetch(:event_stream_fd), "w"
10
+ event_stream = Timeout.timeout(1) do
11
+ begin
12
+ Socket.tcp("localhost", sib_vars.fetch(:event_stream_port))
13
+ rescue Errno::ECONNREFUSED
14
+ sleep 0.1
15
+ retry
16
+ end
17
+ end
18
+
9
19
  $SiB = SeeingIsBelieving::EventStream::Producer.new(event_stream)
10
20
  $SiB.record_ruby_version RUBY_VERSION
11
21
  $SiB.record_sib_version SeeingIsBelieving::VERSION
@@ -14,13 +24,16 @@ $SiB.record_num_lines sib_vars.fetch(:num_lines)
14
24
  $SiB.record_max_line_captures sib_vars.fetch(:max_line_captures)
15
25
 
16
26
  STDOUT.sync = true
27
+ STDOUT.binmode
28
+ STDERR.binmode
29
+ STDIN.set_encoding "utf-8"
17
30
  stdout, stderr = STDOUT, STDERR
18
31
 
19
32
  finish = lambda do
20
33
  $SiB.finish!
21
34
  event_stream.close
22
- stdout.flush
23
- stderr.flush
35
+ stdout.flush unless stdout.closed?
36
+ stderr.flush unless stderr.closed?
24
37
  end
25
38
 
26
39
  real_exec = method :exec
@@ -31,7 +44,6 @@ fork_defn = lambda do |*args|
31
44
  $SiB.send :forking_occurred_and_you_are_the_child, event_stream unless result
32
45
  result
33
46
  end
34
-
35
47
  Kernel.module_eval do
36
48
  private
37
49
 
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '3.1.1'
2
+ VERSION = '3.2.0'
3
3
  end
@@ -102,7 +102,7 @@ class SeeingIsBelieving
102
102
  def wrap_recursive(ast)
103
103
  return wrappings unless ast.kind_of? ::AST::Node
104
104
  case ast.type
105
- when :args, :redo, :retry, :alias, :undef, :null_node
105
+ when :args, :redo, :retry, :alias, :undef, :null_node, :iflipflop, :eflipflop
106
106
  # no op
107
107
  when :defs, :module
108
108
  add_to_wrappings ast
@@ -16,7 +16,7 @@ class SeeingIsBelieving
16
16
  attribute(:encoding) { nil }
17
17
  attribute(:stdin) { "" }
18
18
  attribute(:require_files) { ['seeing_is_believing/the_matrix'] }
19
- attribute(:load_path_dirs) { [File.expand_path('..', __FILE__)] }
19
+ attribute(:load_path_dirs) { [File.realpath(__dir__)] }
20
20
  attribute(:timeout_seconds) { 0 }
21
21
  attribute(:debugger) { Debugger::Null }
22
22
  attribute(:max_line_captures) { Float::INFINITY }
@@ -36,7 +36,7 @@ class SeeingIsBelieving
36
36
 
37
37
  def call
38
38
  @memoized_result ||= Dir.mktmpdir("seeing_is_believing_temp_dir") { |dir|
39
- filename = options.filename || File.join(dir, 'program.rb')
39
+ filename = options.filename || File.join(dir, 'program.rb')
40
40
  new_program = options.rewrite_code.call @program
41
41
 
42
42
  options.debugger.context("REWRITTEN PROGRAM") { new_program }
@@ -44,7 +44,7 @@ class SeeingIsBelieving
44
44
  EvaluateByMovingFiles.call \
45
45
  new_program,
46
46
  filename,
47
- event_handler: debugging_handler,
47
+ event_handler: event_handler(options.debugger, options.event_handler),
48
48
  provided_input: options.stdin,
49
49
  require_files: options.require_files,
50
50
  load_path_dirs: options.load_path_dirs,
@@ -58,12 +58,9 @@ class SeeingIsBelieving
58
58
 
59
59
  private
60
60
 
61
- # Even though the debugger can be disabled,
62
- # Handlers::Debug is somewhat expensive, and there could be tens of millions of calls
63
- # e.g. https://github.com/JoshCheek/seeing_is_believing/issues/12
64
- # so just skip it in this case
65
- def debugging_handler
66
- return options.event_handler unless options.debugger.enabled?
67
- EventStream::Handlers::Debug.new options.debugger, options.event_handler
61
+ # If we need debugging, wrap a debugging handler around the current handler
62
+ def event_handler(debugger, current_handler)
63
+ return current_handler unless debugger.enabled?
64
+ EventStream::Handlers::Debug.new debugger, current_handler
68
65
  end
69
66
  end
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
2
+ $:.push File.realpath("lib", __dir__)
3
3
  require "seeing_is_believing/version"
4
4
 
5
5
  Gem::Specification.new do |s|
@@ -19,14 +19,15 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency "parser", ">= 2.3.0.7", "< 3.0"
22
+ s.add_dependency "parser", ">= 2.3.0.7", "< 3.0"
23
+ s.add_dependency "childprocess","~> 0.5.9"
23
24
 
24
- s.add_development_dependency "haiti", ">= 0.1", "< 0.3"
25
- s.add_development_dependency "rake", "~> 11.2.2"
26
- s.add_development_dependency "mrspec", "~> 0.3.1"
27
- s.add_development_dependency "rspec", "~> 3.5"
28
- s.add_development_dependency "cucumber", "~> 2.4"
29
- s.add_development_dependency "ichannel", "~> 8.1"
25
+ s.add_development_dependency "haiti", ">= 0.1", "< 0.3"
26
+ s.add_development_dependency "rake", "~> 11.2.2"
27
+ s.add_development_dependency "mrspec", "~> 0.3.1"
28
+ s.add_development_dependency "rspec", "~> 3.5"
29
+ s.add_development_dependency "cucumber", "~> 2.4"
30
+ s.add_development_dependency "ripper-tags", "~> 0.3"
30
31
 
31
32
  s.post_install_message = <<'Omg, frogs <3'.gsub(/(gg+)/) { |capture| "\e[32m#{capture.gsub 'g', '.'}\e[0m" }.gsub("brown", "\e[33m").gsub("off", "\e[0m")
32
33
  .7
@@ -69,6 +69,21 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
69
69
  end
70
70
  end
71
71
 
72
+ shared_examples 'it can extract its argument from conjoined shortflags' do |flag, arg, verify|
73
+ it 'can be the only item (the argument has no space)' do
74
+ instance_exec parse(%W[-#{flag}#{arg}]), &verify
75
+
76
+ # sanity check the verifier by giving it someting it should fail on
77
+ expect { instance_exec parse(%W[-#{flag}#{arg}X]), &verify }.to raise_error RSpec::Expectations::ExpectationNotMetError
78
+ end
79
+
80
+ it 'can appear after another flag' do
81
+ expect( parse(%W[-#{flag}#{arg}] )[:debug]).to eq false
82
+ expect( parse(%W[-g#{flag}#{arg}] )[:debug]).to eq true
83
+ instance_exec parse(%W[-g#{flag}#{arg}] ), &verify
84
+ end
85
+ end
86
+
72
87
  describe 'parsing from args' do
73
88
  it 'does not mutate the input array' do
74
89
  ary = ['a']
@@ -186,6 +201,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
186
201
  expect(parse(%w[-a ])).to have_error /-a/
187
202
  expect(parse(%w[--as ])).to have_error /--as/
188
203
  end
204
+
205
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'a', 'some-filename.whatever', -> parsed do
206
+ expect(parsed.lib_options.filename).to eq 'some-filename.whatever'
207
+ end
189
208
  end
190
209
 
191
210
 
@@ -200,6 +219,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
200
219
  end
201
220
 
202
221
  it_behaves_like 'it requires a positive int argument', ['-D', '--result-length']
222
+
223
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'D', '12', -> parsed do
224
+ expect(parsed.annotator_options.max_result_length).to eq 12
225
+ end
203
226
  end
204
227
 
205
228
  describe 'annotator_options.max_line_length' do
@@ -213,6 +236,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
213
236
  end
214
237
 
215
238
  it_behaves_like 'it requires a positive int argument', ['-d', '--line-length']
239
+
240
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'd', '12', -> parsed do
241
+ expect(parsed.annotator_options.max_line_length).to eq 12
242
+ end
216
243
  end
217
244
 
218
245
  describe 'lib_options.require_files' do
@@ -233,6 +260,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
233
260
  specify '-r and --require add the filename into the result array' do
234
261
  expect(parse(%w[-r f1 --require f2]).lib_options.require_files).to eq [matrix_file, 'f1', 'f2']
235
262
  end
263
+
264
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'r', 'filename', -> parsed do
265
+ expect(parsed.lib_options.require_files).to include 'filename'
266
+ end
236
267
  end
237
268
 
238
269
  describe 'print_help? and help_screen' do
@@ -284,10 +315,14 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
284
315
  expect(parse(['--program', 'body'])).to_not have_error /--program/
285
316
  expect(parse(['--program' ])).to have_error /--program/
286
317
  end
318
+
319
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'e', 'some program', -> parsed do
320
+ expect(parsed.body).to eq 'some program'
321
+ end
287
322
  end
288
323
 
289
324
  describe'lib_options.load_path_dirs' do
290
- let(:lib_path) { File.expand_path '../../../lib', __FILE__ }
325
+ let(:lib_path) { File.realpath '../../lib', __dir__ }
291
326
 
292
327
  it 'defaults to sib\'s lib path' do
293
328
  expect(parse([]).lib_options.load_path_dirs).to eq [lib_path]
@@ -302,6 +337,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
302
337
  expect(parse(['-I'])).to have_error /-I\b/
303
338
  expect(parse(['--load-path'])).to have_error /--load-path\b/
304
339
  end
340
+
341
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'I', 'added-path', -> parsed do
342
+ expect(parsed.lib_options.load_path_dirs).to include 'added-path'
343
+ end
305
344
  end
306
345
 
307
346
  describe 'lib_options.encoding' do
@@ -326,6 +365,16 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
326
365
  expect(parse(['-K'])).to have_error /-K/
327
366
  expect(parse(['--encoding'])).to have_error /--encoding/
328
367
  end
368
+
369
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'K', 'u', -> parsed do
370
+ expect(parsed.lib_options.encoding).to eq 'u'
371
+ end
372
+
373
+ it 'is deprecated' do
374
+ assert_deprecated '--encoding', 'u'
375
+ assert_deprecated '-K', 'u'
376
+ assert_deprecated '-Ku'
377
+ end
329
378
  end
330
379
 
331
380
  describe '.print_cleaned?' do
@@ -368,6 +417,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
368
417
  end
369
418
 
370
419
  it_behaves_like 'it requires a non-negative float or int', ['-t', '--timeout-seconds', '--timeout']
420
+
421
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 't', '123', -> parsed do
422
+ expect(parsed.lib_options.timeout_seconds).to eq 123
423
+ end
371
424
  end
372
425
 
373
426
  describe 'annotator_options.alignment_strategy' do
@@ -403,6 +456,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
403
456
  expect(parse(['-s', 'file'])).to_not have_error '-s'
404
457
  expect(parse(['-s', 'unknown'])).to have_error '-s', 'expected one of'
405
458
  end
459
+
460
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 's', 'file', -> parsed do
461
+ expect(parsed.annotator_options.alignment_strategy).to eq align_file
462
+ end
406
463
  end
407
464
 
408
465
  describe 'inherit_exitstatus?' do
@@ -499,6 +556,10 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
499
556
  end
500
557
 
501
558
  it_behaves_like 'it requires a positive int argument', ['-n', '--max-line-captures', '--number-of-captures']
559
+
560
+ it_behaves_like 'it can extract its argument from conjoined shortflags', 'n', '12', -> parsed do
561
+ expect(parsed.lib_options.max_line_captures).to eq 12
562
+ end
502
563
  end
503
564
 
504
565
  describe 'result_as_json?' do
@@ -580,7 +641,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
580
641
 
581
642
  describe '.finalize' do
582
643
  let(:stdin_data) { 'stdin data' }
583
- let(:stdin) { object_double $stdin, read: stdin_data }
644
+ let(:stdin) { object_double $stdin, read: stdin_data, set_encoding: nil }
584
645
  let(:stdout) { object_double $stdout }
585
646
  let(:stderr) { object_double $stderr, :tty? => true }
586
647
 
@@ -592,7 +653,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
592
653
  before do
593
654
  allow(file_class).to receive(:exist?).with(existing_filename).and_return(true)
594
655
  allow(file_class).to receive(:exist?).with(nonexisting_filename).and_return(false)
595
- allow(file_class).to receive(:read).with(existing_filename).and_return(file_body)
656
+ allow(file_class).to receive(:read).with(existing_filename, any_args).and_return(file_body)
596
657
  end
597
658
 
598
659
  def call(attrs={})
@@ -715,7 +776,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
715
776
  end
716
777
 
717
778
  context 'when debug? is a string' do
718
- let(:proving_grounds_dir) { File.expand_path '../../../proving_grounds', __FILE__ }
779
+ let(:proving_grounds_dir) { File.realdirpath '../../proving_grounds', __dir__ }
719
780
  let(:path) { File.join proving_grounds_dir, 'test.log' }
720
781
  before { Dir.mkdir proving_grounds_dir unless Dir.exist? proving_grounds_dir }
721
782
 
@@ -14,7 +14,7 @@ class SeeingIsBelieving
14
14
  end
15
15
 
16
16
  def assert_must_evaluate(message)
17
- engine = call ''
17
+ engine = call '1+1'
18
18
  expect { engine.__send__ message }.to raise_error MustEvaluateFirst, /#{message}/
19
19
  engine.evaluate!
20
20
  engine.__send__ message
@@ -4,9 +4,10 @@ require 'spec_helper'
4
4
  require 'seeing_is_believing/evaluate_by_moving_files'
5
5
  require 'seeing_is_believing/event_stream/handlers/update_result'
6
6
  require 'fileutils'
7
+ require 'childprocess'
7
8
 
8
9
  RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
9
- let(:filedir) { File.expand_path '../../proving_grounds', __FILE__ }
10
+ let(:filedir) { File.realdirpath '../proving_grounds', __dir__ }
10
11
  let(:filename) { File.join filedir, 'some_filename' }
11
12
 
12
13
  before { FileUtils.mkdir_p filedir }
@@ -143,15 +144,40 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
143
144
  end
144
145
 
145
146
  it 'can set a timeout, which interrupts the process group and then waits for the events to finish' do
146
- expect(Timeout).to receive(:timeout).with(123).and_raise(Timeout::Error)
147
- expect(Process).to receive(:kill).with("-INT", an_instance_of(Fixnum))
148
- result = invoke 'p gets', timeout_seconds: 123
147
+ pre = Time.now
148
+ result = invoke <<-RUBY, timeout_seconds: 0.5
149
+ child_pid = spawn 'ruby', '-e', 'sleep' # child makes a grandchild which sleeps
150
+ puts Process.pid, child_pid # print ids so we can check they got killed
151
+ sleep # child sleeps
152
+ RUBY
153
+ post = Time.now
149
154
  expect(result.timeout?).to eq true
150
- expect(result.timeout_seconds).to eq 123
155
+ expect(result.timeout_seconds).to eq 0.5
156
+ expect(post - pre).to be > 0.5
157
+ child_id, grandchild_id, *rest = result.stdout.lines
158
+ expect(child_id).to match /^\d+$/
159
+ expect(grandchild_id).to match /^\d+$/
160
+ expect(rest).to be_empty
161
+ expect { Process.wait child_id.to_i } .to raise_error /no.*processes/i
162
+ expect { Process.wait grandchild_id.to_i } .to raise_error /no.*processes/i
151
163
  end
152
164
 
153
165
  it 'raises an ArgumentError if given arguments it doesn\'t know' do
154
166
  expect { invoke '1', watisthis: :idontknow }
155
167
  .to raise_error ArgumentError, /watisthis/
156
168
  end
169
+
170
+ it 'doesn\'t explode or do anything else obnoxious when the input stream is closed' do
171
+ infinite_string = Object.new
172
+ def infinite_string.each_char
173
+ loop { yield 'c' }
174
+ end
175
+ result = nil
176
+ expect {
177
+ result = invoke '$stdin.close', provided_input: infinite_string
178
+ }.to_not output.to_stderr
179
+ expect(result.exitstatus).to eq 0
180
+ expect(result.stderr).to be_empty
181
+ expect(result.stdout).to be_empty
182
+ end
157
183
  end
@@ -24,9 +24,9 @@ module SeeingIsBelieving::EventStream
24
24
  end
25
25
 
26
26
  before do
27
- self.eventstream_consumer, self.eventstream_producer = IO.pipe
28
- self.stdout_consumer, self.stdout_producer = IO.pipe
29
- self.stderr_consumer, self.stderr_producer = IO.pipe
27
+ self.eventstream_consumer, self.eventstream_producer = IO.pipe("utf-8")
28
+ self.stdout_consumer, self.stdout_producer = IO.pipe("utf-8")
29
+ self.stderr_consumer, self.stderr_producer = IO.pipe("utf-8")
30
30
 
31
31
  self.producer = SeeingIsBelieving::EventStream::Producer.new eventstream_producer
32
32
  self.consumer = SeeingIsBelieving::EventStream::Consumer.new \
@@ -42,9 +42,8 @@ module SeeingIsBelieving::EventStream
42
42
 
43
43
  describe 'emitting an event' do
44
44
  def has_message?(io)
45
- io.read_nonblock(1) # ~> IO::EAGAINWaitReadable: Resource temporarily unavailable - read would block
46
- rescue Errno::EAGAIN
47
- return false
45
+ readables, * = IO.select([io], [], [], 0.1) # 0.1 is the timeout
46
+ readables.to_a.any? # when it times out, IO.select may return nil...
48
47
  end
49
48
 
50
49
  it 'writes its events to the event stream' do
@@ -592,6 +591,15 @@ module SeeingIsBelieving::EventStream
592
591
  expect(consumer.call).to eq Events::Exitstatus.new(value: 92)
593
592
  end
594
593
 
594
+ it 'translates missing statusses to 1 (eg this happens on my machine when the program segfaults, see #100)' do
595
+ # I'm not totally sure this is the right thing for it to do, but a segfault is the only way
596
+ # I know of to invoke this situation, and a segfault is printable, so until I get some info
597
+ # that proves this is the wrong thing to do, we're just going to give it a normal exit status
598
+ # since that's the easiest thing to do, and it's more correct in this one case.
599
+ consumer.process_exitstatus nil
600
+ expect(consumer.call).to eq Events::Exitstatus.new(value: 1)
601
+ end
602
+
595
603
  it 'emits a Finished event when all streams are closed and it has an exit status' do
596
604
  consumer.process_exitstatus 1
597
605
  close_streams eventstream_producer, stdout_producer, stderr_producer