judges 0.55.0 → 0.57.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/Gemfile.lock +16 -15
- data/README.md +2 -2
- data/Rakefile +1 -0
- data/bin/judges +29 -4
- data/features/update.feature +43 -2
- data/judges.gemspec +1 -1
- data/lib/judges/ascii_loog.rb +81 -0
- data/lib/judges/commands/update.rb +21 -9
- data/lib/judges/commands/upload.rb +6 -1
- data/lib/judges/options.rb +5 -2
- data/lib/judges/statistics.rb +83 -0
- data/lib/judges.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 25236f211b356d3dfe62fd6ba9eb7eb70a04e2de64d2581d682db989967cefa9
|
|
4
|
+
data.tar.gz: c804cf366c81db1834496d71852422dbb245edfc76f0a24511621a4aa7979ada
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1c33f8ae0312222d0ae2ec848d031648fc5f04cb2a64d49e63a26176157a2d9e82bb2fa1f8e010aa0fe8a482fe16690145bdca92f367f05afa851687a18f09af
|
|
7
|
+
data.tar.gz: 6bfafca7127eca496160d7b57325e92815ced1872da8f929380aa0fee185d5f3931cdf5eb753d39a3c68c9c19800a2cfa100b99da60288d9dbae33ccb9680309
|
data/Gemfile.lock
CHANGED
|
@@ -78,8 +78,9 @@ GEM
|
|
|
78
78
|
loog (~> 0.6)
|
|
79
79
|
tago (~> 0.1)
|
|
80
80
|
ellipsized (0.3.0)
|
|
81
|
-
ethon (0.
|
|
81
|
+
ethon (0.18.0)
|
|
82
82
|
ffi (>= 1.15.0)
|
|
83
|
+
logger
|
|
83
84
|
factbase (0.16.8)
|
|
84
85
|
backtrace (~> 0.4)
|
|
85
86
|
decoor (~> 0.1)
|
|
@@ -99,8 +100,8 @@ GEM
|
|
|
99
100
|
faraday (>= 0.8)
|
|
100
101
|
faraday-multipart (1.1.1)
|
|
101
102
|
multipart-post (~> 2.0)
|
|
102
|
-
faraday-net_http (3.4.
|
|
103
|
-
net-http (
|
|
103
|
+
faraday-net_http (3.4.2)
|
|
104
|
+
net-http (~> 0.5)
|
|
104
105
|
faraday-retry (2.3.2)
|
|
105
106
|
faraday (~> 2.0)
|
|
106
107
|
ffi (1.17.2-aarch64-linux-gnu)
|
|
@@ -116,7 +117,7 @@ GEM
|
|
|
116
117
|
ostruct
|
|
117
118
|
hashdiff (1.2.1)
|
|
118
119
|
iri (0.11.2)
|
|
119
|
-
json (2.
|
|
120
|
+
json (2.16.0)
|
|
120
121
|
language_server-protocol (3.17.0.5)
|
|
121
122
|
lint_roller (1.1.0)
|
|
122
123
|
logger (1.7.0)
|
|
@@ -124,7 +125,7 @@ GEM
|
|
|
124
125
|
logger (~> 1.0)
|
|
125
126
|
memoist3 (1.0.0)
|
|
126
127
|
mini_mime (1.1.5)
|
|
127
|
-
minitest (5.26.
|
|
128
|
+
minitest (5.26.1)
|
|
128
129
|
minitest-reporters (1.7.1)
|
|
129
130
|
ansi
|
|
130
131
|
builder
|
|
@@ -135,7 +136,7 @@ GEM
|
|
|
135
136
|
moments (0.3.0)
|
|
136
137
|
multi_test (1.1.0)
|
|
137
138
|
multipart-post (2.4.1)
|
|
138
|
-
net-http (0.
|
|
139
|
+
net-http (0.7.0)
|
|
139
140
|
uri
|
|
140
141
|
nokogiri (1.18.10-aarch64-linux-gnu)
|
|
141
142
|
racc (~> 1.4)
|
|
@@ -161,25 +162,25 @@ GEM
|
|
|
161
162
|
ostruct (0.6.3)
|
|
162
163
|
others (0.1.1)
|
|
163
164
|
parallel (1.27.0)
|
|
164
|
-
parser (3.3.
|
|
165
|
+
parser (3.3.10.0)
|
|
165
166
|
ast (~> 2.4.1)
|
|
166
167
|
racc
|
|
167
168
|
prism (1.6.0)
|
|
168
169
|
public_suffix (6.0.2)
|
|
169
|
-
qbash (0.4.
|
|
170
|
+
qbash (0.4.7)
|
|
170
171
|
backtrace (> 0)
|
|
171
172
|
elapsed (> 0)
|
|
172
173
|
loog (> 0)
|
|
173
174
|
tago (> 0)
|
|
174
175
|
racc (1.8.1)
|
|
175
176
|
rainbow (3.1.1)
|
|
176
|
-
rake (13.3.
|
|
177
|
+
rake (13.3.1)
|
|
177
178
|
random-port (0.7.6)
|
|
178
179
|
tago (~> 0.0)
|
|
179
180
|
regexp_parser (2.11.3)
|
|
180
181
|
retries (0.0.5)
|
|
181
182
|
rexml (3.4.4)
|
|
182
|
-
rubocop (1.81.
|
|
183
|
+
rubocop (1.81.7)
|
|
183
184
|
json (~> 2.3)
|
|
184
185
|
language_server-protocol (~> 3.17.0.2)
|
|
185
186
|
lint_roller (~> 1.1.0)
|
|
@@ -190,7 +191,7 @@ GEM
|
|
|
190
191
|
rubocop-ast (>= 1.47.1, < 2.0)
|
|
191
192
|
ruby-progressbar (~> 1.7)
|
|
192
193
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
193
|
-
rubocop-ast (1.
|
|
194
|
+
rubocop-ast (1.48.0)
|
|
194
195
|
parser (>= 3.3.7.2)
|
|
195
196
|
prism (~> 1.4)
|
|
196
197
|
rubocop-minitest (0.38.2)
|
|
@@ -217,20 +218,20 @@ GEM
|
|
|
217
218
|
sys-uname (1.4.1)
|
|
218
219
|
ffi (~> 1.1)
|
|
219
220
|
memoist3 (~> 1.0.0)
|
|
220
|
-
tago (0.
|
|
221
|
-
timeout (0.4.
|
|
221
|
+
tago (0.4.0)
|
|
222
|
+
timeout (0.4.4)
|
|
222
223
|
total (0.4.1)
|
|
223
224
|
typhoeus (1.4.1)
|
|
224
225
|
ethon (>= 0.9.0)
|
|
225
226
|
unicode-display_width (3.2.0)
|
|
226
227
|
unicode-emoji (~> 4.1)
|
|
227
228
|
unicode-emoji (4.1.0)
|
|
228
|
-
uri (1.
|
|
229
|
+
uri (1.1.1)
|
|
229
230
|
w3c_validators (1.3.7)
|
|
230
231
|
json (>= 1.8)
|
|
231
232
|
nokogiri (~> 1.6)
|
|
232
233
|
rexml (~> 3.2)
|
|
233
|
-
webmock (3.
|
|
234
|
+
webmock (3.26.1)
|
|
234
235
|
addressable (>= 2.8.0)
|
|
235
236
|
crack (>= 0.3.2)
|
|
236
237
|
hashdiff (>= 0.4.0, < 2.0.0)
|
data/README.md
CHANGED
|
@@ -20,10 +20,10 @@ of `.yml` files. A script in the Ruby file is executed with the following
|
|
|
20
20
|
global variables available to it:
|
|
21
21
|
|
|
22
22
|
* `$fb` — an instance
|
|
23
|
-
of [
|
|
23
|
+
of [Factbase](https://www.rubydoc.info/gems/factbase/0.0.22/Factbase),
|
|
24
24
|
where facts may be added/updated;
|
|
25
25
|
* `$loog` — an instance
|
|
26
|
-
of [
|
|
26
|
+
of [Loog](https://www.rubydoc.info/gems/loog/0.5.1/Loog),
|
|
27
27
|
where `.info` and `.debug` logs are welcome;
|
|
28
28
|
* `$options` — a holder of options coming from either the `--option` command
|
|
29
29
|
line flag or the `.yml` file during testing;
|
data/Rakefile
CHANGED
data/bin/judges
CHANGED
|
@@ -13,6 +13,7 @@ require 'tago'
|
|
|
13
13
|
require 'time'
|
|
14
14
|
require 'total'
|
|
15
15
|
require_relative '../lib/judges'
|
|
16
|
+
require_relative '../lib/judges/ascii_loog'
|
|
16
17
|
|
|
17
18
|
Encoding.default_external = Encoding::UTF_8
|
|
18
19
|
Encoding.default_internal = Encoding::UTF_8
|
|
@@ -57,11 +58,16 @@ class JudgesGLI extend GLI::App
|
|
|
57
58
|
switch([:echo])
|
|
58
59
|
desc 'Stay strictly offline, never attempt to reach Internet resources'
|
|
59
60
|
switch([:offline])
|
|
61
|
+
desc 'Suppress all Unicode symbols in the output'
|
|
62
|
+
switch([:ascii])
|
|
60
63
|
|
|
61
64
|
pre do |global, command, options, args|
|
|
62
65
|
if global[:verbose]
|
|
63
66
|
@@loog = Loog::VERBOSE
|
|
64
67
|
end
|
|
68
|
+
if global[:ascii]
|
|
69
|
+
@@loog = Judges::AsciiLoog.new(@@loog)
|
|
70
|
+
end
|
|
65
71
|
if global[:echo]
|
|
66
72
|
@@loog.info("+ #{File.absolute_path($0)} #{@@args.join(' ')}")
|
|
67
73
|
end
|
|
@@ -73,15 +79,20 @@ class JudgesGLI extend GLI::App
|
|
|
73
79
|
end
|
|
74
80
|
require 'factbase'
|
|
75
81
|
if global[:hello]
|
|
76
|
-
[
|
|
82
|
+
info = [
|
|
77
83
|
"Judges #{Judges::VERSION}",
|
|
78
84
|
"Factbase #{Factbase::VERSION}",
|
|
79
85
|
"Baza-rb #{BazaRb::VERSION}",
|
|
80
86
|
"Ruby: #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}",
|
|
81
87
|
"Current directory: #{Dir.getwd}",
|
|
82
|
-
"Time: #{Time.now.utc.iso8601}"
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
"Time: #{Time.now.utc.iso8601}"
|
|
89
|
+
]
|
|
90
|
+
begin
|
|
91
|
+
info << "Total memory: #{Total::Mem.new.bytes / (1024 * 1024)}Mb"
|
|
92
|
+
rescue Total::CantDetect => e
|
|
93
|
+
@@loog.debug("Can't detect memory: #{e.message}")
|
|
94
|
+
end
|
|
95
|
+
info.each { |m| @@loog.info(m) }
|
|
85
96
|
end
|
|
86
97
|
true
|
|
87
98
|
end
|
|
@@ -94,6 +105,7 @@ class JudgesGLI extend GLI::App
|
|
|
94
105
|
end
|
|
95
106
|
|
|
96
107
|
desc 'Update the factbase by executing all judges sequentially'
|
|
108
|
+
arg_name '<dir> <factbase>'
|
|
97
109
|
command :update do |c|
|
|
98
110
|
c.desc 'Options to pass to each judge'
|
|
99
111
|
c.flag([:o, :option], multiple: true, arg_name: '<key=value>')
|
|
@@ -123,12 +135,15 @@ class JudgesGLI extend GLI::App
|
|
|
123
135
|
c.flag([:summary], must_match: %w[off add append], default_value: 'off')
|
|
124
136
|
c.desc 'Use default logging facility'
|
|
125
137
|
c.switch([:log], default_value: true)
|
|
138
|
+
c.desc 'Show execution statistics for each judge'
|
|
139
|
+
c.switch([:statistics], default_value: false)
|
|
126
140
|
c.desc 'Expect at least one judge to be used (fail if none are used)'
|
|
127
141
|
c.switch([:'expect-judges'], default_value: true)
|
|
128
142
|
run_it(c, 'update')
|
|
129
143
|
end
|
|
130
144
|
|
|
131
145
|
desc 'Evaluate a single Ruby expression against the factbase'
|
|
146
|
+
arg_name '<factbase> <expression>'
|
|
132
147
|
command :eval do |c|
|
|
133
148
|
c.desc 'Use default logging facility'
|
|
134
149
|
c.switch([:log], default_value: true)
|
|
@@ -136,11 +151,13 @@ class JudgesGLI extend GLI::App
|
|
|
136
151
|
end
|
|
137
152
|
|
|
138
153
|
desc 'Join two factbases'
|
|
154
|
+
arg_name '<first> <second>'
|
|
139
155
|
command :join do |c|
|
|
140
156
|
run_it(c, 'join')
|
|
141
157
|
end
|
|
142
158
|
|
|
143
159
|
desc 'Import YAML into a factbase'
|
|
160
|
+
arg_name '<factbase> <yaml>'
|
|
144
161
|
command :import do |c|
|
|
145
162
|
c.desc 'Use default logging facility'
|
|
146
163
|
c.switch([:log], default_value: true)
|
|
@@ -148,6 +165,7 @@ class JudgesGLI extend GLI::App
|
|
|
148
165
|
end
|
|
149
166
|
|
|
150
167
|
desc 'Remove outdated facts from the factbase'
|
|
168
|
+
arg_name 'factbase'
|
|
151
169
|
command :trim do |c|
|
|
152
170
|
c.desc 'Delete only facts matching the specified expression'
|
|
153
171
|
c.flag([:query], default_value: '(never)')
|
|
@@ -155,6 +173,7 @@ class JudgesGLI extend GLI::App
|
|
|
155
173
|
end
|
|
156
174
|
|
|
157
175
|
desc 'Convert the factbase to a human-readable format (YAML, JSON, etc.)'
|
|
176
|
+
arg_name '<factbase> [<output>]'
|
|
158
177
|
command :print do |c|
|
|
159
178
|
c.desc 'Output format (xml, json, or yaml)'
|
|
160
179
|
c.flag([:format], default_value: 'yaml')
|
|
@@ -174,11 +193,13 @@ class JudgesGLI extend GLI::App
|
|
|
174
193
|
end
|
|
175
194
|
|
|
176
195
|
desc 'Inspect the factbase and display all available metadata'
|
|
196
|
+
arg_name '<factbase>'
|
|
177
197
|
command :inspect do |c|
|
|
178
198
|
run_it(c, 'inspect')
|
|
179
199
|
end
|
|
180
200
|
|
|
181
201
|
desc 'Run automated tests for all judges'
|
|
202
|
+
arg_name '<dir>'
|
|
182
203
|
command :test do |c|
|
|
183
204
|
c.desc 'Options to pass to each judge (may be overridden by YAML)'
|
|
184
205
|
c.flag([:o, :option], multiple: true, arg_name: '<key=value>')
|
|
@@ -200,6 +221,7 @@ class JudgesGLI extend GLI::App
|
|
|
200
221
|
end
|
|
201
222
|
|
|
202
223
|
desc 'Push the factbase to the server and unlock it remotely'
|
|
224
|
+
arg_name '<name> <factbase>'
|
|
203
225
|
command :push do |c|
|
|
204
226
|
c.desc 'Authentication token'
|
|
205
227
|
c.flag([:token])
|
|
@@ -223,6 +245,7 @@ class JudgesGLI extend GLI::App
|
|
|
223
245
|
end
|
|
224
246
|
|
|
225
247
|
desc 'Pull the factbase from the server and lock it remotely'
|
|
248
|
+
arg_name '<name> <factbase>'
|
|
226
249
|
command :pull do |c|
|
|
227
250
|
c.desc 'Authentication token'
|
|
228
251
|
c.flag([:token])
|
|
@@ -244,6 +267,7 @@ class JudgesGLI extend GLI::App
|
|
|
244
267
|
end
|
|
245
268
|
|
|
246
269
|
desc 'Download a durable from the server by ID'
|
|
270
|
+
arg_name '<id> <factbase>'
|
|
247
271
|
command :download do |c|
|
|
248
272
|
c.desc 'Authentication token'
|
|
249
273
|
c.flag([:token])
|
|
@@ -263,6 +287,7 @@ class JudgesGLI extend GLI::App
|
|
|
263
287
|
end
|
|
264
288
|
|
|
265
289
|
desc 'Upload a file as a durable to the server'
|
|
290
|
+
arg_name '<file> <path>'
|
|
266
291
|
command :upload do |c|
|
|
267
292
|
c.desc 'Authentication token'
|
|
268
293
|
c.flag([:token])
|
data/features/update.feature
CHANGED
|
@@ -55,14 +55,14 @@ Feature: Update
|
|
|
55
55
|
"""
|
|
56
56
|
n = $fb.insert
|
|
57
57
|
n.type = 'first'
|
|
58
|
-
sleep 1.
|
|
58
|
+
sleep 1.91
|
|
59
59
|
"""
|
|
60
60
|
Then I have a "second/second.rb" file with content:
|
|
61
61
|
"""
|
|
62
62
|
n = $fb.insert
|
|
63
63
|
n.type = 'second'
|
|
64
64
|
"""
|
|
65
|
-
Then I run bin/judges with "--verbose update --quiet --lifetime
|
|
65
|
+
Then I run bin/judges with "--verbose update --quiet --lifetime 4 --timeout 3 --max-cycles 5 . simple.fb"
|
|
66
66
|
Then Stdout contains "Update completed in 2 cycle(s), did 3i/0d/3a"
|
|
67
67
|
And Exit code is zero
|
|
68
68
|
|
|
@@ -214,3 +214,44 @@ Feature: Update
|
|
|
214
214
|
Then Stdout contains "Running delayed"
|
|
215
215
|
Then Stdout contains "3 judge(s) processed"
|
|
216
216
|
And Exit code is zero
|
|
217
|
+
|
|
218
|
+
Scenario: Show statistics for judge execution
|
|
219
|
+
Given I make a temp directory
|
|
220
|
+
Then I have a "alpha/alpha.rb" file with content:
|
|
221
|
+
"""
|
|
222
|
+
$fb.insert.name = 'alpha'
|
|
223
|
+
"""
|
|
224
|
+
Then I have a "beta/beta.rb" file with content:
|
|
225
|
+
"""
|
|
226
|
+
$fb.insert.name = 'beta'
|
|
227
|
+
"""
|
|
228
|
+
Then I run bin/judges with "update --statistics --quiet --max-cycles 1 . stats.fb"
|
|
229
|
+
Then Stdout contains "Judge execution summary:"
|
|
230
|
+
Then Stdout contains "Judge"
|
|
231
|
+
Then Stdout contains "Seconds"
|
|
232
|
+
Then Stdout contains "Changes"
|
|
233
|
+
Then Stdout contains "Cycles"
|
|
234
|
+
Then Stdout contains "Result"
|
|
235
|
+
Then Stdout contains "alpha"
|
|
236
|
+
Then Stdout contains "beta"
|
|
237
|
+
Then Stdout contains "OK"
|
|
238
|
+
And Exit code is zero
|
|
239
|
+
|
|
240
|
+
Scenario: Summarize results in statistics
|
|
241
|
+
Given I make a temp directory
|
|
242
|
+
Then I have a "alpha/alpha.rb" file with content:
|
|
243
|
+
"""
|
|
244
|
+
$fb.insert.name = 'alpha'
|
|
245
|
+
sleep 1.91
|
|
246
|
+
"""
|
|
247
|
+
Then I have a "beta/beta.rb" file with content:
|
|
248
|
+
"""
|
|
249
|
+
$fb.insert.name = 'beta'
|
|
250
|
+
"""
|
|
251
|
+
Then I run bin/judges with "update --statistics --quiet --max-cycles 2 --lifetime 4 --timeout 3 . stats.fb"
|
|
252
|
+
Then Stdout contains "Judge execution summary:"
|
|
253
|
+
Then Stdout contains "alpha"
|
|
254
|
+
Then Stdout contains "beta"
|
|
255
|
+
Then Stdout contains "1xOK"
|
|
256
|
+
Then Stdout contains "1xSKIPPED (timeout)"
|
|
257
|
+
And Exit code is zero
|
data/judges.gemspec
CHANGED
|
@@ -9,7 +9,7 @@ Gem::Specification.new do |s|
|
|
|
9
9
|
s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
|
|
10
10
|
s.required_ruby_version = '>=3.2'
|
|
11
11
|
s.name = 'judges'
|
|
12
|
-
s.version = '0.
|
|
12
|
+
s.version = '0.57.0'
|
|
13
13
|
s.license = 'MIT'
|
|
14
14
|
s.summary = 'Command-Line Tool for a Factbase'
|
|
15
15
|
s.description =
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../judges'
|
|
7
|
+
|
|
8
|
+
# ASCII wrapper for Loog logging facility.
|
|
9
|
+
#
|
|
10
|
+
# This class wraps any Loog logger and converts Unicode symbols to ASCII
|
|
11
|
+
# equivalents when the --ascii option is enabled.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Judges::AsciiLoog
|
|
17
|
+
# Unicode to ASCII symbol mapping
|
|
18
|
+
UNICODE_TO_ASCII = {
|
|
19
|
+
'👍' => '+',
|
|
20
|
+
'👎' => '-',
|
|
21
|
+
'❌' => '!',
|
|
22
|
+
'👉' => '>',
|
|
23
|
+
'✓' => '+',
|
|
24
|
+
'✗' => '!',
|
|
25
|
+
'►' => '>',
|
|
26
|
+
'◄' => '<',
|
|
27
|
+
'▼' => 'v',
|
|
28
|
+
'▲' => '^'
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
# Initialize the ASCII wrapper.
|
|
32
|
+
# @param [Loog] loog The original logging facility to wrap
|
|
33
|
+
def initialize(loog)
|
|
34
|
+
@loog = loog
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Convert Unicode symbols to ASCII equivalents.
|
|
38
|
+
# @param [String] message The message to convert
|
|
39
|
+
# @return [String] The converted message with ASCII symbols
|
|
40
|
+
def to_ascii(message)
|
|
41
|
+
result = message.to_s
|
|
42
|
+
UNICODE_TO_ASCII.each do |unicode, ascii|
|
|
43
|
+
result = result.gsub(unicode, ascii)
|
|
44
|
+
end
|
|
45
|
+
result
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Log an info message, converting Unicode to ASCII.
|
|
49
|
+
# @param [String] message The message to log
|
|
50
|
+
def info(message)
|
|
51
|
+
@loog.info(to_ascii(message))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Log a warning message, converting Unicode to ASCII.
|
|
55
|
+
# @param [String] message The message to log
|
|
56
|
+
def warn(message)
|
|
57
|
+
@loog.warn(to_ascii(message))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Log an error message, converting Unicode to ASCII.
|
|
61
|
+
# @param [String] message The message to log
|
|
62
|
+
def error(message)
|
|
63
|
+
@loog.error(to_ascii(message))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Log a debug message, converting Unicode to ASCII.
|
|
67
|
+
# @param [String] message The message to log
|
|
68
|
+
def debug(message)
|
|
69
|
+
@loog.debug(to_ascii(message))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Delegate all other methods to the original logger.
|
|
73
|
+
def method_missing(method, *, &)
|
|
74
|
+
@loog.send(method, *, &)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Check if the original logger responds to a method.
|
|
78
|
+
def respond_to_missing?(method, include_private = false)
|
|
79
|
+
@loog.respond_to?(method, include_private) || super
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -16,6 +16,7 @@ require_relative '../../judges'
|
|
|
16
16
|
require_relative '../../judges/impex'
|
|
17
17
|
require_relative '../../judges/judges'
|
|
18
18
|
require_relative '../../judges/options'
|
|
19
|
+
require_relative '../../judges/statistics'
|
|
19
20
|
require_relative '../../judges/to_rel'
|
|
20
21
|
|
|
21
22
|
# The +update+ command.
|
|
@@ -88,6 +89,7 @@ class Judges::Update
|
|
|
88
89
|
c = 0
|
|
89
90
|
churn = Factbase::Churn.new
|
|
90
91
|
errors = []
|
|
92
|
+
statistics = opts['statistics'] ? Judges::Statistics.new : nil
|
|
91
93
|
sum = fb.query('(eq what "judges-summary")').each.to_a
|
|
92
94
|
if sum.empty?
|
|
93
95
|
@loog.info('Summary fact not found') unless opts['summary'] == 'off'
|
|
@@ -108,7 +110,7 @@ class Judges::Update
|
|
|
108
110
|
end
|
|
109
111
|
@loog.info("\nStarting cycle ##{c}#{" (out of #{opts['max-cycles']})" if opts['max-cycles']}...")
|
|
110
112
|
end
|
|
111
|
-
delta = cycle(opts, judges, fb, options, errors)
|
|
113
|
+
delta = cycle(opts, judges, fb, options, errors, statistics)
|
|
112
114
|
churn += delta
|
|
113
115
|
impex.export(fb)
|
|
114
116
|
if delta.zero?
|
|
@@ -127,6 +129,7 @@ class Judges::Update
|
|
|
127
129
|
end
|
|
128
130
|
throw :"👍 Update completed in #{c} cycle(s), did #{churn}"
|
|
129
131
|
end
|
|
132
|
+
statistics&.report(@loog)
|
|
130
133
|
return unless %w[add append].include?(opts['summary'])
|
|
131
134
|
summarize(fb, churn, errors, c)
|
|
132
135
|
impex.export(fb)
|
|
@@ -172,9 +175,10 @@ class Judges::Update
|
|
|
172
175
|
# @param [Factbase] fb The factbase
|
|
173
176
|
# @param [Judges::Options] options The options
|
|
174
177
|
# @param [Array<String>] errors List of errors
|
|
178
|
+
# @param [Judges::Statistics] statistics Statistics tracking object (optional)
|
|
175
179
|
# @return [Factbase::Churn] How many modifications have been made
|
|
176
|
-
def cycle(opts, judges, fb, options, errors)
|
|
177
|
-
|
|
180
|
+
def cycle(opts, judges, fb, options, errors, statistics = nil)
|
|
181
|
+
delta = Factbase::Churn.new
|
|
178
182
|
global = {}
|
|
179
183
|
used = 0
|
|
180
184
|
elapsed(@loog, level: Logger::INFO) do
|
|
@@ -182,27 +186,35 @@ class Judges::Update
|
|
|
182
186
|
judges.each_with_index do |judge, i|
|
|
183
187
|
if opts['fail-fast'] && !errors.empty?
|
|
184
188
|
@loog.info("Not running #{judge.name.inspect} due to #{errors.count} errors above, in --fail-fast mode")
|
|
189
|
+
statistics&.record(judge.name, 0, 'SKIPPED (fail-fast)') if include?(opts, judge.name)
|
|
185
190
|
next
|
|
186
191
|
end
|
|
187
192
|
if opts['lifetime'] && opts['timeout']
|
|
188
193
|
remained = @start + opts['lifetime'] - Time.now
|
|
189
|
-
if remained < opts['timeout'] / 16
|
|
194
|
+
if remained < opts['timeout'].to_f / 16
|
|
190
195
|
@loog.info("Not running #{judge.name.inspect}, not enough time left (just #{remained.seconds})")
|
|
196
|
+
statistics&.record(judge.name, 0, 'SKIPPED (timeout)') if include?(opts, judge.name)
|
|
191
197
|
next
|
|
192
198
|
end
|
|
193
199
|
end
|
|
194
200
|
next unless include?(opts, judge.name)
|
|
195
201
|
@loog.info("\n👉 Running #{judge.name} (##{i}) at #{judge.dir.to_rel} (#{@start.ago} already)...")
|
|
196
202
|
used += 1
|
|
203
|
+
start_time = Time.now
|
|
204
|
+
result = 'OK'
|
|
205
|
+
impact = nil
|
|
197
206
|
elapsed(@loog, level: Logger::INFO) do
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
throw :"👍 The '#{judge.name}' judge made zero changes to #{fb.size} facts" if
|
|
201
|
-
throw :"👍 The '#{judge.name}' judge #{
|
|
207
|
+
impact = one_judge(opts, fb, judge, global, options, errors)
|
|
208
|
+
delta += impact
|
|
209
|
+
throw :"👍 The '#{judge.name}' judge made zero changes to #{fb.size} facts" if impact.zero?
|
|
210
|
+
throw :"👍 The '#{judge.name}' judge #{impact} out of #{fb.size} facts"
|
|
202
211
|
end
|
|
203
212
|
rescue StandardError, SyntaxError => e
|
|
204
213
|
@loog.warn(Backtrace.new(e))
|
|
205
214
|
errors << e.message
|
|
215
|
+
result = 'ERROR'
|
|
216
|
+
ensure
|
|
217
|
+
statistics&.record(judge.name, Time.now - start_time, result, impact) if start_time
|
|
206
218
|
end
|
|
207
219
|
throw :"👍 #{done} judge(s) processed" if errors.empty?
|
|
208
220
|
throw :"❌ #{done} judge(s) processed with #{errors.size} errors"
|
|
@@ -215,7 +227,7 @@ class Judges::Update
|
|
|
215
227
|
raise "Failed to update correctly (#{errors.size} errors)" unless opts['quiet']
|
|
216
228
|
@loog.info('Not failing because of the --quiet flag provided')
|
|
217
229
|
end
|
|
218
|
-
|
|
230
|
+
delta
|
|
219
231
|
end
|
|
220
232
|
|
|
221
233
|
# Run a single judge.
|
|
@@ -45,10 +45,15 @@ class Judges::Upload
|
|
|
45
45
|
id = baza.durable_find(jname, name)
|
|
46
46
|
size = File.size(path)
|
|
47
47
|
if id.nil? || id.to_s.strip.empty?
|
|
48
|
-
Tempfile.
|
|
48
|
+
f = Tempfile.new('placeholder')
|
|
49
|
+
begin
|
|
49
50
|
File.write(f.path, 'placeholder')
|
|
51
|
+
f.close
|
|
50
52
|
id = baza.durable_place(jname, f.path)
|
|
51
53
|
@loog.info("Placed a placeholder to new durable '#{name}' in '#{jname}' (ID: #{id})")
|
|
54
|
+
ensure
|
|
55
|
+
f.close unless f.closed?
|
|
56
|
+
f.unlink
|
|
52
57
|
end
|
|
53
58
|
end
|
|
54
59
|
id = id.to_i
|
data/lib/judges/options.rb
CHANGED
|
@@ -144,8 +144,11 @@ class Judges::Options
|
|
|
144
144
|
# Method names are automatically converted to uppercase symbols to match
|
|
145
145
|
# the keys in the options hash.
|
|
146
146
|
#
|
|
147
|
-
#
|
|
148
|
-
#
|
|
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
|
|
149
152
|
# @example Access options as methods
|
|
150
153
|
# options = Judges::Options.new(["token=abc123", "max_speed=100"])
|
|
151
154
|
# options.token # => "abc123"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
require_relative '../judges'
|
|
7
|
+
|
|
8
|
+
# Statistics collector for judge executions.
|
|
9
|
+
#
|
|
10
|
+
# This class collects and aggregates statistics about judge executions
|
|
11
|
+
# across multiple cycles, providing insights into performance and results.
|
|
12
|
+
#
|
|
13
|
+
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
|
14
|
+
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
|
15
|
+
# License:: MIT
|
|
16
|
+
class Judges::Statistics
|
|
17
|
+
# Initialize empty statistics.
|
|
18
|
+
def initialize
|
|
19
|
+
@data = {}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Check if statistics are empty.
|
|
23
|
+
# @return [Boolean] True if no statistics have been collected
|
|
24
|
+
def empty?
|
|
25
|
+
@data.empty?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Record statistics for a judge execution.
|
|
29
|
+
# @param [String] name The judge name
|
|
30
|
+
# @param [Float] time The execution time for this run
|
|
31
|
+
# @param [String] result The result for this run
|
|
32
|
+
# @param [Churn] churn The churn for this run (can be nil)
|
|
33
|
+
def record(name, time, result, churn = nil)
|
|
34
|
+
unless @data[name]
|
|
35
|
+
@data[name] = {
|
|
36
|
+
total_time: 0.0,
|
|
37
|
+
cycles: 0,
|
|
38
|
+
results: [],
|
|
39
|
+
total_churn: nil
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
stats = @data[name]
|
|
43
|
+
stats[:total_time] += time
|
|
44
|
+
stats[:cycles] += 1
|
|
45
|
+
stats[:results] << result
|
|
46
|
+
return unless churn
|
|
47
|
+
if stats[:total_churn]
|
|
48
|
+
stats[:total_churn] += churn
|
|
49
|
+
else
|
|
50
|
+
stats[:total_churn] = churn
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Generate a formatted statistics report.
|
|
55
|
+
# @param [Loog] loog Logging facility for output
|
|
56
|
+
def report(loog)
|
|
57
|
+
return if empty?
|
|
58
|
+
fmt = "%-30s\t%9s\t%7s\t%15s\t%-15s"
|
|
59
|
+
loog.info(
|
|
60
|
+
[
|
|
61
|
+
'Judge execution summary:',
|
|
62
|
+
format(fmt, 'Judge', 'Seconds', 'Cycles', 'Changes', 'Results'),
|
|
63
|
+
format(fmt, '---', '---', '---', '---', '---'),
|
|
64
|
+
@data.sort_by { |_, stats| stats[:total_time] }.reverse.map do |name, stats|
|
|
65
|
+
format(fmt, name, format('%.3f', stats[:total_time]), stats[:cycles],
|
|
66
|
+
stats[:total_churn] ? stats[:total_churn].to_s : 'N/A', summarize(stats[:results]))
|
|
67
|
+
end.join("\n ")
|
|
68
|
+
].join("\n ")
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
# Summarize results across multiple cycles into a compact string.
|
|
75
|
+
# @param [Array<String>] results Array of result strings from different cycles
|
|
76
|
+
# @return [String] Compact summary of results
|
|
77
|
+
def summarize(results)
|
|
78
|
+
return 'N/A' if results.empty?
|
|
79
|
+
counts = results.each_with_object(Hash.new(0)) { |result, hash| hash[result] += 1 }
|
|
80
|
+
return results.first if counts.size == 1
|
|
81
|
+
counts.sort_by { |_, count| -count }.map { |result, count| "#{count}x#{result}" }.join(', ')
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/judges.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: judges
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.57.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yegor Bugayenko
|
|
@@ -280,6 +280,7 @@ files:
|
|
|
280
280
|
- fixtures/try/try.rb
|
|
281
281
|
- judges.gemspec
|
|
282
282
|
- lib/judges.rb
|
|
283
|
+
- lib/judges/ascii_loog.rb
|
|
283
284
|
- lib/judges/categories.rb
|
|
284
285
|
- lib/judges/commands/download.rb
|
|
285
286
|
- lib/judges/commands/eval.rb
|
|
@@ -297,6 +298,7 @@ files:
|
|
|
297
298
|
- lib/judges/judge.rb
|
|
298
299
|
- lib/judges/judges.rb
|
|
299
300
|
- lib/judges/options.rb
|
|
301
|
+
- lib/judges/statistics.rb
|
|
300
302
|
- lib/judges/to_rel.rb
|
|
301
303
|
- package-lock.json
|
|
302
304
|
- package.json
|