rspec-interactive 0.8.1 → 0.9.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 255c2aca56cb003fad2c6e7e5f72cb75b2ed82f1820edb42294ded182dbdd25c
4
- data.tar.gz: afb9f469028aa3a4388585e6d34e281ce1bae8fe7227e73ba0939f2cf3585255
3
+ metadata.gz: 17f3d7cb90e4271d19e1eb1a835a9c759084d12baf1f7404bedc521d8ff54047
4
+ data.tar.gz: 98688109728bbaa18e0b64339987fc0d2320331095ad09f2761d2162254ad868
5
5
  SHA512:
6
- metadata.gz: abae1515ca7dade3476088e21fef619f1cdda2b3b126f0a19db30b0aabd2f7250bee9874f11cccf5a836d39dd45d7b6a5848003a32637bf2eb925c4d229622dc
7
- data.tar.gz: 90c4118df931f6e30c3af286f8fb9a1a382bd30dfcd3d052480943fa4f305975784c87e51fddba17de4343ef0748169c07831dc52e7f31190efefef516216efa
6
+ metadata.gz: f0fbe89b3308f29db1302f28b265c0374a6b5c586b32b5c2bee2351d035faaf82c5f5d0362bb95158966d5ea9354de404dc126efd7b899d1bb9d2f8bee40edde
7
+ data.tar.gz: a8bbeaf1ca9e8f51b975cde6bf51be7cc5c6a57b1266bc4ee527c1e1ffcf995b9ac8c78ac77fe1a8ca79b937c4c31d712c9d58119ca4283854da8a6c4f78ccc9
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  rspec-interactive-*.gem
2
2
  .rspec_interactive_history
3
3
  .rspec_interactive_config
4
+ .idea
data/Gemfile.lock CHANGED
@@ -1,33 +1,43 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-interactive (0.8.0)
4
+ rspec-interactive (0.9.1)
5
5
  listen
6
6
  pry
7
7
  rspec-core
8
+ rspec-teamcity (= 1.0.0)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
13
  coderay (1.1.3)
13
14
  diff-lcs (1.4.4)
14
- ffi (1.15.3)
15
- listen (3.5.1)
15
+ ffi (1.15.5)
16
+ listen (3.7.1)
16
17
  rb-fsevent (~> 0.10, >= 0.10.3)
17
18
  rb-inotify (~> 0.9, >= 0.9.10)
18
19
  method_source (1.0.0)
19
20
  pry (0.14.1)
20
21
  coderay (~> 1.1)
21
22
  method_source (~> 1.0)
22
- rb-fsevent (0.11.0)
23
+ rb-fsevent (0.11.1)
23
24
  rb-inotify (0.10.1)
24
25
  ffi (~> 1.0)
26
+ rspec (3.9.0)
27
+ rspec-core (~> 3.9.0)
28
+ rspec-expectations (~> 3.9.0)
29
+ rspec-mocks (~> 3.9.0)
25
30
  rspec-core (3.9.3)
26
31
  rspec-support (~> 3.9.3)
27
32
  rspec-expectations (3.9.4)
28
33
  diff-lcs (>= 1.2.0, < 2.0)
29
34
  rspec-support (~> 3.9.0)
35
+ rspec-mocks (3.9.1)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.9.0)
30
38
  rspec-support (3.9.4)
39
+ rspec-teamcity (1.0.0)
40
+ rspec (>= 2.99, >= 2.14.2, < 4)
31
41
 
32
42
  PLATFORMS
33
43
  x86_64-darwin-20
data/README.md CHANGED
@@ -69,10 +69,10 @@ See: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#ra
69
69
 
70
70
  ## Usage
71
71
 
72
- Optionally, specify a config file with `--config <config-file>`. Optionally, specify arguments to an initial RSpec invocation with `--initial-rspec-args <initial-rspec-args>`.
72
+ Optionally, specify a config file with `--config <config-file>`.
73
73
 
74
74
  ```shell
75
- bundle exec rspec-interactive [--config <config-file>] [--initial-rspec-args <initial-rspec-args>]
75
+ bundle exec rspec-interactive [--config <config-file>]
76
76
  ```
77
77
 
78
78
  ## Example Usage In This Repo
@@ -83,12 +83,6 @@ Start:
83
83
  bundle exec rspec-interactive
84
84
  ```
85
85
 
86
- Start with an initial RSpec invocation:
87
-
88
- ```shell
89
- bundle exec rspec-interactive --initial-rspec-args examples/passing_spec.rb
90
- ```
91
-
92
86
  Run a passing spec:
93
87
 
94
88
  ```shell
@@ -3,18 +3,27 @@
3
3
  require 'optparse'
4
4
  require 'rspec-interactive'
5
5
 
6
- @options = {}
6
+ @options = {
7
+ server: true,
8
+ server_port: RSpec::Interactive::DEFAULT_PORT
9
+ }
10
+
7
11
  parser = OptionParser.new do |opts|
8
12
  opts.banner = "Starts an interactive RSpec shell.\n\n"\
9
- "Usage: bundle exec rspec-interactive [-c config-file] <initial-rspec-args>"
13
+ "Usage: bundle exec rspec-interactive [--config config-file] [--no-server] [--port <port>]"
10
14
 
11
15
  opts.on("-c", "--config <config-file>", String, "Optional. Path to the RSpec Interactive config file.") do |config_file|
12
16
  @options[:config_file] = config_file
13
17
  end
14
18
 
15
- opts.on("-r", "--initial-rspec-args <initial-rspec-args>", String, "Optional. Arguments to pass to an initial invocation of RSpec.") do |initial_rspec_args|
16
- @options[:initial_rspec_args] = initial_rspec_args
19
+ opts.on("--no-server", "Optional. Disable server.") do
20
+ @options[:server] = false
21
+ end
22
+
23
+ opts.on("-p", "--port <port>", Integer, "Optional. Server port. Defaults to #{RSpec::Interactive::DEFAULT_PORT}.") do |port|
24
+ @options[:port] = port
17
25
  end
26
+
18
27
  end.parse!
19
28
 
20
- RSpec::Interactive.start(config_file: @options[:config_file], initial_rspec_args: @options[:initial_rspec_args])
29
+ RSpec::Interactive.start(config_file: @options[:config_file], server: @options[:server], port: @options[:server_port])
@@ -0,0 +1,7 @@
1
+ RSpec::Interactive.configure do |config|
2
+ config.watch_dirs += ['lib']
3
+
4
+ config.configure_rspec do
5
+ RSpec.configuration.formatter = :documentation
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ module RSpec
2
+ module Interactive
3
+ class ClientOutput
4
+ def initialize(client)
5
+ @client = client
6
+ end
7
+
8
+ def puts(str = "")
9
+ @client.puts(str&.to_s || '')
10
+ end
11
+
12
+ def string
13
+ @output
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ require 'pry'
2
+
3
+ class Pry
4
+ alias_method :old_eval, :eval
5
+
6
+ def eval(line, options = {})
7
+ RSpec::Interactive.eval do
8
+ old_eval(line, options)
9
+ end
10
+ end
11
+ end
12
+
@@ -0,0 +1,15 @@
1
+ require 'rspec/core'
2
+
3
+ module RSpec
4
+ module Core
5
+ class Example
6
+ alias_method :old_run, :run
7
+
8
+ def run(example_group_instance, reporter)
9
+ execution_result.started_at = RSpec::Core::Time.now
10
+ old_run(example_group_instance, reporter)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,39 @@
1
+ module RSpec
2
+ module Interactive
3
+ class Stdio
4
+ def self.capture(output)
5
+ raise ArgumentError, 'missing block' unless block_given?
6
+
7
+ stdout, stderr = STDOUT.dup, STDERR.dup
8
+
9
+ IO.pipe do |stdout_read, stdout_write|
10
+ IO.pipe do |stderr_read, stderr_write|
11
+ STDOUT.reopen(stdout_write)
12
+ STDERR.reopen(stderr_write)
13
+
14
+ stdout_write.close
15
+ stderr_write.close
16
+
17
+ thread = Thread.new do
18
+ until stdout_read.eof? && stderr_read.eof? do
19
+ line = stdout_read.gets
20
+ output.puts line if line
21
+ line = stderr_read.gets
22
+ output.puts if line
23
+ end
24
+ end
25
+
26
+ begin
27
+ yield
28
+ ensure
29
+ STDOUT.reopen stdout
30
+ STDERR.reopen stderr
31
+ end
32
+
33
+ thread.join
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module Interactive
5
- VERSION = "0.8.1"
5
+ VERSION = "0.9.2"
6
6
  end
7
7
  end
@@ -4,19 +4,26 @@ require 'pry'
4
4
  require 'readline'
5
5
  require 'rspec/core'
6
6
  require 'shellwords'
