Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-21 02:50:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-21 02:50:22 +0300
commit9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch)
tree70467ae3692a0e35e5ea56bcb803eb512a10bedb /spec/models/ci
parent4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff)
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'spec/models/ci')
-rw-r--r--spec/models/ci/artifact_blob_spec.rb1
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb162
-rw-r--r--spec/models/ci/build_trace_chunk_spec.rb1
-rw-r--r--spec/models/ci/daily_build_group_report_result_spec.rb1
-rw-r--r--spec/models/ci/job_artifact_spec.rb16
-rw-r--r--spec/models/ci/pipeline_schedule_spec.rb12
-rw-r--r--spec/models/ci/pipeline_spec.rb102
-rw-r--r--spec/models/ci/runner_spec.rb1
-rw-r--r--spec/models/ci/stage_spec.rb13
-rw-r--r--spec/models/ci/test_case_failure_spec.rb73
-rw-r--r--spec/models/ci/test_case_spec.rb31
-rw-r--r--spec/models/ci/unit_test_failure_spec.rb73
-rw-r--r--spec/models/ci/unit_test_spec.rb87
14 files changed, 415 insertions, 160 deletions
diff --git a/spec/models/ci/artifact_blob_spec.rb b/spec/models/ci/artifact_blob_spec.rb
index 44f895cc1c5..c00f46683b9 100644
--- a/spec/models/ci/artifact_blob_spec.rb
+++ b/spec/models/ci/artifact_blob_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Ci::ArtifactBlob do
let_it_be(:project) { create(:project, :public) }
let_it_be(:build) { create(:ci_build, :artifacts, project: project) }
+
let(:entry) { build.artifacts_metadata_entry('other_artifacts_0.1.2/another-subdirectory/banana_sample.gif') }
subject { described_class.new(entry) }
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index f3029598b02..db956b26b6b 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -50,7 +50,7 @@ RSpec.describe Ci::Bridge do
CI_PROJECT_PATH_SLUG CI_PROJECT_NAMESPACE CI_PROJECT_ROOT_NAMESPACE
CI_PIPELINE_IID CI_CONFIG_PATH CI_PIPELINE_SOURCE CI_COMMIT_MESSAGE
CI_COMMIT_TITLE CI_COMMIT_DESCRIPTION CI_COMMIT_REF_PROTECTED
- CI_COMMIT_TIMESTAMP
+ CI_COMMIT_TIMESTAMP CI_COMMIT_AUTHOR
]
expect(bridge.scoped_variables.map { |v| v[:key] }).to include(*variables)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 5b07bd8923f..339dffa507f 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -585,6 +585,68 @@ RSpec.describe Ci::Build do
is_expected.to be_falsey
end
end
+
+ context 'with runners_cached_states feature flag enabled' do
+ before do
+ stub_feature_flags(runners_cached_states: true)
+ end
+
+ it 'caches the result in Redis' do
+ expect(Rails.cache).to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute)
+
+ build.any_runners_online?
+ end
+ end
+
+ context 'with runners_cached_states feature flag disabled' do
+ before do
+ stub_feature_flags(runners_cached_states: false)
+ end
+
+ it 'does not cache' do
+ expect(Rails.cache).not_to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute)
+
+ build.any_runners_online?
+ end
+ end
+ end
+
+ describe '#any_runners_available?' do
+ subject { build.any_runners_available? }
+
+ context 'when no runners' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when there are runners' do
+ let!(:runner) { create(:ci_runner, :project, projects: [build.project]) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with runners_cached_states feature flag enabled' do
+ before do
+ stub_feature_flags(runners_cached_states: true)
+ end
+
+ it 'caches the result in Redis' do
+ expect(Rails.cache).to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute)
+
+ build.any_runners_available?
+ end
+ end
+
+ context 'with runners_cached_states feature flag disabled' do
+ before do
+ stub_feature_flags(runners_cached_states: false)
+ end
+
+ it 'does not cache' do
+ expect(Rails.cache).not_to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute)
+
+ build.any_runners_available?
+ end
+ end
end
describe '#artifacts?' do
@@ -821,45 +883,6 @@ RSpec.describe Ci::Build do
{ cache: [{ key: "key", paths: ["public"], policy: "pull-push" }] }
end
- context 'with multiple_cache_per_job FF disabled' do
- before do
- stub_feature_flags(multiple_cache_per_job: false)
- end
- let(:options) { { cache: { key: "key", paths: ["public"], policy: "pull-push" } } }
-
- subject { build.cache }
-
- context 'when build has cache' do
- before do
- allow(build).to receive(:options).and_return(options)
- end
-
- context 'when project has jobs_cache_index' do
- before do
- allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
- end
-
- it { is_expected.to be_an(Array).and all(include(key: "key-1")) }
- end
-
- context 'when project does not have jobs_cache_index' do
- before do
- allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
- end
-
- it { is_expected.to eq([options[:cache]]) }
- end
- end
-
- context 'when build does not have cache' do
- before do
- allow(build).to receive(:options).and_return({})
- end
-
- it { is_expected.to eq([]) }
- end
- end
-
subject { build.cache }
context 'when build has cache' do
@@ -1174,6 +1197,8 @@ RSpec.describe Ci::Build do
end
describe 'state transition as a deployable' do
+ subject { build.send(event) }
+
let!(:build) { create(:ci_build, :with_deployment, :start_review_app, project: project, pipeline: pipeline) }
let(:deployment) { build.deployment }
let(:environment) { deployment.environment }
@@ -1188,54 +1213,78 @@ RSpec.describe Ci::Build do
expect(environment.name).to eq('review/master')
end
- context 'when transits to running' do
- before do
- build.run!
+ shared_examples_for 'avoid deadlock' do
+ it 'executes UPDATE in the right order' do
+ recorded = ActiveRecord::QueryRecorder.new { subject }
+
+ index_for_build = recorded.log.index { |l| l.include?("UPDATE \"ci_builds\"") }
+ index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") }
+
+ expect(index_for_build).to be < index_for_deployment
end
+ end
+
+ context 'when transits to running' do
+ let(:event) { :run! }
+
+ it_behaves_like 'avoid deadlock'
it 'transits deployment status to running' do
+ subject
+
expect(deployment).to be_running
end
end
context 'when transits to success' do
+ let(:event) { :success! }
+
before do
allow(Deployments::UpdateEnvironmentWorker).to receive(:perform_async)
allow(Deployments::ExecuteHooksWorker).to receive(:perform_async)
- build.success!
end
+ it_behaves_like 'avoid deadlock'
+
it 'transits deployment status to success' do
+ subject
+
expect(deployment).to be_success
end
end
context 'when transits to failed' do
- before do
- build.drop!
- end
+ let(:event) { :drop! }
+
+ it_behaves_like 'avoid deadlock'
it 'transits deployment status to failed' do
+ subject
+
expect(deployment).to be_failed
end
end
context 'when transits to skipped' do
- before do
- build.skip!
- end
+ let(:event) { :skip! }
+
+ it_behaves_like 'avoid deadlock'
it 'transits deployment status to skipped' do
+ subject
+
expect(deployment).to be_skipped
end
end
context 'when transits to canceled' do
- before do
- build.cancel!
- end
+ let(:event) { :cancel! }
+
+ it_behaves_like 'avoid deadlock'
it 'transits deployment status to canceled' do
+ subject
+
expect(deployment).to be_canceled
end
end
@@ -2500,6 +2549,7 @@ RSpec.describe Ci::Build do
{ key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false },
{ key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false },
{ key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false },
+ { key: 'CI_COMMIT_AUTHOR', value: pipeline.git_author_full_text, public: true, masked: false },
{ key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false },
{ key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false },
{ key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false },
@@ -3620,10 +3670,10 @@ RSpec.describe Ci::Build do
end
describe 'state transition when build fails' do
- let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user) }
+ let(:service) { ::MergeRequests::AddTodoWhenBuildFailsService.new(project, user) }
before do
- allow(MergeRequests::AddTodoWhenBuildFailsService).to receive(:new).and_return(service)
+ allow(::MergeRequests::AddTodoWhenBuildFailsService).to receive(:new).and_return(service)
allow(service).to receive(:close)
end
@@ -3708,7 +3758,7 @@ RSpec.describe Ci::Build do
subject.drop!
end
- it 'creates a todo' do
+ it 'creates a todo async', :sidekiq_inline do
project.add_developer(user)
expect_next_instance_of(TodoService) do |todo_service|
@@ -3741,6 +3791,7 @@ RSpec.describe Ci::Build do
describe '.matches_tag_ids' do
let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) }
+
let(:tag_ids) { ::ActsAsTaggableOn::Tag.named_any(tag_list).ids }
subject { described_class.where(id: build).matches_tag_ids(tag_ids) }
@@ -4192,6 +4243,7 @@ RSpec.describe Ci::Build do
describe '#artifacts_metadata_entry' do
let_it_be(:build) { create(:ci_build, project: project) }
+
let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' }
around do |example|
diff --git a/spec/models/ci/build_trace_chunk_spec.rb b/spec/models/ci/build_trace_chunk_spec.rb
index 3d728b9335e..12bc5d9aa3c 100644
--- a/spec/models/ci/build_trace_chunk_spec.rb
+++ b/spec/models/ci/build_trace_chunk_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Ci::BuildTraceChunk, :clean_gitlab_redis_shared_state do
include ExclusiveLeaseHelpers
let_it_be(:build) { create(:ci_build, :running) }
+
let(:chunk_index) { 0 }
let(:data_store) { :redis }
let(:raw_data) { nil }
diff --git a/spec/models/ci/daily_build_group_report_result_spec.rb b/spec/models/ci/daily_build_group_report_result_spec.rb
index 4e96ec7cecb..acc87c61036 100644
--- a/spec/models/ci/daily_build_group_report_result_spec.rb
+++ b/spec/models/ci/daily_build_group_report_result_spec.rb
@@ -86,6 +86,7 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
describe 'scopes' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
+
let(:recent_build_group_report_result) { create(:ci_daily_build_group_report_result, project: project, group: group) }
let(:old_build_group_report_result) do
create(:ci_daily_build_group_report_result, date: 1.week.ago, project: project)
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 796947be4c8..cdb123573f1 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -195,6 +195,22 @@ RSpec.describe Ci::JobArtifact do
end
end
+ describe '#archived_trace_exists?' do
+ subject { artifact.archived_trace_exists? }
+
+ context 'when the file exists' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when the file does not exist' do
+ before do
+ artifact.file.remove!
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '.for_sha' do
let(:first_pipeline) { create(:ci_pipeline) }
let(:second_pipeline) { create(:ci_pipeline, project: first_pipeline.project, sha: Digest::SHA1.hexdigest(SecureRandom.hex)) }
diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb
index cec3b544e50..3e5fbbfe823 100644
--- a/spec/models/ci/pipeline_schedule_spec.rb
+++ b/spec/models/ci/pipeline_schedule_spec.rb
@@ -90,6 +90,18 @@ RSpec.describe Ci::PipelineSchedule do
end
end
+ describe '.owned_by' do
+ let(:user) { create(:user) }
+ let!(:owned_pipeline_schedule) { create(:ci_pipeline_schedule, owner: user) }
+ let!(:other_pipeline_schedule) { create(:ci_pipeline_schedule) }
+
+ subject { described_class.owned_by(user) }
+
+ it 'returns owned pipeline schedules' do
+ is_expected.to eq([owned_pipeline_schedule])
+ end
+ end
+
describe '#set_next_run_at' do
let(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly) }
let(:ideal_next_run_at) { pipeline_schedule.send(:ideal_next_run_from, Time.zone.now) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index d57a39d133f..b7f5811e945 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -40,6 +40,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
it { is_expected.to respond_to :git_author_name }
it { is_expected.to respond_to :git_author_email }
+ it { is_expected.to respond_to :git_author_full_text }
it { is_expected.to respond_to :short_sha }
it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
@@ -426,6 +427,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
subject { pipeline.legacy_detached_merge_request_pipeline? }
let_it_be(:merge_request) { create(:merge_request) }
+
let(:ref) { 'feature' }
let(:target_sha) { nil }
@@ -819,6 +821,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
CI_COMMIT_DESCRIPTION
CI_COMMIT_REF_PROTECTED
CI_COMMIT_TIMESTAMP
+ CI_COMMIT_AUTHOR
CI_BUILD_REF
CI_BUILD_BEFORE_SHA
CI_BUILD_REF_NAME
@@ -830,6 +833,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
let_it_be(:assignees) { create_list(:user, 2) }
let_it_be(:milestone) { create(:milestone, project: project) }
let_it_be(:labels) { create_list(:label, 2) }
+
let(:merge_request) do
create(:merge_request, :simple,
source_project: project,
@@ -1274,6 +1278,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe 'state machine' do
let_it_be_with_reload(:pipeline) { create(:ci_empty_pipeline, :created) }
+
let(:current) { Time.current.change(usec: 0) }
let(:build) { create_build('build1', queued_at: 0) }
let(:build_b) { create_build('build2', queued_at: 0) }
@@ -2277,6 +2282,35 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
)
end
end
+
+ context 'when method is scoped' do
+ let!(:commit_123_ref_master_parent_pipeline) do
+ create(
+ :ci_pipeline,
+ sha: '123',
+ ref: 'master',
+ project: project
+ )
+ end
+
+ let!(:commit_123_ref_master_child_pipeline) do
+ create(
+ :ci_pipeline,
+ sha: '123',
+ ref: 'master',
+ project: project,
+ child_of: commit_123_ref_master_parent_pipeline
+ )
+ end
+
+ it 'returns the latest pipeline after applying the scope' do
+ result = described_class.ci_sources.latest_pipeline_per_commit(%w[123], 'master')
+
+ expect(result).to match(
+ '123' => commit_123_ref_master_parent_pipeline
+ )
+ end
+ end
end
describe '.latest_successful_ids_per_project' do
@@ -2325,6 +2359,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
subject { pipeline.reload.status }
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) }
+
let(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') }
context 'on waiting for resource' do
@@ -2633,6 +2668,37 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
expect(latest_status).to eq %w(canceled canceled)
end
end
+
+ context 'preloading relations' do
+ let(:pipeline1) { create(:ci_empty_pipeline, :created) }
+ let(:pipeline2) { create(:ci_empty_pipeline, :created) }
+
+ before do
+ create(:ci_build, :pending, pipeline: pipeline1)
+ create(:generic_commit_status, :pending, pipeline: pipeline1)
+
+ create(:ci_build, :pending, pipeline: pipeline2)
+ create(:ci_build, :pending, pipeline: pipeline2)
+ create(:generic_commit_status, :pending, pipeline: pipeline2)
+ create(:generic_commit_status, :pending, pipeline: pipeline2)
+ create(:generic_commit_status, :pending, pipeline: pipeline2)
+ end
+
+ it 'preloads relations for each build to avoid N+1 queries' do
+ control1 = ActiveRecord::QueryRecorder.new do
+ pipeline1.cancel_running
+ end
+
+ control2 = ActiveRecord::QueryRecorder.new do
+ pipeline2.cancel_running
+ end
+
+ extra_update_queries = 3 # transition ... => :canceled
+ extra_generic_commit_status_validation_queries = 2 # name_uniqueness_across_types
+
+ expect(control2.count).to eq(control1.count + extra_update_queries + extra_generic_commit_status_validation_queries)
+ end
+ end
end
describe '#retry_failed' do
@@ -2688,6 +2754,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#execute_hooks' do
let_it_be(:pipeline) { create(:ci_empty_pipeline, :created) }
+
let!(:build_a) { create_build('a', 0) }
let!(:build_b) { create_build('b', 0) }
@@ -3353,6 +3420,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#build_with_artifacts_in_self_and_descendants' do
let_it_be(:pipeline) { create(:ci_pipeline) }
+
let!(:build) { create(:ci_build, name: 'test', pipeline: pipeline) }
let(:child_pipeline) { create(:ci_pipeline, child_of: pipeline) }
let!(:child_build) { create(:ci_build, :artifacts, name: 'test', pipeline: child_pipeline) }
@@ -3780,6 +3848,26 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
end
end
+ describe '#uses_needs?' do
+ let_it_be(:pipeline) { create(:ci_pipeline) }
+
+ context 'when the scheduling type is `dag`' do
+ it 'returns true' do
+ create(:ci_build, pipeline: pipeline, scheduling_type: :dag)
+
+ expect(pipeline.uses_needs?).to eq(true)
+ end
+ end
+
+ context 'when the scheduling type is nil or stage' do
+ it 'returns false' do
+ create(:ci_build, pipeline: pipeline, scheduling_type: :stage)
+
+ expect(pipeline.uses_needs?).to eq(false)
+ end
+ end
+ end
+
describe '#total_size' do
let(:pipeline) { create(:ci_pipeline) }
let!(:build_job1) { create(:ci_build, pipeline: pipeline, stage_idx: 0) }
@@ -3814,6 +3902,16 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
pipeline.drop
end
end
+
+ context 'with failure_reason' do
+ let(:pipeline) { create(:ci_pipeline, :running) }
+ let(:failure_reason) { 'config_error' }
+ let(:counter) { Gitlab::Metrics.counter(:gitlab_ci_pipeline_failure_reasons, 'desc') }
+
+ it 'increments the counter with the failure_reason' do
+ expect { pipeline.drop!(failure_reason) }.to change { counter.get(reason: failure_reason) }.by(1)
+ end
+ end
end
end
@@ -3843,6 +3941,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
describe '#find_stage_by_name' do
let_it_be(:pipeline) { create(:ci_pipeline) }
+
let(:stage_name) { 'test' }
let(:stage) do
@@ -4128,6 +4227,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
subject { pipeline.base_and_ancestors(same_project: same_project) }
let_it_be(:pipeline) { create(:ci_pipeline, :created) }
+
let(:same_project) { false }
context 'when pipeline is not child nor parent' do
@@ -4164,6 +4264,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
context 'when pipeline is a child of a child pipeline' do
let_it_be(:pipeline) { create(:ci_pipeline, :created) }
+
let(:ancestor) { create(:ci_pipeline) }
let(:parent) { create(:ci_pipeline) }
@@ -4179,6 +4280,7 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
context 'when pipeline is a triggered pipeline' do
let_it_be(:pipeline) { create(:ci_pipeline, :created) }
+
let(:upstream) { create(:ci_pipeline, project: create(:project)) }
before do
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index ff3551d2a18..ffe0b0d0b19 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -353,6 +353,7 @@ RSpec.describe Ci::Runner do
using RSpec::Parameterized::TableSyntax
let_it_be(:pipeline) { create(:ci_pipeline) }
+
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:runner_project) { build.project }
let(:runner) { create(:ci_runner, :project, projects: [runner_project], tag_list: tag_list, run_untagged: run_untagged) }
diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb
index 0afc491dc73..e46d9189c86 100644
--- a/spec/models/ci/stage_spec.rb
+++ b/spec/models/ci/stage_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Ci::Stage, :models do
let_it_be(:pipeline) { create(:ci_empty_pipeline) }
+
let(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: pipeline.project) }
it_behaves_like 'having unique enum values'
@@ -27,6 +28,18 @@ RSpec.describe Ci::Stage, :models do
end
end
+ describe '.by_name' do
+ it 'finds stages by name' do
+ a = create(:ci_stage_entity, name: 'a')
+ b = create(:ci_stage_entity, name: 'b')
+ c = create(:ci_stage_entity, name: 'c')
+
+ expect(described_class.by_name('a')).to contain_exactly(a)
+ expect(described_class.by_name('b')).to contain_exactly(b)
+ expect(described_class.by_name(%w[a c])).to contain_exactly(a, c)
+ end
+ end
+
describe '#status' do
context 'when stage is pending' do
let(:stage) { create(:ci_stage_entity, status: 'pending') }
diff --git a/spec/models/ci/test_case_failure_spec.rb b/spec/models/ci/test_case_failure_spec.rb
deleted file mode 100644
index 34f89b663ed..00000000000
--- a/spec/models/ci/test_case_failure_spec.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::TestCaseFailure do
- describe 'relationships' do
- it { is_expected.to belong_to(:build) }
- it { is_expected.to belong_to(:test_case) }
- end
-
- describe 'validations' do
- subject { build(:ci_test_case_failure) }
-
- it { is_expected.to validate_presence_of(:test_case) }
- it { is_expected.to validate_presence_of(:build) }
- it { is_expected.to validate_presence_of(:failed_at) }
- end
-
- describe '.recent_failures_count' do
- let_it_be(:project) { create(:project) }
-
- subject(:recent_failures) do
- described_class.recent_failures_count(
- project: project,
- test_case_keys: test_case_keys
- )
- end
-
- context 'when test case failures are within the date range and are for the test case keys' do
- let(:tc_1) { create(:ci_test_case, project: project) }
- let(:tc_2) { create(:ci_test_case, project: project) }
- let(:test_case_keys) { [tc_1.key_hash, tc_2.key_hash] }
-
- before do
- create_list(:ci_test_case_failure, 3, test_case: tc_1, failed_at: 1.day.ago)
- create_list(:ci_test_case_failure, 2, test_case: tc_2, failed_at: 3.days.ago)
- end
-
- it 'returns the number of failures for each test case key hash for the past 14 days by default' do
- expect(recent_failures).to eq(
- tc_1.key_hash => 3,
- tc_2.key_hash => 2
- )
- end
- end
-
- context 'when test case failures are within the date range but are not for the test case keys' do
- let(:tc) { create(:ci_test_case, project: project) }
- let(:test_case_keys) { ['some-other-key-hash'] }
-
- before do
- create(:ci_test_case_failure, test_case: tc, failed_at: 1.day.ago)
- end
-
- it 'excludes them from the count' do
- expect(recent_failures[tc.key_hash]).to be_nil
- end
- end
-
- context 'when test case failures are not within the date range but are for the test case keys' do
- let(:tc) { create(:ci_test_case, project: project) }
- let(:test_case_keys) { [tc.key_hash] }
-
- before do
- create(:ci_test_case_failure, test_case: tc, failed_at: 15.days.ago)
- end
-
- it 'excludes them from the count' do
- expect(recent_failures[tc.key_hash]).to be_nil
- end
- end
- end
-end
diff --git a/spec/models/ci/test_case_spec.rb b/spec/models/ci/test_case_spec.rb
deleted file mode 100644
index 45311e285a6..00000000000
--- a/spec/models/ci/test_case_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Ci::TestCase do
- describe 'relationships' do
- it { is_expected.to belong_to(:project) }
- it { is_expected.to have_many(:test_case_failures) }
- end
-
- describe 'validations' do
- subject { build(:ci_test_case) }
-
- it { is_expected.to validate_presence_of(:project) }
- it { is_expected.to validate_presence_of(:key_hash) }
- end
-
- describe '.find_or_create_by_batch' do
- it 'finds or creates records for the given test case keys', :aggregate_failures do
- project = create(:project)
- existing_tc = create(:ci_test_case, project: project)
- new_key = Digest::SHA256.hexdigest(SecureRandom.hex)
- keys = [existing_tc.key_hash, new_key]
-
- result = described_class.find_or_create_by_batch(project, keys)
-
- expect(result.map(&:key_hash)).to match_array([existing_tc.key_hash, new_key])
- expect(result).to all(be_persisted)
- end
- end
-end
diff --git a/spec/models/ci/unit_test_failure_spec.rb b/spec/models/ci/unit_test_failure_spec.rb
new file mode 100644
index 00000000000..f9b8c66b603
--- /dev/null
+++ b/spec/models/ci/unit_test_failure_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::UnitTestFailure do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:build) }
+ it { is_expected.to belong_to(:unit_test) }
+ end
+
+ describe 'validations' do
+ subject { build(:ci_unit_test_failure) }
+
+ it { is_expected.to validate_presence_of(:unit_test) }
+ it { is_expected.to validate_presence_of(:build) }
+ it { is_expected.to validate_presence_of(:failed_at) }
+ end
+
+ describe '.recent_failures_count' do
+ let_it_be(:project) { create(:project) }
+
+ subject(:recent_failures) do
+ described_class.recent_failures_count(
+ project: project,
+ unit_test_keys: unit_test_keys
+ )
+ end
+
+ context 'when unit test failures are within the date range and are for the unit test keys' do
+ let(:test_1) { create(:ci_unit_test, project: project) }
+ let(:test_2) { create(:ci_unit_test, project: project) }
+ let(:unit_test_keys) { [test_1.key_hash, test_2.key_hash] }
+
+ before do
+ create_list(:ci_unit_test_failure, 3, unit_test: test_1, failed_at: 1.day.ago)
+ create_list(:ci_unit_test_failure, 2, unit_test: test_2, failed_at: 3.days.ago)
+ end
+
+ it 'returns the number of failures for each unit test key hash for the past 14 days by default' do
+ expect(recent_failures).to eq(
+ test_1.key_hash => 3,
+ test_2.key_hash => 2
+ )
+ end
+ end
+
+ context 'when unit test failures are within the date range but are not for the unit test keys' do
+ let(:test) { create(:ci_unit_test, project: project) }
+ let(:unit_test_keys) { ['some-other-key-hash'] }
+
+ before do
+ create(:ci_unit_test_failure, unit_test: test, failed_at: 1.day.ago)
+ end
+
+ it 'excludes them from the count' do
+ expect(recent_failures[test.key_hash]).to be_nil
+ end
+ end
+
+ context 'when unit test failures are not within the date range but are for the unit test keys' do
+ let(:test) { create(:ci_unit_test, project: project) }
+ let(:unit_test_keys) { [test.key_hash] }
+
+ before do
+ create(:ci_unit_test_failure, unit_test: test, failed_at: 15.days.ago)
+ end
+
+ it 'excludes them from the count' do
+ expect(recent_failures[test.key_hash]).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/unit_test_spec.rb b/spec/models/ci/unit_test_spec.rb
new file mode 100644
index 00000000000..2207a362be3
--- /dev/null
+++ b/spec/models/ci/unit_test_spec.rb
@@ -0,0 +1,87 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::UnitTest do
+ describe 'relationships' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_many(:unit_test_failures) }
+ end
+
+ describe 'validations' do
+ subject { build(:ci_unit_test) }
+
+ it { is_expected.to validate_presence_of(:project) }
+ it { is_expected.to validate_presence_of(:key_hash) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_presence_of(:suite_name) }
+ end
+
+ describe '.find_or_create_by_batch' do
+ let(:project) { create(:project) }
+
+ it 'finds or creates records for the given unit test keys', :aggregate_failures do
+ existing_test = create(:ci_unit_test, project: project, suite_name: 'rspec', name: 'Math#sum adds numbers')
+ new_key = Digest::SHA256.hexdigest(SecureRandom.hex)
+ attrs = [
+ {
+ key_hash: existing_test.key_hash,
+ name: 'This new name will not apply',
+ suite_name: 'This new suite name will not apply'
+ },
+ {
+ key_hash: new_key,
+ name: 'Component works',
+ suite_name: 'jest'
+ }
+ ]
+
+ result = described_class.find_or_create_by_batch(project, attrs)
+
+ expect(result).to match_array([
+ have_attributes(
+ key_hash: existing_test.key_hash,
+ suite_name: 'rspec',
+ name: 'Math#sum adds numbers'
+ ),
+ have_attributes(
+ key_hash: new_key,
+ suite_name: 'jest',
+ name: 'Component works'
+ )
+ ])
+
+ expect(result).to all(be_persisted)
+ end
+
+ context 'when a given name or suite_name exceeds the string size limit' do
+ before do
+ stub_const("#{described_class}::MAX_NAME_SIZE", 6)
+ stub_const("#{described_class}::MAX_SUITE_NAME_SIZE", 6)
+ end
+
+ it 'truncates the values before storing the information' do
+ new_key = Digest::SHA256.hexdigest(SecureRandom.hex)
+ attrs = [
+ {
+ key_hash: new_key,
+ name: 'abcdefg',
+ suite_name: 'abcdefg'
+ }
+ ]
+
+ result = described_class.find_or_create_by_batch(project, attrs)
+
+ expect(result).to match_array([
+ have_attributes(
+ key_hash: new_key,
+ suite_name: 'abc...',
+ name: 'abc...'
+ )
+ ])
+
+ expect(result).to all(be_persisted)
+ end
+ end
+ end
+end