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.
@@ -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.new do
22
- begin
23
- resultstream.sync = true
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.kind_of? SystemExit
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
- exception.backtrace.size,
98
- *exception.backtrace.map { |line| to_string_token line }
99
- ].join(" ")
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
@@ -33,8 +33,8 @@ class SeeingIsBelieving
33
33
 
34
34
  def invoke_ensure
35
35
  return if ensure_invoked
36
- trap 'INT', old_handler
37
36
  self.ensure_invoked = true
37
+ trap 'INT', old_handler
38
38
  options[:ensure].call
39
39
  end
40
40
 
@@ -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, stderr = STDOUT, STDERR
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)
@@ -1,3 +1,3 @@
1
1
  class SeeingIsBelieving
2
- VERSION = '3.0.0.beta.7'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -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", "~> 10.0"
27
- s.add_development_dependency "mrspec", "~> 0.3.0"
28
- s.add_development_dependency "rspec", "~> 3.2"
29
- s.add_development_dependency "cucumber", "~> 1.2"
30
- s.add_development_dependency "ichannel", "~> 5.1"
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
@@ -447,14 +447,26 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
447
447
  end
448
448
  end
449
449
 
450
- describe 'debug?' do
451
- specify 'debug? defaults to a false' do
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 '-g and --debug set debug? to true' do
456
- expect(parse(['-g']).debug?).to eq true
457
- expect(parse(['--debug']).debug?).to eq true
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
- specify 'are set to debug to stderr when debug? is true' do
667
- handler = call debug: true
668
- expect(handler.debugger).to be_enabled
669
- expect(handler.debugger.stream).to eq stderr
670
- expect(handler.lib_options.debugger).to equal handler.debugger
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
@@ -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 KILL interrupts the process and then waits for the events to finish' do
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("KILL", an_instance_of(Fixnum))
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.new Marshal
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.new Marshal
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, <<, and flush'
569
- specify 'when Queue does not have <<, shift, and clear'
570
- specify 'when Symbol does not have ==, to_s, inspect'
571
- specify 'when String does not have ==, to_s, inspect, to_i'
572
- specify 'when Fixnum does not have <, next, =='
573
- specify 'when Hash does not have [], []='
574
- specify 'when Array does not have pack, <<, to_ary, grep, first, [], []='
575
- specify 'when Marshal does not have dump'
576
- specify 'when Kernel does not have defined?, kind_of?'
577
- specify 'when SystemExit does not have status'
578
- specify 'when Enumerable does not have map'
579
- specify 'when Exception does not have status'
580
- specify 'when Thread does not have new, join'
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