seeing_is_believing 3.3.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +39 -0
  3. data/{Readme.md → README.md} +9 -12
  4. data/Rakefile +12 -23
  5. data/appveyor.yml +3 -1
  6. data/features/flags.feature +251 -1
  7. data/features/regression.feature +166 -23
  8. data/features/xmpfilter-style.feature +1 -0
  9. data/lib/seeing_is_believing.rb +5 -2
  10. data/lib/seeing_is_believing/binary.rb +6 -1
  11. data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +1 -1
  12. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +8 -5
  13. data/lib/seeing_is_believing/binary/comment_lines.rb +2 -2
  14. data/lib/seeing_is_believing/binary/commentable_lines.rb +7 -7
  15. data/lib/seeing_is_believing/binary/config.rb +21 -4
  16. data/lib/seeing_is_believing/binary/engine.rb +58 -1
  17. data/lib/seeing_is_believing/binary/format_comment.rb +9 -6
  18. data/lib/seeing_is_believing/binary/remove_annotations.rb +1 -1
  19. data/lib/seeing_is_believing/code.rb +5 -5
  20. data/lib/seeing_is_believing/compatibility.rb +28 -0
  21. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +52 -71
  22. data/lib/seeing_is_believing/event_stream/consumer.rb +26 -28
  23. data/lib/seeing_is_believing/event_stream/events.rb +7 -0
  24. data/lib/seeing_is_believing/event_stream/handlers/record_exit_events.rb +3 -3
  25. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +2 -1
  26. data/lib/seeing_is_believing/event_stream/producer.rb +29 -5
  27. data/lib/seeing_is_believing/safe.rb +8 -7
  28. data/lib/seeing_is_believing/swap_files.rb +90 -0
  29. data/lib/seeing_is_believing/the_matrix.rb +17 -4
  30. data/lib/seeing_is_believing/version.rb +1 -1
  31. data/lib/seeing_is_believing/wrap_expressions.rb +27 -13
  32. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +4 -1
  33. data/seeing_is_believing.gemspec +4 -6
  34. data/spec/binary/config_spec.rb +63 -35
  35. data/spec/binary/engine_spec.rb +11 -1
  36. data/spec/binary/format_comment_spec.rb +5 -1
  37. data/spec/evaluate_by_moving_files_spec.rb +35 -18
  38. data/spec/event_stream_spec.rb +15 -0
  39. data/spec/seeing_is_believing_spec.rb +148 -26
  40. data/spec/sib_spec_helpers/version.rb +17 -0
  41. data/spec/spec_helper.rb +2 -18
  42. data/spec/wrap_expressions_spec.rb +9 -2
  43. metadata +31 -31
  44. data/.travis.yml +0 -10
  45. data/lib/seeing_is_believing/customize_pp.rb +0 -5
  46. data/spec/binary/options_spec.rb +0 -0
@@ -5,11 +5,14 @@ class SeeingIsBelieving
5
5
  # NOTE: if it received the AST, it could figure out if it needs
6
6
  # to always wrap the expression in parentheses
7
7
  WrapExpressions.call program,
8
+ before_all: -> {
9
+ "BEGIN { $SiB.file_loaded };"
10
+ },
8
11
  before_each: -> line_number {
9
12
  "$SiB.record_result(:inspect, #{line_number}, ("
10
13
  },
11
14
  after_each: -> line_number {
12
- "))"
15
+ ")) { |v| v.inspect }"
13
16
  }
14
17
  end
15
18
  end
