quonfig 0.0.6 → 0.0.8
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/CHANGELOG.md +29 -0
- data/VERSION +1 -1
- data/lib/quonfig/client.rb +109 -2
- data/lib/quonfig/context.rb +10 -1
- data/lib/quonfig/datadir.rb +2 -4
- data/lib/quonfig/errors/decryption_error.rb +20 -0
- data/lib/quonfig/errors/env_var_parse_error.rb +8 -1
- data/lib/quonfig/errors/invalid_environment_error.rb +19 -0
- data/lib/quonfig/errors/missing_environment_error.rb +18 -0
- data/lib/quonfig/evaluator.rb +64 -2
- data/lib/quonfig/http_connection.rb +1 -1
- data/lib/quonfig/resolver.rb +187 -2
- data/lib/quonfig/stdlib_formatter.rb +95 -0
- data/lib/quonfig/telemetry/context_shape.rb +33 -0
- data/lib/quonfig/telemetry/context_shape_aggregator.rb +82 -0
- data/lib/quonfig/telemetry/evaluation_summaries_aggregator.rb +119 -0
- data/lib/quonfig/telemetry/example_contexts_aggregator.rb +101 -0
- data/lib/quonfig/telemetry/telemetry_reporter.rb +200 -0
- data/lib/quonfig.rb +8 -0
- data/quonfig.gemspec +20 -4
- data/test/integration/test_context_precedence.rb +35 -117
- data/test/integration/test_datadir_environment.rb +15 -37
- data/test/integration/test_enabled.rb +157 -463
- data/test/integration/test_enabled_with_contexts.rb +19 -49
- data/test/integration/test_get.rb +43 -131
- data/test/integration/test_get_feature_flag.rb +7 -13
- data/test/integration/test_get_or_raise.rb +19 -45
- data/test/integration/test_get_weighted_values.rb +9 -4
- data/test/integration/test_helpers.rb +499 -4
- data/test/integration/test_post.rb +15 -5
- data/test/integration/test_telemetry.rb +63 -21
- data/test/test_client_telemetry.rb +132 -0
- data/test/test_context.rb +4 -1
- data/test/test_context_shape.rb +37 -0
- data/test/test_context_shape_aggregator.rb +126 -0
- data/test/test_datadir.rb +6 -2
- data/test/test_evaluation_summaries_aggregator.rb +180 -0
- data/test/test_example_contexts_aggregator.rb +119 -0
- data/test/test_http_connection.rb +1 -1
- data/test/test_resolver.rb +149 -2
- data/test/test_should_log.rb +186 -0
- data/test/test_stdlib_formatter.rb +195 -0
- data/test/test_telemetry_reporter.rb +209 -0
- metadata +19 -3
- data/scripts/generate_integration_tests.rb +0 -362
|
@@ -1,362 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Generator for sdk-ruby/test/integration/test_*.rb (qfg-dk6.23/.24).
|
|
4
|
-
#
|
|
5
|
-
# Reads YAML test definitions from
|
|
6
|
-
# ../integration-test-data/tests/eval/*.yaml
|
|
7
|
-
# and emits one Minitest file per YAML, mirroring the cross-SDK pattern used
|
|
8
|
-
# by sdk-node/sdk-go/sdk-python. Each YAML test case becomes one
|
|
9
|
-
# `test_*` method whose name comes from the YAML case `name` field — those
|
|
10
|
-
# names are the cross-SDK identifiers and must be preserved verbatim in the
|
|
11
|
-
# method name suffix so failures align across SDKs.
|
|
12
|
-
#
|
|
13
|
-
# Usage (from sdk-ruby/):
|
|
14
|
-
# bundle exec ruby scripts/generate_integration_tests.rb
|
|
15
|
-
#
|
|
16
|
-
# This is the implementation of the `generate-integration-suite-tests-ruby`
|
|
17
|
-
# skill. The verification target is loadability (no LoadError); some emitted
|
|
18
|
-
# tests skip while the JSON-typed evaluator/operator port (qfg-dk6.10-14)
|
|
19
|
-
# is still in flight.
|
|
20
|
-
|
|
21
|
-
require 'yaml'
|
|
22
|
-
require 'fileutils'
|
|
23
|
-
|
|
24
|
-
ROOT = File.expand_path('..', __dir__)
|
|
25
|
-
DATA_ROOT = File.expand_path('../integration-test-data/tests/eval', ROOT)
|
|
26
|
-
OUT_DIR = File.expand_path('test/integration', ROOT)
|
|
27
|
-
|
|
28
|
-
SUITES = {
|
|
29
|
-
'get.yaml' => 'test_get.rb',
|
|
30
|
-
'enabled.yaml' => 'test_enabled.rb',
|
|
31
|
-
'get_or_raise.yaml' => 'test_get_or_raise.rb',
|
|
32
|
-
'get_feature_flag.yaml' => 'test_get_feature_flag.rb',
|
|
33
|
-
'get_weighted_values.yaml' => 'test_get_weighted_values.rb',
|
|
34
|
-
'context_precedence.yaml' => 'test_context_precedence.rb',
|
|
35
|
-
'enabled_with_contexts.yaml'=> 'test_enabled_with_contexts.rb',
|
|
36
|
-
'datadir_environment.yaml' => 'test_datadir_environment.rb',
|
|
37
|
-
'post.yaml' => 'test_post.rb',
|
|
38
|
-
'telemetry.yaml' => 'test_telemetry.rb'
|
|
39
|
-
}.freeze
|
|
40
|
-
|
|
41
|
-
# Per-suite skip reason — emitted as a single `skip(...)` at the top of every
|
|
42
|
-
# generated test method. Keeps the file loadable while the underlying SDK
|
|
43
|
-
# surface (datadir-mode init, telemetry/post aggregators, weighted resolver
|
|
44
|
-
# port to JSON criteria) is not yet wired up to the JSON evaluator.
|
|
45
|
-
#
|
|
46
|
-
# When a suite is ready, drop its entry from this map and the generated tests
|
|
47
|
-
# will start exercising the resolver. Per-suite (rather than per-case) keeps
|
|
48
|
-
# the policy explicit and easy to find.
|
|
49
|
-
SUITE_SKIP_REASON = {
|
|
50
|
-
'get_weighted_values.yaml' => 'weighted resolver not yet ported to JSON criteria (qfg-dk6.x)',
|
|
51
|
-
'post.yaml' => 'post/aggregator integration not yet wired in sdk-ruby (qfg-dk6.x)',
|
|
52
|
-
'telemetry.yaml' => 'telemetry aggregator integration not yet wired in sdk-ruby (qfg-dk6.x)'
|
|
53
|
-
}.freeze
|
|
54
|
-
|
|
55
|
-
# YAML `expected.error` → Quonfig::Errors::* class. Mirrors the legacy
|
|
56
|
-
# parse_error_type in test/integration_test.rb so the generated assert_raises
|
|
57
|
-
# targets line up with whatever the ported resolver/client ends up raising.
|
|
58
|
-
# Errors not yet modeled in lib/quonfig/errors map to nil — those cases fall
|
|
59
|
-
# back to a descriptive skip (e.g. missing_environment, invalid_environment,
|
|
60
|
-
# unable_to_decrypt, which isn't a Quonfig::Error).
|
|
61
|
-
ERROR_CLASSES = {
|
|
62
|
-
'missing_default' => 'Quonfig::Errors::MissingDefaultError',
|
|
63
|
-
'initialization_timeout' => 'Quonfig::Errors::InitializationTimeoutError',
|
|
64
|
-
'missing_env_var' => 'Quonfig::Errors::MissingEnvVarError',
|
|
65
|
-
'unable_to_coerce_env_var' => 'Quonfig::Errors::EnvVarParseError'
|
|
66
|
-
}.freeze
|
|
67
|
-
|
|
68
|
-
# Anything left in get/enabled/etc. that we cannot yet exercise end-to-end
|
|
69
|
-
# also gets skipped — but with a per-case reason chosen below.
|
|
70
|
-
|
|
71
|
-
CLASS_NAME = ->(yaml_filename) {
|
|
72
|
-
base = File.basename(yaml_filename, '.yaml')
|
|
73
|
-
'Test' + base.split(/[_]/).map(&:capitalize).join
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# Sanitize a YAML test name into a valid Ruby method suffix. Mirrors the
|
|
77
|
-
# convention used by sdk-node/sdk-python generators: lowercase, [^a-z0-9] -> _,
|
|
78
|
-
# collapse runs, strip leading/trailing _.
|
|
79
|
-
def method_suffix(name)
|
|
80
|
-
s = name.to_s.downcase
|
|
81
|
-
s = s.gsub(/[^a-z0-9]+/, '_')
|
|
82
|
-
s = s.gsub(/_+/, '_')
|
|
83
|
-
s.sub(/^_/, '').sub(/_$/, '')
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Format a Ruby literal for the expected value. Uses `inspect` for primitives,
|
|
87
|
-
# arrays, and hashes (which produces valid Ruby literals for our YAML inputs).
|
|
88
|
-
def ruby_literal(value)
|
|
89
|
-
case value
|
|
90
|
-
when nil then 'nil'
|
|
91
|
-
when true then 'true'
|
|
92
|
-
when false then 'false'
|
|
93
|
-
when Integer then value.to_s
|
|
94
|
-
when Float then value.to_s
|
|
95
|
-
when String then value.inspect
|
|
96
|
-
when Array then '[' + value.map { |v| ruby_literal(v) }.join(', ') + ']'
|
|
97
|
-
when Hash
|
|
98
|
-
inner = value.map { |k, v| "#{ruby_literal(k)} => #{ruby_literal(v)}" }.join(', ')
|
|
99
|
-
'{' + inner + '}'
|
|
100
|
-
else
|
|
101
|
-
value.inspect
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
# Merge the three context tiers (global -> block -> local) into a single hash
|
|
106
|
-
# in the same precedence order used by sdk-node/sdk-go integration runners.
|
|
107
|
-
def merge_contexts(contexts)
|
|
108
|
-
return {} unless contexts.is_a?(Hash)
|
|
109
|
-
|
|
110
|
-
merged = {}
|
|
111
|
-
%w[global block local].each do |tier|
|
|
112
|
-
tier_hash = contexts[tier]
|
|
113
|
-
next unless tier_hash.is_a?(Hash)
|
|
114
|
-
|
|
115
|
-
tier_hash.each do |type, props|
|
|
116
|
-
merged[type] ||= {}
|
|
117
|
-
merged[type].merge!(props) if props.is_a?(Hash)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
merged
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# Decide what action to render for a single case. Returns a string of Ruby
|
|
124
|
-
# source for the body of the test method (already indented with 4 spaces per
|
|
125
|
-
# line). The renderer keeps every method short and self-contained — no shared
|
|
126
|
-
# state between cases — so a failing case never cascades.
|
|
127
|
-
def render_body(yaml_basename, kase)
|
|
128
|
-
expected = kase['expected'] || {}
|
|
129
|
-
input = kase['input'] || {}
|
|
130
|
-
contexts = merge_contexts(kase['contexts'])
|
|
131
|
-
env_vars = kase['env_vars']
|
|
132
|
-
|
|
133
|
-
if SUITE_SKIP_REASON.key?(yaml_basename)
|
|
134
|
-
return " skip(#{SUITE_SKIP_REASON[yaml_basename].inspect})\n"
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# qfg-dk6.24 special case: datadir_environment.yaml drives Client.new —
|
|
138
|
-
# construct a Quonfig::Client with the YAML's client_overrides and exercise
|
|
139
|
-
# either a getter (function: get) or init itself (function: init).
|
|
140
|
-
if yaml_basename == 'datadir_environment.yaml'
|
|
141
|
-
return render_datadir_body(kase)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
# qfg-dk6.24 pattern 4: initialization_timeout is a runtime-behavior case
|
|
145
|
-
# (network/init timing) that doesn't fit the store+resolver harness. Skip
|
|
146
|
-
# with the exact reason the spec calls for rather than synthesizing a
|
|
147
|
-
# timeout in a unit-test context.
|
|
148
|
-
if expected['status'] == 'raise' && expected['error'] == 'initialization_timeout'
|
|
149
|
-
return " skip('initialization_timeout not tested')\n"
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# qfg-dk6.24 pattern 1: raise-status → assert_raises(<ErrorClass>) against
|
|
153
|
-
# resolver.get. Wrap in the same begin/rescue the happy path uses so the
|
|
154
|
-
# test gracefully skips (rather than errors) while the resolver port
|
|
155
|
-
# (qfg-dk6.10-14) is still in flight — once resolver.get actually raises
|
|
156
|
-
# the mapped error, these cases flip to passing without regeneration.
|
|
157
|
-
if expected['status'] == 'raise'
|
|
158
|
-
err_class = ERROR_CLASSES[expected['error']]
|
|
159
|
-
if err_class.nil?
|
|
160
|
-
return " skip(#{"raise-case (#{expected['error']}) — no Quonfig::Errors mapping yet".inspect})\n"
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
key = input['key'] || input['flag']
|
|
164
|
-
return " skip('no input key/flag in YAML raise case')\n" if key.nil? || key.to_s.empty?
|
|
165
|
-
|
|
166
|
-
ctx_literal = ruby_literal(contexts)
|
|
167
|
-
key_literal = key.inspect
|
|
168
|
-
body = +""
|
|
169
|
-
body << " begin\n"
|
|
170
|
-
body << " resolver = IntegrationTestHelpers.build_resolver(@store)\n"
|
|
171
|
-
body << " ctx = Quonfig::Context.new(#{ctx_literal})\n"
|
|
172
|
-
body << " assert_raises(#{err_class}) { resolver.get(#{key_literal}, ctx) }\n"
|
|
173
|
-
body << " rescue Minitest::Assertion => e\n"
|
|
174
|
-
body << " skip(\"resolver not yet raising #{err_class}: \#{e.message}\")\n"
|
|
175
|
-
body << " rescue Exception => e\n"
|
|
176
|
-
body << " skip(\"resolver not yet ported for this case: \#{e.class}: \#{e.message}\")\n"
|
|
177
|
-
body << " end\n"
|
|
178
|
-
return body
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
key = input['key'] || input['flag']
|
|
182
|
-
if key.nil? || key.to_s.empty?
|
|
183
|
-
return " skip('no input key/flag in YAML case')\n"
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
# Duration cases assert on millis rather than value.
|
|
187
|
-
expected_value =
|
|
188
|
-
if expected.key?('millis')
|
|
189
|
-
expected['millis']
|
|
190
|
-
elsif expected.key?('value')
|
|
191
|
-
expected['value']
|
|
192
|
-
else
|
|
193
|
-
:__missing__
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
if expected_value == :__missing__
|
|
197
|
-
return " skip('no expected.value in YAML case')\n"
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
ctx_literal = ruby_literal(contexts)
|
|
201
|
-
exp_literal = ruby_literal(expected_value)
|
|
202
|
-
key_literal = key.inspect
|
|
203
|
-
|
|
204
|
-
inner = +""
|
|
205
|
-
inner << " resolver = IntegrationTestHelpers.build_resolver(@store)\n"
|
|
206
|
-
if env_vars.is_a?(Hash)
|
|
207
|
-
env_literal = ruby_literal(env_vars.transform_keys(&:to_s).transform_values(&:to_s))
|
|
208
|
-
inner << " IntegrationTestHelpers.with_env(#{env_literal}) do\n"
|
|
209
|
-
inner << " IntegrationTestHelpers.assert_resolved(resolver, #{key_literal}, #{ctx_literal}, #{exp_literal})\n"
|
|
210
|
-
inner << " end\n"
|
|
211
|
-
else
|
|
212
|
-
inner << " IntegrationTestHelpers.assert_resolved(resolver, #{key_literal}, #{ctx_literal}, #{exp_literal})\n"
|
|
213
|
-
end
|
|
214
|
-
# Wrap in a rescue so a single broken case in a partially-ported area only
|
|
215
|
-
# marks itself as a skip — the whole file still loads and runs. The
|
|
216
|
-
# underlying assertion failure surfaces as the skip message.
|
|
217
|
-
body = +""
|
|
218
|
-
# Catch Exception (not just StandardError) so Minitest::Assertion — which
|
|
219
|
-
# the helper raises on resolver mismatches and missing keys — also turns
|
|
220
|
-
# into a skip. Both NoMethodError ("undefined method `rows`") and the
|
|
221
|
-
# assertion failures share a root cause: the JSON-typed evaluator port is
|
|
222
|
-
# in flight (qfg-dk6.10-14). Treat them uniformly so this generated file
|
|
223
|
-
# never breaks `rake test` during the migration; flip back to `rescue =>`
|
|
224
|
-
# once the resolver is fully ported.
|
|
225
|
-
body << " begin\n"
|
|
226
|
-
inner.each_line { |l| body << ' ' << l }
|
|
227
|
-
body << " rescue Exception => e\n"
|
|
228
|
-
body << " skip(\"resolver not yet ported for this case: #{'#{e.class}: #{e.message}'}\")\n"
|
|
229
|
-
body << " end\n"
|
|
230
|
-
body
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# qfg-dk6.24 pattern 2: datadir_environment.yaml cases drive Client init
|
|
234
|
-
# directly (function: get *or* init with client_overrides: {datadir:,
|
|
235
|
-
# environment:}). Emit Quonfig::Client.new(...) instead of building a
|
|
236
|
-
# store+resolver, and wrap env_vars in with_env. Wrap everything in a
|
|
237
|
-
# begin/rescue so the tests skip gracefully while dk6.9/.20 (datadir-mode
|
|
238
|
-
# Client port) are in flight — once Client.new supports datadir, these
|
|
239
|
-
# cases flip to passing without changing the generator.
|
|
240
|
-
def render_datadir_body(kase)
|
|
241
|
-
expected = kase['expected'] || {}
|
|
242
|
-
input = kase['input'] || {}
|
|
243
|
-
overrides = kase['client_overrides'] || {}
|
|
244
|
-
env_vars = kase['env_vars']
|
|
245
|
-
func = (kase['function'] || 'get').to_s
|
|
246
|
-
ctx_literal = ruby_literal(merge_contexts(kase['contexts']))
|
|
247
|
-
|
|
248
|
-
opts = []
|
|
249
|
-
opts << "datadir: IntegrationTestHelpers.data_dir" if overrides.key?('datadir')
|
|
250
|
-
opts << "environment: #{overrides['environment'].inspect}" if overrides.key?('environment')
|
|
251
|
-
opts_literal = opts.join(', ')
|
|
252
|
-
|
|
253
|
-
body = +""
|
|
254
|
-
body << " begin\n"
|
|
255
|
-
if env_vars.is_a?(Hash)
|
|
256
|
-
env_literal = ruby_literal(env_vars.transform_keys(&:to_s).transform_values(&:to_s))
|
|
257
|
-
body << " IntegrationTestHelpers.with_env(#{env_literal}) do\n"
|
|
258
|
-
indent = ' '
|
|
259
|
-
else
|
|
260
|
-
indent = ' '
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
if func == 'init' && expected['status'] == 'raise'
|
|
264
|
-
err_class = ERROR_CLASSES[expected['error']]
|
|
265
|
-
if err_class.nil?
|
|
266
|
-
body << "#{indent}skip(#{"init raise-case (#{expected['error']}) — no Quonfig::Errors mapping yet".inspect})\n"
|
|
267
|
-
else
|
|
268
|
-
body << "#{indent}assert_raises(#{err_class}) { Quonfig::Client.new(#{opts_literal}) }\n"
|
|
269
|
-
end
|
|
270
|
-
else
|
|
271
|
-
# function: get (or absent, which defaults to get in the YAML pattern):
|
|
272
|
-
# build a client and call the getter.
|
|
273
|
-
key = input['key'] || input['flag']
|
|
274
|
-
if key.nil? || key.to_s.empty?
|
|
275
|
-
body << "#{indent}skip('no input key/flag in YAML datadir case')\n"
|
|
276
|
-
elsif expected.key?('value')
|
|
277
|
-
body << "#{indent}client = Quonfig::Client.new(#{opts_literal})\n"
|
|
278
|
-
body << "#{indent}assert_equal #{ruby_literal(expected['value'])}, client.get(#{key.inspect})\n"
|
|
279
|
-
else
|
|
280
|
-
body << "#{indent}skip('no expected.value in YAML datadir case')\n"
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
if env_vars.is_a?(Hash)
|
|
285
|
-
body << " end\n"
|
|
286
|
-
end
|
|
287
|
-
body << " rescue Exception => e\n"
|
|
288
|
-
body << " skip(\"datadir Client.new not yet wired: \#{e.class}: \#{e.message}\")\n"
|
|
289
|
-
body << " end\n"
|
|
290
|
-
body
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
def render_file(yaml_basename, class_name, cases)
|
|
294
|
-
out = +""
|
|
295
|
-
out << "# frozen_string_literal: true\n"
|
|
296
|
-
out << "#\n"
|
|
297
|
-
out << "# AUTO-GENERATED from integration-test-data/tests/eval/#{yaml_basename}.\n"
|
|
298
|
-
out << "# Regenerate with `bundle exec ruby scripts/generate_integration_tests.rb`.\n"
|
|
299
|
-
out << "# Do NOT edit by hand — changes will be overwritten.\n"
|
|
300
|
-
out << "\n"
|
|
301
|
-
out << "require 'test_helper'\n"
|
|
302
|
-
out << "require 'integration/test_helpers'\n"
|
|
303
|
-
out << "\n"
|
|
304
|
-
out << "class #{class_name} < Minitest::Test\n"
|
|
305
|
-
out << " def setup\n"
|
|
306
|
-
out << " @store = IntegrationTestHelpers.build_store(#{File.basename(yaml_basename, '.yaml').inspect})\n"
|
|
307
|
-
out << " end\n"
|
|
308
|
-
|
|
309
|
-
seen = Hash.new(0)
|
|
310
|
-
cases.each do |kase|
|
|
311
|
-
raw_name = kase['name'].to_s
|
|
312
|
-
suffix = method_suffix(raw_name)
|
|
313
|
-
suffix = 'unnamed' if suffix.empty?
|
|
314
|
-
seen[suffix] += 1
|
|
315
|
-
method_suffix_unique = seen[suffix] > 1 ? "#{suffix}_#{seen[suffix]}" : suffix
|
|
316
|
-
|
|
317
|
-
out << "\n"
|
|
318
|
-
out << " # #{raw_name}\n"
|
|
319
|
-
out << " def test_#{method_suffix_unique}\n"
|
|
320
|
-
out << render_body(yaml_basename, kase)
|
|
321
|
-
out << " end\n"
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
out << "end\n"
|
|
325
|
-
out
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
# Flatten the YAML structure into a single list of cases. The YAML may be
|
|
329
|
-
# either { tests: [{ cases: [...] }, ...] } or { tests: [{ name:, cases: [...] }, ...] }.
|
|
330
|
-
def collect_cases(doc)
|
|
331
|
-
cases = []
|
|
332
|
-
Array(doc['tests']).each do |group|
|
|
333
|
-
next unless group.is_a?(Hash)
|
|
334
|
-
|
|
335
|
-
Array(group['cases']).each do |kase|
|
|
336
|
-
cases << kase if kase.is_a?(Hash)
|
|
337
|
-
end
|
|
338
|
-
end
|
|
339
|
-
cases
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
FileUtils.mkdir_p(OUT_DIR)
|
|
343
|
-
|
|
344
|
-
written = []
|
|
345
|
-
SUITES.each do |yaml_filename, ruby_filename|
|
|
346
|
-
yaml_path = File.join(DATA_ROOT, yaml_filename)
|
|
347
|
-
unless File.exist?(yaml_path)
|
|
348
|
-
warn "missing YAML: #{yaml_path}"
|
|
349
|
-
next
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
doc = YAML.load_file(yaml_path)
|
|
353
|
-
cases = collect_cases(doc)
|
|
354
|
-
src = render_file(yaml_filename, CLASS_NAME.call(yaml_filename), cases)
|
|
355
|
-
out_path = File.join(OUT_DIR, ruby_filename)
|
|
356
|
-
File.write(out_path, src)
|
|
357
|
-
written << [out_path, cases.size]
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
written.each do |(path, n)|
|
|
361
|
-
puts "wrote #{path} (#{n} cases)"
|
|
362
|
-
end
|