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
@@ -4,6 +4,10 @@ require 'seeing_is_believing/event_stream/events'
4
4
  require 'seeing_is_believing/error'
5
5
  require 'thread'
6
6
 
7
+ # Polyfill String#scrub on Ruby 2.0.0
8
+ require 'seeing_is_believing/compatibility'
9
+ using SeeingIsBelieving::Compatibility
10
+
7
11
  class SeeingIsBelieving
8
12
  module EventStream
9
13
  class Consumer
@@ -60,15 +64,7 @@ class SeeingIsBelieving
60
64
  rescue EncodingError
61
65
  str = str.force_encoding(Encoding::UTF_8)
62
66
  end
63
- return str.scrub('�') if str.respond_to? :scrub
64
- # basically reimplement scrub, b/c it's not implemented on 2.0.0
65
- str.each_char.inject("") do |new_str, char|
66
- if char.valid_encoding?
67
- new_str << char
68
- else
69
- new_str << '�'
70
- end
71
- end
67
+ str.scrub('�')
72
68
  end
73
69
 
74
70
  def initialize(streams)
@@ -169,51 +165,53 @@ class SeeingIsBelieving
169
165
  end
170
166
  end
171
167
 
172
- def extract_token(line)
168
+ def shift_token(line)
173
169
  event_name = line[/[^ ]+/]
174
170
  line.sub!(/^\s*[^ ]+\s*/, '')
175
171
  event_name
176
172
  end
177
173
 
178
174
  # For a consideration of many different ways of passing the message, see 5633064
179
- def extract_string(line)
180
- str = Marshal.load extract_token(line).unpack('m0').first
175
+ def shift_string(line)
176
+ str = Marshal.load shift_token(line).unpack('m0').first
181
177
  Consumer.fix_encoding(str)
182
178
  end
183
179
 
184
180
  def event_for(original_line)
185
181
  line = original_line.chomp
186
- event_name = extract_token(line).intern
182
+ event_name = shift_token(line).intern
187
183
  case event_name
188
184
  when :result
189
- line_number = extract_token(line).to_i
190
- type = extract_token(line).intern
191
- inspected = extract_string(line)
185
+ line_number = shift_token(line).to_i
186
+ type = shift_token(line).intern
187
+ inspected = shift_string(line)
192
188
  Events::LineResult.new(type: type, line_number: line_number, inspected: inspected)
193
189
  when :maxed_result
194
- line_number = extract_token(line).to_i
195
- type = extract_token(line).intern
190
+ line_number = shift_token(line).to_i
191
+ type = shift_token(line).intern
196
192
  Events::ResultsTruncated.new(type: type, line_number: line_number)
197
193
  when :exception
198
194
  Events::Exception.new \
199
- line_number: extract_token(line).to_i,
200
- class_name: extract_string(line),
201
- message: extract_string(line),
202
- backtrace: extract_token(line).to_i.times.map { extract_string line }
195
+ line_number: shift_token(line).to_i,
196
+ class_name: shift_string(line),
197
+ message: shift_string(line),
198
+ backtrace: shift_token(line).to_i.times.map { shift_string line }
203
199
  when :max_line_captures
204
- token = extract_token(line)
200
+ token = shift_token(line)
205
201
  value = token =~ /infinity/i ? Float::INFINITY : token.to_i
206
202
  Events::MaxLineCaptures.new(value: value)
207
203
  when :num_lines
208
- Events::NumLines.new(value: extract_token(line).to_i)
204
+ Events::NumLines.new(value: shift_token(line).to_i)
209
205
  when :sib_version
210
- Events::SiBVersion.new(value: extract_string(line))
206
+ Events::SiBVersion.new(value: shift_string(line))
211
207
  when :ruby_version
212
- Events::RubyVersion.new(value: extract_string(line))
208
+ Events::RubyVersion.new(value: shift_string(line))
213
209
  when :filename
214
- Events::Filename.new(value: extract_string(line))
210
+ Events::Filename.new(value: shift_string(line))
211
+ when :file_loaded
212
+ Events::FileLoaded.new
215
213
  when :exec
216
- Events::Exec.new(args: extract_string(line))
214
+ Events::Exec.new(args: shift_string(line))
217
215
  else
218
216
  raise UnknownEvent, original_line.inspect
219
217
  end
@@ -58,6 +58,13 @@ class SeeingIsBelieving
58
58
  attributes :value
59
59
  end
60
60
 
61
+ # For knowing when you can perform certain tasks (eg moving the file back)
62
+ class FileLoaded < Event
63
+ def self.event_name
64
+ :file_loaded
65
+ end
66
+ end
67
+
61
68
  # Number of lines in the program.
