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.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +39 -0
- data/{Readme.md → README.md} +9 -12
- data/Rakefile +12 -23
- data/appveyor.yml +3 -1
- data/features/flags.feature +251 -1
- data/features/regression.feature +166 -23
- data/features/xmpfilter-style.feature +1 -0
- data/lib/seeing_is_believing.rb +5 -2
- data/lib/seeing_is_believing/binary.rb +6 -1
- data/lib/seeing_is_believing/binary/annotate_end_of_file.rb +1 -1
- data/lib/seeing_is_believing/binary/annotate_marked_lines.rb +8 -5
- data/lib/seeing_is_believing/binary/comment_lines.rb +2 -2
- data/lib/seeing_is_believing/binary/commentable_lines.rb +7 -7
- data/lib/seeing_is_believing/binary/config.rb +21 -4
- data/lib/seeing_is_believing/binary/engine.rb +58 -1
- data/lib/seeing_is_believing/binary/format_comment.rb +9 -6
- data/lib/seeing_is_believing/binary/remove_annotations.rb +1 -1
- data/lib/seeing_is_believing/code.rb +5 -5
- data/lib/seeing_is_believing/compatibility.rb +28 -0
- data/lib/seeing_is_believing/evaluate_by_moving_files.rb +52 -71
- data/lib/seeing_is_believing/event_stream/consumer.rb +26 -28
- data/lib/seeing_is_believing/event_stream/events.rb +7 -0
- data/lib/seeing_is_believing/event_stream/handlers/record_exit_events.rb +3 -3
- data/lib/seeing_is_believing/event_stream/handlers/update_result.rb +2 -1
- data/lib/seeing_is_believing/event_stream/producer.rb +29 -5
- data/lib/seeing_is_believing/safe.rb +8 -7
- data/lib/seeing_is_believing/swap_files.rb +90 -0
- data/lib/seeing_is_believing/the_matrix.rb +17 -4
- data/lib/seeing_is_believing/version.rb +1 -1
- data/lib/seeing_is_believing/wrap_expressions.rb +27 -13
- data/lib/seeing_is_believing/wrap_expressions_with_inspect.rb +4 -1
- data/seeing_is_believing.gemspec +4 -6
- data/spec/binary/config_spec.rb +63 -35
- data/spec/binary/engine_spec.rb +11 -1
- data/spec/binary/format_comment_spec.rb +5 -1
- data/spec/evaluate_by_moving_files_spec.rb +35 -18
- data/spec/event_stream_spec.rb +15 -0
- data/spec/seeing_is_believing_spec.rb +148 -26
- data/spec/sib_spec_helpers/version.rb +17 -0
- data/spec/spec_helper.rb +2 -18
- data/spec/wrap_expressions_spec.rb +9 -2
- metadata +31 -31
- data/.travis.yml +0 -10
- data/lib/seeing_is_believing/customize_pp.rb +0 -5
- 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
|
-
|
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
|
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
|
180
|
-
str = Marshal.load
|
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 =
|
182
|
+
event_name = shift_token(line).intern
|
187
183
|
case event_name
|
188
184
|
when :result
|
189
|
-
line_number =
|
190
|
-
type =
|
191
|
-
inspected =
|
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 =
|
195
|
-
type =
|
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:
|
200
|
-
class_name:
|
201
|
-
message:
|
202
|
-
backtrace:
|
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 =
|
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:
|
204
|
+
Events::NumLines.new(value: shift_token(line).to_i)
|
209
205
|
when :sib_version
|
210
|
-
Events::SiBVersion.new(value:
|
206
|
+
Events::SiBVersion.new(value: shift_string(line))
|
211
207
|
when :ruby_version
|
212
|
-
Events::RubyVersion.new(value:
|
208
|
+
Events::RubyVersion.new(value: shift_string(line))
|
213
209
|
when :filename
|
214
|
-
Events::Filename.new(value:
|
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:
|
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(
|
11
|
-
@
|
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
|
-
@
|
21
|
+
@next_handler.call(event)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
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)
|
67
|
+
inspected = yield(value)
|
53
68
|
else
|
54
|
-
inspected = value.inspect
|
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
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
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
|
-
|
79
|
-
|
80
|
-
|
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)
|
@@ -25,25 +25,25 @@ class SeeingIsBelieving
|
|
25
25
|
|
26
26
|
wrappings = wrappings().sort_by(&:first)
|
27
27
|
|
28
|
-
wrappings.each do |line_num, (range,
|
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.
|
33
|
+
rewriter.insert_before range, '~' # Regexp#~
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
wrappings.each do |line_num, (range,
|
38
|
-
rewriter.
|
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,
|
42
|
-
rewriter.
|
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.
|
46
|
-
rewriter.
|
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
|
57
|
-
|
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 :
|
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
|
-
#
|
139
|
-
|
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
|