Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ jobs:
image: ${{ matrix.image }}
env:
POSTGRES_PASSWORD: rootpassword
# Test-only durability tuning. The data directory is tmpfs already,
# so durability has been given up — these flags just stop Postgres
# from doing the matching work.
command: >-
postgres
-c fsync=off
-c synchronous_commit=off
-c full_page_writes=off
options: >-
--tmpfs /var/lib/postgresql/data:rw,size=2g
--health-cmd pg_isready
Expand All @@ -61,8 +69,22 @@ jobs:
- uses: hmarr/debug-action@v3
- uses: actions/checkout@v6
- uses: ./.github/workflows/composite/setup
- name: Restore parallel_rspec runtime logs
uses: actions/cache/restore@v4
with:
path: tmp/parallel_runtime_rspec_*.log
key: parallel-runtime-postgres-${{ matrix.image }}-${{ github.run_id }}
restore-keys: |
parallel-runtime-postgres-${{ matrix.image }}-
parallel-runtime-postgres-
- name: Run tests
run: DB=postgres POSTGRES_CONNECTION_PREFIX="postgres://postgres:rootpassword@localhost:5432" bundle exec rake spec
- name: Save parallel_rspec runtime logs
if: always()
uses: actions/cache/save@v4
with:
path: tmp/parallel_runtime_rspec_*.log
key: parallel-runtime-postgres-${{ matrix.image }}-${{ github.run_id }}
- uses: ravsamhq/notify-slack-action@v2
if: github.event_name == 'push'
with:
Expand All @@ -84,6 +106,12 @@ jobs:
env:
MYSQL_DATABASE: cc_test
MYSQL_ROOT_PASSWORD: password
# Test-only durability tuning. tmpfs has already removed durability,
# so skip the redo/binlog/doublewrite work that would back it up.
command: >-
--innodb-flush-log-at-trx-commit=0
--sync-binlog=0
--innodb-doublewrite=OFF
options: >-
--tmpfs /var/lib/mysql:rw,size=2g
--health-cmd="mysqladmin ping"
Expand All @@ -96,8 +124,22 @@ jobs:
- uses: hmarr/debug-action@v3
- uses: actions/checkout@v6
- uses: ./.github/workflows/composite/setup
- name: Restore parallel_rspec runtime logs
uses: actions/cache/restore@v4
with:
path: tmp/parallel_runtime_rspec_*.log
key: parallel-runtime-mysql-${{ matrix.image }}-${{ github.run_id }}
restore-keys: |
parallel-runtime-mysql-${{ matrix.image }}-
parallel-runtime-mysql-
- name: Run tests
run: DB=mysql MYSQL_CONNECTION_PREFIX="mysql2://root:password@127.0.0.1:3306" bundle exec rake spec
- name: Save parallel_rspec runtime logs
if: always()
uses: actions/cache/save@v4
with:
path: tmp/parallel_runtime_rspec_*.log
key: parallel-runtime-mysql-${{ matrix.image }}-${{ github.run_id }}
- uses: ravsamhq/notify-slack-action@v2
if: github.event_name == 'push'
with:
Expand Down
1 change: 0 additions & 1 deletion .rspec_parallel
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
--format RSpec::Instafail
--format progress
--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
--order rand
1 change: 1 addition & 0 deletions .rubocop_cc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Metrics/BlockLength:
- config/routes.rb
- lib/tasks/db.rake
- lib/tasks/jobs.rake
- lib/tasks/spec.rake
Max: 50
Metrics/CyclomaticComplexity:
Max: 12
Expand Down
19 changes: 16 additions & 3 deletions lib/tasks/spec.rake
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace :spec do
run_specs(ARGV[1])
else
run_specs_parallel('spec')
# Run isolated specs separately since they might affect other tests
run_migration_specs_parallel
run_specs('spec/isolated_specs')
end
end
Expand All @@ -32,6 +32,7 @@ namespace :spec do
run_specs(ARGV[1], 'NO_DB_MIGRATION=true')
else
run_specs_parallel('spec', 'NO_DB_MIGRATION=true')
run_migration_specs_parallel('NO_DB_MIGRATION=true')
# Run isolated specs separately since they might affect other tests
run_specs('spec/isolated_specs', 'NO_DB_MIGRATION=true')
end
Expand All @@ -44,17 +45,29 @@ namespace :spec do
def run_specs_parallel(path, env_vars='')
command = <<~CMD
#{env_vars} bundle exec parallel_rspec \
--test-options '--order rand' \
--test-options '--order rand --profile 20 --format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec_main.log' \
--runtime-log tmp/parallel_runtime_rspec_main.log \
--single spec/integration/ \
--single spec/acceptance/ \
--isolate \
--exclude-pattern 'spec/isolated_specs/' \
--exclude-pattern '(spec/isolated_specs/|spec/migrations/)' \
-- #{path}
CMD

