Skip to content

Commit

Permalink
Add some initial tests
Browse files Browse the repository at this point in the history
- end-to-end tests
- scheduling tests

Part of #1
  • Loading branch information
agis committed Jul 21, 2020
1 parent 955c3cf commit 2390b97
Show file tree
Hide file tree
Showing 30 changed files with 422 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.gem
dump.rdb
Gemfile.lock
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ in the final report (`--report`).
Workers emit a timestamp after each example, as a heartbeat, to denote
that they're fine and performing jobs. If a worker hasn't reported for
a given amount of time (see `WORKER_LIVENESS_SEC`) it is considered dead
and the job it reserved will be requeued, so that it is picked up by another worker.
and the job it reserved will be requeued, so that it is picked up by another
worker.

This protects us against unrecoverable worker failures
(e.g. a segmentation fault in MRI).
Expand All @@ -100,12 +101,18 @@ This protects us against unrecoverable worker failures

**Update**: ci-queue [deprecated support for RSpec](https://github.com/Shopify/ci-queue/pull/149).

While evaluating ci-queue for our RSpec suite, we experienced slow worker boot times (up to 3 minutes in some cases) combined with disk saturation and increased memory consumption. This is due to the fact that a worker in ci-queue has to
load every spec file on boot. In applications with
a large number of spec files this may result in a significant performance hit and in case of cloud environments increased billings.
While evaluating ci-queue for our RSpec suite, we experienced slow worker boot
times (up to 3 minutes in some cases) combined with disk saturation and
increased memory consumption. This is due to the fact that a worker in
ci-queue has to
load every spec file on boot. In applications with large number of spec
files this may result in a significant performance hit and, in case of cloud
environments, increased usage billings.

RSpecQ works with spec files as its unit of work (as opposed to ci-queue which
works with individual examples). This means that an RSpecQ worker only loads a file when it's needed and each worker only loads a subset of all files. Additionally this allows suites to keep using `before(:all)` hooks
works with individual examples). This means that an RSpecQ worker only loads a
file when it's needed and each worker only loads a subset of all files.
Additionally this allows suites to keep using `before(:all)` hooks
(which ci-queue explicitly rejects). (Note: RSpecQ also schedules individual
examples, but only when this is deemed necessary, see section
"Spec file splitting").
Expand All @@ -121,6 +128,20 @@ file threshold" which, currently has to be set manually (but this can be
improved).


## Development

First install the required development/runtime dependencies:

```
$ bundle install
```

Then you can execute the tests after spinning up a Redis instance at
127.0.0.1:6379:

```
$ bundle exec rake
```


## License
Expand Down
9 changes: 9 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
require "rake/testtask"

Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = FileList['test/test*.rb']
t.verbose = true
end

task default: :test
4 changes: 3 additions & 1 deletion rspecq.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Gem::Specification.new do |s|
s.add_dependency "rspec-core"
s.add_dependency "redis"

s.add_development_dependency "minitest", "~> 5.14"
s.add_development_dependency "rake"
s.add_development_dependency "pry-byebug"
s.add_development_dependency "minitest"
s.add_development_dependency "rspec"
end
1 change: 1 addition & 0 deletions test/sample_suites/README
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sample RSpec suites, used as fixtures in the tests.
5 changes: 5 additions & 0 deletions test/sample_suites/different_spec_path/mytests/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RSpec.describe do
it "I should not be executed!" do
expect(1).to eq 2
end
end
13 changes: 13 additions & 0 deletions test/sample_suites/different_spec_path/mytests/qwe_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
RSpec.describe do
context "foo" do
describe "abc" do
it { expect(false).to be false }
end
end

context "bar" do
describe "dfg" do
it { expect(true).to be true }
end
end
end
4 changes: 4 additions & 0 deletions test/sample_suites/failing_suite/spec/bar_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RSpec.describe do
it { expect(false).to be false }
it { expect(1).to be 2 }
end
3 changes: 3 additions & 0 deletions test/sample_suites/failing_suite/spec/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RSpec.describe do
it { expect(true).to be true }
end
8 changes: 8 additions & 0 deletions test/sample_suites/flakey_suite/spec/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
RSpec.describe do
it do
$tries ||= 0
$tries += 1

expect($tries).to eq 3
end
end
3 changes: 3 additions & 0 deletions test/sample_suites/non_example_error/spec/bar_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RSpec.describe do
it { expect(true).to be true }
end
3 changes: 3 additions & 0 deletions test/sample_suites/non_example_error/spec/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RSpec.describe IDONTEXISTZ do
it { expect(true).to be true }
end
3 changes: 3 additions & 0 deletions test/sample_suites/passing_suite/spec/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RSpec.describe do
it { expect(true).to be true }
end
9 changes: 9 additions & 0 deletions test/sample_suites/scheduling/spec/bar_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
RSpec.describe do
it do
expect(true).to be true
end

