Skip to content

Commit

Permalink
fix: rendering mock values for recursive messages no longer crashes (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
software-dov authored Sep 3, 2020
1 parent 299393b commit c2a83e5
Showing 1 changed file with 25 additions and 2 deletions.
27 changes: 25 additions & 2 deletions gapic/schema/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ class Field:
)
oneof: Optional[str] = None

# Arbitrary cap set via heuristic rule of thumb.
MAX_MOCK_DEPTH: int = 20

def __getattr__(self, name):
return getattr(self.field_pb, name)

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

@utils.cached_property
def mock_value(self) -> str:
depth = 0
stack = [self]
answer = "{}"
while stack:
expr = stack.pop()
answer = answer.format(expr.inner_mock(stack, depth))
depth += 1

return answer

def inner_mock(self, stack, depth):
"""Return a repr of a valid, usually truthy mock value."""
# For primitives, send a truthy value computed from the
# field name.
Expand Down Expand Up @@ -113,9 +127,18 @@ def mock_value(self) -> str:
answer = f'{self.type.ident}.{mock_value.name}'

# If this is another message, set one value on the message.
if isinstance(self.type, MessageType) and len(self.type.fields):
if (
not self.map # Maps are handled separately
and isinstance(self.type, MessageType)
and len(self.type.fields)
# Nested message types need to terminate eventually
and depth < self.MAX_MOCK_DEPTH
):
sub = next(iter(self.type.fields.values()))
answer = f'{self.type.ident}({sub.name}={sub.mock_value})'
stack.append(sub)
# Don't do the recursive rendering here, just set up
# where the nested value should go with the double {}.
answer = f'{self.type.ident}({sub.name}={{}})'

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

0 comments on commit c2a83e5

Please sign in to comment.