rfcxml 0.3.0 → 0.4.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/.github/workflows/roundtrip.yml +79 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +9 -3
- data/.rubocop_todo.yml +86 -20
- data/Gemfile +3 -1
- data/README.adoc +255 -35
- data/Rakefile +55 -0
- data/lib/rfcxml/v3/abstract.rb +2 -1
- data/lib/rfcxml/v3/address.rb +2 -1
- data/lib/rfcxml/v3/annotation.rb +2 -1
- data/lib/rfcxml/v3/area.rb +1 -1
- data/lib/rfcxml/v3/artset.rb +2 -1
- data/lib/rfcxml/v3/artwork.rb +13 -10
- data/lib/rfcxml/v3/aside.rb +2 -1
- data/lib/rfcxml/v3/author.rb +16 -9
- data/lib/rfcxml/v3/back.rb +2 -1
- data/lib/rfcxml/v3/bcp14.rb +1 -1
- data/lib/rfcxml/v3/blockquote.rb +2 -1
- data/lib/rfcxml/v3/boilerplate.rb +2 -1
- data/lib/rfcxml/v3/br.rb +1 -1
- data/lib/rfcxml/v3/c.rb +2 -1
- data/lib/rfcxml/v3/city.rb +1 -1
- data/lib/rfcxml/v3/cityarea.rb +1 -1
- data/lib/rfcxml/v3/code.rb +1 -1
- data/lib/rfcxml/v3/contact.rb +2 -1
- data/lib/rfcxml/v3/country.rb +1 -1
- data/lib/rfcxml/v3/cref.rb +2 -1
- data/lib/rfcxml/v3/date.rb +4 -4
- data/lib/rfcxml/v3/dd.rb +2 -1
- data/lib/rfcxml/v3/displayreference.rb +1 -1
- data/lib/rfcxml/v3/dl.rb +2 -1
- data/lib/rfcxml/v3/dt.rb +2 -1
- data/lib/rfcxml/v3/email.rb +1 -1
- data/lib/rfcxml/v3/eref.rb +1 -1
- data/lib/rfcxml/v3/extaddr.rb +1 -1
- data/lib/rfcxml/v3/facsimile.rb +1 -1
- data/lib/rfcxml/v3/figure.rb +17 -11
- data/lib/rfcxml/v3/format.rb +1 -1
- data/lib/rfcxml/v3/front.rb +2 -1
- data/lib/rfcxml/v3/iref.rb +4 -3
- data/lib/rfcxml/v3/keyword.rb +1 -1
- data/lib/rfcxml/v3/li.rb +2 -1
- data/lib/rfcxml/v3/link.rb +1 -1
- data/lib/rfcxml/v3/list.rb +2 -1
- data/lib/rfcxml/v3/middle.rb +2 -1
- data/lib/rfcxml/v3/name.rb +2 -1
- data/lib/rfcxml/v3/note.rb +2 -1
- data/lib/rfcxml/v3/ol.rb +5 -2
- data/lib/rfcxml/v3/organization.rb +10 -5
- data/lib/rfcxml/v3/phone.rb +1 -1
- data/lib/rfcxml/v3/pobox.rb +1 -1
- data/lib/rfcxml/v3/postal.rb +2 -1
- data/lib/rfcxml/v3/postal_line.rb +1 -1
- data/lib/rfcxml/v3/postamble.rb +2 -1
- data/lib/rfcxml/v3/preamble.rb +2 -1
- data/lib/rfcxml/v3/refcontent.rb +2 -1
- data/lib/rfcxml/v3/reference.rb +7 -4
- data/lib/rfcxml/v3/referencegroup.rb +2 -1
- data/lib/rfcxml/v3/references.rb +2 -1
- data/lib/rfcxml/v3/region.rb +1 -1
- data/lib/rfcxml/v3/relref.rb +1 -1
- data/lib/rfcxml/v3/rfc.rb +60 -12
- data/lib/rfcxml/v3/section.rb +11 -4
- data/lib/rfcxml/v3/series_info.rb +5 -4
- data/lib/rfcxml/v3/sortingcode.rb +1 -1
- data/lib/rfcxml/v3/sourcecode.rb +13 -9
- data/lib/rfcxml/v3/spanx.rb +1 -1
- data/lib/rfcxml/v3/street.rb +1 -1
- data/lib/rfcxml/v3/strong.rb +2 -1
- data/lib/rfcxml/v3/sub.rb +2 -1
- data/lib/rfcxml/v3/sup.rb +2 -1
- data/lib/rfcxml/v3/table.rb +1 -1
- data/lib/rfcxml/v3/tbody.rb +1 -1
- data/lib/rfcxml/v3/td.rb +23 -4
- data/lib/rfcxml/v3/text.rb +2 -1
- data/lib/rfcxml/v3/texttable.rb +12 -6
- data/lib/rfcxml/v3/tfoot.rb +1 -1
- data/lib/rfcxml/v3/th.rb +1 -1
- data/lib/rfcxml/v3/thead.rb +1 -1
- data/lib/rfcxml/v3/title.rb +3 -2
- data/lib/rfcxml/v3/toc.rb +1 -1
- data/lib/rfcxml/v3/tr.rb +3 -1
- data/lib/rfcxml/v3/tt.rb +1 -1
- data/lib/rfcxml/v3/ttcol.rb +4 -2
- data/lib/rfcxml/v3/u.rb +1 -1
- data/lib/rfcxml/v3/ul.rb +10 -4
- data/lib/rfcxml/v3/uri.rb +1 -1
- data/lib/rfcxml/v3/vspace.rb +1 -1
- data/lib/rfcxml/v3/workgroup.rb +1 -1
- data/lib/rfcxml/v3/xref.rb +2 -1
- data/lib/rfcxml/version.rb +1 -1
- data/lib/rfcxml.rb +1 -0
- data/scripts/README.md +110 -0
- data/scripts/roundtrip_test.rb +361 -0
- metadata +5 -2
data/scripts/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# RFC XML Round-Trip Test Script
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Test round-trip parsing of RFC XML v3 files using Canon gem for semantic comparison.
|
|
6
|
+
|
|
7
|
+
**Process:** XML → Parse → Serialize → Compare
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Test single file
|
|
13
|
+
ruby scripts/roundtrip_test.rb spec/xmlsource-rfc8650-latest/rfc8704.xml
|
|
14
|
+
|
|
15
|
+
# Test multiple files
|
|
16
|
+
ruby scripts/roundtrip_test.rb file1.xml file2.xml file3.xml
|
|
17
|
+
|
|
18
|
+
# Test all files in directory
|
|
19
|
+
ruby scripts/roundtrip_test.rb spec/xmlsource-rfc8650-latest/
|
|
20
|
+
|
|
21
|
+
# Test with glob pattern
|
|
22
|
+
ruby scripts/roundtrip_test.rb "spec/xmlsource-rfc8650-latest/rfc87*.xml"
|
|
23
|
+
|
|
24
|
+
# Test all 920 files (default)
|
|
25
|
+
ruby scripts/roundtrip_test.rb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Environment Variables
|
|
29
|
+
|
|
30
|
+
| Variable | Default | Description |
|
|
31
|
+
|-----------|---------|--------------------------------|
|
|
32
|
+
| `THREADS` | 8 | Number of worker threads |
|
|
33
|
+
| `VERBOSE` | false | Show per-file progress |
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
THREADS=1 VERBOSE=1 ruby scripts/roundtrip_test.rb file.xml
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Output
|
|
40
|
+
|
|
41
|
+
Test results are written to `tmp/roundtrip-results-{timestamp}/`:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
tmp/roundtrip-results-20260318_200000/
|
|
45
|
+
SUMMARY.yml # Overall summary (YAML)
|
|
46
|
+
PASS_{filename} # Empty marker for passed tests
|
|
47
|
+
FAIL_{filename}.yml # Structured failure details
|
|
48
|
+
ERROR_{filename}.yml # Structured error details
|
|
49
|
+
SOURCE_{filename} # Round-tripped output for debugging
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### SUMMARY.yml Structure
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
timestamp: "2026-03-18T20:00:00+08:00"
|
|
56
|
+
configuration:
|
|
57
|
+
threads: 8
|
|
58
|
+
files_tested: 920
|
|
59
|
+
elapsed_seconds: 664.75
|
|
60
|
+
results:
|
|
61
|
+
passed: 833
|
|
62
|
+
failed: 83
|
|
63
|
+
errors: 4
|
|
64
|
+
pass_rate: "90.5%"
|
|
65
|
+
failed_files:
|
|
66
|
+
- rfc8704.xml
|
|
67
|
+
- rfc8705.xml
|
|
68
|
+
error_files:
|
|
69
|
+
- rfc8650.xml
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### FAIL_*.yml Structure
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
source_file: spec/xmlsource-rfc8650-latest/rfc8704.xml
|
|
76
|
+
status: failed
|
|
77
|
+
normative_differences: 1
|
|
78
|
+
total_differences: 267
|
|
79
|
+
differences:
|
|
80
|
+
- path: /rfc[1]/back[1]/references/reference/front/author
|
|
81
|
+
dimension: attribute_presence
|
|
82
|
+
reason: "only in first: initials, surname"
|
|
83
|
+
normative: true
|
|
84
|
+
formatting:
|
|
85
|
+
attributes_before: { initials: "", surname: "" }
|
|
86
|
+
attributes_after: {}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Known Issues
|
|
90
|
+
|
|
91
|
+
### Threading Bug (4 files)
|
|
92
|
+
|
|
93
|
+
When using `THREADS > 1`, some files fail with:
|
|
94
|
+
```
|
|
95
|
+
NoMethodError: undefined method 'transform' for nil
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Affected files: rfc8650.xml, rfc8651.xml, rfc8654.xml, rfc8657.xml
|
|
99
|
+
|
|
100
|
+
**Workaround:** Use `THREADS=1` for these files.
|
|
101
|
+
|
|
102
|
+
### Empty Attribute Handling
|
|
103
|
+
|
|
104
|
+
Some models need `value_map: { to: { empty: :empty } }` to preserve empty string attributes during round-trip.
|
|
105
|
+
|
|
106
|
+
## Dependencies
|
|
107
|
+
|
|
108
|
+
- `rfcxml` gem - RFC XML parsing
|
|
109
|
+
- `canon` gem - Semantic XML comparison
|
|
110
|
+
- `lutaml-model` - XML serialization
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# RFC XML Round-Trip Test Script
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# ruby scripts/roundtrip_test.rb # All 920 files
|
|
8
|
+
# ruby scripts/roundtrip_test.rb file.xml # Single file
|
|
9
|
+
# ruby scripts/roundtrip_test.rb file1.xml file2.xml # Multiple files
|
|
10
|
+
# ruby scripts/roundtrip_test.rb "spec/*.xml" # Glob pattern
|
|
11
|
+
# ruby scripts/roundtrip_test.rb spec/xmlsource-rfc8650-latest/ # Directory
|
|
12
|
+
#
|
|
13
|
+
# Environment:
|
|
14
|
+
# THREADS=n - Number of threads (default: 8)
|
|
15
|
+
# VERBOSE=1 - Show per-file progress
|
|
16
|
+
#
|
|
17
|
+
# Output:
|
|
18
|
+
# tmp/roundtrip-results-{timestamp}/
|
|
19
|
+
# SUMMARY.yml - Overall summary
|
|
20
|
+
# PASS_{filename}.xml # Empty marker for passed tests
|
|
21
|
+
# FAIL_{filename}.yml # Structured failure details
|
|
22
|
+
# ERROR_{filename}.yml # Structured error details
|
|
23
|
+
# SOURCE_{filename}.xml # Round-tripped output for debugging
|
|
24
|
+
#
|
|
25
|
+
|
|
26
|
+
require "bundler/setup"
|
|
27
|
+
require "rfcxml"
|
|
28
|
+
require "canon"
|
|
29
|
+
require "yaml"
|
|
30
|
+
require "fileutils"
|
|
31
|
+
require "time"
|
|
32
|
+
|
|
33
|
+
class RoundTripTester
|
|
34
|
+
DEFAULT_THREADS = 8
|
|
35
|
+
DEFAULT_XML_DIR = File.expand_path("../spec/xmlsource-rfc8650-latest",
|
|
36
|
+
__dir__)
|
|
37
|
+
TMP_DIR = File.expand_path("../tmp", __dir__)
|
|
38
|
+
|
|
39
|
+
attr_reader :results_dir
|
|
40
|
+
|
|
41
|
+
def initialize(files:, threads: DEFAULT_THREADS, verbose: false)
|
|
42
|
+
@files = files
|
|
43
|
+
@threads = threads
|
|
44
|
+
@verbose = verbose
|
|
45
|
+
@mutex = Mutex.new
|
|
46
|
+
@processed = 0
|
|
47
|
+
@results = []
|
|
48
|
+
@start_time = nil
|
|
49
|
+
|
|
50
|
+
# Create temp results directory
|
|
51
|
+
timestamp = Time.now.strftime("%Y%m%d_%H%M%S")
|
|
52
|
+
@results_dir = File.join(TMP_DIR, "roundtrip-results-#{timestamp}")
|
|
53
|
+
FileUtils.mkdir_p(@results_dir)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def run
|
|
57
|
+
puts "=" * 70
|
|
58
|
+
puts "RFC XML Round-Trip Test"
|
|
59
|
+
puts "=" * 70
|
|
60
|
+
puts "Files: #{@files.size}"
|
|
61
|
+
puts "Threads: #{@threads}"
|
|
62
|
+
puts "Output: #{@results_dir}"
|
|
63
|
+
puts "=" * 70
|
|
64
|
+
|
|
65
|
+
@start_time = Time.now
|
|
66
|
+
|
|
67
|
+
if @threads == 1 || @files.size == 1
|
|
68
|
+
run_sequential
|
|
69
|
+
else
|
|
70
|
+
run_parallel
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
write_summary
|
|
74
|
+
print_report
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def run_sequential
|
|
80
|
+
@files.each_with_index do |file, i|
|
|
81
|
+
test_file(file)
|
|
82
|
+
print "\r Progress: #{i + 1}/#{@files.size}" unless @verbose
|
|
83
|
+
end
|
|
84
|
+
puts
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def run_parallel
|
|
88
|
+
queue = Queue.new
|
|
89
|
+
@files.each { |f| queue << f }
|
|
90
|
+
|
|
91
|
+
workers = Array.new(@threads) { Thread.new { worker(queue) } }
|
|
92
|
+
monitor = Thread.new { progress_monitor(queue) }
|
|
93
|
+
|
|
94
|
+
workers.each(&:join)
|
|
95
|
+
monitor.kill
|
|
96
|
+
puts
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def worker(queue)
|
|
100
|
+
until queue.empty?
|
|
101
|
+
file = queue.pop(true)
|
|
102
|
+
test_file(file)
|
|
103
|
+
end
|
|
104
|
+
rescue ThreadError
|
|
105
|
+
# Queue empty
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def test_file(filepath)
|
|
109
|
+
basename = File.basename(filepath)
|
|
110
|
+
result = perform_test(filepath, basename)
|
|
111
|
+
|
|
112
|
+
@mutex.synchronize do
|
|
113
|
+
@results << result
|
|
114
|
+
@processed += 1
|
|
115
|
+
puts " [#{@processed}/#{@files.size}] #{result[:status].upcase}: #{basename}" if @verbose
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def perform_test(filepath, basename)
|
|
120
|
+
# Read input
|
|
121
|
+
input = begin
|
|
122
|
+
File.read(filepath)
|
|
123
|
+
rescue StandardError => e
|
|
124
|
+
return build_error_result(basename, "Read", e)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Parse
|
|
128
|
+
parsed = begin
|
|
129
|
+
Rfcxml::V3::Rfc.from_xml(input)
|
|
130
|
+
rescue StandardError => e
|
|
131
|
+
return build_error_result(basename, "Parse", e)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Serialize
|
|
135
|
+
output = begin
|
|
136
|
+
parsed.to_xml(pretty: true, declaration: true, encoding: "utf-8")
|
|
137
|
+
rescue StandardError => e
|
|
138
|
+
return build_error_result(basename, "Serialize", e)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Compare using Canon DOM diff with explicit match options
|
|
142
|
+
# Using DOM diff with attribute_order: ignore and attribute_values: normalize
|
|
143
|
+
# handles round-trip differences correctly
|
|
144
|
+
comparison = begin
|
|
145
|
+
Canon::Comparison.equivalent?(
|
|
146
|
+
output,
|
|
147
|
+
input,
|
|
148
|
+
diff_algorithm: :dom,
|
|
149
|
+
format: :xml,
|
|
150
|
+
match: {
|
|
151
|
+
attribute_order: :ignore,
|
|
152
|
+
attribute_values: :normalize,
|
|
153
|
+
text_content: :normalize,
|
|
154
|
+
structural_whitespace: :ignore,
|
|
155
|
+
},
|
|
156
|
+
verbose: true,
|
|
157
|
+
)
|
|
158
|
+
rescue StandardError => e
|
|
159
|
+
return build_error_result(basename, "Compare", e)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Check result
|
|
163
|
+
equivalent = comparison.respond_to?(:equivalent?) ? comparison.equivalent? : comparison
|
|
164
|
+
|
|
165
|
+
if equivalent
|
|
166
|
+
# Pass - write empty marker file
|
|
167
|
+
write_pass_marker(basename)
|
|
168
|
+
{ file: basename, status: :pass }
|
|
169
|
+
else
|
|
170
|
+
# Fail - write detailed report and source
|
|
171
|
+
differences = extract_differences(comparison)
|
|
172
|
+
write_fail_report(basename, filepath, differences)
|
|
173
|
+
write_source_output(basename, output)
|
|
174
|
+
{ file: basename, status: :fail, differences: differences }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def build_error_result(basename, phase, error)
|
|
179
|
+
error_info = {
|
|
180
|
+
phase: phase,
|
|
181
|
+
error_class: error.class.name,
|
|
182
|
+
error_message: error.message,
|
|
183
|
+
backtrace: error.backtrace&.first(5),
|
|
184
|
+
}
|
|
185
|
+
write_error_report(basename, error_info)
|
|
186
|
+
{ file: basename, status: :error, error: error_info }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def extract_differences(comparison)
|
|
190
|
+
return [] unless comparison.respond_to?(:differences)
|
|
191
|
+
|
|
192
|
+
comparison.differences.map do |diff|
|
|
193
|
+
{
|
|
194
|
+
path: diff.path,
|
|
195
|
+
dimension: diff.dimension,
|
|
196
|
+
reason: diff.reason,
|
|
197
|
+
normative: diff.normative,
|
|
198
|
+
formatting: diff.formatting,
|
|
199
|
+
attributes_before: diff.attributes_before&.to_h,
|
|
200
|
+
attributes_after: diff.attributes_after&.to_h,
|
|
201
|
+
}
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def write_pass_marker(basename)
|
|
206
|
+
path = File.join(@results_dir, "PASS_#{basename}")
|
|
207
|
+
FileUtils.touch(path)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def write_fail_report(basename, source_path, differences)
|
|
211
|
+
normative = differences.select { |d| d[:normative] }
|
|
212
|
+
|
|
213
|
+
report = {
|
|
214
|
+
source_file: source_path,
|
|
215
|
+
status: :failed,
|
|
216
|
+
normative_differences: normative.size,
|
|
217
|
+
total_differences: differences.size,
|
|
218
|
+
differences: normative.first(20), # Limit to first 20 normative diffs
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
safe_name = basename.gsub(/[^\w.-]/, "_")
|
|
222
|
+
path = File.join(@results_dir, "FAIL_#{safe_name}.yml")
|
|
223
|
+
File.write(path, report.to_yaml)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def write_error_report(basename, error_info)
|
|
227
|
+
report = {
|
|
228
|
+
source_file: basename,
|
|
229
|
+
status: :error,
|
|
230
|
+
**error_info,
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
safe_name = basename.gsub(/[^\w.-]/, "_")
|
|
234
|
+
path = File.join(@results_dir, "ERROR_#{safe_name}.yml")
|
|
235
|
+
File.write(path, report.to_yaml)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def write_source_output(basename, output)
|
|
239
|
+
safe_name = basename.gsub(/[^\w.-]/, "_")
|
|
240
|
+
path = File.join(@results_dir, "SOURCE_#{safe_name}")
|
|
241
|
+
File.write(path, output)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def write_summary
|
|
245
|
+
passed = @results.select { |r| r[:status] == :pass }
|
|
246
|
+
failed = @results.select { |r| r[:status] == :fail }
|
|
247
|
+
errors = @results.select { |r| r[:status] == :error }
|
|
248
|
+
|
|
249
|
+
summary = {
|
|
250
|
+
timestamp: Time.now.iso8601,
|
|
251
|
+
configuration: {
|
|
252
|
+
threads: @threads,
|
|
253
|
+
files_tested: @files.size,
|
|
254
|
+
elapsed_seconds: (Time.now - @start_time).round(2),
|
|
255
|
+
},
|
|
256
|
+
results: {
|
|
257
|
+
passed: passed.size,
|
|
258
|
+
failed: failed.size,
|
|
259
|
+
errors: errors.size,
|
|
260
|
+
pass_rate: "#{(passed.size.to_f / @files.size * 100).round(1)}%",
|
|
261
|
+
},
|
|
262
|
+
failed_files: failed.map { |r| r[:file] },
|
|
263
|
+
error_files: errors.map { |r| r[:file] },
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
path = File.join(@results_dir, "SUMMARY.yml")
|
|
267
|
+
File.write(path, summary.to_yaml)
|
|
268
|
+
|
|
269
|
+
@summary = summary
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def print_report
|
|
273
|
+
elapsed = Time.now - @start_time
|
|
274
|
+
passed = @results.count { |r| r[:status] == :pass }
|
|
275
|
+
failed = @results.count { |r| r[:status] == :fail }
|
|
276
|
+
errors = @results.count { |r| r[:status] == :error }
|
|
277
|
+
|
|
278
|
+
puts
|
|
279
|
+
puts "=" * 70
|
|
280
|
+
puts "RESULTS"
|
|
281
|
+
puts "=" * 70
|
|
282
|
+
puts "Elapsed: #{elapsed.round(2)}s"
|
|
283
|
+
puts "PASSED: #{passed}/#{@files.size} (#{pct(passed)}%)"
|
|
284
|
+
puts "FAILED: #{failed}/#{@files.size} (#{pct(failed)}%)"
|
|
285
|
+
puts "ERRORS: #{errors}/#{@files.size} (#{pct(errors)}%)"
|
|
286
|
+
puts
|
|
287
|
+
puts "Output dir: #{@results_dir}"
|
|
288
|
+
|
|
289
|
+
if failed.positive? || errors.positive?
|
|
290
|
+
puts
|
|
291
|
+
puts "See #{@results_dir}/ for detailed reports:"
|
|
292
|
+
puts " FAIL_*.yml - Failure details"
|
|
293
|
+
puts " ERROR_*.yml - Error details"
|
|
294
|
+
puts " SOURCE_*.xml - Round-tripped output"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def pct(count)
|
|
299
|
+
return 0.0 if @files.empty?
|
|
300
|
+
|
|
301
|
+
(count.to_f / @files.size * 100).round(1)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def progress_monitor(_queue)
|
|
305
|
+
loop do
|
|
306
|
+
sleep 1
|
|
307
|
+
@mutex.synchronize do
|
|
308
|
+
return if @processed >= @files.size
|
|
309
|
+
|
|
310
|
+
print "\r Progress: #{@processed}/#{@files.size} (#{pct(@processed)}%) "
|
|
311
|
+
$stdout.flush
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# === CLI Argument Parsing ===
|
|
318
|
+
|
|
319
|
+
def parse_args(args)
|
|
320
|
+
threads = ENV.fetch("THREADS", RoundTripTester::DEFAULT_THREADS).to_i
|
|
321
|
+
verbose = ENV["VERBOSE"] || ENV.fetch("V", nil)
|
|
322
|
+
|
|
323
|
+
files = []
|
|
324
|
+
|
|
325
|
+
args.each do |arg|
|
|
326
|
+
if File.directory?(arg)
|
|
327
|
+
files.concat(Dir.glob(File.join(arg, "*.xml")))
|
|
328
|
+
elsif File.file?(arg)
|
|
329
|
+
files << File.expand_path(arg)
|
|
330
|
+
elsif arg.include?("*")
|
|
331
|
+
files.concat(Dir.glob(arg))
|
|
332
|
+
else
|
|
333
|
+
# Try as file path
|
|
334
|
+
path = File.expand_path(arg)
|
|
335
|
+
files << path if File.file?(path)
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Default: all files in default directory
|
|
340
|
+
if files.empty?
|
|
341
|
+
files = Dir.glob(File.join(RoundTripTester::DEFAULT_XML_DIR, "*.xml"))
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
files = files.sort.uniq
|
|
345
|
+
|
|
346
|
+
{ files: files, threads: threads, verbose: verbose }
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# === Main ===
|
|
350
|
+
|
|
351
|
+
if __FILE__ == $PROGRAM_NAME
|
|
352
|
+
options = parse_args(ARGV)
|
|
353
|
+
|
|
354
|
+
if options[:files].empty?
|
|
355
|
+
puts "No XML files found"
|
|
356
|
+
exit 1
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
tester = RoundTripTester.new(**options)
|
|
360
|
+
tester.run
|
|
361
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rfcxml
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lutaml-model
|
|
@@ -47,6 +47,7 @@ extra_rdoc_files: []
|
|
|
47
47
|
files:
|
|
48
48
|
- ".github/workflows/rake.yml"
|
|
49
49
|
- ".github/workflows/release.yml"
|
|
50
|
+
- ".github/workflows/roundtrip.yml"
|
|
50
51
|
- ".gitignore"
|
|
51
52
|
- ".rspec"
|
|
52
53
|
- ".rubocop.yml"
|
|
@@ -149,6 +150,8 @@ files:
|
|
|
149
150
|
- reference-docs/v3.xsd
|
|
150
151
|
- reference-docs/xml.xsd
|
|
151
152
|
- rfcxml.gemspec
|
|
153
|
+
- scripts/README.md
|
|
154
|
+
- scripts/roundtrip_test.rb
|
|
152
155
|
- sig/xml2rfc.rbs
|
|
153
156
|
homepage: https://github.com/metanorma/rfcxml
|
|
154
157
|
licenses:
|