Skip to content

Commit a973411

Browse files
authored
Merge pull request #378 from chadlwilson/1-2-fix-flaky-tests
1.2: [tests] fix flaky tests
2 parents a6d7159 + e7f6d37 commit a973411

File tree

10 files changed

+116
-111
lines changed

10 files changed

+116
-111
lines changed

.bundle/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
BUNDLE_VERSION: "system"

examples/camping/.bundle/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
BUNDLE_VERSION: "system"

examples/rails8/.bundle/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
BUNDLE_VERSION: "system"

examples/sinatra/.bundle/config

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
BUNDLE_VERSION: "system"

gemfiles/.bundle/config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
---
2-
BUNDLE_RETRY: "1"
2+
BUNDLE_VERSION: "system"

src/main/java/org/jruby/rack/RackApplicationFactoryDecorator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ public void destroy() {
137137
public RackApplication getApplication() throws RackException {
138138
final RuntimeException error = getInitError();
139139
if ( error != null ) {
140-
log(DEBUG, "due a previous initialization failure application instance can not be returned");
141-
throw error; // this is better - we shall never return null here ...
140+
log(DEBUG, "due to a previous initialization failure application instance can not be returned");
141+
throw error;
142142
}
143143
return getApplicationImpl();
144144
}

src/spec/ruby/jruby/rack/booter_spec.rb

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -299,17 +299,17 @@
299299
@runtime.evalScriptlet("load 'jruby/rack/boot/rack.rb'")
300300

301301
# booter got setup :
302-
should_not_eval_as_nil "defined?(JRuby::Rack.booter)"
303-
should_not_eval_as_nil "JRuby::Rack.booter"
302+
should_eval_as_not_nil "defined?(JRuby::Rack.booter)"
303+
should_eval_as_not_nil "JRuby::Rack.booter"
304304
should_eval_as_eql_to "JRuby::Rack.booter.class.name", 'JRuby::Rack::Booter'
305305

306306
# Booter.boot! run :
307-
should_not_eval_as_nil "ENV['RACK_ENV']"
307+
should_eval_as_not_nil "ENV['RACK_ENV']"
308308
# rack got required :
309-
should_not_eval_as_nil "defined?(Rack::VERSION)"
310-
should_not_eval_as_nil "defined?(Rack.release)"
309+
should_eval_as_not_nil "defined?(Rack::VERSION)"
310+
should_eval_as_not_nil "defined?(Rack.release)"
311311
# check if it got loaded correctly :
312-
should_not_eval_as_nil "Rack::Request.new({}) rescue nil"
312+
should_eval_as_not_nil "Rack::Request.new({}) rescue nil"
313313
end
314314

315315
end
@@ -346,13 +346,13 @@
346346
@runtime.evalScriptlet("load 'jruby/rack/boot/rails.rb'")
347347

348348
# booter got setup :
349-
should_not_eval_as_nil "defined?(JRuby::Rack.booter)"
350-
should_not_eval_as_nil "JRuby::Rack.booter"
349+
should_eval_as_not_nil "defined?(JRuby::Rack.booter)"
350+
should_eval_as_not_nil "JRuby::Rack.booter"
351351
should_eval_as_eql_to "JRuby::Rack.booter.class.name", 'JRuby::Rack::RailsBooter'
352352

353353
# Booter.boot! run :
354-
should_not_eval_as_nil "ENV['RACK_ENV']"
355-
should_not_eval_as_nil "ENV['RAILS_ENV']"
354+
should_eval_as_not_nil "ENV['RACK_ENV']"
355+
should_eval_as_not_nil "ENV['RAILS_ENV']"
356356

357357
# rack not yet required (let bundler decide which rack version to load) :
358358
should_eval_as_nil "defined?(Rack::VERSION)"

src/spec/ruby/jruby/rack/rails_booter_spec.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,6 @@
129129
end
130130
end
131131

132-
after :all do
133-
$servlet_context = nil
134-
end
135-
136132
it "should have loaded the railtie" do
137133
expect(defined?(JRuby::Rack::Railtie)).not_to be nil
138134
end

src/spec/ruby/rack/application_spec.rb

Lines changed: 74 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ def it_should_rewind_body
104104

105105
before :each do
106106
@app_factory = DefaultRackApplicationFactory.new
107+
reset_booter
108+
reset_servlet_context_global
107109
end
108110

109111
it "should receive a rackup script via the 'rackup' parameter" do
@@ -161,11 +163,6 @@ def it_should_rewind_body
161163
expect(input_stream.getMaximumBufferSize).to eq 420
162164
end
163165

164-
before do
165-
reset_booter
166-
JRuby::Rack.context = $servlet_context = nil
167-
end
168-
169166
it "should init and create application object without a rackup script" do
170167
$servlet_context = @servlet_context
171168
# NOTE: a workaround to be able to mock it :
@@ -303,8 +300,8 @@ def newRuntime()
303300

304301
it "creates a new Ruby runtime with the jruby-rack environment pre-loaded" do
305302
@runtime = app_factory.newRuntime
306-
should_not_eval_as_nil "defined?(::Rack)"
307-
should_not_eval_as_nil "defined?(::Rack::Handler::Servlet)"
303+
should_eval_as_not_nil "defined?(::Rack)"
304+
should_eval_as_not_nil "defined?(::Rack::Handler::Servlet)"
308305
should_eval_as_nil "defined?(Rack::Handler::Bogus)"
309306
end
310307

@@ -333,14 +330,14 @@ def newRuntime()
333330
app_factory.checkAndSetRackVersion(@runtime)
334331
@runtime.evalScriptlet "require 'rack'"
335332

336-
should_not_eval_as_nil "defined?(Bundler)"
333+
should_eval_as_not_nil "defined?(Bundler)"
337334
should_eval_as_eql_to "Rack.release", '2.2.0'
338335
should_eval_as_eql_to "Gem.loaded_specs['rack'].version.to_s", '2.2.0'
339336
end
340337

341338
it "initializes the $servlet_context global variable" do
342339
@runtime = app_factory.new_runtime
343-
should_not_eval_as_nil "defined?($servlet_context)"
340+
expect(@runtime.evalScriptlet("defined?($servlet_context)")).to be_truthy
344341
end
345342

346343
it "clears environment variables if the configuration ignores the environment" do
@@ -534,7 +531,7 @@ def reset_config
534531

535532
describe org.jruby.rack.rails.RailsRackApplicationFactory do
536533

537-
java_import org.jruby.rack.rails.RailsRackApplicationFactory
534+
require 'jruby/rack/rails_booter'
538535

539536
before :each do
540537
@app_factory = RailsRackApplicationFactory.new
@@ -575,8 +572,20 @@ def createRackServletWrapper(runtime, rackup, filename)
575572

576573
describe org.jruby.rack.PoolingRackApplicationFactory do
577574

575+
# Workaround rspec mocks/proxies not being thread-safe which causes occasional failures
576+
class Synchronized
577+
def initialize(obj)
578+
@delegate = obj
579+
@lock = Mutex.new
580+
end
581+
582+
def method_missing(name, *args, &block)
583+
@lock.synchronize { @delegate.send(name, *args, &block) }
584+
end
585+
end
586+
578587
before :each do
579-
@factory = double "factory"
588+
@factory = Synchronized.new(double("factory").as_null_object)
580589
@pooling_factory = org.jruby.rack.PoolingRackApplicationFactory.new @factory
581590
@pooling_factory.context = @rack_context
582591
end
@@ -626,7 +635,7 @@ def createRackServletWrapper(runtime, rackup, filename)
626635
it "creates applications during initialization according to the jruby.min.runtimes context parameter" do
627636
allow(@factory).to receive(:init)
628637
allow(@factory).to receive(:newApplication) do
629-
app = double "app"
638+
app = Synchronized.new(double("app").as_null_object)
630639
expect(app).to receive(:init)
631640
app
632641
end
@@ -659,7 +668,7 @@ def createRackServletWrapper(runtime, rackup, filename)
659668
it "forces the maximum size to be greater or equal to the initial size" do
660669
allow(@factory).to receive(:init)
661670
allow(@factory).to receive(:newApplication) do
662-
app = double "app"
671+
app = Synchronized.new(double("app").as_null_object)
663672
expect(app).to receive(:init)
664673
app
665674
end
@@ -673,17 +682,17 @@ def createRackServletWrapper(runtime, rackup, filename)
673682
end
674683

675684
it "retrieves the error application from the delegate factory" do
676-
app = double("app")
685+
app = double "app"
677686
expect(@factory).to receive(:getErrorApplication).and_return app
678687
expect(@pooling_factory.getErrorApplication).to eq app
679688
end
680689

681690
it "waits till initial runtimes get initialized (with wait set to true)" do
682691
allow(@factory).to receive(:init)
683692
allow(@factory).to receive(:newApplication) do
684-
app = double "app"
693+
app = Synchronized.new(double("app").as_null_object)
685694
allow(app).to receive(:init) do
686-
sleep(0.10)
695+
sleep(0.05)
687696
end
688697
app
689698
end
@@ -697,15 +706,16 @@ def createRackServletWrapper(runtime, rackup, filename)
697706

698707
it "throws an exception from getApplication when an app failed to initialize " +
699708
"(even when only a single application initialization fails)" do
709+
app_init_secs = 0.05
700710
allow(@factory).to receive(:init)
701711
app_count = java.util.concurrent.atomic.AtomicInteger.new(0)
702712
allow(@factory).to receive(:newApplication) do
703-
app = double "app"
713+
app = Synchronized.new(double("app").as_null_object)
704714
allow(app).to receive(:init) do
705715
if app_count.addAndGet(1) == 2
706716
raise org.jruby.rack.RackInitializationException.new('failed app init')
707717
end
708-
sleep(0.05)
718+
sleep(app_init_secs)
709719
end
710720
app
711721
end
@@ -719,7 +729,7 @@ def createRackServletWrapper(runtime, rackup, filename)
719729
rescue org.jruby.rack.RackInitializationException
720730
# ignore - sometimes initialization happens fast enough that the init error is thrown already
721731
end
722-
sleep(0.20)
732+
sleep(num_runtimes * app_init_secs + 0.07) # sleep with a buffer
723733

724734
failed = 0
725735
num_runtimes.times do
@@ -735,28 +745,29 @@ def createRackServletWrapper(runtime, rackup, filename)
735745
end
736746

737747
it "wait until pool is filled when invoking getApplication (with wait set to false)" do
748+
app_init_secs = 0.2
738749
allow(@factory).to receive(:init)
739750
allow(@factory).to receive(:newApplication) do
740-
app = double "app"
741-
allow(app).to receive(:init) { sleep(0.2) }
751+
app = Synchronized.new(double("app").as_null_object)
752+
allow(app).to receive(:init) { sleep(app_init_secs) }
742753
app
743754
end
744755
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
745756
expect(@rack_config).to receive(:getInitialRuntimes).and_return 3
746757
expect(@rack_config).to receive(:getMaximumRuntimes).and_return 4
747758

748759
@pooling_factory.init(@rack_context)
749-
millis = java.lang.System.currentTimeMillis
760+
start = java.lang.System.currentTimeMillis
750761
expect(@pooling_factory.getApplication).not_to be nil
751-
millis = java.lang.System.currentTimeMillis - millis
752-
expect(millis).to be >= 150 # getApplication waited ~ 0.2 secs
762+
expect(java.lang.System.currentTimeMillis - start).to be_within(70).of(app_init_secs * 1000) # getApplication waited ~ sleep time
753763
end
754764

755765
it "waits acquire timeout till an application is available from the pool (than raises)" do
766+
app_init_secs = 0.2
756767
allow(@factory).to receive(:init)
757768
expect(@factory).to receive(:newApplication).twice do
758-
app = double "app"
759-
expect(app).to receive(:init) { sleep(0.2) }
769+
app = Synchronized.new(double("app").as_null_object)
770+
expect(app).to receive(:init) { sleep(app_init_secs) }
760771
app
761772
end
762773
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
@@ -765,86 +776,90 @@ def createRackServletWrapper(runtime, rackup, filename)
765776

766777
@pooling_factory.init(@rack_context)
767778
@pooling_factory.acquire_timeout = 1.to_java # second
768-
millis = java.lang.System.currentTimeMillis
779+
start = java.lang.System.currentTimeMillis
769780
expect(@pooling_factory.getApplication).not_to be nil
770-
millis = java.lang.System.currentTimeMillis - millis
771-
expect(millis).to be >= 150 # getApplication waited ~ 0.2 secs
781+
expect(java.lang.System.currentTimeMillis - start).to be_within(70).of(app_init_secs * 1000)
772782

773783
app2 = @pooling_factory.getApplication # now the pool is empty
774-
775-
@pooling_factory.acquire_timeout = 0.1.to_java # second
776-
millis = java.lang.System.currentTimeMillis
784+
timeout_secs = 0.1
785+
@pooling_factory.acquire_timeout = (timeout_secs).to_java
786+
start = java.lang.System.currentTimeMillis
777787
expect { @pooling_factory.getApplication }.to raise_error(org.jruby.rack.AcquireTimeoutException)
778-
millis = java.lang.System.currentTimeMillis - millis
779-
expect(millis).to be >= 90 # waited about ~ 0.1 secs
788+
expect(java.lang.System.currentTimeMillis - start).to be_within(20).of(timeout_secs * 1000)
780789

781790
@pooling_factory.finishedWithApplication(app2) # gets back to the pool
782791
expect(@pooling_factory.getApplication).to eq app2
783792
end
784793

785794
it "gets and initializes new applications until maximum allows to create more" do
795+
app_init_secs = 0.1
786796
allow(@factory).to receive(:init)
787797
expect(@factory).to receive(:newApplication).twice do
788-
app = double "app (new)"
789-
expect(app).to receive(:init) { sleep(0.1) }
798+
app = Synchronized.new(double("app (new)").as_null_object)
799+
expect(app).to receive(:init) { sleep(app_init_secs) }
790800
app
791801
end
792802
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
793803
allow(@rack_config).to receive(:getInitialRuntimes).and_return 2
794804
allow(@rack_config).to receive(:getMaximumRuntimes).and_return 4
795805

806+
timeout_secs = 0.1
796807
@pooling_factory.init(@rack_context)
797-
@pooling_factory.acquire_timeout = 0.10.to_java # second
808+
@pooling_factory.acquire_timeout = (timeout_secs).to_java # second
798809

799810
2.times { expect(@pooling_factory.getApplication).not_to be nil }
800811

812+
app_get_secs = 0.15
801813
expect(@factory).to receive(:getApplication).twice do
802-
app = double "app (get)"; sleep(0.15); app
814+
app = Synchronized.new(double("app (get)").as_null_object)
815+
sleep(app_get_secs)
816+
app
803817
end
804818

805-
millis = java.lang.System.currentTimeMillis
819+
start = java.lang.System.currentTimeMillis
806820
2.times { expect(@pooling_factory.getApplication).not_to be nil }
807-
millis = java.lang.System.currentTimeMillis - millis
808-
expect(millis).to be >= 300 # waited about 2 x 0.15 secs
821+
expect(java.lang.System.currentTimeMillis - start).to be_within(70).of(2 * app_get_secs * 1000)
809822

810-
millis = java.lang.System.currentTimeMillis
823+
start = java.lang.System.currentTimeMillis
811824
expect {
812825
@pooling_factory.getApplication
813826
}.to raise_error(org.jruby.rack.AcquireTimeoutException)
814-
millis = java.lang.System.currentTimeMillis - millis
815-
expect(millis).to be >= 90 # waited about ~ 0.10 secs
827+
expect(java.lang.System.currentTimeMillis - start).to be_within(20).of(timeout_secs * 1000)
816828
end
817829

818-
it "initializes initial runtimes in paralel (with wait set to false)" do
830+
it "initializes initial runtimes in parallel (with wait set to false)" do
831+
app_init_secs = 0.15
819832
allow(@factory).to receive(:init)
820833
allow(@factory).to receive(:newApplication) do
821-
app = double "app"
822-
allow(app).to receive(:init) do
823-
sleep(0.15)
824-
end
834+
app = Synchronized.new(double("app").as_null_object)
835+
allow(app).to receive(:init) { sleep(app_init_secs) }
825836
app
826837
end
838+
839+
init_threads = 4
840+
init_runtimes = 6
827841
allow(@rack_config).to receive(:getBooleanProperty).with("jruby.runtime.init.wait").and_return false
828-
allow(@rack_config).to receive(:getInitialRuntimes).and_return 6
842+
allow(@rack_config).to receive(:getRuntimeInitThreads).and_return init_threads
843+
allow(@rack_config).to receive(:getInitialRuntimes).and_return init_runtimes
829844
allow(@rack_config).to receive(:getMaximumRuntimes).and_return 8
830845

846+
expected_initial_init_time = 1.1 * (init_runtimes.to_f / init_threads.to_f).ceil * app_init_secs # 10% margin
831847
@pooling_factory.init(@rack_context)
832-
sleep(0.10)
833-
expect(@pooling_factory.getApplicationPool.size).to be < 6
834-
sleep(0.9)
835-
expect(@pooling_factory.getApplicationPool.size).to be >= 6
848+
sleep(app_init_secs) # wait for at some (but not possibly all) to finish
849+
expect(@pooling_factory.getApplicationPool.size).to be < init_runtimes
850+
sleep(expected_initial_init_time - app_init_secs) # remaining time
851+
expect(@pooling_factory.getApplicationPool.size).to be >= init_runtimes
836852

837853
expect(@pooling_factory.getManagedApplications).to_not be_empty
838-
expect(@pooling_factory.getManagedApplications.size).to eql 6
854+
expect(@pooling_factory.getManagedApplications.size).to eql init_runtimes
839855
end
840856

841857
it "throws from init when application initialization in thread failed" do
858+
app_init_secs = 0.05
842859
allow(@factory).to receive(:init)
843860
allow(@factory).to receive(:newApplication) do
844-
app = double "app"
845-
allow(app).to receive(:init) do
846-
sleep(0.05); raise "app.init raising"
847-
end
861+
app = Synchronized.new(double("app").as_null_object)
862+
allow(app).to receive(:init) { sleep(app_init_secs); raise "app.init raising" }
848863
app
849864
end
850865
allow(@rack_config).to receive(:getInitialRuntimes).and_return 2
@@ -863,9 +878,6 @@ def createRackServletWrapper(runtime, rackup, filename)
863878

864879
expect { @pooling_factory.init(@rack_context) }.to raise_error org.jruby.rack.RackInitializationException
865880
expect(raise_error_logged).to eql 1 # logs same init exception once
866-
867-
# NOTE: seems it's not such a good idea to return empty on init error
868-
# expect(@pooling_factory.getManagedApplications).to be_empty
869881
end
870882

871883
end

0 commit comments

Comments
 (0)