summaryrefslogtreecommitdiff
path: root/tooling/lib
diff options
context:
space:
mode:
Diffstat (limited to 'tooling/lib')
-rw-r--r--tooling/lib/tooling/crystalball/coverage_lines_execution_detector.rb44
-rw-r--r--tooling/lib/tooling/crystalball/coverage_lines_strategy.rb23
-rw-r--r--tooling/lib/tooling/helm3_client.rb3
-rw-r--r--tooling/lib/tooling/test_map_generator.rb36
-rw-r--r--tooling/lib/tooling/test_map_packer.rb58
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