seeing_is_believing 3.0.0.beta.7 → 3.0.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.
- checksums.yaml +4 -4
- data/.travis.yml +3 -4
- data/Gemfile +1 -0
- data/Rakefile +18 -6
- data/Readme.md +79 -32
- data/features/examples.feature +4 -7
- data/lib/seeing_is_believing/binary/config.rb +13 -4
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +34 -20
- data/lib/seeing_is_believing/event_stream/producer.rb +51 -25
- data/lib/seeing_is_believing/hard_core_ensure.rb +1 -1
- data/lib/seeing_is_believing/safe.rb +43 -0
- data/lib/seeing_is_believing/the_matrix.rb +27 -2
- data/lib/seeing_is_believing/version.rb +1 -1
- data/seeing_is_believing.gemspec +5 -6
- data/spec/binary/config_spec.rb +64 -11
- data/spec/binary/marker_spec.rb +3 -3
- data/spec/evaluate_by_moving_files_spec.rb +2 -2
- data/spec/hard_core_ensure_spec.rb +2 -2
- data/spec/seeing_is_believing_spec.rb +303 -13
- metadata +16 -29
@@ -1,38 +1,29 @@
|
|
1
1
|
require 'seeing_is_believing/event_stream/events'
|
2
|
+
require 'seeing_is_believing/safe'
|
2
3
|
require 'thread'
|
3
4
|
|
4
5
|
class SeeingIsBelieving
|
5
6
|
module EventStream
|
6
7
|
class Producer
|
7
|
-
|
8
8
|
module NullQueue
|
9
9
|
extend self
|
10
10
|
def <<(*) end
|
11
11
|
def shift() end
|
12
|
+
# TODO: this one doesn't have clear, but we can call that on the real one.
|
13
|
+
# find a way to test this situation?
|
12
14
|
end
|
13
15
|
|
14
16
|
attr_accessor :max_line_captures, :filename
|
15
17
|
|
16
18
|
def initialize(resultstream)
|
19
|
+
resultstream = Safe::Stream[resultstream]
|
17
20
|
self.filename = nil
|
18
21
|
self.max_line_captures = Float::INFINITY
|
19
22
|
self.recorded_results = []
|
20
|
-
self.queue = Queue.new
|
21
|
-
self.producer_thread = Thread
|
22
|
-
|
23
|
-
|
24
|
-
loop do
|
25
|
-
to_publish = queue.shift
|
26
|
-
break if to_publish == :break
|
27
|
-
resultstream << (to_publish << "\n")
|
28
|
-
end
|
29
|
-
rescue IOError, Errno::EPIPE
|
30
|
-
queue.clear
|
31
|
-
ensure
|
32
|
-
self.queue = NullQueue
|
33
|
-
resultstream.flush rescue nil
|
34
|
-
end
|
35
|
-
end
|
23
|
+
self.queue = Safe::Queue[Queue.new]
|
24
|
+
self.producer_thread = Safe::Thread[
|
25
|
+
build_producer_thread(resultstream)
|
26
|
+
]
|
36
27
|
end
|
37
28
|
|
38
29
|
attr_reader :version
|
@@ -54,7 +45,7 @@ class SeeingIsBelieving
|
|
54
45
|
StackErrors = [SystemStackError]
|
55
46
|
StackErrors << Java::JavaLang::StackOverflowError if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
|
56
47
|
def record_result(type, line_number, value)
|
57
|
-
counts = recorded_results[line_number] ||= Hash.new(0)
|
48
|
+
counts = recorded_results[line_number] ||= Safe::Hash.new(0)
|
58
49
|
count = counts[type]
|
59
50
|
recorded_results[line_number][type] = count.next
|
60
51
|
if count < max_line_captures
|
@@ -82,21 +73,24 @@ class SeeingIsBelieving
|
|
82
73
|
|
83
74
|
# records the exception, returns the exitstatus for that exception
|
84
75
|
def record_exception(line_number, exception)
|
85
|
-
return exception.status if exception
|
76
|
+
return exception.status if SystemExit === exception
|
77
|
+
exception = Safe::Exception[exception]
|
86
78
|
if !line_number && filename
|
87
79
|
begin line_number = exception.backtrace.grep(/#{filename}/).first[/:\d+/][1..-1].to_i
|
88
80
|
rescue NoMethodError
|
89
81
|
end
|
90
82
|
end
|
91
83
|
line_number ||= -1
|
92
|
-
queue << [
|
84
|
+
queue << Safe::Array[[
|
93
85
|
"exception",
|
94
|
-
line_number,
|
86
|
+
Safe::Fixnum[line_number],
|
95
87
|
to_string_token(exception.class.name),
|
96
88
|
to_string_token(exception.message),
|
97
|
-
|
98
|
-
|
99
|
-
|
89
|
+
Safe::Fixnum[
|
90
|
+
Safe::Array[exception.backtrace].size
|
91
|
+
],
|
92
|
+
*Safe::Array[exception.backtrace].map { |line| to_string_token line }
|
93
|
+
]].join(" ")
|
100
94
|
1 # exit status
|
101
95
|
end
|
102
96
|
|
@@ -124,8 +118,40 @@ class SeeingIsBelieving
|
|
124
118
|
|
125
119
|
# for a consideration of many different ways of doing this, see 5633064
|
126
120
|
def to_string_token(string)
|
127
|
-
[Marshal.dump(string.to_s)].pack('m0')
|
121
|
+
Safe::Array[[Safe::Marshal.dump(Safe::String[string].to_s)]].pack('m0')
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_producer_thread(resultstream)
|
125
|
+
::Thread.new {
|
126
|
+
Safe::Thread.current.abort_on_exception = true
|
127
|
+
begin
|
128
|
+
resultstream.sync = true
|
129
|
+
loop do
|
130
|
+
to_publish = queue.shift
|
131
|
+
break if Safe::Symbol[:break] == to_publish
|
132
|
+
resultstream << (to_publish << "\n")
|
133
|
+
end
|
134
|
+
rescue IOError, Errno::EPIPE
|
135
|
+
queue.clear
|
136
|
+
ensure
|
137
|
+
self.queue = NullQueue
|
138
|
+
resultstream.flush rescue nil
|
139
|
+
end
|
140
|
+
}
|
128
141
|
end
|
142
|
+
|
143
|
+
def forking_occurred_and_you_are_the_child(resultstream)
|
144
|
+
# clear the queue b/c we don't want to report the same lines 2x,
|
145
|
+
# parent process can report them
|
146
|
+
queue << :fork
|
147
|
+
loop { break if queue.shift == :fork }
|
148
|
+
|
149
|
+
# recreate the thread since forking in Ruby kills threads
|
150
|
+
@producer_thread = Safe::Thread[
|
151
|
+
build_producer_thread(resultstream)
|
152
|
+
]
|
153
|
+
end
|
154
|
+
|
129
155
|
end
|
130
156
|
end
|
131
157
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class SeeingIsBelieving
|
2
|
+
module Safe
|
3
|
+
def self.build(klass, *method_names)
|
4
|
+
options = {}
|
5
|
+
options = method_names.pop if method_names.last.kind_of? ::Hash
|
6
|
+
|
7
|
+
Class.new do
|
8
|
+
class << self
|
9
|
+
alias [] new
|
10
|
+
end
|
11
|
+
|
12
|
+
define_method :initialize do |instance|
|
13
|
+
@_instance = instance
|
14
|
+
end
|
15
|
+
|
16
|
+
methods = method_names.map { |name| [name, klass.instance_method(name)] }
|
17
|
+
methods.each do |name, method|
|
18
|
+
define_method(name) do |*args, &block|
|
19
|
+
method.bind(@_instance).call(*args, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
singleton_methods = options.fetch(:class, []).map { |name| [name, klass.method(name)] }
|
24
|
+
singleton_methods.each do |name, method|
|
25
|
+
define_singleton_method name do |*args, &block|
|
26
|
+
method.call(*args, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
Queue = build ::Queue, :<<, :shift, :clear
|
33
|
+
Stream = build ::IO, :sync=, :<<, :flush, :close
|
34
|
+
Symbol = build ::Symbol, :==, class: [:define_method]
|
35
|
+
String = build ::String, :to_s
|
36
|
+
Fixnum = build ::Fixnum, :to_s
|
37
|
+
Array = build ::Array, :pack, :map, :size, :join
|
38
|
+
Hash = build ::Hash, :[], :[]=, class: [:new]
|
39
|
+
Marshal = build ::Marshal, class: [:dump]
|
40
|
+
Exception = build ::Exception, :message, :backtrace, :class, class: [:define_method]
|
41
|
+
Thread = build ::Thread, :join, class: [:current]
|
42
|
+
end
|
43
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative 'version'
|
2
|
+
require_relative 'safe'
|
2
3
|
require_relative 'event_stream/producer'
|
3
4
|
|
4
5
|
sib_vars = Marshal.load ENV["SIB_VARIABLES.MARSHAL.B64"].unpack('m0').first
|
@@ -11,16 +12,25 @@ $SiB.record_num_lines sib_vars.fetch(:num_lines)
|
|
11
12
|
$SiB.record_max_line_captures sib_vars.fetch(:max_line_captures)
|
12
13
|
|
13
14
|
STDOUT.sync = true
|
14
|
-
stdout
|
15
|
+
stdout = SeeingIsBelieving::Safe::Stream[STDOUT]
|
16
|
+
stderr = SeeingIsBelieving::Safe::Stream[STDERR]
|
17
|
+
|
15
18
|
finish = lambda do
|
16
19
|
$SiB.finish!
|
17
|
-
event_stream.close
|
20
|
+
SeeingIsBelieving::Safe::Stream[event_stream].close
|
18
21
|
stdout.flush
|
19
22
|
stderr.flush
|
20
23
|
end
|
21
24
|
|
22
25
|
real_exec = method :exec
|
26
|
+
real_fork = method :fork
|
23
27
|
real_exit_bang = method :exit!
|
28
|
+
fork_defn = lambda do |*args|
|
29
|
+
result = real_fork.call(*args)
|
30
|
+
$SiB.send :forking_occurred_and_you_are_the_child, event_stream unless result
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
24
34
|
Kernel.module_eval do
|
25
35
|
private
|
26
36
|
|
@@ -38,9 +48,24 @@ Kernel.module_eval do
|
|
38
48
|
finish.call
|
39
49
|
real_exit_bang.call(status)
|
40
50
|
end
|
51
|
+
|
52
|
+
define_method :fork, &fork_defn
|
41
53
|
end
|
42
54
|
|
55
|
+
Kernel.define_singleton_method :fork, &fork_defn
|
56
|
+
Process.define_singleton_method :fork, &fork_defn
|
57
|
+
|
58
|
+
|
59
|
+
# Some things need to just be recorded and readded as they are called from Ruby C code
|
60
|
+
symbol_to_s = Symbol.instance_method(:to_s)
|
61
|
+
exception_message = Exception.instance_method(:message)
|
62
|
+
# exception_backtrace = Exception.instance_method(:backtrace)
|
63
|
+
|
43
64
|
at_exit do
|
65
|
+
# SeeingIsBelieving::Safe::Exception.define_method :backtrace, exception_backtrace
|
66
|
+
SeeingIsBelieving::Safe::Exception.define_method :message, exception_message
|
67
|
+
SeeingIsBelieving::Safe::Symbol.define_method :to_s, symbol_to_s
|
68
|
+
|
44
69
|
exitstatus = ($! ? $SiB.record_exception(nil, $!) : 0)
|
45
70
|
finish.call
|
46
71
|
real_exit_bang.call(exitstatus) # clears exceptions so they don't print to stderr and change the processes actual exit status (we recorded what it should be)
|
data/seeing_is_believing.gemspec
CHANGED
@@ -21,13 +21,12 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
s.add_dependency "parser", ">= 2.3.0.7", "< 3.0"
|
23
23
|
|
24
|
-
s.add_development_dependency "what_weve_got_here_is_an_error_to_communicate"
|
25
24
|
s.add_development_dependency "haiti", ">= 0.1", "< 0.3"
|
26
|
-
s.add_development_dependency "rake", "~>
|
27
|
-
s.add_development_dependency "mrspec", "~> 0.3.
|
28
|
-
s.add_development_dependency "rspec", "~> 3.
|
29
|
-
s.add_development_dependency "cucumber", "~>
|
30
|
-
s.add_development_dependency "ichannel", "~>
|
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"
|
31
30
|
|
32
31
|
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")
|
33
32
|
.7
|
data/spec/binary/config_spec.rb
CHANGED
@@ -447,14 +447,26 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
447
447
|
end
|
448
448
|
end
|
449
449
|
|
450
|
-
describe 'debug
|
451
|
-
specify 'debug
|
450
|
+
describe '-g and --debug' do
|
451
|
+
specify 'set debug to true' do
|
452
|
+
expect(parse([])[:debug]).to eq false
|
453
|
+
expect(parse([]).debug?).to eq false
|
454
|
+
expect(parse(['-g'])[:debug]).to eq true
|
455
|
+
expect(parse(['-g']).debug?).to eq true
|
456
|
+
expect(parse(['--debug'])[:debug]).to eq true
|
457
|
+
expect(parse(['--debug']).debug?).to eq true
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
describe '--debug-to FILE' do
|
462
|
+
specify 'sets debug to FILE' do
|
452
463
|
expect(parse([])[:debug]).to eq false
|
464
|
+
expect(parse(['--debug-to', 'somefile'])[:debug]).to eq 'somefile'
|
453
465
|
end
|
454
466
|
|
455
|
-
specify '
|
456
|
-
expect(parse(['-
|
457
|
-
expect(parse(['--debug'])
|
467
|
+
specify 'sets an error if not given a next arg for the filename' do
|
468
|
+
expect(parse(['--debug-to', 'somefile'])).to_not have_error /--debug-to/
|
469
|
+
expect(parse(['--debug-to'])).to have_error /--debug-to/
|
458
470
|
end
|
459
471
|
end
|
460
472
|
|
@@ -555,7 +567,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
555
567
|
let(:stdin_data) { 'stdin data' }
|
556
568
|
let(:stdin) { object_double $stdin, read: stdin_data }
|
557
569
|
let(:stdout) { object_double $stdout }
|
558
|
-
let(:stderr) { object_double $stderr }
|
570
|
+
let(:stderr) { object_double $stderr, :tty? => true }
|
559
571
|
|
560
572
|
let(:file_class) { class_double File }
|
561
573
|
let(:nonexisting_filename) { 'badfilename' }
|
@@ -663,11 +675,52 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
|
|
663
675
|
expect(handler.lib_options.debugger).to_not be_enabled
|
664
676
|
end
|
665
677
|
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
678
|
+
context 'when debug? is true' do
|
679
|
+
specify 'are the same debugger' do
|
680
|
+
handler = call debug: true
|
681
|
+
expect(handler.lib_options.debugger).to equal handler.debugger
|
682
|
+
end
|
683
|
+
|
684
|
+
specify 'print their output to stderr' do
|
685
|
+
handler = call debug: true
|
686
|
+
expect(handler.debugger).to be_enabled
|
687
|
+
expect(handler.debugger.stream).to eq stderr
|
688
|
+
end
|
689
|
+
|
690
|
+
specify 'turn colour on if stderr is a tty' do
|
691
|
+
handler = call debug: true
|
692
|
+
expect(handler.debugger).to be_coloured
|
693
|
+
end
|
694
|
+
|
695
|
+
specify 'turn colour off if stderr isn\'t a tty' do
|
696
|
+
allow(stderr).to receive(:tty?).and_return(false)
|
697
|
+
handler = call debug: true
|
698
|
+
expect(handler.debugger).to_not be_coloured
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
context 'when debug? is a string' do
|
703
|
+
let(:proving_grounds_dir) { File.expand_path '../../../proving_grounds', __FILE__ }
|
704
|
+
let(:path) { File.join proving_grounds_dir, 'test.log' }
|
705
|
+
before { Dir.mkdir proving_grounds_dir unless Dir.exist? proving_grounds_dir }
|
706
|
+
|
707
|
+
specify 'are the same debugger' do
|
708
|
+
handler = call debug: path
|
709
|
+
expect(handler.lib_options.debugger).to equal handler.debugger
|
710
|
+
end
|
711
|
+
|
712
|
+
specify 'consider the string to be a filename, appending to it' do
|
713
|
+
File.write path, "pre"
|
714
|
+
debugger = call(debug: path).debugger
|
715
|
+
expect(debugger.stream.path).to eq path
|
716
|
+
debugger.context("test")
|
717
|
+
expect(File.read path).to match /pretest/
|
718
|
+
end
|
719
|
+
|
720
|
+
specify 'do not turn colour on' do
|
721
|
+
handler = call debug: path
|
722
|
+
expect(handler.debugger).to_not be_coloured
|
723
|
+
end
|
671
724
|
end
|
672
725
|
end
|
673
726
|
end
|
data/spec/binary/marker_spec.rb
CHANGED
@@ -47,9 +47,9 @@ RSpec.describe SeeingIsBelieving::Binary::Marker do
|
|
47
47
|
|
48
48
|
it 'requires prefix and a regex' do
|
49
49
|
described_class.new prefix: '', regex: //
|
50
|
-
expect { described_class.new }.to raise_error
|
51
|
-
expect { described_class.new prefix: '' }.to raise_error
|
52
|
-
expect { described_class.new regex: // }.to raise_error
|
50
|
+
expect { described_class.new }.to raise_error ArgumentError
|
51
|
+
expect { described_class.new prefix: '' }.to raise_error ArgumentError
|
52
|
+
expect { described_class.new regex: // }.to raise_error ArgumentError
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'stores the prefix and a regex' do
|
@@ -142,9 +142,9 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
|
|
142
142
|
expect(seen.last).to eq SeeingIsBelieving::EventStream::Events::Finished.new
|
143
143
|
end
|
144
144
|
|
145
|
-
it 'can set a timeout, which
|
145
|
+
it 'can set a timeout, which interrupts the process group and then waits for the events to finish' do
|
146
146
|
expect(Timeout).to receive(:timeout).with(123).and_raise(Timeout::Error)
|
147
|
-
expect(Process).to receive(:kill).with("
|
147
|
+
expect(Process).to receive(:kill).with("-INT", an_instance_of(Fixnum))
|
148
148
|
result = invoke 'p gets', timeout_seconds: 123
|
149
149
|
expect(result.timeout?).to eq true
|
150
150
|
expect(result.timeout_seconds).to eq 123
|
@@ -43,7 +43,7 @@ RSpec.describe SeeingIsBelieving::HardCoreEnsure do
|
|
43
43
|
|
44
44
|
it 'invokes the code even if an interrupt is sent and there is a default handler' do
|
45
45
|
test = lambda do
|
46
|
-
channel = IChannel.
|
46
|
+
channel = IChannel.unix
|
47
47
|
pid = fork do
|
48
48
|
old_handler = trap('INT') { channel.put "old handler invoked" }
|
49
49
|
call code: -> { sleep 0.1 }, ensure: -> { channel.put "ensure invoked" }
|
@@ -66,7 +66,7 @@ RSpec.describe SeeingIsBelieving::HardCoreEnsure do
|
|
66
66
|
|
67
67
|
it 'invokes the code even if an interrupt is sent and interrupts are set to ignore' do
|
68
68
|
test = lambda do
|
69
|
-
channel = IChannel.
|
69
|
+
channel = IChannel.unix
|
70
70
|
pid = fork do
|
71
71
|
old_handler = trap 'INT', 'IGNORE'
|
72
72
|
result = call code: -> { sleep 0.1; 'code result' }, ensure: -> { channel.put "ensure invoked" }
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
|
2
4
|
require 'spec_helper'
|
3
5
|
require 'stringio'
|
4
6
|
require 'seeing_is_believing'
|
7
|
+
require 'ichannel'
|
5
8
|
|
6
9
|
RSpec.describe SeeingIsBelieving do
|
7
10
|
def method_result(name)
|
@@ -507,6 +510,32 @@ RSpec.describe SeeingIsBelieving do
|
|
507
510
|
expect(values_for 'o = BasicObject.new; def o.inspect; "some obj"; end; o').to eq [['some obj']]
|
508
511
|
end
|
509
512
|
|
513
|
+
it 'respects timeout, even when children do semi-ridiculous things, it cleans up children rather than orphaning them' do
|
514
|
+
# https://github.com/JoshCheek/seeing_is_believing/issues/53
|
515
|
+
result = invoke <<-RUBY, timeout_seconds: 0.1
|
516
|
+
read, write = IO.pipe
|
517
|
+
fork # child makes a grandchild
|
518
|
+
puts Process.pid # print current pid
|
519
|
+
read.read # block child and grandchild
|
520
|
+
RUBY
|
521
|
+
result.stdout.lines.map(&:to_i).each do |pid|
|
522
|
+
expect { Process.kill 0, pid }.to raise_error(Errno::ESRCH)
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
it 'does not kill parent processes' do
|
527
|
+
channel = IChannel.unix
|
528
|
+
fork do
|
529
|
+
# it's basically undocumented, but I think 0 makes it choose a new group id
|
530
|
+
# this is so that we don't kill the test if it gets messed up
|
531
|
+
Process.setpgid(Process.pid, 0)
|
532
|
+
channel.put invoke("1" ).timeout_seconds
|
533
|
+
channel.put invoke("sleep", timeout_seconds: 0.1).timeout_seconds
|
534
|
+
end
|
535
|
+
expect(channel.get).to eq nil # child exited normally
|
536
|
+
expect(channel.get).to eq 0.1 # child timed out
|
537
|
+
end
|
538
|
+
|
510
539
|
context 'when given a debugger' do
|
511
540
|
let(:stream) { StringIO.new }
|
512
541
|
let(:debugger) { SeeingIsBelieving::Debugger.new stream: stream }
|
@@ -529,6 +558,76 @@ RSpec.describe SeeingIsBelieving do
|
|
529
558
|
end
|
530
559
|
end
|
531
560
|
|
561
|
+
describe 'fork' do
|
562
|
+
it 'records results from both parent and child, without double reporting items that may have been left in the queue at the time of forking' do
|
563
|
+
n = 10000
|
564
|
+
result = invoke <<-RUBY
|
565
|
+
n = #{n}
|
566
|
+
as = '0' * n; 0
|
567
|
+
bs = '1' * n; 1
|
568
|
+
|
569
|
+
$$
|
570
|
+
if fork
|
571
|
+
$$
|
572
|
+
print as
|
573
|
+
else
|
574
|
+
$$
|
575
|
+
print bs
|
576
|
+
end
|
577
|
+
$$
|
578
|
+
RUBY
|
579
|
+
|
580
|
+
expect(result[1]).to eq [n.to_s]
|
581
|
+
expect(result[2]).to eq ['0']
|
582
|
+
expect(result[3]).to eq ['1']
|
583
|
+
expect(result[4]).to eq []
|
584
|
+
ppid = result[5][0]
|
585
|
+
pid, null = result[6].sort
|
586
|
+
expect(null).to eq 'nil'
|
587
|
+
|
588
|
+
expect(result[ 7]).to eq [ppid]
|
589
|
+
expect(result[10]).to eq [pid]
|
590
|
+
expect(result[13].sort).to eq [pid, ppid].sort
|
591
|
+
|
592
|
+
zeros, ones = result.stdout.split(/01|10/).sort
|
593
|
+
expect(zeros).to eq ("0"*(n-1)) # spent one on the split
|
594
|
+
expect(ones).to eq ("1"*(n-1))
|
595
|
+
end
|
596
|
+
|
597
|
+
it 'works for Kernel#fork, Kernel.fork, Process.fork' do
|
598
|
+
result = invoke <<-RUBY
|
599
|
+
$$
|
600
|
+
if fork
|
601
|
+
$$
|
602
|
+
elsif Kernel.fork
|
603
|
+
$$
|
604
|
+
elsif Process.fork
|
605
|
+
$$
|
606
|
+
else
|
607
|
+
$$
|
608
|
+
end
|
609
|
+
$$
|
610
|
+
RUBY
|
611
|
+
|
612
|
+
pid_main = result[1][0]
|
613
|
+
|
614
|
+
pid_private, null = result[2].sort
|
615
|
+
expect(result[3]).to eq [pid_main]
|
616
|
+
expect(null).to eq 'nil'
|
617
|
+
|
618
|
+
pid_kernel, null = result[4].sort
|
619
|
+
expect(result[5]).to eq [pid_private]
|
620
|
+
expect(null).to eq 'nil'
|
621
|
+
|
622
|
+
pid_process, null = result[6].sort
|
623
|
+
expect(result[7]).to eq [pid_kernel]
|
624
|
+
expect(null).to eq 'nil'
|
625
|
+
|
626
|
+
expect(result[9]).to eq [pid_process]
|
627
|
+
expect(result[11].sort).to eq [pid_main, pid_private, pid_kernel, pid_process].sort
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
532
631
|
describe 'exec' do
|
533
632
|
it 'passes stdin, stdout, stderr, and actually does exec the process' do
|
534
633
|
result = invoke \
|
@@ -565,18 +664,209 @@ RSpec.describe SeeingIsBelieving do
|
|
565
664
|
# this is a list of things to check that it can work without.
|
566
665
|
# based on this issue: https://github.com/JoshCheek/seeing_is_believing/issues/55
|
567
666
|
describe 'it works even in hostile environments' do
|
568
|
-
specify 'when IO does not have sync
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
specify 'when
|
579
|
-
|
580
|
-
|
667
|
+
specify 'when IO does not have sync=, <<, flush, puts, close' do
|
668
|
+
expect(invoke('class IO
|
669
|
+
undef sync
|
670
|
+
undef <<
|
671
|
+
undef flush
|
672
|
+
undef puts
|
673
|
+
undef close
|
674
|
+
end').stderr).to eq ''
|
675
|
+
end
|
676
|
+
|
677
|
+
specify 'when File does not have sync=, <<, flush, puts, close' do
|
678
|
+
expect(invoke('class File
|
679
|
+
undef sync
|
680
|
+
undef <<
|
681
|
+
undef flush
|
682
|
+
undef puts
|
683
|
+
undef close
|
684
|
+
end').stderr).to eq ''
|
685
|
+
end
|
686
|
+
|
687
|
+
specify 'when Queue does not have <<, shift, and clear' do
|
688
|
+
expect(invoke('class Queue
|
689
|
+
undef <<
|
690
|
+
undef shift
|
691
|
+
undef clear
|
692
|
+
end').stderr).to eq ''
|
693
|
+
end
|
694
|
+
|
695
|
+
specify 'when Symbol does not have ==, to_s, inspect' do
|
696
|
+
expect(invoke('class Symbol
|
697
|
+
undef ==
|
698
|
+
undef to_s
|
699
|
+
undef inspect
|
700
|
+
end').stderr).to eq ''
|
701
|
+
end
|
702
|
+
|
703
|
+
specify 'when String does not have ==, to_s, inspect, to_i' do
|
704
|
+
expect(invoke('class String
|
705
|
+
undef ==
|
706
|
+
undef to_s
|
707
|
+
undef to_str
|
708
|
+
undef inspect
|
709
|
+
undef to_i
|
710
|
+
end').stderr).to eq ''
|
711
|
+
end
|
712
|
+
|
713
|
+
specify 'when Fixnum does not have <, <<, next, ==, inspect, to_s' do
|
714
|
+
expect(invoke('class Fixnum
|
715
|
+
undef <
|
716
|
+
undef <<
|
717
|
+
undef ==
|
718
|
+
undef next
|
719
|
+
undef to_s
|
720
|
+
undef inspect
|
721
|
+
end').stderr).to eq ''
|
722
|
+
end
|
723
|
+
|
724
|
+
specify 'when Hash does not have [], []=, fetch' do
|
725
|
+
RUBY_VERSION == '2.0.0' and
|
726
|
+
skip '2.0.0 implements all the thread stuff in Ruby, with no obvious way to override its attempt to call removed methods'
|
727
|
+
expect(invoke('class Hash
|
728
|
+
undef []
|
729
|
+
undef []=
|
730
|
+
undef fetch
|
731
|
+
end').stderr).to eq ''
|
732
|
+
end
|
733
|
+
|
734
|
+
specify 'when Array does not have pack, <<, to_ary, grep, first, [], []=, each, map' do
|
735
|
+
expect(invoke('class Array
|
736
|
+
undef pack
|
737
|
+
undef <<
|
738
|
+
undef to_ary
|
739
|
+
undef grep
|
740
|
+
undef first
|
741
|
+
undef []
|
742
|
+
undef []=
|
743
|
+
undef each
|
744
|
+
undef map
|
745
|
+
undef join
|
746
|
+
undef size
|
747
|
+
end').stderr).to eq ''
|
748
|
+
end
|
749
|
+
|
750
|
+
specify 'when Marshal does not have dump, load' do
|
751
|
+
expect(invoke('class << Marshal
|
752
|
+
undef dump
|
753
|
+
undef load
|
754
|
+
end').stderr).to eq ''
|
755
|
+
end
|
756
|
+
|
757
|
+
specify 'when Kernel does not have defined?, kind_of?' do
|
758
|
+
expect(invoke('class Kernel
|
759
|
+
undef defined?
|
760
|
+
undef kind_of?
|
761
|
+
end').stderr).to eq ''
|
762
|
+
end
|
763
|
+
|
764
|
+
specify 'when Enumerable does not have map' do
|
765
|
+
expect(invoke('class Module
|
766
|
+
undef map
|
767
|
+
end
|
768
|
+
').stderr).to eq ''
|
769
|
+
end
|
770
|
+
|
771
|
+
specify 'when SystemExit does not have status' do
|
772
|
+
# its fine to blow up if they undefine it and then they call exit,
|
773
|
+
# that's probably the expected behaviour
|
774
|
+
# But our code should be as resilient as possible,
|
775
|
+
# so shouldn't blow up b/c some feature happened to call exit
|
776
|
+
expect(invoke('class SystemExit
|
777
|
+
undef status
|
778
|
+
end
|
779
|
+
').stderr).to eq ''
|
780
|
+
end
|
781
|
+
|
782
|
+
specify 'when Exception does not have message, class (can\'t get backtrace to work)' do
|
783
|
+
result = invoke('class Exception
|
784
|
+
undef message
|
785
|
+
# undef backtrace
|
786
|
+
undef class
|
787
|
+
end
|
788
|
+
raise "hello"
|
789
|
+
')
|
790
|
+
expect(result.exception.message).to eq 'hello'
|
791
|
+
# expect(result.exception.backtrace).to eq 'hello'
|
792
|
+
expect(result.exception.class_name).to eq 'RuntimeError'
|
793
|
+
end
|
794
|
+
|
795
|
+
specify 'when Thread does not have .new, .current, #join, #abort_on_exception' do
|
796
|
+
expect(invoke('class << Thread
|
797
|
+
undef new
|
798
|
+
undef current if "2.0.0" != RUBY_VERSION && "1.9.3" != RUBY_VERSION
|
799
|
+
end
|
800
|
+
class Thread
|
801
|
+
undef join
|
802
|
+
undef abort_on_exception
|
803
|
+
end
|
804
|
+
').stderr).to eq ''
|
805
|
+
end
|
806
|
+
|
807
|
+
specify 'when Class does not have new, allocate, singleton_class, class_eval' do
|
808
|
+
expect(invoke('class Class
|
809
|
+
undef new
|
810
|
+
undef allocate
|
811
|
+
undef singleton_class
|
812
|
+
undef class_eval
|
813
|
+
end
|
814
|
+
').stderr).to eq ''
|
815
|
+
end
|
816
|
+
|
817
|
+
specify 'when Class#new is overridden' do
|
818
|
+
result = invoke('class Class
|
819
|
+
def new(arg)
|
820
|
+
arg
|
821
|
+
end
|
822
|
+
end
|
823
|
+
class A
|
824
|
+
end
|
825
|
+
A.new 123')
|
826
|
+
expect(result.exception).to eq nil
|
827
|
+
expect(result[3]).to eq ["123"]
|
828
|
+
end
|
829
|
+
|
830
|
+
specify 'when BasicObject does not have initialize' do
|
831
|
+
expect(invoke('class BasicObject
|
832
|
+
undef initialize
|
833
|
+
end
|
834
|
+
').stderr).to eq ''
|
835
|
+
end
|
836
|
+
|
837
|
+
specify 'when Module does not have define_method, instance_method' do
|
838
|
+
expect(invoke('class Module
|
839
|
+
undef define_method
|
840
|
+
undef instance_method
|
841
|
+
end
|
842
|
+
').stderr).to eq ''
|
843
|
+
end
|
844
|
+
|
845
|
+
context 'not clear that there are good ways to deal with these' do
|
846
|
+
before { skip }
|
847
|
+
|
848
|
+
specify 'when UnboundMethod does not have bind' do
|
849
|
+
expect(invoke('class UnboundMethod
|
850
|
+
undef bind
|
851
|
+
end
|
852
|
+
').stderr).to eq ''
|
853
|
+
end
|
854
|
+
|
855
|
+
specify 'when Method does not have call' do
|
856
|
+
expect(invoke('class Method
|
857
|
+
undef call
|
858
|
+
end
|
859
|
+
').stderr).to eq ''
|
860
|
+
end
|
861
|
+
specify 'when Proc does not have call, to_proc' do
|
862
|
+
expect(invoke('class Proc
|
863
|
+
undef call
|
864
|
+
undef to_proc
|
865
|
+
end
|
866
|
+
').stderr).to eq ''
|
867
|
+
end
|
868
|
+
end
|
581
869
|
end
|
582
870
|
end
|
871
|
+
|
872
|
+
# all of them together
|