i18n-tasks 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +321 -242
- data/Rakefile +5 -1
- data/benchmarks/bench_helper.rb +156 -0
- data/benchmarks/data_bench.rb +173 -0
- data/benchmarks/end_to_end_bench.rb +138 -0
- data/benchmarks/fixtures/generator.rb +257 -0
- data/benchmarks/results/baseline.json +250 -0
- data/benchmarks/run_all.rb +60 -0
- data/benchmarks/scanning_bench.rb +135 -0
- data/benchmarks/tree_bench.rb +205 -0
- data/config/locales/en.yml +10 -0
- data/config/locales/ru.yml +14 -0
- data/i18n-tasks.gemspec +1 -1
- data/lib/i18n/tasks/base_task.rb +2 -0
- data/lib/i18n/tasks/command/commands/check_prism.rb +67 -15
- data/lib/i18n/tasks/command/commands/prune.rb +57 -0
- data/lib/i18n/tasks/commands.rb +2 -0
- data/lib/i18n/tasks/data/adapter/json_adapter.rb +1 -1
- data/lib/i18n/tasks/data/file_formats.rb +4 -1
- data/lib/i18n/tasks/data/router/conservative_router.rb +13 -22
- data/lib/i18n/tasks/data/router/pattern_router.rb +26 -14
- data/lib/i18n/tasks/data/tree/node.rb +3 -3
- data/lib/i18n/tasks/data/tree/siblings.rb +11 -3
- data/lib/i18n/tasks/data/tree/traversal.rb +20 -14
- data/lib/i18n/tasks/interpolations.rb +61 -3
- data/lib/i18n/tasks/logging.rb +1 -2
- data/lib/i18n/tasks/missing_keys.rb +7 -38
- data/lib/i18n/tasks/plural_keys.rb +56 -0
- data/lib/i18n/tasks/prune.rb +18 -0
- data/lib/i18n/tasks/references.rb +9 -1
- data/lib/i18n/tasks/scanners/ast_matchers/base_matcher.rb +11 -2
- data/lib/i18n/tasks/scanners/ast_matchers/rails_model_matcher.rb +16 -2
- data/lib/i18n/tasks/scanners/erb_ast_scanner.rb +6 -3
- data/lib/i18n/tasks/scanners/file_scanner.rb +4 -1
- data/lib/i18n/tasks/scanners/files/file_finder.rb +29 -6
- data/lib/i18n/tasks/scanners/files/parse_cache.rb +24 -0
- data/lib/i18n/tasks/scanners/prism_scanners/arguments_visitor.rb +33 -0
- data/lib/i18n/tasks/scanners/prism_scanners/nodes.rb +57 -23
- data/lib/i18n/tasks/scanners/prism_scanners/visitor.rb +70 -16
- data/lib/i18n/tasks/scanners/relative_keys.rb +21 -11
- data/lib/i18n/tasks/scanners/ruby_scanner.rb +37 -5
- data/lib/i18n/tasks/split_key.rb +16 -6
- data/lib/i18n/tasks/translators/base_translator.rb +25 -23
- data/lib/i18n/tasks/translators/deepl_translator.rb +18 -15
- data/lib/i18n/tasks/translators/google_translator.rb +41 -14
- data/lib/i18n/tasks/translators/openai_translator.rb +31 -17
- data/lib/i18n/tasks/translators/watsonx_translator.rb +8 -10
- data/lib/i18n/tasks/translators/yandex_translator.rb +4 -0
- data/lib/i18n/tasks/unused_keys.rb +35 -5
- data/lib/i18n/tasks/used_keys.rb +6 -1
- data/lib/i18n/tasks/version.rb +1 -1
- data/script/report_test_results.rb +58 -0
- data/templates/config/i18n-tasks.yml +11 -3
- metadata +17 -7
data/Rakefile
CHANGED
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
require "rspec/core/rake_task"
|
|
5
|
-
RSpec::Core::RakeTask.new(:rspec)
|
|
5
|
+
RSpec::Core::RakeTask.new(:rspec) do |task|
|
|
6
|
+
if ENV["RSPEC_JUNIT_OUTPUT"]
|
|
7
|
+
task.rspec_opts = ["--format", "RspecJunitFormatter", "--out", ENV.fetch("RSPEC_JUNIT_OUTPUT")]
|
|
8
|
+
end
|
|
9
|
+
end
|
|
6
10
|
task default: :rspec
|
|
7
11
|
|
|
8
12
|
task :irb do
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "benchmark/ips"
|
|
9
|
+
require "memory_profiler"
|
|
10
|
+
rescue LoadError => e
|
|
11
|
+
warn "#{e.message}\n\nBenchmark gems are missing. Install them with:\n\n bundle install\n\n" \
|
|
12
|
+
"If you have BUNDLE_WITHOUT set, make sure it does not exclude the :bench group.\n"
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Load the gem under test
|
|
17
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __dir__)
|
|
18
|
+
require "i18n/tasks"
|
|
19
|
+
|
|
20
|
+
require_relative "fixtures/generator"
|
|
21
|
+
|
|
22
|
+
BENCH_ROOT = File.expand_path("..", __dir__)
|
|
23
|
+
|
|
24
|
+
module BenchHelper
|
|
25
|
+
# Generate all fixture scales (idempotent - reads from cache unless forced).
|
|
26
|
+
def self.prepare_fixtures(force: false)
|
|
27
|
+
BenchmarkFixtures.generate_all(force: force)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Build a configured I18n::Tasks::BaseTask context pointed at a fixture directory
|
|
31
|
+
# and yield it within the correct working directory.
|
|
32
|
+
#
|
|
33
|
+
# i18n-tasks resolves relative search paths against the current working directory
|
|
34
|
+
# at scan time, so the task must be used within Dir.chdir(fixture_dir).
|
|
35
|
+
#
|
|
36
|
+
# @param scale [:small, :medium, :large]
|
|
37
|
+
# @yieldparam task [I18n::Tasks::BaseTask]
|
|
38
|
+
def self.build_context(scale)
|
|
39
|
+
dir = BenchmarkFixtures.generate(scale)
|
|
40
|
+
config_file = File.join(dir, "config", "i18n-tasks.yml")
|
|
41
|
+
Dir.chdir(dir) do
|
|
42
|
+
yield I18n::Tasks::BaseTask.new(config_file: config_file)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Run a block with working directory set to the fixture dir.
|
|
47
|
+
# The context is created fresh each time so memoized caches don't carry over.
|
|
48
|
+
def self.in_fixture_dir(scale)
|
|
49
|
+
dir = BenchmarkFixtures.generate(scale)
|
|
50
|
+
Dir.chdir(dir) { yield }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Pre-warm the context so file-system caches are hot before timing.
|
|
54
|
+
def self.warm_context(context)
|
|
55
|
+
context.used_tree
|
|
56
|
+
context
|
|
57
|
+
rescue => e
|
|
58
|
+
warn "BenchHelper.warm_context failed: #{e.class}: #{e.message}"
|
|
59
|
+
context
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Print a section header.
|
|
63
|
+
def self.header(title)
|
|
64
|
+
puts
|
|
65
|
+
puts "=" * 60
|
|
66
|
+
puts " #{title}"
|
|
67
|
+
puts "=" * 60
|
|
68
|
+
puts
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Save IPS results to a JSON file for baseline comparison.
|
|
72
|
+
#
|
|
73
|
+
# @param suite [Benchmark::IPS::Suite] the completed suite
|
|
74
|
+
# @param label [String] top-level key in the JSON object
|
|
75
|
+
# @param path [String] path to the results JSON file
|
|
76
|
+
def self.save_results(suite, label, path: File.join(BENCH_ROOT, "benchmarks", "results", "baseline.json"))
|
|
77
|
+
require "json"
|
|
78
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
79
|
+
existing = File.exist?(path) ? JSON.parse(File.read(path)) : {}
|
|
80
|
+
existing["_env"] = environment_info
|
|
81
|
+
existing[label] ||= {}
|
|
82
|
+
suite.entries.each do |entry|
|
|
83
|
+
existing[label][entry.label] = {
|
|
84
|
+
ips: entry.stats.central_tendency.round(2),
|
|
85
|
+
ips_sd: entry.stats.error.round(2),
|
|
86
|
+
microseconds: (1_000_000.0 / entry.stats.central_tendency).round(2)
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
File.write(path, JSON.pretty_generate(existing))
|
|
90
|
+
puts "Results saved to #{path}"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Compare an IPS suite against a stored baseline and warn about regressions.
|
|
94
|
+
#
|
|
95
|
+
# @param suite [Benchmark::IPS::Suite]
|
|
96
|
+
# @param label [String]
|
|
97
|
+
# @param threshold [Float] fraction below baseline that counts as a regression (default 0.10 = 10%)
|
|
98
|
+
# @param path [String] path to the baseline JSON file
|
|
99
|
+
# @return [Boolean] true if no regressions found
|
|
100
|
+
def self.compare_baseline(suite, label, threshold: 0.10,
|
|
101
|
+
path: File.join(BENCH_ROOT, "benchmarks", "results", "baseline.json"))
|
|
102
|
+
require "json"
|
|
103
|
+
return true unless File.exist?(path)
|
|
104
|
+
|
|
105
|
+
baseline = JSON.parse(File.read(path))
|
|
106
|
+
|
|
107
|
+
if (saved_env = baseline["_env"])
|
|
108
|
+
current_env = environment_info
|
|
109
|
+
mismatches = current_env.filter_map do |key, val|
|
|
110
|
+
"#{key}: baseline=#{saved_env[key].inspect} current=#{val.inspect}" if saved_env[key] != val
|
|
111
|
+
end
|
|
112
|
+
if mismatches.any?
|
|
113
|
+
warn "WARNING: baseline environment differs from current — comparison may be unreliable:\n" \
|
|
114
|
+
" #{mismatches.join("\n ")}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
section = baseline[label]
|
|
119
|
+
return true unless section
|
|
120
|
+
|
|
121
|
+
regressions = []
|
|
122
|
+
suite.entries.each do |entry|
|
|
123
|
+
baseline_entry = section[entry.label]
|
|
124
|
+
next unless baseline_entry
|
|
125
|
+
|
|
126
|
+
baseline_ips = baseline_entry["ips"].to_f
|
|
127
|
+
next if baseline_ips.zero?
|
|
128
|
+
|
|
129
|
+
current_ips = entry.stats.central_tendency
|
|
130
|
+
ratio = current_ips / baseline_ips
|
|
131
|
+
if ratio < (1 - threshold)
|
|
132
|
+
regressions << format(" %-55s %.1fx slower (%.0f vs %.0f ips)",
|
|
133
|
+
entry.label, 1.0 / ratio, current_ips, baseline_ips)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
if regressions.any?
|
|
138
|
+
puts
|
|
139
|
+
puts "WARNING: REGRESSIONS DETECTED (>#{(threshold * 100).to_i}% slowdown):"
|
|
140
|
+
regressions.each { |r| puts r }
|
|
141
|
+
puts
|
|
142
|
+
false
|
|
143
|
+
else
|
|
144
|
+
true
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Returns a hash of relevant environment metadata for baseline comparison.
|
|
149
|
+
def self.environment_info
|
|
150
|
+
{
|
|
151
|
+
"ruby_version" => RUBY_VERSION,
|
|
152
|
+
"ruby_platform" => RUBY_PLATFORM,
|
|
153
|
+
"ruby_engine" => RUBY_ENGINE
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Benchmarks for YAML data loading and writing (the I/O layer).
|
|
4
|
+
#
|
|
5
|
+
# Measures the cost of:
|
|
6
|
+
# - Parsing YAML locale files into Ruby hashes
|
|
7
|
+
# - Dumping Ruby hashes back to YAML strings
|
|
8
|
+
# - Building Siblings trees from parsed YAML hashes
|
|
9
|
+
# - The full data[locale] load path used by BaseTask
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bundle exec ruby benchmarks/data_bench.rb
|
|
13
|
+
# bundle exec ruby benchmarks/data_bench.rb --save
|
|
14
|
+
# bundle exec ruby benchmarks/data_bench.rb --compare
|
|
15
|
+
# bundle exec ruby benchmarks/data_bench.rb --memory # include memory profiles
|
|
16
|
+
|
|
17
|
+
require_relative "bench_helper"
|
|
18
|
+
|
|
19
|
+
require "yaml"
|
|
20
|
+
require "i18n/tasks/data/adapter/yaml_adapter"
|
|
21
|
+
require "i18n/tasks/data/tree/siblings"
|
|
22
|
+
|
|
23
|
+
SAVE_RESULTS = ARGV.include?("--save")
|
|
24
|
+
COMPARE_RESULTS = ARGV.include?("--compare")
|
|
25
|
+
MEMORY_PROFILE = ARGV.include?("--memory")
|
|
26
|
+
|
|
27
|
+
Siblings = I18n::Tasks::Data::Tree::Siblings
|
|
28
|
+
YamlAdapter = I18n::Tasks::Data::Adapter::YamlAdapter
|
|
29
|
+
|
|
30
|
+
# Build YAML strings of various sizes in memory (no disk I/O during benchmarks)
|
|
31
|
+
def yaml_string_for_scale(scale)
|
|
32
|
+
dir = BenchmarkFixtures.generate(scale)
|
|
33
|
+
File.read(File.join(dir, "config", "locales", "en.yml"))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
SMALL_YAML = yaml_string_for_scale(:small).freeze
|
|
37
|
+
MEDIUM_YAML = yaml_string_for_scale(:medium).freeze
|
|
38
|
+
LARGE_YAML = yaml_string_for_scale(:large).freeze
|
|
39
|
+
|
|
40
|
+
SMALL_HASH = YamlAdapter.parse(SMALL_YAML, nil).freeze
|
|
41
|
+
MEDIUM_HASH = YamlAdapter.parse(MEDIUM_YAML, nil).freeze
|
|
42
|
+
LARGE_HASH = YamlAdapter.parse(LARGE_YAML, nil).freeze
|
|
43
|
+
|
|
44
|
+
SMALL_TREE = Siblings.from_nested_hash(SMALL_HASH).freeze
|
|
45
|
+
MEDIUM_TREE = Siblings.from_nested_hash(MEDIUM_HASH).freeze
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# YAML parse benchmarks
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
BenchHelper.header("YAML parse (string → hash)")
|
|
52
|
+
|
|
53
|
+
parse_suite = Benchmark.ips do |x|
|
|
54
|
+
x.config(warmup: 3, time: 10)
|
|
55
|
+
|
|
56
|
+
x.report("YAML parse small (~#{SMALL_YAML.size / 1024}KB)") do
|
|
57
|
+
YamlAdapter.parse(SMALL_YAML, nil)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
x.report("YAML parse medium (~#{MEDIUM_YAML.size / 1024}KB)") do
|
|
61
|
+
YamlAdapter.parse(MEDIUM_YAML, nil)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
x.report("YAML parse large (~#{LARGE_YAML.size / 1024}KB)") do
|
|
65
|
+
YamlAdapter.parse(LARGE_YAML, nil)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
x.compare!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# YAML dump benchmarks
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
BenchHelper.header("YAML dump (hash → string)")
|
|
76
|
+
|
|
77
|
+
dump_suite = Benchmark.ips do |x|
|
|
78
|
+
x.config(warmup: 3, time: 10)
|
|
79
|
+
|
|
80
|
+
x.report("YAML dump small tree") do
|
|
81
|
+
YamlAdapter.dump(SMALL_HASH, nil)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
x.report("YAML dump medium tree") do
|
|
85
|
+
YamlAdapter.dump(MEDIUM_HASH, nil)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
x.report("YAML dump large tree") do
|
|
89
|
+
YamlAdapter.dump(LARGE_HASH, nil)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
x.compare!
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Hash → Siblings tree construction
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
BenchHelper.header("Hash → Siblings tree construction")
|
|
100
|
+
|
|
101
|
+
load_suite = Benchmark.ips do |x|
|
|
102
|
+
x.config(warmup: 3, time: 10)
|
|
103
|
+
|
|
104
|
+
x.report("from_nested_hash small") do
|
|
105
|
+
Siblings.from_nested_hash(SMALL_HASH)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
x.report("from_nested_hash medium") do
|
|
109
|
+
Siblings.from_nested_hash(MEDIUM_HASH)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
x.report("from_nested_hash large") do
|
|
113
|
+
Siblings.from_nested_hash(LARGE_HASH)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
x.compare!
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# Full data load via BaseTask
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
BenchHelper.header("Full data[locale] load via BaseTask")
|
|
124
|
+
|
|
125
|
+
data_load_suite = Benchmark.ips do |x|
|
|
126
|
+
x.config(warmup: 2, time: 10)
|
|
127
|
+
|
|
128
|
+
[:small, :medium].each do |scale|
|
|
129
|
+
dir = BenchmarkFixtures.generate(scale)
|
|
130
|
+
|
|
131
|
+
x.report("data['en'] load (#{scale})") do
|
|
132
|
+
Dir.chdir(dir) do
|
|
133
|
+
task = I18n::Tasks::BaseTask.new(config_file: File.join(dir, "config", "i18n-tasks.yml"))
|
|
134
|
+
task.data["en"]
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
x.compare!
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
# Memory profiles (opt-in via --memory)
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
if MEMORY_PROFILE
|
|
147
|
+
BenchHelper.header("Memory — YAML parse + tree build (medium)")
|
|
148
|
+
MemoryProfiler.report do
|
|
149
|
+
hash = YamlAdapter.parse(MEDIUM_YAML, nil)
|
|
150
|
+
Siblings.from_nested_hash(hash)
|
|
151
|
+
end.pretty_print(scale_bytes: true, detailed_report: false)
|
|
152
|
+
|
|
153
|
+
BenchHelper.header("Memory — YAML dump (medium)")
|
|
154
|
+
MemoryProfiler.report { YamlAdapter.dump(MEDIUM_HASH, nil) }
|
|
155
|
+
.pretty_print(scale_bytes: true, detailed_report: false)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
passed = true
|
|
159
|
+
if SAVE_RESULTS
|
|
160
|
+
BenchHelper.save_results(parse_suite, "data/yaml_parse")
|
|
161
|
+
BenchHelper.save_results(dump_suite, "data/yaml_dump")
|
|
162
|
+
BenchHelper.save_results(load_suite, "data/tree_load")
|
|
163
|
+
BenchHelper.save_results(data_load_suite, "data/full_load")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if COMPARE_RESULTS
|
|
167
|
+
passed = false unless BenchHelper.compare_baseline(parse_suite, "data/yaml_parse")
|
|
168
|
+
passed = false unless BenchHelper.compare_baseline(dump_suite, "data/yaml_dump")
|
|
169
|
+
passed = false unless BenchHelper.compare_baseline(load_suite, "data/tree_load")
|
|
170
|
+
passed = false unless BenchHelper.compare_baseline(data_load_suite, "data/full_load")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
exit(1) if COMPARE_RESULTS && !passed
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# End-to-end benchmarks for the full i18n-tasks pipeline.
|
|
4
|
+
#
|
|
5
|
+
# These measure the complete workflows that users actually run:
|
|
6
|
+
# - missing_keys: find translations present in base locale but missing elsewhere
|
|
7
|
+
# - unused_keys: find keys defined in locale files but not used in source
|
|
8
|
+
# - used_tree: scan all source files and build the usage tree
|
|
9
|
+
#
|
|
10
|
+
# These are the most important benchmarks for catching regressions — any change
|
|
11
|
+
# to the scanner, data layer, or tree operations will show up here.
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# bundle exec ruby benchmarks/end_to_end_bench.rb
|
|
15
|
+
# bundle exec ruby benchmarks/end_to_end_bench.rb --save # save as new baseline
|
|
16
|
+
# bundle exec ruby benchmarks/end_to_end_bench.rb --compare # compare against baseline
|
|
17
|
+
# bundle exec ruby benchmarks/end_to_end_bench.rb --memory # include memory profiles
|
|
18
|
+
|
|
19
|
+
require_relative "bench_helper"
|
|
20
|
+
|
|
21
|
+
SAVE_RESULTS = ARGV.include?("--save")
|
|
22
|
+
COMPARE_RESULTS = ARGV.include?("--compare")
|
|
23
|
+
MEMORY_PROFILE = ARGV.include?("--memory")
|
|
24
|
+
|
|
25
|
+
all_suites = []
|
|
26
|
+
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
# Benchmark each pipeline operation across all scales
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
[:small, :medium, :large].each do |scale|
|
|
32
|
+
dir = BenchmarkFixtures.generate(scale)
|
|
33
|
+
|
|
34
|
+
BenchHelper.header("End-to-end pipeline — #{scale} fixture")
|
|
35
|
+
|
|
36
|
+
suite = Benchmark.ips do |x|
|
|
37
|
+
x.config(warmup: 2, time: 15)
|
|
38
|
+
|
|
39
|
+
x.report("used_tree (#{scale})") do
|
|
40
|
+
Dir.chdir(dir) do
|
|
41
|
+
task = I18n::Tasks::BaseTask.new(config_file: File.join(dir, "config", "i18n-tasks.yml"))
|
|
42
|
+
task.used_tree
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
x.report("missing_keys (#{scale})") do
|
|
47
|
+
Dir.chdir(dir) do
|
|
48
|
+
task = I18n::Tasks::BaseTask.new(config_file: File.join(dir, "config", "i18n-tasks.yml"))
|
|
49
|
+
task.missing_keys
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
x.report("unused_keys (#{scale})") do
|
|
54
|
+
Dir.chdir(dir) do
|
|
55
|
+
task = I18n::Tasks::BaseTask.new(config_file: File.join(dir, "config", "i18n-tasks.yml"))
|
|
56
|
+
task.unused_keys
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
x.compare!
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
all_suites << [suite, "end_to_end/#{scale}"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
# Scanner backend comparison: parser AST vs Prism on medium fixture
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
BenchHelper.header("Scanner backend: parser AST vs Prism (medium used_tree)")
|
|
71
|
+
|
|
72
|
+
dir = BenchmarkFixtures.generate(:medium)
|
|
73
|
+
|
|
74
|
+
backend_suite = Benchmark.ips do |x|
|
|
75
|
+
x.config(warmup: 2, time: 15)
|
|
76
|
+
|
|
77
|
+
x.report("used_tree — parser AST (medium)") do
|
|
78
|
+
Dir.chdir(dir) do
|
|
79
|
+
task = I18n::Tasks::BaseTask.new(
|
|
80
|
+
config_file: File.join(dir, "config", "i18n-tasks.yml"),
|
|
81
|
+
search: {scanners: [["::I18n::Tasks::Scanners::RubyScanner", {only: %w[*.rb], prism: nil}],
|
|
82
|
+
["::I18n::Tasks::Scanners::ErbAstScanner", {only: %w[*.erb]}]]}
|
|
83
|
+
)
|
|
84
|
+
task.used_tree
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
x.report("used_tree — prism (medium)") do
|
|
89
|
+
Dir.chdir(dir) do
|
|
90
|
+
task = I18n::Tasks::BaseTask.new(
|
|
91
|
+
config_file: File.join(dir, "config", "i18n-tasks.yml"),
|
|
92
|
+
search: {scanners: [["::I18n::Tasks::Scanners::RubyScanner", {only: %w[*.rb], prism: "rails"}],
|
|
93
|
+
["::I18n::Tasks::Scanners::ErbAstScanner", {only: %w[*.erb]}]]}
|
|
94
|
+
)
|
|
95
|
+
task.used_tree
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
x.compare!
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
all_suites << [backend_suite, "end_to_end/backend_comparison"]
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# Memory profiling on the most realistic scale (medium, opt-in via --memory)
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
if MEMORY_PROFILE
|
|
109
|
+
dir = BenchmarkFixtures.generate(:medium)
|
|
110
|
+
|
|
111
|
+
BenchHelper.header("Memory profile — missing_keys (medium)")
|
|
112
|
+
MemoryProfiler.report do
|
|
113
|
+
Dir.chdir(dir) do
|
|
114
|
+
task = I18n::Tasks::BaseTask.new(config_file: File.join(dir, "config", "i18n-tasks.yml"))
|
|
115
|
+
task.missing_keys
|
|
116
|
+
end
|
|
117
|
+
end.pretty_print(scale_bytes: true, detailed_report: false)
|
|
118
|
+
|
|
119
|
+
BenchHelper.header("Memory profile — unused_keys (medium)")
|
|
120
|
+
MemoryProfiler.report do
|
|
121
|
+
Dir.chdir(dir) do
|
|
122
|
+
task = I18n::Tasks::BaseTask.new(config_file: File.join(dir, "config", "i18n-tasks.yml"))
|
|
123
|
+
task.unused_keys
|
|
124
|
+
end
|
|
125
|
+
end.pretty_print(scale_bytes: true, detailed_report: false)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# Save / compare results
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
passed = true
|
|
133
|
+
all_suites.each do |(suite, label)|
|
|
134
|
+
BenchHelper.save_results(suite, label) if SAVE_RESULTS
|
|
135
|
+
passed = false if COMPARE_RESULTS && !BenchHelper.compare_baseline(suite, label)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
exit(1) if COMPARE_RESULTS && !passed
|