7
+ require 'socket'
8
+ require 'teamcity/spec/runner/formatter/teamcity/formatter'
7
9
 
8
- require 'rspec-interactive/runner'
10
+ require 'rspec-interactive/client_output'
9
11
  require 'rspec-interactive/config'
10
- require 'rspec-interactive/rspec_config_cache'
11
12
  require 'rspec-interactive/input_completer'
13
+ require 'rspec-interactive/pry'
12
14
  require 'rspec-interactive/refresh_command'
13
15
  require 'rspec-interactive/rspec_command'
16
+ require 'rspec-interactive/rspec_config_cache'
17
+ require 'rspec-interactive/rspec_core_example'
14
18
  require 'rspec-interactive/rubo_cop_command'
19
+ require 'rspec-interactive/runner'
20
+ require 'rspec-interactive/stdio'
15
21
 
16
22
  module RSpec
17
23
  module Interactive
18
24
 
19
25
  DEFAULT_HISTORY_FILE = '.rspec_interactive_history'.freeze
26
+ DEFAULT_PORT = 5678
20
27
 
21
28
  class << self
22
29
  attr_accessor :configuration
@@ -26,11 +33,20 @@ module RSpec
26
33
  block.call(@configuration)
27
34
  end
28
35
 
29
- def self.start(config_file: nil, initial_rspec_args: nil, history_file: DEFAULT_HISTORY_FILE, input_stream: STDIN, output_stream: STDOUT, error_stream: STDERR)
36
+ def self.start(
37
+ config_file: nil,
38
+ server: false,
39
+ port: DEFAULT_PORT,
40
+ history_file: DEFAULT_HISTORY_FILE,
41
+ input_stream: STDIN,
42
+ output_stream: STDOUT,
43
+ error_stream: STDERR)
44
+
30
45
  @history_file = history_file
31
46
  @updated_files = []
32
47
  @stty_save = %x`stty -g`.chomp
33
- @mutex = Mutex.new
48
+ @file_change_mutex = Mutex.new
49
+ @command_mutex = Mutex.new
34
50
  @output_stream = output_stream
35
51
  @input_stream = input_stream
36
52
  @error_stream = error_stream
@@ -40,21 +56,34 @@ module RSpec
40
56
  load config_file if config_file
41
57
 
42
58
  check_rails
43
- trap_interrupt
44
59
  configure_pry
45
60
 
46
- @init_thread = Thread.start {
47
- @config_cache.record_configuration { @configuration.configure_rspec.call }
48
- start_file_watcher
49
- }
61
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
62
+ @config_cache.record_configuration { @configuration.configure_rspec.call }
63
+ end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
64
+ if end_time - start_time > 5
65
+ @output_stream.puts "Configuring RSpec took #{(end_time - start_time).round} seconds."
66
+ end
67
+
68
+ start_file_watcher
69
+
70
+ if server
71
+ @output_stream.puts "Listening on port #{port}."
72
+ server_thread = Thread.start do
73
+ server = TCPServer.new port
50
74
 
51
- if initial_rspec_args
52
- open(@history_file, 'a') { |f| f.puts "rspec #{initial_rspec_args.strip}" }
53
- rspec Shellwords.split(initial_rspec_args)
75
+ while client = server.accept
76
+ request = client.gets
77
+ args = Shellwords.split(request)
78
+ rspec_for_server(client, args)
79
+ client.close
80
+ end
81
+ end
54
82
  end
55
83
 
56
84
  Pry.start
57
85
  @listener.stop if @listener
86
+ server_thread.exit if server_thread
58
87
  0
59
88
  end
60
89
 
@@ -66,23 +95,11 @@ module RSpec
66
95
  end
67
96
  end
68
97
 
69
- def self.configure_rspec
98
+ def self.configure_rspec(error_stream: @error_stream, output_stream: @output_stream)
70
99
  RSpec.configure do |config|
71
- config.error_stream = @error_stream
72
- config.output_stream = @output_stream
73
- end
74
- end
75
-
76
- def self.trap_interrupt
77
- trap('INT') do
78
- if @runner
79
- # We are on a different thread. There is a race here. Ignore nil.
80
- @runner&.quit
81
- else
82
- @output_stream.puts
83
- system "stty", @stty_save
84
- exit!(0)
85
- end
100
+ config.error_stream = error_stream
101
+ config.output_stream = output_stream
102
+ config.start_time = RSpec::Core::Time.now
86
103
  end
87
104
  end
88
105
 