@@ -12,20 +12,18 @@ Gem::Specification.new do |s|
12
12
  s.description = %q{Records the results of every line of code in your file (intended to be like xmpfilter), inspired by Bret Victor's JavaScript example in his talk "Inventing on Principle"}
13
13
  s.license = "WTFPL"
14
14
 
15
- s.rubyforge_project = "seeing_is_believing"
16
-
17
15
  s.files = `git ls-files`.split("\n") - ['docs/seeing is believing.psd'] # remove psd b/c it boosts the gem size from 50kb to 20mb O.o
18
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
18
  s.require_paths = ["lib"]
21
19
 
22
- s.add_dependency "parser", "~> 2.4.0"
23
- s.add_dependency "childprocess","~> 0.7.1"
20
+ s.add_dependency "parser", "~> 2.7.0"
21
+ s.add_dependency "childprocess","~> 3.0.0"
22
+ s.add_dependency "ffi", "~> 1.11.1"
24
23
 
25
24
  s.add_development_dependency "pry"
26
25
  s.add_development_dependency "haiti", ">= 0.1", "< 0.3"
27
- s.add_development_dependency "rake", "~> 12.0.0"
28
- s.add_development_dependency "mrspec", "~> 0.3.1"
26
+ s.add_development_dependency "rake", "~> 13.0.0"
29
27
  s.add_development_dependency "rspec", "~> 3.6.0"
30
28
  s.add_development_dependency "cucumber", "~> 2.4"
31
29
  s.add_development_dependency "ripper-tags", "~> 0.3"
@@ -208,6 +208,16 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
208
208
  end
209
209
 
210
210
 
211
+ describe 'lib_options.local_cwd?' do
212
+ it 'defaults to false' do
213
+ expect(parse([]).lib_options.local_cwd?).to eq false
214
+ end
215
+ it 'is set to true by --local-cwd' do
216
+ expect(parse(['--local-cwd']).lib_options.local_cwd?).to eq true
217
+ end
218
+ end
219
+
220
+
211
221
  describe 'annotator_options.max_result_length' do
212
222
  it 'defaults to infinity' do
213
223
  expect(parse([]).annotator_options.max_result_length).to eq Float::INFINITY
@@ -247,8 +257,8 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
247
257
  expect(parse([]).lib_options.require_files).to eq [matrix_file]
248
258
  end
249
259
 
250
- it 'appends pp and our customizations for xmpfilter style' do
251
- expect(parse(['-x']).lib_options.require_files).to eq [matrix_file, 'pp', 'seeing_is_believing/customize_pp']
260
+ it 'appends pp for xmpfilter style' do
261
+ expect(parse(['-x']).lib_options.require_files).to eq [matrix_file, 'pp']
252
262
  end
253
263
 
254
264
  specify '-r and --require set an error if not provided with a filename' do
@@ -601,40 +611,58 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
601
611
  it('defaults :stdout to "# >> "') { expect(default_markers.stdout .prefix).to eq "# >> " }
602
612
  it('defaults :stderr to "# !> "') { expect(default_markers.stderr .prefix).to eq "# !> " }
603
613
  end
604
- end
605
614
 
606
- describe 'print_event_stream?' do
607
- it 'print_event_stream? is false by default' do
608
- expect(parse([]).print_event_stream?).to eq false
609
- end
610
- it 'print_event_stream? can be turned on with --stream' do
611
- expect(parse(['--stream']).print_event_stream?).to eq true
612
- end
613
- it 'adds an error if --stream is used with --json' do
614
- expect(parse(['--stream'])).to_not have_error '--stream'
615
- expect(parse(['--stream', '--json'])).to have_error '--stream'
616
- expect(parse(['--json', '--stream'])).to have_error '--stream'
617
- end
618
- it 'adds an error if --stream is used with -x or --xmpfilter-style' do
619
- expect(parse(['--stream'])).to_not have_error '--stream'
620
- expect(parse(['--stream', '-x'])).to have_error '--stream'
621
- expect(parse(['-x', '--stream'])).to have_error '--stream'
622
- expect(parse(['--xmpfilter-style', '--stream'])).to have_error '--stream'
615
+ describe 'print_event_stream?' do
616
+ it 'print_event_stream? is false by default' do
617
+ expect(parse([]).print_event_stream?).to eq false
618
+ end
619
+ it 'print_event_stream? can be turned on with --stream' do
620
+ expect(parse(['--stream']).print_event_stream?).to eq true
621
+ end
622
+ it 'adds an error if --stream is used with --json' do
623
+ expect(parse(['--stream'])).to_not have_error '--stream'
624
+ expect(parse(['--stream', '--json'])).to have_error '--stream'
625
+ expect(parse(['--json', '--stream'])).to have_error '--stream'
626
+ end
627
+ it 'adds an error if --stream is used with -x or --xmpfilter-style' do
628
+ expect(parse(['--stream'])).to_not have_error '--stream'
629
+ expect(parse(['--stream', '-x'])).to have_error '--stream'
630
+ expect(parse(['-x', '--stream'])).to have_error '--stream'
631
+ expect(parse(['--xmpfilter-style', '--stream'])).to have_error '--stream'
632
+ end
623
633
  end
624
- end
625
-
626
634
 
627
- describe 'align_results?' do
628
- it 'defaults to true' do
629
- expect(parse([]).annotator_options.interline_align?).to eq true
630
- end
631
- it 'can be turned on with --interline-align' do
632
- expect(parse(['--interline-align'])).to_not have_error '--interline-align'
633
- expect(parse(['--interline-align']).annotator_options.interline_align?).to eq true
635
+ describe 'align_results?' do
636
+ it 'defaults to true' do
637
+ expect(parse([]).annotator_options.interline_align?).to eq true
638
+ end
639
+ it 'can be turned on with --interline-align' do
640
+ expect(parse(['--interline-align'])).to_not have_error '--interline-align'
641
+ expect(parse(['--interline-align']).annotator_options.interline_align?).to eq true
642
+ end
643
+ it 'can be turned off with --no-interline-align' do
644
+ expect(parse(['--no-interline-align'])).to_not have_error '--no-interline-align'
645
+ expect(parse(['--no-interline-align']).annotator_options.interline_align?).to eq false
646
+ end
634
647
  end
635
- it 'can be turned off with --no-interline-align' do
636
- expect(parse(['--no-interline-align'])).to_not have_error '--no-interline-align'
637
- expect(parse(['--no-interline-align']).annotator_options.interline_align?).to eq false
648
+
649
+ describe 'toggle_mark/toggle_mark?' do
650
+ it 'defaults to nil/false' do
651
+ expect(parse([]).toggle_mark).to eq nil
652
+ expect(parse([]).toggle_mark?).to eq false
653
+ end
654
+ it 'can be set to true/linenum with --toggle-mark' do
655
+ expect(parse(['--toggle-mark', '123']).toggle_mark).to eq 123
656
+ expect(parse(['--toggle-mark', '123']).toggle_mark?).to eq true
657
+ end
658
+ it_behaves_like 'it requires a positive int argument', ['--toggle-mark']
659
+ it 'adds an error if used with --json or --stream' do
660
+ # expect(parse(['--toggle-mark', '123'])).to_not have_error /--toggle-mark/
661
+ expect(parse(['--stream', '--toggle-mark', '123'])).to have_error /--toggle-mark/
662
+ expect(parse(['--toggle-mark', '123', '--stream'])).to have_error /--toggle-mark/
663
+ expect(parse(['--json', '--toggle-mark', '123'])).to have_error /--toggle-mark/
664
+ expect(parse(['--toggle-mark', '123', '--json'])).to have_error /--toggle-mark/
665
+ end
638
666
  end
639
667
  end
640
668
 
@@ -677,11 +705,11 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
677
705
  end
678
706
 
679
707
  describe 'setting the body' do
680
- it 'does not override the if already set e.g. with -e' do
708
+ it 'does not override the body if already set e.g. with -e' do
681
709
  expect(call(body: 'b', filename: nil).body).to eq 'b'
682
710
  end
683
711
 
684
- it 'is the file body if the filename is provded and exists' do
712
+ it 'is the file body if the filename is provided and exists' do
685
713
  expect(call(body: nil, filename: existing_filename).body)
686
714
  .to eq file_body
687
715
  end
@@ -776,7 +804,7 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
776
804
  end
777
805
 
778
806
  context 'when debug? is a string' do
779
- let(:proving_grounds_dir) { File.realdirpath '../../proving_grounds', __dir__ }
807
+ let(:proving_grounds_dir) { File.expand_path '../../proving_grounds', __dir__ }
780
808
  let(:path) { File.join proving_grounds_dir, 'test.log' }
781
809
  before { Dir.mkdir proving_grounds_dir unless Dir.exist? proving_grounds_dir }
782
810
 
@@ -7,7 +7,8 @@ class SeeingIsBelieving
7
7
  def call(body, options={})
8
8
  timeout = options.fetch :timeout, 0
9
9
  filename = options.fetch :filename, "program.rb"
10
- config = Config.new body: body, timeout_seconds: timeout
10
+ toggle = options.fetch :toggled_mark, nil
11
+ config = Config.new body: body, timeout_seconds: timeout, toggle_mark: toggle
11
12
  config.lib_options.timeout_seconds = timeout
12
13
  config.lib_options.filename = filename
13
14
  Engine.new config
@@ -50,6 +51,15 @@ class SeeingIsBelieving
50
51
  end
51
52
  end
52
53
 
54
+ context 'toggled_mark' do
55
+ it 'has the mark toggled and doesn\'t change the newline' do
56
+ expect(call("1", toggled_mark: 1).toggled_mark).to eq "1 # => "
57
+ expect(call("1 # => ", toggled_mark: 1).toggled_mark).to eq "1"
58
+ expect(call("1\n", toggled_mark: 1).toggled_mark).to eq "1 # => \n"
59
+ expect(call("1 # =>\n", toggled_mark: 1).toggled_mark).to eq "1\n"
60
+ end
61
+ end
62
+
53
63
  context 'before evaluating it raises if asked for' do
54
64
  specify('result') { assert_must_evaluate :result }
55
65
  specify('exitstatus') { assert_must_evaluate :exitstatus }
@@ -60,7 +60,10 @@ RSpec.describe SeeingIsBelieving::Binary::FormatComment do
60
60
 
61
61
  def assert_printed(c, printed)
62
62
  c = c.force_encoding 'utf-8'
63
- expect(result_for 0, '', c).to eq printed
63
+ result = result_for 0, '', c
64
+ expect(result).to eq printed
65
+ expect(result.encoding).to eq Encoding::UTF_8
66
+ expect(result).to be_valid_encoding
64
67
  end
65
68
 
66
69
  it 'escapes any non-printable characters' do
@@ -192,6 +195,7 @@ RSpec.describe SeeingIsBelieving::Binary::FormatComment do
192
195
  assert_printed 124.chr , "|"
193
196
  assert_printed 125.chr , "}"
194
197
  assert_printed 126.chr , "~"
198
+ assert_printed 127.chr, "\u007F"
195
199
  end
196
200
 
197
201
  it 'can be given a list of characters to not escape' do
@@ -7,7 +7,7 @@ require 'fileutils'
7
7
  require 'childprocess'
8
8
 
9
9
  RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
10
- let(:filedir) { File.realdirpath '../proving_grounds', __dir__ }
10
+ let(:filedir) { File.expand_path '../proving_grounds', __dir__ }
11
11
  let(:filename) { File.join filedir, 'some_filename' }
12
12
 
13
13
  before { FileUtils.mkdir_p filedir }
@@ -23,32 +23,32 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
23
23
  def invoke(program, options={})
24
24
  result = SeeingIsBelieving::Result.new
25
25
  options[:event_handler] ||= SeeingIsBelieving::EventStream::Handlers::UpdateResult.new(result)
26
- evaluator = described_class.new(program, filename, options)
27
- FileUtils.rm_f evaluator.backup_filename
26
+ evaluator = described_class.new(filename, program, program, options)
27
+ FileUtils.rm_f evaluator.backup_path
28
28
  evaluator.call
29
29
  result
30
30
  end
31
31
 
32
- it 'evaluates the code when the file DNE' do
32
+ it 'evaluates the code as the given file' do
33
+ expect(invoke('print __FILE__').stdout).to eq filename
34
+ end
35
+
36
+ it 'evaluates the code when the given file DNE' do
33
37
  FileUtils.rm_f filename
34
38
  expect(invoke('print 1').stdout).to eq '1'
35
39
  end
36
40
 
37
- it 'evaluates the code when the file Exists' do
41
+ it 'evaluates the code when the given file Exists' do
38
42
  FileUtils.touch filename
39
43
  expect(invoke('print 1').stdout).to eq '1'
40
44
  end
41
45
 
42
46
  it 'raises an error when the temp file already exists' do
43
- evaluator = described_class.new('', filename, null_options)
44
- FileUtils.touch evaluator.backup_filename
47
+ evaluator = described_class.new(filename, '', '', null_options)
48
+ FileUtils.touch evaluator.backup_path
45
49
  expect { evaluator.call }.to raise_error SeeingIsBelieving::TempFileAlreadyExists
46
50
  end
47
51
 
48
- it 'evaluates the code as the given file' do
49
- expect(invoke('print __FILE__').stdout).to eq filename
50
- end
51
-
52
52
  it 'does not change the original file' do
53
53
  File.open(filename, 'w') { |f| f.write "ORIGINAL" }
54
54
  invoke '1 + 1'
@@ -56,29 +56,29 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
56
56
  end
57
57
 
58
58
  it 'uses HardCoreEnsure to move the file back' do
59
- evaluator = described_class.new 'PROGRAM', filename, null_options
59
+ evaluator = described_class.new filename, 'PROGRAM', 'PROGRAM', null_options
60
60
  File.open(filename, 'w') { |f| f.write 'ORIGINAL' }
61
- FileUtils.rm_rf evaluator.backup_filename
61
+ FileUtils.rm_rf evaluator.backup_path
62
62
  expect(SeeingIsBelieving::HardCoreEnsure).to receive(:call) do |options|
63
63
  # initial state
64
- expect(File.exist? evaluator.backup_filename).to eq false
64
+ expect(File.exist? evaluator.backup_path).to eq false
65
65
  expect(File.read filename).to eq 'ORIGINAL'
66
66
 
67
67
  # after code
68
68
  options[:code].call rescue nil
69
- expect(File.read evaluator.backup_filename).to eq 'ORIGINAL'
69
+ expect(File.read evaluator.backup_path).to eq 'ORIGINAL'
70
70
  expect(File.read filename).to eq 'PROGRAM'
71
71
 
72
72
  # after ensure
73
73
  options[:ensure].call
74
74
  expect(File.read filename).to eq 'ORIGINAL'
75
- expect(File.exist? evaluator.backup_filename).to eq false
75
+ expect(File.exist? evaluator.backup_path).to eq false
76
76
  end
77
77
  evaluator.call
78
78
  end
79
79
 
80
80
  it 'uses HardCoreEnsure to delete the file if it wrote it where one did not previously exist' do
81
- evaluator = described_class.new 'PROGRAM', filename, null_options
81
+ evaluator = described_class.new filename, 'PROGRAM', 'PROGRAM', null_options
82
82
  FileUtils.rm_rf filename
83
83
  expect(SeeingIsBelieving::HardCoreEnsure).to receive(:call) do |options|
84
84
  # initial state
@@ -120,6 +120,22 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
120
120
  end
121
121
  end
122
122
 
123
+ it 'sets the program\'s working directory to the file\'s directory, when given local_cwd' do
124
+ Dir.chdir '/' do
125
+ root = File.expand_path '/'
126
+ result = invoke 'print Dir.pwd'
127
+ expect(result.stdout).to eq root
128
+ dir, cwd, file_path = invoke(<<-RUBY, local_cwd: true).stdout.lines.map(&:chomp)
129
+ puts File.expand_path(__dir__)
130
+ puts Dir.pwd
131
+ puts __FILE__
132
+ RUBY
133
+ expect(dir).to_not eq root
134
+ expect(cwd).to eq dir
135
+ expect(file_path).to eq File.basename(filename)
136
+ end
137
+ end
138
+
123
139
  it 'does not blow up on exceptions raised in at_exit blocks' do
124
140
  expect { invoke 'at_exit { raise "zomg" }' }.to_not raise_error
125
141
  end
@@ -133,7 +149,7 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
133
149
 
134
150
  it 'must provide an event handler, which receives the process\'s events' do
135
151
  # raises error
136
- expect { described_class.new("", filename, {}) }
152
+ expect { described_class.new(filename, "", "", {}) }
137
153
  .to raise_error ArgumentError, /event_handler/
138
154
 
139
155
  # sees all the events
@@ -148,6 +164,7 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
148
164
  result = invoke <<-RUBY, timeout_seconds: 0.5
149
165
  child_pid = spawn 'ruby', '-e', 'sleep' # child makes a grandchild which sleeps
150
166
  puts Process.pid, child_pid # print ids so we can check they got killed
167
+ $stdout.flush
151
168
  sleep # child sleeps
152
169
  RUBY
153
170
  post = Time.now
@@ -305,6 +305,13 @@ module SeeingIsBelieving::EventStream
305
305
  producer.record_result :type, 1, obj
306
306
  expect(count).to eq 1
307
307
  end
308
+
309
+ it 'can deal with results of inspect that have singleton methods' do
310
+ str = "a string"
311
+ def str.inspect() self end
312
+ producer.record_result :type, 1, str
313
+ expect(consumer.call.inspected).to eq str
314
+ end
308
315
  end
309
316
 
310
317
  context 'inspect performed by the block' do
@@ -430,6 +437,13 @@ module SeeingIsBelieving::EventStream
430
437
  end
431
438
  end
432
439
 
440
+ it 'works with objects whose boolean inquiries have been messed with (#131)' do
441
+ exception = begin; raise; rescue; $!; end
442
+ bad_bool = Object.new
443
+ def bad_bool.!(*) raise; end
444
+ producer.record_exception bad_bool, exception # should not explode
445
+ end
446
+
433
447
  context 'recorded line number | line num is provided | it knows the file | exception comes from within file' do
434
448
  let(:exception) { begin; raise "zomg"; rescue; $!; end }
435
449
  let(:linenum) { __LINE__ - 1 }
@@ -650,6 +664,7 @@ module SeeingIsBelieving::EventStream
650
664
  [Events::StderrClosed , :stderr_closed],
651
665
  [Events::EventStreamClosed, :event_stream_closed],
652
666
  [Events::Finished , :finished],
667
+ [Events::FileLoaded , :file_loaded],
653
668
  ]
