@redpanda-data/docs-extensions-and-macros 4.11.1 → 4.12.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.
- package/bin/doc-tools.js +201 -10
- package/package.json +3 -1
- package/tools/property-extractor/COMPUTED_CONSTANTS.md +173 -0
- package/tools/property-extractor/Makefile +12 -1
- package/tools/property-extractor/README.adoc +828 -97
- package/tools/property-extractor/compare-properties.js +38 -13
- package/tools/property-extractor/constant_resolver.py +610 -0
- package/tools/property-extractor/file_pair.py +42 -0
- package/tools/property-extractor/generate-handlebars-docs.js +41 -8
- package/tools/property-extractor/helpers/gt.js +9 -0
- package/tools/property-extractor/helpers/includes.js +17 -0
- package/tools/property-extractor/helpers/index.js +3 -0
- package/tools/property-extractor/helpers/isEnterpriseEnum.js +24 -0
- package/tools/property-extractor/helpers/renderPropertyExample.js +6 -5
- package/tools/property-extractor/overrides.json +248 -0
- package/tools/property-extractor/parser.py +254 -32
- package/tools/property-extractor/property_bag.py +40 -0
- package/tools/property-extractor/property_extractor.py +1417 -430
- package/tools/property-extractor/requirements.txt +1 -0
- package/tools/property-extractor/templates/property-backup.hbs +161 -0
- package/tools/property-extractor/templates/property.hbs +104 -49
- package/tools/property-extractor/templates/topic-property-backup.hbs +148 -0
- package/tools/property-extractor/templates/topic-property.hbs +72 -34
- package/tools/property-extractor/tests/test_known_values.py +617 -0
- package/tools/property-extractor/tests/transformers_test.py +81 -6
- package/tools/property-extractor/topic_property_extractor.py +23 -10
- package/tools/property-extractor/transformers.py +2191 -369
- package/tools/property-extractor/type_definition_extractor.py +669 -0
- package/tools/redpanda-connect/helpers/renderConnectFields.js +33 -1
- package/tools/redpanda-connect/report-delta.js +132 -9
- package/tools/property-extractor/definitions.json +0 -245
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive integration tests with known values for property extraction.
|
|
3
|
+
|
|
4
|
+
These tests use complete, real-world property examples with known expected outputs
|
|
5
|
+
to ensure the entire transformation pipeline produces correct results. Each test
|
|
6
|
+
verifies the full property output rather than individual transformer behavior.
|
|
7
|
+
|
|
8
|
+
This helps catch regressions as the codebase scales and makes it harder to miss
|
|
9
|
+
mistakes in the complex transformation logic.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import unittest
|
|
13
|
+
from property_bag import PropertyBag
|
|
14
|
+
from file_pair import FilePair
|
|
15
|
+
from transformers import (
|
|
16
|
+
BasicInfoTransformer,
|
|
17
|
+
ParamNormalizerTransformer,
|
|
18
|
+
IsNullableTransformer,
|
|
19
|
+
IsArrayTransformer,
|
|
20
|
+
NeedsRestartTransformer,
|
|
21
|
+
GetsRestoredTransformer,
|
|
22
|
+
IsSecretTransformer,
|
|
23
|
+
VisibilityTransformer,
|
|
24
|
+
TypeTransformer,
|
|
25
|
+
DeprecatedTransformer,
|
|
26
|
+
NumericBoundsTransformer,
|
|
27
|
+
DurationBoundsTransformer,
|
|
28
|
+
SimpleDefaultValuesTransformer,
|
|
29
|
+
FriendlyDefaultTransformer,
|
|
30
|
+
ExperimentalTransformer,
|
|
31
|
+
AliasTransformer,
|
|
32
|
+
EnterpriseTransformer,
|
|
33
|
+
MetaParamTransformer,
|
|
34
|
+
ExampleTransformer,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_complete_property_info(
|
|
39
|
+
name,
|
|
40
|
+
description,
|
|
41
|
+
declaration=None,
|
|
42
|
+
metadata=None,
|
|
43
|
+
default_value="{}",
|
|
44
|
+
property_type="property",
|
|
45
|
+
):
|
|
46
|
+
"""Helper to create a complete property info structure"""
|
|
47
|
+
info = PropertyBag()
|
|
48
|
+
info["name_in_file"] = name
|
|
49
|
+
info["declaration"] = declaration
|
|
50
|
+
info["type"] = property_type
|
|
51
|
+
info["params"] = []
|
|
52
|
+
|
|
53
|
+
# Add property name param
|
|
54
|
+
info["params"].append(PropertyBag(value=name, type="string_literal"))
|
|
55
|
+
|
|
56
|
+
# Add description param
|
|
57
|
+
info["params"].append(PropertyBag(value=description, type="string_literal"))
|
|
58
|
+
|
|
59
|
+
# Add metadata param if provided
|
|
60
|
+
if metadata is not None:
|
|
61
|
+
if isinstance(metadata, str):
|
|
62
|
+
# String metadata (will be parsed by MetaParamTransformer)
|
|
63
|
+
info["params"].append(PropertyBag(value=metadata, type="initializer_list"))
|
|
64
|
+
else:
|
|
65
|
+
# Already parsed metadata dict
|
|
66
|
+
info["params"].append(PropertyBag(value=metadata, type="initializer_list"))
|
|
67
|
+
|
|
68
|
+
# Add default value param
|
|
69
|
+
info["params"].append(PropertyBag(value=default_value, type="number_literal" if isinstance(default_value, (int, float)) else "string_literal"))
|
|
70
|
+
|
|
71
|
+
return info
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def apply_transformer_pipeline(info, file_pair=None):
|
|
75
|
+
"""Apply the standard transformer pipeline to a property info"""
|
|
76
|
+
if file_pair is None:
|
|
77
|
+
file_pair = FilePair("test.h", "test.cc")
|
|
78
|
+
|
|
79
|
+
property = PropertyBag()
|
|
80
|
+
|
|
81
|
+
# Create type transformer instance (needed by several other transformers)
|
|
82
|
+
type_transformer = TypeTransformer()
|
|
83
|
+
|
|
84
|
+
# Apply transformers in standard order
|
|
85
|
+
transformers = [
|
|
86
|
+
ParamNormalizerTransformer(),
|
|
87
|
+
BasicInfoTransformer(),
|
|
88
|
+
MetaParamTransformer(),
|
|
89
|
+
NeedsRestartTransformer(),
|
|
90
|
+
GetsRestoredTransformer(),
|
|
91
|
+
IsSecretTransformer(),
|
|
92
|
+
VisibilityTransformer(),
|
|
93
|
+
IsNullableTransformer(),
|
|
94
|
+
IsArrayTransformer(type_transformer),
|
|
95
|
+
type_transformer,
|
|
96
|
+
DeprecatedTransformer(),
|
|
97
|
+
NumericBoundsTransformer(type_transformer),
|
|
98
|
+
DurationBoundsTransformer(type_transformer),
|
|
99
|
+
SimpleDefaultValuesTransformer(),
|
|
100
|
+
FriendlyDefaultTransformer(),
|
|
101
|
+
ExperimentalTransformer(),
|
|
102
|
+
AliasTransformer(),
|
|
103
|
+
EnterpriseTransformer(),
|
|
104
|
+
ExampleTransformer(),
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
for transformer in transformers:
|
|
108
|
+
if transformer.accepts(info, file_pair):
|
|
109
|
+
transformer.parse(property, info, file_pair)
|
|
110
|
+
|
|
111
|
+
return property
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class SimpleIntegerPropertyTest(unittest.TestCase):
|
|
115
|
+
"""Test a simple integer property with known values"""
|
|
116
|
+
|
|
117
|
+
def test_kafka_qdc_depth_alpha(self):
|
|
118
|
+
"""Test kafka_qdc_depth_alpha property extraction"""
|
|
119
|
+
info = create_complete_property_info(
|
|
120
|
+
name="kafka_qdc_depth_alpha",
|
|
121
|
+
description="Smoothing parameter for Kafka queue depth control",
|
|
122
|
+
declaration="property<double> kafka_qdc_depth_alpha;",
|
|
123
|
+
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::tunable}",
|
|
124
|
+
default_value="0.8"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
property = apply_transformer_pipeline(info)
|
|
128
|
+
|
|
129
|
+
# Verify all expected fields
|
|
130
|
+
self.assertEqual(property["name"], "kafka_qdc_depth_alpha")
|
|
131
|
+
self.assertEqual(property["description"], "Smoothing parameter for Kafka queue depth control")
|
|
132
|
+
self.assertEqual(property["type"], "number")
|
|
133
|
+
self.assertEqual(property["default"], 0.8)
|
|
134
|
+
self.assertFalse(property["needs_restart"])
|
|
135
|
+
self.assertEqual(property["visibility"], "tunable")
|
|
136
|
+
self.assertEqual(property["defined_in"], "test.cc")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class ChronoPropertyTest(unittest.TestCase):
|
|
140
|
+
"""Test chrono duration properties with known values"""
|
|
141
|
+
|
|
142
|
+
def test_log_segment_ms_weeks(self):
|
|
143
|
+
"""Test log_segment_ms with std::chrono::weeks{2}"""
|
|
144
|
+
info = create_complete_property_info(
|
|
145
|
+
name="log_segment_ms",
|
|
146
|
+
description="How long to keep a log segment before rolling",
|
|
147
|
+
declaration="property<std::chrono::milliseconds> log_segment_ms;",
|
|
148
|
+
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::user}",
|
|
149
|
+
default_value="std::chrono::weeks{2}"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
property = apply_transformer_pipeline(info)
|
|
153
|
+
|
|
154
|
+
self.assertEqual(property["name"], "log_segment_ms")
|
|
155
|
+
self.assertEqual(property["type"], "integer")
|
|
156
|
+
self.assertEqual(property["default"], "2 weeks")
|
|
157
|
+
self.assertFalse(property["needs_restart"])
|
|
158
|
+
self.assertEqual(property["visibility"], "user")
|
|
159
|
+
|
|
160
|
+
def test_raft_heartbeat_interval_with_literal(self):
|
|
161
|
+
"""Test chrono property with inner literal: chrono::milliseconds{150ms}"""
|
|
162
|
+
info = create_complete_property_info(
|
|
163
|
+
name="raft_heartbeat_interval_ms",
|
|
164
|
+
description="Raft heartbeat interval",
|
|
165
|
+
declaration="property<std::chrono::milliseconds> raft_heartbeat_interval_ms;",
|
|
166
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
167
|
+
default_value="std::chrono::milliseconds{150ms}"
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
property = apply_transformer_pipeline(info)
|
|
171
|
+
|
|
172
|
+
self.assertEqual(property["name"], "raft_heartbeat_interval_ms")
|
|
173
|
+
self.assertEqual(property["type"], "integer")
|
|
174
|
+
self.assertEqual(property["default"], "150 milliseconds")
|
|
175
|
+
self.assertFalse(property["needs_restart"])
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ArrayPropertyTest(unittest.TestCase):
|
|
179
|
+
"""Test array/vector properties with known values"""
|
|
180
|
+
|
|
181
|
+
def test_vector_string_property(self):
|
|
182
|
+
"""Test std::vector<ss::sstring> property"""
|
|
183
|
+
info = create_complete_property_info(
|
|
184
|
+
name="seed_servers",
|
|
185
|
+
description="List of seed servers",
|
|
186
|
+
declaration="property<std::vector<ss::sstring>> seed_servers;",
|
|
187
|
+
metadata="meta{.needs_restart = needs_restart::yes}",
|
|
188
|
+
default_value='std::vector<ss::sstring>{"localhost:9092"}'
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
property = apply_transformer_pipeline(info)
|
|
192
|
+
|
|
193
|
+
self.assertEqual(property["name"], "seed_servers")
|
|
194
|
+
self.assertEqual(property["type"], "array")
|
|
195
|
+
self.assertEqual(property["items"]["type"], "string")
|
|
196
|
+
self.assertEqual(property["default"], ["localhost:9092"])
|
|
197
|
+
self.assertTrue(property["needs_restart"])
|
|
198
|
+
|
|
199
|
+
def test_braced_list_default(self):
|
|
200
|
+
"""Test array property with braced list default"""
|
|
201
|
+
info = create_complete_property_info(
|
|
202
|
+
name="advertised_kafka_api",
|
|
203
|
+
description="Advertised Kafka API addresses",
|
|
204
|
+
declaration="property<std::vector<ss::sstring>> advertised_kafka_api;",
|
|
205
|
+
metadata="meta{.needs_restart = needs_restart::yes}",
|
|
206
|
+
default_value='{"127.0.0.1:9092", "localhost:9092"}'
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
property = apply_transformer_pipeline(info)
|
|
210
|
+
|
|
211
|
+
self.assertEqual(property["name"], "advertised_kafka_api")
|
|
212
|
+
self.assertEqual(property["type"], "array")
|
|
213
|
+
self.assertEqual(property["default"], ["127.0.0.1:9092", "localhost:9092"])
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class NullablePropertyTest(unittest.TestCase):
|
|
217
|
+
"""Test nullable/optional properties with known values"""
|
|
218
|
+
|
|
219
|
+
def test_optional_int_with_nullopt(self):
|
|
220
|
+
"""Test std::optional<int> with std::nullopt default"""
|
|
221
|
+
info = create_complete_property_info(
|
|
222
|
+
name="target_quota_byte_rate",
|
|
223
|
+
description="Target quota in bytes per second",
|
|
224
|
+
declaration="property<std::optional<int64_t>> target_quota_byte_rate;",
|
|
225
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
226
|
+
default_value="std::nullopt"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
property = apply_transformer_pipeline(info)
|
|
230
|
+
|
|
231
|
+
self.assertEqual(property["name"], "target_quota_byte_rate")
|
|
232
|
+
self.assertEqual(property["type"], "integer")
|
|
233
|
+
self.assertIsNone(property["default"])
|
|
234
|
+
self.assertTrue(property.get("nullable", False))
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class EnterprisePropertyTest(unittest.TestCase):
|
|
238
|
+
"""Test enterprise properties with known values"""
|
|
239
|
+
|
|
240
|
+
def test_enterprise_property_with_restrictions(self):
|
|
241
|
+
"""Test enterprise property with restricted values (enterprise-only values)"""
|
|
242
|
+
# Simulate enterprise_property with restricted value
|
|
243
|
+
# Pattern: enterprise_property<string>(restricted_value, *this, name, description, meta, default)
|
|
244
|
+
info = PropertyBag()
|
|
245
|
+
info["name_in_file"] = "partition_autobalancing_mode"
|
|
246
|
+
info["declaration"] = "enterprise_property<ss::sstring> partition_autobalancing_mode;"
|
|
247
|
+
info["type"] = "enterprise_property"
|
|
248
|
+
info["is_enterprise"] = True
|
|
249
|
+
info["params"] = [
|
|
250
|
+
PropertyBag(value="continuous", type="string_literal"), # Restricted enterprise value
|
|
251
|
+
PropertyBag(value="partition_autobalancing_mode", type="string_literal"),
|
|
252
|
+
PropertyBag(value="Partition autobalancing mode", type="string_literal"),
|
|
253
|
+
PropertyBag(value="meta{.needs_restart = needs_restart::no}", type="initializer_list"),
|
|
254
|
+
PropertyBag(value='"off"', type="string_literal"), # Default value
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
property = apply_transformer_pipeline(info)
|
|
258
|
+
|
|
259
|
+
self.assertEqual(property["name"], "partition_autobalancing_mode")
|
|
260
|
+
self.assertEqual(property["type"], "string")
|
|
261
|
+
self.assertTrue(property.get("is_enterprise", False))
|
|
262
|
+
self.assertEqual(property.get("enterprise_constructor"), "restricted_only")
|
|
263
|
+
self.assertIn("continuous", property.get("enterprise_restricted_value", []))
|
|
264
|
+
|
|
265
|
+
def test_simple_enterprise_property_without_restrictions(self):
|
|
266
|
+
"""Test that simple enterprise properties without restrictions are not marked as enterprise
|
|
267
|
+
|
|
268
|
+
This test documents current behavior: enterprise properties without explicit
|
|
269
|
+
restriction/sanction patterns are not detected as enterprise by the transformer.
|
|
270
|
+
This may be a limitation to address in future work.
|
|
271
|
+
"""
|
|
272
|
+
info = create_complete_property_info(
|
|
273
|
+
name="audit_enabled",
|
|
274
|
+
description="Enable audit logging",
|
|
275
|
+
declaration="enterprise_property<bool> audit_enabled;",
|
|
276
|
+
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::user}",
|
|
277
|
+
default_value="false",
|
|
278
|
+
property_type="enterprise_property"
|
|
279
|
+
)
|
|
280
|
+
# Mark as enterprise property (normally set by parser)
|
|
281
|
+
info["is_enterprise"] = True
|
|
282
|
+
|
|
283
|
+
property = apply_transformer_pipeline(info)
|
|
284
|
+
|
|
285
|
+
self.assertEqual(property["name"], "audit_enabled")
|
|
286
|
+
self.assertEqual(property["type"], "boolean")
|
|
287
|
+
self.assertFalse(property["default"])
|
|
288
|
+
# Current behavior: simple enterprise properties without restrictions are not detected
|
|
289
|
+
self.assertFalse(property.get("is_enterprise", False))
|
|
290
|
+
self.assertEqual(property["visibility"], "user")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class NumericBoundsTest(unittest.TestCase):
|
|
294
|
+
"""Test numeric properties with bounds"""
|
|
295
|
+
|
|
296
|
+
def test_int32_bounds(self):
|
|
297
|
+
"""Test int32_t property has correct bounds"""
|
|
298
|
+
info = create_complete_property_info(
|
|
299
|
+
name="kafka_connection_rate_limit",
|
|
300
|
+
description="Connection rate limit per broker",
|
|
301
|
+
declaration="property<int32_t> kafka_connection_rate_limit;",
|
|
302
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
303
|
+
default_value="1000"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
property = apply_transformer_pipeline(info)
|
|
307
|
+
|
|
308
|
+
self.assertEqual(property["name"], "kafka_connection_rate_limit")
|
|
309
|
+
self.assertEqual(property["type"], "integer")
|
|
310
|
+
self.assertEqual(property["default"], 1000)
|
|
311
|
+
self.assertEqual(property["minimum"], -(2**31))
|
|
312
|
+
self.assertEqual(property["maximum"], 2**31 - 1)
|
|
313
|
+
|
|
314
|
+
def test_int64_bounds(self):
|
|
315
|
+
"""Test int64_t property has correct bounds"""
|
|
316
|
+
info = create_complete_property_info(
|
|
317
|
+
name="log_segment_size",
|
|
318
|
+
description="Log segment size in bytes",
|
|
319
|
+
declaration="property<int64_t> log_segment_size;",
|
|
320
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
321
|
+
default_value="1073741824" # 1 GiB
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
property = apply_transformer_pipeline(info)
|
|
325
|
+
|
|
326
|
+
self.assertEqual(property["name"], "log_segment_size")
|
|
327
|
+
self.assertEqual(property["type"], "integer")
|
|
328
|
+
self.assertEqual(property["default"], 1073741824)
|
|
329
|
+
self.assertEqual(property["minimum"], -(2**63))
|
|
330
|
+
self.assertEqual(property["maximum"], 2**63 - 1)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class DeprecatedPropertyTest(unittest.TestCase):
|
|
334
|
+
"""Test deprecated properties"""
|
|
335
|
+
|
|
336
|
+
def test_deprecated_visibility(self):
|
|
337
|
+
"""Test property with deprecated visibility and metadata"""
|
|
338
|
+
info = create_complete_property_info(
|
|
339
|
+
name="old_config_option",
|
|
340
|
+
description="Deprecated configuration option",
|
|
341
|
+
declaration="property<bool> old_config_option;",
|
|
342
|
+
metadata="meta{.visibility = visibility::deprecated, .deprecated = yes}",
|
|
343
|
+
default_value="false"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
property = apply_transformer_pipeline(info)
|
|
347
|
+
|
|
348
|
+
self.assertEqual(property["name"], "old_config_option")
|
|
349
|
+
self.assertTrue(property.get("is_deprecated", False))
|
|
350
|
+
self.assertEqual(property["visibility"], "deprecated")
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class SecretPropertyTest(unittest.TestCase):
|
|
354
|
+
"""Test secret/sensitive properties"""
|
|
355
|
+
|
|
356
|
+
def test_secret_string_property(self):
|
|
357
|
+
"""Test property marked as secret"""
|
|
358
|
+
info = create_complete_property_info(
|
|
359
|
+
name="cloud_storage_secret_key",
|
|
360
|
+
description="Secret key for cloud storage",
|
|
361
|
+
declaration="property<ss::sstring> cloud_storage_secret_key;",
|
|
362
|
+
metadata="meta{.needs_restart = needs_restart::no, .secret = is_secret::yes}",
|
|
363
|
+
default_value='""'
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
property = apply_transformer_pipeline(info)
|
|
367
|
+
|
|
368
|
+
self.assertEqual(property["name"], "cloud_storage_secret_key")
|
|
369
|
+
self.assertEqual(property["type"], "string")
|
|
370
|
+
self.assertTrue(property.get("is_secret", False))
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class SizeLiteralTest(unittest.TestCase):
|
|
374
|
+
"""Test size literal conversion (e.g., 20_GiB)"""
|
|
375
|
+
|
|
376
|
+
def test_gib_size_literal(self):
|
|
377
|
+
"""Test conversion of _GiB size literal"""
|
|
378
|
+
info = create_complete_property_info(
|
|
379
|
+
name="compacted_log_segment_size",
|
|
380
|
+
description="Compacted log segment size",
|
|
381
|
+
declaration="property<int64_t> compacted_log_segment_size;",
|
|
382
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
383
|
+
default_value="256_MiB"
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
property = apply_transformer_pipeline(info)
|
|
387
|
+
|
|
388
|
+
self.assertEqual(property["name"], "compacted_log_segment_size")
|
|
389
|
+
self.assertEqual(property["type"], "integer")
|
|
390
|
+
# 256 * 1024^2 = 268435456
|
|
391
|
+
self.assertEqual(property["default"], 268435456)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class UnresolvedAddressTest(unittest.TestCase):
|
|
395
|
+
"""Test net::unresolved_address defaults"""
|
|
396
|
+
|
|
397
|
+
def test_unresolved_address_default(self):
|
|
398
|
+
"""Test net::unresolved_address parsing"""
|
|
399
|
+
info = create_complete_property_info(
|
|
400
|
+
name="kafka_api_bind_address",
|
|
401
|
+
description="Kafka API bind address",
|
|
402
|
+
declaration="property<net::unresolved_address> kafka_api_bind_address;",
|
|
403
|
+
metadata="meta{.needs_restart = needs_restart::yes}",
|
|
404
|
+
default_value='net::unresolved_address("0.0.0.0", 9092)'
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
property = apply_transformer_pipeline(info)
|
|
408
|
+
|
|
409
|
+
self.assertEqual(property["name"], "kafka_api_bind_address")
|
|
410
|
+
self.assertEqual(property["type"], "object")
|
|
411
|
+
self.assertEqual(property["$ref"], "#/definitions/net::unresolved_address")
|
|
412
|
+
self.assertIsInstance(property["default"], dict)
|
|
413
|
+
self.assertEqual(property["default"]["address"], "0.0.0.0")
|
|
414
|
+
self.assertEqual(property["default"]["port"], 9092)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class BooleanPropertyTest(unittest.TestCase):
|
|
418
|
+
"""Test boolean properties"""
|
|
419
|
+
|
|
420
|
+
def test_true_default(self):
|
|
421
|
+
"""Test boolean property with true default"""
|
|
422
|
+
info = create_complete_property_info(
|
|
423
|
+
name="enable_idempotence",
|
|
424
|
+
description="Enable idempotent producer",
|
|
425
|
+
declaration="property<bool> enable_idempotence;",
|
|
426
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
427
|
+
default_value="true"
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
property = apply_transformer_pipeline(info)
|
|
431
|
+
|
|
432
|
+
self.assertEqual(property["name"], "enable_idempotence")
|
|
433
|
+
self.assertEqual(property["type"], "boolean")
|
|
434
|
+
self.assertTrue(property["default"])
|
|
435
|
+
|
|
436
|
+
def test_false_default(self):
|
|
437
|
+
"""Test boolean property with false default"""
|
|
438
|
+
info = create_complete_property_info(
|
|
439
|
+
name="enable_legacy_mode",
|
|
440
|
+
description="Enable legacy compatibility mode",
|
|
441
|
+
declaration="property<bool> enable_legacy_mode;",
|
|
442
|
+
metadata="meta{.needs_restart = needs_restart::yes}",
|
|
443
|
+
default_value="false"
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
property = apply_transformer_pipeline(info)
|
|
447
|
+
|
|
448
|
+
self.assertEqual(property["name"], "enable_legacy_mode")
|
|
449
|
+
self.assertEqual(property["type"], "boolean")
|
|
450
|
+
self.assertFalse(property["default"])
|
|
451
|
+
self.assertTrue(property["needs_restart"])
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
class ExampleMetadataTest(unittest.TestCase):
|
|
455
|
+
"""Test example metadata extraction"""
|
|
456
|
+
|
|
457
|
+
def test_example_in_meta(self):
|
|
458
|
+
"""Test extraction of example value from meta"""
|
|
459
|
+
info = create_complete_property_info(
|
|
460
|
+
name="log_cleanup_policy",
|
|
461
|
+
description="Log cleanup policy",
|
|
462
|
+
declaration="property<ss::sstring> log_cleanup_policy;",
|
|
463
|
+
metadata='meta{.needs_restart = needs_restart::no, .example = "delete", .visibility = visibility::user}',
|
|
464
|
+
default_value='"compact"'
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
property = apply_transformer_pipeline(info)
|
|
468
|
+
|
|
469
|
+
self.assertEqual(property["name"], "log_cleanup_policy")
|
|
470
|
+
self.assertEqual(property.get("example"), "delete")
|
|
471
|
+
self.assertEqual(property["default"], "compact")
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class NumericSuffixStrippingTest(unittest.TestCase):
|
|
475
|
+
"""Test C++ numeric suffix stripping"""
|
|
476
|
+
|
|
477
|
+
def test_unsigned_suffix(self):
|
|
478
|
+
"""Test stripping 'u' suffix from unsigned integers"""
|
|
479
|
+
info = create_complete_property_info(
|
|
480
|
+
name="buffer_size",
|
|
481
|
+
description="Buffer size",
|
|
482
|
+
declaration="property<uint32_t> buffer_size;",
|
|
483
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
484
|
+
default_value="1024u"
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
property = apply_transformer_pipeline(info)
|
|
488
|
+
|
|
489
|
+
self.assertEqual(property["name"], "buffer_size")
|
|
490
|
+
self.assertEqual(property["default"], 1024)
|
|
491
|
+
|
|
492
|
+
def test_long_suffix(self):
|
|
493
|
+
"""Test stripping 'L' suffix from long integers"""
|
|
494
|
+
info = create_complete_property_info(
|
|
495
|
+
name="max_value",
|
|
496
|
+
description="Maximum value",
|
|
497
|
+
declaration="property<int64_t> max_value;",
|
|
498
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
499
|
+
default_value="9999999L"
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
property = apply_transformer_pipeline(info)
|
|
503
|
+
|
|
504
|
+
self.assertEqual(property["name"], "max_value")
|
|
505
|
+
self.assertEqual(property["default"], 9999999)
|
|
506
|
+
|
|
507
|
+
def test_unsigned_long_long_suffix(self):
|
|
508
|
+
"""Test stripping 'ULL' suffix"""
|
|
509
|
+
info = create_complete_property_info(
|
|
510
|
+
name="max_offset",
|
|
511
|
+
description="Maximum offset",
|
|
512
|
+
declaration="property<uint64_t> max_offset;",
|
|
513
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
514
|
+
default_value="18446744073709551615ULL"
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
property = apply_transformer_pipeline(info)
|
|
518
|
+
|
|
519
|
+
self.assertEqual(property["name"], "max_offset")
|
|
520
|
+
self.assertEqual(property["default"], 18446744073709551615)
|
|
521
|
+
|
|
522
|
+
def test_float_suffix(self):
|
|
523
|
+
"""Test stripping 'f' suffix from floats"""
|
|
524
|
+
info = create_complete_property_info(
|
|
525
|
+
name="ratio",
|
|
526
|
+
description="Compression ratio",
|
|
527
|
+
declaration="property<float> ratio;",
|
|
528
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
529
|
+
default_value="0.75f"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
property = apply_transformer_pipeline(info)
|
|
533
|
+
|
|
534
|
+
self.assertEqual(property["name"], "ratio")
|
|
535
|
+
self.assertAlmostEqual(property["default"], 0.75, places=2)
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
class ComplexPropertyTest(unittest.TestCase):
|
|
539
|
+
"""Test complex real-world property examples"""
|
|
540
|
+
|
|
541
|
+
def test_retention_bytes_with_nullopt(self):
|
|
542
|
+
"""Test retention.bytes with std::nullopt (unlimited)"""
|
|
543
|
+
info = create_complete_property_info(
|
|
544
|
+
name="retention_bytes",
|
|
545
|
+
description="Max bytes per partition",
|
|
546
|
+
declaration="property<std::optional<int64_t>> retention_bytes;",
|
|
547
|
+
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::user}",
|
|
548
|
+
default_value="std::nullopt"
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
property = apply_transformer_pipeline(info)
|
|
552
|
+
|
|
553
|
+
self.assertEqual(property["name"], "retention_bytes")
|
|
554
|
+
self.assertEqual(property["type"], "integer")
|
|
555
|
+
self.assertIsNone(property["default"])
|
|
556
|
+
self.assertTrue(property.get("nullable", False))
|
|
557
|
+
self.assertFalse(property["needs_restart"])
|
|
558
|
+
self.assertEqual(property["visibility"], "user")
|
|
559
|
+
|
|
560
|
+
def test_log_segment_ms_evaluated_default(self):
|
|
561
|
+
"""Test that chrono defaults are human-readable (not raw milliseconds)"""
|
|
562
|
+
info = create_complete_property_info(
|
|
563
|
+
name="delete_retention_ms",
|
|
564
|
+
description="Retention time for delete records",
|
|
565
|
+
declaration="property<std::chrono::milliseconds> delete_retention_ms;",
|
|
566
|
+
metadata="meta{.needs_restart = needs_restart::no}",
|
|
567
|
+
default_value="std::chrono::hours{24}"
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
property = apply_transformer_pipeline(info)
|
|
571
|
+
|
|
572
|
+
self.assertEqual(property["name"], "delete_retention_ms")
|
|
573
|
+
self.assertEqual(property["type"], "integer")
|
|
574
|
+
# Should be "24 hours", not 86400000
|
|
575
|
+
self.assertEqual(property["default"], "24 hours")
|
|
576
|
+
|
|
577
|
+
def test_max_serializable_ms_constant_resolution(self):
|
|
578
|
+
"""Test that max_serializable_ms constant is resolved to actual numeric value"""
|
|
579
|
+
info = create_complete_property_info(
|
|
580
|
+
name="log_message_timestamp_before_max_ms",
|
|
581
|
+
description="Maximum timestamp difference for record validation",
|
|
582
|
+
declaration="property<std::chrono::milliseconds> log_message_timestamp_before_max_ms;",
|
|
583
|
+
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::user}",
|
|
584
|
+
default_value="max_serializable_ms"
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
property = apply_transformer_pipeline(info)
|
|
588
|
+
|
|
589
|
+
self.assertEqual(property["name"], "log_message_timestamp_before_max_ms")
|
|
590
|
+
self.assertEqual(property["type"], "integer")
|
|
591
|
+
# max_serializable_ms = std::numeric_limits<int64_t>::max() / 1,000,000 = 9223372036854 ms
|
|
592
|
+
self.assertEqual(property["default"], 9223372036854)
|
|
593
|
+
self.assertFalse(property["needs_restart"])
|
|
594
|
+
self.assertEqual(property["visibility"], "user")
|
|
595
|
+
|
|
596
|
+
def test_max_serializable_ms_with_namespace(self):
|
|
597
|
+
"""Test that serde::max_serializable_ms (namespace-qualified) is resolved correctly"""
|
|
598
|
+
info = create_complete_property_info(
|
|
599
|
+
name="log_message_timestamp_before_max_ms",
|
|
600
|
+
description="Maximum timestamp difference for record validation",
|
|
601
|
+
declaration="property<std::chrono::milliseconds> log_message_timestamp_before_max_ms;",
|
|
602
|
+
metadata="meta{.needs_restart = needs_restart::no, .visibility = visibility::user}",
|
|
603
|
+
default_value="serde::max_serializable_ms"
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
property = apply_transformer_pipeline(info)
|
|
607
|
+
|
|
608
|
+
self.assertEqual(property["name"], "log_message_timestamp_before_max_ms")
|
|
609
|
+
self.assertEqual(property["type"], "integer")
|
|
610
|
+
# serde::max_serializable_ms = 9223372036854 ms (same as max_serializable_ms)
|
|
611
|
+
self.assertEqual(property["default"], 9223372036854)
|
|
612
|
+
self.assertFalse(property["needs_restart"])
|
|
613
|
+
self.assertEqual(property["visibility"], "user")
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
if __name__ == "__main__":
|
|
617
|
+
unittest.main()
|