seeing_is_believing 3.6.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -5
  3. data/Rakefile +2 -0
  4. data/Readme.md +1 -1
  5. data/appveyor.yml +3 -5
  6. data/features/regression.feature +7 -0
  7. data/features/xmpfilter-style.feature +1 -0
  8. data/lib/seeing_is_believing.rb +2 -1
  9. data/lib/seeing_is_believing/binary.rb +1 -1
  10. data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +1 -0
  11. data/lib/seeing_is_believing/binary/comment_lines.rb +1 -1
  12. data/lib/seeing_is_believing/binary/commentable_lines.rb +7 -7
  13. data/lib/seeing_is_believing/binary/config.rb +1 -3
  14. data/lib/seeing_is_believing/binary/remove_annotations.rb +1 -1
  15. data/lib/seeing_is_believing/code.rb +3 -3
  16. data/lib/seeing_is_believing/evaluate_by_moving_files.rb +30 -32
  17. data/lib/seeing_is_believing/event_stream/consumer.rb +2 -0
  18. data/lib/seeing_is_believing/event_stream/events.rb +7 -0
  19. data/lib/seeing_is_believing/event_stream/handlers/record_exit_events.rb +3 -3
  20. data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +2 -1
  21. data/lib/seeing_is_believing/event_stream/producer.rb +14 -0
  22. data/lib/seeing_is_believing/safe.rb +1 -7
  23. data/lib/seeing_is_believing/swap_files.rb +90 -0
  24. data/lib/seeing_is_believing/the_matrix.rb +17 -4
  25. data/lib/seeing_is_believing/version.rb +1 -1
  26. data/lib/seeing_is_believing/wrap_expressions.rb +22 -8
  27. data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +3 -0
  28. data/seeing_is_believing.gemspec +4 -5
  29. data/spec/binary/config_spec.rb +2 -2
  30. data/spec/evaluate_by_moving_files_spec.rb +18 -17
  31. data/spec/event_stream_spec.rb +1 -0
  32. data/spec/seeing_is_believing_spec.rb +99 -24
  33. data/spec/sib_spec_helpers/version.rb +17 -0
  34. data/spec/spec_helper.rb +2 -18
  35. data/spec/wrap_expressions_spec.rb +9 -3
  36. metadata +26 -12
  37. data/lib/seeing_is_believing/backup_file.rb +0 -50
  38. data/lib/seeing_is_believing/customize_pp.rb +0 -5
@@ -48,23 +48,32 @@ Kernel.module_eval do
48
48
  private
49
49
 
50
50
  define_method :warn do |*args, &block|
51
- $stderr.puts *args
51
+ $stderr.puts(*args)
52
52
  end
53
53
 
54
+ alias :exec :exec # disable warning
54
55
  define_method :exec do |*args, &block|
55
56
  $SiB.record_exec(args)
56
57
  finish.call
57
58
  real_exec.call(*args, &block)
58
59
  end
59
60
 
61
+ alias :exit! :exit! # disable warning
60
62
  define_method :exit! do |status=false|
61
63
  finish.call
62
64
  real_exit_bang.call(status)
63
65
  end
64
66
 
67
+ alias :fork :fork
65
68
  define_method :fork, &fork_defn
66
69
  end
67
70
 
71
+ module Process
72
+ class << self
73
+ alias :fork :fork
74
+ end
75
+ end
76
+
68
77
  Kernel.define_singleton_method :fork, &fork_defn
69
78
  Process.define_singleton_method :fork, &fork_defn
70
79
 
@@ -74,10 +83,14 @@ symbol_to_s = Symbol.instance_method(:to_s)
74
83
  exception_message = Exception.instance_method(:message)
75
84
  exception_backtrace = Exception.instance_method(:backtrace)
76
85
 
86
+ # Guarding against hostile users (e.g. me) that do ridiculous things like blowing away these constants
87
+ exception_class = Exception
88
+ symbol_class = Symbol
89
+
77
90
  at_exit do
78
- Exception.class_eval { define_method :message, exception_message }
79
- Exception.class_eval { define_method :backtrace, exception_backtrace }
80
- Symbol.class_eval { define_method :to_s, symbol_to_s }
91
+ exception_class.class_eval { define_method :message, exception_message }
92
+ exception_class.class_eval { define_method :backtrace, exception_backtrace }
93
+ symbol_class.class_eval { define_method :to_s, symbol_to_s }
81
94
  exitstatus = ($! ? $SiB.record_exception(nil, $!) : 0)
