diff options
Diffstat (limited to 'app/services/ci/test_failure_history_service.rb')
-rw-r--r-- | app/services/ci/test_failure_history_service.rb | 95 |
1 files changed, 95 insertions, 0 deletions
diff --git a/app/services/ci/test_failure_history_service.rb b/app/services/ci/test_failure_history_service.rb new file mode 100644 index 00000000000..99a2592ec06 --- /dev/null +++ b/app/services/ci/test_failure_history_service.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module Ci + class TestFailureHistoryService + class Async + attr_reader :service + + def initialize(service) + @service = service + end + + def perform_if_needed + TestFailureHistoryWorker.perform_async(service.pipeline.id) if service.should_track_failures? + end + end + + MAX_TRACKABLE_FAILURES = 200 + + attr_reader :pipeline + delegate :project, to: :pipeline + + def initialize(pipeline) + @pipeline = pipeline + end + + def execute + return unless should_track_failures? + + track_failures + end + + def should_track_failures? + return false unless Feature.enabled?(:test_failure_history, project) + return false unless project.default_branch_or_master == pipeline.ref + + # 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 + # 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 + end + + def async + Async.new(self) + end + + private + + def failed_builds + @failed_builds ||= pipeline.builds_with_failed_tests(limit: MAX_TRACKABLE_FAILURES + 1) + end + + def track_failures + failed_test_cases = gather_failed_test_cases(failed_builds) + + return if failed_test_cases.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) + + Ci::TestCaseFailure.insert_all(failures) + end + end + end + + def gather_failed_test_cases(failed_builds) + failed_builds.each_with_object({}) do |build, failed_test_cases| + test_suite = generate_test_suite!(build) + test_suite.failed.keys.each do |key| + failed_test_cases[key] = build + end + end + end + + def generate_test_suite!(build) + # Returns an instance of Gitlab::Ci::Reports::TestSuite + 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] + + { + test_case_id: test_case.id, + build_id: build.id, + failed_at: build.finished_at + } + end + end + end +end |