Commit e6226e8c authored by Gilbert Roulot's avatar Gilbert Roulot Committed by Kamil Trzciński

Generalise test compare service

It adds a base class for CompareTestReportsService
containing common code with CompareLicenseManagementReportsService
which is present in GitLab Enterprise Edition.
parent 85f430cb
...@@ -742,7 +742,7 @@ module Ci ...@@ -742,7 +742,7 @@ module Ci
def collect_test_reports!(test_reports) def collect_test_reports!(test_reports)
test_reports.get_suite(group_name).tap do |test_suite| test_reports.get_suite(group_name).tap do |test_suite|
each_report(Ci::JobArtifact::TEST_REPORT_FILE_TYPES) do |file_type, blob| each_report(Ci::JobArtifact::TEST_REPORT_FILE_TYPES) do |file_type, blob|
Gitlab::Ci::Parsers::Test.fabricate!(file_type).parse!(blob, test_suite) Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, test_suite)
end end
end end
end end
......
...@@ -1118,14 +1118,17 @@ class MergeRequest < ActiveRecord::Base ...@@ -1118,14 +1118,17 @@ class MergeRequest < ActiveRecord::Base
end end
end end
# rubocop: disable CodeReuse/ServiceClass
def compare_test_reports def compare_test_reports
unless has_test_reports? unless has_test_reports?
return { status: :error, status_reason: 'This merge request does not have test reports' } return { status: :error, status_reason: 'This merge request does not have test reports' }
end end
with_reactive_cache(:compare_test_results) do |data| compare_reports(Ci::CompareTestReportsService)
unless Ci::CompareTestReportsService.new(project) end
def compare_reports(service_class)
with_reactive_cache(service_class.name) do |data|
unless service_class.new(project)
.latest?(base_pipeline, actual_head_pipeline, data) .latest?(base_pipeline, actual_head_pipeline, data)
raise InvalidateReactiveCache raise InvalidateReactiveCache
end end
...@@ -1133,19 +1136,14 @@ class MergeRequest < ActiveRecord::Base ...@@ -1133,19 +1136,14 @@ class MergeRequest < ActiveRecord::Base
data data
end || { status: :parsing } end || { status: :parsing }
end end
# rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass
def calculate_reactive_cache(identifier, *args) def calculate_reactive_cache(identifier, *args)
case identifier.to_sym service_class = identifier.constantize
when :compare_test_results
Ci::CompareTestReportsService.new(project).execute( raise NameError, service_class unless service_class < Ci::CompareReportsBaseService
base_pipeline, actual_head_pipeline)
else service_class.new(project).execute(base_pipeline, actual_head_pipeline)
raise NotImplementedError, "Unknown identifier: #{identifier}"
end
end end
# rubocop: enable CodeReuse/ServiceClass
def all_commits def all_commits
# MySQL doesn't support LIMIT in a subquery. # MySQL doesn't support LIMIT in a subquery.
......
# frozen_string_literal: true
module Ci
class CompareReportsBaseService < ::BaseService
def execute(base_pipeline, head_pipeline)
comparer = comparer_class.new(get_report(base_pipeline), get_report(head_pipeline))
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: serializer_class
.new(project: project)
.represent(comparer).as_json
}
rescue Gitlab::Ci::Parsers::ParserError => e
{
status: :error,
key: key(base_pipeline, head_pipeline),
status_reason: e.message
}
end
def latest?(base_pipeline, head_pipeline, data)
data&.fetch(:key, nil) == key(base_pipeline, head_pipeline)
end
private
def key(base_pipeline, head_pipeline)
[
base_pipeline&.id, base_pipeline&.updated_at,
head_pipeline&.id, head_pipeline&.updated_at
]
end
def comparer_class
raise NotImplementedError
end
def serializer_class
raise NotImplementedError
end
def get_report(pipeline)
raise NotImplementedError
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
module Ci module Ci
class CompareTestReportsService < ::BaseService class CompareTestReportsService < CompareReportsBaseService
def execute(base_pipeline, head_pipeline) def comparer_class
# rubocop: disable CodeReuse/Serializer Gitlab::Ci::Reports::TestReportsComparer
comparer = Gitlab::Ci::Reports::TestReportsComparer
.new(base_pipeline&.test_reports, head_pipeline.test_reports)
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: TestReportsComparerSerializer
.new(project: project)
.represent(comparer).as_json
}
rescue => e
{
status: :error,
key: key(base_pipeline, head_pipeline),
status_reason: e.message
}
# rubocop: enable CodeReuse/Serializer
end end
def latest?(base_pipeline, head_pipeline, data) def serializer_class
data&.fetch(:key, nil) == key(base_pipeline, head_pipeline) TestReportsComparerSerializer
end end
private def get_report(pipeline)
pipeline&.test_reports
def key(base_pipeline, head_pipeline)
[
base_pipeline&.id, base_pipeline&.updated_at,
head_pipeline&.id, head_pipeline&.updated_at
]
end end
end end
end end
...@@ -3,18 +3,18 @@ ...@@ -3,18 +3,18 @@
module Gitlab module Gitlab
module Ci module Ci
module Parsers module Parsers
module Test ParserNotFoundError = Class.new(ParserError)
ParserNotFoundError = Class.new(StandardError)
PARSERS = { def self.parsers
{
junit: ::Gitlab::Ci::Parsers::Test::Junit junit: ::Gitlab::Ci::Parsers::Test::Junit
}.freeze }
end
def self.fabricate!(file_type) def self.fabricate!(file_type)
PARSERS.fetch(file_type.to_sym).new parsers.fetch(file_type.to_sym).new
rescue KeyError rescue KeyError
raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'" raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'"
end
end end
end end
end end
......
# frozen_string_literal: true
module Gitlab
module Ci
module Parsers
ParserError = Class.new(StandardError)
end
end
end
...@@ -5,7 +5,7 @@ module Gitlab ...@@ -5,7 +5,7 @@ module Gitlab
module Parsers module Parsers
module Test module Test
class Junit class Junit
JunitParserError = Class.new(StandardError) JunitParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
def parse!(xml_data, test_suite) def parse!(xml_data, test_suite)
root = Hash.from_xml(xml_data) root = Hash.from_xml(xml_data)
......
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Parsers::Test do describe Gitlab::Ci::Parsers do
describe '.fabricate!' do describe '.fabricate!' do
subject { described_class.fabricate!(file_type) } subject { described_class.fabricate!(file_type) }
...@@ -8,7 +10,7 @@ describe Gitlab::Ci::Parsers::Test do ...@@ -8,7 +10,7 @@ describe Gitlab::Ci::Parsers::Test do
let(:file_type) { 'junit' } let(:file_type) { 'junit' }
it 'fabricates the class' do it 'fabricates the class' do
is_expected.to be_a(described_class::Junit) is_expected.to be_a(described_class::Test::Junit)
end end
end end
...@@ -16,7 +18,7 @@ describe Gitlab::Ci::Parsers::Test do ...@@ -16,7 +18,7 @@ describe Gitlab::Ci::Parsers::Test do
let(:file_type) { 'undefined' } let(:file_type) { 'undefined' }
it 'raises an error' do it 'raises an error' do
expect { subject }.to raise_error(Gitlab::Ci::Parsers::Test::ParserNotFoundError) expect { subject }.to raise_error(Gitlab::Ci::Parsers::ParserNotFoundError)
end end
end end
end end
......
...@@ -1339,6 +1339,30 @@ describe MergeRequest do ...@@ -1339,6 +1339,30 @@ describe MergeRequest do
end end
end end
describe '#calculate_reactive_cache' do
let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
subject { merge_request.calculate_reactive_cache(service_class_name) }
context 'when given an unknown service class name' do
let(:service_class_name) { 'Integer' }
it 'raises a NameError exception' do
expect { subject }.to raise_error(NameError, service_class_name)
end
end
context 'when given a known service class name' do
let(:service_class_name) { 'Ci::CompareTestReportsService' }
it 'does not raises a NameError exception' do
allow_any_instance_of(service_class_name.constantize).to receive(:execute).and_return(nil)
expect { subject }.not_to raise_error(NameError)
end
end
end
describe '#compare_test_reports' do describe '#compare_test_reports' do
subject { merge_request.compare_test_reports } subject { merge_request.compare_test_reports }
......
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