rspec-interactive 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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