rspec-interactive 0.1.0 → 0.5.0

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