82
95
  finish.call
83
96
  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.6.1'
2
+ VERSION = '4.0.0'
3
3
  end
@@ -25,7 +25,7 @@ class SeeingIsBelieving
25
25
 
26
26
  wrappings = wrappings().sort_by(&:first)
27
27
 
28
- wrappings.each do |line_num, (range, last_col, meta)|
28
+ wrappings.each do |line_num, (range, _last_col, meta)|
29
29
  case meta
30
30
  when :total_fucking_failure
31
31
  rewriter.replace range, '.....TOTAL FUCKING FAILURE!.....'
@@ -34,11 +34,11 @@ class SeeingIsBelieving
34
34
  end
35
35
  end
36
36
 
37
- wrappings.each do |line_num, (range, last_col, meta)|
37
+ wrappings.each do |line_num, (range, _last_col, _meta)|
38
38
  rewriter.insert_before range, before_each.call(line_num)
39
39
  end
40
40
 
41
- wrappings.each do |line_num, (range, last_col, meta)|
41
+ wrappings.each do |line_num, (range, _last_col, _meta)|
42
42
  rewriter.insert_after range, after_each.call(line_num)
43
43
  end
44
44
 
@@ -53,8 +53,13 @@ class SeeingIsBelieving
53
53
  attr_accessor :before_all, :after_all, :before_each, :after_each
54
54
  attr_accessor :code, :wrappings
55
55
 
56
- def buffer() code.buffer end
57
- def rewriter() code.rewriter end
56
+ def buffer
57
+ code.buffer
58
+ end
59
+
60
+ def rewriter
61
+ code.rewriter
62
+ end
58
63
 
59
64
  def root_range
60
65
  code.root.location.expression
@@ -107,7 +112,10 @@ class SeeingIsBelieving
107
112
  when :defs, :module
108
113
  add_to_wrappings ast
109
114
  add_children ast, true
110
- when :rescue, :ensure, :return, :break, :next, :splat, :kwsplat
115
+ when :ensure, :return, :break, :next, :splat, :kwsplat
116
+ add_children ast
117
+ when :rescue
118
+ add_to_wrappings ast if inline_rescue? ast
111
119
  add_children ast
112
120
  when :class
113
121
  name, * = ast.children
@@ -135,8 +143,8 @@ class SeeingIsBelieving
135
143
  add_to_wrappings ast
136
144
  the_begin = ast.location.begin
137
145
  if !the_begin
138
- # uhhhh, idk, nothing seems to hit this branch (at least in its spec)
139
- # maybe this is 'a = 1,2,3'? (I'd try it out, but have to call the folks)
146
+ # a = 1,2,3
147
+ add_children ast
140
148
  elsif the_begin.source !~ /\A%/
141
149
  # normal array
142
150
  add_children ast
@@ -247,5 +255,11 @@ class SeeingIsBelieving
247
255
  add_children ast
248
256
  end
249
257
  end
258
+
259
+ def inline_rescue?(ast)
260
+ primary_code, rescue_body, _else_body = ast.children
261
+ return false unless primary_code
262
+ primary_code.loc.expression.last_line == rescue_body.loc.expression.first_line
263
+ end
250
264
  end
251
265
  end
@@ -5,6 +5,9 @@ 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
  },
@@ -12,19 +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.5.0"
23
- s.add_dependency "childprocess","~> 0.8.0"
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.2.0"
26
+ s.add_development_dependency "rake", "~> 13.0.0"
28
27
  s.add_development_dependency "mrspec", "~> 0.3.1"
29
28
  s.add_development_dependency "rspec", "~> 3.6.0"
30
29
  s.add_development_dependency "cucumber", "~> 2.4"
@@ -257,8 +257,8 @@ RSpec.describe SeeingIsBelieving::Binary::Config do
257
257
  expect(parse([]).lib_options.require_files).to eq [matrix_file]
258
258
  end
259
259
 
260
- it 'appends pp and our customizations for xmpfilter style' do
261
- 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']
262
262
  end
