Commit 8a21dab1 authored by Erick Bajao's avatar Erick Bajao

Add replacement tables for unit test history

Adds ci_unit_tests and ci_unit_test_failures as replacement
for ci_test_cases and ci_test_case_failures.
parent 8379fd26
# frozen_string_literal: true
module Ci
class TestCase < ApplicationRecord
class UnitTest < ApplicationRecord
extend Gitlab::Ci::Model
validates :project, :key_hash, presence: true
MAX_NAME_SIZE = 255
MAX_SUITE_NAME_SIZE = 255
has_many :test_case_failures, class_name: 'Ci::TestCaseFailure'
validates :project, :key_hash, :name, :suite_name, presence: true
has_many :unit_test_failures, class_name: 'Ci::UnitTestFailure'
belongs_to :project
scope :by_project_and_keys, -> (project, keys) { where(project_id: project.id, key_hash: keys) }
class << self
def find_or_create_by_batch(project, test_case_keys)
def find_or_create_by_batch(project, unit_test_attrs)
# Insert records first. Existing ones will be skipped.
insert_all(test_case_attrs(project, test_case_keys))
insert_all(build_insert_attrs(project, unit_test_attrs))
# Find all matching records now that we are sure they all are persisted.
by_project_and_keys(project, test_case_keys)
by_project_and_keys(project, gather_keys(unit_test_attrs))
end
private
def test_case_attrs(project, test_case_keys)
def build_insert_attrs(project, unit_test_attrs)
# NOTE: Rails 6.1 will add support for insert_all on relation so that
# we will be able to do project.test_cases.insert_all.
test_case_keys.map do |hashed_key|
{ project_id: project.id, key_hash: hashed_key }
unit_test_attrs.map do |attrs|
attrs.merge(
project_id: project.id,
name: attrs[:name].truncate(MAX_NAME_SIZE),
suite_name: attrs[:suite_name].truncate(MAX_SUITE_NAME_SIZE)
)
end
end
def gather_keys(unit_test_attrs)
unit_test_attrs.map { |attrs| attrs[:key_hash] }
end
end
end
end
# frozen_string_literal: true
module Ci
class TestCaseFailure < ApplicationRecord
class UnitTestFailure < ApplicationRecord
extend Gitlab::Ci::Model
REPORT_WINDOW = 14.days
validates :test_case, :build, :failed_at, presence: true
validates :unit_test, :build, :failed_at, presence: true
belongs_to :test_case, class_name: "Ci::TestCase", foreign_key: :test_case_id
belongs_to :unit_test, class_name: "Ci::UnitTest", foreign_key: :unit_test_id
belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id
def self.recent_failures_count(project:, test_case_keys:, date_range: REPORT_WINDOW.ago..Time.current)
joins(:test_case)
def self.recent_failures_count(project:, unit_test_keys:, date_range: REPORT_WINDOW.ago..Time.current)
joins(:unit_test)
.where(
ci_test_cases: {
ci_unit_tests: {
project_id: project.id,
key_hash: test_case_keys
key_hash: unit_test_keys
},
ci_test_case_failures: {
ci_unit_test_failures: {
failed_at: date_range
}
)
.group(:key_hash)
.count('ci_test_case_failures.id')
.count('ci_unit_test_failures.id')
end
end
end
......@@ -34,7 +34,7 @@ module Ci
# We fetch for up to MAX_TRACKABLE_FAILURES + 1 builds. So if ever we get
# 201 total number of builds with the assumption that each job has at least
# 1 failed test case, then we have at least 201 failed test cases which exceeds
# 1 failed unit test, then we have at least 201 failed unit tests which exceeds
# the MAX_TRACKABLE_FAILURES of 200. If this is the case, let's early exit so we
# don't have to parse each JUnit report of each of the 201 builds.
failed_builds.length <= MAX_TRACKABLE_FAILURES
......@@ -51,25 +51,29 @@ module Ci
end
def track_failures
failed_test_cases = gather_failed_test_cases(failed_builds)
failed_unit_tests = gather_failed_unit_tests_from_reports(failed_builds)
return if failed_test_cases.size > MAX_TRACKABLE_FAILURES
return if failed_unit_tests.size > MAX_TRACKABLE_FAILURES
failed_test_cases.keys.each_slice(100) do |key_hashes|
Ci::TestCase.transaction do
ci_test_cases = Ci::TestCase.find_or_create_by_batch(project, key_hashes)
failures = test_case_failures(ci_test_cases, failed_test_cases)
failed_unit_tests.each_slice(100) do |batch|
Ci::UnitTest.transaction do
unit_test_attrs = ci_unit_test_attrs(batch)
ci_unit_tests = Ci::UnitTest.find_or_create_by_batch(project, unit_test_attrs)
Ci::TestCaseFailure.insert_all(failures)
failures = ci_unit_test_failure_attrs(ci_unit_tests, failed_unit_tests)
Ci::UnitTestFailure.insert_all(failures)
end
end
end
def gather_failed_test_cases(failed_builds)
failed_builds.each_with_object({}) do |build, failed_test_cases|
def gather_failed_unit_tests_from_reports(failed_builds)
failed_builds.each_with_object({}) do |build, failed_unit_tests|
test_suite = generate_test_suite!(build)
test_suite.failed.keys.each do |key|
failed_test_cases[key] = build
test_suite.failed.each do |key, unit_test|
failed_unit_tests[key] = {
build: build, # This will be used in ci_unit_test_failure_attrs
unit_test: unit_test # This will be used in ci_unit_test_attrs
}
end
end
end
......@@ -79,12 +83,24 @@ module Ci
build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
end
def test_case_failures(ci_test_cases, failed_test_cases)
ci_test_cases.map do |test_case|
build = failed_test_cases[test_case.key_hash]
def ci_unit_test_attrs(batch)
batch.map do |item|
unit_test = item.last[:unit_test]
{
test_case_id: test_case.id,
key_hash: unit_test.key,
name: unit_test.name,
suite_name: unit_test.suite_name
}
end
end
def ci_unit_test_failure_attrs(ci_unit_tests, failed_unit_tests)
ci_unit_tests.map do |ci_unit_test|
build = failed_unit_tests[ci_unit_test.key_hash][:build]
{
unit_test_id: ci_unit_test.id,
build_id: build.id,
failed_at: build.finished_at
}
......
---
title: Create new unit test tables
merge_request: 56137
author:
type: other
# frozen_string_literal: true
class CreateCiUnitTests < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
unless table_exists?(:ci_unit_tests)
create_table :ci_unit_tests do |t|
t.bigint :project_id, null: false
t.text :key_hash, null: false
t.text :name, null: false
t.text :suite_name, null: false
t.index [:project_id, :key_hash], unique: true
# NOTE: FK for projects will be added on a separate migration as per guidelines
end
end
add_text_limit :ci_unit_tests, :key_hash, 64
add_text_limit :ci_unit_tests, :name, 255
add_text_limit :ci_unit_tests, :suite_name, 255
end
def down
drop_table :ci_unit_tests
end
end
# frozen_string_literal: true
class AddProjectsFkToCiUnitTests < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :ci_unit_tests, :projects, column: :project_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :ci_unit_tests, column: :project_id
end
end
end
# frozen_string_literal: true
class CreateCiUnitTestFailures < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
create_table :ci_unit_test_failures do |t|
t.datetime_with_timezone :failed_at, null: false
t.bigint :unit_test_id, null: false
t.bigint :build_id, null: false
t.index [:unit_test_id, :failed_at, :build_id], name: 'index_unit_test_failures_unique_columns', unique: true, order: { failed_at: :desc }
t.index :build_id
# NOTE: Adding the index for failed_at now for later use when we do scheduled clean up
t.index :failed_at, order: { failed_at: :desc }, name: 'index_unit_test_failures_failed_at'
t.foreign_key :ci_unit_tests, column: :unit_test_id, on_delete: :cascade
# NOTE: FK for ci_builds will be added on a separate migration as per guidelines
end
end
def down
drop_table :ci_unit_test_failures
end
end
# frozen_string_literal: true
class AddCiBuildsFkToCiUnitTestFailures < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_concurrent_foreign_key :ci_unit_test_failures, :ci_builds, column: :build_id, on_delete: :cascade
end
def down
with_lock_retries do
remove_foreign_key :ci_unit_test_failures, column: :build_id
end
end
end
cf63d7ffd6bfb93c25c894b26424e9890b43652b4f0bfc259917a4857ff414e2
\ No newline at end of file
4c1ae24594ccb85706a4c9836ed1fc8ce47d68863262e90b9109ddc1d83d121b
\ No newline at end of file
8f9957b7f7744e3d72bba1b2bf9bd2c9a06203091bf8f9dcafc69755db25fef0
\ No newline at end of file
43af4a4200ba87ebb50627d341bb324896cbe0c36896d50dd81a8a9cfb2eb426
\ No newline at end of file
......@@ -11128,6 +11128,42 @@ CREATE SEQUENCE ci_triggers_id_seq
ALTER SEQUENCE ci_triggers_id_seq OWNED BY ci_triggers.id;
CREATE TABLE ci_unit_test_failures (
id bigint NOT NULL,
failed_at timestamp with time zone NOT NULL,
unit_test_id bigint NOT NULL,
build_id bigint NOT NULL
);
CREATE SEQUENCE ci_unit_test_failures_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_unit_test_failures_id_seq OWNED BY ci_unit_test_failures.id;
CREATE TABLE ci_unit_tests (
id bigint NOT NULL,
project_id bigint NOT NULL,
key_hash text NOT NULL,
name text NOT NULL,
suite_name text NOT NULL,
CONSTRAINT check_248fae1a3b CHECK ((char_length(name) <= 255)),
CONSTRAINT check_b288215ffe CHECK ((char_length(key_hash) <= 64)),
CONSTRAINT check_c2d57b3c49 CHECK ((char_length(suite_name) <= 255))
);
CREATE SEQUENCE ci_unit_tests_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE ci_unit_tests_id_seq OWNED BY ci_unit_tests.id;
CREATE TABLE ci_variables (
id integer NOT NULL,
key character varying NOT NULL,
......@@ -19081,6 +19117,10 @@ ALTER TABLE ONLY ci_trigger_requests ALTER COLUMN id SET DEFAULT nextval('ci_tri
ALTER TABLE ONLY ci_triggers ALTER COLUMN id SET DEFAULT nextval('ci_triggers_id_seq'::regclass);
ALTER TABLE ONLY ci_unit_test_failures ALTER COLUMN id SET DEFAULT nextval('ci_unit_test_failures_id_seq'::regclass);
ALTER TABLE ONLY ci_unit_tests ALTER COLUMN id SET DEFAULT nextval('ci_unit_tests_id_seq'::regclass);
ALTER TABLE ONLY ci_variables ALTER COLUMN id SET DEFAULT nextval('ci_variables_id_seq'::regclass);
ALTER TABLE ONLY cluster_agent_tokens ALTER COLUMN id SET DEFAULT nextval('cluster_agent_tokens_id_seq'::regclass);
......@@ -20249,6 +20289,12 @@ ALTER TABLE ONLY ci_trigger_requests
ALTER TABLE ONLY ci_triggers
ADD CONSTRAINT ci_triggers_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_unit_test_failures
ADD CONSTRAINT ci_unit_test_failures_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_unit_tests
ADD CONSTRAINT ci_unit_tests_pkey PRIMARY KEY (id);
ALTER TABLE ONLY ci_variables
ADD CONSTRAINT ci_variables_pkey PRIMARY KEY (id);
......@@ -22123,6 +22169,10 @@ CREATE INDEX index_ci_triggers_on_owner_id ON ci_triggers USING btree (owner_id)
CREATE INDEX index_ci_triggers_on_project_id ON ci_triggers USING btree (project_id);
CREATE INDEX index_ci_unit_test_failures_on_build_id ON ci_unit_test_failures USING btree (build_id);
CREATE UNIQUE INDEX index_ci_unit_tests_on_project_id_and_key_hash ON ci_unit_tests USING btree (project_id, key_hash);
CREATE INDEX index_ci_variables_on_key ON ci_variables USING btree (key);
CREATE UNIQUE INDEX index_ci_variables_on_project_id_and_key_and_environment_scope ON ci_variables USING btree (project_id, key, environment_scope);
......@@ -23833,6 +23883,10 @@ CREATE INDEX index_u2f_registrations_on_key_handle ON u2f_registrations USING bt
CREATE INDEX index_u2f_registrations_on_user_id ON u2f_registrations USING btree (user_id);
CREATE INDEX index_unit_test_failures_failed_at ON ci_unit_test_failures USING btree (failed_at DESC);
CREATE UNIQUE INDEX index_unit_test_failures_unique_columns ON ci_unit_test_failures USING btree (unit_test_id, failed_at DESC, build_id);
CREATE INDEX index_uploads_on_checksum ON uploads USING btree (checksum);
CREATE INDEX index_uploads_on_model_id_and_model_type ON uploads USING btree (model_id, model_type);
......@@ -24466,6 +24520,9 @@ ALTER TABLE ONLY notification_settings
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_0d3f677137 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_unit_test_failures
ADD CONSTRAINT fk_0f09856e1f FOREIGN KEY (build_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
ALTER TABLE ONLY project_pages_metadata
ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL;
......@@ -24712,6 +24769,9 @@ ALTER TABLE ONLY geo_event_log
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_7a5553d60f FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_unit_tests
ADD CONSTRAINT fk_7a8fabf0a8 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY protected_branches
ADD CONSTRAINT fk_7a9c6d93e7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
......@@ -25372,6 +25432,9 @@ ALTER TABLE ONLY group_custom_attributes
ALTER TABLE ONLY incident_management_oncall_rotations
ADD CONSTRAINT fk_rails_256e0bc604 FOREIGN KEY (oncall_schedule_id) REFERENCES incident_management_oncall_schedules(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_unit_test_failures
ADD CONSTRAINT fk_rails_259da3e79c FOREIGN KEY (unit_test_id) REFERENCES ci_unit_tests(id) ON DELETE CASCADE;
ALTER TABLE ONLY analytics_devops_adoption_snapshots
ADD CONSTRAINT fk_rails_25da9a92c0 FOREIGN KEY (segment_id) REFERENCES analytics_devops_adoption_segments(id) ON DELETE CASCADE;
......@@ -6,32 +6,32 @@ module Gitlab
class TestFailureHistory
include Gitlab::Utils::StrongMemoize
def initialize(failed_test_cases, project)
@failed_test_cases = build_map(failed_test_cases)
def initialize(failed_junit_tests, project)
@failed_junit_tests = build_map(failed_junit_tests)
@project = project
end
def load!
recent_failures_count.each do |key_hash, count|
failed_test_cases[key_hash].set_recent_failures(count, project.default_branch_or_master)
failed_junit_tests[key_hash].set_recent_failures(count, project.default_branch_or_master)
end
end
private
attr_reader :report, :project, :failed_test_cases
attr_reader :report, :project, :failed_junit_tests
def recent_failures_count
::Ci::TestCaseFailure.recent_failures_count(
::Ci::UnitTestFailure.recent_failures_count(
project: project,
test_case_keys: failed_test_cases.keys
unit_test_keys: failed_junit_tests.keys
)
end
def build_map(test_cases)
def build_map(junit_tests)
{}.tap do |hash|
test_cases.each do |test_case|
hash[test_case.key] = test_case
junit_tests.each do |test|
hash[test.key] = test
end
end
end
......
# frozen_string_literal: true
FactoryBot.define do
factory :ci_test_case, class: 'Ci::TestCase' do
factory :ci_unit_test, class: 'Ci::UnitTest' do
project
suite_name { 'rspec' }
name { 'Math#add returns sum' }
key_hash { Digest::SHA256.hexdigest(SecureRandom.hex) }
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :ci_test_case_failure, class: 'Ci::TestCaseFailure' do
factory :ci_unit_test_failure, class: 'Ci::UnitTestFailure' do
build factory: :ci_build
test_case factory: :ci_test_case
unit_test factory: :ci_unit_test
failed_at { Time.current }
end
end
......@@ -13,9 +13,9 @@ RSpec.describe Gitlab::Ci::Reports::TestFailureHistory, :aggregate_failures do
subject(:load_history) { described_class.new([failed_rspec, failed_java], project).load! }
before do
allow(Ci::TestCaseFailure)
allow(Ci::UnitTestFailure)
.to receive(:recent_failures_count)
.with(project: project, test_case_keys: [failed_rspec.key, failed_java.key])
.with(project: project, unit_test_keys: [failed_rspec.key, failed_java.key])
.and_return(
failed_rspec.key => 2,
failed_java.key => 1
......
# 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
......@@ -2,16 +2,16 @@
require 'spec_helper'
RSpec.describe Ci::TestCaseFailure do
RSpec.describe Ci::UnitTestFailure do
describe 'relationships' do
it { is_expected.to belong_to(:build) }
it { is_expected.to belong_to(:test_case) }
it { is_expected.to belong_to(:unit_test) }
end
describe 'validations' do
subject { build(:ci_test_case_failure) }
subject { build(:ci_unit_test_failure) }
it { is_expected.to validate_presence_of(:test_case) }
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
......@@ -22,51 +22,51 @@ RSpec.describe Ci::TestCaseFailure do
subject(:recent_failures) do
described_class.recent_failures_count(
project: project,
test_case_keys: test_case_keys
unit_test_keys: unit_test_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] }
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_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)
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 test case key hash for the past 14 days by default' do
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(
tc_1.key_hash => 3,
tc_2.key_hash => 2
test_1.key_hash => 3,
test_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'] }
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_test_case_failure, test_case: tc, failed_at: 1.day.ago)
create(:ci_unit_test_failure, unit_test: test, failed_at: 1.day.ago)
end
it 'excludes them from the count' do
expect(recent_failures[tc.key_hash]).to be_nil
expect(recent_failures[test.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] }
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_test_case_failure, test_case: tc, failed_at: 15.days.ago)
create(:ci_unit_test_failure, unit_test: test, failed_at: 15.days.ago)
end
it 'excludes them from the count' do
expect(recent_failures[tc.key_hash]).to be_nil
expect(recent_failures[test.key_hash]).to be_nil
end
end
end
......
# 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
......@@ -11,15 +11,15 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
context 'when pipeline has failed builds with test reports' do
before do
# The test report has 2 test case failures
# The test report has 2 unit test failures
create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
end
it 'creates test case failures records' do
it 'creates unit test failures records' do
execute_service
expect(Ci::TestCase.count).to eq(2)
expect(Ci::TestCaseFailure.count).to eq(2)
expect(Ci::UnitTest.count).to eq(2)
expect(Ci::UnitTestFailure.count).to eq(2)
end
context 'when pipeline is not for the default branch' do
......@@ -30,8 +30,8 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do
execute_service
expect(Ci::TestCase.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0)
expect(Ci::UnitTest.count).to eq(0)
expect(Ci::UnitTestFailure.count).to eq(0)
end
end
......@@ -43,12 +43,12 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not fail but does not persist new data' do
expect { described_class.new(pipeline).execute }.not_to raise_error
expect(Ci::TestCase.count).to eq(2)
expect(Ci::TestCaseFailure.count).to eq(2)
expect(Ci::UnitTest.count).to eq(2)
expect(Ci::UnitTestFailure.count).to eq(2)
end
end
context 'when number of failed test cases exceed the limit' do
context 'when number of failed unit tests exceed the limit' do
before do
stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1)
end
......@@ -56,16 +56,16 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do
execute_service
expect(Ci::TestCase.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0)
expect(Ci::UnitTest.count).to eq(0)
expect(Ci::UnitTestFailure.count).to eq(0)
end
end
context 'when number of failed test cases across multiple builds exceed the limit' do
context 'when number of failed unit tests across multiple builds exceed the limit' do
before do
stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 2)
# This other test report has 1 unique test case failure which brings us to 3 total failures across all builds
# This other test report has 1 unique unit test failure which brings us to 3 total failures across all builds
# thus exceeding the limit of 2 for MAX_TRACKABLE_FAILURES
create(:ci_build, :failed, :test_reports_with_duplicate_failed_test_names, pipeline: pipeline, project: project)
end
......@@ -73,23 +73,23 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do
execute_service
expect(Ci::TestCase.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0)
expect(Ci::UnitTest.count).to eq(0)
expect(Ci::UnitTestFailure.count).to eq(0)
end
end
end
context 'when test failure data have duplicates within the same payload (happens when the JUnit report has duplicate test case names but have different failures)' do
context 'when test failure data have duplicates within the same payload (happens when the JUnit report has duplicate unit test names but have different failures)' do
before do
# The test report has 2 test case failures but with the same test case keys
# The test report has 2 unit test failures but with the same unit test keys
create(:ci_build, :failed, :test_reports_with_duplicate_failed_test_names, pipeline: pipeline, project: project)
end
it 'does not fail but does not persist duplicate data' do
expect { execute_service }.not_to raise_error
expect(Ci::TestCase.count).to eq(1)
expect(Ci::TestCaseFailure.count).to eq(1)
expect(Ci::UnitTest.count).to eq(1)
expect(Ci::UnitTestFailure.count).to eq(1)
end
end
......@@ -102,8 +102,8 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do
execute_service
expect(Ci::TestCase.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0)
expect(Ci::UnitTest.count).to eq(0)
expect(Ci::UnitTestFailure.count).to eq(0)
end
end
end
......
......@@ -40,8 +40,8 @@ RSpec.describe ::Ci::TestFailureHistoryWorker do
subject
expect(Ci::TestCase.count).to eq(2)
expect(Ci::TestCaseFailure.count).to eq(2)
expect(Ci::UnitTest.count).to eq(2)
expect(Ci::UnitTestFailure.count).to eq(2)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment