mega-sharp-tool 0.0.1
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 +7 -0
- data/mega-sharp-tool.gemspec +12 -0
- data/minitest-6.0.6/History.rdoc +1860 -0
- data/minitest-6.0.6/Manifest.txt +41 -0
- data/minitest-6.0.6/README.rdoc +763 -0
- data/minitest-6.0.6/Rakefile +89 -0
- data/minitest-6.0.6/bin/minitest +5 -0
- data/minitest-6.0.6/design_rationale.rb +54 -0
- data/minitest-6.0.6/lib/hoe/minitest.rb +30 -0
- data/minitest-6.0.6/lib/minitest/assertions.rb +821 -0
- data/minitest-6.0.6/lib/minitest/autorun.rb +5 -0
- data/minitest-6.0.6/lib/minitest/benchmark.rb +452 -0
- data/minitest-6.0.6/lib/minitest/bisect.rb +304 -0
- data/minitest-6.0.6/lib/minitest/complete.rb +56 -0
- data/minitest-6.0.6/lib/minitest/compress.rb +94 -0
- data/minitest-6.0.6/lib/minitest/error_on_warning.rb +11 -0
- data/minitest-6.0.6/lib/minitest/expectations.rb +321 -0
- data/minitest-6.0.6/lib/minitest/find_minimal_combination.rb +127 -0
- data/minitest-6.0.6/lib/minitest/hell.rb +11 -0
- data/minitest-6.0.6/lib/minitest/manual_plugins.rb +4 -0
- data/minitest-6.0.6/lib/minitest/parallel.rb +72 -0
- data/minitest-6.0.6/lib/minitest/path_expander.rb +432 -0
- data/minitest-6.0.6/lib/minitest/pride.rb +4 -0
- data/minitest-6.0.6/lib/minitest/pride_plugin.rb +135 -0
- data/minitest-6.0.6/lib/minitest/server.rb +49 -0
- data/minitest-6.0.6/lib/minitest/server_plugin.rb +88 -0
- data/minitest-6.0.6/lib/minitest/spec.rb +324 -0
- data/minitest-6.0.6/lib/minitest/sprint.rb +105 -0
- data/minitest-6.0.6/lib/minitest/sprint_plugin.rb +39 -0
- data/minitest-6.0.6/lib/minitest/test.rb +232 -0
- data/minitest-6.0.6/lib/minitest/test_task.rb +331 -0
- data/minitest-6.0.6/lib/minitest.rb +1232 -0
- data/minitest-6.0.6/test/minitest/metametameta.rb +150 -0
- data/minitest-6.0.6/test/minitest/test_bisect.rb +249 -0
- data/minitest-6.0.6/test/minitest/test_find_minimal_combination.rb +138 -0
- data/minitest-6.0.6/test/minitest/test_minitest_assertions.rb +1729 -0
- data/minitest-6.0.6/test/minitest/test_minitest_benchmark.rb +151 -0
- data/minitest-6.0.6/test/minitest/test_minitest_reporter.rb +437 -0
- data/minitest-6.0.6/test/minitest/test_minitest_spec.rb +1095 -0
- data/minitest-6.0.6/test/minitest/test_minitest_test.rb +1295 -0
- data/minitest-6.0.6/test/minitest/test_minitest_test_task.rb +57 -0
- data/minitest-6.0.6/test/minitest/test_path_expander.rb +229 -0
- data/minitest-6.0.6/test/minitest/test_server.rb +146 -0
- metadata +83 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
require_relative "find_minimal_combination"
|
|
2
|
+
require_relative "server"
|
|
3
|
+
require "shellwords"
|
|
4
|
+
require "rbconfig"
|
|
5
|
+
require_relative "path_expander" # this is gonna break some shit?
|
|
6
|
+
|
|
7
|
+
module Minitest; end # :nodoc:
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Minitest::Bisect helps you isolate and debug random test failures.
|
|
11
|
+
|
|
12
|
+
class Minitest::Bisect
|
|
13
|
+
VERSION = "1.8.0" # :nodoc:
|
|
14
|
+
|
|
15
|
+
class PathExpander < Minitest::VendoredPathExpander # :nodoc:
|
|
16
|
+
TEST_GLOB = "**/{test_*,*_test,spec_*,*_spec}.rb" # :nodoc:
|
|
17
|
+
|
|
18
|
+
attr_accessor :rb_flags
|
|
19
|
+
|
|
20
|
+
def initialize args = ARGV # :nodoc:
|
|
21
|
+
super args, TEST_GLOB, "test"
|
|
22
|
+
self.rb_flags = %w[]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Overrides PathExpander#process_flags to filter out ruby flags
|
|
27
|
+
# from minitest flags. Only supports -I<paths>, -d, and -w for
|
|
28
|
+
# ruby.
|
|
29
|
+
|
|
30
|
+
def process_flags flags
|
|
31
|
+
flags.reject { |flag| # all hits are truthy, so this works out well
|
|
32
|
+
case flag
|
|
33
|
+
when /^-I(.*)/ then
|
|
34
|
+
rb_flags << flag
|
|
35
|
+
when /^-d/ then
|
|
36
|
+
rb_flags << flag
|
|
37
|
+
when /^-w/ then
|
|
38
|
+
rb_flags << flag
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
mtbv = ENV["MTB_VERBOSE"].to_i
|
|
47
|
+
SHH = case # :nodoc:
|
|
48
|
+
when mtbv == 1 then " > /dev/null"
|
|
49
|
+
when mtbv >= 2 then nil
|
|
50
|
+
else " > /dev/null 2>&1"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Borrowed from rake
|
|
54
|
+
RUBY = ENV['RUBY'] ||
|
|
55
|
+
File.join(RbConfig::CONFIG['bindir'],
|
|
56
|
+
RbConfig::CONFIG['ruby_install_name'] +
|
|
57
|
+
RbConfig::CONFIG['EXEEXT']).sub(/.*\s.*/m, '"\&"')
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# True if this run has seen a failure.
|
|
61
|
+
|
|
62
|
+
attr_accessor :tainted
|
|
63
|
+
alias :tainted? :tainted
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Failures seen in this run. Shape:
|
|
67
|
+
#
|
|
68
|
+
# {"file.rb"=>{"Class"=>["test_method1", "test_method2"] ...} ...}
|
|
69
|
+
|
|
70
|
+
attr_accessor :failures
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# An array of tests seen so far. NOT cleared by #reset.
|
|
74
|
+
|
|
75
|
+
attr_accessor :culprits
|
|
76
|
+
|
|
77
|
+
attr_accessor :seen_bad # :nodoc:
|
|
78
|
+
|
|
79
|
+
##
|
|
80
|
+
# Top-level runner. Instantiate and call +run+, handling exceptions.
|
|
81
|
+
|
|
82
|
+
def self.run files
|
|
83
|
+
new.run files
|
|
84
|
+
rescue => e
|
|
85
|
+
warn e.message
|
|
86
|
+
warn "Try running with MTB_VERBOSE=2 to verify."
|
|
87
|
+
exit 1
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Instantiate a new Bisect.
|
|
92
|
+
|
|
93
|
+
def initialize
|
|
94
|
+
self.culprits = []
|
|
95
|
+
self.failures = Hash.new { |h, k| h[k] = Hash.new { |h2, k2| h2[k2] = [] } }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# Reset per-bisect-run variables.
|
|
100
|
+
|
|
101
|
+
def reset
|
|
102
|
+
self.seen_bad = false
|
|
103
|
+
self.tainted = false
|
|
104
|
+
failures.clear
|
|
105
|
+
# not clearing culprits on purpose
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Instance-level runner. Handles Minitest::Server, argument
|
|
110
|
+
# processing, and invoking +bisect_methods+.
|
|
111
|
+
|
|
112
|
+
def run args
|
|
113
|
+
Minitest::Server.run self
|
|
114
|
+
|
|
115
|
+
cmd = nil
|
|
116
|
+
|
|
117
|
+
mt_flags = args.dup
|
|
118
|
+
expander = Minitest::Bisect::PathExpander.new mt_flags
|
|
119
|
+
|
|
120
|
+
files = expander.process.to_a
|
|
121
|
+
rb_flags = expander.rb_flags
|
|
122
|
+
mt_flags += ["--server", $$.to_s]
|
|
123
|
+
|
|
124
|
+
cmd = bisect_methods files, rb_flags, mt_flags
|
|
125
|
+
|
|
126
|
+
puts "Final reproduction:"
|
|
127
|
+
puts
|
|
128
|
+
|
|
129
|
+
system({"MINITEST_SERVER" => "1"}, cmd.sub(/--server \d+/, "", ))
|
|
130
|
+
ensure
|
|
131
|
+
Minitest::Server.stop
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Normal: find "what is the minimal combination of tests to run to
|
|
136
|
+
# make X fail?"
|
|
137
|
+
#
|
|
138
|
+
# Run with: minitest_bisect ... --seed=N
|
|
139
|
+
#
|
|
140
|
+
# 1. Verify the failure running normally with the seed.
|
|
141
|
+
# 2. If no failure, punt.
|
|
142
|
+
# 3. If no passing tests before failure, punt. (No culprits == no debug)
|
|
143
|
+
# 4. Verify the failure doesn't fail in isolation.
|
|
144
|
+
# 5. If it still fails by itself, warn that it might not be an ordering
|
|
145
|
+
# issue.
|
|
146
|
+
# 6. Cull all tests after the failure, they're not involved.
|
|
147
|
+
# 7. Bisect the culprits + bad until you find a minimal combo that fails.
|
|
148
|
+
# 8. Display minimal combo by running one last time.
|
|
149
|
+
#
|
|
150
|
+
# Inverted: find "what is the minimal combination of tests to run to
|
|
151
|
+
# make this test pass?"
|
|
152
|
+
#
|
|
153
|
+
# Run with: minitest_bisect ... --seed=N -n="/failing_test_name_regexp/"
|
|
154
|
+
#
|
|
155
|
+
# 1. Verify the failure by running normally w/ the seed and -n=/.../
|
|
156
|
+
# 2. If no failure, punt.
|
|
157
|
+
# 3. Verify the passing case by running everything.
|
|
158
|
+
# 4. If failure, punt. This is not a false positive.
|
|
159
|
+
# 5. Cull all tests after the bad test from #1, they're not involved.
|
|
160
|
+
# 6. Bisect the culprits + bad until you find a minimal combo that passes.
|
|
161
|
+
# 7. Display minimal combo by running one last time.
|
|
162
|
+
|
|
163
|
+
def bisect_methods files, rb_flags, mt_flags
|
|
164
|
+
bad_names, mt_flags = mt_flags.partition { |s| s =~ /^(?:-n|--name)/ }
|
|
165
|
+
normal = bad_names.empty?
|
|
166
|
+
inverted = !normal
|
|
167
|
+
|
|
168
|
+
if inverted then
|
|
169
|
+
time_it "reproducing w/ scoped failure (inverted run!)...", build_methods_cmd(build_files_cmd(files, rb_flags, mt_flags + bad_names))
|
|
170
|
+
raise "No failures. Probably not a false positive. Aborting." if failures.empty?
|
|
171
|
+
bad = map_failures
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
cmd = build_files_cmd(files, rb_flags, mt_flags)
|
|
175
|
+
|
|
176
|
+
msg = normal ? "reproducing..." : "reproducing false positive..."
|
|
177
|
+
time_it msg, build_methods_cmd(cmd)
|
|
178
|
+
|
|
179
|
+
if normal then
|
|
180
|
+
raise "Reproduction run passed? Aborting." unless tainted?
|
|
181
|
+
raise "Verification failed. No culprits? Aborting." if culprits.empty? && seen_bad
|
|
182
|
+
else
|
|
183
|
+
raise "Reproduction failed? Not false positive. Aborting." if tainted?
|
|
184
|
+
raise "Verification failed. No culprits? Aborting." if culprits.empty? || seen_bad
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
if normal then
|
|
188
|
+
bad = map_failures
|
|
189
|
+
|
|
190
|
+
time_it "verifying...", build_methods_cmd(cmd, [], bad)
|
|
191
|
+
|
|
192
|
+
new_bad = map_failures
|
|
193
|
+
|
|
194
|
+
if bad == new_bad then
|
|
195
|
+
warn "Tests fail by themselves. This may not be an ordering issue."
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
idx = culprits.index bad.first
|
|
200
|
+
self.culprits = culprits.take idx+1 if idx # cull tests after bad
|
|
201
|
+
|
|
202
|
+
# culprits populated by initial reproduction via minitest/server
|
|
203
|
+
found, count = culprits.find_minimal_combination_and_count do |test|
|
|
204
|
+
prompt = "# of culprit methods: #{test.size}"
|
|
205
|
+
|
|
206
|
+
time_it prompt, build_methods_cmd(cmd, test, bad)
|
|
207
|
+
|
|
208
|
+
normal == tainted? # either normal and failed, or inverse and passed
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
puts
|
|
212
|
+
puts "Minimal methods found in #{count} steps:"
|
|
213
|
+
puts
|
|
214
|
+
puts "Culprit methods: %p" % [found + bad]
|
|
215
|
+
puts
|
|
216
|
+
cmd = build_methods_cmd cmd, found, bad
|
|
217
|
+
puts cmd.sub(/--server \d+/, "")
|
|
218
|
+
puts
|
|
219
|
+
cmd
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def time_it prompt, cmd # :nodoc:
|
|
223
|
+
print prompt
|
|
224
|
+
t0 = Time.now
|
|
225
|
+
system({"MINITEST_SERVER" => "1"}, "#{cmd} #{SHH}")
|
|
226
|
+
puts " in %.2f sec" % (Time.now - t0)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def map_failures # :nodoc:
|
|
230
|
+
# from: {"file.rb"=>{"Class"=>["test_method1", "test_method2"]}}
|
|
231
|
+
# to: ["Class#test_method1", "Class#test_method2"]
|
|
232
|
+
failures.values.map { |h|
|
|
233
|
+
h.map { |k,vs| vs.map { |v| "#{k}##{v}" } }
|
|
234
|
+
}.flatten.sort
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def build_files_cmd culprits, rb, mt, cmd:$0 # :nodoc:
|
|
238
|
+
([cmd] + rb + culprits + mt).shelljoin
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build_methods_cmd cmd, culprits = [], bad = nil # :nodoc:
|
|
242
|
+
reset
|
|
243
|
+
|
|
244
|
+
if bad then
|
|
245
|
+
re = build_re culprits + bad
|
|
246
|
+
|
|
247
|
+
cmd += " -n \"#{re}\"" if bad
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
if ENV["MTB_VERBOSE"].to_i >= 1 then
|
|
251
|
+
puts
|
|
252
|
+
puts cmd
|
|
253
|
+
puts
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
cmd
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def build_re bad # :nodoc:
|
|
260
|
+
re = []
|
|
261
|
+
|
|
262
|
+
# bad by class, you perv
|
|
263
|
+
bbc = bad.map { |s| s.split(/#/, 2) }.group_by(&:first)
|
|
264
|
+
|
|
265
|
+
bbc.each do |klass, methods|
|
|
266
|
+
methods = methods.map(&:last).flatten.uniq.map { |method|
|
|
267
|
+
re_escape method
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
methods = methods.join "|"
|
|
271
|
+
re << /#{re_escape klass}#(?:#{methods})/.to_s[7..-2] # (?-mix:...)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
re = re.join("|").to_s.gsub(/-mix/, "")
|
|
275
|
+
|
|
276
|
+
"/^(?:#{re})$/"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def re_escape str # :nodoc:
|
|
280
|
+
str.gsub(/([`'"!?&\[\]\(\)\{\}\|\+])/, '\\\\\1')
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
############################################################
|
|
284
|
+
# Server Methods:
|
|
285
|
+
|
|
286
|
+
def minitest_start # :nodoc:
|
|
287
|
+
self.failures.clear
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def minitest_result file, klass, method, fails, assertions, time # :nodoc:
|
|
291
|
+
fails.reject! { |fail| Minitest::Skip === fail }
|
|
292
|
+
|
|
293
|
+
if fails.empty? then
|
|
294
|
+
culprits << "#{klass}##{method}" unless seen_bad # UGH
|
|
295
|
+
else
|
|
296
|
+
self.seen_bad = true
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
return if fails.empty?
|
|
300
|
+
|
|
301
|
+
self.tainted = true
|
|
302
|
+
self.failures[file][klass] << method
|
|
303
|
+
end
|
|
304
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env -S ruby
|
|
2
|
+
|
|
3
|
+
# :stopdoc:
|
|
4
|
+
|
|
5
|
+
require "optparse"
|
|
6
|
+
require "shellwords"
|
|
7
|
+
|
|
8
|
+
# complete -o bashdefault -f -C 'ruby lib/minitest/complete.rb' minitest
|
|
9
|
+
# using eg:
|
|
10
|
+
# COMP_LINE="blah test/test_file.rb -n test_pattern"
|
|
11
|
+
# or test directly with:
|
|
12
|
+
# ./lib/minitest/complete.rb test/test_file.rb -n test_pattern
|
|
13
|
+
|
|
14
|
+
argv = Shellwords.split ENV["COMP_LINE"] || ARGV.join(" ")
|
|
15
|
+
comp_re = nil
|
|
16
|
+
|
|
17
|
+
begin
|
|
18
|
+
OptionParser.new do |opts|
|
|
19
|
+
# part of my unofficial embedded gem "makeoptparseworkwell"
|
|
20
|
+
def opts.topdict(name) = (name.length > 1 ? top.long : top.short)
|
|
21
|
+
def opts.alias(from, to) = (dict = topdict(from) ; dict[to] = dict[from])
|
|
22
|
+
|
|
23
|
+
opts.on "-n", "--name [METHOD]", "minitest option" do |m|
|
|
24
|
+
comp_re = Regexp.new m
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
opts.alias "name", "include"
|
|
28
|
+
opts.alias "name", "exclude"
|
|
29
|
+
opts.alias "n", "i"
|
|
30
|
+
opts.alias "n", "e"
|
|
31
|
+
opts.alias "n", "x"
|
|
32
|
+
end.parse! argv
|
|
33
|
+
rescue
|
|
34
|
+
retry # ignore options passed to Ruby
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
path = argv.find_all { |f| File.file? f }.last
|
|
38
|
+
|
|
39
|
+
exit unless comp_re && path
|
|
40
|
+
|
|
41
|
+
require "prism"
|
|
42
|
+
|
|
43
|
+
names, queue = [], [Prism.parse_file(path).value]
|
|
44
|
+
|
|
45
|
+
while node = queue.shift do
|
|
46
|
+
if node.type == :def_node then
|
|
47
|
+
name = node.name
|
|
48
|
+
names << name if name =~ comp_re
|
|
49
|
+
else
|
|
50
|
+
queue.concat node.compact_child_nodes # no need to process def body
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
puts names.sort
|
|
55
|
+
|
|
56
|
+
# :startdoc:
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
module Minitest
|
|
2
|
+
##
|
|
3
|
+
# Compresses backtraces.
|
|
4
|
+
|
|
5
|
+
module Compress
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Takes a backtrace (array of strings) and compresses repeating
|
|
9
|
+
# cycles in it to make it more readable.
|
|
10
|
+
|
|
11
|
+
def compress orig
|
|
12
|
+
ary = orig
|
|
13
|
+
|
|
14
|
+
eswo = ->(a, n, off) { # each_slice_with_offset
|
|
15
|
+
if off.zero? then
|
|
16
|
+
a.each_slice n
|
|
17
|
+
else
|
|
18
|
+
# [ ...off... [...n...] [...n...] ... ]
|
|
19
|
+
front, back = a.take(off), a.drop(off)
|
|
20
|
+
[front].chain back.each_slice n
|
|
21
|
+
end
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
3.times do # maybe don't use loop do here?
|
|
25
|
+
index = ary # [ a b c b c b c d ]
|
|
26
|
+
.size
|
|
27
|
+
.times # 0...size
|
|
28
|
+
.group_by { |i| ary[i] } # { a: [0] b: [1 3 5], c: [2 4 6], d: [7] }
|
|
29
|
+
|
|
30
|
+
order = index
|
|
31
|
+
.reject { |k, v| v.size == 1 } # { b: [1 3 5], c: [2 4 6] }
|
|
32
|
+
.sort_by { |k, a1| ### sort by max dist + min offset
|
|
33
|
+
d = a1.each_cons(2).sum { |a2, b| b-a2 }
|
|
34
|
+
[-d, a1.first]
|
|
35
|
+
} # b: [1 3 5] c: [2 4 6]
|
|
36
|
+
|
|
37
|
+
ranges = order
|
|
38
|
+
.map { |k, a1| # [[1..2 3..4] [2..3 4..5]]
|
|
39
|
+
a1
|
|
40
|
+
.each_cons(2)
|
|
41
|
+
.map { |a2, b| a2..b-1 }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
big_ranges = ranges
|
|
45
|
+
.flat_map { |a| # [1..2 3..4 2..3 4..5]
|
|
46
|
+
a.sort_by { |r| [-r.size, r.first] }.first 5
|
|
47
|
+
}
|
|
48
|
+
.first(100)
|
|
49
|
+
|
|
50
|
+
culprits = big_ranges
|
|
51
|
+
.map { |r|
|
|
52
|
+
eswo[ary, r.size, r.begin] # [o1 s1 s1 s2 s2]
|
|
53
|
+
.chunk_while { |a, b| a == b } # [[o1] [s1 s1] [s2 s2]]
|
|
54
|
+
.map { |a| [a.size, a.first] } # [[1 o1] [2 s1] [2 s2]]
|
|
55
|
+
}
|
|
56
|
+
.select { |chunks|
|
|
57
|
+
chunks.any? { |a| a.first > 1 } # compressed anything?
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
min = culprits
|
|
61
|
+
.min_by { |a| a.flatten.size } # most compressed
|
|
62
|
+
|
|
63
|
+
break unless min
|
|
64
|
+
|
|
65
|
+
ary = min.flat_map { |(n, lines)|
|
|
66
|
+
if n > 1 then
|
|
67
|
+
[[n, compress(lines)]] # [o1 [2 s1] [2 s2]]
|
|
68
|
+
else
|
|
69
|
+
lines
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
format = ->(lines) {
|
|
75
|
+
lines.flat_map { |line|
|
|
76
|
+
case line
|
|
77
|
+
when Array then
|
|
78
|
+
n, lines = line
|
|
79
|
+
lines = format[lines]
|
|
80
|
+
[
|
|
81
|
+
" +->> #{n} cycles of #{lines.size} lines:",
|
|
82
|
+
*lines.map { |s| " | #{s}" },
|
|
83
|
+
" +-<<",
|
|
84
|
+
]
|
|
85
|
+
else
|
|
86
|
+
line
|
|
87
|
+
end
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
format[ary]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|