654
669
  pairs.each { |klass, name| expect(klass.event_name).to eq name }
655
670
 
@@ -31,7 +31,7 @@ RSpec.describe SeeingIsBelieving do
31
31
  end
32
32
 
33
33
  root_path = File.realpath("..", __dir__)
34
- proving_grounds_path = File.realdirpath('proving_grounds', root_path)
34
+ proving_grounds_path = File.expand_path('proving_grounds', root_path)
35
35
  before(:all) { Dir.mkdir proving_grounds_path unless Dir.exist? proving_grounds_path }
36
36
  around { |spec| Dir.chdir proving_grounds_path, &spec }
37
37
 
@@ -251,8 +251,8 @@ RSpec.describe SeeingIsBelieving do
251
251
  # Currently we dont' differentiate between inline and multiline if statements,
252
252
  # also, we can't wrap the whole statement since it's void value, which means we'd have to introduce
253
253
  # the idea of multiple wrappings for the same line, which I just don't care enough about to consider
254
- expect(values_for("def meth \n return 1 if true \n end \n meth")).to eq [[], ['1'], [], ['1']] # records true instead of 1
255
- expect(values_for("def meth \n return 1 if false \n end \n meth")).to eq [[], ['nil'], [], ['nil']] # records false instead of nil
254
+ expect(values_for("def meth \n return 1 if true \n end \n meth")).to eq [[], ['1'], [':meth'], ['1']] # records true instead of 1
255
+ expect(values_for("def meth \n return 1 if false \n end \n meth")).to eq [[], ['nil'], [':meth'], ['nil']] # records false instead of nil
256
256
  end
