diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-21 02:50:22 +0300 |
commit | 9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch) | |
tree | 70467ae3692a0e35e5ea56bcb803eb512a10bedb /spec/models/ci | |
parent | 4b0f34b6d759d6299322b3a54453e930c6121ff0 (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.rb | 1 | ||||
-rw-r--r-- | spec/models/ci/bridge_spec.rb | 2 | ||||
-rw-r--r-- | spec/models/ci/build_spec.rb | 162 | ||||
-rw-r--r-- | spec/models/ci/build_trace_chunk_spec.rb | 1 | ||||
-rw-r--r-- | spec/models/ci/daily_build_group_report_result_spec.rb | 1 | ||||
-rw-r--r-- | spec/models/ci/job_artifact_spec.rb | 16 | ||||
-rw-r--r-- | spec/models/ci/pipeline_schedule_spec.rb | 12 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 102 | ||||
-rw-r--r-- | spec/models/ci/runner_spec.rb | 1 | ||||
-rw-r--r-- | spec/models/ci/stage_spec.rb | 13 | ||||
-rw-r--r-- | spec/models/ci/test_case_failure_spec.rb | 73 | ||||
-rw-r--r-- | spec/models/ci/test_case_spec.rb | 31 | ||||
-rw-r--r-- | spec/models/ci/unit_test_failure_spec.rb | 73 | ||||
-rw-r--r-- | spec/models/ci/unit_test_spec.rb | 87 |
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 |