mongo/buildscripts/idl/tests/test_binder.py

3584 lines
103 KiB
Python

#!/usr/bin/env python3
#
# Copyright (C) 2018-present MongoDB, Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the Server Side Public License, version 1,
# as published by MongoDB, Inc.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# Server Side Public License for more details.
#
# You should have received a copy of the Server Side Public License
# along with this program. If not, see
# <http://www.mongodb.com/licensing/server-side-public-license>.
#
# As a special exception, the copyright holders give permission to link the
# code of portions of this program with the OpenSSL library under certain
# conditions as described in each individual source file and distribute
# linked combinations including the program with the OpenSSL library. You
# must comply with the Server Side Public License in all respects for
# all of the code used other than as permitted herein. If you modify file(s)
# with this exception, you may extend this exception to your version of the
# file(s), but you are not obligated to do so. If you do not wish to do so,
# delete this exception statement from your version. If you delete this
# exception statement from all source files in the program, then also delete
# it in the license file.
#
"""Test cases for IDL binder."""
import textwrap
import unittest
# import package so that it works regardless of whether we run as a module or file
if __package__ is None:
import sys
from os import path
sys.path.append(path.dirname(path.abspath(__file__)))
import testcase
from context import idl
else:
from . import testcase
from .context import idl
# All YAML tests assume 4 space indent
INDENT_SPACE_COUNT = 4
def fill_spaces(count):
# type: (int) -> str
"""Fill a string full of spaces."""
fill = ""
for _ in range(count * INDENT_SPACE_COUNT):
fill += " "
return fill
def indent_text(count, unindented_text):
# type: (int, str) -> str
"""Indent each line of a multi-line string."""
lines = unindented_text.splitlines()
fill = fill_spaces(count)
return "\n".join(fill + line for line in lines)
class TestBinder(testcase.IDLTestcase):
"""Test cases for the IDL binder."""
# Create a text wrap for common types.
common_types = textwrap.dedent("""
types:
object:
description: foo
cpp_type: foo
bson_serialization_type: object
serializer: foo
deserializer: foo
is_view: false
bool:
description: foo
cpp_type: foo
bson_serialization_type: any
serializer: foo
deserializer: foo
is_view: false
string:
description: foo
cpp_type: foo
bson_serialization_type: string
serializer: foo
deserializer: foo
is_view: false
any_type:
description: foo
cpp_type: foo
bson_serialization_type: any
serializer: foo
deserializer: foo
is_view: false
tenant_id:
bson_serialization_type: any
description: foo
cpp_type: foo
deserializer: foo
serializer: foo
is_view: false
database_name:
bson_serialization_type: string
description: foo
cpp_type: foo
serializer: foo
deserializer: foo
is_view: false
serialization_context:
bson_serialization_type: any
description: foo
cpp_type: foo
internal_only: true
is_view: false
""")
def test_empty(self):
# type: () -> None
"""Test an empty document works."""
self.assert_bind("")
def test_global_positive(self):
# type: () -> None
"""Postive global tests."""
spec = self.assert_bind(
textwrap.dedent("""
global:
cpp_namespace: 'mongo'
cpp_includes:
- 'bar'
- 'foo'""")
)
self.assertEqual(spec.globals.cpp_namespace, "mongo")
self.assertListEqual(spec.globals.cpp_includes, ["bar", "foo"])
spec = self.assert_bind(
textwrap.dedent("""
global:
cpp_namespace: 'mongo::nested'
""")
)
self.assertEqual(spec.globals.cpp_namespace, "mongo::nested")
def test_global_negatives(self):
# type: () -> None
"""Postive global tests."""
self.assert_bind_fail(
textwrap.dedent("""
global:
cpp_namespace: 'something'
"""),
idl.errors.ERROR_ID_BAD_CPP_NAMESPACE,
)
def test_type_positive(self):
# type: () -> None
"""Positive type tests."""
self.assert_bind(
textwrap.dedent("""
types:
foo:
description: foo
cpp_type: foo
bson_serialization_type: string
serializer: foo
deserializer: foo
default: foo
is_view: false
""")
)
# Test supported types
for bson_type in [
"bool",
"date",
"null",
"decimal",
"double",
"int",
"long",
"objectid",
"regex",
"string",
"timestamp",
"undefined",
]:
self.assert_bind(
textwrap.dedent(
"""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: %s
default: foo
deserializer: BSONElement::fake
is_view: false
"""
% (bson_type)
)
)
# Test supported numeric types
for cpp_type in [
"std::int32_t",
"std::uint32_t",
"std::int32_t",
"std::uint64_t",
"std::vector<std::uint8_t>",
"std::array<std::uint8_t, 16>",
]:
self.assert_bind(
textwrap.dedent(
"""
types:
foofoo:
description: foo
cpp_type: %s
bson_serialization_type: int
deserializer: BSONElement::fake
is_view: false
"""
% (cpp_type)
)
)
# Test object
self.assert_bind(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: object
serializer: foo
deserializer: foo
default: foo
is_view: false
""")
)
# Test 'any'
self.assert_bind(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: any
serializer: foo
deserializer: foo
default: foo
is_view: false
""")
)
# Test 'chain'
self.assert_bind(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: chain
serializer: foo
deserializer: foo
default: foo
is_view: false
""")
)
# Test supported bindata_subtype
for bindata_subtype in ["generic", "function", "uuid", "md5"]:
self.assert_bind(
textwrap.dedent(
"""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: %s
deserializer: BSONElement::fake
is_view: false
"""
% (bindata_subtype)
)
)
def test_type_negative(self):
# type: () -> None
"""Negative type tests for properties that types and fields share."""
# Test bad bson type name
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: foo
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_TYPE,
)
# Test bad cpp_type name
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: StringData
bson_serialization_type: string
deserializer: bar
is_view: false
"""),
idl.errors.ERROR_ID_NO_STRINGDATA,
)
# Test unsupported serialization
for cpp_type in [
"char",
"signed char",
"unsigned char",
"signed short int",
"short int",
"short",
"signed short",
"unsigned short",
"unsigned short int",
"signed int",
"signed",
"unsigned int",
"unsigned",
"signed long int",
"signed long",
"int",
"long int",
"long",
"unsigned long int",
"unsigned long",
"signed long long int",
"signed long long",
"long long int",
"long long",
"unsigned long int",
"unsigned long",
"wchar_t",
"char16_t",
"char32_t",
"int8_t",
"int16_t",
"int32_t",
"int64_t",
"uint8_t",
"uint16_t",
"uint32_t",
"uint64_t",
]:
self.assert_bind_fail(
textwrap.dedent(
"""
types:
foofoo:
description: foo
cpp_type: %s
bson_serialization_type: int
deserializer: BSONElement::int
is_view: false
"""
% (cpp_type)
),
idl.errors.ERROR_ID_BAD_NUMERIC_CPP_TYPE,
)
# Test the std prefix 8 and 16-byte integers fail
for std_cpp_type in ["std::int8_t", "std::int16_t", "std::uint8_t", "std::uint16_t"]:
self.assert_bind_fail(
textwrap.dedent(
"""
types:
foofoo:
description: foo
cpp_type: %s
bson_serialization_type: int
deserializer: BSONElement::int
is_view: false
"""
% (std_cpp_type)
),
idl.errors.ERROR_ID_BAD_NUMERIC_CPP_TYPE,
)
# Test bindata_subtype missing
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: bindata
deserializer: BSONElement::fake
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE,
)
# Test fake bindata_subtype is wrong
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: foo
deserializer: BSONElement::fake
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE,
)
# Test deprecated bindata_subtype 'binary', and 'uuid_old' are wrong
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: binary
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE,
)
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: uuid_old
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_VALUE,
)
# Test bindata_subtype on wrong type
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: string
bindata_subtype: generic
deserializer: BSONElement::fake
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_BINDATA_SUBTYPE_TYPE,
)
# Test bindata with default
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: uuid
default: 42
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BINDATA_DEFAULT,
)
# Test bindata in list of types
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type:
- bindata
- string
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_TYPE,
)
# Test bindata in list of types
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: StringData
bson_serialization_type:
- bindata
- string
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_TYPE,
)
# Test 'any' in list of types
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type:
- any
- int
is_view: false
"""),
idl.errors.ERROR_ID_BAD_ANY_TYPE_USE,
)
# Test object in list of types
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type:
- object
- int
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_TYPE_LIST,
)
# Test fake in list of types
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type:
- int
- fake
is_view: false
"""),
idl.errors.ERROR_ID_BAD_BSON_TYPE,
)
# Test 'chain' in list of types
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type:
- chain
- int
is_view: false
"""),
idl.errors.ERROR_ID_BAD_ANY_TYPE_USE,
)
# Test 'any' serialization needs deserializer
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: any
is_view: false
"""),
idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD,
)
# Test 'chain' serialization needs deserializer
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: chain
serializer: bar
is_view: false
"""),
idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD,
)
# Test 'string' serialization needs deserializer
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: string
serializer: bar
is_view: false
"""),
idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD,
)
# Test 'date' serialization needs deserializer
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: date
is_view: false
"""),
idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD,
)
# Test 'chain' serialization needs serializer
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: foo
bson_serialization_type: chain
deserializer: bar
is_view: false
"""),
idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD,
)
# Test list of bson types needs deserializer
self.assert_bind_fail(
textwrap.dedent("""
types:
foofoo:
description: foo
cpp_type: std::int32_t
bson_serialization_type:
- int
- string
is_view: false
"""),
idl.errors.ERROR_ID_MISSING_AST_REQUIRED_FIELD,
)
# Test array as name
self.assert_bind_fail(
textwrap.dedent("""
types:
array<foo>:
description: foo
cpp_type: foo
bson_serialization_type: string
deserializer: bar
is_view: false
"""),
idl.errors.ERROR_ID_ARRAY_NOT_VALID_TYPE,
)
def test_struct_positive(self):
# type: () -> None
"""Positive struct tests."""
# Setup some common types
test_preamble = self.common_types + indent_text(
1,
textwrap.dedent("""
int:
description: foo
cpp_type: std::int32_t
bson_serialization_type: int
deserializer: mongo::BSONElement::_numberInt
is_view: false
"""),
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo: string
""")
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo: array<int>
""")
)
def test_struct_negative(self):
# type: () -> None
"""Negative struct tests."""
# Setup some common types
test_preamble = self.common_types + indent_text(
1,
textwrap.dedent("""
int:
description: foo
cpp_type: std::int32_t
bson_serialization_type: int
deserializer: mongo::BSONElement::_numberInt
is_view: false
"""),
)
# Test array as name
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
array<foo>:
description: foo
strict: true
fields:
foo: string
"""),
idl.errors.ERROR_ID_ARRAY_NOT_VALID_TYPE,
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo: array<int>
""")
)
def test_variant_positive(self):
# type: () -> None
"""Positive variant test cases."""
# Setup some common types
test_preamble = self.common_types + indent_text(
1,
textwrap.dedent("""
int:
description: foo
cpp_type: std::int32_t
bson_serialization_type: int
deserializer: mongo::BSONElement::_numberInt
is_view: false
bindata_function:
bson_serialization_type: bindata
bindata_subtype: function
description: "A BSON bindata of function sub type"
cpp_type: "std::vector<std::uint8_t>"
deserializer: "mongo::BSONElement::_binDataVector"
is_view: false
"""),
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
- int
""")
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
- bindata_function
""")
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
- int
default: 1
""")
)
# Test multiple BSON serialization type Object.
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
insert_type:
description: foo
fields: {insert: string}
update_type:
description: foo
fields: {update: int}
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- insert_type
- update_type
- int
""")
)
def test_variant_negative(self):
# type: () -> None
"""Negative variant test cases."""
# Setup some common types
test_preamble = (
self.common_types
+ indent_text(
1,
textwrap.dedent("""
int:
description: foo
cpp_type: std::int32_t
bson_serialization_type: int
deserializer: mongo::BSONElement::_numberInt
is_view: false
safeInt:
bson_serialization_type:
- long
- int
- decimal
- double
description: foo
cpp_type: "std::int32_t"
deserializer: "mongo::BSONElement::safeNumberInt"
is_view: false
"""),
)
+ textwrap.dedent("""
enums:
foo_enum:
description: foo
type: int
values:
v1: 0
v2: 1
""")
)
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
"""),
idl.errors.ERROR_ID_USELESS_VARIANT,
)
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
- int
- not_defined
"""),
idl.errors.ERROR_ID_UNKNOWN_TYPE,
True,
)
# Enums are banned in variants for now.
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
- foo_enum
"""),
idl.errors.ERROR_ID_NO_VARIANT_ENUM,
True,
)
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- string
- string
"""),
idl.errors.ERROR_ID_VARIANT_DUPLICATE_TYPES,
)
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- array<string>
- array<string>
"""),
idl.errors.ERROR_ID_VARIANT_DUPLICATE_TYPES,
)
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
struct0:
description: foo
struct1:
description: foo
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- array<struct0>
- array<struct1>
"""),
idl.errors.ERROR_ID_VARIANT_DUPLICATE_TYPES,
)
# At most one array can have BSON serialization type NumberInt.
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- array<int>
- array<safeInt>
"""),
idl.errors.ERROR_ID_VARIANT_DUPLICATE_TYPES,
)
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
one_string:
description: foo
fields: {value: string}
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- one_string
- one_string
- int
"""),
idl.errors.ERROR_ID_VARIANT_STRUCTS,
True,
)
# For multiple BSON serialization type Objects they must have different field names
# for their first field.
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
one_string:
description: foo
fields: {value: string}
one_int:
description: foo
fields: {value: int}
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- one_string
- one_int
- int
"""),
idl.errors.ERROR_ID_VARIANT_STRUCTS,
True,
)
# At most one type can have BSON serialization type NumberInt.
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
my_variant_field:
type:
variant:
- safeInt
- int
"""),
idl.errors.ERROR_ID_VARIANT_DUPLICATE_TYPES,
)
def test_field_positive(self):
# type: () -> None
"""Positive test cases for field."""
# Setup some common types
test_preamble = self.common_types
# Short type
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
bar:
description: foo
strict: false
fields:
foo: string
""")
)
# Long type
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
bar:
description: foo
strict: false
fields:
foo:
type: string
""")
)
# Long type with default
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
bar:
description: foo
strict: false
fields:
foo:
type: string
default: bar
""")
)
# Test array as field type
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo: array<string>
""")
)
# Test array as field type
self.assert_bind(
self.common_types
+ indent_text(
1,
textwrap.dedent("""
arrayfake:
description: foo
cpp_type: foo
bson_serialization_type: string
serializer: foo
deserializer: foo
is_view: false
"""),
)
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
arrayOfString: arrayfake
""")
)
# Test always_serialize with optional
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo:
type: string
optional: true
always_serialize: true
""")
)
# Test field of a struct type with default=true
self.assert_bind(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
field1: string
bar:
description: foo
fields:
field2:
type: foo
default: true
""")
)
def test_field_negative(self):
# type: () -> None
"""Negative field tests."""
# Setup some common types
test_preamble = textwrap.dedent("""
types:
string:
description: foo
cpp_type: foo
bson_serialization_type: string
serializer: foo
deserializer: foo
default: foo
is_view: false
bindata:
description: foo
cpp_type: foo
bson_serialization_type: bindata
bindata_subtype: uuid
is_view: false
serialization_context:
bson_serialization_type: any
description: foo
cpp_type: foo
internal_only: true
is_view: false
""")
# Test field of a struct type with a non-true default
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
field1: string
bar:
description: foo
fields:
field2:
type: foo
default: foo
"""),
idl.errors.ERROR_ID_DEFAULT_MUST_BE_TRUE_OR_EMPTY_FOR_STRUCT,
)
# Test array as field name
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
array<foo>: string
"""),
idl.errors.ERROR_ID_ARRAY_NOT_VALID_TYPE,
)
# Test recursive array as field type
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo: array<array<string>>
"""),
idl.errors.ERROR_ID_BAD_ARRAY_TYPE_NAME,
True,
)
# Test inherited default with array
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo: array<string>
"""),
idl.errors.ERROR_ID_ARRAY_NO_DEFAULT,
)
# Test non-inherited default with array
self.assert_bind_fail(
self.common_types
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo:
type: array<string>
default: 123
"""),
idl.errors.ERROR_ID_ARRAY_NO_DEFAULT,
)
# Test bindata with default
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo:
type: bindata
default: 42
"""),
idl.errors.ERROR_ID_BAD_BINDATA_DEFAULT,
)
# Test default and optional for the same field
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo:
type: string
default: 42
optional: true
"""),
idl.errors.ERROR_ID_ILLEGAL_FIELD_DEFAULT_AND_OPTIONAL,
)
# Test always_serialize without optional for the same field
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: true
fields:
foo:
type: string
default: 42
always_serialize: true
"""),
idl.errors.ERROR_ID_ILLEGAL_FIELD_ALWAYS_SERIALIZE_NOT_OPTIONAL,
)
# Test duplicate comparison order
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
strict: false
generate_comparison_operators: true
fields:
foo:
type: string
comparison_order: 1
bar:
type: string
comparison_order: 1
"""),
idl.errors.ERROR_ID_IS_DUPLICATE_COMPARISON_ORDER,
)
# Test field marked with non_const_getter in immutable struct
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
immutable: true
fields:
foo:
type: string
non_const_getter: true
"""),
idl.errors.ERROR_ID_NON_CONST_GETTER_IN_IMMUTABLE_STRUCT,
)
def test_ignored_field_negative(self):
# type: () -> None
"""Test that if a field is marked as ignored, no other properties are set."""
for test_value in [
"optional: true",
"default: foo",
]:
self.assert_bind_fail(
self.common_types
+ textwrap.dedent(
"""
structs:
foo:
description: foo
strict: false
fields:
foo:
type: string
ignore: true
%s
"""
% (test_value)
),
idl.errors.ERROR_ID_FIELD_MUST_BE_EMPTY_FOR_IGNORED,
)
def test_chained_struct_positive(self):
# type: () -> None
"""Positive parser chaining test cases."""
# Setup some common types
test_preamble = (
self.common_types
+ indent_text(
1,
textwrap.dedent("""
foo1:
description: foo
cpp_type: foo
bson_serialization_type: chain
serializer: foo
deserializer: foo
default: foo
is_view: false
"""),
)
+ textwrap.dedent("""
structs:
chained:
description: foo
strict: false
chained2:
description: foo
strict: false
fields:
field1: string
""")
)
# A struct with only chaining
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
chained_structs:
chained2: alias
"""),
)
)
# Chaining struct's fields and explicit fields
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
chained_structs:
chained2: alias
fields:
str1: string
"""),
)
)
# Chained types and structs
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: false
chained_structs:
chained2: alias
fields:
str1: string
"""),
)
)
# Non-strict chained struct
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: false
chained_structs:
chained2: alias
fields:
foo1: string
"""),
)
)
# Inline Chained struct with strict true
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
fields:
field1: string
foobar:
description: foo
strict: false
inline_chained_structs: true
chained_structs:
bar1: alias
fields:
f1: string
"""),
)
)
# Inline Chained struct with strict true and inline_chained_structs defaulted
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
fields:
field1: string
foobar:
description: foo
strict: false
chained_structs:
bar1: alias
fields:
f1: string
"""),
)
)
# Chained struct with nested chained struct
self.assert_bind(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: false
chained_structs:
chained: alias
foobar:
description: foo
strict: false
chained_structs:
bar1: alias
fields:
f1: string
"""),
)
)
def test_chained_struct_negative(self):
# type: () -> None
"""Negative parser chaining test cases."""
# Setup some common types
test_preamble = (
self.common_types
+ indent_text(
1,
textwrap.dedent("""
foo1:
description: foo
cpp_type: foo
bson_serialization_type: chain
serializer: foo
deserializer: foo
default: foo
is_view: false
"""),
)
+ textwrap.dedent("""
structs:
chained:
description: foo
strict: false
fields:
field1: string
chained2:
description: foo
strict: false
fields:
field1: string
""")
)
# Non-existing chained struct
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
chained_structs:
foobar1: alias
"""),
),
idl.errors.ERROR_ID_UNKNOWN_TYPE,
True,
)
# Type as chained struct
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
chained_structs:
foo1: alias
"""),
),
idl.errors.ERROR_ID_CHAINED_STRUCT_NOT_FOUND,
)
# Duplicated field names across chained struct's fields and fields
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: false
chained_structs:
chained: alias
fields:
field1: string
"""),
),
idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD,
)
# Duplicated field names across chained structs
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: false
chained_structs:
chained: alias
chained2: alias
"""),
),
idl.errors.ERROR_ID_CHAINED_DUPLICATE_FIELD,
)
# Chained struct with strict true
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
bar1:
description: foo
strict: true
fields:
field1: string
foobar:
description: foo
strict: false
inline_chained_structs: false
chained_structs:
bar1: alias
fields:
f1: string
"""),
),
idl.errors.ERROR_ID_CHAINED_NO_NESTED_STRUCT_STRICT,
)
def test_enum_positive(self):
# type: () -> None
"""Positive enum test cases."""
# Test int
self.assert_bind(
textwrap.dedent("""
enums:
foo:
description: foo
type: int
values:
v1: 3
v2: 1
v3: 2
""")
)
# Test int - non continuous
self.assert_bind(
textwrap.dedent("""
enums:
foo:
description: foo
type: int
values:
v1: 0
v3: 2
""")
)
# Test string
self.assert_bind(
textwrap.dedent("""
enums:
foo:
description: foo
type: string
values:
v1: 0
v2: 1
v3: 2
""")
)
def test_enum_negative(self):
# type: () -> None
"""Negative enum test cases."""
# Test wrong type
self.assert_bind_fail(
textwrap.dedent("""
enums:
foo:
description: foo
type: foo
values:
v1: 0
"""),
idl.errors.ERROR_ID_ENUM_BAD_TYPE,
)
# Test int - dups
self.assert_bind_fail(
textwrap.dedent("""
enums:
foo:
description: foo
type: int
values:
v1: 1
v3: 1
"""),
idl.errors.ERROR_ID_ENUM_NON_UNIQUE_VALUES,
)
# Test int - non-integer value
self.assert_bind_fail(
textwrap.dedent("""
enums:
foo:
description: foo
type: int
values:
v1: foo
v3: 1
"""),
idl.errors.ERROR_ID_ENUM_BAD_INT_VAUE,
)
# Test string - dups
self.assert_bind_fail(
textwrap.dedent("""
enums:
foo:
description: foo
type: string
values:
v1: foo
v3: foo
"""),
idl.errors.ERROR_ID_ENUM_NON_UNIQUE_VALUES,
)
def test_struct_enum_negative(self):
# type: () -> None
"""Negative enum test cases."""
test_preamble = self.common_types + textwrap.dedent("""
enums:
foo:
description: foo
type: int
values:
v1: 0
v2: 1
""")
# Test array of enums
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo1:
description: foo
fields:
foo1: array<foo>
"""),
idl.errors.ERROR_ID_NO_ARRAY_ENUM,
True,
)
def test_command_positive(self):
# type: () -> None
"""Positive command tests."""
# Setup some common types
test_preamble = self.common_types + textwrap.dedent("""
structs:
reply:
description: foo
fields:
foo: string
""")
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
strict: true
fields:
foo1: string
reply_type: reply
""")
)
def test_command_negative(self):
# type: () -> None
"""Negative command tests."""
# Setup some common types
test_preamble = self.common_types
# Commands cannot be fields in other commands
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo1: string
bar:
description: foo
command_name: bar
namespace: ignored
api_version: ""
fields:
foo: foo
"""),
idl.errors.ERROR_ID_FIELD_NO_COMMAND,
)
# Commands cannot be fields in structs
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo1: string
structs:
bar:
description: foo
fields:
foo: foo
"""),
idl.errors.ERROR_ID_FIELD_NO_COMMAND,
)
# Commands cannot have a field as the same name
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo: string
"""),
idl.errors.ERROR_ID_COMMAND_DUPLICATES_FIELD,
)
# Reply type must be resolvable
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
reply_type: not_defined
"""),
idl.errors.ERROR_ID_UNKNOWN_TYPE,
)
# Reply type must be a struct
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
reply_type: string
"""),
idl.errors.ERROR_ID_INVALID_REPLY_TYPE,
)
def test_command_doc_sequence_positive(self):
# type: () -> None
"""Positive supports_doc_sequence tests."""
# Setup some common types
test_preamble = self.common_types + textwrap.dedent("""
structs:
foo_struct:
description: foo
strict: true
fields:
foo: object
""")
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo1:
type: array<object>
supports_doc_sequence: true
""")
)
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo1:
type: array<foo_struct>
supports_doc_sequence: true
""")
)
def test_command_doc_sequence_negative(self):
# type: () -> None
"""Negative supports_doc_sequence tests."""
# Setup some common types
test_preamble = self.common_types
test_preamble2 = test_preamble + textwrap.dedent("""
structs:
foo_struct:
description: foo
strict: true
fields:
foo: object
""")
# A struct
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
structs:
foo:
description: foo
fields:
foo:
type: array<object>
supports_doc_sequence: true
"""),
idl.errors.ERROR_ID_STRUCT_NO_DOC_SEQUENCE,
)
# A non-array type
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo:
type: object
supports_doc_sequence: true
"""),
idl.errors.ERROR_ID_NO_DOC_SEQUENCE_FOR_NON_ARRAY,
)
# An array of a scalar
self.assert_bind_fail(
test_preamble2
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo1:
type: array<string>
supports_doc_sequence: true
"""),
idl.errors.ERROR_ID_NO_DOC_SEQUENCE_FOR_NON_OBJECT,
)
# An array of 'any'
self.assert_bind_fail(
test_preamble2
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: ignored
api_version: ""
fields:
foo1:
type: array<string>
supports_doc_sequence: true
"""),
idl.errors.ERROR_ID_NO_DOC_SEQUENCE_FOR_NON_OBJECT,
)
def test_command_type_positive(self):
# type: () -> None
"""Positive command custom type test cases."""
# Setup some common types
test_preamble = self.common_types
# string
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
strict: true
namespace: type
api_version: ""
type: string
fields:
field1: string
""")
)
# array of string
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
strict: true
namespace: type
api_version: ""
type: array<string>
fields:
field1: string
""")
)
def test_command_type_negative(self):
# type: () -> None
"""Negative command type test cases."""
# Setup some common types
test_preamble = self.common_types
# supports_doc_sequence must be a bool
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
namespace: type
api_version: ""
type: int
fields:
field1: string
"""),
idl.errors.ERROR_ID_UNKNOWN_TYPE,
True,
)
def test_server_parameter_positive(self):
# type: () -> None
"""Positive server parameter test cases."""
# server parameter with storage.
# Also try valid set_at values.
for set_at in ["startup", "runtime", "[ startup, runtime ]", "cluster"]:
if set_at != "cluster":
self.assert_bind(
textwrap.dedent(
"""
server_parameters:
foo:
set_at: %s
description: bar
redact: false
cpp_varname: baz
"""
% (set_at)
)
)
else:
self.assert_bind(
textwrap.dedent(
"""
server_parameters:
foo:
set_at: %s
description: bar
redact: false
cpp_varname: baz
omit_in_ftdc: false
"""
% (set_at)
)
)
# server parameter with storage and optional fields.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
default: 42
on_update: buzz
validator:
gt: 0
gte: 1
lte: 999
lt: 1000
callback: qux
""")
)
# Cluster server parameter with storage.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: cluster
description: bar
redact: false
cpp_varname: baz
cpp_vartype: bazStorage
on_update: buzz
omit_in_ftdc: false
validator:
gt: 0
gte: 1
lte: 999
lt: 1000
callback: qux
""")
)
# Bound setting with arbitrary expression default and validators.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_varname: baz
default:
expr: 'kDefaultValue'
validator:
gte:
expr: 'kMinimumValue'
is_constexpr: true
lte:
expr: 'kMaximumValue'
is_constexpr: false
gt: 0
lt: 255
""")
)
# Specialized SCPs.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_class: baz
""")
)
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_class:
name: baz
""")
)
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_class:
name: baz
data: bling
override_set: true
override_ctor: false
override_validate: true
""")
)
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
cpp_class: baz
condition: { expr: "true" }
redact: true
test_only: true
deprecated_name: bling
""")
)
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: cluster
description: bar
cpp_class:
name: baz
override_validate: true
condition: { expr: "true" }
redact: true
omit_in_ftdc: true
test_only: true
deprecated_name: bling
""")
)
# Default without data.
self.assert_bind(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_class: baz
default: blong
""")
)
def test_server_parameter_negative(self):
# type: () -> None
"""Negative server parameter test cases."""
# Invalid set_at values.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: shutdown
description: bar
redact: false
cpp_varname: baz
"""),
idl.errors.ERROR_ID_BAD_SETAT_SPECIFIER,
)
# Mix of specialized with bound storage.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
redact: false
cpp_class: baz
cpp_varname: bling
"""),
idl.errors.ERROR_ID_SERVER_PARAMETER_INVALID_ATTR,
)
# Startup with omit_in_ftdc=true.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
cpp_varname: baz
redact: false
omit_in_ftdc: true
"""),
idl.errors.ERROR_ID_SERVER_PARAMETER_INVALID_ATTR,
)
# Startup with omit_in_ftdc=false.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: startup
description: bar
cpp_varname: baz
redact: false
omit_in_ftdc: false
"""),
idl.errors.ERROR_ID_SERVER_PARAMETER_INVALID_ATTR,
)
# Runtime with omit_in_ftdc=true.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: runtime
description: bar
cpp_varname: baz
redact: false
omit_in_ftdc: true
"""),
idl.errors.ERROR_ID_SERVER_PARAMETER_INVALID_ATTR,
)
# Runtime with omit_in_ftdc=false.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: runtime
description: bar
cpp_varname: baz
redact: false
omit_in_ftdc: false
"""),
idl.errors.ERROR_ID_SERVER_PARAMETER_INVALID_ATTR,
)
# Cluster with omit_in_ftdc unspecified.
self.assert_bind_fail(
textwrap.dedent("""
server_parameters:
foo:
set_at: cluster
description: bar
redact: false
cpp_varname: baz
cpp_vartype: bazStorage
on_update: buzz
validator:
gt: 0
gte: 1
lte: 999
lt: 1000
callback: qux
"""),
idl.errors.ERROR_ID_SERVER_PARAMETER_REQUIRED_ATTR,
)
def test_config_option_positive(self):
# type: () -> None
"""Positive config option test cases."""
# Every field.
self.assert_bind(
textwrap.dedent("""
configs:
foo:
short_name: bar
deprecated_name: baz
deprecated_short_name: qux
description: comment
section: here
arg_vartype: String
cpp_varname: gStringVal
conflicts: bling
requires: blong
hidden: false
default: one
implicit: two
duplicate_behavior: append
source: yaml
positional: 1-2
validator:
gt: 0
lt: 100
gte: 1
lte: 99
callback: doSomething
""")
)
# Required fields only.
self.assert_bind(
textwrap.dedent("""
configs:
foo:
description: comment
arg_vartype: Switch
source: yaml
""")
)
# List and enum variants.
self.assert_bind(
textwrap.dedent("""
configs:
foo:
deprecated_name: [ baz, baz ]
deprecated_short_name: [ bling, blong ]
description: comment
arg_vartype: StringVector
source: [ cli, ini, yaml ]
conflicts: [ a, b, c ]
requires: [ d, e, f ]
hidden: true
duplicate_behavior: overwrite
""")
)
# Positional variants.
for positional in ["1", "1-", "-2", "1-2"]:
self.assert_bind(
textwrap.dedent(
"""
configs:
foo:
short_name: foo
description: comment
arg_vartype: Bool
source: cli
positional: %s
"""
% (positional)
)
)
# With implicit short name.
self.assert_bind(
textwrap.dedent(
"""
configs:
foo:
description: comment
arg_vartype: Bool
source: cli
positional: %s
"""
% (positional)
)
)
# Expressions in default, implicit, and validators.
self.assert_bind(
textwrap.dedent("""
configs:
foo:
description: bar
arg_vartype: String
source: cli
default: { expr: kDefault, is_constexpr: true }
implicit: { expr: kImplicit, is_constexpr: false }
validator:
gte: { expr: kMinimum }
lte: { expr: kMaximum }
""")
)
def test_config_option_negative(self):
# type: () -> None
"""Negative config option test cases."""
# Invalid source.
self.assert_bind_fail(
textwrap.dedent("""
configs:
foo:
description: comment
arg_vartype: Long
source: json
"""),
idl.errors.ERROR_ID_BAD_SOURCE_SPECIFIER,
)
self.assert_bind_fail(
textwrap.dedent("""
configs:
foo:
description: comment
arg_vartype: StringMap
source: [ cli, yaml ]
duplicate_behavior: guess
"""),
idl.errors.ERROR_ID_BAD_DUPLICATE_BEHAVIOR_SPECIFIER,
)
for positional in ["x", "1-2-3", "-2-", "1--3"]:
self.assert_bind_fail(
textwrap.dedent(
"""
configs:
foo:
description: comment
arg_vartype: String
source: cli
positional: %s
"""
% (positional)
),
idl.errors.ERROR_ID_BAD_NUMERIC_RANGE,
)
self.assert_bind_fail(
textwrap.dedent("""
configs:
foo:
description: comment
short_name: "bar.baz"
arg_vartype: Bool
source: cli
"""),
idl.errors.ERROR_ID_INVALID_SHORT_NAME,
)
self.assert_bind_fail(
textwrap.dedent("""
configs:
foo:
description: comment
short_name: bar
deprecated_short_name: "baz.qux"
arg_vartype: Long
source: cli
"""),
idl.errors.ERROR_ID_INVALID_SHORT_NAME,
)
# dottedName is not valid as a shortName.
self.assert_bind_fail(
textwrap.dedent("""
configs:
"foo.bar":
description: comment
arg_vartype: String
source: cli
positional: 1
"""),
idl.errors.ERROR_ID_MISSING_SHORTNAME_FOR_POSITIONAL,
)
# Invalid shortname using boost::po format directly.
self.assert_bind_fail(
textwrap.dedent("""
configs:
foo:
short_name: "foo,f"
arg_vartype: Switch
description: comment
source: cli
"""),
idl.errors.ERROR_ID_INVALID_SHORT_NAME,
)
# Invalid single names, must be single alpha char.
for name in ["foo", "1", ".", ""]:
self.assert_bind_fail(
textwrap.dedent(
"""
configs:
foo:
single_name: "%s"
arg_vartype: Switch
description: comment
source: cli
"""
% (name)
),
idl.errors.ERROR_ID_INVALID_SINGLE_NAME,
)
# Single names require a valid short name.
self.assert_bind_fail(
textwrap.dedent("""
configs:
"foo.bar":
single_name: f
arg_vartype: Switch
description: comment
source: cli
"""),
idl.errors.ERROR_ID_MISSING_SHORT_NAME_WITH_SINGLE_NAME,
)
def test_feature_flag(self):
# type: () -> None
"""Test feature flag checks around version."""
# feature flag can default to false without a version (fcv_gated can be true or false)
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: false
fcv_gated: false
""")
)
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: false
fcv_gated: true
""")
)
# if fcv_gated: true, feature flag can default to true with a version
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: true
version: 123
fcv_gated: true
""")
)
# if fcv_gated: false, we do not need a version
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: true
fcv_gated: false
""")
)
# IFR flag does not need a default or version
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: released
fcv_gated: false
""")
)
# IFR flag can specify a compatible version
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: released
default: true
fcv_gated: false
""")
)
# explicitly mark non-IFR flag
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: not_for_incremental_rollout
default: true
version: 123
fcv_gated: true
""")
)
# if fcv_gated: true and default: true, a version is required
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: true
fcv_gated: true
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_DEFAULT_TRUE_MISSING_VERSION,
)
# false is not allowed with a version and fcv_gated: true
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: false
version: 123
fcv_gated: true
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_DEFAULT_FALSE_HAS_VERSION,
)
# false is not allowed with a version and fcv_gated: false
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: false
version: 123
fcv_gated: false
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_DEFAULT_FALSE_HAS_VERSION,
)
# if fcv_gated is false, a version is not allowed
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: true
version: 123
fcv_gated: false
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_SHOULD_BE_FCV_GATED_FALSE_HAS_UNSUPPORTED_OPTION,
)
# if fcv_gated is false, enable_on_transitional_fcv_UNSAFE is not allowed
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: >
Make toast
(Enable on transitional FCV): Lorem ipsum dolor sit amet
cpp_varname: gToaster
default: true
fcv_gated: false
enable_on_transitional_fcv_UNSAFE: true
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_SHOULD_BE_FCV_GATED_FALSE_HAS_UNSUPPORTED_OPTION,
)
# if fcv_gated: true, enable_on_transitional_fcv_UNSAFE is allowed
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: >
Make toast
(Enable on transitional FCV): Lorem ipsum dolor sit amet
cpp_varname: gToaster
default: true
version: 123
fcv_gated: true
enable_on_transitional_fcv_UNSAFE: true
""")
)
# if enable_on_transitional_fcv_UNSAFE: true, the description must contain a
# "(Enable on transitional FCV): ..." text explaining why the usage is safe
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: true
version: 123
fcv_gated: true
enable_on_transitional_fcv_UNSAFE: true
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_ENABLED_ON_TRANSITIONAL_FCV_MISSING_SAFETY_EXPLANATION,
)
# if fcv_gated is false, fcv_context_unaware is not allowed
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: true
fcv_gated: false
fcv_context_unaware: true
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_SHOULD_BE_FCV_GATED_FALSE_HAS_UNSUPPORTED_OPTION,
)
# if fcv_gated: true, fcv_context_unaware is allowed
self.assert_bind(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
default: false
fcv_gated: true
fcv_context_unaware: true
""")
)
# incremental_rollout_phase must have a valid value
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: waning_gibbous
fcv_gated: false
"""),
idl.errors.ERROR_ID_INCREMENTAL_ROLLOUT_PHASE_INVALID_VALUE,
)
# if set for incremental feature rollout (IFR), version not allowed
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: released
default: true
fcv_gated: true
"""),
idl.errors.ERROR_ID_ILLEGALLY_FCV_GATED_FEATURE_FLAG,
)
# incremental_rollout_phase must not specify incompatible default
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: in_development
default: true
fcv_gated: false
"""),
idl.errors.ERROR_ID_INCREMENTAL_FEATURE_FLAG_DEFAULT_VALUE,
)
# default required for non-IFR feature flag
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
fcv_gated: true
"""),
idl.errors.ERROR_ID_FEATURE_FLAG_WITHOUT_DEFAULT_VALUE,
)
# incremental_rollout_phase must have a valid value
self.assert_bind_fail(
textwrap.dedent("""
feature_flags:
featureFlagToaster:
description: "Make toast"
cpp_varname: gToaster
incremental_rollout_phase: rollout
version: 123
fcv_gated: false
"""),
idl.errors.ERROR_ID_IFR_FLAG_WITH_VERSION,
)
def test_access_check(self):
# type: () -> None
"""Test access check."""
test_preamble = self.common_types + textwrap.dedent("""
enums:
AccessCheck:
description: "test"
type: string
values:
kIsAuthenticated : "is_authenticated"
kIsCoAuthorized : "is_coauthorized"
ActionType:
description: "test"
type: string
values:
addShard : "addShard"
serverStatus : "serverStatus"
MatchType:
description: "test"
type: string
values:
matchClusterResource : "cluster"
structs:
reply:
description: foo
fields:
foo: string
""")
# Test none
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
none: true
fields:
foo: string
reply_type: reply
""")
)
# Test simple with access check
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
check: is_authenticated
fields:
foo: string
reply_type: reply
""")
)
# Test simple with privilege
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
privilege:
resource_pattern: cluster
action_type: addShard
fields:
foo: string
reply_type: reply
""")
)
self.assert_parse(
textwrap.dedent("""
commands:
foo:
description: foo
command_name: foo
api_version: 1
namespace: ignored
access_check:
complex:
- privilege:
resource_pattern: cluster
action_type: addShard
- privilege:
resource_pattern: cluster
action_type: serverStatus
- check: is_authenticated
fields:
foo: bar
reply_type: foo_reply_struct
""")
)
def test_access_check_negative(self):
# type: () -> None
"""Negative access check tests."""
test_preamble = self.common_types + textwrap.dedent("""
enums:
AccessCheck:
description: "test"
type: string
values:
kIsAuthenticated : "is_authenticated"
kIsCoAuthorized : "is_coauthorized"
ActionType:
description: "test"
type: string
values:
addShard : "addShard"
serverStatus : "serverStatus"
MatchType:
description: "test"
type: string
values:
matchClusterResource : "cluster"
structs:
reply:
description: foo
fields:
foo: string
""")
# Test simple with bad access check
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
check: unknown
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_UNKOWN_ENUM_VALUE,
)
# Test simple with bad access check with privilege
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
privilege:
resource_pattern: foo
action_type: addShard
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_UNKOWN_ENUM_VALUE,
)
# Test simple with bad access check with privilege
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
privilege:
resource_pattern: cluster
action_type: foo
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_UNKOWN_ENUM_VALUE,
)
# Test simple with access check and privileges
self.assert_bind(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
privilege:
resource_pattern: cluster
action_type: [addShard, serverStatus]
fields:
foo: string
reply_type: reply
""")
)
# Test simple with privilege with duplicate action_type
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: ""
namespace: ignored
access_check:
simple:
privilege:
resource_pattern: cluster
action_type: [addShard, addShard]
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_DUPLICATE_ACTION_TYPE,
)
# complex with duplicate check
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: 1
namespace: ignored
access_check:
complex:
- check: is_authenticated
- check: is_authenticated
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_DUPLICATE_ACCESS_CHECK,
)
# complex with duplicate priv
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: 1
namespace: ignored
access_check:
complex:
- privilege:
resource_pattern: cluster
action_type: addShard
- privilege:
resource_pattern: cluster
action_type: [addShard, serverStatus]
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_DUPLICATE_ACCESS_CHECK,
)
# api_version != "" but not access_check
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
test1:
description: foo
command_name: foo
api_version: 1
namespace: ignored
fields:
foo: string
reply_type: reply
"""),
idl.errors.ERROR_ID_MISSING_ACCESS_CHECK,
)
def test_query_shape_component_validation(self):
self.assert_bind(
self.common_types
+ textwrap.dedent("""
structs:
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field1:
query_shape: literal
type: string
field2:
type: bool
query_shape: parameter
""")
)
self.assert_bind_fail(
self.common_types
+ textwrap.dedent("""
structs:
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field1:
type: string
field2:
type: bool
query_shape: parameter
"""),
idl.errors.ERROR_ID_FIELD_MUST_DECLARE_SHAPE_LITERAL,
)
self.assert_bind_fail(
self.common_types
+ textwrap.dedent("""
structs:
struct1:
strict: true
description: ""
fields:
field1:
type: string
field2:
type: bool
query_shape: parameter
"""),
idl.errors.ERROR_ID_CANNOT_DECLARE_SHAPE_LITERAL,
)
# Validating query_shape_anonymize relies on std::string
basic_types = textwrap.dedent("""
types:
string:
bson_serialization_type: string
description: "A BSON UTF-8 string"
cpp_type: "std::string"
deserializer: "mongo::BSONElement::str"
is_view: false
bool:
bson_serialization_type: bool
description: "A BSON bool"
cpp_type: "bool"
deserializer: "mongo::BSONElement::boolean"
is_view: false
serialization_context:
bson_serialization_type: any
description: foo
cpp_type: foo
internal_only: true
is_view: false
""")
self.assert_bind(
basic_types
+ textwrap.dedent("""
structs:
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field1:
query_shape: anonymize
type: string
field2:
query_shape: parameter
type: bool
""")
)
self.assert_bind(
basic_types
+ textwrap.dedent("""
structs:
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field1:
query_shape: anonymize
type: array<string>
field2:
query_shape: parameter
type: bool
""")
)
self.assert_bind_fail(
basic_types
+ textwrap.dedent("""
structs:
struct1:
strict: true
description: ""
fields:
field1:
query_shape: blah
type: string
"""),
idl.errors.ERROR_ID_QUERY_SHAPE_INVALID_VALUE,
)
self.assert_bind_fail(
basic_types
+ textwrap.dedent("""
structs:
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field1:
query_shape: anonymize
type: bool
field2:
query_shape: parameter
type: bool
"""),
idl.errors.ERROR_ID_INVALID_TYPE_FOR_SHAPIFY,
)
self.assert_bind_fail(
basic_types
+ textwrap.dedent("""
structs:
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field1:
query_shape: anonymize
type: array<bool>
field2:
query_shape: parameter
type: bool
"""),
idl.errors.ERROR_ID_INVALID_TYPE_FOR_SHAPIFY,
)
self.assert_bind_fail(
basic_types
+ textwrap.dedent("""
structs:
StructZero:
strict: true
description: ""
fields:
field1:
query_shape: literal
type: string
"""),
idl.errors.ERROR_ID_CANNOT_DECLARE_SHAPE_LITERAL,
)
self.assert_bind_fail(
basic_types
+ textwrap.dedent("""
structs:
StructZero:
strict: true
description: ""
fields:
field1:
type: string
struct1:
query_shape_component: true
strict: true
description: ""
fields:
field2:
type: StructZero
description: ""
query_shape: literal
"""),
idl.errors.ERROR_ID_CANNOT_DECLARE_SHAPE_LITERAL,
)
def test_struct_unsafe_dangerous_disable_extra_field_duplicate_checks_negative(self):
# type: () -> None
"""Negative struct tests for unsafe_dangerous_disable_extra_field_duplicate_checks."""
# Setup some common types
test_preamble = self.common_types + textwrap.dedent("""
structs:
danger:
description: foo
strict: false
unsafe_dangerous_disable_extra_field_duplicate_checks: true
fields:
foo: string
""")
# Test strict and unsafe_dangerous_disable_extra_field_duplicate_checks are not allowed
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
danger1:
description: foo
strict: true
unsafe_dangerous_disable_extra_field_duplicate_checks: true
fields:
foo: string
"""),
),
idl.errors.ERROR_ID_STRICT_AND_DISABLE_CHECK_NOT_ALLOWED,
)
# Test inheritance is prohibited through structs
self.assert_bind_fail(
test_preamble
+ indent_text(
1,
textwrap.dedent("""
danger2:
description: foo
strict: true
fields:
foo: string
d1: danger
"""),
),
idl.errors.ERROR_ID_INHERITANCE_AND_DISABLE_CHECK_NOT_ALLOWED,
)
# Test inheritance is prohibited through commands
self.assert_bind_fail(
test_preamble
+ textwrap.dedent("""
commands:
dangerc:
description: foo
namespace: ignored
command_name: dangerc
strict: false
api_version: ""
fields:
foo: string
d1: danger
"""),
idl.errors.ERROR_ID_INHERITANCE_AND_DISABLE_CHECK_NOT_ALLOWED,
)
if __name__ == "__main__":
unittest.main()