rspec-interactive 0.1.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cc41d7480a29852c93f700e92b70314e0b6e8fc70f73089ca419eae750f32bc
4
- data.tar.gz: b7b3357dbf44d8dc21beef28a684950f148eac98aa40b4de53e2eb7faa9eb2e9
3
+ metadata.gz: 88fe45587c743f490177b20ef35c1a6372c56b22e631ca8d5bf24fb5836448bc
4
+ data.tar.gz: 23bd0531b2836fb9d3d6d86a462fe65b4f9ddde3b2671db608f1b76fbaf7989b
5
5
  SHA512:
6
- metadata.gz: f6413fa767a8c4dd85e6f63129d35b9b341a264ae82183696d5c160f63cada92fe96b17972913246bea85b9fb5217b19d0bfd72c4dc1a0a3152a38861cb011de
7
- data.tar.gz: 9a42fc226690f04ea027d5cf42403164cbd0cd7f3dae80a9eec4383ef1d3c5f36d40e4f9291432366f449e0651b19b91d1b54c88b6b455cd906b73ae2bad2c08
6
+ metadata.gz: a066316a818ebaf46096e655d5e20c87280ae47863aeae564e275b066647b004afad528e40e0cc4dbe56935f6bd3c9275541302893b2fbd77c2eb01c84cdbf9c
7
+ data.tar.gz: 45d608d8bf7524a575bf833be24bcd13ec3ea6d2612da9ef40d7da5f9366fb43474eb078ab56c88f61c45d67143515a140d9d8de094e418906895cb277393386
data/.gitignore CHANGED
@@ -1 +1,3 @@
1
1
  rspec-interactive-*.gem
2
+ .rspec_interactive_history
3
+ .rspec_interactive_config
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
- gem 'rspec-core'
7
+ group :test do
8
+ gem 'rspec-expectations'
9
+ end
data/Gemfile.lock CHANGED
@@ -1,29 +1,40 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-console (0.1.0)
4
+ rspec-interactive (0.5.0)
5
5
  listen
6
+ pry
7
+ rspec-core
6
8
 
7
9
  GEM
8
10
  remote: https://rubygems.org/
9
11
  specs:
10
- ffi (1.15.0)
12
+ coderay (1.1.3)
13
+ diff-lcs (1.4.4)
14
+ ffi (1.15.3)
11
15
  listen (3.5.1)
12
16
  rb-fsevent (~> 0.10, >= 0.10.3)
13
17
  rb-inotify (~> 0.9, >= 0.9.10)
18
+ method_source (1.0.0)
19
+ pry (0.14.1)
20
+ coderay (~> 1.1)
21
+ method_source (~> 1.0)
14
22
  rb-fsevent (0.11.0)
15
23
  rb-inotify (0.10.1)
16
24
  ffi (~> 1.0)
17
- rspec-core (3.10.1)
18
- rspec-support (~> 3.10.0)
19
- rspec-support (3.10.2)
25
+ rspec-core (3.9.3)
26
+ rspec-support (~> 3.9.3)
27
+ rspec-expectations (3.9.4)
28
+ diff-lcs (>= 1.2.0, < 2.0)
29
+ rspec-support (~> 3.9.0)
30
+ rspec-support (3.9.4)
20
31
 
21
32
  PLATFORMS
22
33
  x86_64-darwin-20
23
34
 
24
35
  DEPENDENCIES
25
- rspec-console!
26
- rspec-core
36
+ rspec-expectations
37
+ rspec-interactive!
27
38
 
28
39
  BUNDLED WITH
