rspec-interactive 0.2.0 → 0.6.0
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 +4 -4
- data/Gemfile.lock +10 -10
- data/README.md +35 -31
- data/bin/rspec-interactive +17 -1
- data/bin/test +2 -0
- data/examples/failing_spec.rb +0 -4
- data/examples/other_passing_spec.rb +11 -0
- data/examples/spec_with_syntax_error.rb +5 -0
- data/lib/rspec-interactive.rb +84 -73
- data/lib/rspec-interactive/config.rb +23 -0
- data/lib/rspec-interactive/rspec_command.rb +2 -45
- data/lib/rspec-interactive/{config_cache.rb → rspec_config_cache.rb} +0 -0
- data/lib/rspec-interactive/runner.rb +37 -45
- data/lib/rspec-interactive/version.rb +1 -1
- data/rspec-interactive.gemspec +3 -3
- data/scripts/release.sh +25 -0
- data/scripts/run-with-local-dep.sh +36 -0
- data/tests/debugged_spec_test.rb +75 -0
- data/tests/eof_test.rb +10 -0
- data/tests/example_file_test.rb +54 -0
- data/tests/failing_spec_test.rb +41 -0
- data/tests/glob_test.rb +18 -0
- data/tests/line_number_test.rb +19 -0
- data/tests/passing_spec_test.rb +18 -0
- data/tests/rerun_failed_specs_test.rb +90 -0
- data/tests/spec_with_syntax_error_test.rb +30 -0
- data/tests/support/ansi.rb +32 -0
- data/tests/support/test_helper.rb +286 -0
- metadata +28 -23
data/tests/glob_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'support/test_helper'
|
2
|
+
|
3
|
+
Test.test "passing spec" do
|
4
|
+
await_prompt
|
5
|
+
input "rspec examples/*passing*_spec.rb"
|
6
|
+
await_prompt
|
7
|
+
input "exit"
|
8
|
+
await_termination
|
9
|
+
expect_output <<~EOF
|
10
|
+
[1] pry(main)> rspec examples/*passing*_spec.rb
|
11
|
+
....
|
12
|
+
|
13
|
+
Finished in 0 seconds (files took 0 seconds to load)
|
14
|
+
4 examples, 0 failures
|
15
|
+
|
16
|
+
[2] pry(main)> exit
|
17
|
+
EOF
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative 'support/test_helper'
|
2
|
+
|
3
|
+
Test.test "running example group at line number" do
|
4
|
+
await_prompt
|
5
|
+
input "rspec examples/passing_spec.rb:8"
|
6
|
+
await_prompt
|
7
|
+
input "exit"
|
8
|
+
await_termination
|
9
|
+
expect_output <<~EOF
|
10
|
+
[1] pry(main)> rspec examples/passing_spec.rb:8
|
11
|
+
Run options: include {:locations=>{"./examples/passing_spec.rb"=>[8]}}
|
12
|
+
.
|
13
|
+
|
14
|
+
Finished in 0 seconds (files took 0 seconds to load)
|
15
|
+
1 example, 0 failures
|
16
|
+
|
17
|
+
[2] pry(main)> exit
|
18
|
+
EOF
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'support/test_helper'
|
2
|
+
|
3
|
+
Test.test "passing spec" do
|
4
|
+
await_prompt
|
5
|
+
input "rspec examples/passing_spec.rb"
|
6
|
+
await_prompt
|
7
|
+
input "exit"
|
8
|
+
await_termination
|
9
|
+
expect_output <<~EOF
|
10
|
+
[1] pry(main)> rspec examples/passing_spec.rb
|
11
|
+
..
|
12
|
+
|
13
|
+
Finished in 0 seconds (files took 0 seconds to load)
|
14
|
+
2 examples, 0 failures
|
15
|
+
|
16
|
+
[2] pry(main)> exit
|
17
|
+
EOF
|
18
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require_relative 'support/test_helper'
|
2
|
+
|
3
|
+
examples = Tempfile.new('examples')
|
4
|
+
|
5
|
+
config = Tempfile.new('config')
|
6
|
+
config.write <<~EOF
|
7
|
+
RSpec.configuration.example_status_persistence_file_path = "#{examples.path}"
|
8
|
+
EOF
|
9
|
+
config.rewind
|
10
|
+
|
11
|
+
Test.test "failing spec with example file", config_path: config.path do
|
12
|
+
await_prompt
|
13
|
+
|
14
|
+
RSpec.configuration.backtrace_exclusion_patterns = [ /.*/ ]
|
15
|
+
RSpec.configuration.backtrace_inclusion_patterns = [ /examples\/failing_spec.rb/ ]
|
16
|
+
|
17
|
+
input "rspec examples/failing_spec.rb examples/passing_spec.rb"
|
18
|
+
await_prompt
|
19
|
+
|
20
|
+
RSpec.configuration.backtrace_exclusion_patterns = [ /.*/ ]
|
21
|
+
RSpec.configuration.backtrace_inclusion_patterns = [ /examples\/failing_spec.rb/ ]
|
22
|
+
|
23
|
+
input "rspec examples/failing_spec.rb examples/passing_spec.rb --only-failures"
|
24
|
+
await_prompt
|
25
|
+
input "exit"
|
26
|
+
await_termination
|
27
|
+
expect_output <<~EOF
|
28
|
+
[1] pry(main)> rspec examples/failing_spec.rb examples/passing_spec.rb
|
29
|
+
F..
|
30
|
+
|
31
|
+
Failures:
|
32
|
+
|
33
|
+
1) example spec fails
|
34
|
+
Failure/Error: expect(true).to eq(false)
|
35
|
+
|
36
|
+
expected: false
|
37
|
+
got: true
|
38
|
+
|
39
|
+
(compared using ==)
|
40
|
+
|
41
|
+
Diff:
|
42
|
+
@@ -1 +1 @@
|
43
|
+
-false
|
44
|
+
+true
|
45
|
+
# ./examples/failing_spec.rb:5:in `block (2 levels) in <top (required)>'
|
46
|
+
|
47
|
+
Finished in 0 seconds (files took 0 seconds to load)
|
48
|
+
3 examples, 1 failure
|
49
|
+
|
50
|
+
Failed examples:
|
51
|
+
|
52
|
+
rspec ./examples/failing_spec.rb:4 # example spec fails
|
53
|
+
|
54
|
+
Rerun failures by executing the previous command with --only-failures or --next-failure.
|
55
|
+
|
56
|
+
[2] pry(main)> rspec examples/failing_spec.rb examples/passing_spec.rb --only-failures
|
57
|
+
Run options: include {:last_run_status=>"failed"}
|
58
|
+
F
|
59
|
+
|
60
|
+
Failures:
|
61
|
+
|
62
|
+
1) example spec fails
|
63
|
+
Failure/Error: expect(true).to eq(false)
|
64
|
+
|
65
|
+
expected: false
|
66
|
+
got: true
|
67
|
+
|
68
|
+
(compared using ==)
|
69
|
+
|
70
|
+
Diff:
|
71
|
+
@@ -1 +1 @@
|
72
|
+
-false
|
73
|
+
+true
|
74
|
+
# ./examples/failing_spec.rb:5:in `block (2 levels) in <top (required)>'
|
75
|
+
|
76
|
+
Finished in 0 seconds (files took 0 seconds to load)
|
77
|
+
1 example, 1 failure
|
78
|
+
|
79
|
+
Failed examples:
|
80
|
+
|
81
|
+
rspec ./examples/failing_spec.rb:4 # example spec fails
|
82
|
+
|
83
|
+
Rerun failures by executing the previous command with --only-failures or --next-failure.
|
84
|
+
|
85
|
+
[3] pry(main)> exit
|
86
|
+
EOF
|
87
|
+
end
|
88
|
+
|
89
|
+
config.close
|
90
|
+
examples.close
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'support/test_helper'
|
2
|
+
|
3
|
+
RSpec.configuration.backtrace_exclusion_patterns = [ /.*/ ]
|
4
|
+
RSpec.configuration.backtrace_inclusion_patterns = [ /`load_spec_files'/ ]
|
5
|
+
|
6
|
+
Test.test "spec with syntax error" do
|
7
|
+
await_prompt
|
8
|
+
input "rspec examples/spec_with_syntax_error.rb"
|
9
|
+
await_prompt
|
10
|
+
input "exit"
|
11
|
+
await_termination
|
12
|
+
expect_equal "output", output.gsub(/.+(?=(\\|\/)[a-z_-]+[.]rb:[0-9]+:.*)/, ' [...]'), <<~EOF
|
13
|
+
[1] pry(main)> rspec examples/spec_with_syntax_error.rb
|
14
|
+
|
15
|
+
An error occurred while loading ./examples/spec_with_syntax_error.rb.
|
16
|
+
Failure/Error: ::RSpec.configuration.load_spec_files
|
17
|
+
|
18
|
+
SyntaxError:
|
19
|
+
[...]/spec_with_syntax_error.rb:5: unterminated string meets end of file
|
20
|
+
[...]/spec_with_syntax_error.rb:5: syntax error, unexpected end-of-input, expecting `end'
|
21
|
+
[...]/configuration.rb:1607:in `load_spec_files'
|
22
|
+
No examples found.
|
23
|
+
|
24
|
+
|
25
|
+
Finished in 0 seconds (files took 0 seconds to load)
|
26
|
+
0 examples, 0 failures, 1 error occurred outside of examples
|
27
|
+
|
28
|
+
[2] pry(main)> exit
|
29
|
+
EOF
|
30
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Ansi
|
2
|
+
@ansi_colors = {
|
3
|
+
:black => '0;30',
|
4
|
+
:red => '0;31',
|
5
|
+
:green => '0;32',
|
6
|
+
:orange => '0;33',
|
7
|
+
:blue => '0;34',
|
8
|
+
:purple => '0;35',
|
9
|
+
:cyan => '0;36',
|
10
|
+
:light_gray => '0;37',
|
11
|
+
:dark_gray => '1;30',
|
12
|
+
:light_red => '1;31',
|
13
|
+
:light_green => '1;32',
|
14
|
+
:yellow => '1;33',
|
15
|
+
:light_blue => '1;34',
|
16
|
+
:light_purple => '1;35',
|
17
|
+
:light_cyan => '1;36',
|
18
|
+
:white => '1;37'
|
19
|
+
}
|
20
|
+
|
21
|
+
def self.puts(color, string, output = STDOUT)
|
22
|
+
raise "invalid color: #{color}" unless @ansi_colors[color]
|
23
|
+
string = "" if string == nil
|
24
|
+
output.puts "\033[#{@ansi_colors[color]}m#{string}\033[0m"
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.print(color, string, output = STDOUT)
|
28
|
+
raise "invalid color: #{color}" unless @ansi_colors[color]
|
29
|
+
return unless string
|
30
|
+
output.print "\033[#{@ansi_colors[color]}m#{string}\033[0m"
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
ENV['TERM'] = 'dumb'
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
require 'readline'
|
5
|
+
require 'rspec/core'
|
6
|
+
require 'rspec-interactive'
|
7
|
+
require 'timeout'
|
8
|
+
require 'time'
|
9
|
+
require_relative 'ansi'
|
10
|
+
|
11
|
+
class Time
|
12
|
+
@start = Time.parse('1982-08-05 07:21:00 -0500')
|
13
|
+
|
14
|
+
def self.now
|
15
|
+
@start
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module RSpec::Core
|
20
|
+
class Time
|
21
|
+
def self.now
|
22
|
+
::Time.now
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Output
|
28
|
+
def initialize
|
29
|
+
@output = ""
|
30
|
+
@pos = 0
|
31
|
+
end
|
32
|
+
|
33
|
+
def print(string)
|
34
|
+
return if string == nil || string == "\e[0G"
|
35
|
+
@output += string
|
36
|
+
end
|
37
|
+
|
38
|
+
def puts(string = "")
|
39
|
+
string = "" if string == nil
|
40
|
+
print(string + "\n")
|
41
|
+
end
|
42
|
+
|
43
|
+
def closed?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
47
|
+
def tty?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
def flush
|
52
|
+
end
|
53
|
+
|
54
|
+
def next_string
|
55
|
+
output = @output[@pos..-1]
|
56
|
+
@pos = @output.size
|
57
|
+
output
|
58
|
+
end
|
59
|
+
|
60
|
+
def string
|
61
|
+
@output
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module Readline
|
66
|
+
class << self
|
67
|
+
attr_accessor :error
|
68
|
+
end
|
69
|
+
|
70
|
+
@original_readline = method(:readline)
|
71
|
+
|
72
|
+
def self.reset
|
73
|
+
@next_response = []
|
74
|
+
@signal = ConditionVariable.new
|
75
|
+
@mutex = Mutex.new
|
76
|
+
@state = :waiting_for_prompt
|
77
|
+
@error = nil
|
78
|
+
end
|
79
|
+
|
80
|
+
reset
|
81
|
+
|
82
|
+
def self.readline(prompt = nil, save_history = nil)
|
83
|
+
temp = nil
|
84
|
+
input_read = nil
|
85
|
+
@mutex.synchronize do
|
86
|
+
Thread.current.kill if @error
|
87
|
+
|
88
|
+
if @state != :waiting_for_prompt
|
89
|
+
@error = "prompted in invalid state: #{@state}"
|
90
|
+
Thread.current.kill
|
91
|
+
end
|
92
|
+
|
93
|
+
@state = :prompted
|
94
|
+
@signal.signal
|
95
|
+
@signal.wait(@mutex, 1)
|
96
|
+
|
97
|
+
if @state != :response_available
|
98
|
+
@error = "sending response in invalid state: #{@state}"
|
99
|
+
Thread.current.kill
|
100
|
+
end
|
101
|
+
@state = :waiting_for_prompt
|
102
|
+
|
103
|
+
if @next_response.empty?
|
104
|
+
@error = "readline response signaled with nothing to respond with"
|
105
|
+
Thread.current.kill
|
106
|
+
end
|
107
|
+
if @next_response.size > 1
|
108
|
+
@error = "readline response signaled with too much to respond with"
|
109
|
+
Thread.current.kill
|
110
|
+
end
|
111
|
+
|
112
|
+
response = @next_response[0]
|
113
|
+
@next_response.clear
|
114
|
+
|
115
|
+
temp = Tempfile.new('input')
|
116
|
+
|
117
|
+
unless response.nil?
|
118
|
+
temp.write("#{response}\n")
|
119
|
+
temp.rewind
|
120
|
+
end
|
121
|
+
|
122
|
+
input_read = File.new(temp.path, 'r')
|
123
|
+
Readline.input = input_read
|
124
|
+
|
125
|
+
@next_response.clear
|
126
|
+
|
127
|
+
@original_readline.call(prompt, save_history)
|
128
|
+
end
|
129
|
+
ensure
|
130
|
+
temp&.close
|
131
|
+
input_read&.close
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.await_readline
|
135
|
+
@mutex.synchronize do
|
136
|
+
raise @error if @error
|
137
|
+
@signal.wait(@mutex, 1)
|
138
|
+
raise @error if @error
|
139
|
+
if @state != :prompted
|
140
|
+
@error = "timed out waiting for prompt"
|
141
|
+
raise @error
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.puts(string)
|
147
|
+
@mutex.synchronize do
|
148
|
+
raise @error if @error
|
149
|
+
if @state != :prompted
|
150
|
+
@error = "puts called in invalid state: #{@state}"
|
151
|
+
raise @error
|
152
|
+
end
|
153
|
+
@next_response << string
|
154
|
+
@state = :response_available
|
155
|
+
@signal.signal
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def self.ctrl_d
|
160
|
+
puts(nil)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class Test
|
165
|
+
|
166
|
+
def self.test(name, config_path: nil, &block)
|
167
|
+
Test.new.run(name, config_path, &block)
|
168
|
+
end
|
169
|
+
|
170
|
+
def run(name, config_path, &block)
|
171
|
+
puts "running: #{name}"
|
172
|
+
|
173
|
+
@output_temp_file = Tempfile.new('output')
|
174
|
+
@output_write = File.open(@output_temp_file.path, 'w')
|
175
|
+
|
176
|
+
@error_temp_file = Tempfile.new('error')
|
177
|
+
@error_write = File.open(@error_temp_file.path, 'w')
|
178
|
+
|
179
|
+
@history_temp_file = Tempfile.new('history')
|
180
|
+
|
181
|
+
@interactive_thread = Thread.start do
|
182
|
+
begin
|
183
|
+
@result = RSpec::Interactive.start(
|
184
|
+
config_file: config_path,
|
185
|
+
history_file: @history_temp_file.path,
|
186
|
+
input_stream: STDIN,
|
187
|
+
output_stream: @output_write,
|
188
|
+
error_stream: @error_write)
|
189
|
+
ensure
|
190
|
+
RSpec.clear_examples
|
191
|
+
RSpec.reset
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
begin
|
196
|
+
instance_eval &block
|
197
|
+
rescue Exception => e
|
198
|
+
failed = true
|
199
|
+
STDERR.puts e.message
|
200
|
+
e.backtrace[0..5].each { |line| STDERR.puts " #{line}" }
|
201
|
+
end
|
202
|
+
|
203
|
+
await_termination
|
204
|
+
|
205
|
+
if Readline.error
|
206
|
+
failed = true
|
207
|
+
STDOUT.puts Readline.error
|
208
|
+
end
|
209
|
+
|
210
|
+
if failed
|
211
|
+
Ansi.puts :red, "failed: #{name}"
|
212
|
+
else
|
213
|
+
Ansi.puts :green, "passed: #{name}"
|
214
|
+
end
|
215
|
+
puts
|
216
|
+
ensure
|
217
|
+
@output_write.close
|
218
|
+
@output_temp_file.close
|
219
|
+
|
220
|
+
@error_write.close
|
221
|
+
@error_temp_file.close
|
222
|
+
|
223
|
+
@history_temp_file.close
|
224
|
+
|
225
|
+
Readline.reset
|
226
|
+
Pry.reset_defaults
|
227
|
+
end
|
228
|
+
|
229
|
+
def await_termination
|
230
|
+
Timeout.timeout(5) do
|
231
|
+
@interactive_thread.join
|
232
|
+
end
|
233
|
+
rescue Timeout::Error => e
|
234
|
+
@interactive_thread.kill
|
235
|
+
raise "timed out waiting for interactive session to terminate"
|
236
|
+
end
|
237
|
+
|
238
|
+
def await_prompt
|
239
|
+
Readline.await_readline
|
240
|
+
end
|
241
|
+
|
242
|
+
def input(string)
|
243
|
+
Readline.puts(string)
|
244
|
+
end
|
245
|
+
|
246
|
+
def ctrl_d
|
247
|
+
Readline.ctrl_d
|
248
|
+
end
|
249
|
+
|
250
|
+
def output
|
251
|
+
@output_temp_file.rewind
|
252
|
+
File.read(@output_temp_file.path).gsub("\e[0G", "")
|
253
|
+
end
|
254
|
+
|
255
|
+
def error_output
|
256
|
+
@error_write.flush
|
257
|
+
@error_temp_file.rewind
|
258
|
+
File.read(@error_temp_file.path)
|
259
|
+
end
|
260
|
+
|
261
|
+
def expect_history(expected)
|
262
|
+
@history_temp_file.rewind
|
263
|
+
history = File.read(@history_temp_file.path)
|
264
|
+
if expected != history
|
265
|
+
raise "unexpected history:\n expected: #{expected.inspect}\n actual: #{history.inspect}"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def expect_output(expected)
|
270
|
+
expect_equal("output", output, expected)
|
271
|
+
end
|
272
|
+
|
273
|
+
def expect_error_output(expected)
|
274
|
+
expect_equal("error output", error_output, expected)
|
275
|
+
end
|
276
|
+
|
277
|
+
def expect_result(expected)
|
278
|
+
expect_equal("result", @result, expected)
|
279
|
+
end
|
280
|
+
|
281
|
+
def expect_equal(name, actual, expected)
|
282
|
+
if expected != actual
|
283
|
+
raise "unexpected #{name}:\n expected: #{expected.inspect}\n actual: #{actual.inspect}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|