smartest 0.5.0 → 0.6.0.alpha1
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +53 -14
- data/SMARTEST_DESIGN.md +3 -0
- data/exe/smartest +13 -0
- data/lib/smartest/fixture.rb +1 -1
- data/lib/smartest/init_rails_generator.rb +209 -0
- data/lib/smartest/rails.rb +74 -0
- data/lib/smartest/simple_stub.rb +37 -36
- data/lib/smartest/version.rb +1 -1
- data/lib/smartest.rb +1 -0
- data/smartest/simple_stub_test.rb +83 -32
- data/smartest/smartest_test.rb +204 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52a400f8b0fb05816a1adae02b69ce475d81bd63ad3516dded34fc47b3efc516
|
|
4
|
+
data.tar.gz: eeef5df2ac8161d3998fb942e7796ccfead8f7f8409b835c771fec4db7756137
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8336c21065222424824f95636730ab6ba1683fff0f711a3d37d180bebe6bac8cbf95be4c9bb8e0a794f0719fb5ba86102703d5af99902c1ab8461b5de0f6ab7c
|
|
7
|
+
data.tar.gz: 067c9a2f862cf07035b50bc8783e92f919a68ec8000546b2ab96e0ed2bde7c8cb0b811d99f7e9bf8bb2983d85ac7883fd830a38f86930c5a688b6e800f459fb7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 (not published yet)
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- Add Rails browser-test integration with `bundle exec smartest --init-rails`,
|
|
8
|
+
generated Playwright fixtures, and `Smartest::Rails::TestServer` loaded by
|
|
9
|
+
explicit `require "smartest/rails"`.
|
|
10
|
+
|
|
11
|
+
### Breaking Changes
|
|
12
|
+
|
|
13
|
+
- Make method stubs process-wide across Fibers and Threads. Remove
|
|
14
|
+
`Smartest::SimpleStub#apply!` and `#reset!`; `#apply` and `#reset` now raise
|
|
15
|
+
when called on an already-applied or not-applied stub object.
|
|
16
|
+
|
|
3
17
|
## 0.5.0
|
|
4
18
|
|
|
5
19
|
### Breaking Changes
|
data/README.md
CHANGED
|
@@ -141,6 +141,9 @@ To create a Smartest test scaffold:
|
|
|
141
141
|
For browser tests:
|
|
142
142
|
bundle exec smartest --init-browser
|
|
143
143
|
|
|
144
|
+
For Rails browser tests:
|
|
145
|
+
bundle exec smartest --init-rails
|
|
146
|
+
|
|
144
147
|
See all commands:
|
|
145
148
|
bundle exec smartest --help
|
|
146
149
|
```
|
|
@@ -190,6 +193,7 @@ bundle exec smartest smartest/user_test.rb
|
|
|
190
193
|
bundle exec smartest smartest/user_test.rb:12
|
|
191
194
|
bundle exec smartest --init
|
|
192
195
|
bundle exec smartest --init-browser
|
|
196
|
+
bundle exec smartest --init-rails
|
|
193
197
|
```
|
|
194
198
|
|
|
195
199
|
Output resembles:
|
|
@@ -235,6 +239,39 @@ Run the generated browser example with:
|
|
|
235
239
|
bundle exec smartest smartest/example_browser_test.rb
|
|
236
240
|
```
|
|
237
241
|
|
|
242
|
+
## Rails browser quick start
|
|
243
|
+
|
|
244
|
+
Initialize a Rails browser-test scaffold:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
bundle exec smartest --init-rails
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The Rails init command creates the normal Smartest helper and matcher files,
|
|
251
|
+
then adds:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
smartest/fixtures/rails_system_fixture.rb
|
|
255
|
+
smartest/matchers/playwright_matcher.rb
|
|
256
|
+
smartest/example_rails_system_test.rb
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
The generated fixture requires `smartest/rails` and starts
|
|
260
|
+
`Smartest::Rails::TestServer` against `Rails.application` in the same Ruby
|
|
261
|
+
process as the test runner. `Smartest::Rails::TestServer` is only loaded by
|
|
262
|
+
explicitly requiring `smartest/rails`; plain `require "smartest"` does not load
|
|
263
|
+
Puma.
|
|
264
|
+
|
|
265
|
+
The generated `page` fixture uses a per-test Playwright browser context with
|
|
266
|
+
`baseURL` set to the Rails server URL. Set `SMARTEST_RAILS_PORT` when you need a
|
|
267
|
+
fixed port; otherwise the test server asks the OS for an available port.
|
|
268
|
+
|
|
269
|
+
Run the generated Rails browser example with:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
bundle exec smartest smartest/example_rails_system_test.rb
|
|
273
|
+
```
|
|
274
|
+
|
|
238
275
|
## Defining tests
|
|
239
276
|
|
|
240
277
|
Use `test` at the top level:
|
|
@@ -646,7 +683,7 @@ Use simple stub helpers when a fixture needs to temporarily replace a Ruby
|
|
|
646
683
|
method and reset it during teardown:
|
|
647
684
|
|
|
648
685
|
```ruby
|
|
649
|
-
class
|
|
686
|
+
class ApplicationTestFixture < Smartest::Fixture
|
|
650
687
|
fixture :payment_gateway_stub do
|
|
651
688
|
simple_stub_any_instance_of(PaymentGateway, :charge) { :approved }
|
|
652
689
|
end
|
|
@@ -657,7 +694,7 @@ Register the fixture class from `around_suite` before tests request the fixture:
|
|
|
657
694
|
|
|
658
695
|
```ruby
|
|
659
696
|
around_suite do |suite|
|
|
660
|
-
use_fixture
|
|
697
|
+
use_fixture ApplicationTestFixture
|
|
661
698
|
suite.run
|
|
662
699
|
end
|
|
663
700
|
```
|
|
@@ -665,10 +702,10 @@ end
|
|
|
665
702
|
`use_fixture` is available inside `around_suite` or `around_test` blocks, not as
|
|
666
703
|
a top-level method in a test file.
|
|
667
704
|
|
|
668
|
-
The stub affects existing instances and new instances of the target class
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
705
|
+
The stub affects existing instances and new instances of the target class until
|
|
706
|
+
it is reset. Method stubs are shared across Fibers and Threads, including a
|
|
707
|
+
Rails test server running in another thread. Tests can request the fixture to
|
|
708
|
+
make the side effect explicit:
|
|
672
709
|
|
|
673
710
|
```ruby
|
|
674
711
|
test("checkout succeeds") do |payment_gateway_stub:|
|
|
@@ -680,20 +717,22 @@ Use `simple_stub(Time, :now) { fixed_time }` for singleton methods such as class
|
|
|
680
717
|
methods.
|
|
681
718
|
|
|
682
719
|
Use `with_stub_const("AppConfig::PAYMENT_PROVIDER", "fake") { ... }` for
|
|
683
|
-
constants in test bodies, `around_test`, or `around_suite`.
|
|
684
|
-
|
|
720
|
+
constants in test bodies, `around_test`, or `around_suite`.
|
|
721
|
+
|
|
722
|
+
Smartest stubs are process-wide state. They are intended for serial test
|
|
723
|
+
execution and for cases like a Rails test server thread serving the current
|
|
724
|
+
test. They do not provide isolation for multi-threaded parallel test execution:
|
|
725
|
+
one test can observe or reset another test's method or constant stub.
|
|
685
726
|
|
|
686
727
|
The method stub helpers call `Smartest::SimpleStub` internally, apply the stub,
|
|
687
728
|
register `on_teardown { stub.reset }`, and return the stub object.
|
|
688
729
|
`with_stub_const` records the previous constant value, replaces it, yields to
|
|
689
730
|
the block, and restores or removes the constant with `ensure`.
|
|
690
731
|
|
|
691
|
-
`Smartest::SimpleStub#apply`
|
|
692
|
-
`
|
|
693
|
-
`Smartest::SimpleStub::
|
|
694
|
-
|
|
695
|
-
`Smartest::SimpleStub::NotAppliedError` when it is not active there. See
|
|
696
|
-
[Stubs](documentation/docs/stubs.md).
|
|
732
|
+
`Smartest::SimpleStub#apply` raises
|
|
733
|
+
`Smartest::SimpleStub::AlreadyAppliedError` when the same stub object is already
|
|
734
|
+
applied. `#reset` raises `Smartest::SimpleStub::NotAppliedError` when that stub
|
|
735
|
+
object is not applied. See [Stubs](documentation/docs/stubs.md).
|
|
697
736
|
|
|
698
737
|
## Logged-in client example
|
|
699
738
|
|
data/SMARTEST_DESIGN.md
CHANGED
data/exe/smartest
CHANGED
|
@@ -28,6 +28,9 @@ usage = <<~USAGE
|
|
|
28
28
|
bundle exec smartest --init-browser
|
|
29
29
|
Generate a Playwright browser-test scaffold
|
|
30
30
|
|
|
31
|
+
bundle exec smartest --init-rails
|
|
32
|
+
Generate a Rails Playwright browser-test scaffold
|
|
33
|
+
|
|
31
34
|
Options:
|
|
32
35
|
--profile N
|
|
33
36
|
Print the N slowest tests. Defaults to 5.
|
|
@@ -48,6 +51,9 @@ missing_smartest_directory_message = <<~MESSAGE
|
|
|
48
51
|
For browser tests:
|
|
49
52
|
bundle exec smartest --init-browser
|
|
50
53
|
|
|
54
|
+
For Rails browser tests:
|
|
55
|
+
bundle exec smartest --init-rails
|
|
56
|
+
|
|
51
57
|
See all commands:
|
|
52
58
|
bundle exec smartest --help
|
|
53
59
|
MESSAGE
|
|
@@ -75,6 +81,11 @@ begin
|
|
|
75
81
|
exit Smartest::InitBrowserGenerator.new.run
|
|
76
82
|
end
|
|
77
83
|
|
|
84
|
+
if ARGV.include?("--init-rails")
|
|
85
|
+
command = :init_rails
|
|
86
|
+
exit Smartest::InitRailsGenerator.new.run
|
|
87
|
+
end
|
|
88
|
+
|
|
78
89
|
Smartest.disable_autorun!
|
|
79
90
|
Smartest.install_dsl!
|
|
80
91
|
test_load_path = File.expand_path("smartest", Dir.pwd)
|
|
@@ -103,6 +114,8 @@ rescue Exception => error
|
|
|
103
114
|
"Error initializing Smartest:"
|
|
104
115
|
when :init_browser
|
|
105
116
|
"Error initializing Smartest browser scaffold:"
|
|
117
|
+
when :init_rails
|
|
118
|
+
"Error initializing Smartest Rails browser scaffold:"
|
|
106
119
|
else
|
|
107
120
|
"Error loading tests:"
|
|
108
121
|
end
|
data/lib/smartest/fixture.rb
CHANGED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Smartest
|
|
6
|
+
class InitRailsGenerator
|
|
7
|
+
RAILS_SYSTEM_FIXTURE = <<~RUBY
|
|
8
|
+
# frozen_string_literal: true
|
|
9
|
+
|
|
10
|
+
require 'smartest/rails'
|
|
11
|
+
require "playwright"
|
|
12
|
+
|
|
13
|
+
class RailsSystemFixture < Smartest::Fixture
|
|
14
|
+
suite_fixture :rails_server do
|
|
15
|
+
# Set the environment before loading config/environment so the test
|
|
16
|
+
# server cannot boot against the development database by default.
|
|
17
|
+
ENV["RAILS_ENV"] ||= "test"
|
|
18
|
+
ENV["RACK_ENV"] ||= ENV["RAILS_ENV"]
|
|
19
|
+
require_relative "../../config/environment"
|
|
20
|
+
|
|
21
|
+
server = Smartest::Rails::TestServer.new(app: Rails.application)
|
|
22
|
+
server.start
|
|
23
|
+
server.wait_for_ready
|
|
24
|
+
|
|
25
|
+
on_teardown do
|
|
26
|
+
server.stop
|
|
27
|
+
server.wait_for_stopped
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
server
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
suite_fixture :base_url do |rails_server:|
|
|
34
|
+
rails_server.base_url
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
suite_fixture :playwright do
|
|
38
|
+
runtime = Playwright.create(
|
|
39
|
+
playwright_cli_executable_path: "./node_modules/.bin/playwright",
|
|
40
|
+
)
|
|
41
|
+
on_teardown { runtime.stop }
|
|
42
|
+
runtime.playwright
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
suite_fixture :browser do |playwright:|
|
|
46
|
+
browser_type = case ENV["BROWSER"]
|
|
47
|
+
when "firefox"
|
|
48
|
+
:firefox
|
|
49
|
+
when "webkit"
|
|
50
|
+
:webkit
|
|
51
|
+
else
|
|
52
|
+
:chromium
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
launch_options = {}
|
|
56
|
+
launch_options[:headless] = !%w[0 false].include?(ENV["HEADLESS"])
|
|
57
|
+
if (slow_mo = ENV["SLOW_MO"].to_i) > 0
|
|
58
|
+
launch_options[:slowMo] = slow_mo
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
browser = playwright.send(browser_type).launch(**launch_options)
|
|
62
|
+
on_teardown { browser.close }
|
|
63
|
+
browser
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
fixture :browser_context do |base_url:, browser:|
|
|
67
|
+
context = browser.new_context(baseURL: base_url)
|
|
68
|
+
on_teardown { context.close }
|
|
69
|
+
context
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
fixture :page do |browser_context:|
|
|
73
|
+
page = browser_context.new_page
|
|
74
|
+
on_teardown { page.close }
|
|
75
|
+
page
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
RUBY
|
|
79
|
+
|
|
80
|
+
PLAYWRIGHT_MATCHER = <<~RUBY
|
|
81
|
+
# frozen_string_literal: true
|
|
82
|
+
|
|
83
|
+
require "playwright"
|
|
84
|
+
require "playwright/test"
|
|
85
|
+
|
|
86
|
+
module PlaywrightMatcher
|
|
87
|
+
include Playwright::Test::Matchers
|
|
88
|
+
end
|
|
89
|
+
RUBY
|
|
90
|
+
|
|
91
|
+
EXAMPLE_RAILS_SYSTEM_TEST = <<~RUBY
|
|
92
|
+
# frozen_string_literal: true
|
|
93
|
+
|
|
94
|
+
require "test_helper"
|
|
95
|
+
|
|
96
|
+
test("loads the Rails application") do |page:|
|
|
97
|
+
response = page.goto("/")
|
|
98
|
+
|
|
99
|
+
expect(response.status).to be_between(200, 599)
|
|
100
|
+
end
|
|
101
|
+
RUBY
|
|
102
|
+
|
|
103
|
+
def initialize(root: Dir.pwd, output: $stdout, command_runner: nil)
|
|
104
|
+
@root = root
|
|
105
|
+
@output = output
|
|
106
|
+
@command_runner = command_runner || method(:run_system_command)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def run
|
|
110
|
+
Smartest::InitGenerator.new(
|
|
111
|
+
root: @root,
|
|
112
|
+
output: @output,
|
|
113
|
+
files: smartest_files,
|
|
114
|
+
final_message: nil
|
|
115
|
+
).run
|
|
116
|
+
create_file("smartest/fixtures/rails_system_fixture.rb", RAILS_SYSTEM_FIXTURE)
|
|
117
|
+
create_file("smartest/matchers/playwright_matcher.rb", PLAYWRIGHT_MATCHER)
|
|
118
|
+
update_test_helper
|
|
119
|
+
update_gemfile
|
|
120
|
+
install_dependencies
|
|
121
|
+
@output.puts
|
|
122
|
+
@output.puts "Run your Rails browser test suite with: bundle exec smartest smartest/example_rails_system_test.rb"
|
|
123
|
+
|
|
124
|
+
0
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def smartest_files
|
|
130
|
+
Smartest::InitGenerator::FILES.merge("smartest/example_rails_system_test.rb" => EXAMPLE_RAILS_SYSTEM_TEST)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def create_file(path, contents)
|
|
134
|
+
absolute_path = File.join(@root, path)
|
|
135
|
+
|
|
136
|
+
if File.exist?(absolute_path)
|
|
137
|
+
@output.puts "exist #{path}"
|
|
138
|
+
return
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
FileUtils.mkdir_p(File.dirname(absolute_path))
|
|
142
|
+
File.write(absolute_path, contents)
|
|
143
|
+
@output.puts "create #{path}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def update_test_helper
|
|
147
|
+
path = File.join(@root, "smartest/test_helper.rb")
|
|
148
|
+
contents = File.read(path)
|
|
149
|
+
updated = ensure_rails_registered(contents)
|
|
150
|
+
|
|
151
|
+
return if updated == contents
|
|
152
|
+
|
|
153
|
+
File.write(path, updated)
|
|
154
|
+
@output.puts "update smartest/test_helper.rb"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def ensure_rails_registered(contents)
|
|
158
|
+
missing_lines = []
|
|
159
|
+
missing_lines << " use_fixture RailsSystemFixture\n" unless contents.include?("use_fixture RailsSystemFixture")
|
|
160
|
+
missing_lines << " use_matcher PlaywrightMatcher\n" unless contents.include?("use_matcher PlaywrightMatcher")
|
|
161
|
+
return contents if missing_lines.empty?
|
|
162
|
+
|
|
163
|
+
if contents.include?("use_matcher PredicateMatcher")
|
|
164
|
+
contents.sub(/^(\s*use_matcher PredicateMatcher\n)/) do
|
|
165
|
+
"#{Regexp.last_match(1)}#{missing_lines.join}"
|
|
166
|
+
end
|
|
167
|
+
else
|
|
168
|
+
"#{contents.chomp}\n\naround_suite do |suite|\n#{missing_lines.join} suite.run\nend\n"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def update_gemfile
|
|
173
|
+
path = File.join(@root, "Gemfile")
|
|
174
|
+
exists = File.exist?(path)
|
|
175
|
+
contents = exists ? File.read(path) : "source \"https://rubygems.org\"\n"
|
|
176
|
+
|
|
177
|
+
if contents.match?(/gem ["']playwright-ruby-client["']/)
|
|
178
|
+
@output.puts "exist Gemfile playwright-ruby-client"
|
|
179
|
+
return
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
separator = contents.end_with?("\n") ? "" : "\n"
|
|
183
|
+
updated = "#{contents}#{separator}\ngem \"playwright-ruby-client\", group: :test\n"
|
|
184
|
+
File.write(path, updated)
|
|
185
|
+
@output.puts(exists ? "update Gemfile" : "create Gemfile")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def install_dependencies
|
|
189
|
+
install_commands.each do |command|
|
|
190
|
+
@output.puts "run #{command.join(" ")}"
|
|
191
|
+
next if @command_runner.call(command, chdir: @root)
|
|
192
|
+
|
|
193
|
+
raise "command failed: #{command.join(" ")}"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def install_commands
|
|
198
|
+
commands = [["bundle", "install"]]
|
|
199
|
+
commands << ["npm", "init", "--yes"] unless File.exist?(File.join(@root, "package.json"))
|
|
200
|
+
commands << ["npm", "install", "playwright", "--save-dev"]
|
|
201
|
+
commands << ["./node_modules/.bin/playwright", "install"]
|
|
202
|
+
commands
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def run_system_command(command, chdir:)
|
|
206
|
+
system(*command, chdir: chdir)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "puma"
|
|
5
|
+
require "timeout"
|
|
6
|
+
|
|
7
|
+
require_relative "../smartest"
|
|
8
|
+
|
|
9
|
+
module Smartest
|
|
10
|
+
module Rails
|
|
11
|
+
class TestServer
|
|
12
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
13
|
+
DEFAULT_READY_TIMEOUT = 10
|
|
14
|
+
|
|
15
|
+
attr_reader :host, :port
|
|
16
|
+
|
|
17
|
+
def initialize(app:, host: DEFAULT_HOST, port: nil)
|
|
18
|
+
@app = app
|
|
19
|
+
@host = host
|
|
20
|
+
@requested_port = port || ENV["SMARTEST_RAILS_PORT"]&.to_i || 0
|
|
21
|
+
@server = Puma::Server.new(@app)
|
|
22
|
+
@port = bind_tcp_listener
|
|
23
|
+
@thread = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def start
|
|
27
|
+
@thread ||= @server.run
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def stop
|
|
31
|
+
@server.stop
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def wait_for_ready(timeout: DEFAULT_READY_TIMEOUT)
|
|
35
|
+
Timeout.timeout(timeout) do
|
|
36
|
+
sleep 0.05 until responsive?
|
|
37
|
+
end
|
|
38
|
+
rescue Timeout::Error
|
|
39
|
+
raise "Rails test server did not become ready at #{base_url} within #{timeout} seconds"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def wait_for_stopped(timeout: DEFAULT_READY_TIMEOUT)
|
|
43
|
+
return unless @thread
|
|
44
|
+
return if @thread.join(timeout)
|
|
45
|
+
|
|
46
|
+
raise "Rails test server did not stop within #{timeout} seconds"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def base_url
|
|
50
|
+
"http://#{host}:#{port}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def bind_tcp_listener
|
|
56
|
+
listener = @server.add_tcp_listener(@host, @requested_port)
|
|
57
|
+
bound_port = listener.respond_to?(:addr) ? listener.addr[1] : @requested_port
|
|
58
|
+
|
|
59
|
+
return bound_port if bound_port && bound_port.positive?
|
|
60
|
+
|
|
61
|
+
raise "Rails test server could not determine bound port"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def responsive?
|
|
65
|
+
Net::HTTP.start(host, port, open_timeout: 0.2, read_timeout: 0.2) do |http|
|
|
66
|
+
http.head("/")
|
|
67
|
+
end
|
|
68
|
+
true
|
|
69
|
+
rescue EOFError, IOError, Net::OpenTimeout, Net::ReadTimeout, SocketError, SystemCallError
|
|
70
|
+
false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/smartest/simple_stub.rb
CHANGED
|
@@ -4,22 +4,40 @@ require "digest"
|
|
|
4
4
|
|
|
5
5
|
module Smartest
|
|
6
6
|
class SimpleStub
|
|
7
|
-
|
|
7
|
+
StubEntry = Struct.new(:owner, :implementation)
|
|
8
8
|
|
|
9
9
|
class AlreadyAppliedError < Smartest::Error; end
|
|
10
10
|
class NotAppliedError < Smartest::Error; end
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
13
|
def implementation_for(klass_key, method_name)
|
|
14
|
-
|
|
14
|
+
stub_registry_mutex.synchronize do
|
|
15
|
+
active_stubs.fetch(stub_key(klass_key, method_name), nil)&.last&.implementation
|
|
16
|
+
end
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
+
def activate_stub(stub, stub_key, implementation)
|
|
20
|
+
stub_registry_mutex.synchronize do
|
|
21
|
+
stack = active_stubs[stub_key] ||= []
|
|
22
|
+
return false if stack.any? { |entry| entry.owner.equal?(stub) }
|
|
23
|
+
|
|
24
|
+
stack << StubEntry.new(stub, implementation)
|
|
25
|
+
true
|
|
26
|
+
end
|
|
19
27
|
end
|
|
20
28
|
|
|
21
|
-
def
|
|
22
|
-
|
|
29
|
+
def deactivate_stub(stub, stub_key)
|
|
30
|
+
stub_registry_mutex.synchronize do
|
|
31
|
+
stack = active_stubs.fetch(stub_key, nil)
|
|
32
|
+
return false unless stack
|
|
33
|
+
|
|
34
|
+
index = stack.index { |entry| entry.owner.equal?(stub) }
|
|
35
|
+
return false unless index
|
|
36
|
+
|
|
37
|
+
stack.delete_at(index)
|
|
38
|
+
active_stubs.delete(stub_key) if stack.empty?
|
|
39
|
+
true
|
|
40
|
+
end
|
|
23
41
|
end
|
|
24
42
|
|
|
25
43
|
def ensure_dispatcher_method(klass, klass_key, method_name)
|
|
@@ -45,10 +63,6 @@ module Smartest
|
|
|
45
63
|
[klass_key, method_name]
|
|
46
64
|
end
|
|
47
65
|
|
|
48
|
-
def current_stubs
|
|
49
|
-
Thread.current[STORAGE_KEY]
|
|
50
|
-
end
|
|
51
|
-
|
|
52
66
|
def call_implementation(receiver, implementation, args, kwargs, block)
|
|
53
67
|
return call_implementation_with_block(receiver, implementation, args, kwargs, block) if block
|
|
54
68
|
|
|
@@ -110,6 +124,14 @@ module Smartest
|
|
|
110
124
|
def call_mutex
|
|
111
125
|
@call_mutex ||= Mutex.new
|
|
112
126
|
end
|
|
127
|
+
|
|
128
|
+
def active_stubs
|
|
129
|
+
@active_stubs ||= {}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def stub_registry_mutex
|
|
133
|
+
@stub_registry_mutex ||= Mutex.new
|
|
134
|
+
end
|
|
113
135
|
end
|
|
114
136
|
|
|
115
137
|
def initialize(klass, method_name, &implementation)
|
|
@@ -122,26 +144,10 @@ module Smartest
|
|
|
122
144
|
end
|
|
123
145
|
|
|
124
146
|
def apply
|
|
125
|
-
return if stub_defined?
|
|
126
|
-
|
|
127
|
-
apply_stub
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def apply!
|
|
131
|
-
raise AlreadyAppliedError, "stub for #{@klass}##{@method_name} is already applied" if stub_defined?
|
|
132
|
-
|
|
133
147
|
apply_stub
|
|
134
148
|
end
|
|
135
149
|
|
|
136
150
|
def reset
|
|
137
|
-
return unless stub_defined?
|
|
138
|
-
|
|
139
|
-
reset_stub
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def reset!
|
|
143
|
-
raise NotAppliedError, "stub for #{@klass}##{@method_name} is not applied" unless stub_defined?
|
|
144
|
-
|
|
145
151
|
reset_stub
|
|
146
152
|
end
|
|
147
153
|
|
|
@@ -151,16 +157,15 @@ module Smartest
|
|
|
151
157
|
raise ArgumentError, "block must be given for applying stub" unless @implementation
|
|
152
158
|
|
|
153
159
|
self.class.ensure_dispatcher_method(@klass, klass_key, @method_name)
|
|
154
|
-
|
|
160
|
+
return self if self.class.activate_stub(self, stub_key, @implementation)
|
|
161
|
+
|
|
162
|
+
raise AlreadyAppliedError, "stub for #{@klass}##{@method_name} is already applied"
|
|
155
163
|
end
|
|
156
164
|
|
|
157
165
|
def reset_stub
|
|
158
|
-
|
|
159
|
-
self.class.clear_active_stubs_if_empty
|
|
160
|
-
end
|
|
166
|
+
return self if self.class.deactivate_stub(self, stub_key)
|
|
161
167
|
|
|
162
|
-
|
|
163
|
-
self.class.active_stubs
|
|
168
|
+
raise NotAppliedError, "stub for #{@klass}##{@method_name} is not applied"
|
|
164
169
|
end
|
|
165
170
|
|
|
166
171
|
def stub_key
|
|
@@ -170,9 +175,5 @@ module Smartest
|
|
|
170
175
|
def klass_key
|
|
171
176
|
@klass_key ||= Digest::SHA256.hexdigest(@klass.object_id.to_s)
|
|
172
177
|
end
|
|
173
|
-
|
|
174
|
-
def stub_defined?
|
|
175
|
-
self.class.current_stubs&.key?(stub_key)
|
|
176
|
-
end
|
|
177
178
|
end
|
|
178
179
|
end
|
data/lib/smartest/version.rb
CHANGED
data/lib/smartest.rb
CHANGED
|
@@ -28,6 +28,7 @@ require_relative "smartest/reporter"
|
|
|
28
28
|
require_relative "smartest/runner"
|
|
29
29
|
require_relative "smartest/init_generator"
|
|
30
30
|
require_relative "smartest/init_browser_generator"
|
|
31
|
+
require_relative "smartest/init_rails_generator"
|
|
31
32
|
require_relative "smartest/cli_arguments"
|
|
32
33
|
|
|
33
34
|
module Smartest
|
|
@@ -71,7 +71,7 @@ test("simple stub stubs instance methods until reset") do
|
|
|
71
71
|
existing = SimpleStubSelfTestSubject.new("Alice")
|
|
72
72
|
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed" }
|
|
73
73
|
|
|
74
|
-
stub.apply
|
|
74
|
+
stub.apply
|
|
75
75
|
|
|
76
76
|
begin
|
|
77
77
|
expect(existing.name).to eq("stubbed")
|
|
@@ -84,13 +84,23 @@ test("simple stub stubs instance methods until reset") do
|
|
|
84
84
|
expect(SimpleStubSelfTestSubject.new("Bob").name).to eq("original Bob")
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
test("simple stub
|
|
88
|
-
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting)
|
|
87
|
+
test("simple stub reset requires the applied stub object") do
|
|
88
|
+
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting) do |prefix|
|
|
89
|
+
"#{prefix}, stubbed"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
stub.apply
|
|
89
93
|
|
|
90
94
|
begin
|
|
91
95
|
expect(SimpleStubSelfTestSubject.new("Alice").greeting("Hi")).to eq("Hi, stubbed")
|
|
96
|
+
|
|
97
|
+
error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::NotAppliedError) do
|
|
98
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :greeting).reset
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
expect(error.message).to eq("stub for SimpleStubSelfTestSubject#greeting is not applied")
|
|
92
102
|
ensure
|
|
93
|
-
|
|
103
|
+
stub.reset
|
|
94
104
|
end
|
|
95
105
|
|
|
96
106
|
expect(SimpleStubSelfTestSubject.new("Alice").greeting("Hi")).to eq("Hi, Alice")
|
|
@@ -127,6 +137,39 @@ test("simple_stub_any_instance_of applies and resets from fixture teardown") do
|
|
|
127
137
|
expect(status).to eq(0)
|
|
128
138
|
end
|
|
129
139
|
|
|
140
|
+
test("test-scoped method stubs restore suite-scoped method stubs") do
|
|
141
|
+
fixture_class = Class.new(Smartest::Fixture) do
|
|
142
|
+
suite_fixture :suite_name_stub do
|
|
143
|
+
simple_stub_any_instance_of(SimpleStubSelfTestSubject, :name) { "suite #{@name}" }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
fixture :test_name_stub do |suite_name_stub:|
|
|
147
|
+
expect(suite_name_stub).to be_a(Smartest::SimpleStub)
|
|
148
|
+
simple_stub_any_instance_of(SimpleStubSelfTestSubject, :name) { "test #{@name}" }
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
suite = Smartest::Suite.new
|
|
153
|
+
suite.fixture_classes.add(fixture_class)
|
|
154
|
+
suite.tests.add(
|
|
155
|
+
SimpleStubSelfTest.test_case(
|
|
156
|
+
"uses test scoped override",
|
|
157
|
+
proc { |test_name_stub:| expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("test Alice") }
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
suite.tests.add(
|
|
161
|
+
SimpleStubSelfTest.test_case(
|
|
162
|
+
"sees suite scoped stub after override teardown",
|
|
163
|
+
proc { |suite_name_stub:| expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("suite Alice") }
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
status, = SimpleStubSelfTest.run_suite(suite)
|
|
168
|
+
|
|
169
|
+
expect(status).to eq(0)
|
|
170
|
+
expect(SimpleStubSelfTestSubject.new("Alice").name).to eq("original Alice")
|
|
171
|
+
end
|
|
172
|
+
|
|
130
173
|
test("simple_stub applies and resets singleton methods from fixture teardown") do
|
|
131
174
|
fixture_class = Class.new(Smartest::Fixture) do
|
|
132
175
|
fixture :stubbed_time do
|
|
@@ -261,7 +304,7 @@ test("simple stub preserves receiver self and method blocks") do
|
|
|
261
304
|
block.call("#{prefix}, stubbed #{@name}")
|
|
262
305
|
end
|
|
263
306
|
|
|
264
|
-
stub.apply
|
|
307
|
+
stub.apply
|
|
265
308
|
|
|
266
309
|
begin
|
|
267
310
|
result = subject.yielding_greeting("Hi") { |message| message.upcase }
|
|
@@ -273,16 +316,16 @@ test("simple stub preserves receiver self and method blocks") do
|
|
|
273
316
|
expect(subject.yielding_greeting("Hi") { |message| message }).to eq("Hi, Alice")
|
|
274
317
|
end
|
|
275
318
|
|
|
276
|
-
test("simple stub is
|
|
319
|
+
test("simple stub is shared with fibers") do
|
|
277
320
|
subject = SimpleStubSelfTestSubject.new("Alice")
|
|
278
321
|
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed #{@name}" }
|
|
279
322
|
|
|
280
|
-
stub.apply
|
|
323
|
+
stub.apply
|
|
281
324
|
|
|
282
325
|
begin
|
|
283
326
|
expect(subject.name).to eq("stubbed Alice")
|
|
284
327
|
Fiber.new do
|
|
285
|
-
expect(subject.name).to eq("
|
|
328
|
+
expect(subject.name).to eq("stubbed Alice")
|
|
286
329
|
end.resume
|
|
287
330
|
ensure
|
|
288
331
|
stub.reset
|
|
@@ -291,52 +334,62 @@ test("simple stub is scoped to the current fiber") do
|
|
|
291
334
|
expect(subject.name).to eq("original Alice")
|
|
292
335
|
end
|
|
293
336
|
|
|
294
|
-
test("simple stub
|
|
337
|
+
test("simple stub is shared with threads") do
|
|
295
338
|
subject = SimpleStubSelfTestSubject.new("Alice")
|
|
296
339
|
queue = Queue.new
|
|
297
|
-
|
|
340
|
+
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed #{@name}" }
|
|
298
341
|
|
|
299
|
-
|
|
342
|
+
stub.apply
|
|
300
343
|
|
|
301
344
|
thread = Thread.new do
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
begin
|
|
306
|
-
queue << subject.name
|
|
307
|
-
rescue Exception => error
|
|
308
|
-
queue << error
|
|
309
|
-
ensure
|
|
310
|
-
thread_stub.reset
|
|
311
|
-
end
|
|
345
|
+
queue << subject.name
|
|
346
|
+
rescue Exception => error
|
|
347
|
+
queue << error
|
|
312
348
|
end
|
|
313
349
|
|
|
314
350
|
begin
|
|
315
|
-
expect(subject.name).to eq("
|
|
351
|
+
expect(subject.name).to eq("stubbed Alice")
|
|
316
352
|
|
|
317
353
|
thread_result = queue.pop
|
|
318
354
|
raise thread_result if thread_result.is_a?(Exception)
|
|
319
355
|
|
|
320
|
-
expect(thread_result).to eq("
|
|
356
|
+
expect(thread_result).to eq("stubbed Alice")
|
|
321
357
|
thread.join
|
|
322
|
-
expect(subject.name).to eq("main Alice")
|
|
323
358
|
ensure
|
|
324
|
-
|
|
359
|
+
stub.reset
|
|
325
360
|
thread.kill if thread.alive?
|
|
326
361
|
end
|
|
327
362
|
|
|
328
363
|
expect(subject.name).to eq("original Alice")
|
|
329
364
|
end
|
|
330
365
|
|
|
331
|
-
test("simple stub
|
|
366
|
+
test("simple stub restores previous implementation when nested stubs reset") do
|
|
367
|
+
subject = SimpleStubSelfTestSubject.new("Alice")
|
|
368
|
+
outer_stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "outer #{@name}" }
|
|
369
|
+
inner_stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "inner #{@name}" }
|
|
370
|
+
|
|
371
|
+
outer_stub.apply
|
|
372
|
+
inner_stub.apply
|
|
373
|
+
|
|
374
|
+
begin
|
|
375
|
+
expect(subject.name).to eq("inner Alice")
|
|
376
|
+
inner_stub.reset
|
|
377
|
+
expect(subject.name).to eq("outer Alice")
|
|
378
|
+
ensure
|
|
379
|
+
outer_stub.reset
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
expect(subject.name).to eq("original Alice")
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
test("simple stub apply and reset are strict") do
|
|
332
386
|
stub = Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name) { "stubbed" }
|
|
333
387
|
|
|
334
|
-
stub.apply
|
|
335
388
|
stub.apply
|
|
336
389
|
|
|
337
390
|
begin
|
|
338
391
|
error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::AlreadyAppliedError) do
|
|
339
|
-
|
|
392
|
+
stub.apply
|
|
340
393
|
end
|
|
341
394
|
|
|
342
395
|
expect(error.message).to eq("stub for SimpleStubSelfTestSubject#name is already applied")
|
|
@@ -344,10 +397,8 @@ test("simple stub supports safe and strict apply reset APIs") do
|
|
|
344
397
|
stub.reset
|
|
345
398
|
end
|
|
346
399
|
|
|
347
|
-
stub.reset
|
|
348
|
-
|
|
349
400
|
error = SimpleStubSelfTest.capture_error(Smartest::SimpleStub::NotAppliedError) do
|
|
350
|
-
stub.reset
|
|
401
|
+
stub.reset
|
|
351
402
|
end
|
|
352
403
|
|
|
353
404
|
expect(error.message).to eq("stub for SimpleStubSelfTestSubject#name is not applied")
|
|
@@ -367,7 +418,7 @@ test("simple stub validates constructor arguments and apply block") do
|
|
|
367
418
|
expect(error.message).to eq("method name must be a Symbol.")
|
|
368
419
|
|
|
369
420
|
error = SimpleStubSelfTest.capture_error(ArgumentError) do
|
|
370
|
-
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name).apply
|
|
421
|
+
Smartest::SimpleStub.new(SimpleStubSelfTestSubject, :name).apply
|
|
371
422
|
end
|
|
372
423
|
|
|
373
424
|
expect(error.message).to eq("block must be given for applying stub")
|
data/smartest/smartest_test.rb
CHANGED
|
@@ -1723,6 +1723,7 @@ test("cli prints help") do
|
|
|
1723
1723
|
expect(stdout).not_to include("Run a browser test file")
|
|
1724
1724
|
expect(stdout).to include("bundle exec smartest --init")
|
|
1725
1725
|
expect(stdout).to include("bundle exec smartest --init-browser")
|
|
1726
|
+
expect(stdout).to include("bundle exec smartest --init-rails")
|
|
1726
1727
|
expect(stdout).to include("--profile N")
|
|
1727
1728
|
expect(stdout).to include("smartest/**/*_test.rb")
|
|
1728
1729
|
end
|
|
@@ -1741,6 +1742,7 @@ test("cli explains how to initialize when default smartest directory is missing"
|
|
|
1741
1742
|
expect(stdout).to include("No smartest/ directory found.")
|
|
1742
1743
|
expect(stdout).to include("bundle exec smartest --init")
|
|
1743
1744
|
expect(stdout).to include("bundle exec smartest --init-browser")
|
|
1745
|
+
expect(stdout).to include("bundle exec smartest --init-rails")
|
|
1744
1746
|
expect(stdout).to include("bundle exec smartest --help")
|
|
1745
1747
|
end
|
|
1746
1748
|
end
|
|
@@ -2198,3 +2200,205 @@ test("cli browser init generator skips npm init when package.json already exists
|
|
|
2198
2200
|
expect(output.string).not_to include("npm init --yes")
|
|
2199
2201
|
end
|
|
2200
2202
|
end
|
|
2203
|
+
|
|
2204
|
+
test("smartest rails helper is loaded only by explicit require") do
|
|
2205
|
+
lib_path = File.expand_path("../lib", __dir__)
|
|
2206
|
+
stdout, stderr, status = Open3.capture3(
|
|
2207
|
+
{ "RUBYLIB" => lib_path },
|
|
2208
|
+
"ruby",
|
|
2209
|
+
"-e",
|
|
2210
|
+
'require "smartest"; puts Smartest.const_defined?(:Rails, false)'
|
|
2211
|
+
)
|
|
2212
|
+
|
|
2213
|
+
expect(status.success?).to eq(true)
|
|
2214
|
+
expect(stderr).to eq("")
|
|
2215
|
+
expect(stdout).to eq("false\n")
|
|
2216
|
+
|
|
2217
|
+
Dir.mktmpdir do |dir|
|
|
2218
|
+
File.write(File.join(dir, "puma.rb"), "module Puma\n class Server\n end\nend\n")
|
|
2219
|
+
|
|
2220
|
+
stdout, stderr, status = Open3.capture3(
|
|
2221
|
+
{ "RUBYLIB" => "#{dir}:#{lib_path}" },
|
|
2222
|
+
"ruby",
|
|
2223
|
+
"-e",
|
|
2224
|
+
'require "smartest/rails"; puts Smartest::Rails.const_defined?(:TestServer, false)'
|
|
2225
|
+
)
|
|
2226
|
+
|
|
2227
|
+
expect(status.success?).to eq(true)
|
|
2228
|
+
expect(stderr).to eq("")
|
|
2229
|
+
expect(stdout).to eq("true\n")
|
|
2230
|
+
end
|
|
2231
|
+
end
|
|
2232
|
+
|
|
2233
|
+
test("smartest rails test server wraps puma with env port and lifecycle") do
|
|
2234
|
+
lib_path = File.expand_path("../lib", __dir__)
|
|
2235
|
+
|
|
2236
|
+
Dir.mktmpdir do |dir|
|
|
2237
|
+
File.write(File.join(dir, "puma.rb"), <<~RUBY)
|
|
2238
|
+
module Puma
|
|
2239
|
+
class Listener
|
|
2240
|
+
def initialize(port)
|
|
2241
|
+
@port = port
|
|
2242
|
+
end
|
|
2243
|
+
|
|
2244
|
+
def addr
|
|
2245
|
+
["AF_INET", @port]
|
|
2246
|
+
end
|
|
2247
|
+
end
|
|
2248
|
+
|
|
2249
|
+
class Server
|
|
2250
|
+
attr_reader :stopped
|
|
2251
|
+
|
|
2252
|
+
def initialize(app)
|
|
2253
|
+
@app = app
|
|
2254
|
+
end
|
|
2255
|
+
|
|
2256
|
+
def add_tcp_listener(_host, port)
|
|
2257
|
+
Listener.new(port.zero? ? 4321 : port)
|
|
2258
|
+
end
|
|
2259
|
+
|
|
2260
|
+
def run
|
|
2261
|
+
Thread.new {}
|
|
2262
|
+
end
|
|
2263
|
+
|
|
2264
|
+
def stop
|
|
2265
|
+
@stopped = true
|
|
2266
|
+
end
|
|
2267
|
+
end
|
|
2268
|
+
end
|
|
2269
|
+
RUBY
|
|
2270
|
+
|
|
2271
|
+
stdout, stderr, status = Open3.capture3(
|
|
2272
|
+
{ "RUBYLIB" => "#{dir}:#{lib_path}", "SMARTEST_RAILS_PORT" => "4567" },
|
|
2273
|
+
"ruby",
|
|
2274
|
+
"-e",
|
|
2275
|
+
<<~'RUBY'
|
|
2276
|
+
require "smartest/rails"
|
|
2277
|
+
|
|
2278
|
+
server = Smartest::Rails::TestServer.new(app: Object.new)
|
|
2279
|
+
thread = server.start
|
|
2280
|
+
server.stop
|
|
2281
|
+
server.wait_for_stopped
|
|
2282
|
+
|
|
2283
|
+
puts server.base_url
|
|
2284
|
+
puts thread.is_a?(Thread)
|
|
2285
|
+
RUBY
|
|
2286
|
+
)
|
|
2287
|
+
|
|
2288
|
+
expect(status.success?).to eq(true)
|
|
2289
|
+
expect(stderr).to eq("")
|
|
2290
|
+
expect(stdout).to eq("http://127.0.0.1:4567\ntrue\n")
|
|
2291
|
+
end
|
|
2292
|
+
end
|
|
2293
|
+
|
|
2294
|
+
test("cli rails init generator creates Rails browser scaffold and installation commands") do
|
|
2295
|
+
Dir.mktmpdir do |dir|
|
|
2296
|
+
File.write(File.join(dir, "Gemfile"), <<~RUBY)
|
|
2297
|
+
source "https://rubygems.org"
|
|
2298
|
+
|
|
2299
|
+
gem "rails"
|
|
2300
|
+
gem "smartest"
|
|
2301
|
+
RUBY
|
|
2302
|
+
|
|
2303
|
+
commands = []
|
|
2304
|
+
output = StringIO.new
|
|
2305
|
+
generator = Smartest::InitRailsGenerator.new(
|
|
2306
|
+
root: dir,
|
|
2307
|
+
output: output,
|
|
2308
|
+
command_runner: ->(command, chdir:) {
|
|
2309
|
+
commands << [command, chdir]
|
|
2310
|
+
true
|
|
2311
|
+
}
|
|
2312
|
+
)
|
|
2313
|
+
|
|
2314
|
+
status = generator.run
|
|
2315
|
+
|
|
2316
|
+
expect(status).to eq(0)
|
|
2317
|
+
rails_fixture = File.read(File.join(dir, "smartest/fixtures/rails_system_fixture.rb"))
|
|
2318
|
+
expect(rails_fixture).to include("require 'smartest/rails'")
|
|
2319
|
+
expect(rails_fixture).to include("class RailsSystemFixture < Smartest::Fixture")
|
|
2320
|
+
expect(rails_fixture).to include("suite_fixture :rails_server")
|
|
2321
|
+
expect(rails_fixture).to include("server cannot boot against the development database")
|
|
2322
|
+
expect(rails_fixture).to include('require_relative "../../config/environment"')
|
|
2323
|
+
expect(rails_fixture).to include("Smartest::Rails::TestServer.new(app: Rails.application)")
|
|
2324
|
+
expect(rails_fixture).not_to include("SmartestRailsTestServer")
|
|
2325
|
+
expect(rails_fixture).not_to include("Puma::Server")
|
|
2326
|
+
expect(rails_fixture).to include("suite_fixture :base_url")
|
|
2327
|
+
expect(rails_fixture).to include("fixture :browser_context do |base_url:, browser:|")
|
|
2328
|
+
expect(rails_fixture).to include("context = browser.new_context(baseURL: base_url)")
|
|
2329
|
+
expect(rails_fixture).to include("fixture :page do |browser_context:|")
|
|
2330
|
+
|
|
2331
|
+
example_test = File.read(File.join(dir, "smartest/example_rails_system_test.rb"))
|
|
2332
|
+
expect(example_test).to include('test("loads the Rails application") do |page:|')
|
|
2333
|
+
expect(example_test).to include('response = page.goto("/")')
|
|
2334
|
+
expect(example_test).to include("expect(response.status).to be_between(200, 599)")
|
|
2335
|
+
|
|
2336
|
+
helper_contents = File.read(File.join(dir, "smartest/test_helper.rb"))
|
|
2337
|
+
expect(helper_contents).to include("use_matcher PredicateMatcher\n use_fixture RailsSystemFixture\n use_matcher PlaywrightMatcher\n suite.run")
|
|
2338
|
+
expect(helper_contents).not_to include("Smartest::SimpleStub")
|
|
2339
|
+
|
|
2340
|
+
gemfile_contents = File.read(File.join(dir, "Gemfile"))
|
|
2341
|
+
expect(gemfile_contents).to include('gem "playwright-ruby-client", group: :test')
|
|
2342
|
+
expect(commands).to eq(
|
|
2343
|
+
[
|
|
2344
|
+
[["bundle", "install"], dir],
|
|
2345
|
+
[["npm", "init", "--yes"], dir],
|
|
2346
|
+
[["npm", "install", "playwright", "--save-dev"], dir],
|
|
2347
|
+
[["./node_modules/.bin/playwright", "install"], dir]
|
|
2348
|
+
]
|
|
2349
|
+
)
|
|
2350
|
+
expect(output.string).to include("Run your Rails browser test suite with: bundle exec smartest smartest/example_rails_system_test.rb")
|
|
2351
|
+
end
|
|
2352
|
+
end
|
|
2353
|
+
|
|
2354
|
+
test("cli rails init generator skips duplicate registration and dependencies") do
|
|
2355
|
+
Dir.mktmpdir do |dir|
|
|
2356
|
+
FileUtils.mkdir_p(File.join(dir, "smartest/fixtures"))
|
|
2357
|
+
FileUtils.mkdir_p(File.join(dir, "smartest/matchers"))
|
|
2358
|
+
File.write(File.join(dir, "package.json"), "{\n \"name\": \"existing\"\n}\n")
|
|
2359
|
+
File.write(File.join(dir, "Gemfile"), <<~RUBY)
|
|
2360
|
+
source "https://rubygems.org"
|
|
2361
|
+
|
|
2362
|
+
gem "smartest"
|
|
2363
|
+
gem "playwright-ruby-client", group: :test
|
|
2364
|
+
RUBY
|
|
2365
|
+
File.write(File.join(dir, "smartest/test_helper.rb"), <<~RUBY)
|
|
2366
|
+
require "smartest/autorun"
|
|
2367
|
+
|
|
2368
|
+
around_suite do |suite|
|
|
2369
|
+
use_matcher PredicateMatcher
|
|
2370
|
+
use_fixture RailsSystemFixture
|
|
2371
|
+
use_matcher PlaywrightMatcher
|
|
2372
|
+
suite.run
|
|
2373
|
+
end
|
|
2374
|
+
RUBY
|
|
2375
|
+
|
|
2376
|
+
commands = []
|
|
2377
|
+
output = StringIO.new
|
|
2378
|
+
generator = Smartest::InitRailsGenerator.new(
|
|
2379
|
+
root: dir,
|
|
2380
|
+
output: output,
|
|
2381
|
+
command_runner: ->(command, chdir:) {
|
|
2382
|
+
commands << [command, chdir]
|
|
2383
|
+
true
|
|
2384
|
+
}
|
|
2385
|
+
)
|
|
2386
|
+
|
|
2387
|
+
status = generator.run
|
|
2388
|
+
|
|
2389
|
+
expect(status).to eq(0)
|
|
2390
|
+
helper_contents = File.read(File.join(dir, "smartest/test_helper.rb"))
|
|
2391
|
+
expect(helper_contents.scan("use_fixture RailsSystemFixture").length).to eq(1)
|
|
2392
|
+
expect(helper_contents.scan("use_matcher PlaywrightMatcher").length).to eq(1)
|
|
2393
|
+
expect(helper_contents).not_to include("Smartest::SimpleStub")
|
|
2394
|
+
expect(commands).to eq(
|
|
2395
|
+
[
|
|
2396
|
+
[["bundle", "install"], dir],
|
|
2397
|
+
[["npm", "install", "playwright", "--save-dev"], dir],
|
|
2398
|
+
[["./node_modules/.bin/playwright", "install"], dir]
|
|
2399
|
+
]
|
|
2400
|
+
)
|
|
2401
|
+
expect(output.string).to include("exist Gemfile playwright-ruby-client")
|
|
2402
|
+
expect(output.string).not_to include("npm init --yes")
|
|
2403
|
+
end
|
|
2404
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: smartest
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0.alpha1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yusuke Iwaki
|
|
@@ -62,9 +62,11 @@ files:
|
|
|
62
62
|
- lib/smartest/hook_contexts.rb
|
|
63
63
|
- lib/smartest/init_browser_generator.rb
|
|
64
64
|
- lib/smartest/init_generator.rb
|
|
65
|
+
- lib/smartest/init_rails_generator.rb
|
|
65
66
|
- lib/smartest/matcher_registry.rb
|
|
66
67
|
- lib/smartest/matchers.rb
|
|
67
68
|
- lib/smartest/parameter_extractor.rb
|
|
69
|
+
- lib/smartest/rails.rb
|
|
68
70
|
- lib/smartest/reporter.rb
|
|
69
71
|
- lib/smartest/runner.rb
|
|
70
72
|
- lib/smartest/simple_stub.rb
|
|
@@ -101,9 +103,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
101
103
|
version: '2.7'
|
|
102
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
105
|
requirements:
|
|
104
|
-
- - "
|
|
106
|
+
- - ">"
|
|
105
107
|
- !ruby/object:Gem::Version
|
|
106
|
-
version:
|
|
108
|
+
version: 1.3.1
|
|
107
109
|
requirements: []
|
|
108
110
|
rubygems_version: 3.4.19
|
|
109
111
|
signing_key:
|