29
40
  2.2.17
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # RSpec Interactive
2
+
3
+ An Pry console capable of running specs.
4
+
5
+ ## Installation & Configuration
6
+
7
+ Install:
8
+
9
+ ```ruby
10
+ gem 'rspec-interactive'
11
+ ```
12
+
13
+ Add a config file which configures RSpec and RSpec::Interactive, for example `spec/rspec_interactive.rb`:
14
+
15
+ ```ruby
16
+ load 'spec/spec_helper.rb'
17
+
18
+ RSpec::Interactive.configure do |config|
19
+ # Directories to watch for file changes. When a file changes, it will be reloaded like `load 'path/to/file'`.
20
+ config.watch_dirs += ["app", "lib", "config"]
21
+
22
+ # This block is invoked on startup. RSpec configuration must happen here so that it can be reloaded before each test run.
23
+ config.configure_rspec do
24
+ require './spec/spec_helper.rb'
25
+ end
26
+
27
+ # Invoked whenever a class is loaded due to a file change in one of the watch_dirs.
28
+ config.on_class_load do |clazz|
29
+ clazz.clear_validators! if clazz < ApplicationRecord
30
+ end
31
+ end
32
+ ```
33
+
34
+ Update `.gitignore`
35
+
36
+ ```shell
37
+ echo '.rspec_interactive_history' >> .gitignore
38
+ ```
39
+
40
+ ## Usage
41
+
42
+ See more examples below.
43
+
44
+ ```shell
45
+ bundle exec rspec-interactive spec/rspec_interactive.rb
46
+ ```
47
+
48
+ ## Example Usage In This Repo
49
+
50
+ Start:
51
+
52
+ ```shell
53
+ bundle exec rspec-interactive
54
+ ```
55
+
56
+ Run a passing spec:
57
+
58
+ ```shell
59
+ [1] pry(main)> rspec examples/passing_spec.rb
60
+ ```
61
+
62
+ Run a failing spec:
63
+
64
+ ```shell
65
+ [3] pry(main)> rspec examples/failing_spec.rb
66
+ ```
67
+
68
+ Run an example group:
69
+
70
+ ```shell
71
+ [5] pry(main)> rspec examples/passing_spec.rb:4
72
+ ```
73
+
74
+ Run multiple specs:
75
+
76
+ ```shell
77
+ [6] pry(main)> rspec examples/passing_spec.rb examples/failing_spec.rb
78
+ ```
79
+
80
+ Debug a spec (use `exit` to resume while debugging):
81
+
82
+ ```shell
83
+ [7] pry(main)> rspec examples/debugged_spec.rb
84
+ ```
85
+
86
+ Run multiple specs using globbing (use `exit` to resume while debugging):
87
+
88
+ ```shell
89
+ [8] pry(main)> rspec examples/*_spec.rb
90
+ ```
91
+
92
+ Exit:
93
+
94
+ ```shell
95
+ [9] pry(main)> exit
96
+ ```
97
+
98
+ ## Running Tests
99
+
100
+ ```shell
101
+ bundle exec bin/test
102
+ ```
data/bin/console CHANGED
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "bundler/setup"
5
- require "repo"
5
+ require 'rspec-interactive'
6
6
 
7
7
  # You can add fixtures and/or initialization code here to make experimenting
8
8
  # with your gem easier. You can also use a different console, if you like.
@@ -1,5 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'rspec-interactive.rb'
3
+ require 'rspec-interactive'
4
4
 
