Commit cfff4ab8 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch 'eb-rename-ci-test-tables' into 'master'

Create new unit test tables

See merge request gitlab-org/gitlab!56137
parents fc6442e2 8a21dab1
# frozen_string_literal: true # frozen_string_literal: true
module Ci module Ci
class TestCase < ApplicationRecord class UnitTest < ApplicationRecord
extend Gitlab::Ci::Model 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 belongs_to :project
scope :by_project_and_keys, -> (project, keys) { where(project_id: project.id, key_hash: keys) } scope :by_project_and_keys, -> (project, keys) { where(project_id: project.id, key_hash: keys) }
class << self 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 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. # 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 end
private 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 # 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. # we will be able to do project.test_cases.insert_all.
test_case_keys.map do |hashed_key| unit_test_attrs.map do |attrs|
{ project_id: project.id, key_hash: hashed_key } 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
end end
def gather_keys(unit_test_attrs)
unit_test_attrs.map { |attrs| attrs[:key_hash] }
end
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
module Ci module Ci
class TestCaseFailure < ApplicationRecord class UnitTestFailure < ApplicationRecord
extend Gitlab::Ci::Model extend Gitlab::Ci::Model
REPORT_WINDOW = 14.days 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 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) def self.recent_failures_count(project:, unit_test_keys:, date_range: REPORT_WINDOW.ago..Time.current)
joins(:test_case) joins(:unit_test)
.where( .where(
ci_test_cases: { ci_unit_tests: {
project_id: project.id, 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 failed_at: date_range
} }
) )
.group(:key_hash) .group(:key_hash)
.count('ci_test_case_failures.id') .count('ci_unit_test_failures.id')
end end
end end
end end
...@@ -34,7 +34,7 @@ module Ci ...@@ -34,7 +34,7 @@ module Ci
# We fetch for up to MAX_TRACKABLE_FAILURES + 1 builds. So if ever we get # 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 # 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 # 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. # don't have to parse each JUnit report of each of the 201 builds.
failed_builds.length <= MAX_TRACKABLE_FAILURES failed_builds.length <= MAX_TRACKABLE_FAILURES
...@@ -51,25 +51,29 @@ module Ci ...@@ -51,25 +51,29 @@ module Ci
end end
def track_failures 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| failed_unit_tests.each_slice(100) do |batch|
Ci::TestCase.transaction do Ci::UnitTest.transaction do
ci_test_cases = Ci::TestCase.find_or_create_by_batch(project, key_hashes) unit_test_attrs = ci_unit_test_attrs(batch)
failures = test_case_failures(ci_test_cases, failed_test_cases) 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 end
end end
def gather_failed_test_cases(failed_builds) def gather_failed_unit_tests_from_reports(failed_builds)
failed_builds.each_with_object({}) do |build, failed_test_cases| failed_builds.each_with_object({}) do |build, failed_unit_tests|
test_suite = generate_test_suite!(build) test_suite = generate_test_suite!(build)
test_suite.failed.keys.each do |key| test_suite.failed.each do |key, unit_test|
failed_test_cases[key] = build 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 end
end end
...@@ -79,12 +83,24 @@ module Ci ...@@ -79,12 +83,24 @@ module Ci
build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new) build.collect_test_reports!(Gitlab::Ci::Reports::TestReports.new)
end end
def test_case_failures(ci_test_cases, failed_test_cases) def ci_unit_test_attrs(batch)
ci_test_cases.map do |test_case| batch.map do |item|
build = failed_test_cases[test_case.key_hash] 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, build_id: build.id,
failed_at: build.finished_at 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
...@@ -11130,6 +11130,42 @@ CREATE SEQUENCE ci_triggers_id_seq ...@@ -11130,6 +11130,42 @@ CREATE SEQUENCE ci_triggers_id_seq
ALTER SEQUENCE ci_triggers_id_seq OWNED BY ci_triggers.id; 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 ( CREATE TABLE ci_variables (
id integer NOT NULL, id integer NOT NULL,
key character varying NOT NULL, key character varying NOT NULL,
...@@ -19121,6 +19157,10 @@ ALTER TABLE ONLY ci_trigger_requests ALTER COLUMN id SET DEFAULT nextval('ci_tri ...@@ -19121,6 +19157,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_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 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); ALTER TABLE ONLY cluster_agent_tokens ALTER COLUMN id SET DEFAULT nextval('cluster_agent_tokens_id_seq'::regclass);
...@@ -20291,6 +20331,12 @@ ALTER TABLE ONLY ci_trigger_requests ...@@ -20291,6 +20331,12 @@ ALTER TABLE ONLY ci_trigger_requests
ALTER TABLE ONLY ci_triggers ALTER TABLE ONLY ci_triggers
ADD CONSTRAINT ci_triggers_pkey PRIMARY KEY (id); 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 ALTER TABLE ONLY ci_variables
ADD CONSTRAINT ci_variables_pkey PRIMARY KEY (id); ADD CONSTRAINT ci_variables_pkey PRIMARY KEY (id);
...@@ -22172,6 +22218,10 @@ CREATE INDEX index_ci_triggers_on_owner_id ON ci_triggers USING btree (owner_id) ...@@ -22172,6 +22218,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_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 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); CREATE UNIQUE INDEX index_ci_variables_on_project_id_and_key_and_environment_scope ON ci_variables USING btree (project_id, key, environment_scope);
...@@ -23884,6 +23934,10 @@ CREATE INDEX index_u2f_registrations_on_key_handle ON u2f_registrations USING bt ...@@ -23884,6 +23934,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_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_checksum ON uploads USING btree (checksum);
CREATE INDEX index_uploads_on_model_id_and_model_type ON uploads USING btree (model_id, model_type); CREATE INDEX index_uploads_on_model_id_and_model_type ON uploads USING btree (model_id, model_type);
...@@ -24517,6 +24571,9 @@ ALTER TABLE ONLY notification_settings ...@@ -24517,6 +24571,9 @@ ALTER TABLE ONLY notification_settings
ALTER TABLE ONLY lists ALTER TABLE ONLY lists
ADD CONSTRAINT fk_0d3f677137 FOREIGN KEY (board_id) REFERENCES boards(id) ON DELETE CASCADE; 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 ALTER TABLE ONLY project_pages_metadata
ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL; ADD CONSTRAINT fk_0fd5b22688 FOREIGN KEY (pages_deployment_id) REFERENCES pages_deployments(id) ON DELETE SET NULL;
...@@ -24763,6 +24820,9 @@ ALTER TABLE ONLY geo_event_log ...@@ -24763,6 +24820,9 @@ ALTER TABLE ONLY geo_event_log
ALTER TABLE ONLY lists ALTER TABLE ONLY lists
ADD CONSTRAINT fk_7a5553d60f FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE; 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 ALTER TABLE ONLY protected_branches
ADD CONSTRAINT fk_7a9c6d93e7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; ADD CONSTRAINT fk_7a9c6d93e7 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
...@@ -25423,6 +25483,9 @@ ALTER TABLE ONLY group_custom_attributes ...@@ -25423,6 +25483,9 @@ ALTER TABLE ONLY group_custom_attributes
ALTER TABLE ONLY incident_management_oncall_rotations 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; 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 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; ADD CONSTRAINT fk_rails_25da9a92c0 FOREIGN KEY (segment_id) REFERENCES analytics_devops_adoption_segments(id) ON DELETE CASCADE;
...@@ -6,32 +6,32 @@ module Gitlab ...@@ -6,32 +6,32 @@ module Gitlab
class TestFailureHistory class TestFailureHistory
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
def initialize(failed_test_cases, project) def initialize(failed_junit_tests, project)
@failed_test_cases = build_map(failed_test_cases) @failed_junit_tests = build_map(failed_junit_tests)
@project = project @project = project
end end
def load! def load!
recent_failures_count.each do |key_hash, count| 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
end end
private private
attr_reader :report, :project, :failed_test_cases attr_reader :report, :project, :failed_junit_tests
def recent_failures_count def recent_failures_count
::Ci::TestCaseFailure.recent_failures_count( ::Ci::UnitTestFailure.recent_failures_count(
project: project, project: project,
test_case_keys: failed_test_cases.keys unit_test_keys: failed_junit_tests.keys
) )
end end
def build_map(test_cases) def build_map(junit_tests)
{}.tap do |hash| {}.tap do |hash|
test_cases.each do |test_case| junit_tests.each do |test|
hash[test_case.key] = test_case hash[test.key] = test
end end
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.define do
factory :ci_test_case, class: 'Ci::TestCase' do factory :ci_unit_test, class: 'Ci::UnitTest' do
project project
suite_name { 'rspec' }
name { 'Math#add returns sum' }
key_hash { Digest::SHA256.hexdigest(SecureRandom.hex) } key_hash { Digest::SHA256.hexdigest(SecureRandom.hex) }
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do 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 build factory: :ci_build
test_case factory: :ci_test_case unit_test factory: :ci_unit_test
failed_at { Time.current } failed_at { Time.current }
end end
end end
...@@ -13,9 +13,9 @@ RSpec.describe Gitlab::Ci::Reports::TestFailureHistory, :aggregate_failures do ...@@ -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! } subject(:load_history) { described_class.new([failed_rspec, failed_java], project).load! }
before do before do
allow(Ci::TestCaseFailure) allow(Ci::UnitTestFailure)
.to receive(:recent_failures_count) .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( .and_return(
failed_rspec.key => 2, failed_rspec.key => 2,
failed_java.key => 1 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 @@ ...@@ -2,16 +2,16 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Ci::TestCaseFailure do RSpec.describe Ci::UnitTestFailure do
describe 'relationships' do describe 'relationships' do
it { is_expected.to belong_to(:build) } it { is_expected.to belong_to(:build) }
it { is_expected.to belong_to(:test_case) } it { is_expected.to belong_to(:unit_test) }
end end
describe 'validations' do 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(:build) }
it { is_expected.to validate_presence_of(:failed_at) } it { is_expected.to validate_presence_of(:failed_at) }
end end
...@@ -22,51 +22,51 @@ RSpec.describe Ci::TestCaseFailure do ...@@ -22,51 +22,51 @@ RSpec.describe Ci::TestCaseFailure do
subject(:recent_failures) do subject(:recent_failures) do
described_class.recent_failures_count( described_class.recent_failures_count(
project: project, project: project,
test_case_keys: test_case_keys unit_test_keys: unit_test_keys
) )
end end
context 'when test case failures are within the date range and are for the test case keys' do context 'when unit test failures are within the date range and are for the unit test keys' do
let(:tc_1) { create(:ci_test_case, project: project) } let(:test_1) { create(:ci_unit_test, project: project) }
let(:tc_2) { create(:ci_test_case, project: project) } let(:test_2) { create(:ci_unit_test, project: project) }
let(:test_case_keys) { [tc_1.key_hash, tc_2.key_hash] } let(:unit_test_keys) { [test_1.key_hash, test_2.key_hash] }
before do before do
create_list(:ci_test_case_failure, 3, test_case: tc_1, failed_at: 1.day.ago) create_list(:ci_unit_test_failure, 3, unit_test: test_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, 2, unit_test: test_2, failed_at: 3.days.ago)
end 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( expect(recent_failures).to eq(
tc_1.key_hash => 3, test_1.key_hash => 3,
tc_2.key_hash => 2 test_2.key_hash => 2
) )
end end
end end
context 'when test case failures are within the date range but are not for the test case keys' do context 'when unit test failures are within the date range but are not for the unit test keys' do
let(:tc) { create(:ci_test_case, project: project) } let(:test) { create(:ci_unit_test, project: project) }
let(:test_case_keys) { ['some-other-key-hash'] } let(:unit_test_keys) { ['some-other-key-hash'] }
before do 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 end
it 'excludes them from the count' do 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 end
context 'when test case failures are not within the date range but are for the test case keys' do context 'when unit test failures are not within the date range but are for the unit test keys' do
let(:tc) { create(:ci_test_case, project: project) } let(:test) { create(:ci_unit_test, project: project) }
let(:test_case_keys) { [tc.key_hash] } let(:unit_test_keys) { [test.key_hash] }
before do 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 end
it 'excludes them from the count' do 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 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 ...@@ -11,15 +11,15 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
context 'when pipeline has failed builds with test reports' do context 'when pipeline has failed builds with test reports' do
before 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) create(:ci_build, :failed, :test_reports, pipeline: pipeline, project: project)
end end
it 'creates test case failures records' do it 'creates unit test failures records' do
execute_service execute_service
expect(Ci::TestCase.count).to eq(2) expect(Ci::UnitTest.count).to eq(2)
expect(Ci::TestCaseFailure.count).to eq(2) expect(Ci::UnitTestFailure.count).to eq(2)
end end
context 'when pipeline is not for the default branch' do context 'when pipeline is not for the default branch' do
...@@ -30,8 +30,8 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do ...@@ -30,8 +30,8 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do it 'does not persist data' do
execute_service execute_service
expect(Ci::TestCase.count).to eq(0) expect(Ci::UnitTest.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0) expect(Ci::UnitTestFailure.count).to eq(0)
end end
end end
...@@ -43,12 +43,12 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do ...@@ -43,12 +43,12 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not fail but does not persist new data' do it 'does not fail but does not persist new data' do
expect { described_class.new(pipeline).execute }.not_to raise_error expect { described_class.new(pipeline).execute }.not_to raise_error
expect(Ci::TestCase.count).to eq(2) expect(Ci::UnitTest.count).to eq(2)
expect(Ci::TestCaseFailure.count).to eq(2) expect(Ci::UnitTestFailure.count).to eq(2)
end end
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 before do
stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1) stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 1)
end end
...@@ -56,16 +56,16 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do ...@@ -56,16 +56,16 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do it 'does not persist data' do
execute_service execute_service
expect(Ci::TestCase.count).to eq(0) expect(Ci::UnitTest.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0) expect(Ci::UnitTestFailure.count).to eq(0)
end end
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 before do
stub_const("#{described_class.name}::MAX_TRACKABLE_FAILURES", 2) 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 # 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) create(:ci_build, :failed, :test_reports_with_duplicate_failed_test_names, pipeline: pipeline, project: project)
end end
...@@ -73,23 +73,23 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do ...@@ -73,23 +73,23 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do it 'does not persist data' do
execute_service execute_service
expect(Ci::TestCase.count).to eq(0) expect(Ci::UnitTest.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0) expect(Ci::UnitTestFailure.count).to eq(0)
end end
end 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 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) create(:ci_build, :failed, :test_reports_with_duplicate_failed_test_names, pipeline: pipeline, project: project)
end end
it 'does not fail but does not persist duplicate data' do it 'does not fail but does not persist duplicate data' do
expect { execute_service }.not_to raise_error expect { execute_service }.not_to raise_error
expect(Ci::TestCase.count).to eq(1) expect(Ci::UnitTest.count).to eq(1)
expect(Ci::TestCaseFailure.count).to eq(1) expect(Ci::UnitTestFailure.count).to eq(1)
end end
end end
...@@ -102,8 +102,8 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do ...@@ -102,8 +102,8 @@ RSpec.describe Ci::TestFailureHistoryService, :aggregate_failures do
it 'does not persist data' do it 'does not persist data' do
execute_service execute_service
expect(Ci::TestCase.count).to eq(0) expect(Ci::UnitTest.count).to eq(0)
expect(Ci::TestCaseFailure.count).to eq(0) expect(Ci::UnitTestFailure.count).to eq(0)
end end
end end
end end
......
...@@ -40,8 +40,8 @@ RSpec.describe ::Ci::TestFailureHistoryWorker do ...@@ -40,8 +40,8 @@ RSpec.describe ::Ci::TestFailureHistoryWorker do
subject subject
expect(Ci::TestCase.count).to eq(2) expect(Ci::UnitTest.count).to eq(2)
expect(Ci::TestCaseFailure.count).to eq(2) expect(Ci::UnitTestFailure.count).to eq(2)
end end
end 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