257
257
 
258
258
  it 'does not try to record the keyword next' do
@@ -332,10 +332,23 @@ RSpec.describe SeeingIsBelieving do
332
332
  expect(result.stdout).to eq filename
333
333
  end
334
334
 
335
- specify 'cwd of the file is the cwd of the evaluating program' do
336
- filename = File.join proving_grounds_path, 'mah_file.rb'
337
- FileUtils.rm_f filename
338
- expect(invoke('print File.realdirpath(Dir.pwd)', filename: filename).stdout).to eq Dir.pwd
335
+ describe 'cwd' do
336
+ let(:filename) { File.join proving_grounds_path, 'mah_file.rb' }
337
+ before { FileUtils.rm_f filename }
338
+
339
+ it 'defaults to the cwd of the the evaluating program' do
340
+ dir = Dir.chdir '/' do
341
+ invoke('print File.expand_path(Dir.pwd)', filename: filename).stdout
342
+ end
343
+ expect(dir).to eq File.expand_path('/')
344
+ end
345
+
346
+ it 'is the file\'s directory when local_cwd is set' do
347
+ dir = Dir.chdir '/' do
348
+ invoke('print File.expand_path(Dir.pwd)', filename: filename, local_cwd: true).stdout
349
+ end
350
+ expect(dir).to eq proving_grounds_path
351
+ end
339
352
  end