5
- RSpecInteractive::Console.new(ARGV).start()
5
+ if ARGV.size == 0
6
+ RSpec::Interactive.start
7
+ elsif ARGV.size == 1
8
+ RSpec::Interactive.start(config_file: ARGV[0])
9
+ else
10
+ STDERR.puts "expected 0 or 1 argument, got: #{args.join(', ')}"
11
+ STDERR.puts ''
12
+ STDOUT.puts 'usage: rspec-interactive [config-file]'
13
+ exit 1
14
+ end
data/bin/test ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ Dir["tests/*.rb"].each {|file| load file }
@@ -0,0 +1,9 @@
1
+ require 'rspec/core'
2
+ require 'pry'
3
+
4
+ describe "example spec" do
5
+ it "gets debugged" do
6
+ binding.pry
7
+ expect(true).to eq(true)
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec/core'
2
+
3
+ describe "example spec" do
4
+ it "fails" do
5
+ expect(true).to eq(false)
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspec/core'
2
+
3
+ describe "other example spec" do
4
+ it "succeeds" do
5
+ expect(true).to eq(true)
6
+ end
7
+
8
+ it "succeeds again" do
9
+ expect(true).to eq(true)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'rspec/core'
2
+
3
+ describe "example spec" do
4
+ it "succeeds" do
5
+ expect(true).to eq(true)
6
+ end
7
+
8
+ it "succeeds again" do
9
+ expect(true).to eq(true)
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ require 'rspec/core'
2
+
3
+ describe "spec with syntax error" do
4
+ it "succeeds
5
+ end
@@ -1,205 +1,152 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  require 'json'
4
2
  require 'listen'
3
+ require 'pry'
5
4
  require 'readline'
6
5
  require 'rspec/core'
7
- require 'shellwords'
8
-
9
- require_relative 'rspec-interactive/runner.rb'
10
6
 
11
- module RSpecInteractive
12
- class Console
7
+ require 'rspec-interactive/runner'
8
+ require 'rspec-interactive/config'
9
+ require 'rspec-interactive/rspec_config_cache'
10
+ require 'rspec-interactive/input_completer'
11
+ require 'rspec-interactive/rspec_command'
13
12
 
14
- HISTORY_FILE = '.rspec_interactive_history'.freeze
15
- CONFIG_FILE = '.rspec_interactive_config'.freeze
16
- MAX_HISTORY_ITEMS = 100
17
- COMMANDS = ['help', 'rspec']
13
+ module RSpec
14
+ module Interactive
18
15
 
19
- def initialize(args)
20
- if args.size > 1
21
- STDERR.puts "expected 0 or 1 argument, got: #{args.join(', ')}"
22
- exit!(1)
23
- end
16
+ DEFAULT_HISTORY_FILE = '.rspec_interactive_history'.freeze
24
17
 
25
- @stty_save = %x`stty -g`.chomp
26
- @mutex = Mutex.new
27
- @runner = nil
28
- load_config(args[0])
18
+ class << self
19
+ attr_accessor :configuration
29
20
  end
30
21
 
31
- def start()
32
- start_file_watcher
33
- load_history
34
- configure_auto_complete
35
- trap_interrupt
36
- start_console
22
+ def self.configure(&block)
23
+ block.call(@configuration)
37
24
  end
38
25
 
39
- private
26
+ def self.start(config_file: nil, history_file: DEFAULT_HISTORY_FILE, input_stream: STDIN, output_stream: STDOUT, error_stream: STDERR)
27
+ @history_file = history_file
28
+ @updated_files = []
29
+ @stty_save = %x`stty -g`.chomp
30
+ @mutex = Mutex.new
31
+ @output_stream = output_stream
32
+ @input_stream = input_stream
33
+ @error_stream = error_stream
34
+ @config_cache = RSpec::Interactive::ConfigCache.new
40
35
 
41
- def load_config(name = nil)
42
- @config = get_config(name)
43
- load @config["init_script"]
44
- end
36
+ @configuration = Configuration.new
37
+ load config_file if config_file
45
38
 
46
- def get_config(name = nil)
47
- if !File.exists? CONFIG_FILE
48
- STDERR.puts "file not found: #{CONFIG_FILE}"
49
- exit!(1)
50
- end
39
+ @config_cache.record_configuration { @configuration.configure_rspec.call }
51
40
 
52
- configs = JSON.parse(File.read(CONFIG_FILE))["configs"]
53
- if configs.empty?
54
- STDERR.puts "no configs found in: #{CONFIG_FILE}"
55
- exit!(1)
56
- end
41
+ check_rails
42
+ start_file_watcher
43
+ trap_interrupt
44
+ configure_pry
57
45
 
