From c1a8905ef539b169c020f030ee197f0f7d01e4ee Mon Sep 17 00:00:00 2001 From: Peter Leitzen <pleitzen@gitlab.com> Date: Wed, 15 May 2024 17:32:37 +0200 Subject: [PATCH] Add tff_mappings tool to show found or missing tests.yml mappings --- tooling/bin/tff_mappings | 167 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100755 tooling/bin/tff_mappings diff --git a/tooling/bin/tff_mappings b/tooling/bin/tff_mappings new file mode 100755 index 0000000000000..3b7ad805beccb --- /dev/null +++ b/tooling/bin/tff_mappings @@ -0,0 +1,167 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'optparse' +require 'test_file_finder' + +# Show mappings for `test_file_finder` defined in `tests.yml`. +# +# Usage: +# tooling/bin/tff_mappings --help +# tooling/bin/tff_mappings # Show summary for all commited files +# tooling/bin/tff_mappings app/ lib/ # Show summary for specific folders +# tooling/bin/tff_mappings --missing app/ # List missing files in app/ folder +# tooling/bin/tff_mappings --ignored --no-summary # List ignored files without summary +module Tooling + class TffMappings + TESTS_YAML = File.expand_path('../../tests.yml', __dir__) + STRATEGY = TestFileFinder::MappingStrategies::PatternMatching.load(TESTS_YAML) + + IGNORE = %w[ + **/.*ignore + **/.{,git}keep + **/*.{jpg,js,mjs,md,patch,png,sh,svg,toml,txt} + .rubocop.yml .rubocop_todo/ + db/schema_migrations db/*.sql + gems/ vendor/ + locale/ + public/ app/assets/ ee/app/assets/ + qa/ + spec/components/previews/ ee/spec/components/previews/ + spec/contracts/ ee/spec/contracts/ + spec/factories/ ee/spec/factories/ + spec/fixtures/ ee/spec/fixtures/ + spec/frontend/ ee/spec/frontend/ + spec/frontend_integration/ ee/spec/frontend_integration/ + spec/support/ ee/spec/support/ + tmp/ + workhorse/ + ].freeze + + UNIGNORE = %w[ + spec/contracts/provider_specs ee/spec/contracts/provider_specs + ].freeze + + def self.options(argv) + options = { + found: false, + missing: false, + ignored: false, + summary: true + } + + OptionParser.new do |parser| + parser.banner = "Usage: #{__FILE__} [options] [<file> ...]" + + parser.on('--found', 'Show found tff mappings.') + parser.on('--missing', 'Show missing tff mappings.') + parser.on('--ignored', 'Show ignored files.') + parser.on('--all', 'Show all states: found, missing, and ignored.') do + options.except(:summary).each_key { |key| options[key] = true } + end + parser.on('--[no-]summary', 'Show summary of found and missing entries. Default: true') + + parser.on('-h', '--help', 'Show this help.') do + puts parser + exit + end + end.parse!(argv, into: options) + + if argv.empty? + files = `git ls-files -z`.split("\0") + else + pattern = argv + .map { |arg| File.directory?(arg) ? "#{arg.delete_suffix('/')}/**/*" : arg } + .join(",") + files = Dir.glob("{#{pattern}}").reject { |file| File.directory?(file) } + end + + [options, files] + end + + def initialize(options) + @options = options + @output = any_combination?(@options.except(:summary)) ? with_label : without_label + @ignore = matchers_for(IGNORE) + @unignore = matchers_for(UNIGNORE) + end + + def run(files) + stats = { + total: 0, + found: 0, + missing: 0, + ignored: 0 + } + + files.each do |file| + stats[:total] += 1 + + if match_list?(@ignore, file) && !match_list?(@unignore, file) + stats[:ignored] += 1 + puts @output.call('IGNORED', file) if @options[:ignored] + next + end + + result = test_files(file) + + if result.empty? + stats[:missing] += 1 + puts @output.call('MISSING', file) if @options[:missing] + else + stats[:found] += 1 + puts @output.call('FOUND', file) if @options[:found] + end + end + + print_summary(stats) + end + + private + + def print_summary(stats) + return unless @options[:summary] + + puts stats.map { |key, value| "#{key}=#{value}" }.join(' ').prepend('# ') + end + + def matchers_for(list) + list.map do |item| + %r{[*?\[\{]}.match?(item) ? pattern_match(item) : start_with_match(item) + end + end + + def pattern_match(glob) + ->(path) { File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB) } + end + + def start_with_match(string) + ->(path) { path.start_with?(string) } + end + + def match_list?(list, file) + list.any? { |matcher| matcher.call(file) } + end + + def any_combination?(options) + options.values.count(&:itself) >= 2 + end + + def with_label + ->(label, file) { "#{file} #{label}" } + end + + def without_label + ->(_label, file) { file } + end + + def test_files(source) + tff = TestFileFinder::FileFinder.new(paths: [source]) + tff.use STRATEGY + tff.test_files + end + end +end + +options, files = Tooling::TffMappings.options(ARGV) +Tooling::TffMappings.new(**options).run(files) -- GitLab