Skip to content

Commit 310d830

Browse files
authored
Improve Spring preloading (#4802)
- Load the "init_block" of spec_helper pre-fork when using Spring - Previously the init_block was only used by Spork. - Spring would run it on every fork. This made tests load slower and reduced the benefit of using Spring, since less was being preloaded than could be. - I blindly copied over the current init/run blocks, without investigating in any depth. It's possible that more can be moved into the "init" method, to further reduce test load times when using Spring. This could be a worthwhile follow-up, especially if/when Spork is removed. - Extract out a SpecHelperHelper module so the "init_block" can be shared between spec_helper and Spring - Enforce that SpecHelperHelper.init only runs once. That way it doesn't run post-fork, which is a minor performance improvement and means we don't need to worry about everything in there being idempotent. - Add documentation to spec/README
1 parent 80ce779 commit 310d830

File tree

4 files changed

+239
-214
lines changed

4 files changed

+239
-214
lines changed

config/spring.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require_relative '../spec/spec_helper_helper'
2+
SpecHelperHelper.init

spec/README.md

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,55 @@ user 2.968 0.046 2.856 2.991 3.015
114114
sys 1.636 0.042 1.569 1.640 1.720
115115
```
116116

117-
## Running Tests In Preloaded (Fast) Mode:
117+
### Running Tests In Preloaded (Fast) Mode:
118118

119119
Running unit tests is a good thing, but it can be annoying waiting for
120120
the ruby interpreter to load and then initialize `rspec` every single
121121
time you make a change. Fortunately, many other people have run into
122-
this same frustration and published their solutions to the problem. We
123-
use the `spork` library to speed up the `edit-run-fix` cycle.
122+
this same frustration and published their solutions to the problem.
123+
124+
#### Spring
125+
126+
Rails Spring (not to be confused with the Java project with the same name) runs
127+
CC in a background process and then forks it every time you run tests. That
128+
means that everything loaded prior to the fork doesn't need to be re-loaded
129+
every time you run tests. This can speed up tests substantially.
130+
131+
To use spring, run `./bin/rspec` in place of `rspec`. Spring should
132+
automatically watch and reload files, but you can manually stop it with
133+
`./bin/spring stop`. It will automatically start again the next time you run
134+
`./bin/rspec`.
135+
136+
Example performance improvement:
137+
```sh
138+
❯ multitime -n 10 bundle exec rspec spec/unit/actions/app_create_spec.rb
139+
140+
...
141+
142+
===> multitime results
143+
1: bundle exec rspec spec/unit/actions/app_create_spec.rb
144+
Mean Std.Dev. Min Median Max
145+
real 24.471 2.420 20.169 25.499 27.303
146+
user 4.320 0.255 3.761 4.354 4.642
147+
sys 2.514 0.158 2.206 2.562 2.698
148+
```
149+
150+
```sh
151+
❯ multitime -n 10 bundle exec ./bin/rspec spec/unit/actions/app_create_spec.rb
152+
153+
...
154+
155+
===> multitime results
156+
1: bundle exec ./bin/rspec spec/unit/actions/app_create_spec.rb
157+
Mean Std.Dev. Min Median Max
158+
real 18.628 2.077 13.934 19.062 21.821
159+
user 0.177 0.032 0.129 0.185 0.233
160+
sys 0.103 0.014 0.078 0.107 0.126
161+
```
162+
163+
#### Spork (Legacy)
164+
165+
Spork is an older implementation of the same "forking" strategy implemented by Spring.
124166

125167
### Running Individual Tests
126168

spec/spec_helper.rb

Lines changed: 5 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
SPEC_HELPER_LOADED = true
22
require 'rubygems'
33
require 'mock_redis'
4+
require 'spec_helper_helper'
45

56
begin
67
require 'spork'
@@ -12,225 +13,18 @@
1213
run_spork = false
1314
end
1415

15-
# --- Instructions ---
16-
# Sort the contents of this file into a Spork.prefork and a Spork.each_run
17-
# block.
18-
#
19-
# The Spork.prefork block is run only once when the spork server is started.
20-
# You typically want to place most of your (slow) initializer code in here, in
21-
# particular, require'ing any 3rd-party gems that you don't normally modify
22-
# during development.
23-
#
24-
# The Spork.each_run block is run each time you run your specs. In case you
25-
# need to load files that tend to change during development, require them here.
26-
# With Rails, your application modules are loaded automatically, so sometimes
27-
# this block can remain empty.
28-
#
29-
# Note: You can modify files loaded *from* the Spork.each_run block without
30-
# restarting the spork server. However, this file itself will not be reloaded,
31-
# so if you change any of the code inside the each_run block, you still need to
32-
# restart the server. In general, if you have non-trivial code in this file,
33-
# it's advisable to move it into a separate file so you can easily edit it
34-
# without restarting spork. (For example, with RSpec, you could move
35-
# non-trivial code into a file spec/support/my_helper.rb, making sure that the
36-
# spec/support/* files are require'd from inside the each_run block.)
37-
#
38-
# Any code that is left outside the two blocks will be run during preforking
39-
# *and* during each_run -- that's probably not what you want.
40-
#
41-
# These instructions should self-destruct in 10 seconds. If they don't, feel
42-
# free to delete them.
43-
44-
init_block = proc do
45-
$LOAD_PATH.push(File.expand_path(__dir__))
46-
47-
require File.expand_path('../config/boot', __dir__)
48-
49-
if ENV['COVERAGE']
50-
require 'simplecov'
51-
SimpleCov.start do
52-
add_filter '/spec/'
53-
add_filter '/errors/'
54-
add_filter '/docs/'
55-
end
56-
end
57-
ENV['PB_IGNORE_DEPRECATIONS'] = 'true'
58-
ENV['RAILS_ENV'] ||= 'test'
59-
60-
require 'machinist/sequel'
61-
require 'machinist/object'
62-
require 'rack/test'
63-
require 'timecop'
64-
65-
require 'steno/steno'
66-
require 'webmock/rspec'
67-
68-
require 'pry'
69-
70-
require 'cloud_controller'
71-
require 'allowy/rspec'
72-
73-
require 'rspec_api_documentation'
74-
require 'services'
75-
76-
require 'support/bootstrap/spec_bootstrap'
77-
require 'rspec/collection_matchers'
78-
require 'rspec/its'
79-
require 'rspec/wait'
80-
end
81-
82-
each_run_block = proc do
83-
# Moving SpecBootstrap.init into the init-block means that changes in code files aren't detected.
84-
VCAP::CloudController::SpecBootstrap.init(do_schema_migration: !ENV['NO_DB_MIGRATION'])
85-
86-
Dir[File.expand_path('support/**/*.rb', File.dirname(__FILE__))].each { |file| require file }
87-
88-
# each-run here?
89-
RSpec.configure do |rspec_config|
90-
rspec_config.filter_run_when_matching :focus
91-
92-
rspec_config.mock_with :rspec do |mocks|
93-
mocks.verify_partial_doubles = true
94-
end
95-
rspec_config.filter_run_excluding :stepper
96-
rspec_config.expose_dsl_globally = false
97-
rspec_config.backtrace_exclusion_patterns = [%r{/gems/}, %r{/bin/rspec}]
98-
99-
rspec_config.expect_with(:rspec) do |config|
100-
config.syntax = :expect
101-
config.max_formatted_output_length = 1000
102-
end
103-
rspec_config.extend DeprecationHelpers
104-
rspec_config.include Rack::Test::Methods
105-
rspec_config.include ModelCreation
106-
rspec_config.include TimeHelpers
107-
rspec_config.include LinkHelpers
108-
rspec_config.include BackgroundJobHelpers
109-
rspec_config.include LogHelpers
110-
111-
rspec_config.include ServiceBrokerHelpers
112-
rspec_config.include UserHelpers
113-
rspec_config.include UserHeaderHelpers
114-
rspec_config.include ControllerHelpers, type: :v2_controller, file_path: EscapedPath.join(%w[spec unit controllers])
115-
rspec_config.include ControllerHelpers, type: :api
116-
rspec_config.include ControllerHelpers, file_path: EscapedPath.join(%w[spec acceptance])
117-
rspec_config.include RequestSpecHelper, file_path: EscapedPath.join(%w[spec acceptance])
118-
rspec_config.include ControllerHelpers, file_path: EscapedPath.join(%w[spec request])
119-
rspec_config.include RequestSpecHelper, file_path: EscapedPath.join(%w[spec request])
120-
rspec_config.include LifecycleSpecHelper, file_path: EscapedPath.join(%w[spec request lifecycle])
121-
rspec_config.include ApiDsl, type: :api
122-
rspec_config.include LegacyApiDsl, type: :legacy_api
123-
124-
rspec_config.include IntegrationHelpers, type: :integration
125-
rspec_config.include IntegrationHttp, type: :integration
126-
rspec_config.include IntegrationSetupHelpers, type: :integration
127-
rspec_config.include IntegrationSetup, type: :integration
128-
129-
rspec_config.include SpaceRestrictedResponseGenerators
130-
131-
rspec_config.before(:all) do
132-
WebMock.disable_net_connect!(allow: %w[codeclimate.com fake.bbs])
133-
end
134-
rspec_config.before(:all, type: :integration) do
135-
WebMock.allow_net_connect!
136-
@uaa_server = FakeUAAServer.new(6789)
137-
@uaa_server.start
138-
end
139-
rspec_config.before(:all, type: :migration) do
140-
skip 'Skipped due to NO_DB_MIGRATION env variable being set' if ENV['NO_DB_MIGRATION']
141-
end
142-
rspec_config.after(:all, type: :integration) do
143-
WebMock.disable_net_connect!(allow: %w[codeclimate.com fake.bbs])
144-
@uaa_server.stop
145-
end
146-
147-
rspec_config.before(:example, :log_db) do
148-
db = DbConfig.new.connection
149-
db.loggers << Logger.new($stdout)
150-
db.sql_log_level = :info
151-
end
152-
153-
rspec_config.example_status_persistence_file_path = 'spec/examples.txt'
154-
rspec_config.expose_current_running_example_as :example # Can be removed when we upgrade to rspec 3
155-
156-
rspec_config.before :suite do
157-
VCAP::CloudController::SpecBootstrap.seed
158-
# We only want to load rake tasks once:
159-
# calling this more than once will load tasks again and 'invoke' or 'execute' calls
160-
# will call rake tasks multiple times
161-
Application.load_tasks
162-
end
163-
164-
rspec_config.before do
165-
Delayed::Worker.destroy_failed_jobs = false
166-
Sequel::Deprecation.output = StringIO.new
167-
Sequel::Deprecation.backtrace_filter = 5
168-
169-
TestConfig.context = example.metadata[:job_context] || :api
170-
TestConfig.reset
171-
172-
Fog::Mock.reset
173-
174-
if Fog.mock?
175-
CloudController::DependencyLocator.instance.droplet_blobstore.ensure_bucket_exists
176-
CloudController::DependencyLocator.instance.package_blobstore.ensure_bucket_exists
177-
CloudController::DependencyLocator.instance.global_app_bits_cache.ensure_bucket_exists
178-
CloudController::DependencyLocator.instance.buildpack_blobstore.ensure_bucket_exists
179-
end
180-
181-
VCAP::CloudController::SecurityContext.clear
182-
VCAP::Request.current_id = nil
183-
allow_any_instance_of(VCAP::CloudController::UaaTokenDecoder).to receive(:uaa_issuer).and_return(UAAIssuer::ISSUER)
184-
185-
mock_redis = MockRedis.new
186-
allow(Redis).to receive(:new).and_return(mock_redis)
187-
end
188-
189-
rspec_config.around do |example|
190-
isolation = DatabaseIsolation.choose(example.metadata[:isolation], DbConfig.new.connection)
191-
isolation.cleanly { example.run }
192-
end
193-
194-
rspec_config.after do
195-
raise "Sequel Deprecation String found: #{Sequel::Deprecation.output.string}" unless Sequel::Deprecation.output.string == ''
196-
197-
Sequel::Deprecation.output.close unless Sequel::Deprecation.output.closed?
198-
end
199-
200-
rspec_config.after :all do
201-
TmpdirCleaner.clean
202-
end
203-
204-
rspec_config.after do
205-
Timecop.return
206-
end
207-
208-
rspec_config.after(:each, type: :legacy_api) { add_deprecation_warning }
209-
210-
RspecApiDocumentation.configure do |c|
211-
c.app = VCAP::CloudController::RackAppBuilder.new.build(TestConfig.config_instance,
212-
VCAP::CloudController::Metrics::RequestMetrics.new,
213-
VCAP::CloudController::Logs::RequestLogs.new(Steno.logger('request.logs')))
214-
c.format = %i[html json]
215-
c.api_name = 'Cloud Foundry API'
216-
c.template_path = 'spec/api/documentation/templates'
217-
c.curl_host = 'https://api.[your-domain.com]'
218-
end
219-
end
220-
end
221-
22216
if run_spork
22317
Spork.prefork do
22418
# Loading more in this block will cause your tests to run faster. However,
22519
# if you change any configuration or code from libraries loaded here, you'll
22620
# need to restart spork for it to take effect.
227-
init_block.call
21+
SpecHelperHelper.init
22822
end
22923
Spork.each_run do
23024
# This code will be run each time you run your specs.
231-
each_run_block.call
25+
SpecHelperHelper.each_run
23226
end
23327
else
234-
init_block.call
235-
each_run_block.call
28+
SpecHelperHelper.init
29+
SpecHelperHelper.each_run
23630
end

0 commit comments

Comments
 (0)