seeing_is_believing 3.0.0.beta.7 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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