diff options
Diffstat (limited to 'tooling/lib')
-rw-r--r-- | tooling/lib/tooling/crystalball/coverage_lines_execution_detector.rb | 44 | ||||
-rw-r--r-- | tooling/lib/tooling/crystalball/coverage_lines_strategy.rb | 23 | ||||
-rw-r--r-- | tooling/lib/tooling/helm3_client.rb | 3 | ||||
-rw-r--r-- | tooling/lib/tooling/test_map_generator.rb | 36 | ||||
-rw-r--r-- | tooling/lib/tooling/test_map_packer.rb | 58 |
5 files changed, 162 insertions, 2 deletions
diff --git a/tooling/lib/tooling/crystalball/coverage_lines_execution_detector.rb b/tooling/lib/tooling/crystalball/coverage_lines_execution_detector.rb new file mode 100644 index 00000000000..47ddf568fe4 --- /dev/null +++ b/tooling/lib/tooling/crystalball/coverage_lines_execution_detector.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'crystalball/map_generator/helpers/path_filter' + +module Tooling + module Crystalball + # Class for detecting code execution path based on coverage information diff + class CoverageLinesExecutionDetector + include ::Crystalball::MapGenerator::Helpers::PathFilter + + attr_reader :exclude_prefixes + + def initialize(*args, exclude_prefixes: []) + super(*args) + @exclude_prefixes = exclude_prefixes + end + + # Detects files affected during example execution based on line coverage. + # Transforms absolute paths to relative. + # Exclude paths outside of repository and in excluded prefixes + # + # @param[Hash] hash of files affected before example execution + # @param[Hash] hash of files affected after example execution + # @return [Array<String>] + def detect(before, after) + file_names = after.keys + covered_files = file_names.reject { |file_name| same_coverage?(before, after, file_name) } + filter(covered_files) + end + + private + + def same_coverage?(before, after, file_name) + before[file_name] && before[file_name][:lines] == after[file_name][:lines] + end + + def filter(paths) + super.reject do |file_name| + exclude_prefixes.any? { |prefix| file_name.start_with?(prefix) } + end + end + end + end +end diff --git a/tooling/lib/tooling/crystalball/coverage_lines_strategy.rb b/tooling/lib/tooling/crystalball/coverage_lines_strategy.rb new file mode 100644 index 00000000000..ebcaab0b8d8 --- /dev/null +++ b/tooling/lib/tooling/crystalball/coverage_lines_strategy.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'coverage' +require 'crystalball/map_generator/coverage_strategy' +require_relative './coverage_lines_execution_detector' + +module Tooling + module Crystalball + # Crystalball map generator strategy based on Crystalball::MapGenerator::CoverageStrategy, + # modified to use Coverage.start(lines: true) + # This maintains compatibility with SimpleCov on Ruby >= 2.5 with start arguments + # and SimpleCov.start uses Coverage.start(lines: true) by default + class CoverageLinesStrategy < ::Crystalball::MapGenerator::CoverageStrategy + def initialize(execution_detector = CoverageLinesExecutionDetector) + super(execution_detector) + end + + def after_register + Coverage.start(lines: true) + end + end + end +end diff --git a/tooling/lib/tooling/helm3_client.rb b/tooling/lib/tooling/helm3_client.rb index 802ff9b9661..d6671688794 100644 --- a/tooling/lib/tooling/helm3_client.rb +++ b/tooling/lib/tooling/helm3_client.rb @@ -3,7 +3,6 @@ require 'time' require 'json' require_relative '../../../lib/gitlab/popen' unless defined?(Gitlab::Popen) -require_relative '../../../lib/gitlab/json' unless defined?(Gitlab::Json) module Tooling class Helm3Client @@ -67,7 +66,7 @@ module Tooling %(--output json), *args ] - releases = Gitlab::Json.parse(run_command(command)) + releases = JSON.parse(run_command(command)) # rubocop:disable Gitlab/Json releases.map do |release| Release.new(*release.values_at(*RELEASE_JSON_ATTRIBUTES)) diff --git a/tooling/lib/tooling/test_map_generator.rb b/tooling/lib/tooling/test_map_generator.rb new file mode 100644 index 00000000000..bd0415f6e67 --- /dev/null +++ b/tooling/lib/tooling/test_map_generator.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'set' +require 'yaml' + +module Tooling + class TestMapGenerator + def initialize + @mapping = Hash.new { |h, k| h[k] = Set.new } + end + + def parse(yaml_files) + Array(yaml_files).each do |yaml_file| + data = File.read(yaml_file) + _metadata, example_groups = data.split("---\n").reject(&:empty?).map { |yml| YAML.safe_load(yml, [Symbol]) } + + example_groups.each do |example_id, files| + files.each do |file| + spec_file = strip_example_uid(example_id) + @mapping[file] << spec_file + end + end + end + end + + def mapping + @mapping.transform_values { |set| set.to_a } + end + + private + + def strip_example_uid(example_id) + example_id.gsub(/\[.+\]/, '') + end + end +end diff --git a/tooling/lib/tooling/test_map_packer.rb b/tooling/lib/tooling/test_map_packer.rb new file mode 100644 index 00000000000..520d69610eb --- /dev/null +++ b/tooling/lib/tooling/test_map_packer.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Tooling + class TestMapPacker + SEPARATOR = '/'.freeze + MARKER = 1 + + def pack(map) + map.transform_values(&method(:create_tree_from_tests)) + end + + def unpack(compact_map) + compact_map.transform_values(&method(:retrieve_tests_from_tree)) + end + + private + + def create_tree_from_tests(tests) + tests.inject({}) do |tree, test| + segments = test.split(SEPARATOR) + branch = create_branch_from_segments(segments) + deep_merge(tree, branch) + end + end + + def create_branch_from_segments(segments) + segments.reverse.inject(MARKER) { |node, parent| { parent => node } } + end + + def deep_merge(hash, other) + hash.merge(other) do |_, this_val, other_val| + if this_val.is_a?(Hash) && other_val.is_a?(Hash) + deep_merge(this_val, other_val) + else + other_val + end + end + end + + def retrieve_tests_from_tree(tree) + traverse(tree).inject([]) do |tests, test| + tests << test + end + end + + def traverse(tree, segments = [], &block) + return to_enum(__method__, tree, segments) unless block_given? + + if tree == MARKER + return yield segments.join(SEPARATOR) + end + + tree.each do |key, value| + traverse(value, segments + [key], &block) + end + end + end +end |