Skip to content

Commit c2a83e5

Browse files
authored
fix: rendering mock values for recursive messages no longer crashes (#587)
Protobuf allows recursive message types, i.e. messages whose fields are of the same type as the message itself. message Foo { Foo foo = 1; // Degenerate case } A real world example is bigquery.v2.data:RowFilter These recursive types cause a problem when trying to render mock values for unit tests because there's no inherent limit on when to stop rendering nested values. The solution in this commit is an artifical cap on the depth of recursion in rendering mock values.
1 parent 299393b commit c2a83e5

File tree

1 file changed

+25
-2
lines changed

1 file changed

+25
-2
lines changed

gapic/schema/wrappers.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ class Field:
5656
)
5757
oneof: Optional[str] = None
5858

59+
# Arbitrary cap set via heuristic rule of thumb.
60+
MAX_MOCK_DEPTH: int = 20
61+
5962
def __getattr__(self, name):
6063
return getattr(self.field_pb, name)
6164

@@ -85,6 +88,17 @@ def map(self) -> bool:
8588

8689
@utils.cached_property
8790
def mock_value(self) -> str:
91+
depth = 0
92+
stack = [self]
93+
answer = "{}"
94+
while stack:
95+
expr = stack.pop()
96+
answer = answer.format(expr.inner_mock(stack, depth))
97+
depth += 1
98+
99+
return answer
100+
101+
def inner_mock(self, stack, depth):
88102
"""Return a repr of a valid, usually truthy mock value."""
89103
# For primitives, send a truthy value computed from the
90104
# field name.
@@ -113,9 +127,18 @@ def mock_value(self) -> str:
113127
answer = f'{self.type.ident}.{mock_value.name}'
114128

115129
# If this is another message, set one value on the message.
116-
if isinstance(self.type, MessageType) and len(self.type.fields):
130+
if (
131+
not self.map # Maps are handled separately
132+
and isinstance(self.type, MessageType)
133+
and len(self.type.fields)
134+
# Nested message types need to terminate eventually
135+
and depth < self.MAX_MOCK_DEPTH
136+
):
117137
sub = next(iter(self.type.fields.values()))
118-
answer = f'{self.type.ident}({sub.name}={sub.mock_value})'
138+
stack.append(sub)
139+
# Don't do the recursive rendering here, just set up
140+
# where the nested value should go with the double {}.
141+
answer = f'{self.type.ident}({sub.name}={{}})'
119142

120143
if self.map:
121144
# Maps are a special case beacuse they're represented internally as

0 commit comments

Comments
 (0)