gitlab-ce/tooling/bin/tff_mappings

168 lines
4.6 KiB
Ruby
Executable File

#!/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)