58
- # If a specific config was specified, use it.
59
- if name
60
- config = configs.find { |e| e["name"] == name }
61
- return config if config
62
- STDERR.puts "invalid config: #{name}"
63
- exit!(1)
64
- end
46
+ Pry.start
47
+ @listener.stop if @listener
48
+ 0
49
+ end
65
50
 
66
- # If there is only one, use it.
67
- if configs.size == 1
68
- return configs[0]
51
+ def self.check_rails
52
+ if defined?(::Rails)
53
+ if ::Rails.application.config.cache_classes
54
+ @error_stream.puts "warning: Rails.application.config.cache_classes enabled. Disable to ensure code is reloaded."
55
+ end
69
56
  end
57
+ end
70
58
 
71
- # Ask the user which to use.
72
- loop do
73
- names = configs.map { |e| e["name"] }
74
- names[0] = "#{names[0]} (default)"
75
- print "Multiple simultaneous configs not yet supported. Please choose a config. #{names.join(', ')}: "
76
- answer = STDIN.gets.chomp
77
- if answer.strip.empty?
78
- return configs[0]
79
- end
80
- config = configs.find { |e| e["name"] == answer }
81
- return config if config
82
- STDERR.puts "invalid config: #{answer}"
59
+ def self.configure_rspec
60
+ RSpec.configure do |config|
61
+ config.error_stream = @error_stream
62
+ config.output_stream = @output_stream
83
63
  end
84
64
  end
85
65
 
86
- def trap_interrupt
66
+ def self.trap_interrupt
87
67
  trap('INT') do
88
- @mutex.synchronize do
89
- if @runner
90
- @runner.quit
91
- else
92
- puts
93
- system "stty", @stty_save
94
- exit!(0)
95
- end
68
+ if @runner
69
+ # We are on a different thread. There is a race here. Ignore nil.
70
+ @runner&.quit
71
+ else
72
+ @output_stream.puts
73
+ system "stty", @stty_save
74
+ exit!(0)
96
75
  end
97
76
  end
98
77
  end
99
78
 
100
- def start_file_watcher
101
- # Only polling seems to work in Docker.
102
- listener = Listen.to(*@config["watch_dirs"], only: /\.rb$/, force_polling: true) do |modified, added, removed|
103
- (added + modified).each { |filename| load filename }
104
- end
105
- end
79
+ def self.start_file_watcher
80
+ return if @configuration.watch_dirs.empty?
106
81
 
107
- def load_history
108
- if File.exists? HISTORY_FILE
109
- lines = File.readlines(HISTORY_FILE)
110
- lines.each do |line|
111
- Readline::HISTORY << line.strip
82
+ # Only polling seems to work in Docker.
83
+ @listener = Listen.to(*@configuration.watch_dirs, only: /\.rb$/, force_polling: true) do |modified, added, removed|
84
+ @mutex.synchronize do
85
+ @updated_files.concat(added + modified)
112
86
  end
113
87
  end
88
+ @listener.start
114
89
  end
115
90
 
116
- def configure_auto_complete
117
- Readline.completion_append_character = ""
118
- end
91
+ def self.configure_pry
92
+ # Prevent Pry from trapping too. It will break ctrl-c handling.
93
+ Pry.config.should_trap_interrupts = false
119
94
 
120
- def start_console
121
- loop do
122
- buffer = Readline.readline('> ', true)&.strip
95
+ # Set up IO.
96
+ Pry.config.input = Readline
97
+ Pry.config.output = @output_stream
98
+ Readline.output = @output_stream
99
+ Readline.input = @input_stream
123
100
 
124
- # Exit on ctrl-D.
125
- if !buffer
126
- puts
127
- system "stty", @stty_save
128
- exit!(0)
129
- end
130
-
131
- # Ignore blank lines.
132
- if buffer.empty?
133
- Readline::HISTORY.pop
134
- next
135
- end
101
+ # Use custom completer to get file completion.
102
+ Pry.config.completer = RSpec::Interactive::InputCompleter
136
103
 
