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.
@@ -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