OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 Google Inc. All Rights Reserved. |
| 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 # http://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 |
| 15 """Test utilities for API modules. |
| 16 |
| 17 Classes: |
| 18 ModuleInterfaceTest: Test framework for developing public modules. |
| 19 """ |
| 20 |
| 21 # pylint: disable=g-bad-name |
| 22 |
| 23 import __future__ |
| 24 import json |
| 25 import os |
| 26 import types |
| 27 |
| 28 |
| 29 def AssertDictEqual(expected, actual, testcase): |
| 30 """Utility method to dump diffs if the dictionaries aren't equal. |
| 31 |
| 32 Args: |
| 33 expected: dict, the expected results. |
| 34 actual: dict, the actual results. |
| 35 testcase: unittest.TestCase, the test case this assertion is used within. |
| 36 """ |
| 37 if expected != actual: |
| 38 testcase.assertMultiLineEqual( |
| 39 json.dumps(expected, indent=2, sort_keys=True), |
| 40 json.dumps(actual, indent=2, sort_keys=True)) |
| 41 |
| 42 |
| 43 class ModuleInterfaceTest(object): |
| 44 r"""Test to ensure module interface is carefully constructed. |
| 45 |
| 46 A module interface is the set of public objects listed in the module __all__ |
| 47 attribute. Modules that will be used by the public should have this interface |
| 48 carefully declared. At all times, the __all__ attribute should have objects |
| 49 intended to be used by the public and other objects in the module should be |
| 50 considered unused. |
| 51 |
| 52 Protected attributes (those beginning with '_') and other imported modules |
| 53 should not be part of this set of variables. An exception is for variables |
| 54 that begin and end with '__' which are implicitly part of the interface |
| 55 (eg. __name__, __file__, __all__ itself, etc.). |
| 56 |
| 57 Modules that are imported in to the tested modules are an exception and may |
| 58 be left out of the __all__ definition. The test is done by checking the value |
| 59 of what would otherwise be a public name and not allowing it to be exported |
| 60 if it is an instance of a module. Modules that are explicitly exported are |
| 61 for the time being not permitted. |
| 62 |
| 63 To use this test class a module should define a new class that inherits first |
| 64 from ModuleInterfaceTest and then from unittest.TestCase. No other tests |
| 65 should be added to this test case, making the order of inheritance less |
| 66 important, but if setUp for some reason is overidden, it is important that |
| 67 ModuleInterfaceTest is first in the list so that its setUp method is |
| 68 invoked. |
| 69 |
| 70 Multiple inheretance is required so that ModuleInterfaceTest is not itself |
| 71 a test, and is not itself executed as one. |
| 72 |
| 73 The test class is expected to have the following class attributes defined: |
| 74 |
| 75 MODULE: A reference to the module that is being validated for interface |
| 76 correctness. |
| 77 |
| 78 Example: |
| 79 Module definition (hello.py): |
| 80 |
| 81 import sys |
| 82 |
| 83 __all__ = ['hello'] |
| 84 |
| 85 def _get_outputter(): |
| 86 return sys.stdout |
| 87 |
| 88 def hello(): |
| 89 _get_outputter().write('Hello\n') |
| 90 |
| 91 Test definition: |
| 92 |
| 93 import test_util |
| 94 import unittest |
| 95 |
| 96 import hello |
| 97 |
| 98 class ModuleInterfaceTest(module_testutil.ModuleInterfaceTest, |
| 99 unittest.TestCase): |
| 100 |
| 101 MODULE = hello |
| 102 |
| 103 |
| 104 class HelloTest(unittest.TestCase): |
| 105 ... Test 'hello' module ... |
| 106 |
| 107 |
| 108 def main(unused_argv): |
| 109 unittest.main() |
| 110 |
| 111 |
| 112 if __name__ == '__main__': |
| 113 app.run() |
| 114 """ |
| 115 |
| 116 def setUp(self): |
| 117 """Set up makes sure that MODULE and IMPORTED_MODULES is defined. |
| 118 |
| 119 This is a basic configuration test for the test itself so does not |
| 120 get it's own test case. |
| 121 """ |
| 122 if not hasattr(self, 'MODULE'): |
| 123 self.fail( |
| 124 "You must define 'MODULE' on ModuleInterfaceTest sub-class %s." % |
| 125 type(self).__name__) |
| 126 |
| 127 def testAllExist(self): |
| 128 """Test that all attributes defined in __all__ exist.""" |
| 129 missing_attributes = [] |
| 130 for attribute in self.MODULE.__all__: |
| 131 if not hasattr(self.MODULE, attribute): |
| 132 missing_attributes.append(attribute) |
| 133 if missing_attributes: |
| 134 self.fail('%s of __all__ are not defined in module.' % |
| 135 missing_attributes) |
| 136 |
| 137 def testAllExported(self): |
| 138 """Test that all public attributes not imported are in __all__.""" |
| 139 missing_attributes = [] |
| 140 for attribute in dir(self.MODULE): |
| 141 if not attribute.startswith('_'): |
| 142 if attribute not in self.MODULE.__all__: |
| 143 attribute_value = getattr(self.MODULE, attribute) |
| 144 if isinstance(attribute_value, types.ModuleType): |
| 145 continue |
| 146 # pylint: disable=protected-access |
| 147 if isinstance(attribute_value, __future__._Feature): |
| 148 continue |
| 149 missing_attributes.append(attribute) |
| 150 if missing_attributes: |
| 151 self.fail('%s are not modules and not defined in __all__.' % |
| 152 missing_attributes) |
| 153 |
| 154 def testNoExportedProtectedVariables(self): |
| 155 """Test that there are no protected variables listed in __all__.""" |
| 156 protected_variables = [] |
| 157 for attribute in self.MODULE.__all__: |
| 158 if attribute.startswith('_'): |
| 159 protected_variables.append(attribute) |
| 160 if protected_variables: |
| 161 self.fail('%s are protected variables and may not be exported.' % |
| 162 protected_variables) |
| 163 |
| 164 def testNoExportedModules(self): |
| 165 """Test that no modules exist in __all__.""" |
| 166 exported_modules = [] |
| 167 for attribute in self.MODULE.__all__: |
| 168 try: |
| 169 value = getattr(self.MODULE, attribute) |
| 170 except AttributeError: |
| 171 # This is a different error case tested for in testAllExist. |
| 172 pass |
| 173 else: |
| 174 if isinstance(value, types.ModuleType): |
| 175 exported_modules.append(attribute) |
| 176 if exported_modules: |
| 177 self.fail('%s are modules and may not be exported.' % exported_modules) |
| 178 |
| 179 |
| 180 class DevServerTest(object): |
| 181 |
| 182 @staticmethod |
| 183 def setUpDevServerEnv(server_software_key='SERVER_SOFTWARE', |
| 184 server_software_value='Development/2.0.0'): |
| 185 original_env_value = os.environ.get(server_software_key) |
| 186 os.environ[server_software_key] = server_software_value |
| 187 return server_software_key, original_env_value |
| 188 |
| 189 @staticmethod |
| 190 def restoreEnv(server_software_key, server_software_value): |
| 191 if server_software_value is None: |
| 192 os.environ.pop(server_software_key, None) |
| 193 else: |
| 194 os.environ[server_software_key] = server_software_value |
| 195 |
OLD | NEW |