it do
expect(true).to be true
end
end
13 changes: 13 additions & 0 deletions test/sample_suites/scheduling/spec/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
RSpec.describe "slow spec file (will be split)" do
it do
sleep 0.1
expect(true).to be true
end

context "foo" do
it do
sleep 0.2
expect(true).to be true
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RSpec.describe do
it do
expect(true).to be true
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/scheduling_untimed/spec/foo/bar_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.15
expect(true).to be true
end
end
5 changes: 5 additions & 0 deletions test/sample_suites/scheduling_untimed/spec/foo/baz_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RSpec.describe do
it do
expect(true).to be true
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/scheduling_untimed/spec/foo/foo_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.1
expect(true).to be true
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/scheduling_untimed/spec/foo/zxc_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.05
expect(true).to be true
end
end
4 changes: 4 additions & 0 deletions test/sample_suites/spec_file_splitting/spec/fast_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
RSpec.describe do
it { expect(true).to be true }
it { expect(true).to be true }
end
13 changes: 13 additions & 0 deletions test/sample_suites/spec_file_splitting/spec/slow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
RSpec.describe do
it do
sleep 0.6
expect(true).to be true
end

context "foo" do
it do
sleep 0.6
expect(true).to be true
end
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/timings/spec/fast_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.1
expect(true).to be true
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/timings/spec/medium_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.2
expect(true).to be true
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/timings/spec/slow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.3
expect(true).to be true
end
end
5 changes: 5 additions & 0 deletions test/sample_suites/timings/spec/very_fast_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
RSpec.describe do
it do
expect(true).to be true
end
end
6 changes: 6 additions & 0 deletions test/sample_suites/timings/spec/very_slow_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
RSpec.describe do
it do
sleep 0.4
expect(true).to be true
end
end
96 changes: 96 additions & 0 deletions test/test_e2e.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
require "test_helpers"

class TestEndToEnd < RSpecQTest
def test_suite_with_legit_failures
queue = exec_build("failing_suite")

refute queue.build_successful?

assert_processed_jobs [
"./spec/foo_spec.rb",
"./spec/bar_spec.rb",
"./spec/bar_spec.rb[1:2]",
], queue

assert_equal 3+RSpecQ::MAX_REQUEUES, queue.example_count

assert_equal({ "./spec/bar_spec.rb[1:2]" => "3" }, queue.requeued_jobs)
end

def test_passing_suite
queue = exec_build("passing_suite")

assert queue.build_successful?
assert_build_not_flakey(queue)
assert_equal 1, queue.example_count
assert_equal ["./spec/foo_spec.rb"], queue.processed_jobs
end

def test_flakey_suite
queue = exec_build("flakey_suite")

assert queue.build_successful?
assert_processed_jobs [
"./spec/foo_spec.rb",
"./spec/foo_spec.rb[1:1]",
], queue

assert_equal({ "./spec/foo_spec.rb[1:1]" => "2" }, queue.requeued_jobs)
end

def test_scheduling_by_file_and_custom_spec_path
queue = exec_build("different_spec_path", "mytests/qwe_spec.rb")

assert queue.build_successful?
assert_build_not_flakey(queue)
assert_equal 2, queue.example_count
assert_processed_jobs ["./mytests/qwe_spec.rb"], queue
end

def test_non_example_error
queue = exec_build("non_example_error")

refute queue.build_successful?
assert_build_not_flakey(queue)
assert_equal 1, queue.example_count
assert_processed_jobs ["./spec/foo_spec.rb", "./spec/bar_spec.rb"], queue
assert_equal ["./spec/foo_spec.rb"], queue.non_example_errors.keys
end

def test_timings_update
queue = exec_build("timings", "--update-timings")

assert queue.build_successful?

assert_equal [
"./spec/very_fast_spec.rb",
"./spec/fast_spec.rb",
"./spec/medium_spec.rb",
"./spec/slow_spec.rb",
"./spec/very_slow_spec.rb",
], queue.timings.sort_by { |k,v| v }.map(&:first)
end

def test_timings_no_update
queue = exec_build("timings")

assert queue.build_successful?
assert_empty queue.timings
end

def test_spec_file_splitting
queue = exec_build( "spec_file_splitting", "--update-timings")
assert queue.build_successful?
refute_empty queue.timings

queue = exec_build( "spec_file_splitting", "--file-split-threshold 1")

assert queue.build_successful?
refute_empty queue.timings
assert_processed_jobs([
"./spec/slow_spec.rb[1:2:1]",
"./spec/slow_spec.rb[1:1]",
"./spec/fast_spec.rb",
], queue)
end
end
Loading

0 comments on commit 2390b97

Please sign in to comment.