mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
173 lines
5.4 KiB
Ruby
173 lines
5.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
|
|
module GithubActionsFailures
|
|
Result = Data.define(:errors, :failures_explanation, :merge_branch_sha)
|
|
|
|
class Error
|
|
attr_accessor :location, :page_html, :page_screenshot, :tests_group, :loading_error
|
|
end
|
|
|
|
class TestsGroup
|
|
attr_accessor :test_env_number, :seed, :files
|
|
|
|
def initialize
|
|
@files = []
|
|
end
|
|
|
|
def include_error?(error)
|
|
return false if error.location.nil?
|
|
|
|
files.any? { |file| error.location.include?(file) }
|
|
end
|
|
|
|
def inspect
|
|
"#<#{self.class} @test_env_number=#{test_env_number} @seed=#{seed} (#{files.count} files)>"
|
|
end
|
|
end
|
|
|
|
class JobErrorsFinder
|
|
SPEC_FAILURES_PATTERN = %r{^\S+ rspec (\S+) #.+$}
|
|
SPEC_LOADING_ERRORS_PATTERN = %r{^\S+ An error occurred while loading (\S+)\.\r?$}
|
|
SCREENSHOT_PATTERN = /\{"message":"Screenshot captured for failed feature test"[^\n]+$/
|
|
# Looks like this in the job log:
|
|
# rubocop:disable Layout/LineLength
|
|
# Process 28: TEST_ENV_NUMBER=28 RUBYOPT=-I/usr/local/bundle/bundler/gems/turbo_tests-3148ae6c3482/lib -r/usr/local/bundle/gems/bundler-2.5.23/lib/bundler/setup -W0 RSPEC_SILENCE_FILTER_ANNOUNCEMENTS=1 /usr/local/bundle/gems/bundler-2.5.23/exe/bundle exec rspec --seed 52674 --format TurboTests::JsonRowsFormatter --out tmp/test-pipes/subprocess-28 --format ParallelTests::RSpec::RuntimeLogger --out spec/support/turbo_runtime_features.log spec/features/api_docs/index_spec.rb spec/features/custom_fields/reorder_options_spec.rb spec/features/projects/projects_portfolio_spec.rb spec/features/projects/template_spec.rb spec/features/versions/edit_spec.rb spec/features/work_packages/details/markdown/description_editor_spec.rb spec/features/work_packages/table/hierarchy/hierarchy_parent_below_spec.rb spec/features/work_packages/table/inline_create/inline_create_refresh_spec.rb spec/features/work_packages/table/invalid_query_spec.rb spec/features/work_packages/tabs/activity_revisions_spec.rb
|
|
# rubocop:enable Layout/LineLength
|
|
TESTS_GROUP_PATTERN = /Process \d+: TEST_ENV_NUMBER=\d+ [^\n]+$/
|
|
BRANCH_MERGE_PATTERN = /Merge \w{40} into (\w{40})$/
|
|
|
|
def self.scan_logs(logs)
|
|
finder = new
|
|
logs.each do |log|
|
|
finder.scan_log(log)
|
|
end
|
|
|
|
Result.new(
|
|
errors: finder.errors,
|
|
failures_explanation: finder.failures_explanation,
|
|
merge_branch_sha: finder.merge_branch_sha
|
|
)
|
|
end
|
|
|
|
attr_reader :failures_explanation, :merge_branch_sha
|
|
|
|
def scan_log(log)
|
|
find_failures(log)
|
|
find_failures_explanation(log)
|
|
find_loading_errors(log)
|
|
find_screenshots(log)
|
|
find_tests_groups(log)
|
|
find_merge_branch_info(log)
|
|
end
|
|
|
|
def errors
|
|
@errors.values
|
|
end
|
|
|
|
private
|
|
|
|
def initialize
|
|
@errors = {}
|
|
end
|
|
|
|
def create_error(location)
|
|
return if location.nil?
|
|
|
|
error = Error.new
|
|
error.location = location
|
|
@errors[location] ||= error
|
|
end
|
|
|
|
def with_matching_error(location: nil, id: nil)
|
|
error = @errors[id] || @errors[location]
|
|
yield error if error && block_given?
|
|
error
|
|
end
|
|
|
|
def find_failures(log)
|
|
log.scan(SPEC_FAILURES_PATTERN)
|
|
.flatten
|
|
.uniq
|
|
.sort
|
|
.each do |rerun_location|
|
|
create_error(rerun_location)
|
|
end
|
|
end
|
|
|
|
def find_failures_explanation(log)
|
|
explanations = []
|
|
log.split("\n").each do |line|
|
|
if line.end_with?("Failures:") .. line.end_with?("Failed examples:")
|
|
explanations << line
|
|
end
|
|
end
|
|
explanations.map! { it[29..] } # Remove leading GitHub Actions log timestamp (e.g. "2024-02-05T08:37:54.5175930Z ")
|
|
explanations.reject! do |line|
|
|
line == "Failures:" ||
|
|
line == "Failed examples:" ||
|
|
line.include?("gems/rspec-retry-") ||
|
|
line.include?("gems/webmock-")
|
|
end
|
|
@failures_explanation = explanations.join("\n")
|
|
end
|
|
|
|
def find_loading_errors(log)
|
|
log.scan(SPEC_LOADING_ERRORS_PATTERN)
|
|
.flatten
|
|
.uniq
|
|
.sort
|
|
.each do |location|
|
|
error = create_error(location)
|
|
error.loading_error = true
|
|
end
|
|
end
|
|
|
|
def find_screenshots(log)
|
|
log.scan(SCREENSHOT_PATTERN)
|
|
.map { JSON.parse(it) }
|
|
.each do |screenshot_info|
|
|
id = screenshot_info["test_id"]
|
|
location = screenshot_info["test_location"]
|
|
with_matching_error(location:, id:) do |error|
|
|
error.page_html = screenshot_info["html"]
|
|
error.page_screenshot = screenshot_info["image"]
|
|
end
|
|
end
|
|
end
|
|
|
|
def find_tests_groups(log)
|
|
tests_groups = log
|
|
.scan(TESTS_GROUP_PATTERN)
|
|
.flatten
|
|
.map { build_tests_group_from_command(it) }
|
|
|
|
errors.each do |error|
|
|
error.tests_group = tests_groups.find { it.include_error?(error) }
|
|
end
|
|
end
|
|
|
|
def find_merge_branch_info(log)
|
|
merge_branch_sha = log.scan(BRANCH_MERGE_PATTERN).flatten.first
|
|
@merge_branch_sha = merge_branch_sha if merge_branch_sha
|
|
end
|
|
|
|
def build_tests_group_from_command(line)
|
|
tests_group = TestsGroup.new
|
|
parts = line.split
|
|
while parts.any?
|
|
case part = parts.shift
|
|
when /^TEST_ENV_NUMBER=/
|
|
tests_group.test_env_number = part.delete_prefix("TEST_ENV_NUMBER=")
|
|
when "--seed"
|
|
tests_group.seed = parts.shift
|
|
when /_spec.rb$/
|
|
tests_group.files << part
|
|
end
|
|
end
|
|
tests_group
|
|
end
|
|
end
|
|
end
|