@@ -91,7 +108,7 @@ module RSpec
91
108
 
92
109
  # Only polling seems to work in Docker.
93
110
  @listener = Listen.to(*@configuration.watch_dirs, only: /\.rb$/, force_polling: true) do |modified, added|
94
- @mutex.synchronize do
111
+ @file_change_mutex.synchronize do
95
112
  @updated_files.concat(added + modified)
96
113
  end
97
114
  end
@@ -99,9 +116,6 @@ module RSpec
99
116
  end
100
117
 
101
118
  def self.configure_pry
102
- # Prevent Pry from trapping too. It will break ctrl-c handling.
103
- Pry.config.should_trap_interrupts = false
104
-
105
119
  # Set up IO.
106
120
  Pry.config.input = Readline
107
121
  Pry.config.output = @output_stream
@@ -115,7 +129,7 @@ module RSpec
115
129
  end
116
130
 
117
131
  def self.refresh
118
- @mutex.synchronize do
132
+ @file_change_mutex.synchronize do
119
133
  @updated_files.uniq.each do |filename|
120
134
  @output_stream.puts "changed: #{filename}"
121
135
  trace = TracePoint.new(:class) do |tp|
@@ -131,8 +145,8 @@ module RSpec
131
145
  @configuration.refresh.call
132
146
  end
133
147
 