sh command
end

def run_migration_specs_parallel(env_vars='')
command = <<~CMD
#{env_vars} bundle exec parallel_rspec \
--test-options '--order rand --profile 20 --format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec_migrations.log' \
--runtime-log tmp/parallel_runtime_rspec_migrations.log \
-- spec/migrations
CMD

sh command
end

def run_failed_specs
sh 'bundle exec rspec --only-failures --color --tty spec --require rspec/instafail --format RSpec::Instafail'
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
require 'database/bigint_migration'

RSpec.shared_context 'bigint migration step1' do
before(:all) { skip unless Sequel::Model.db.database_type == :postgres } # rubocop:disable RSpec/BeforeAfterAll

include_context 'migration'

let(:skip_bigint_id_migration) { nil }

before do
skip unless db.database_type == :postgres

allow_any_instance_of(VCAP::CloudController::Config).to receive(:get).with(:skip_bigint_id_migration).and_return(skip_bigint_id_migration)
allow_any_instance_of(VCAP::CloudController::Config).to receive(:get).with(:migration_psql_concurrent_statement_timeout_in_seconds).and_return(300)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require 'database/bigint_migration'

RSpec.shared_context 'bigint migration step3a' do
before(:all) { skip unless Sequel::Model.db.database_type == :postgres } # rubocop:disable RSpec/BeforeAfterAll

let(:migration_filename) { migration_filename_step1 }
let(:current_migration_index_step3a) { migration_filename_step3a.match(/\A\d+/)[0].to_i }

Expand All @@ -12,8 +14,6 @@
let(:logger) { double(:logger, info: nil) }

before do
skip unless db.database_type == :postgres

allow_any_instance_of(VCAP::CloudController::Config).to receive(:get).with(:skip_bigint_id_migration).and_return(skip_bigint_id_migration)
allow_any_instance_of(VCAP::CloudController::Config).to receive(:get).with(:migration_psql_concurrent_statement_timeout_in_seconds).and_return(300)
end
Expand Down Expand Up @@ -107,6 +107,8 @@
end

RSpec.shared_context 'bigint migration step3b' do
before(:all) { skip unless Sequel::Model.db.database_type == :postgres } # rubocop:disable RSpec/BeforeAfterAll

let(:migration_filename) { migration_filename_step1 }
let(:current_migration_index_step3a) { migration_filename_step3a.match(/\A\d+/)[0].to_i }
let(:current_migration_index_step3b) { migration_filename_step3b.match(/\A\d+/)[0].to_i }
Expand All @@ -117,8 +119,6 @@
let(:logger) { double(:logger, info: nil) }

before do
skip unless db.database_type == :postgres

allow_any_instance_of(VCAP::CloudController::Config).to receive(:get).with(:skip_bigint_id_migration).and_return(skip_bigint_id_migration)
allow_any_instance_of(VCAP::CloudController::Config).to receive(:get).with(:migration_psql_concurrent_statement_timeout_in_seconds).and_return(300)
end
Expand Down
41 changes: 35 additions & 6 deletions spec/support/database_isolation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,49 @@ def self.choose(isolation, db)
end
end