62
69
  class NumLines < Event
63
70
  def self.event_name
@@ -7,8 +7,8 @@ class SeeingIsBelieving
7
7
  attr_reader :exitstatus
8
8
  attr_reader :timeout_seconds
9
9
 
10
- def initialize(next_observer)
11
- @next_observer = next_observer
10
+ def initialize(next_handler)
11
+ @next_handler = next_handler
12
12
  end
13
13
 
14
14
  def call(event)
@@ -18,7 +18,7 @@ class SeeingIsBelieving
18
18
  when Events::Timeout
19
19
  @timeout_seconds = event.seconds
20
20
  end
21
- @next_observer.call(event)
21
+ @next_handler.call(event)
22
22
  end
23
23
  end
24
24
  end
@@ -29,7 +29,8 @@ class SeeingIsBelieving
29
29
  Finished,
30
30
  StdoutClosed,
31
31
  StderrClosed,
32
- EventStreamClosed
32
+ EventStreamClosed,
33
+ FileLoaded
33
34
  # no op
34
35
  else raise "Unknown event: #{event.inspect}"
35
36
  end
@@ -7,6 +7,17 @@ using SeeingIsBelieving::Safe
7
7
  class SeeingIsBelieving
8
8
  module EventStream
9
9
  class Producer
10
+
11
+ # Guarding against hostile users (e.g. me) that do ridiculous things like blowing away these constants
12
+ old_w, $-w = $-w, nil # Ruby warns about accessing deprecated constants
13
+ Object.constants.each do |name|
14
+ next if name == :SortedSet # Removed in 3.0, but apparently the constant still exists, it just explodes if you reference it
15
+ const_set name, Object.const_get(name)
16
+ end
17
+ $-w = old_w
18
+
19
+ ErrnoEPIPE = Errno::EPIPE # not actually tested, but we can see it is referenced below
20
+
10
21
  module NullQueue
11
22
  extend self
12
23
  Queue.instance_methods(false).each do |name|
@@ -40,6 +51,10 @@ class SeeingIsBelieving
40
51
  queue << "max_line_captures #{max_line_captures}"
41
52
  end
42
53
 
54
+ def file_loaded
55
+ queue << "file_loaded"
56
+ end
57
+
43
58
  StackErrors = [SystemStackError]
44
59
  StackErrors << Java::JavaLang::StackOverflowError if defined?(RUBY_PLATFORM) && RUBY_PLATFORM == 'java'
45
60
  def record_result(type, line_number, value)
@@ -49,9 +64,13 @@ class SeeingIsBelieving
49
64
  if count < max_line_captures
50
65
  begin
51
66
  if block_given?
52
- inspected = yield(value).to_str
67
+ inspected = yield(value)
53
68
  else
54
- inspected = value.inspect.to_str
69
+ inspected = value.inspect
70
+ end
71
+ unless String === inspected
72
+ inspected = inspected.to_str
73
+ raise unless String === inspected
55
74
  end
56
75
  rescue *StackErrors
57
76
  # this is necessary because SystemStackError won't show the backtrace of the method we tried to call
@@ -76,9 +95,11 @@ class SeeingIsBelieving
76
95
  # records the exception, returns the exitstatus for that exception
77
96
  def record_exception(line_number, exception)
78
97
  return exception.status if SystemExit === exception # TODO === is not in the list
