seeing_is_believing 3.3.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|