SERVER-122105 IDL compiler generic annotations block (#52568)
GitOrigin-RevId: 44194d7de6043776c2f1940520f3b296b9f98931
This commit is contained in:
parent
20bbf04949
commit
18025e809a
@ -492,6 +492,8 @@ class ServerParameter(common.SourceLocation):
|
||||
|
||||
self.is_deprecated = False # type: bool
|
||||
|
||||
self.annotations = None # type: Dict[str, Any]
|
||||
|
||||
super(ServerParameter, self).__init__(file_name, line, column)
|
||||
|
||||
|
||||
|
||||
@ -1567,6 +1567,7 @@ def _bind_server_parameter(ctxt, param):
|
||||
ast_param.omit_in_ftdc = param.omit_in_ftdc
|
||||
|
||||
ast_param.is_deprecated = param.is_deprecated
|
||||
ast_param.annotations = param.annotations
|
||||
|
||||
ast_param.set_at = _bind_server_parameter_set_at(ctxt, param)
|
||||
if ast_param.set_at is None:
|
||||
|
||||
@ -30,6 +30,7 @@
|
||||
import hashlib
|
||||
import io
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
@ -3171,6 +3172,86 @@ class _CppSourceFileWriter(_CppFileWriterBase):
|
||||
f'{{"{entry.name}", {"true" if entry.generic_field_info.get_should_forward() else "false"} }},'
|
||||
)
|
||||
|
||||
def _gen_annotations_elem(self, builder, key, value, depth=0):
|
||||
# type: (str, Optional[str], Any, int) -> None
|
||||
"""Dispatch an annotation value to the appropriate generator."""
|
||||
if value is None:
|
||||
self._gen_annotations_null(builder, key)
|
||||
elif isinstance(value, dict):
|
||||
self._gen_annotations_obj(builder, key, value, depth)
|
||||
elif isinstance(value, list):
|
||||
self._gen_annotations_arr(builder, key, value, depth)
|
||||
else:
|
||||
self._gen_annotations_scalar(builder, key, value)
|
||||
|
||||
def _gen_annotations_obj(self, builder, key, obj, depth):
|
||||
# type: (str, Optional[str], Dict[str, Any], int) -> None
|
||||
"""Emit a BSONObjBuilder sub-object for a dict."""
|
||||
sub = "sub%d" % depth
|
||||
start = "%s.subobjStart(%s)" % (builder, key) if key else "%s.subobjStart()" % builder
|
||||
with self._block("{", "}"):
|
||||
self._writer.write_line("BSONObjBuilder %s(%s);" % (sub, start))
|
||||
for k, v in obj.items():
|
||||
self._gen_annotations_elem(sub, _encaps(k), v, depth + 1)
|
||||
|
||||
def _gen_annotations_arr(self, builder, key, items, depth):
|
||||
# type: (str, Optional[str], List[Any], int) -> None
|
||||
"""Emit a BSONArrayBuilder sub-array for a list."""
|
||||
arr = "arr%d" % depth
|
||||
start = "%s.subarrayStart(%s)" % (builder, key) if key else "%s.subarrayStart()" % builder
|
||||
with self._block("{", "}"):
|
||||
self._writer.write_line("BSONArrayBuilder %s(%s);" % (arr, start))
|
||||
for item in items:
|
||||
self._gen_annotations_elem(arr, None, item, depth + 1)
|
||||
|
||||
def _gen_annotations_null(self, builder, key):
|
||||
# type: (str, Optional[str]) -> None
|
||||
"""Emit an appendNull call."""
|
||||
if key:
|
||||
self._writer.write_line("%s.appendNull(%s);" % (builder, key))
|
||||
else:
|
||||
self._writer.write_line("%s.appendNull();" % builder)
|
||||
|
||||
def _gen_annotations_scalar(self, builder, key, value):
|
||||
# type: (str, Optional[str], Any) -> None
|
||||
"""Emit an append call for a scalar value."""
|
||||
if isinstance(value, bool):
|
||||
literal = "true" if value else "false"
|
||||
elif isinstance(value, int):
|
||||
literal = str(value)
|
||||
elif isinstance(value, float):
|
||||
if not math.isfinite(value):
|
||||
raise ValueError(
|
||||
f"Unsupported annotation value {value!r} for key {key!r}: "
|
||||
f"NaN and infinity are not valid C++ literals."
|
||||
)
|
||||
literal = str(value)
|
||||
elif isinstance(value, str):
|
||||
literal = _encaps(value)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported annotation type {type(value).__name__} for {key}: "
|
||||
f"expected bool, int, float, or str."
|
||||
)
|
||||
|
||||
if key:
|
||||
self._writer.write_line("%s.append(%s, %s);" % (builder, key, literal))
|
||||
else:
|
||||
self._writer.write_line("%s.append(%s);" % (builder, literal))
|
||||
|
||||
def _gen_server_parameter_annotations(self, var_name, param):
|
||||
# type: (str, ast.ServerParameter) -> None
|
||||
"""Generate setAnnotations call if the parameter has annotations."""
|
||||
if param.annotations:
|
||||
with self.get_initializer_lambda(
|
||||
"static const auto kAnnotations", return_type="BSONObj"
|
||||
):
|
||||
self._writer.write_line("BSONObjBuilder builder;")
|
||||
for k, v in param.annotations.items():
|
||||
self._gen_annotations_elem("builder", _encaps(k), v)
|
||||
self._writer.write_line("return builder.obj();")
|
||||
self._writer.write_line("%s->setAnnotations(kAnnotations);" % var_name)
|
||||
|
||||
def _gen_server_parameter_specialized(self, param):
|
||||
# type: (ast.ServerParameter) -> None
|
||||
"""Generate a specialized ServerParameter."""
|
||||
@ -3187,6 +3268,8 @@ class _CppSourceFileWriter(_CppFileWriterBase):
|
||||
if param.is_deprecated:
|
||||
self._writer.write_line("sp->setIsDeprecated(true);")
|
||||
|
||||
self._gen_server_parameter_annotations("sp", param)
|
||||
|
||||
self._writer.write_line("return std::move(sp);")
|
||||
|
||||
def _gen_server_parameter_class_definitions(self, param):
|
||||
@ -3261,6 +3344,8 @@ class _CppSourceFileWriter(_CppFileWriterBase):
|
||||
if param.is_deprecated:
|
||||
self._writer.write_line("ret->setIsDeprecated(true);")
|
||||
|
||||
self._gen_server_parameter_annotations("ret", param)
|
||||
|
||||
if param.default and not (param.cpp_vartype and param.cpp_varname):
|
||||
# Only need to call setDefault() if we haven't in-place initialized the declared var.
|
||||
self._writer.write_line(
|
||||
|
||||
@ -917,6 +917,13 @@ def _parse_server_parameter_class(ctxt, node):
|
||||
return spc
|
||||
|
||||
|
||||
def _parse_annotations(ctxt, node):
|
||||
# type: (errors.ParserContext, yaml.nodes.MappingNode) -> Dict[str, Any]
|
||||
"""Parse an annotations block as an opaque YAML mapping."""
|
||||
constructor = yaml.constructor.SafeConstructor()
|
||||
return constructor.construct_object(node, deep=True)
|
||||
|
||||
|
||||
def _parse_server_parameter(ctxt, spec, name, node):
|
||||
# type: (errors.ParserContext, syntax.IDLSpec, str, Union[yaml.nodes.MappingNode, yaml.nodes.ScalarNode, yaml.nodes.SequenceNode]) -> None
|
||||
"""Parse a server_parameters section in the IDL file."""
|
||||
@ -950,6 +957,7 @@ def _parse_server_parameter(ctxt, spec, name, node):
|
||||
"omit_in_ftdc": _RuleDesc("bool_scalar"),
|
||||
"cpp_class": _RuleDesc("scalar_or_mapping", mapping_parser_func=map_class),
|
||||
"is_deprecated": _RuleDesc("bool_scalar"),
|
||||
"annotations": _RuleDesc("mapping", mapping_parser_func=_parse_annotations),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -858,6 +858,8 @@ class ServerParameter(common.SourceLocation):
|
||||
|
||||
self.is_deprecated = False # type: bool
|
||||
|
||||
self.annotations = None # type: Dict[str, Any]
|
||||
|
||||
super(ServerParameter, self).__init__(file_name, line, column)
|
||||
|
||||
|
||||
|
||||
@ -690,6 +690,11 @@ properties:
|
||||
description: >
|
||||
Marks the server parameter as deprecated. Warns users if the server parameter
|
||||
is ever used. Defaults to false.
|
||||
annotations:
|
||||
type: object
|
||||
description: >
|
||||
Opaque metadata block attached to the server parameter. Parsed as-is and
|
||||
exposed at runtime via ServerParameter::annotations() as a BSONObj.
|
||||
on_update:
|
||||
type: string
|
||||
description: >
|
||||
|
||||
@ -39,3 +39,4 @@ import idl.errors # noqa: F401
|
||||
import idl.generator # noqa: F401
|
||||
import idl.parser # noqa: F401
|
||||
import idl.syntax # noqa: F401
|
||||
import idl.writer # noqa: F401
|
||||
|
||||
@ -2364,6 +2364,76 @@ class TestBinder(testcase.IDLTestcase):
|
||||
""")
|
||||
)
|
||||
|
||||
def test_server_parameter_annotations_positive(self):
|
||||
# type: () -> None
|
||||
"""Positive server parameter annotations test cases."""
|
||||
|
||||
# server parameter with cpp_varname and simple annotations.
|
||||
self.assert_bind(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: fooWire
|
||||
applicability: queryShape
|
||||
""")
|
||||
)
|
||||
|
||||
# server parameter with cpp_class and annotations.
|
||||
self.assert_bind(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_class: baz
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: fooWire
|
||||
""")
|
||||
)
|
||||
|
||||
# annotations with nested mapping (fcv sub-block).
|
||||
self.assert_bind(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: fooWire
|
||||
applicability: queryShape
|
||||
fcv:
|
||||
min: "9.0"
|
||||
""")
|
||||
)
|
||||
|
||||
# multiple annotation keys.
|
||||
self.assert_bind(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: fooWire
|
||||
security:
|
||||
audit: true
|
||||
""")
|
||||
)
|
||||
|
||||
def test_server_parameter_negative(self):
|
||||
# type: () -> None
|
||||
"""Negative server parameter test cases."""
|
||||
|
||||
@ -36,6 +36,7 @@ idl base directory:
|
||||
$ coverage run run_tests.py && coverage html
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import io
|
||||
import os
|
||||
import unittest
|
||||
@ -475,6 +476,157 @@ class TestGenerator(testcase.IDLTestcase):
|
||||
)
|
||||
self.assertIn(expected, header)
|
||||
|
||||
def test_server_parameter_annotations_generates_set_call(self) -> None:
|
||||
"""Test generation of setAnnotations call for annotated server parameter."""
|
||||
_, source = self.assert_generate(
|
||||
self.view_test_common_types
|
||||
+ dedent("""
|
||||
server_parameters:
|
||||
testAnnotatedParam:
|
||||
description: "Test annotated parameter"
|
||||
set_at: ["startup", "runtime"]
|
||||
redact: false
|
||||
cpp_varname: testParameter
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: testWire
|
||||
applicability: queryShape
|
||||
""")
|
||||
)
|
||||
|
||||
# Verify the BSON builder lambda is generated.
|
||||
self.assertIn("kAnnotations", source)
|
||||
self.assertIn("BSONObjBuilder builder", source)
|
||||
self.assertIn('"query_knob"', source)
|
||||
self.assertIn('"wire_name"', source)
|
||||
self.assertIn('"testWire"', source)
|
||||
self.assertIn('"applicability"', source)
|
||||
self.assertIn('"queryShape"', source)
|
||||
|
||||
# Verify setAnnotations call.
|
||||
self.assertIn("setAnnotations(kAnnotations)", source)
|
||||
|
||||
def test_server_parameter_no_annotations_no_set_call(self) -> None:
|
||||
"""Test that server parameter without annotations does not generate setAnnotations."""
|
||||
_, source = self.assert_generate(
|
||||
self.view_test_common_types
|
||||
+ dedent("""
|
||||
server_parameters:
|
||||
testUnannotatedParam:
|
||||
description: "Test unannotated parameter"
|
||||
set_at: ["startup", "runtime"]
|
||||
redact: false
|
||||
cpp_varname: testParameter
|
||||
""")
|
||||
)
|
||||
|
||||
self.assertNotIn("setAnnotations", source)
|
||||
self.assertNotIn("kAnnotations", source)
|
||||
|
||||
def test_server_parameter_nested_annotations_bson(self) -> None:
|
||||
"""Test nested annotation values produce correct nested BSON output."""
|
||||
_, source = self.assert_generate(
|
||||
self.view_test_common_types
|
||||
+ dedent("""
|
||||
server_parameters:
|
||||
testNestedParam:
|
||||
description: "Test nested annotations"
|
||||
set_at: ["startup", "runtime"]
|
||||
redact: false
|
||||
cpp_varname: testParameter
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: testNestedWire
|
||||
applicability: queryShape
|
||||
fcv:
|
||||
min: "9.0"
|
||||
""")
|
||||
)
|
||||
|
||||
# Verify nested BSON structure.
|
||||
self.assertIn("kAnnotations", source)
|
||||
self.assertIn("subobjStart", source)
|
||||
self.assertIn('"fcv"', source)
|
||||
self.assertIn('"min"', source)
|
||||
self.assertIn('"9.0"', source)
|
||||
|
||||
# Verify setAnnotations call.
|
||||
self.assertIn("setAnnotations(kAnnotations)", source)
|
||||
|
||||
def test_server_parameter_annotations_mixed_types_bson(self) -> None:
|
||||
"""Test annotation values of different YAML types produce correct BSON output."""
|
||||
_, source = self.assert_generate(
|
||||
self.view_test_common_types
|
||||
+ dedent("""
|
||||
server_parameters:
|
||||
testMixedParam:
|
||||
description: "Test mixed-type annotations"
|
||||
set_at: ["startup", "runtime"]
|
||||
redact: false
|
||||
cpp_varname: testParameter
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: testMixedWire
|
||||
pqs_settable: true
|
||||
max_retries: 3
|
||||
applicability:
|
||||
- queryShape
|
||||
- opCtx
|
||||
""")
|
||||
)
|
||||
|
||||
# Verify string value.
|
||||
self.assertIn('"testMixedWire"', source)
|
||||
|
||||
# Verify boolean value appended.
|
||||
self.assertIn('append("pqs_settable", true)', source)
|
||||
|
||||
# Verify integer value appended.
|
||||
self.assertIn('append("max_retries", 3)', source)
|
||||
|
||||
# Verify array value.
|
||||
self.assertIn("subarrayStart", source)
|
||||
self.assertIn('"queryShape"', source)
|
||||
self.assertIn('"opCtx"', source)
|
||||
|
||||
# Verify setAnnotations call.
|
||||
self.assertIn("setAnnotations(kAnnotations)", source)
|
||||
|
||||
def test_server_parameter_annotations_rejects_unquoted_date(self) -> None:
|
||||
"""YAML auto-coerces unquoted dates to datetime.date; the generator must reject them."""
|
||||
with self.assertRaises(ValueError):
|
||||
self.assert_generate(
|
||||
self.view_test_common_types
|
||||
+ dedent("""
|
||||
server_parameters:
|
||||
testDateParam:
|
||||
description: "Test param with unquoted date annotation"
|
||||
set_at: ["startup", "runtime"]
|
||||
redact: false
|
||||
cpp_varname: testParameter
|
||||
annotations:
|
||||
wire_name: 2026-04-14
|
||||
""")
|
||||
)
|
||||
|
||||
def test_server_parameter_annotations_accepts_quoted_date(self) -> None:
|
||||
"""A quoted date stays a string and flows through as a C++ string literal."""
|
||||
_, source = self.assert_generate(
|
||||
self.view_test_common_types
|
||||
+ dedent("""
|
||||
server_parameters:
|
||||
testQuotedDateParam:
|
||||
description: "Test param with quoted date annotation"
|
||||
set_at: ["startup", "runtime"]
|
||||
redact: false
|
||||
cpp_varname: testParameter
|
||||
annotations:
|
||||
wire_name: "2026-04-14"
|
||||
""")
|
||||
)
|
||||
self.assertIn('"2026-04-14"', source)
|
||||
self.assertIn("setAnnotations(kAnnotations)", source)
|
||||
|
||||
def test_command_view_type_generates_anchor(self) -> None:
|
||||
"""Test anchor generation on command with view parameter."""
|
||||
header, _ = self.assert_generate(
|
||||
@ -1700,5 +1852,136 @@ class TestGenerator(testcase.IDLTestcase):
|
||||
)
|
||||
|
||||
|
||||
class TestAnnotationsCodegen(testcase.IDLTestcase):
|
||||
"""Direct unit tests for the _gen_annotations_* helpers on _CppSourceFileWriter."""
|
||||
|
||||
def _emit(self, key, value, depth=0):
|
||||
# type: (object, object, int) -> str
|
||||
"""Run _gen_annotations_elem on a fresh writer and return the generated text."""
|
||||
stream = io.StringIO()
|
||||
source_writer = idl.generator._CppSourceFileWriter(
|
||||
idl.writer.IndentedTextWriter(stream), target_arch=""
|
||||
)
|
||||
source_writer._gen_annotations_elem("builder", key, value, depth=depth)
|
||||
return stream.getvalue()
|
||||
|
||||
def test_scalar_string_keyed(self):
|
||||
self.assertEqual(self._emit('"k"', "hello"), 'builder.append("k", "hello");\n')
|
||||
|
||||
def test_scalar_string_unkeyed(self):
|
||||
self.assertEqual(self._emit(None, "hello"), 'builder.append("hello");\n')
|
||||
|
||||
def test_scalar_bool_true(self):
|
||||
self.assertEqual(self._emit('"k"', True), 'builder.append("k", true);\n')
|
||||
|
||||
def test_scalar_bool_false(self):
|
||||
self.assertEqual(self._emit('"k"', False), 'builder.append("k", false);\n')
|
||||
|
||||
def test_scalar_int(self):
|
||||
self.assertEqual(self._emit('"k"', 3), 'builder.append("k", 3);\n')
|
||||
|
||||
def test_scalar_float(self):
|
||||
self.assertEqual(self._emit('"k"', 3.25), 'builder.append("k", 3.25);\n')
|
||||
|
||||
def test_null_keyed(self):
|
||||
self.assertEqual(self._emit('"k"', None), 'builder.appendNull("k");\n')
|
||||
|
||||
def test_null_unkeyed(self):
|
||||
self.assertEqual(self._emit(None, None), "builder.appendNull();\n")
|
||||
|
||||
def test_empty_dict(self):
|
||||
expected = dedent("""\
|
||||
{
|
||||
BSONObjBuilder sub0(builder.subobjStart("k"));
|
||||
}
|
||||
""")
|
||||
self.assertEqual(self._emit('"k"', {}), expected)
|
||||
|
||||
def test_flat_dict(self):
|
||||
expected = dedent("""\
|
||||
{
|
||||
BSONObjBuilder sub0(builder.subobjStart("k"));
|
||||
sub0.append("a", 1);
|
||||
}
|
||||
""")
|
||||
self.assertEqual(self._emit('"k"', {"a": 1}), expected)
|
||||
|
||||
def test_nested_dict_depth_counter(self):
|
||||
# Depth increments so nested sub-builder names don't collide.
|
||||
expected = dedent("""\
|
||||
{
|
||||
BSONObjBuilder sub0(builder.subobjStart("root"));
|
||||
{
|
||||
BSONObjBuilder sub1(sub0.subobjStart("outer"));
|
||||
sub1.append("inner", 1);
|
||||
}
|
||||
}
|
||||
""")
|
||||
self.assertEqual(self._emit('"root"', {"outer": {"inner": 1}}), expected)
|
||||
|
||||
def test_empty_list(self):
|
||||
expected = dedent("""\
|
||||
{
|
||||
BSONArrayBuilder arr0(builder.subarrayStart("k"));
|
||||
}
|
||||
""")
|
||||
self.assertEqual(self._emit('"k"', []), expected)
|
||||
|
||||
def test_flat_list(self):
|
||||
expected = dedent("""\
|
||||
{
|
||||
BSONArrayBuilder arr0(builder.subarrayStart("k"));
|
||||
arr0.append(1);
|
||||
arr0.append(2);
|
||||
}
|
||||
""")
|
||||
self.assertEqual(self._emit('"k"', [1, 2]), expected)
|
||||
|
||||
def test_list_of_dicts(self):
|
||||
# Items inside a list use the unkeyed subobjStart form and advance depth.
|
||||
expected = dedent("""\
|
||||
{
|
||||
BSONArrayBuilder arr0(builder.subarrayStart("k"));
|
||||
{
|
||||
BSONObjBuilder sub1(arr0.subobjStart());
|
||||
sub1.append("a", 1);
|
||||
}
|
||||
{
|
||||
BSONObjBuilder sub1(arr0.subobjStart());
|
||||
sub1.append("b", 2);
|
||||
}
|
||||
}
|
||||
""")
|
||||
self.assertEqual(self._emit('"k"', [{"a": 1}, {"b": 2}]), expected)
|
||||
|
||||
def test_scalar_rejects_date(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', datetime.date(2026, 4, 14))
|
||||
|
||||
def test_scalar_rejects_datetime(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', datetime.datetime(2026, 4, 14, 10, 30))
|
||||
|
||||
def test_scalar_rejects_nan(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', float("nan"))
|
||||
|
||||
def test_scalar_rejects_positive_inf(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', float("inf"))
|
||||
|
||||
def test_scalar_rejects_negative_inf(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', float("-inf"))
|
||||
|
||||
def test_scalar_rejects_bytes(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', b"hello")
|
||||
|
||||
def test_scalar_rejects_set(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self._emit('"k"', {"a", "b"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -1566,6 +1566,74 @@ class TestParser(testcase.IDLTestcase):
|
||||
idl.errors.ERROR_ID_IS_NODE_TYPE_SCALAR_OR_MAPPING,
|
||||
)
|
||||
|
||||
def test_server_parameter_annotations_positive(self):
|
||||
# type: () -> None
|
||||
"""Positive server parameter annotations tests."""
|
||||
|
||||
# server parameter with annotations mapping.
|
||||
self.assert_parse(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: fooWire
|
||||
applicability: queryShape
|
||||
""")
|
||||
)
|
||||
|
||||
def test_server_parameter_annotations_negative(self):
|
||||
# type: () -> None
|
||||
"""Negative server parameter annotations tests."""
|
||||
|
||||
# annotations as a scalar should fail. Must be a mapping.
|
||||
self.assert_parse_fail(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations: some_scalar_value
|
||||
"""),
|
||||
idl.errors.ERROR_ID_IS_NODE_TYPE,
|
||||
)
|
||||
|
||||
# annotations as a sequence should fail. Must be a mapping.
|
||||
self.assert_parse_fail(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations:
|
||||
- item1
|
||||
- item2
|
||||
"""),
|
||||
idl.errors.ERROR_ID_IS_NODE_TYPE,
|
||||
)
|
||||
|
||||
# annotations as null should fail. Must be a mapping.
|
||||
self.assert_parse_fail(
|
||||
textwrap.dedent("""
|
||||
server_parameters:
|
||||
foo:
|
||||
set_at: startup
|
||||
description: bar
|
||||
redact: false
|
||||
cpp_varname: baz
|
||||
annotations: null
|
||||
"""),
|
||||
idl.errors.ERROR_ID_IS_NODE_TYPE,
|
||||
)
|
||||
|
||||
def test_feature_flag(self):
|
||||
# type: () -> None
|
||||
"""Test feature flag."""
|
||||
|
||||
@ -387,6 +387,14 @@ public:
|
||||
return _isDeprecated;
|
||||
}
|
||||
|
||||
void setAnnotations(BSONObj annotations) {
|
||||
_annotations = std::move(annotations);
|
||||
}
|
||||
|
||||
BSONObj annotations() const {
|
||||
return _annotations;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Helper for translating setParameter values from BSON to string.
|
||||
StatusWith<std::string> _coerceToString(const BSONElement&);
|
||||
@ -410,6 +418,7 @@ private:
|
||||
bool _testOnly = false;
|
||||
bool _redact = false;
|
||||
bool _isOmittedInFTDC = false;
|
||||
BSONObj _annotations;
|
||||
ParameterGatingFeatureFlag* _featureFlag = nullptr;
|
||||
boost::optional<multiversion::FeatureCompatibilityVersion> _minFCV;
|
||||
|
||||
|
||||
@ -298,6 +298,43 @@ TEST(IDLServerParameterWithStorage, exportedDefaults) {
|
||||
ASSERT_EQ(test::kUgly_complicated_name_spDefault, true);
|
||||
}
|
||||
|
||||
TEST(IDLServerParameterWithStorage, annotationsAccessible) {
|
||||
auto* sp = getNodeServerParameter("storageIntAnnotated");
|
||||
ASSERT_BSONOBJ_EQ(
|
||||
sp->annotations(),
|
||||
BSON("query_knob" << BSON("wire_name" << "intAnnotatedWire"
|
||||
<< "applicability" << BSON_ARRAY("queryShape")
|
||||
<< "fcv" << BSON("min" << "9.0"))));
|
||||
}
|
||||
|
||||
TEST(IDLServerParameterWithStorage, noAnnotationsReturnsEmpty) {
|
||||
auto* sp = getNodeServerParameter("stdIntDeclared");
|
||||
ASSERT_TRUE(sp->annotations().isEmpty());
|
||||
}
|
||||
|
||||
TEST(IDLServerParameterWithStorage, annotationsArrayOfObjects) {
|
||||
auto* sp = getNodeServerParameter("storageIntArrayOfObjects");
|
||||
ASSERT_BSONOBJ_EQ(sp->annotations(),
|
||||
BSON("items" << BSON_ARRAY(BSON("name" << "first" << "value" << 1)
|
||||
<< BSON("name" << "second" << "value" << 2))));
|
||||
}
|
||||
|
||||
TEST(IDLServerParameterWithStorage, annotationsNestedArrays) {
|
||||
auto* sp = getNodeServerParameter("storageIntNestedArrays");
|
||||
ASSERT_BSONOBJ_EQ(sp->annotations(),
|
||||
BSON("matrix" << BSON_ARRAY(BSON_ARRAY(1 << 2) << BSON_ARRAY(3 << 4))));
|
||||
}
|
||||
|
||||
TEST(IDLServerParameterWithStorage, annotationsEmptyArray) {
|
||||
auto* sp = getNodeServerParameter("storageIntEmptyArray");
|
||||
ASSERT_BSONOBJ_EQ(sp->annotations(), BSON("tags" << BSONArray()));
|
||||
}
|
||||
|
||||
TEST(IDLServerParameterWithStorage, annotationsOnClusterParameter) {
|
||||
auto* sp = getClusterServerParameter("testClusterServerParameter");
|
||||
ASSERT_BSONOBJ_EQ(sp->annotations(), BSON("cluster_meta" << BSON("scope" << "global")));
|
||||
}
|
||||
|
||||
// Test that the RAIIServerParameterControllerForTest works correctly on IDL-generated types.
|
||||
TEST(IDLServerParameterWithStorage, RAIIServerParameterController) {
|
||||
// Test int
|
||||
|
||||
@ -108,6 +108,9 @@ server_parameters:
|
||||
on_update: "onUpdateTestClusterServerParameter"
|
||||
redact: false
|
||||
omit_in_ftdc: false
|
||||
annotations:
|
||||
cluster_meta:
|
||||
scope: global
|
||||
|
||||
storageIntDeprecated:
|
||||
set_at: [startup, runtime]
|
||||
@ -120,3 +123,55 @@ server_parameters:
|
||||
lt: 1000
|
||||
redact: false
|
||||
is_deprecated: true
|
||||
|
||||
storageIntAnnotated:
|
||||
set_at: [startup, runtime]
|
||||
description: "Server parameter with annotations."
|
||||
cpp_vartype: AtomicWord<int>
|
||||
cpp_varname: gIntAnnotated
|
||||
default: 7
|
||||
redact: false
|
||||
annotations:
|
||||
query_knob:
|
||||
wire_name: intAnnotatedWire
|
||||
applicability: [queryShape]
|
||||
fcv:
|
||||
min: "9.0"
|
||||
|
||||
storageIntArrayOfObjects:
|
||||
set_at: [startup, runtime]
|
||||
description: "Parameter with array-of-objects annotations."
|
||||
cpp_vartype: AtomicWord<int>
|
||||
cpp_varname: gIntArrayOfObjects
|
||||
default: 1
|
||||
redact: false
|
||||
annotations:
|
||||
items:
|
||||
- name: first
|
||||
value: 1
|
||||
- name: second
|
||||
value: 2
|
||||
|
||||
storageIntNestedArrays:
|
||||
set_at: [startup, runtime]
|
||||
description: "Parameter with nested array annotations."
|
||||
cpp_vartype: AtomicWord<int>
|
||||
cpp_varname: gIntNestedArrays
|
||||
default: 1
|
||||
redact: false
|
||||
annotations:
|
||||
matrix:
|
||||
- - 1
|
||||
- 2
|
||||
- - 3
|
||||
- 4
|
||||
|
||||
storageIntEmptyArray:
|
||||
set_at: [startup, runtime]
|
||||
description: "Parameter with empty array annotation."
|
||||
cpp_vartype: AtomicWord<int>
|
||||
cpp_varname: gIntEmptyArray
|
||||
default: 1
|
||||
redact: false
|
||||
annotations:
|
||||
tags: []
|
||||
|
||||
Loading…
Reference in New Issue
Block a user