79
- if !line_number && filename
80
- begin line_number = exception.backtrace.grep(/#{filename.to_s}/).first[/:\d+/][1..-1].to_i
81
- rescue NoMethodError
98
+ unless line_number
99
+ if filename
100
+ begin line_number = exception.backtrace.grep(/#{filename.to_s}/).first[/:\d+/][1..-1].to_i
101
+ rescue NoMethodError
102
+ end
82
103
  end
83
104
  end
84
105
  line_number ||= -1
@@ -118,6 +139,9 @@ class SeeingIsBelieving
118
139
  # for a consideration of many different ways of doing this, see 5633064
119
140
  def to_string_token(string)
120
141
  [Marshal.dump(string.to_s)].pack('m0')
142
+ rescue TypeError => err
143
+ raise unless err.message =~ /singleton can't be dumped/
144
+ to_string_token string.to_s.dup
121
145
  end
122
146
 
123
147
  def build_producer_thread(resultstream)
@@ -1,6 +1,13 @@
1
1
  # require this before anything else, b/c it expects the world to be sane when it is loaded
2
2
  class SeeingIsBelieving
3
3
  module Safe
4
+
5
+ # Subclasses must refine before superclasses in older Rubies, otherwise
6
+ # it finds the superclass method and behaves unexpectedly.
7
+ refine String.singleton_class do
8
+ alias === ===
9
+ end
10
+
4
11
  refine Class do
5
12
  alias === ===
6
13
  end
@@ -43,13 +50,7 @@ class SeeingIsBelieving
43
50
  alias to_str to_str
44
51
  end
45
52
 
46
- # in 2.4 we should use Integer instead, but it's not obvious to me how
47
- # to detect this. eg defined?(Fixnum) returns "constant". Accessing it
48
- # leads to a warning, but SiB turns warnings off so you don't see it.
49
- # So.... for now, it incidentally doesn't do anything annoying, but would
50
- # be good to figure out something better (eg if we ever wanted to allow the
51
- # user to decide whether warnings should display or not)
52
- refine Fixnum do
53
+ refine Integer do
53
54
  alias to_s to_s
54
55
  alias next next
55
56
  alias < <
@@ -0,0 +1,90 @@
1
+ require 'seeing_is_believing/error'
2
+ require 'seeing_is_believing/hard_core_ensure'
3
+
4
+ class SeeingIsBelieving
5
+ class SwapFiles
6
+ # Might honeslty make more sense to break this out into 2 different classes.
7
+ # We've got to do some confusing state accounting since there are really 2
8
+ # algorithms here:
9
+ #
10
+ # if the file exists:
11
+ # make sure there isn't a backup (could cause the user to lose their file)
12
+ # back the file up
13
+ # write the rewritten code to the file
14
+ # if we are told to show the user program
15
+ # move the backup over the top of the rewritten file
16
+ # do nothing in the ensure block
17
+ # else
18
+ # in the ensure block: move the backup over the top of the rewritten file
19
+ # if the file DNE:
20
+ # write the rewritten code to the file
21
+ # if we are told to show the user program
22
+ # write the user program to the file
23
+ # delete the file in the ensure block
24
+ def self.call(*args, &block)
25
+ new(*args, &block).call
26
+ end
27
+
28
+ def initialize(file_path, backup_path, user_program, rewritten_program, &block)
29
+ self.file_path = file_path
30
+ self.block = block
31
+ self.backup_path = backup_path
32
+ self.user_program = user_program
33
+ self.rewritten_program = rewritten_program
34
+ end
35
+
36
+ def call
37
+ HardCoreEnsure.call \
38
+ code: -> {
39
+ File.exist? backup_path and
40
+ raise TempFileAlreadyExists.new(file_path, backup_path)
41
+
42
+ @has_file = File.exist? file_path
43
+
44
+ if @has_file
45
+ File.rename file_path, backup_path
46
+ @needs_restore = true
47
+ end
48
+
49
+ save_file rewritten_program
50
+
51
+ block.call self
52
+ },
53
+ ensure: -> {
54
+ set_back_to_initial_conditions
55
+ }
56
+ end
57
+
58
+ def show_user_program
59
+ if @needs_restore
60
+ restore
61
+ else
62
+ save_file user_program
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ attr_accessor :block, :file_path, :backup_path, :rewritten_program, :user_program
69
+
70
+ def restore
71
+ File.rename(backup_path, file_path)
72
+ @needs_restore = false
73
+ end
74
+
75
+
76
+ def save_file(program)
77
+ File.open file_path, 'w', external_encoding: "utf-8" do |f|
78
+ f.write program.to_s
79
+ end
80
+ end
81
+
82
+ def set_back_to_initial_conditions
83
+ if @needs_restore
84
+ restore
85
+ elsif !@has_file
86
+ File.delete(file_path)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -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.3.0'
2
+ VERSION = '4.0.1'
3
3
  end
@@ -25,25 +25,25 @@ 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!.....'
32
32
  when :match_current_line
33
- rewriter.insert_before_multi range, '~' # Regexp#~
33
+ rewriter.insert_before range, '~' # Regexp#~
34
34
  end
35
35
  end
36
36
 
37
- wrappings.each do |line_num, (range, last_col, meta)|
38
- rewriter.insert_before_multi range, before_each.call(line_num)
37
+ wrappings.each do |line_num, (range, _last_col, _meta)|
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)|
42
- rewriter.insert_after_multi range, after_each.call(line_num)
41
+ wrappings.each do |line_num, (range, _last_col, _meta)|
42
+ rewriter.insert_after range, after_each.call(line_num)
43
43
  end
44
44
 
45
- rewriter.insert_before_multi root_range, before_all.call
46
- rewriter.insert_after_multi root_range, after_all_text
45
+ rewriter.insert_before root_range, before_all.call
46
+ rewriter.insert_after root_range, after_all_text
47
47
  rewriter.process
48
48
  end
49
49
  end
@@ -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