# Sequel logger that captures the names of tables written to (INSERT/UPDATE/DELETE).
# Attached to the DB only for the duration of one example so we can truncate just
# the tables the example dirtied, instead of all 100+ tables.
class WrittenTablesLogger
WRITE_REGEX = /\b(?:INSERT INTO|UPDATE|DELETE FROM|TRUNCATE TABLE|TRUNCATE)\s+[`"]?(\w+)/i

attr_reader :tables

def initialize
@tables = Set.new
end

def capture(msg)
@tables << ::Regexp.last_match(1).to_sym if msg =~ WRITE_REGEX
end

alias_method :info, :capture
alias_method :warn, :capture
alias_method :debug, :capture
alias_method :error, :capture
alias_method :fatal, :capture
end

class TruncateTables
def initialize(db)
@db = db
end

def cleanly
yield
ensure
reset_tables
logger = WrittenTablesLogger.new
db.loggers << logger
begin
yield
ensure
db.loggers.delete(logger)
reset_tables(logger.tables.to_a & TableTruncator.isolated_tables(db))
end
end

def reset_tables
table_truncator = TableTruncator.new(db)
table_truncator.truncate_tables
def reset_tables(tables)
return if tables.empty?

TableTruncator.new(db, tables).truncate_tables

# VCAP::CloudController::Seeds requires the :api config
TestConfig.context = :api
Expand Down
54 changes: 27 additions & 27 deletions spec/unit/jobs/runtime/prune_completed_builds_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module VCAP::CloudController
module Jobs::Runtime
RSpec.describe PruneCompletedBuilds, job_context: :worker do
let(:max_retained_builds_per_app) { 15 }
let(:max_retained_builds_per_app) { 3 }

subject(:job) { PruneCompletedBuilds.new(max_retained_builds_per_app) }

Expand All @@ -19,67 +19,67 @@ module Jobs::Runtime
it 'deletes all the staged builds over the limit' do
expect(BuildModel.count).to eq(0)

total = 50
(1..50).each do |i|
total = 8
(1..total).each do |i|
BuildModel.make(id: i, state: BuildModel::STAGED_STATE, app: app, created_at: Time.now - total + i)
end

job.perform

expect(BuildModel.count).to eq(15)
expect(BuildModel.map(&:id)).to match_array((36..50).to_a)
expect(BuildModel.count).to eq(3)
expect(BuildModel.map(&:id)).to match_array((6..8).to_a)
end

it 'deletes all failed builds over the limit' do
expect(BuildModel.count).to eq(0)

total = 50
(1..50).each do |i|
total = 8
(1..total).each do |i|
BuildModel.make(id: i, state: BuildModel::FAILED_STATE, app: app, created_at: Time.now - total + i)
end

job.perform

expect(BuildModel.count).to eq(15)
expect(BuildModel.map(&:id)).to match_array((36..50).to_a)
expect(BuildModel.count).to eq(3)
expect(BuildModel.map(&:id)).to match_array((6..8).to_a)
end

it 'does NOT delete any staging builds over the limit' do
expect(BuildModel.count).to eq(0)

total = 50
(1..50).each do |i|
total = 8
(1..total).each do |i|
BuildModel.make(id: i, state: BuildModel::STAGING_STATE, app: app, created_at: Time.now - total + i)
end

job.perform

expect(BuildModel.count).to eq(50)
expect(BuildModel.map(&:id)).to match_array((1..50).to_a)
expect(BuildModel.count).to eq(8)
expect(BuildModel.map(&:id)).to match_array((1..8).to_a)
end

it 'does not delete in-flight builds over the limit' do
total = 60
(1..20).each do |i|
total = 12
(1..4).each do |i|
BuildModel.make(id: i, state: BuildModel::STAGED_STATE, app: app, created_at: Time.now - total + i)
end
(21..40).each do |i|
(5..8).each do |i|
BuildModel.make(id: i, state: BuildModel::STAGING_STATE, app: app, created_at: Time.now - total + i)
end
(41..60).each do |i|
(9..12).each do |i|
BuildModel.make(id: i, state: BuildModel::STAGED_STATE, app: app, created_at: Time.now - total + i)
end

job.perform

expect(BuildModel.count).to be(35)
expect(BuildModel.order(Sequel.asc(:created_at), Sequel.asc(:id)).map(&:id)).to eq((21..40).to_a + (46..60).to_a)
expect(BuildModel.count).to be(7)
expect(BuildModel.order(Sequel.asc(:created_at), Sequel.asc(:id)).map(&:id)).to eq((5..8).to_a + (10..12).to_a)
end

it 'calls destroy on the BuildModel so association dependencies are respected' do
expect(BuildModel.count).to eq(0)

50.times do
8.times do
b = BuildModel.make(state: BuildModel::STAGED_STATE, app: app)
BuildpackLifecycleDataModel.make(build: b)
end
Expand All @@ -97,22 +97,22 @@ module Jobs::Runtime
expect(BuildModel.count).to eq(0)

[app, app_the_second, app_the_third].each_with_index do |current_app, app_index|
total = 50
total = 8
(1..total).each do |i|
BuildModel.make(id: i + (1000 * app_index), state: BuildModel::STAGED_STATE, app: current_app, created_at: Time.now - total + i)
end
end

job.perform

expect(BuildModel.where(app:).count).to eq(15)
expect(BuildModel.where(app:).map(&:id)).to match_array((36..50).to_a)
expect(BuildModel.where(app:).count).to eq(3)
expect(BuildModel.where(app:).map(&:id)).to match_array((6..8).to_a)

expect(BuildModel.where(app: app_the_second).count).to eq(15)
expect(BuildModel.where(app: app_the_second).map(&:id)).to match_array((1036..1050).to_a)
expect(BuildModel.where(app: app_the_second).count).to eq(3)
expect(BuildModel.where(app: app_the_second).map(&:id)).to match_array((1006..1008).to_a)

expect(BuildModel.where(app: app_the_third).count).to eq(15)
expect(BuildModel.where(app: app_the_third).map(&:id)).to match_array((2036..2050).to_a)
expect(BuildModel.where(app: app_the_third).count).to eq(3)
expect(BuildModel.where(app: app_the_third).map(&:id)).to match_array((2006..2008).to_a)
end
end

Expand Down
Loading
Loading