diff options
Diffstat (limited to 'spec/lib/gitlab/ci/pipeline/duration_spec.rb')
-rw-r--r-- | spec/lib/gitlab/ci/pipeline/duration_spec.rb | 156 |
1 files changed, 141 insertions, 15 deletions
diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb index 36714413da6..89c0ce46237 100644 --- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Gitlab::Ci::Pipeline::Duration do +RSpec.describe Gitlab::Ci::Pipeline::Duration, feature_category: :continuous_integration do describe '.from_periods' do let(:calculated_duration) { calculate(data) } @@ -113,16 +113,17 @@ RSpec.describe Gitlab::Ci::Pipeline::Duration do described_class::Period.new(first, last) end - described_class.from_periods(periods.sort_by(&:first)) + described_class.send(:from_periods, periods.sort_by(&:first)) end end describe '.from_pipeline' do + let_it_be_with_reload(:pipeline) { create(:ci_pipeline) } + let_it_be(:start_time) { Time.current.change(usec: 0) } let_it_be(:current) { start_time + 1000 } - let_it_be(:pipeline) { create(:ci_pipeline) } - let_it_be(:success_build) { create_build(:success, started_at: start_time, finished_at: start_time + 60) } - let_it_be(:failed_build) { create_build(:failed, started_at: start_time + 60, finished_at: start_time + 120) } + let_it_be(:success_build) { create_build(:success, started_at: start_time, finished_at: start_time + 50) } + let_it_be(:failed_build) { create_build(:failed, started_at: start_time + 60, finished_at: start_time + 110) } let_it_be(:canceled_build) { create_build(:canceled, started_at: start_time + 120, finished_at: start_time + 180) } let_it_be(:skipped_build) { create_build(:skipped, started_at: start_time) } let_it_be(:pending_build) { create_build(:pending) } @@ -141,21 +142,55 @@ RSpec.describe Gitlab::Ci::Pipeline::Duration do end context 'when there is no running build' do - let(:running_build) { nil } + let!(:running_build) { nil } it 'returns the duration for all the builds' do travel_to(current) do - expect(described_class.from_pipeline(pipeline)).to eq 180.seconds + # 160 = success (50) + failed (50) + canceled (60) + expect(described_class.from_pipeline(pipeline)).to eq 160.seconds end end end - context 'when there are bridge jobs' do - let!(:success_bridge) { create_bridge(:success, started_at: start_time + 220, finished_at: start_time + 280) } - let!(:failed_bridge) { create_bridge(:failed, started_at: start_time + 180, finished_at: start_time + 240) } - let!(:skipped_bridge) { create_bridge(:skipped, started_at: start_time) } - let!(:created_bridge) { create_bridge(:created) } - let!(:manual_bridge) { create_bridge(:manual) } + context 'when there are direct bridge jobs' do + let_it_be(:success_bridge) do + create_bridge(:success, started_at: start_time + 220, finished_at: start_time + 280) + end + + let_it_be(:failed_bridge) { create_bridge(:failed, started_at: start_time + 180, finished_at: start_time + 240) } + # NOTE: bridge won't be `canceled` as it will be marked as failed when downstream pipeline is canceled + # @see Ci::Bridge#inherit_status_from_downstream + let_it_be(:canceled_bridge) do + create_bridge(:failed, started_at: start_time + 180, finished_at: start_time + 210) + end + + let_it_be(:skipped_bridge) { create_bridge(:skipped, started_at: start_time) } + let_it_be(:created_bridge) { create_bridge(:created) } + let_it_be(:manual_bridge) { create_bridge(:manual) } + + let_it_be(:success_bridge_pipeline) do + create(:ci_pipeline, :success, started_at: start_time + 230, finished_at: start_time + 280).tap do |p| + create(:ci_sources_pipeline, source_job: success_bridge, pipeline: p) + create_build(:success, pipeline: p, started_at: start_time + 235, finished_at: start_time + 280) + create_bridge(:success, pipeline: p, started_at: start_time + 240, finished_at: start_time + 280) + end + end + + let_it_be(:failed_bridge_pipeline) do + create(:ci_pipeline, :failed, started_at: start_time + 225, finished_at: start_time + 240).tap do |p| + create(:ci_sources_pipeline, source_job: failed_bridge, pipeline: p) + create_build(:failed, pipeline: p, started_at: start_time + 230, finished_at: start_time + 240) + create_bridge(:success, pipeline: p, started_at: start_time + 235, finished_at: start_time + 240) + end + end + + let_it_be(:canceled_bridge_pipeline) do + create(:ci_pipeline, :canceled, started_at: start_time + 190, finished_at: start_time + 210).tap do |p| + create(:ci_sources_pipeline, source_job: canceled_bridge, pipeline: p) + create_build(:canceled, pipeline: p, started_at: start_time + 200, finished_at: start_time + 210) + create_bridge(:success, pipeline: p, started_at: start_time + 205, finished_at: start_time + 210) + end + end it 'returns the duration of the running build' do travel_to(current) do @@ -166,12 +201,99 @@ RSpec.describe Gitlab::Ci::Pipeline::Duration do context 'when there is no running build' do let!(:running_build) { nil } - it 'returns the duration for all the builds and bridge jobs' do + it 'returns the duration for all the builds (including self and downstreams)' do travel_to(current) do - expect(described_class.from_pipeline(pipeline)).to eq 280.seconds + # 220 = 160 (see above) + # + success build (45) + failed (10) + canceled (10) - overlapping (success & failed) (5) + expect(described_class.from_pipeline(pipeline)).to eq 220.seconds end end end + + # rubocop:disable RSpec/MultipleMemoizedHelpers + context 'when there are downstream bridge jobs' do + let_it_be(:success_direct_bridge) do + create_bridge(:success, started_at: start_time + 280, finished_at: start_time + 400) + end + + let_it_be(:success_downstream_pipeline) do + create(:ci_pipeline, :success, started_at: start_time + 285, finished_at: start_time + 300).tap do |p| + create(:ci_sources_pipeline, source_job: success_direct_bridge, pipeline: p) + create_build(:success, pipeline: p, started_at: start_time + 290, finished_at: start_time + 296) + create_bridge(:success, pipeline: p, started_at: start_time + 285, finished_at: start_time + 288) + end + end + + let_it_be(:failed_downstream_pipeline) do + create(:ci_pipeline, :failed, started_at: start_time + 305, finished_at: start_time + 350).tap do |p| + create(:ci_sources_pipeline, source_job: success_direct_bridge, pipeline: p) + create_build(:failed, pipeline: p, started_at: start_time + 320, finished_at: start_time + 327) + create_bridge(:success, pipeline: p, started_at: start_time + 305, finished_at: start_time + 350) + end + end + + let_it_be(:canceled_downstream_pipeline) do + create(:ci_pipeline, :canceled, started_at: start_time + 360, finished_at: start_time + 400).tap do |p| + create(:ci_sources_pipeline, source_job: success_direct_bridge, pipeline: p) + create_build(:canceled, pipeline: p, started_at: start_time + 390, finished_at: start_time + 398) + create_bridge(:success, pipeline: p, started_at: start_time + 360, finished_at: start_time + 378) + end + end + + it 'returns the duration of the running build' do + travel_to(current) do + expect(described_class.from_pipeline(pipeline)).to eq 1000.seconds + end + end + + context 'when there is no running build' do + let!(:running_build) { nil } + + it 'returns the duration for all the builds (including self and downstreams)' do + travel_to(current) do + # 241 = 220 (see above) + # + success downstream build (6) + failed (7) + canceled (8) + expect(described_class.from_pipeline(pipeline)).to eq 241.seconds + end + end + end + end + # rubocop:enable RSpec/MultipleMemoizedHelpers + end + + it 'does not generate N+1 queries if more builds are added' do + travel_to(current) do + expect do + described_class.from_pipeline(pipeline) + end.not_to exceed_query_limit(1) + + create_list(:ci_build, 2, :success, pipeline: pipeline, started_at: start_time, finished_at: start_time + 50) + + expect do + described_class.from_pipeline(pipeline) + end.not_to exceed_query_limit(1) + end + end + + it 'does not generate N+1 queries if more bridges and their pipeline builds are added' do + travel_to(current) do + expect do + described_class.from_pipeline(pipeline) + end.not_to exceed_query_limit(1) + + create_list( + :ci_bridge, 2, :success, + pipeline: pipeline, started_at: start_time + 220, finished_at: start_time + 280).each do |bridge| + create(:ci_pipeline, :success, started_at: start_time + 235, finished_at: start_time + 280).tap do |p| + create(:ci_sources_pipeline, source_job: bridge, pipeline: p) + create_builds(3, :success) + end + end + + expect do + described_class.from_pipeline(pipeline) + end.not_to exceed_query_limit(1) + end end private @@ -180,6 +302,10 @@ RSpec.describe Gitlab::Ci::Pipeline::Duration do create(:ci_build, trait, pipeline: pipeline, **opts) end + def create_builds(counts, trait, **opts) + create_list(:ci_build, counts, trait, pipeline: pipeline, **opts) + end + def create_bridge(trait, **opts) create(:ci_bridge, trait, pipeline: pipeline, **opts) end |