seeing_is_believing 3.1.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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