Commit 41f28a9f authored by Shinya Maeda's avatar Shinya Maeda

Add factory for parsers. Add required specification in json schema matcher. Improved test code.

parent 06b8f47c
...@@ -635,7 +635,7 @@ module Ci ...@@ -635,7 +635,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_test_report do |file_type, blob| each_test_report do |file_type, blob|
parse_test_report!(test_suite, file_type, blob) Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, test_suite)
end end
end end
end end
...@@ -650,11 +650,6 @@ module Ci ...@@ -650,11 +650,6 @@ module Ci
end end
end end
def parse_test_report!(test_suite, file_type, blob)
"Gitlab::Ci::Parsers::#{file_type.capitalize}Parser".constantize
.new(blob).parse!(test_suite)
end
def update_artifacts_size def update_artifacts_size
self.artifacts_size = legacy_artifacts_file&.size self.artifacts_size = legacy_artifacts_file&.size
end end
......
module Gitlab
module Ci
module Parsers
def self.fabricate!(file_type)
"Gitlab::Ci::Parsers::#{file_type.classify}".constantize.new
end
end
end
end
module Gitlab module Gitlab
module Ci module Ci
module Parsers module Parsers
class JunitParser class Junit
attr_reader :data attr_reader :data
JunitParserError = Class.new(StandardError) JunitParserError = Class.new(StandardError)
def initialize(xml_data) def parse!(xml_data, test_suite)
@data = Hash.from_xml(xml_data) @data = Hash.from_xml(xml_data)
rescue REXML::ParseException
raise JunitParserError, 'Failed to parse XML'
rescue
raise JunitParserError, 'Unknown error'
end
def parse!(test_suite)
each_suite do |testcases| each_suite do |testcases|
testcases.each do |testcase| testcases.each do |testcase|
test_case = create_test_case(testcase) test_case = create_test_case(testcase)
test_suite.add_test_case(test_case) test_suite.add_test_case(test_case)
end end
end end
rescue rescue REXML::ParseException => e
raise JunitParserError, 'Invalid JUnit xml structure' raise JunitParserError, "XML parsing failed: #{e.message}"
rescue => e
raise JunitParserError, "JUnit parsing failed: #{e.message}"
end end
private private
......
...@@ -188,9 +188,8 @@ FactoryBot.define do ...@@ -188,9 +188,8 @@ FactoryBot.define do
end end
trait :test_reports do trait :test_reports do
after(:create) do |build| after(:build) do |build|
create(:ci_job_artifact, :junit, job: build) build.job_artifacts << create(:ci_job_artifact, :junit, job: build)
build.reload
end end
end end
......
...@@ -70,11 +70,11 @@ FactoryBot.define do ...@@ -70,11 +70,11 @@ FactoryBot.define do
protected true protected true
end end
trait :test_reports do trait :with_test_reports do
status :success status :success
after(:build) do |pipeline, evaluator| after(:build) do |pipeline, evaluator|
create(:ci_build, :test_reports, pipeline: pipeline, project: pipeline.project) pipeline.builds << build(:ci_build, :test_reports, pipeline: pipeline, project: pipeline.project)
end end
end end
end end
......
...@@ -90,15 +90,14 @@ FactoryBot.define do ...@@ -90,15 +90,14 @@ FactoryBot.define do
end end
trait :with_test_reports do trait :with_test_reports do
after(:create) do |merge_request| after(:build) do |merge_request|
create(:ci_pipeline, merge_request.head_pipeline = build(
:ci_pipeline,
:success, :success,
:test_reports, :test_reports,
project: merge_request.source_project, project: merge_request.source_project,
ref: merge_request.source_branch, ref: merge_request.source_branch,
sha: merge_request.diff_head_sha).tap do |pipeline| sha: merge_request.diff_head_sha)
merge_request.update!(head_pipeline_id: pipeline.id)
end
end end
end end
......
{ {
"type": "object", "type": "object",
"required" : [
"status",
"name"
],
"properties": { "properties": {
"status": { "type": "string" }, "status": { "type": "string" },
"name": { "type": "string" }, "name": { "type": "string" },
......
{ {
"type": "object", "type": "object",
"required" : [
"status",
"summary",
"suites"
],
"properties": { "properties": {
"status": { "type": "string" }, "status": { "type": "string" },
"summary": { "summary": {
"type": "object",
"properties": {
"total": { "type": "integer" }, "total": { "type": "integer" },
"resolved": { "type": "integer" }, "resolved": { "type": "integer" },
"failed": { "type": "integer" } "failed": { "type": "integer" }
}, },
"required": [
"total",
"resolved",
"failed"
]
},
"suites": { "type": "array", "items": { "$ref": "test_suite_comparer.json" } } "suites": { "type": "array", "items": { "$ref": "test_suite_comparer.json" } }
}, },
"additionalProperties": false "additionalProperties": false
......
{ {
"type": "object", "type": "object",
"required": [
"name",
"status",
"summary",
"new_failures",
"resolved_failures",
"existing_failures"
],
"properties": { "properties": {
"name": { "type": "string" }, "name": { "type": "string" },
"status": { "type": "string" }, "status": { "type": "string" },
"summary": { "summary": {
"type": "object",
"properties": {
"total": { "type": "integer" }, "total": { "type": "integer" },
"resolved": { "type": "integer" }, "resolved": { "type": "integer" },
"failed": { "type": "integer" } "failed": { "type": "integer" }
}, },
"required": [
"total",
"resolved",
"failed"
]
},
"new_failures": { "type": "array", "items": { "$ref": "test_case.json" } }, "new_failures": { "type": "array", "items": { "$ref": "test_case.json" } },
"resolved_failures": { "type": "array", "items": { "$ref": "test_case.json" } }, "resolved_failures": { "type": "array", "items": { "$ref": "test_case.json" } },
"existing_failures": { "type": "array", "items": { "$ref": "test_case.json" } } "existing_failures": { "type": "array", "items": { "$ref": "test_case.json" } }
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Parsers::JunitParser do describe Gitlab::Ci::Parsers::Junit do
describe '#initialize' do
context 'when xml data is given' do
let(:data) do
<<-EOF.strip_heredoc
<testsuite></testsuite>
EOF
end
let(:parser) { described_class.new(data) }
it 'initialize Hash from the given data' do
expect { parser }.not_to raise_error
expect(parser.data).to be_a(Hash)
end
end
context 'when json data is given' do
let(:data) { { testsuite: 'abc' }.to_json }
it 'raises an error' do
expect { described_class.new(data) }.to raise_error(described_class::JunitParserError)
end
end
end
describe '#parse!' do describe '#parse!' do
subject { described_class.new(junit).parse!(test_suite) } subject { described_class.new.parse!(junit, test_suite) }
let(:test_suite) { Gitlab::Ci::Reports::TestSuite.new('rspec') } let(:test_suite) { Gitlab::Ci::Reports::TestSuite.new('rspec') }
let(:test_cases) { flattened_test_cases(test_suite) } let(:test_cases) { flattened_test_cases(test_suite) }
context 'when XML is formated as JUnit' do context 'when data is JUnit style XML' do
context 'when there are no test cases' do context 'when there are no test cases' do
let(:junit) do let(:junit) do
<<-EOF.strip_heredoc <<-EOF.strip_heredoc
...@@ -123,6 +97,16 @@ describe Gitlab::Ci::Parsers::JunitParser do ...@@ -123,6 +97,16 @@ describe Gitlab::Ci::Parsers::JunitParser do
end end
end end
context 'when data is not JUnit style XML' do
let(:junit) { { testsuite: 'abc' }.to_json }
it 'raises an error' do
expect { subject }.to raise_error(described_class::JunitParserError)
end
end
private
def flattened_test_cases(test_suite) def flattened_test_cases(test_suite)
test_suite.test_cases.map do |status, value| test_suite.test_cases.map do |status, value|
value.map do |key, test_case| value.map do |key, test_case|
......
...@@ -1105,30 +1105,20 @@ describe MergeRequest do ...@@ -1105,30 +1105,20 @@ describe MergeRequest do
let(:merge_request) { create(:merge_request, source_project: project) } let(:merge_request) { create(:merge_request, source_project: project) }
let!(:base_pipeline) do let!(:base_pipeline) do
create(:ci_pipeline, create(:ci_pipeline, :with_test_reports, project: project, ref: merge_request.target_branch, sha: merge_request.diff_base_sha)
:success,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_base_sha).tap do |pipeline|
merge_request.update!(head_pipeline_id: pipeline.id)
create(:ci_build, name: 'rspec', pipeline: pipeline, project: project)
end end
before do
merge_request.update!(head_pipeline_id: head_pipeline.id)
end end
context 'when head pipeline has test reports' do
let!(:head_pipeline) do let!(:head_pipeline) do
create(:ci_pipeline, create(:ci_pipeline,
:success, :with_test_reports,
project: merge_request.source_project, project: project,
ref: merge_request.source_branch, ref: merge_request.source_branch,
sha: merge_request.diff_head_sha).tap do |pipeline| sha: merge_request.diff_head_sha)
merge_request.update!(head_pipeline_id: pipeline.id)
create(:ci_build, name: 'rspec', pipeline: pipeline, project: project)
end
end
context 'when head pipeline has test reports' do
before do
create(:ci_job_artifact, :junit, job: head_pipeline.builds.first, project: project)
end end
context 'when reactive cache worker is parsing asynchronously' do context 'when reactive cache worker is parsing asynchronously' do
...@@ -1152,6 +1142,13 @@ describe MergeRequest do ...@@ -1152,6 +1142,13 @@ describe MergeRequest do
end end
context 'when head pipeline does not have test reports' do context 'when head pipeline does not have test reports' do
let!(:head_pipeline) do
create(:ci_pipeline,
project: project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
end
it 'returns status and error message' do it 'returns status and error message' do
expect(subject[:status]).to eq(:error) expect(subject[:status]).to eq(:error)
expect(subject[:status_reason]).to eq('This merge request does not have test reports') expect(subject[:status_reason]).to eq('This merge request does not have test reports')
......
...@@ -3,37 +3,23 @@ require 'spec_helper' ...@@ -3,37 +3,23 @@ require 'spec_helper'
describe Ci::CompareTestReportsService do describe Ci::CompareTestReportsService do
let(:service) { described_class.new(project) } let(:service) { described_class.new(project) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:merge_request) { create(:merge_request, source_project: project) }
describe '#execute' do describe '#execute' do
subject { service.execute(base_pipeline.iid, head_pipeline.iid) } subject { service.execute(base_pipeline&.iid, head_pipeline.iid) }
let!(:base_pipeline) do context 'when head pipeline has test reports' do
create(:ci_pipeline, let!(:base_pipeline) { nil }
:success, let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_base_sha).tap do |pipeline|
merge_request.update!(head_pipeline_id: pipeline.id)
create(:ci_build, name: 'rspec', pipeline: pipeline, project: project)
end
end
let!(:head_pipeline) do it 'returns status and data' do
create(:ci_pipeline, expect(subject[:status]).to eq(:parsed)
:success, expect(subject[:data]).to match_schema('entities/test_reports_comparer')
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha).tap do |pipeline|
merge_request.update!(head_pipeline_id: pipeline.id)
create(:ci_build, name: 'rspec', pipeline: pipeline, project: project)
end end
end end
context 'when head pipeline has test reports' do context 'when base and head pipelines have test reports' do
before do let!(:base_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
create(:ci_job_artifact, :junit, job: head_pipeline.builds.first, project: project) let!(:head_pipeline) { create(:ci_pipeline, :with_test_reports, project: project) }
end
it 'returns status and data' do it 'returns status and data' do
expect(subject[:status]).to eq(:parsed) expect(subject[:status]).to eq(:parsed)
...@@ -42,13 +28,17 @@ describe Ci::CompareTestReportsService do ...@@ -42,13 +28,17 @@ describe Ci::CompareTestReportsService do
end end
context 'when head pipeline has corrupted test reports' do context 'when head pipeline has corrupted test reports' do
let!(:base_pipeline) { nil }
let!(:head_pipeline) { create(:ci_pipeline, project: project) }
before do before do
create(:ci_job_artifact, :junit_with_corrupted_data, job: head_pipeline.builds.first, project: project) build = create(:ci_build, pipeline: head_pipeline, project: head_pipeline.project)
create(:ci_job_artifact, :junit_with_corrupted_data, job: build, project: project)
end end
it 'returns status and error message' do it 'returns status and error message' do
expect(subject[:status]).to eq(:error) expect(subject[:status]).to eq(:error)
expect(subject[:status_reason]).to eq('Failed to parse XML') expect(subject[:status_reason]).to include('XML parsing failed')
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