137
- # Write history to file.
138
- if Readline::HISTORY.size > 0
139
- file = File.open(HISTORY_FILE, 'w')
140
- lines = Readline::HISTORY.to_a
141
- lines[-[MAX_HISTORY_ITEMS, lines.size].min..-1].each do |line|
142
- file.write(line.strip + "\n")
143
- end
144
- file.close
145
- end
146
-
147
- # Handle quoting, etc.
148
- args = Shellwords.shellsplit(buffer)
149
- next if args.empty?
104
+ Pry.config.history_file = @history_file
105
+ end
150
106
 
151
- command = args[0].strip
152
- if COMMANDS.include?(command)
153
- send command.to_sym, args[1..-1]
107
+ def self.rspec(args)
108
+ parsed_args = args.flat_map do |arg|
109
+ if arg.match(/[\*\?\[]/)
110
+ glob = Dir.glob(arg)
111
+ glob.empty? ? [arg] : glob
154
112
  else
155
- STDERR.puts "command not found: #{args[0]}"
113
+ [arg]
156
114
  end
157
115
  end
158
- end
159
-
160
- def help(args)
161
- if !args.empty?
162
- STDERR.puts "invalid argument(s): #{args}"
163
- return
164
- end
165
-
166
- print "commands:\n\n"
167
- print "help - print this message\n"
168
- print "rspec - execute the specified spec file(s), wildcards allowed\n"
169
- end
170
116
 
171
- def rspec(args)
172
- if args.empty?
173
- STDERR.puts "you must specify one or more spec files"
174
- return
117
+ @mutex.synchronize do
118
+ @updated_files.uniq.each do |filename|
119
+ @output_stream.puts "modified: #{filename}"
120
+ trace = TracePoint.new(:class) do |tp|
121
+ @configuration.on_class_load.call(tp.self)
122
+ end
123
+ trace.enable
124
+ load filename
125
+ trace.disable
126
+ @output_stream.puts
127
+ end
128
+ @updated_files.clear
175
129
  end
176
130
 
177
- # Allow wildcards.
178
- filenames = args.flat_map { |filename| Dir.glob(filename) }
131
+ @runner = RSpec::Interactive::Runner.new(parsed_args)
179
132
 
180
- # Store formatters, if any, set by the init script. They will be cleared by RSpec below.
181
- formatters = RSpec.configuration.formatters || []
133
+ # Stop saving history in case a new Pry session is started for debugging.
134
+ Pry.config.history_save = false
182
135
 
183
- # Initialize the runner. Also accessed by the signal handler above.
184
- # RSpecInteractive::Runner sets RSpec.world.wants_to_quit to false. The signal
185
- # handler sets it to true.
186
- @mutex.synchronize { @runner = RSpecInteractive::Runner.new(filenames) }
136
+ # RSpec::Interactive-specific RSpec configuration
137
+ configure_rspec
187
138
 
188
- # Run the specs.
189
- @runner.run
139
+ # Run.
140
+ exit_code = @runner.run
141
+ @runner = nil
190
142
 
191
- # Clear the runner.
192
- @mutex.synchronize { @runner = nil }
143
+ # Reenable history
144
+ Pry.config.history_save = true
193
145
 
194
- # Clear data from previous run.
146
+ # Reset
195
147
  RSpec.clear_examples
196
-
197
- # Formatters get cleared by clear_examples. I don't understand why but the actual run
198
- # also modifies the list of formatters. Reset them to whatever the init script set.
199
- if !RSpec.configuration.formatters.empty?
200
- raise "internal error. expected formatters to be cleared."
201
- end
202
- formatters.each { |f| RSpec.configuration.add_formatter(f) }
148
+ RSpec.reset
149
+ @config_cache.replay_configuration
203
150
  end
204
151
  end
205
152
  end