340
353
 
341
354
  it 'does not capture output from __END__ onward' do
@@ -518,6 +531,17 @@ RSpec.describe SeeingIsBelieving do
518
531
  expect(result.exception.message).to match /recursive/i
519
532
  end
520
533
 
534
+ it 'does not blow up when returning an object that recursively responds to everything' do
535
+ result = invoke('obj = BasicObject.new
536
+ def obj.method_missing(*) self; end
537
+ obj')
538
+ expect(result[3][0]).to start_with '#<BasicObject:'
539
+ end
540
+
541
+ it 'does not blow up when the first line looks like it might have a magic comment in it (#126)' do
542
+ expect(values_for "1+1 # and a comment with 'Accept-Encoding: gzip' in it").to eq [['2']]
543
+ end
544
+
521
545
  it 'makes the SeeingIsBelieving::VERSION available to the program' do
522
546
  expect(values_for "SeeingIsBelieving::VERSION").to eq [[SeeingIsBelieving::VERSION.inspect]]
523
547
  end
@@ -530,6 +554,21 @@ RSpec.describe SeeingIsBelieving do
530
554
  expect(values_for 'o = BasicObject.new; def o.inspect; "some obj"; end; o').to eq [['some obj']]
531
555
  end
532
556
 
557
+ it 'sees refined inspect (#128)' do
558
+ result = invoke <<-RUBY
559
+ module BinMeUp
560
+ refine Integer do
561
+ def inspect
562
+ "%08b" % self
563
+ end
564
+ end
565
+ end
566
+ using BinMeUp
567
+ 5
568
+ RUBY
569
+ expect(result[9]).to eq ['00000101']
570
+ end
571
+
533
572
  it 'respects timeout, even when children do semi-ridiculous things, it cleans up children rather than orphaning them' do
534
573
  pre = Time.now
535
574
  result = invoke <<-CHILD, timeout_seconds: 0.5
@@ -611,6 +650,18 @@ RSpec.describe SeeingIsBelieving do
611
650
  end
612
651
  end
613
652
 
653
+ # Okay, doesn't totally belong here, b/c there could be another implementation which doesn't overwrite files,
654
+ # but it requires coordination between the rewritten code and the evaluator, so isn't really an evaluator unit test,
655
+ # so putting this here.
656
+ context 'cleaning up files' do
657
+ it 'replaces the rewritten source with the original source as soon as it sees a result' do
658
+ program = "sleep 0.01 while File.read(__FILE__).include?('RECORD_RESULT'.downcase); File.read __FILE__\n"
659
+ result = invoke program
660
+ expect(result[1]).to eq [program.inspect]
661
+ expect(File.exist? result.filename).to eq false
662
+ end
663
+ end
664
+
614
665
 
615
666
  context 'when given a debugger' do
616
667
  let(:stream) { StringIO.new }
@@ -766,33 +817,33 @@ RSpec.describe SeeingIsBelieving do
766
817
  end').stderr).to eq ''
767
818
  end
768
819
 
769
- specify 'when String does not have ==, to_s, inspect, to_i' do
820
+ specify 'when String does not have ==, to_s, inspect, to_i, ===' do
770
821
  expect(invoke('class String
771
822
  undef ==
772
823
  undef to_s
773
824
  undef to_str
774
825
  undef inspect
775
826
  undef to_i
776
- end').stderr).to eq ''
827
+ end
828
+ class << String
829
+ undef ===
830
+ end
831
+ ').stderr).to eq ''
777
832
  end
778
833
 
779
- specify 'when Fixnum does not have <, <<, next, ==, inspect, to_s' do
780
- if defined? Fixnum
781
- result = invoke('class Fixnum
782
- undef <
783
- undef <<
784
- undef ==
785
- def next
786
- "redefining instead of undefing b/c it comes from Integer"
787
- end
788
- undef to_s
789
- undef inspect
790
- end
791
- "a"')
792
- expect(result.stderr).to eq ''
793
- expect(result.exitstatus).to eq 0
794
- expect(result.to_a.last).to eq ['"a"']
795
- end
834
+ specify 'when Integer does not have <, <<, next, ==, inspect, to_s' do
835
+ result = invoke('class Integer
836
+ undef <
837
+ undef <<
838
+ undef ==
839
+ undef next
840
+ undef to_s
841
+ undef inspect
842
+ end
843
+ "a"')
844
+ expect(result.stderr).to eq ''
845
+ expect(result.exitstatus).to eq 0
846
+ expect(result.to_a.last).to eq ['"a"']
796
847
  end
797
848
 
798
849
  specify 'when Integer does not have <, <<, next, ==, inspect, to_s' do
@@ -968,5 +1019,76 @@ RSpec.describe SeeingIsBelieving do
968
1019
  end
969
1020
  ').stderr).to eq ''
970
1021
  end
1022
+
1023
+ def executes_without_error!(code)
1024
+ result = invoke(code)
1025
+ expect(result.stderr).to eq ''
1026
+ begin
1027
+ expect(result).to_not have_exception, result.exception.pretty_inspect
1028
+ rescue NoMethodError
1029
+ require 'pp'
1030
+ retry
1031
+ end
1032
+ result
1033
+ end
1034
+
1035
+ specify 'when constants are reassigned' do
1036
+ result = executes_without_error!('
1037
+ # producer
1038
+ Hash = "fake Hash"
1039
+ String = "fake String"
1040
+ class << String
1041
+ undef ===
1042
+ end
1043
+ Exception = "fake Exception"
1044
+
1045
+ kernel = Kernel
1046
+ Kernel = "fake Kernel"
1047
+ SystemExit = "fake SystemExit"
1048
+ NoMethodError = "fake NoMethodError"
1049
+ Marshal = "fake Marshal"
1050
+ TypeError = "fake TypeError"
1051
+
1052
+ # only matter when forking
1053
+ Thread = "fake Thread"
1054
+ IOError = "fake IOError"
1055
+ Errno::EPIPE = "fake Errno::EPIPE" # we don\'t actually put it in a situation to hit this
1056
+ Errno = "fake Errno"
1057
+
1058
+ # the matrix
1059
+ Exception = "Exception"
1060
+ Symbol = "Symbol"
1061
+
1062
+ # normal inspect
1063
+ 1 + 1
1064
+
1065
+ # force it down the sad path
1066
+ obj = Object.new
1067
+ def obj.inspect
1068
+ "pass"
1069
+ end
1070
+ obj
1071
+ puts __LINE__-1 # so we know where to assert
1072
+
1073
+ NotAString = Module.new
1074
+ def obj.inspect
1075
+ NotAString
1076
+ end
1077
+ obj
1078
+
1079
+ def obj.inspect
1080
+ raise "whoops"
1081
+ end
1082
+ obj
1083
+ puts __LINE__-1 # should be replaced with Kernel#inspect
1084
+
1085
+ kernel.exit 0
1086
+ ')
1087
+
1088
+ pass_str, kernel_inspect = result.stdout.lines.map(&:to_i)
1089
+ expect(result[pass_str]).to eq ['pass']
1090
+ expect(result[kernel_inspect].size).to eq 1
1091
+ expect(result[kernel_inspect][0]).to match /^#<Object/
1092
+ end
971
1093
  end
972
1094
  end