134
- def self.rspec(args)
135
- parsed_args = args.flat_map do |arg|
148
+ def self.parse_args(args)
149
+ args.flat_map do |arg|
136
150
  if arg.match(/[\*\?\[]/)
137
151
  glob = Dir.glob(arg)
138
152
  glob.empty? ? [arg] : glob
@@ -140,15 +154,10 @@ module RSpec
140
154
  [arg]
141
155
  end
142
156
  end
157
+ end
143
158
 
144
- # Initialize the runner before waiting for the init thread so that the interrupt
145
- # handler will cancel the RSpec invocation rather than kill the app.
146
- @runner = RSpec::Interactive::Runner.new(parsed_args)
147
-
148
- if @init_thread&.alive?
149
- @init_thread.join
150
- @init_thread = nil
151
- end
159
+ def self.rspec(args)
160
+ @runner = RSpec::Interactive::Runner.new(parse_args(args))
152
161
 
153
162
  refresh
154
163
 
@@ -169,21 +178,71 @@ module RSpec
169
178
  RSpec.clear_examples
170
179
  RSpec.reset
171
180
  @config_cache.replay_configuration
181
+ rescue Interrupt => e
182
+ @runner&.quit
183
+ raise e
172
184
  ensure
173
185
  @runner = nil
174
186
  end
175
187
 
176
- def self.rubo_cop(args)
177
- if @init_thread&.alive?
178
- @init_thread.join
179
- @init_thread = nil
188
+ def self.rspec_for_server(client, args)
189
+ @command_mutex.synchronize do
190
+ # Prevent the debugger from being used. The server isn't interactive.
191
+ disable_pry = ENV['DISABLE_PRY']
192
+ ENV['DISABLE_PRY'] = 'true'
193
+
194
+ output = ClientOutput.new(client)
195
+ Stdio.capture(ClientOutput.new(client)) do
196
+ @runner = RSpec::Interactive::Runner.new(parse_args(args))
197
+
198
+ refresh
199
+
200
+ # RSpec::Interactive-specific RSpec configuration
201
+ configure_rspec
202
+
203
+ # RubyMine specifies --format. That causes a formatter to be added. It does not override
204
+ # the existing formatter (if one is set by default). Clear any formatters but resetting
205
+ # the loader.
206
+ RSpec.configuration.instance_variable_set(
207
+ :@formatter_loader,
208
+ RSpec::Core::Formatters::Loader.new(RSpec::Core::Reporter.new(RSpec.configuration)))
209
+
210
+ # Always use the teamcity formatter, even though RubyMine always specifies it.
211
+ # This make manual testing of rspec-interactive easier.
212
+ RSpec.configuration.formatter = Spec::Runner::Formatter::TeamcityFormatter
213
+
214
+ # Run.
215
+ exit_code = @runner.run
216
+
217
+ # Reset
218
+ RSpec.clear_examples
219
+ RSpec.reset
220
+ @config_cache.replay_configuration
221
+ end
222
+ ensure
223
+ ENV['DISABLE_PRY'] = disable_pry
180
224
  end
225
+ end
181
226
 
227
+ def self.rubo_cop(args)
182
228
  if defined?(RuboCop)
183
229
  RuboCop::CLI.new.run args
184
230
  else
185
231
  @error_stream.puts "fatal: RuboCop not found. Is the gem installed in this project?"
186
232
  end
187
233
  end
234
+
235
+ def self.eval(&block)
236
+ if Thread.current.thread_variable_get('holding_lock')
237
+ yield
238
+ else
239
+ @command_mutex.synchronize do
240
+ Thread.current.thread_variable_set('holding_lock', true)
241
+ yield
242
+ ensure
243
+ Thread.current.thread_variable_set('holding_lock', false)
244
+ end
245
+ end
246
+ end
188
247
  end
189
248
  end
@@ -0,0 +1,10 @@
1
+ require 'rspec/teamcity'
2
+
3
+ module Spec
4
+ module Runner
5
+ module Formatter
6
+ class TeamcityFormatter
7
+ end
8
+ end
9
+ end
10
+ end
@@ -26,6 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.require_paths = ["lib"]
27
27
 
28
28
  spec.add_dependency 'rspec-core'
29
+ spec.add_dependency 'rspec-teamcity', '1.0.0'
29
30
  spec.add_dependency 'listen'
30
31
  spec.add_dependency 'pry'
31
32
  end
data/runner/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ ruby '3.0.0'
4
+
5
+ source "https://rubygems.org"
@@ -0,0 +1,14 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+
5
+ PLATFORMS
6
+ x86_64-darwin-20
7
+
8
+ DEPENDENCIES
9
+
10
+ RUBY VERSION
11
+ ruby 3.0.0p0
12
+
13
+ BUNDLED WITH
14
+ 2.3.6
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'shellwords'
5
+ require 'socket'
6
+
7
+ @options = {
8
+ host: 'localhost',
9
+ port: 5678
10
+ }
11
+
12
+ parser = OptionParser.new do |opts|
13
+ opts.banner = "Executes RSpec by connecting to a running RSpec Interactive shell.\n\n"\
14
+ "Usage: bundle exec rspec-interactive-run [--host <host>] [--port <port>] [rspec-args]"
15
+
16
+ opts.on("-h", "--host <host>", String, "Optional. Server host. Defaults to localhost.") do |host|
17
+ @options[:host] = host
18
+ end
19
+
20
+ opts.on("-p", "--port <port>", Integer, "Optional. Server port. Defaults to 5678.") do |port|
21
+ @options[:port] = port
22
+ end
23
+
24
+ end.parse
25
+
26
+ server = TCPSocket.open(@options[:host], @options[:port])
27
+ server.puts ARGV.map{ |arg| Shellwords.escape arg }.join(' ')
28
+ while response = server.gets do
29
+ puts response
30
+ end
31
+ server.close
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-interactive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-28 00:00:00.000000000 Z
11
+ date: 2022-02-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec-core
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec-teamcity
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: listen
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,21 +84,30 @@ files:
70
84
  - bin/rspec-interactive
71
85
  - bin/setup
72
86
  - bin/test
87
+ - examples/config.rb
73
88
  - examples/debugged_spec.rb
74
89
  - examples/failing_spec.rb
75
90
  - examples/other_passing_spec.rb
76
91
  - examples/passing_spec.rb
77
92
  - examples/spec_with_syntax_error.rb
78
93
  - lib/rspec-interactive.rb
94
+ - lib/rspec-interactive/client_output.rb
79
95
  - lib/rspec-interactive/config.rb
80
96
  - lib/rspec-interactive/input_completer.rb
97
+ - lib/rspec-interactive/pry.rb
81
98
  - lib/rspec-interactive/refresh_command.rb
82
99
  - lib/rspec-interactive/rspec_command.rb
83
100
  - lib/rspec-interactive/rspec_config_cache.rb
101
+ - lib/rspec-interactive/rspec_core_example.rb
84
102
  - lib/rspec-interactive/rubo_cop_command.rb
85
103
  - lib/rspec-interactive/runner.rb
104
+ - lib/rspec-interactive/stdio.rb
86
105
  - lib/rspec-interactive/version.rb
106
+ - lib/teamcity/spec/runner/formatter/teamcity/formatter.rb
87
107
  - rspec-interactive.gemspec
108
+ - runner/Gemfile
109
+ - runner/Gemfile.lock
110
+ - runner/rspec-interactive-run
88
111
  - scripts/release.sh
89
112
  - scripts/run-with-local-dep.sh
90
113
  - tests/debugged_spec_test.rb