263
263
 
264
264
  specify '-r and --require set an error if not provided with a filename' do
@@ -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
@@ -149,7 +149,7 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
149
149
 
150
150
  it 'must provide an event handler, which receives the process\'s events' do
151
151
  # raises error
152
- expect { described_class.new("", filename, {}) }
152
+ expect { described_class.new(filename, "", "", {}) }
153
153
  .to raise_error ArgumentError, /event_handler/
154
154
 
155
155
  # sees all the events
@@ -164,6 +164,7 @@ RSpec.describe SeeingIsBelieving::EvaluateByMovingFiles do
164
164
  result = invoke <<-RUBY, timeout_seconds: 0.5
165
165
  child_pid = spawn 'ruby', '-e', 'sleep' # child makes a grandchild which sleeps
166
166
  puts Process.pid, child_pid # print ids so we can check they got killed
167
+ $stdout.flush
167
168
  sleep # child sleeps
168
169
  RUBY
169
170
  post = Time.now
@@ -664,6 +664,7 @@ module SeeingIsBelieving::EventStream
664
664
  [Events::StderrClosed , :stderr_closed],
665
665
  [Events::EventStreamClosed, :event_stream_closed],
666
666
  [Events::Finished , :finished],
667
+ [Events::FileLoaded , :file_loaded],
667
668
  ]
668
669
  pairs.each { |klass, name| expect(klass.event_name).to eq name }
669
670
 
@@ -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
@@ -557,7 +557,7 @@ RSpec.describe SeeingIsBelieving do
557
557
  it 'sees refined inspect (#128)' do
558
558
  result = invoke <<-RUBY
559
559
  module BinMeUp
560
- refine Fixnum do
560
+ refine Integer do
561
561
  def inspect
562
562
  "%08b" % self
563
563
  end
@@ -569,10 +569,6 @@ RSpec.describe SeeingIsBelieving do
569
569
  expect(result[9]).to eq ['00000101']
570
570
  end
571
571
 
572
- it 'works when the exception does not have a backtrace (#134)' do
573
-
574
- end
575
-
576
572
  it 'respects timeout, even when children do semi-ridiculous things, it cleans up children rather than orphaning them' do
577
573
  pre = Time.now
578
574
  result = invoke <<-CHILD, timeout_seconds: 0.5
@@ -654,6 +650,18 @@ RSpec.describe SeeingIsBelieving do
654
650
  end
655
651
  end
656
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
+
657
665
 
658
666
  context 'when given a debugger' do
659
667
  let(:stream) { StringIO.new }
@@ -823,23 +831,19 @@ RSpec.describe SeeingIsBelieving do
823
831
  ').stderr).to eq ''
824
832
  end
825
833
 
826
- specify 'when Fixnum does not have <, <<, next, ==, inspect, to_s' do
827
- if defined? Fixnum
828
- result = invoke('class Fixnum
829
- undef <
830
- undef <<
831
- undef ==
832
- def next
833
- "redefining instead of undefing b/c it comes from Integer"
834
- end
835
- undef to_s
836
- undef inspect
837
- end
838
- "a"')
839
- expect(result.stderr).to eq ''
840
- expect(result.exitstatus).to eq 0
841
- expect(result.to_a.last).to eq ['"a"']
842
- 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"']
843
847
  end
844
848
 
845
849
  specify 'when Integer does not have <, <<, next, ==, inspect, to_s' do
@@ -1015,5 +1019,76 @@ RSpec.describe SeeingIsBelieving do
1015
1019
  end
1016
1020
  ').stderr).to eq ''
1017
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
1018
1093
  end
1019
1094
  end
@@ -0,0 +1,17 @@
1
+ module SibSpecHelpers
2
+ class Version
3
+ attr_reader :segments
4
+ include Comparable
5
+ def initialize(version_string)
6
+ @segments = version_string.scan(/\d+/).map(&:to_i)
7
+ end
8
+ def <=>(other)
9
+ other = Version.new other unless other.kind_of? Version
10
+ segments.zip(other.segments).each do |ours, theirs|
11
+ return 1 if theirs.nil? || theirs < ours
12
+ return -1 if ours < theirs
13
+ end
14
+ segments.length <=> other.segments.length
15
+ end
16
+ end
17
+ end