1# Copyright 2022 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Helper functions. 15 16Facilitates the implementation of a new profile proxy or a PTS MMI. 17""" 18 19import functools 20import textwrap 21import unittest 22import re 23 24DOCSTRING_WIDTH = 80 - 8 # 80 cols - 8 indentation spaces 25 26 27def assert_description(f): 28 """Decorator which verifies the description of a PTS MMI implementation. 29 30 Asserts that the docstring of a function implementing a PTS MMI is the same 31 as the corresponding official MMI description. 32 33 Args: 34 f: function implementing a PTS MMI. 35 36 Raises: 37 AssertionError: the docstring of the function does not match the MMI 38 description. 39 """ 40 41 @functools.wraps(f) 42 def wrapper(*args, **kwargs): 43 description = textwrap.fill(kwargs['description'], DOCSTRING_WIDTH, replace_whitespace=False) 44 docstring = textwrap.dedent(f.__doc__ or '') 45 46 if docstring.strip() != description.strip(): 47 print(f'Expected description of {f.__name__}:') 48 print(description) 49 50 # Generate AssertionError. 51 test = unittest.TestCase() 52 test.maxDiff = None 53 test.assertMultiLineEqual(docstring.strip(), description.strip(), 54 f'description does not match with function docstring of' 55 f'{f.__name__}') 56 57 return f(*args, **kwargs) 58 59 return wrapper 60 61 62def match_description(f): 63 """Extracts parameters from PTS MMI descriptions. 64 65 Similar to assert_description, but treats the description as an (indented) 66 regex that can be used to extract named capture groups from the PTS command. 67 68 Args: 69 f: function implementing a PTS MMI. 70 71 Raises: 72 AssertionError: the docstring of the function does not match the MMI 73 description. 74 """ 75 76 def normalize(desc): 77 return desc.replace("\n", " ").replace("\t", " ").strip() 78 79 docstring = normalize(textwrap.dedent(f.__doc__)) 80 regex = re.compile(docstring) 81 82 @functools.wraps(f) 83 def wrapper(*args, **kwargs): 84 description = normalize(kwargs['description']) 85 match = regex.fullmatch(description) 86 87 assert match is not None, f'description does not match with function docstring of {f.__name__}:\n{repr(description)}\n!=\n{repr(docstring)}' 88 89 return f(*args, **kwargs, **match.groupdict()) 90 91 return wrapper 92 93 94def format_function(mmi_name, mmi_description): 95 """Returns the base format of a function implementing a PTS MMI.""" 96 wrapped_description = textwrap.fill(mmi_description, DOCSTRING_WIDTH, replace_whitespace=False) 97 return (f'@assert_description\n' 98 f'def {mmi_name}(self, **kwargs):\n' 99 f' """\n' 100 f'{textwrap.indent(wrapped_description, " ")}\n' 101 f' """\n' 102 f'\n' 103 f' return "OK"\n') 104 105 106def format_proxy(profile, mmi_name, mmi_description): 107 """Returns the base format of a profile proxy including a given MMI.""" 108 wrapped_function = textwrap.indent(format_function(mmi_name, mmi_description), ' ') 109 return (f'from mmi2grpc._helpers import assert_description\n' 110 f'from mmi2grpc._proxy import ProfileProxy\n' 111 f'\n' 112 f'from pandora_experimental.{profile.lower()}_grpc import {profile}\n' 113 f'\n' 114 f'\n' 115 f'class {profile}Proxy(ProfileProxy):\n' 116 f'\n' 117 f' def __init__(self, channel):\n' 118 f' super().__init__(channel)\n' 119 f' self.{profile.lower()} = {profile}(channel)\n' 120 f'\n' 121 f'{wrapped_function}') 122