judges 0.60.3 → 0.60.4
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/Gemfile +4 -3
- data/Gemfile.lock +34 -30
- data/README.md +3 -3
- data/Rakefile +1 -1
- data/features/step_definitions/steps.rb +6 -6
- data/features/test.feature +2 -2
- data/fixtures/repeat/repeat.rb +0 -2
- data/judges.gemspec +20 -20
- data/lib/judges/ascii_loog.rb +1 -2
- data/lib/judges/commands/download.rb +5 -6
- data/lib/judges/commands/eval.rb +5 -6
- data/lib/judges/commands/import.rb +6 -6
- data/lib/judges/commands/inspect.rb +2 -2
- data/lib/judges/commands/join.rb +3 -3
- data/lib/judges/commands/print.rb +19 -19
- data/lib/judges/commands/pull.rb +15 -12
- data/lib/judges/commands/push.rb +5 -5
- data/lib/judges/commands/test.rb +115 -83
- data/lib/judges/commands/trim.rb +5 -5
- data/lib/judges/commands/update.rb +102 -74
- data/lib/judges/commands/upload.rb +8 -8
- data/lib/judges/impex.rb +6 -6
- data/lib/judges/judge.rb +14 -12
- data/lib/judges/judges.rb +30 -25
- data/lib/judges/options.rb +32 -41
- data/lib/judges/pretty_exception.rb +1 -1
- data/lib/judges/statistics.rb +5 -8
- data/lib/judges.rb +1 -1
- data/package-lock.json +106 -295
- data/package.json +2 -2
- metadata +1 -1
|
@@ -7,8 +7,8 @@ require 'backtrace'
|
|
|
7
7
|
require 'elapsed'
|
|
8
8
|
require 'factbase'
|
|
9
9
|
require 'factbase/churn'
|
|
10
|
-
require 'factbase/logged'
|
|
11
10
|
require 'factbase/fact_as_yaml'
|
|
11
|
+
require 'factbase/logged'
|
|
12
12
|
require 'logger'
|
|
13
13
|
require 'tago'
|
|
14
14
|
require 'timeout'
|
|
@@ -21,7 +21,7 @@ require_relative '../../judges/to_rel'
|
|
|
21
21
|
|
|
22
22
|
# The +update+ command.
|
|
23
23
|
#
|
|
24
|
-
# This class is instantiated by the +bin/
|
|
24
|
+
# This class is instantiated by the +bin/judges+ command line interface. You
|
|
25
25
|
# are not supposed to instantiate it yourself.
|
|
26
26
|
#
|
|
27
27
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
@@ -40,31 +40,13 @@ class Judges::Update
|
|
|
40
40
|
# @param [Array] args List of command line arguments
|
|
41
41
|
# @raise [RuntimeError] If not exactly two arguments provided or directory is missing
|
|
42
42
|
def run(opts, args)
|
|
43
|
-
raise 'Exactly two arguments required' unless args.size == 2
|
|
43
|
+
raise(ArgumentError, 'Exactly two arguments required') unless args.size == 2
|
|
44
44
|
dir = args[0]
|
|
45
|
-
raise "The directory is absent: #{dir.to_rel}" unless File.exist?(dir)
|
|
45
|
+
raise(StandardError, "The directory is absent: #{dir.to_rel}") unless File.exist?(dir)
|
|
46
46
|
impex = Judges::Impex.new(@loog, args[1])
|
|
47
47
|
fb = impex.import(strict: false)
|
|
48
48
|
fb = Factbase::Logged.new(fb, @loog) if opts['log']
|
|
49
|
-
options =
|
|
50
|
-
if options.lifetime && options.timeout && options.lifetime < options.timeout * 1.1
|
|
51
|
-
raise "The --timeout=#{options.timeout} must be at least 10 percent smaller than --lifetime=#{options.lifetime}"
|
|
52
|
-
end
|
|
53
|
-
options += Judges::Options.new(opts['option'])
|
|
54
|
-
if opts['options-file']
|
|
55
|
-
options += Judges::Options.new(
|
|
56
|
-
File.readlines(opts['options-file'])
|
|
57
|
-
.compact
|
|
58
|
-
.reject(&:empty?)
|
|
59
|
-
.map { |ln| ln.strip.split('=', 1).map(&:strip).join('=') }
|
|
60
|
-
)
|
|
61
|
-
@loog.debug("Options loaded from #{opts['options-file']}")
|
|
62
|
-
end
|
|
63
|
-
if options.empty?
|
|
64
|
-
@loog.debug('No options provided by the --option flag')
|
|
65
|
-
else
|
|
66
|
-
@loog.debug("The following options provided:\n\t#{options.to_s.gsub("\n", "\n\t")}")
|
|
67
|
-
end
|
|
49
|
+
options = build_options(opts)
|
|
68
50
|
judges = Judges::Judges.new(
|
|
69
51
|
dir, opts['lib'], @loog,
|
|
70
52
|
epoch: @epoch, shuffle: opts['shuffle'], boost: opts['boost'],
|
|
@@ -77,7 +59,7 @@ class Judges::Update
|
|
|
77
59
|
end
|
|
78
60
|
rescue Timeout::Error, Timeout::ExitException => e
|
|
79
61
|
@loog.error("Terminated due to --lifetime=#{opts['lifetime']}")
|
|
80
|
-
raise
|
|
62
|
+
raise(e) unless opts['quiet']
|
|
81
63
|
@loog.info("Had to stop due to the --lifetime=#{opts['lifetime']}")
|
|
82
64
|
ensure
|
|
83
65
|
impex.export(fb)
|
|
@@ -90,20 +72,52 @@ class Judges::Update
|
|
|
90
72
|
|
|
91
73
|
private
|
|
92
74
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
75
|
+
def build_options(opts)
|
|
76
|
+
options = Judges::Options.new(timeout: opts['timeout']&.to_i, lifetime: opts['lifetime']&.to_i)
|
|
77
|
+
if options.lifetime && options.timeout && options.lifetime < options.timeout * 1.1
|
|
78
|
+
raise(
|
|
79
|
+
StandardError,
|
|
80
|
+
"The --timeout=#{options.timeout} must be at least 10 percent smaller than --lifetime=#{options.lifetime}"
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
options += Judges::Options.new(opts['option'])
|
|
84
|
+
if opts['options-file']
|
|
85
|
+
options += Judges::Options.new(
|
|
86
|
+
File.readlines(opts['options-file'])
|
|
87
|
+
.compact
|
|
88
|
+
.reject(&:empty?)
|
|
89
|
+
.map { |ln| ln.strip.split('=', 1).map(&:strip).join('=') }
|
|
90
|
+
)
|
|
91
|
+
@loog.debug("Options loaded from #{opts['options-file']}")
|
|
92
|
+
end
|
|
93
|
+
if options.empty?
|
|
94
|
+
@loog.debug('No options provided by the --option flag')
|
|
95
|
+
else
|
|
96
|
+
@loog.debug("The following options provided:\n\t#{options.to_s.gsub("\n", "\n\t")}")
|
|
97
|
+
end
|
|
98
|
+
options
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def log_summary(opts, fb)
|
|
98
102
|
sum = fb.query('(eq what "judges-summary")').each.to_a
|
|
99
103
|
if sum.empty?
|
|
100
104
|
@loog.info('Summary fact not found') unless opts['summary'] == 'off'
|
|
101
105
|
else
|
|
102
106
|
@loog.info("Summary fact found:\n\t#{Factbase::FactAsYaml.new(sum.first).to_s.gsub("\n", "\n\t")}")
|
|
103
107
|
end
|
|
104
|
-
if
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
return if sum.empty?
|
|
109
|
+
return unless opts['summary'] == 'add'
|
|
110
|
+
fb.query('(eq what "judges-summary")').delete!
|
|
111
|
+
@loog.info('Summary fact deleted')
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# rubocop:disable Metrics/MethodLength
|
|
115
|
+
def loop_them(judges, fb, churn, opts, options)
|
|
116
|
+
c = 0
|
|
117
|
+
ch = Factbase::Churn.new
|
|
118
|
+
errors = []
|
|
119
|
+
statistics = opts['statistics'] ? Judges::Statistics.new : nil
|
|
120
|
+
log_summary(opts, fb)
|
|
107
121
|
elapsed(@loog, level: Logger::INFO) do
|
|
108
122
|
loop do
|
|
109
123
|
c += 1
|
|
@@ -131,11 +145,12 @@ class Judges::Update
|
|
|
131
145
|
end
|
|
132
146
|
@loog.info("The cycle #{c} did #{delta}")
|
|
133
147
|
end
|
|
134
|
-
throw
|
|
148
|
+
throw(:"👍 Update completed in #{c} cycle(s), did #{ch}")
|
|
135
149
|
end
|
|
136
150
|
statistics&.report(@loog)
|
|
137
151
|
summarize(fb, ch, errors, c) if %w[add append].include?(opts['summary'])
|
|
138
152
|
end
|
|
153
|
+
# rubocop:enable Metrics/MethodLength
|
|
139
154
|
|
|
140
155
|
# Update the summary.
|
|
141
156
|
# @param [Factbase] fb The factbase
|
|
@@ -186,58 +201,71 @@ class Judges::Update
|
|
|
186
201
|
used = 0
|
|
187
202
|
elapsed(@loog, level: Logger::INFO) do
|
|
188
203
|
done =
|
|
189
|
-
judges.each_with_index do |judge,
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
end
|
|
195
|
-
if opts['lifetime'] && opts['timeout']
|
|
196
|
-
remained = @start + opts['lifetime'] - Time.now
|
|
197
|
-
if remained < opts['timeout'].to_f / 16
|
|
198
|
-
@loog.info("Not running #{judge.name.inspect}, not enough time left (just #{remained.seconds})")
|
|
199
|
-
statistics&.record(judge.name, 0, 'SKIPPED (timeout)') if include?(opts, judge.name)
|
|
200
|
-
next
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
next unless include?(opts, judge.name)
|
|
204
|
-
@loog.info("\n👉 Running #{judge.name} (##{i}) at #{judge.dir.to_rel} (#{@start.ago} already)...")
|
|
205
|
-
used += 1
|
|
206
|
-
start_time = Time.now
|
|
207
|
-
result = 'OK'
|
|
208
|
-
impact = nil
|
|
209
|
-
elapsed(@loog, level: Logger::INFO) do
|
|
210
|
-
impact = one_judge(opts, fb, judge, global, options, errors)
|
|
211
|
-
delta += impact
|
|
212
|
-
churn.append(impact.inserted, impact.deleted, impact.added)
|
|
213
|
-
throw :"👍 The '#{judge.name}' judge made zero changes to #{fb.size} facts" if impact.zero?
|
|
214
|
-
throw :"👍 The '#{judge.name}' judge #{impact} out of #{fb.size} facts"
|
|
215
|
-
end
|
|
216
|
-
rescue StandardError, SyntaxError => e
|
|
217
|
-
if e.is_a?(RuntimeError) && e.message == 'skip'
|
|
218
|
-
result = 'SKIPPED'
|
|
219
|
-
else
|
|
220
|
-
@loog.warn(Backtrace.new(e))
|
|
221
|
-
errors << e.message
|
|
222
|
-
result = 'ERROR'
|
|
204
|
+
judges.each_with_index do |judge, idx|
|
|
205
|
+
result = run_judge_in_cycle(judge, idx, opts, fb, churn, options, errors, global, statistics)
|
|
206
|
+
if result
|
|
207
|
+
used += 1
|
|
208
|
+
delta += result if result.is_a?(Factbase::Churn)
|
|
223
209
|
end
|
|
224
|
-
ensure
|
|
225
|
-
statistics&.record(judge.name, Time.now - start_time, result, impact) if start_time
|
|
226
210
|
end
|
|
227
|
-
throw
|
|
228
|
-
throw
|
|
211
|
+
throw(:"👍 #{done} judge(s) processed") if errors.empty?
|
|
212
|
+
throw(:"❌ #{done} judge(s) processed with #{errors.size} errors")
|
|
229
213
|
end
|
|
230
214
|
if used.zero?
|
|
231
|
-
raise 'No judges were used, while at least one expected to run' if opts['expect-judges']
|
|
215
|
+
raise(StandardError, 'No judges were used, while at least one expected to run') if opts['expect-judges']
|
|
232
216
|
@loog.info('No judges were used (looks like an error); not failing because of --no-expect-judges')
|
|
233
217
|
end
|
|
234
218
|
unless errors.empty?
|
|
235
|
-
raise "Failed to update correctly (#{errors.size} errors)" unless opts['quiet']
|
|
219
|
+
raise(StandardError, "Failed to update correctly (#{errors.size} errors)") unless opts['quiet']
|
|
236
220
|
@loog.info('Not failing because of the --quiet flag provided')
|
|
237
221
|
end
|
|
238
222
|
delta
|
|
239
223
|
end
|
|
240
224
|
|
|
225
|
+
def run_judge_in_cycle(judge, idx, opts, fb, churn, options, errors, global, statistics)
|
|
226
|
+
return if skip_judge?(judge, idx, opts, errors, statistics)
|
|
227
|
+
return unless include?(opts, judge.name)
|
|
228
|
+
@loog.info("\n👉 Running #{judge.name} (##{idx}) at #{judge.dir.to_rel} (#{@start.ago} already)...")
|
|
229
|
+
start = Time.now
|
|
230
|
+
result = 'OK'
|
|
231
|
+
impact = nil
|
|
232
|
+
elapsed(@loog, level: Logger::INFO) do
|
|
233
|
+
impact = one_judge(opts, fb, judge, global, options, errors)
|
|
234
|
+
churn.append(impact.inserted, impact.deleted, impact.added)
|
|
235
|
+
throw(:"👍 The '#{judge.name}' judge made zero changes to #{fb.size} facts") if impact.zero?
|
|
236
|
+
throw(:"👍 The '#{judge.name}' judge #{impact} out of #{fb.size} facts")
|
|
237
|
+
end
|
|
238
|
+
impact
|
|
239
|
+
rescue StandardError, SyntaxError => e
|
|
240
|
+
if e.is_a?(RuntimeError) && e.message == 'skip'
|
|
241
|
+
result = 'SKIPPED'
|
|
242
|
+
else
|
|
243
|
+
@loog.warn(Backtrace.new(e))
|
|
244
|
+
errors << e.message
|
|
245
|
+
result = 'ERROR'
|
|
246
|
+
end
|
|
247
|
+
impact || true
|
|
248
|
+
ensure
|
|
249
|
+
statistics&.record(judge.name, Time.now - start, result, impact) if start
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def skip_judge?(judge, _idx, opts, errors, statistics)
|
|
253
|
+
if opts['fail-fast'] && !errors.empty?
|
|
254
|
+
@loog.info("Not running #{judge.name.inspect} due to #{errors.count} errors above, in --fail-fast mode")
|
|
255
|
+
statistics&.record(judge.name, 0, 'SKIPPED (fail-fast)') if include?(opts, judge.name)
|
|
256
|
+
return true
|
|
257
|
+
end
|
|
258
|
+
if opts['lifetime'] && opts['timeout']
|
|
259
|
+
remained = @start + opts['lifetime'] - Time.now
|
|
260
|
+
if remained < opts['timeout'].to_f / 16
|
|
261
|
+
@loog.info("Not running #{judge.name.inspect}, not enough time left (just #{remained.seconds})")
|
|
262
|
+
statistics&.record(judge.name, 0, 'SKIPPED (timeout)') if include?(opts, judge.name)
|
|
263
|
+
return true
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
false
|
|
267
|
+
end
|
|
268
|
+
|
|
241
269
|
# Run a single judge.
|
|
242
270
|
#
|
|
243
271
|
# @param [Hash] opts The command line options
|
|
@@ -253,7 +281,7 @@ class Judges::Update
|
|
|
253
281
|
fb = Factbase::Tallied.new(fb)
|
|
254
282
|
begin
|
|
255
283
|
if opts['lifetime'] && Time.now - @start > opts['lifetime']
|
|
256
|
-
throw
|
|
284
|
+
throw(:"👎 The '#{judge.name}' judge skipped, no time left")
|
|
257
285
|
end
|
|
258
286
|
Timeout.timeout(opts['timeout']) do
|
|
259
287
|
judge.run(fb, global, local, options)
|
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
-
require 'typhoeus'
|
|
7
|
-
require 'iri'
|
|
8
6
|
require 'baza-rb'
|
|
9
7
|
require 'elapsed'
|
|
8
|
+
require 'iri'
|
|
9
|
+
require 'typhoeus'
|
|
10
10
|
require_relative '../../judges'
|
|
11
11
|
|
|
12
12
|
# The +upload+ command, to send a durable to Zerocracy.
|
|
13
13
|
#
|
|
14
|
-
# This class is instantiated by the +bin/
|
|
14
|
+
# This class is instantiated by the +bin/judges+ command line interface. You
|
|
15
15
|
# are not supposed to instantiate it yourself.
|
|
16
16
|
#
|
|
17
17
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
@@ -28,11 +28,12 @@ class Judges::Upload
|
|
|
28
28
|
# @param [Hash] opts Command line options (start with '--')
|
|
29
29
|
# @param [Array] args List of command line arguments
|
|
30
30
|
# @raise [RuntimeError] If not exactly two arguments provided
|
|
31
|
+
# rubocop:disable Metrics/MethodLength
|
|
31
32
|
def run(opts, args)
|
|
32
|
-
raise 'Exactly two arguments required' unless args.size == 2
|
|
33
|
+
raise(ArgumentError, 'Exactly two arguments required') unless args.size == 2
|
|
33
34
|
jname = args[0]
|
|
34
35
|
path = args[1]
|
|
35
|
-
raise "File not found: #{path}" unless File.exist?(path)
|
|
36
|
+
raise(StandardError, "File not found: #{path}") unless File.exist?(path)
|
|
36
37
|
name = File.basename(path)
|
|
37
38
|
baza = BazaRb.new(
|
|
38
39
|
opts['host'], opts['port'].to_i, opts['token'],
|
|
@@ -44,8 +45,6 @@ class Judges::Upload
|
|
|
44
45
|
elapsed(@loog, level: Logger::INFO) do
|
|
45
46
|
id = baza.durable_find(jname, name)
|
|
46
47
|
if id.nil? || id.to_s.strip.empty?
|
|
47
|
-
# Block form of Dir.mkdir causes error Errno::EACCESS on windows
|
|
48
|
-
# so we use non-block form
|
|
49
48
|
tmp = Dir.mktmpdir
|
|
50
49
|
begin
|
|
51
50
|
f = File.join(tmp, name)
|
|
@@ -61,10 +60,11 @@ class Judges::Upload
|
|
|
61
60
|
baza.durable_lock(id, opts['owner'] || 'default')
|
|
62
61
|
begin
|
|
63
62
|
baza.durable_save(id, path)
|
|
64
|
-
throw
|
|
63
|
+
throw(:"👍 Uploaded #{path} to existing durable '#{name}' in '#{jname}' (ID: #{id}, #{size} bytes)")
|
|
65
64
|
ensure
|
|
66
65
|
baza.durable_unlock(id, opts['owner'] || 'default')
|
|
67
66
|
end
|
|
68
67
|
end
|
|
69
68
|
end
|
|
69
|
+
# rubocop:enable Metrics/MethodLength
|
|
70
70
|
end
|
data/lib/judges/impex.rb
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 Yegor Bugayenko
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
|
+
require 'elapsed'
|
|
6
7
|
require 'factbase'
|
|
7
8
|
require 'fileutils'
|
|
8
|
-
require 'elapsed'
|
|
9
9
|
require_relative '../judges'
|
|
10
10
|
require_relative '../judges/to_rel'
|
|
11
11
|
|
|
@@ -51,10 +51,10 @@ class Judges::Impex
|
|
|
51
51
|
if File.exist?(@file)
|
|
52
52
|
elapsed(@loog, level: Logger::INFO) do
|
|
53
53
|
fb.import(File.binread(@file))
|
|
54
|
-
throw
|
|
54
|
+
throw(:"The factbase imported from #{@file.to_rel} (#{File.size(@file)} bytes, #{fb.size} facts)")
|
|
55
55
|
end
|
|
56
56
|
else
|
|
57
|
-
raise "The factbase is absent at #{@file.to_rel}" if strict
|
|
57
|
+
raise(StandardError, "The factbase is absent at #{@file.to_rel}") if strict
|
|
58
58
|
@loog.info("Nothing to import from #{@file.to_rel} (file not found)")
|
|
59
59
|
end
|
|
60
60
|
fb
|
|
@@ -74,10 +74,10 @@ class Judges::Impex
|
|
|
74
74
|
# # ... populate fb with some data ...
|
|
75
75
|
# impex.import_to(fb) # Adds data from file to existing facts
|
|
76
76
|
def import_to(fb)
|
|
77
|
-
raise "The factbase is absent at #{@file.to_rel}" unless File.exist?(@file)
|
|
77
|
+
raise(StandardError, "The factbase is absent at #{@file.to_rel}") unless File.exist?(@file)
|
|
78
78
|
elapsed(@loog, level: Logger::INFO) do
|
|
79
79
|
fb.import(File.binread(@file))
|
|
80
|
-
throw
|
|
80
|
+
throw(:"The factbase loaded from #{@file.to_rel} (#{File.size(@file)} bytes, #{fb.size} facts)")
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -97,7 +97,7 @@ class Judges::Impex
|
|
|
97
97
|
elapsed(@loog, level: Logger::INFO) do
|
|
98
98
|
FileUtils.mkdir_p(File.dirname(@file))
|
|
99
99
|
File.binwrite(@file, fb.export)
|
|
100
|
-
throw
|
|
100
|
+
throw(:"Factbase exported to #{@file.to_rel} (#{File.size(@file)} bytes, #{fb.size} facts)")
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
end
|
data/lib/judges/judge.rb
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
# SPDX-License-Identifier: MIT
|
|
5
5
|
|
|
6
6
|
require 'elapsed'
|
|
7
|
-
require 'tago'
|
|
8
|
-
require 'timeout'
|
|
9
7
|
require 'factbase/tallied'
|
|
10
8
|
require 'octokit'
|
|
9
|
+
require 'tago'
|
|
10
|
+
require 'timeout'
|
|
11
11
|
require_relative '../judges'
|
|
12
|
-
require_relative '../judges/to_rel'
|
|
13
12
|
require_relative '../judges/pretty_exception'
|
|
13
|
+
require_relative '../judges/to_rel'
|
|
14
14
|
|
|
15
15
|
# A single judge.
|
|
16
16
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
@@ -57,42 +57,44 @@ class Judges::Judge
|
|
|
57
57
|
# @param [Judges::Options] options Command-line options object
|
|
58
58
|
# @return [nil] Nothing
|
|
59
59
|
# @raise [RuntimeError] If the lib directory doesn't exist, the script can't be loaded, or execution fails
|
|
60
|
+
# rubocop:disable Metrics/MethodLength
|
|
60
61
|
def run(fb, global, local, options)
|
|
61
62
|
$fb = fb
|
|
62
63
|
$judge = File.basename(@dir)
|
|
63
64
|
$options = options
|
|
64
65
|
$loog = @loog
|
|
65
66
|
$global = global
|
|
66
|
-
$global.delete(:fb)
|
|
67
|
+
$global.delete(:fb)
|
|
67
68
|
$local = local
|
|
68
69
|
$epoch = @epoch
|
|
69
70
|
$kickoff = Time.now
|
|
70
71
|
options.to_h.each { |k, v| ENV.store(k.to_s, v.to_s) }
|
|
71
72
|
unless @lib.nil?
|
|
72
|
-
raise "Lib dir #{@lib.to_rel} is absent" unless File.exist?(@lib)
|
|
73
|
-
raise "Lib #{@lib.to_rel} is not a directory" unless File.directory?(@lib)
|
|
73
|
+
raise(StandardError, "Lib dir #{@lib.to_rel} is absent") unless File.exist?(@lib)
|
|
74
|
+
raise(StandardError, "Lib #{@lib.to_rel} is not a directory") unless File.directory?(@lib)
|
|
74
75
|
Dir.glob(File.join(@lib, '*.rb')).each do |f|
|
|
75
76
|
require_relative(File.absolute_path(f))
|
|
76
77
|
end
|
|
77
78
|
end
|
|
78
79
|
s = File.join(@dir, script)
|
|
79
|
-
raise "Can't load '#{s}'" unless File.exist?(s)
|
|
80
|
+
raise(StandardError, "Can't load '#{s}'") unless File.exist?(s)
|
|
80
81
|
elapsed(@loog, good: "#{$judge} completed", level: Logger::INFO) do
|
|
81
82
|
load(s, true)
|
|
82
83
|
nil
|
|
83
84
|
# rubocop:disable Lint/RescueException
|
|
84
85
|
rescue Exception => e
|
|
85
86
|
# rubocop:enable Lint/RescueException
|
|
86
|
-
raise
|
|
87
|
+
raise(e) if e.is_a?(RuntimeError) && e.message == 'skip'
|
|
87
88
|
e = Judges::PrettyException.new(e) if e.is_a?(Octokit::ServerError)
|
|
88
89
|
@loog.error(Backtrace.new(e))
|
|
89
|
-
raise
|
|
90
|
-
raise
|
|
91
|
-
raise "#{e.message} (#{e.class.name})"
|
|
90
|
+
raise(e) if e.is_a?(StandardError)
|
|
91
|
+
raise(e) if e.is_a?(Timeout::ExitException)
|
|
92
|
+
raise(StandardError, "#{e.message} (#{e.class.name})")
|
|
92
93
|
ensure
|
|
93
94
|
$fb = $judge = $options = $loog = $epoch = $kickoff = nil
|
|
94
95
|
end
|
|
95
96
|
end
|
|
97
|
+
# rubocop:enable Metrics/MethodLength
|
|
96
98
|
|
|
97
99
|
# Returns the name of the judge.
|
|
98
100
|
#
|
|
@@ -113,7 +115,7 @@ class Judges::Judge
|
|
|
113
115
|
def script
|
|
114
116
|
b = "#{File.basename(@dir)}.rb"
|
|
115
117
|
files = Dir.glob(File.join(@dir, '*.rb')).map { |f| File.basename(f) }
|
|
116
|
-
raise "No #{b} script in #{@dir.to_rel} among #{files}" unless files.include?(b)
|
|
118
|
+
raise(StandardError, "No #{b} script in #{@dir.to_rel} among #{files}") unless files.include?(b)
|
|
117
119
|
b
|
|
118
120
|
end
|
|
119
121
|
|
data/lib/judges/judges.rb
CHANGED
|
@@ -55,7 +55,7 @@ class Judges::Judges
|
|
|
55
55
|
# @raise [RuntimeError] If no judge directory exists with the given name
|
|
56
56
|
def get(name)
|
|
57
57
|
d = File.absolute_path(File.join(@dir, name))
|
|
58
|
-
raise "Judge #{name} doesn't exist in #{@dir}" unless File.exist?(d)
|
|
58
|
+
raise(StandardError, "Judge #{name} doesn't exist in #{@dir}") unless File.exist?(d)
|
|
59
59
|
Judges::Judge.new(d, @lib, @loog, epoch: @epoch)
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -73,30 +73,11 @@ class Judges::Judges
|
|
|
73
73
|
# @return [Enumerator] Returns an enumerator if no block is given
|
|
74
74
|
def each(&)
|
|
75
75
|
return to_enum(__method__) unless block_given?
|
|
76
|
-
|
|
77
|
-
Dir.glob(File.join(@dir, '*')).each.to_a.map do |d|
|
|
78
|
-
next unless File.directory?(d)
|
|
79
|
-
b = File.basename(d)
|
|
80
|
-
next unless File.exist?(File.join(d, "#{b}.rb"))
|
|
81
|
-
Judges::Judge.new(File.absolute_path(d), @lib, @loog, epoch: @epoch)
|
|
82
|
-
end
|
|
83
|
-
list.compact!
|
|
84
|
-
list.sort_by!(&:name)
|
|
85
|
-
all = list.each_with_index.to_a
|
|
86
|
-
good = all.dup
|
|
87
|
-
mapping =
|
|
88
|
-
all
|
|
89
|
-
.map { |a| [a[0].name, a[1], a[1]] }
|
|
90
|
-
.reject { |a| !@shuffle.empty? && a[0].start_with?(@shuffle) }
|
|
91
|
-
.to_h { |a| [a[1], a[2]] }
|
|
92
|
-
positions = mapping.values.shuffle(random: Random.new(@seed))
|
|
93
|
-
mapping.keys.zip(positions).to_h.each do |before, after|
|
|
94
|
-
good[after] = all[before]
|
|
95
|
-
end
|
|
76
|
+
good = reorder_judges
|
|
96
77
|
boosted = []
|
|
97
78
|
demoted = []
|
|
98
79
|
normal = []
|
|
99
|
-
good.
|
|
80
|
+
good.each do |j|
|
|
100
81
|
if fits?(j.name, @boost)
|
|
101
82
|
boosted.append(j)
|
|
102
83
|
elsif fits?(j.name, @demote)
|
|
@@ -105,8 +86,7 @@ class Judges::Judges
|
|
|
105
86
|
normal.append(j)
|
|
106
87
|
end
|
|
107
88
|
end
|
|
108
|
-
|
|
109
|
-
ret.each(&)
|
|
89
|
+
(boosted + normal + demoted).each(&)
|
|
110
90
|
end
|
|
111
91
|
|
|
112
92
|
# Iterates over all judges while tracking their index position.
|
|
@@ -120,7 +100,7 @@ class Judges::Judges
|
|
|
120
100
|
def each_with_index
|
|
121
101
|
idx = 0
|
|
122
102
|
each do |p|
|
|
123
|
-
yield
|
|
103
|
+
yield([p, idx])
|
|
124
104
|
idx += 1
|
|
125
105
|
end
|
|
126
106
|
idx
|
|
@@ -128,6 +108,31 @@ class Judges::Judges
|
|
|
128
108
|
|
|
129
109
|
private
|
|
130
110
|
|
|
111
|
+
def reorder_judges
|
|
112
|
+
list = discover_judges
|
|
113
|
+
list.sort_by!(&:name)
|
|
114
|
+
all = list.each_with_index.to_a
|
|
115
|
+
good = all.dup
|
|
116
|
+
mapping =
|
|
117
|
+
all
|
|
118
|
+
.map { |a| [a[0].name, a[1], a[1]] }
|
|
119
|
+
.reject { |a| !@shuffle.empty? && a[0].start_with?(@shuffle) }
|
|
120
|
+
.to_h { |a| [a[1], a[2]] }
|
|
121
|
+
positions = mapping.values.shuffle(random: Random.new(@seed))
|
|
122
|
+
mapping.keys.zip(positions).to_h.each do |before, after|
|
|
123
|
+
good[after] = all[before]
|
|
124
|
+
end
|
|
125
|
+
good.map { |a| a[0] }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def discover_judges
|
|
129
|
+
Dir.glob(File.join(@dir, '*')).each.to_a.filter_map do |d|
|
|
130
|
+
next unless File.directory?(d)
|
|
131
|
+
next unless File.exist?(File.join(d, "#{File.basename(d)}.rb"))
|
|
132
|
+
Judges::Judge.new(File.absolute_path(d), @lib, @loog, epoch: @epoch)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
131
136
|
# Checks if a judge name matches any of the given patterns.
|
|
132
137
|
# Patterns can contain '*' wildcards which are converted to '.*' regex patterns.
|
|
133
138
|
#
|
data/lib/judges/options.rb
CHANGED
|
@@ -111,49 +111,40 @@ class Judges::Options
|
|
|
111
111
|
# options = Judges::Options.new("token=abc123,max_speed=100,debug")
|
|
112
112
|
# options.to_h # => { TOKEN: "abc123", MAX_SPEED: 100, DEBUG: "true" }
|
|
113
113
|
def to_h
|
|
114
|
-
@to_h ||=
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
114
|
+
@to_h ||= normalize_to_h
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def normalize_to_h
|
|
120
|
+
pp = parse_pairs
|
|
121
|
+
pp
|
|
122
|
+
.reject { |k, _| k.nil? }
|
|
123
|
+
.compact
|
|
124
|
+
.reject { |k, _| k.is_a?(String) && k.empty? }
|
|
125
|
+
.to_h
|
|
126
|
+
.transform_values { |v| v.nil? ? 'true' : v }
|
|
127
|
+
.transform_values { |v| v.is_a?(String) ? v.strip : v }
|
|
128
|
+
.transform_values { |v| v.is_a?(String) && v.match?(/^[0-9]+$/) ? v.to_i : v }
|
|
129
|
+
.transform_keys { |k| k.to_s.strip.upcase.to_sym }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_pairs
|
|
133
|
+
pp = @pairs || []
|
|
134
|
+
pp = pp.split(',') if pp.is_a?(String)
|
|
135
|
+
if pp.is_a?(Array)
|
|
136
|
+
pp = pp
|
|
137
|
+
.compact
|
|
138
|
+
.map(&:strip)
|
|
139
|
+
.reject(&:empty?)
|
|
140
|
+
.map { |s| s.split('=', 2) }
|
|
141
|
+
.map { |a| a.size == 1 ? [a[0], nil] : a }
|
|
142
|
+
.reject { |a| a[0].empty? }
|
|
143
|
+
.to_h
|
|
144
|
+
end
|
|
145
|
+
pp
|
|
138
146
|
end
|
|
139
147
|
|
|
140
|
-
# Get option by name.
|
|
141
|
-
#
|
|
142
|
-
# This method is implemented using the 'others' gem, which provides
|
|
143
|
-
# dynamic method handling. It allows accessing options as method calls.
|
|
144
|
-
# Method names are automatically converted to uppercase symbols to match
|
|
145
|
-
# the keys in the options hash.
|
|
146
|
-
#
|
|
147
|
-
# @!method method_missing(method_name, *args)
|
|
148
|
-
# Dynamic method to access option values
|
|
149
|
-
# @param [Symbol] method_name The name of the option to retrieve
|
|
150
|
-
# @param [Array] args Additional arguments (unused)
|
|
151
|
-
# @return [Object, nil] The value of the option, or nil if not found
|
|
152
|
-
# @example Access options as methods
|
|
153
|
-
# options = Judges::Options.new(["token=abc123", "max_speed=100"])
|
|
154
|
-
# options.token # => "abc123"
|
|
155
|
-
# options.max_speed # => 100
|
|
156
|
-
# options.missing_option # => nil
|
|
157
148
|
others do |*args|
|
|
158
149
|
to_h[args[0].upcase.to_sym]
|
|
159
150
|
end
|
|
@@ -7,7 +7,7 @@ require 'delegate'
|
|
|
7
7
|
require 'ellipsized'
|
|
8
8
|
require_relative '../judges'
|
|
9
9
|
|
|
10
|
-
# Decorates the exception
|
|
10
|
+
# Decorates the exception to show an ellipsized message.
|
|
11
11
|
class Judges::PrettyException < SimpleDelegator
|
|
12
12
|
undef_method :class
|
|
13
13
|
undef_method :instance_of?
|
data/lib/judges/statistics.rb
CHANGED
|
@@ -32,12 +32,7 @@ class Judges::Statistics
|
|
|
32
32
|
# @param [Churn] churn The churn for this run (can be nil)
|
|
33
33
|
def record(name, time, result, churn = nil)
|
|
34
34
|
unless @data[name]
|
|
35
|
-
@data[name] = {
|
|
36
|
-
total_time: 0.0,
|
|
37
|
-
cycles: 0,
|
|
38
|
-
results: [],
|
|
39
|
-
total_churn: nil
|
|
40
|
-
}
|
|
35
|
+
@data[name] = { total_time: 0.0, cycles: 0, results: [], total_churn: nil }
|
|
41
36
|
end
|
|
42
37
|
stats = @data[name]
|
|
43
38
|
stats[:total_time] += time
|
|
@@ -62,8 +57,10 @@ class Judges::Statistics
|
|
|
62
57
|
format(fmt, 'Judge', 'Seconds', 'Cycles', 'Changes', 'Results'),
|
|
63
58
|
format(fmt, '---', '---', '---', '---', '---'),
|
|
64
59
|
@data.sort_by { |_, stats| stats[:total_time] }.reverse.map do |name, stats|
|
|
65
|
-
format(
|
|
66
|
-
|
|
60
|
+
format(
|
|
61
|
+
fmt, name, format('%.3f', stats[:total_time]), stats[:cycles],
|
|
62
|
+
stats[:total_churn] ? stats[:total_churn].to_s : 'N/A', summarize(stats[:results])
|
|
63
|
+
)
|
|
67
64
|
end.join("\n ")
|
|
68
65
|
].join("\n ")
|
|
69
66
|
)
|
data/lib/judges.rb
CHANGED