judges 0.44.0 → 0.44.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 +4 -4
- data/.github/workflows/copyrights.yml +1 -1
- data/bin/judges +7 -0
- data/features/version.feature +10 -0
- data/judges.gemspec +1 -1
- data/lib/judges/categories.rb +35 -5
- data/lib/judges/commands/update.rb +3 -2
- data/lib/judges/impex.rb +47 -6
- data/lib/judges/judge.rb +33 -11
- data/lib/judges/judges.rb +26 -10
- data/lib/judges/options.rb +77 -4
- data/lib/judges.rb +1 -1
- data/test/commands/test_update.rb +15 -0
- data/test/test_judge.rb +24 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4791985ccc60d62edaf96e7fdf59912d4f5c53bfe21cfedd5d5554087c006324
|
4
|
+
data.tar.gz: e49a5acd43f2c9722e36233d7ed228f00dbbb63df3a7259c03e9b9f224e7b3c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3571c3740ed87fd99a5e56f482f297f1ecec20440c7a924122a1a0bd43d26aa5e492d986adf0de8bd65d2931d2a345af84afb469766ab2ce8a025e4c9014e424
|
7
|
+
data.tar.gz: 61e06fba2897f2fa04214372ee646029134cfd7f9877c2c92afdbe113d9665158da79b5c5257bf3a64dd880d276f26b81984c8b6b8240a166f81c16197f55b01
|
data/bin/judges
CHANGED
@@ -61,6 +61,13 @@ class JudgesGLI extend GLI::App
|
|
61
61
|
true
|
62
62
|
end
|
63
63
|
|
64
|
+
desc 'Print version of the tool'
|
65
|
+
command :version do |c|
|
66
|
+
c.action do
|
67
|
+
@@loog.info(Judges::VERSION)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
64
71
|
desc 'Update the factbase by executing all judges sequentially'
|
65
72
|
command :update do |c|
|
66
73
|
c.desc 'Options to pass to each judge'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Yegor Bugayenko
|
2
|
+
# SPDX-License-Identifier: MIT
|
3
|
+
|
4
|
+
Feature: Version
|
5
|
+
I want to know the version of the gem
|
6
|
+
|
7
|
+
Scenario: Print the version
|
8
|
+
Given I make a temp directory
|
9
|
+
Then I run bin/judges with "version"
|
10
|
+
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.44.
|
12
|
+
s.version = '0.44.1'
|
13
13
|
s.license = 'MIT'
|
14
14
|
s.summary = 'Command-Line Tool for a Factbase'
|
15
15
|
s.description =
|
data/lib/judges/categories.rb
CHANGED
@@ -6,21 +6,51 @@
|
|
6
6
|
require_relative '../judges'
|
7
7
|
|
8
8
|
# Categories of tests.
|
9
|
+
#
|
10
|
+
# This class manages test categories, allowing you to enable or disable
|
11
|
+
# specific categories of tests. It provides a mechanism to filter which
|
12
|
+
# tests should be executed based on their associated categories.
|
13
|
+
#
|
9
14
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
10
15
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
11
16
|
# License:: MIT
|
12
17
|
class Judges::Categories
|
13
|
-
#
|
18
|
+
# Initialize a new Categories instance.
|
19
|
+
#
|
20
|
+
# Creates a categories filter with lists of enabled and disabled categories.
|
21
|
+
# The filter logic works as follows:
|
22
|
+
# - If a category is in the disable list, the test is rejected
|
23
|
+
# - If a category is in the enable list, the test is accepted
|
24
|
+
# - If no categories are enabled (empty enable list), all tests are accepted
|
25
|
+
# unless explicitly disabled
|
26
|
+
#
|
14
27
|
# @param [Array<String>] enable List of categories to enable
|
15
|
-
# @param [Array<String>] disable List of categories to
|
28
|
+
# @param [Array<String>] disable List of categories to disable
|
16
29
|
def initialize(enable, disable)
|
17
30
|
@enable = enable.is_a?(Array) ? enable : []
|
18
31
|
@disable = disable.is_a?(Array) ? disable : []
|
19
32
|
end
|
20
33
|
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
34
|
+
# Check if a test with given categories should be executed.
|
35
|
+
#
|
36
|
+
# Determines whether a test associated with the provided categories
|
37
|
+
# should be executed based on the enable/disable lists configured
|
38
|
+
# during initialization.
|
39
|
+
#
|
40
|
+
# The evaluation logic:
|
41
|
+
# 1. If any category is in the disable list, returns false
|
42
|
+
# 2. If any category is in the enable list, returns true
|
43
|
+
# 3. If the enable list is empty, returns true (all tests allowed)
|
44
|
+
# 4. Otherwise, returns false
|
45
|
+
#
|
46
|
+
# @param [Array<String>, String, nil] cats List of categories associated with the test,
|
47
|
+
# can be a single string or nil
|
48
|
+
# @return [Boolean] true if the test should be executed, false otherwise
|
49
|
+
# @example Check if a test with categories should run
|
50
|
+
# categories = Judges::Categories.new(['important'], ['experimental'])
|
51
|
+
# categories.ok?(['important', 'slow']) # => true
|
52
|
+
# categories.ok?(['experimental']) # => false
|
53
|
+
# categories.ok?(nil) # => true (if enable list is empty)
|
24
54
|
def ok?(cats)
|
25
55
|
cats = [] if cats.nil?
|
26
56
|
cats = [cats] unless cats.is_a?(Array)
|
@@ -159,7 +159,8 @@ class Judges::Update
|
|
159
159
|
elapsed(@loog, level: Logger::INFO) do
|
160
160
|
c = one_judge(opts, fb, judge, global, options, errors)
|
161
161
|
churn += c
|
162
|
-
throw :"👍 The '#{judge.name}' judge
|
162
|
+
throw :"👍 The '#{judge.name}' judge made zero changes to #{fb.size} facts" if c.zero?
|
163
|
+
throw :"👍 The '#{judge.name}' judge #{c} out of #{fb.size} facts"
|
163
164
|
end
|
164
165
|
rescue StandardError, SyntaxError => e
|
165
166
|
@loog.warn(Backtrace.new(e))
|
@@ -187,7 +188,7 @@ class Judges::Update
|
|
187
188
|
# @param [Hash] global Global options
|
188
189
|
# @param [Judges::Options] options The options
|
189
190
|
# @param [Array<String>] errors List of errors
|
190
|
-
# @return [Churn] How many modifications have been made
|
191
|
+
# @return [Factbase::Churn] How many modifications have been made
|
191
192
|
def one_judge(opts, fb, judge, global, options, errors)
|
192
193
|
local = {}
|
193
194
|
start = Time.now
|
data/lib/judges/impex.rb
CHANGED
@@ -10,21 +10,42 @@ require_relative '../judges'
|
|
10
10
|
require_relative '../judges/to_rel'
|
11
11
|
|
12
12
|
# Import/Export of factbases.
|
13
|
+
#
|
14
|
+
# This class provides functionality for importing and exporting Factbase
|
15
|
+
# objects to and from binary files. It handles file I/O operations with
|
16
|
+
# proper logging and error handling.
|
17
|
+
#
|
13
18
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
14
19
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
15
20
|
# License:: MIT
|
16
21
|
class Judges::Impex
|
17
|
-
# Initialize.
|
18
|
-
#
|
22
|
+
# Initialize a new Impex instance.
|
23
|
+
#
|
24
|
+
# @param [Loog] loog Logging facility for recording import/export operations
|
19
25
|
# @param [String] file File path for import/export operations
|
26
|
+
# @example Create an Impex instance
|
27
|
+
# impex = Judges::Impex.new(logger, '/path/to/factbase.fb')
|
20
28
|
def initialize(loog, file)
|
21
29
|
@loog = loog
|
22
30
|
@file = file
|
23
31
|
end
|
24
32
|
|
25
33
|
# Import factbase from file.
|
26
|
-
#
|
27
|
-
#
|
34
|
+
#
|
35
|
+
# Creates a new Factbase instance and imports data from the file specified
|
36
|
+
# during initialization. The operation is timed and logged. If the file
|
37
|
+
# doesn't exist, behavior depends on the strict parameter.
|
38
|
+
#
|
39
|
+
# @param [Boolean] strict Whether to raise error if file doesn't exist.
|
40
|
+
# When true (default), raises an error if file is missing.
|
41
|
+
# When false, logs a message and returns an empty factbase.
|
42
|
+
# @return [Factbase] The imported factbase, or empty factbase if file
|
43
|
+
# doesn't exist and strict is false
|
44
|
+
# @raise [RuntimeError] If file doesn't exist and strict is true
|
45
|
+
# @example Import with strict mode (default)
|
46
|
+
# fb = impex.import # Raises error if file missing
|
47
|
+
# @example Import with non-strict mode
|
48
|
+
# fb = impex.import(strict: false) # Returns empty factbase if file missing
|
28
49
|
def import(strict: true)
|
29
50
|
fb = Factbase.new
|
30
51
|
if File.exist?(@file)
|
@@ -40,8 +61,18 @@ class Judges::Impex
|
|
40
61
|
end
|
41
62
|
|
42
63
|
# Import factbase from file into existing factbase.
|
43
|
-
#
|
64
|
+
#
|
65
|
+
# Imports data from the file into an existing Factbase instance rather
|
66
|
+
# than creating a new one. This is useful when you need to merge data
|
67
|
+
# into an already populated factbase. The operation is timed and logged.
|
68
|
+
#
|
69
|
+
# @param [Factbase] fb The factbase to import into. The imported data
|
70
|
+
# will be added to this existing factbase.
|
44
71
|
# @raise [RuntimeError] If file doesn't exist
|
72
|
+
# @example Import into existing factbase
|
73
|
+
# fb = Factbase.new
|
74
|
+
# # ... populate fb with some data ...
|
75
|
+
# impex.import_to(fb) # Adds data from file to existing facts
|
45
76
|
def import_to(fb)
|
46
77
|
raise "The factbase is absent at #{@file.to_rel}" unless File.exist?(@file)
|
47
78
|
elapsed(@loog, level: Logger::INFO) do
|
@@ -51,7 +82,17 @@ class Judges::Impex
|
|
51
82
|
end
|
52
83
|
|
53
84
|
# Export factbase to file.
|
54
|
-
#
|
85
|
+
#
|
86
|
+
# Exports the given Factbase instance to the file specified during
|
87
|
+
# initialization. Creates any necessary parent directories automatically.
|
88
|
+
# The operation is timed and logged with file size and fact count information.
|
89
|
+
#
|
90
|
+
# @param [Factbase] fb The factbase to export. All facts in this factbase
|
91
|
+
# will be serialized to the binary file format.
|
92
|
+
# @example Export a factbase
|
93
|
+
# fb = Factbase.new
|
94
|
+
# # ... add facts to fb ...
|
95
|
+
# impex.export(fb) # Saves to file specified in constructor
|
55
96
|
def export(fb)
|
56
97
|
elapsed(@loog, level: Logger::INFO) do
|
57
98
|
FileUtils.mkdir_p(File.dirname(@file))
|
data/lib/judges/judge.rb
CHANGED
@@ -28,25 +28,32 @@ class Judges::Judge
|
|
28
28
|
@start = start
|
29
29
|
end
|
30
30
|
|
31
|
-
#
|
32
|
-
#
|
31
|
+
# Returns the string representation of the judge.
|
32
|
+
#
|
33
|
+
# @return [String] The name of the judge (same as the directory name)
|
33
34
|
def to_s
|
34
35
|
name
|
35
36
|
end
|
36
37
|
|
37
|
-
#
|
38
|
+
# Executes the judge script with the provided factbase and configuration.
|
39
|
+
#
|
40
|
+
# This method sets up the execution environment by creating global variables,
|
41
|
+
# loading library files, and running the judge script. It tracks execution time
|
42
|
+
# and captures any errors that occur during execution.
|
38
43
|
#
|
39
|
-
# @param [Factbase] fb The factbase
|
40
|
-
# @param [Hash] global Global options
|
41
|
-
# @param [Hash] local Local options
|
42
|
-
# @param [Judges::Options] options
|
43
|
-
# @return [Factbase::Churn]
|
44
|
+
# @param [Factbase] fb The factbase instance to operate on
|
45
|
+
# @param [Hash] global Global configuration options shared across all judges
|
46
|
+
# @param [Hash] local Local configuration options specific to this judge
|
47
|
+
# @param [Judges::Options] options Command-line options object
|
48
|
+
# @return [Factbase::Churn] Object containing statistics about the changes made to the factbase
|
49
|
+
# @raise [RuntimeError] If the lib directory doesn't exist, the script can't be loaded, or execution fails
|
44
50
|
def run(fb, global, local, options)
|
45
51
|
$fb = Factbase::Tallied.new(fb)
|
46
52
|
$judge = File.basename(@dir)
|
47
53
|
$options = options
|
48
54
|
$loog = @loog
|
49
55
|
$global = global
|
56
|
+
$global.delete(:fb) # to make sure Tallied is always actual
|
50
57
|
$local = local
|
51
58
|
$start = @start
|
52
59
|
options.to_h.each { |k, v| ENV.store(k.to_s, v.to_s) }
|
@@ -74,12 +81,22 @@ class Judges::Judge
|
|
74
81
|
end
|
75
82
|
end
|
76
83
|
|
77
|
-
#
|
84
|
+
# Returns the name of the judge.
|
85
|
+
#
|
86
|
+
# The name is derived from the directory name containing the judge.
|
87
|
+
#
|
88
|
+
# @return [String] The base name of the judge directory
|
78
89
|
def name
|
79
90
|
File.basename(@dir)
|
80
91
|
end
|
81
92
|
|
82
|
-
#
|
93
|
+
# Returns the name of the main Ruby script file for this judge.
|
94
|
+
#
|
95
|
+
# The script file must have the same name as the judge directory with a .rb extension.
|
96
|
+
# For example, if the judge directory is "quality", the script must be "quality.rb".
|
97
|
+
#
|
98
|
+
# @return [String] The filename of the judge script (e.g., "judge_name.rb")
|
99
|
+
# @raise [RuntimeError] If the expected script file is not found in the judge directory
|
83
100
|
def script
|
84
101
|
b = "#{File.basename(@dir)}.rb"
|
85
102
|
files = Dir.glob(File.join(@dir, '*.rb')).map { |f| File.basename(f) }
|
@@ -87,7 +104,12 @@ class Judges::Judge
|
|
87
104
|
b
|
88
105
|
end
|
89
106
|
|
90
|
-
#
|
107
|
+
# Returns all YAML test files in the judge directory.
|
108
|
+
#
|
109
|
+
# Test files are expected to have a .yml extension and contain test data
|
110
|
+
# used to validate the judge's behavior.
|
111
|
+
#
|
112
|
+
# @return [Array<String>] Array of absolute paths to all .yml files in the judge directory
|
91
113
|
def tests
|
92
114
|
Dir.glob(File.join(@dir, '*.yml'))
|
93
115
|
end
|
data/lib/judges/judges.rb
CHANGED
@@ -42,19 +42,30 @@ class Judges::Judges
|
|
42
42
|
@boost = boost
|
43
43
|
end
|
44
44
|
|
45
|
-
#
|
46
|
-
#
|
47
|
-
#
|
48
|
-
#
|
45
|
+
# Retrieves a specific judge by its name.
|
46
|
+
#
|
47
|
+
# The judge must exist as a directory within the judges directory with the given name.
|
48
|
+
#
|
49
|
+
# @param [String] name The name of the judge to retrieve (directory name)
|
50
|
+
# @return [Judges::Judge] The judge object initialized with the found directory
|
51
|
+
# @raise [RuntimeError] If no judge directory exists with the given name
|
49
52
|
def get(name)
|
50
53
|
d = File.absolute_path(File.join(@dir, name))
|
51
54
|
raise "Judge #{name} doesn't exist in #{@dir}" unless File.exist?(d)
|
52
55
|
Judges::Judge.new(d, @lib, @loog, start: @start)
|
53
56
|
end
|
54
57
|
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
+
# Iterates over all valid judges in the directory.
|
59
|
+
#
|
60
|
+
# This method discovers all judge directories, validates them (ensuring they contain
|
61
|
+
# a corresponding .rb file), and yields them in a specific order. The order is
|
62
|
+
# determined by:
|
63
|
+
# 1. Judges whose names match the boost list are placed first
|
64
|
+
# 2. Judges whose names start with the shuffle prefix are randomly reordered
|
65
|
+
# 3. All other judges maintain their alphabetical order
|
66
|
+
#
|
67
|
+
# @yield [Judges::Judge] Yields each valid judge object
|
68
|
+
# @return [Enumerator] Returns an enumerator if no block is given
|
58
69
|
def each(&)
|
59
70
|
return to_enum(__method__) unless block_given?
|
60
71
|
list =
|
@@ -87,9 +98,14 @@ class Judges::Judges
|
|
87
98
|
ret.each(&)
|
88
99
|
end
|
89
100
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
101
|
+
# Iterates over all judges while tracking their index position.
|
102
|
+
#
|
103
|
+
# This method calls the #each method and additionally provides a zero-based
|
104
|
+
# index for each judge yielded. The judges are processed in the same order
|
105
|
+
# as determined by the #each method (with boost and shuffle rules applied).
|
106
|
+
#
|
107
|
+
# @yield [Judges::Judge, Integer] Yields each judge object along with its index (starting from 0)
|
108
|
+
# @return [Integer] The total count of judges processed
|
93
109
|
def each_with_index
|
94
110
|
idx = 0
|
95
111
|
each do |p|
|
data/lib/judges/options.rb
CHANGED
@@ -7,25 +7,62 @@ require 'others'
|
|
7
7
|
require_relative '../judges'
|
8
8
|
|
9
9
|
# Options for Ruby scripts in the judges.
|
10
|
+
#
|
11
|
+
# This class manages configuration options that can be passed to judge scripts.
|
12
|
+
# Options are key-value pairs that can be provided in various formats and are
|
13
|
+
# normalized into a hash with symbol keys. Values are automatically converted
|
14
|
+
# to appropriate types (integers for numeric strings, etc.).
|
15
|
+
#
|
16
|
+
# The class also provides dynamic method access to options using the 'others' gem,
|
17
|
+
# allowing options to be accessed as method calls.
|
18
|
+
#
|
10
19
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
11
20
|
# Copyright:: Copyright (c) 2024-2025 Yegor Bugayenko
|
12
21
|
# License:: MIT
|
13
22
|
class Judges::Options
|
14
|
-
#
|
15
|
-
#
|
23
|
+
# Initialize a new Options instance.
|
24
|
+
#
|
25
|
+
# @param [Array<String>, String, Hash, nil] pairs List of key-value pairs.
|
26
|
+
# Can be provided as:
|
27
|
+
# - Array of strings: ["token=af73cd3", "max_speed=1"]
|
28
|
+
# - Comma-separated string: "token=af73cd3,max_speed=1"
|
29
|
+
# - Hash: { token: "af73cd3", max_speed: 1 }
|
30
|
+
# - nil: Creates empty options
|
31
|
+
# @example Initialize with array
|
32
|
+
# options = Judges::Options.new(["token=abc123", "debug=true"])
|
33
|
+
# @example Initialize with string
|
34
|
+
# options = Judges::Options.new("token=abc123,debug")
|
35
|
+
# @example Initialize with hash
|
36
|
+
# options = Judges::Options.new({ token: "abc123", debug: true })
|
16
37
|
def initialize(pairs = nil)
|
17
38
|
@pairs = pairs
|
18
39
|
end
|
19
40
|
|
20
41
|
# Check if options are empty.
|
21
|
-
#
|
42
|
+
#
|
43
|
+
# @return [Boolean] true if no options are set, false otherwise
|
44
|
+
# @example Check if options are empty
|
45
|
+
# options = Judges::Options.new
|
46
|
+
# options.empty? # => true
|
47
|
+
# options = Judges::Options.new(["key=value"])
|
48
|
+
# options.empty? # => false
|
22
49
|
def empty?
|
23
50
|
to_h.empty?
|
24
51
|
end
|
25
52
|
|
26
53
|
# Merge with another Options object.
|
54
|
+
#
|
55
|
+
# Creates a new Options instance containing values from both this instance
|
56
|
+
# and the other. Values from the other Options object override values
|
57
|
+
# with the same key in this instance.
|
58
|
+
#
|
27
59
|
# @param [Judges::Options] other The other options to merge
|
28
60
|
# @return [Judges::Options] A new Options object with merged values
|
61
|
+
# @example Merge two Options objects
|
62
|
+
# opts1 = Judges::Options.new(["token=abc", "debug=true"])
|
63
|
+
# opts2 = Judges::Options.new(["token=xyz", "verbose=true"])
|
64
|
+
# merged = opts1 + opts2
|
65
|
+
# # merged now has token=xyz, debug=true, verbose=true
|
29
66
|
def +(other)
|
30
67
|
h = to_h
|
31
68
|
other.to_h.each do |k, v|
|
@@ -34,7 +71,19 @@ class Judges::Options
|
|
34
71
|
Judges::Options.new(h)
|
35
72
|
end
|
36
73
|
|
37
|
-
# Convert
|
74
|
+
# Convert options to a string representation.
|
75
|
+
#
|
76
|
+
# Creates a human-readable string representation of all options,
|
77
|
+
# suitable for logging. Sensitive values (longer than 8 characters)
|
78
|
+
# are partially masked with asterisks for security.
|
79
|
+
#
|
80
|
+
# @return [String] Formatted string with each option on a new line
|
81
|
+
# @example Convert to string
|
82
|
+
# options = Judges::Options.new(["token=supersecrettoken", "debug=true"])
|
83
|
+
# puts options.to_s
|
84
|
+
# # Output:
|
85
|
+
# # debug → "true"
|
86
|
+
# # token → "supe****oken"
|
38
87
|
def to_s
|
39
88
|
to_h.map do |k, v|
|
40
89
|
v = v.to_s
|
@@ -44,7 +93,18 @@ class Judges::Options
|
|
44
93
|
end
|
45
94
|
|
46
95
|
# Convert options to hash.
|
96
|
+
#
|
97
|
+
# Converts the raw pairs into a normalized hash with the following transformations:
|
98
|
+
# - Keys are converted to uppercase symbols
|
99
|
+
# - Values without equals sign get 'true' as value
|
100
|
+
# - Numeric strings are converted to integers
|
101
|
+
# - Leading/trailing whitespace is stripped
|
102
|
+
# - Empty or nil keys are rejected
|
103
|
+
#
|
47
104
|
# @return [Hash] The options as a hash with symbol keys
|
105
|
+
# @example Convert to hash
|
106
|
+
# options = Judges::Options.new("token=abc123,max_speed=100,debug")
|
107
|
+
# options.to_h # => { TOKEN: "abc123", MAX_SPEED: 100, DEBUG: "true" }
|
48
108
|
def to_h
|
49
109
|
@to_h ||=
|
50
110
|
begin
|
@@ -72,6 +132,19 @@ class Judges::Options
|
|
72
132
|
end
|
73
133
|
|
74
134
|
# Get option by name.
|
135
|
+
#
|
136
|
+
# This method is implemented using the 'others' gem, which provides
|
137
|
+
# dynamic method handling. It allows accessing options as method calls.
|
138
|
+
# Method names are automatically converted to uppercase symbols to match
|
139
|
+
# the keys in the options hash.
|
140
|
+
#
|
141
|
+
# @param [Symbol, String] method_name The option name to retrieve
|
142
|
+
# @return [Object, nil] The value of the option, or nil if not found
|
143
|
+
# @example Access options as methods
|
144
|
+
# options = Judges::Options.new(["token=abc123", "max_speed=100"])
|
145
|
+
# options.token # => "abc123"
|
146
|
+
# options.max_speed # => 100
|
147
|
+
# options.missing_option # => nil
|
75
148
|
others do |*args|
|
76
149
|
to_h[args[0].upcase.to_sym]
|
77
150
|
end
|
data/lib/judges.rb
CHANGED
@@ -157,6 +157,21 @@ class TestUpdate < Minitest::Test
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
+
def test_isolates_churns
|
161
|
+
Dir.mktmpdir do |d|
|
162
|
+
save_it(File.join(d, 'first/first.rb'), '$global[:fb] ||= $fb; 2 + 2')
|
163
|
+
save_it(File.join(d, 'second/second.rb'), '$global[:fb] ||= $fb; $global[:fb].insert')
|
164
|
+
file = File.join(d, 'base.fb')
|
165
|
+
Judges::Update.new(Loog::VERBOSE).run(
|
166
|
+
{ 'max-cycles' => 3, 'boost' => 'first' },
|
167
|
+
[d, file]
|
168
|
+
)
|
169
|
+
fb = Factbase.new
|
170
|
+
fb.import(File.binread(file))
|
171
|
+
assert_equal(3, fb.size)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
160
175
|
def test_fails_when_no_judges_used
|
161
176
|
assert_raises(StandardError) do
|
162
177
|
Dir.mktmpdir do |d|
|
data/test/test_judge.rb
CHANGED
@@ -25,6 +25,30 @@ class TestJudge < Minitest::Test
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
def test_counts_churn
|
29
|
+
Dir.mktmpdir do |d|
|
30
|
+
save_it(File.join(d, "#{File.basename(d)}.rb"), '$fb.insert.foo = 42')
|
31
|
+
judge = Judges::Judge.new(d, nil, Loog::NULL)
|
32
|
+
fb = Factbase.new
|
33
|
+
c = judge.run(fb, {}, {}, {})
|
34
|
+
assert_equal(1, c.inserted)
|
35
|
+
assert_equal(0, c.deleted)
|
36
|
+
assert_equal(1, c.added)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_counts_churn_after_txn
|
41
|
+
Dir.mktmpdir do |d|
|
42
|
+
save_it(File.join(d, "#{File.basename(d)}.rb"), '$fb.txn { |fbt| fbt.insert.foo = 42 }')
|
43
|
+
judge = Judges::Judge.new(d, nil, Loog::NULL)
|
44
|
+
fb = Factbase.new
|
45
|
+
c = judge.run(fb, {}, {}, {})
|
46
|
+
assert_equal(1, c.inserted)
|
47
|
+
assert_equal(0, c.deleted)
|
48
|
+
assert_equal(1, c.added)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
28
52
|
def test_run_isolated
|
29
53
|
Dir.mktmpdir do |d|
|
30
54
|
save_it(File.join(d, "#{File.basename(d)}.rb"), '$fb.insert')
|
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.44.
|
4
|
+
version: 0.44.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yegor Bugayenko
|
@@ -286,6 +286,7 @@ files:
|
|
286
286
|
- features/test.feature
|
287
287
|
- features/trim.feature
|
288
288
|
- features/update.feature
|
289
|
+
- features/version.feature
|
289
290
|
- fixtures/guess/guess.rb
|
290
291
|
- fixtures/guess/guess_made.yml
|
291
292
|
- fixtures/guess/skipped.yml
|