Coverage for nova/test.py: 89%

405 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-17 15:08 +0000

1# Copyright 2010 United States Government as represented by the 

2# Administrator of the National Aeronautics and Space Administration. 

3# All Rights Reserved. 

4# 

5# Licensed under the Apache License, Version 2.0 (the "License"); you may 

6# not use this file except in compliance with the License. You may obtain 

7# a copy of the License at 

8# 

9# http://www.apache.org/licenses/LICENSE-2.0 

10# 

11# Unless required by applicable law or agreed to in writing, software 

12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

14# License for the specific language governing permissions and limitations 

15# under the License. 

16 

17"""Base classes for our unit tests. 

18 

19Allows overriding of flags for use of fakes, and some black magic for 

20inline callbacks. 

21 

22""" 

23 

24import nova.monkey_patch # noqa 

25 

26import abc 

27import builtins 

28import collections 

29import contextlib 

30import copy 

31import datetime 

32import inspect 

33import itertools 

34import os 

35import os.path 

36import pprint 

37import sys 

38from unittest import mock 

39 

40import fixtures 

41from oslo_cache import core as cache 

42from oslo_concurrency import lockutils 

43from oslo_config import cfg 

44from oslo_config import fixture as config_fixture 

45from oslo_log.fixture import logging_error as log_fixture 

46from oslo_log import log as logging 

47from oslo_serialization import jsonutils 

48from oslo_utils.fixture import uuidsentinel as uuids 

49from oslo_utils import timeutils 

50from oslo_versionedobjects import fixture as ovo_fixture 

51from oslotest import base 

52from oslotest import mock_fixture 

53from sqlalchemy.dialects import sqlite 

54import testtools 

55 

56from nova.api.openstack import wsgi_app 

57from nova.compute import rpcapi as compute_rpcapi 

58from nova import context 

59import nova.crypto 

60from nova.db.main import api as db_api 

61from nova import exception 

62from nova import objects 

63from nova.objects import base as objects_base 

64from nova import quota 

65from nova.scheduler.client import report 

66from nova.scheduler import utils as scheduler_utils 

67from nova.tests import fixtures as nova_fixtures 

68from nova.tests.unit import matchers 

69from nova import utils 

70from nova.virt import images 

71 

72CONF = cfg.CONF 

73 

74logging.register_options(CONF) 

75CONF.set_override('use_stderr', False) 

76logging.setup(CONF, 'nova') 

77cache.configure(CONF) 

78LOG = logging.getLogger(__name__) 

79 

80_TRUE_VALUES = ('True', 'true', '1', 'yes') 

81CELL1_NAME = 'cell1' 

82 

83 

84# For compatibility with the large number of tests which use test.nested 

85nested = utils.nested_contexts 

86 

87 

88class TestingException(Exception): 

89 pass 

90 

91 

92# NOTE(claudiub): this needs to be called before any mock.patch calls are 

93# being done, and especially before any other test classes load. This fixes 

94# the mock.patch autospec issue: 

95# https://github.com/testing-cabal/mock/issues/396 

96mock_fixture.patch_mock_module() 

97 

98 

99def _poison_unfair_compute_resource_semaphore_locking(): 

100 """Ensure that every locking on COMPUTE_RESOURCE_SEMAPHORE is called with 

101 fair=True. 

102 """ 

103 orig_synchronized = utils.synchronized 

104 

105 def poisoned_synchronized(*args, **kwargs): 

106 # Only check fairness if the decorator is used with 

107 # COMPUTE_RESOURCE_SEMAPHORE. But the name of the semaphore can be 

108 # passed as args or as kwargs. 

109 # Note that we cannot import COMPUTE_RESOURCE_SEMAPHORE as that would 

110 # apply the decorators we want to poison here. 

111 if len(args) >= 1: 111 ↛ 114line 111 didn't jump to line 114 because the condition on line 111 was always true

112 name = args[0] 

113 else: 

114 name = kwargs.get("name") 

115 if name == "compute_resources" and not kwargs.get("fair", False): 

116 raise AssertionError( 

117 'Locking on COMPUTE_RESOURCE_SEMAPHORE should always be fair. ' 

118 'See bug 1864122.') 

119 # go and act like the original decorator 

120 return orig_synchronized(*args, **kwargs) 

121 

122 # replace the synchronized decorator factory with our own that checks the 

123 # params passed in 

124 utils.synchronized = poisoned_synchronized 

125 

126 

127# NOTE(gibi): This poisoning needs to be done in import time as decorators are 

128# applied in import time on the ResourceTracker 

129_poison_unfair_compute_resource_semaphore_locking() 

130 

131 

132class NovaExceptionReraiseFormatError(object): 

133 real_log_exception = exception.NovaException._log_exception 

134 

135 @classmethod 

136 def patch(cls): 

137 exception.NovaException._log_exception = cls._wrap_log_exception 

138 

139 @staticmethod 

140 def _wrap_log_exception(self): 

141 exc_info = sys.exc_info() 

142 NovaExceptionReraiseFormatError.real_log_exception(self) 

143 raise exc_info[1] 

144 

145 

146# NOTE(melwitt) This needs to be done at import time in order to also catch 

147# NovaException format errors that are in mock decorators. In these cases, the 

148# errors will be raised during test listing, before tests actually run. 

149NovaExceptionReraiseFormatError.patch() 

150 

151 

152class TestCase(base.BaseTestCase): 

153 """Test case base class for all unit tests. 

154 

155 Due to the slowness of DB access, please consider deriving from 

156 `NoDBTestCase` first. 

157 """ 

158 # USES_DB is set to False for tests that inherit from NoDBTestCase. 

159 USES_DB = True 

160 # USES_DB_SELF is set to True in tests that specifically want to use the 

161 # database but need to configure it themselves, for example to setup the 

162 # API DB but not the cell DB. In those cases the test will override 

163 # USES_DB_SELF = True but inherit from the NoDBTestCase class so it does 

164 # not get the default fixture setup when using a database (which is the 

165 # API and cell DBs, and adding the default flavors). 

166 USES_DB_SELF = False 

167 REQUIRES_LOCKING = False 

168 

169 # Setting to True makes the test use the RPCFixture. 

170 STUB_RPC = True 

171 

172 # The number of non-cell0 cells to create. This is only used in the 

173 # base class when USES_DB is True. 

174 NUMBER_OF_CELLS = 1 

175 

176 # The stable compute id stuff is intentionally singleton-ish, which makes 

177 # it a nightmare for testing multiple host/node combinations in tests like 

178 # we do. So, mock it out by default, unless the test is specifically 

179 # designed to handle it. 

180 STUB_COMPUTE_ID = True 

181 

182 def setUp(self): 

183 """Run before each test method to initialize test environment.""" 

184 # Ensure BaseTestCase's ConfigureLogging fixture is disabled since 

185 # we're using our own (StandardLogging). 

186 with fixtures.EnvironmentVariable('OS_LOG_CAPTURE', '0'): 

187 super(TestCase, self).setUp() 

188 

189 self.useFixture( 

190 nova_fixtures.PropagateTestCaseIdToChildEventlets(self.id())) 

191 

192 # How many of which service we've started. {$service-name: $count} 

193 self._service_fixture_count = collections.defaultdict(int) 

194 

195 self.useFixture(nova_fixtures.OpenStackSDKFixture()) 

196 self.useFixture(nova_fixtures.IsolatedGreenPoolFixture(self.id())) 

197 

198 self.useFixture(log_fixture.get_logging_handle_error_fixture()) 

199 

200 self.stdlog = self.useFixture(nova_fixtures.StandardLogging()) 

201 

202 # NOTE(sdague): because of the way we were using the lock 

203 # wrapper we ended up with a lot of tests that started 

204 # relying on global external locking being set up for them. We 

205 # consider all of these to be *bugs*. Tests should not require 

206 # global external locking, or if they do, they should 

207 # explicitly set it up themselves. 

208 # 

209 # The following REQUIRES_LOCKING class parameter is provided 

210 # as a bridge to get us there. No new tests should be added 

211 # that require it, and existing classes and tests should be 

212 # fixed to not need it. 

213 if self.REQUIRES_LOCKING: 

214 lock_path = self.useFixture(fixtures.TempDir()).path 

215 self.fixture = self.useFixture( 

216 config_fixture.Config(lockutils.CONF)) 

217 self.fixture.config(lock_path=lock_path, 

218 group='oslo_concurrency') 

219 

220 self.useFixture(nova_fixtures.ConfFixture(CONF)) 

221 

222 if self.STUB_RPC: 

223 self.useFixture(nova_fixtures.RPCFixture('nova.test')) 

224 

225 # we cannot set this in the ConfFixture as oslo only registers the 

226 # notification opts at the first instantiation of a Notifier that 

227 # happens only in the RPCFixture 

228 CONF.set_default('driver', ['test'], 

229 group='oslo_messaging_notifications') 

230 

231 # NOTE(danms): Make sure to reset us back to non-remote objects 

232 # for each test to avoid interactions. Also, backup the object 

233 # registry. 

234 objects_base.NovaObject.indirection_api = None 

235 self._base_test_obj_backup = copy.copy( 

236 objects_base.NovaObjectRegistry._registry._obj_classes) 

237 self.addCleanup(self._restore_obj_registry) 

238 objects.Service.clear_min_version_cache() 

239 

240 # NOTE(danms): Reset the cached list of cells 

241 from nova.compute import api 

242 api.CELLS = [] 

243 context.CELL_CACHE = {} 

244 context.CELLS = [] 

245 

246 self.computes = {} 

247 self.cell_mappings = {} 

248 self.host_mappings = {} 

249 # NOTE(danms): If the test claims to want to set up the database 

250 # itself, then it is responsible for all the mapping stuff too. 

251 if self.USES_DB: 

252 # NOTE(danms): Full database setup involves a cell0, cell1, 

253 # and the relevant mappings. 

254 self.useFixture(nova_fixtures.Database(database='api')) 

255 self._setup_cells() 

256 self.useFixture(nova_fixtures.DefaultFlavorsFixture()) 

257 elif not self.USES_DB_SELF: 

258 # NOTE(danms): If not using the database, we mock out the 

259 # mapping stuff and effectively collapse everything to a 

260 # single cell. 

261 self.useFixture(nova_fixtures.SingleCellSimple()) 

262 self.useFixture(nova_fixtures.DatabasePoisonFixture()) 

263 

264 # NOTE(blk-u): WarningsFixture must be after the Database fixture 

265 # because sqlalchemy-migrate messes with the warnings filters. 

266 self.useFixture(nova_fixtures.WarningsFixture()) 

267 

268 self.useFixture(ovo_fixture.StableObjectJsonFixture()) 

269 

270 # Reset the global QEMU version flag. 

271 images.QEMU_VERSION = None 

272 

273 # Reset the compute RPC API globals (mostly the _ROUTER). 

274 compute_rpcapi.reset_globals() 

275 

276 self.addCleanup(self._clear_attrs) 

277 self.useFixture(fixtures.EnvironmentVariable('http_proxy')) 

278 self.policy = self.useFixture(nova_fixtures.PolicyFixture()) 

279 

280 self.useFixture(nova_fixtures.PoisonFunctions()) 

281 

282 self.useFixture(nova_fixtures.ForbidNewLegacyNotificationFixture()) 

283 

284 # NOTE(mikal): make sure we don't load a privsep helper accidentally 

285 self.useFixture(nova_fixtures.PrivsepNoHelperFixture()) 

286 self.useFixture(mock_fixture.MockAutospecFixture()) 

287 

288 # FIXME(danms): Disable this for all tests by default to avoid breaking 

289 # any that depend on default/previous ordering 

290 self.flags(build_failure_weight_multiplier=0.0, 

291 group='filter_scheduler') 

292 

293 # NOTE(melwitt): Reset the cached set of projects 

294 quota.UID_QFD_POPULATED_CACHE_BY_PROJECT = set() 

295 quota.UID_QFD_POPULATED_CACHE_ALL = False 

296 

297 self.useFixture(nova_fixtures.GenericPoisonFixture()) 

298 self.useFixture(nova_fixtures.SysFsPoisonFixture()) 

299 

300 # Additional module names can be added to this set if needed 

301 self.useFixture(nova_fixtures.ImportModulePoisonFixture( 

302 set(['guestfs', 'libvirt']))) 

303 

304 # make sure that the wsgi app is fully initialized for all testcase 

305 # instead of only once initialized for test worker 

306 wsgi_app.init_global_data.reset() 

307 wsgi_app.init_application.reset() 

308 

309 # Reset the placement client singleton 

310 report.PLACEMENTCLIENT = None 

311 

312 # Reset our local node uuid cache (and avoid writing to the 

313 # local filesystem when we generate a new one). 

314 if self.STUB_COMPUTE_ID: 

315 self.useFixture(nova_fixtures.ComputeNodeIdFixture()) 

316 

317 # Reset globals indicating affinity filter support. Some tests may set 

318 # self.flags(enabled_filters=...) which could make the affinity filter 

319 # support globals get set to a non-default configuration which affects 

320 # all other tests. 

321 scheduler_utils.reset_globals() 

322 

323 # Wait for bare greenlets spawn_n()'ed from a GreenThreadPoolExecutor 

324 # to finish before moving on from the test. When greenlets from a 

325 # previous test remain running, they may attempt to access structures 

326 # (like the database) that have already been torn down and can cause 

327 # the currently running test to fail. 

328 self.useFixture(nova_fixtures.GreenThreadPoolShutdownWait()) 

329 

330 # Reset the global key manager 

331 nova.crypto._KEYMGR = None 

332 

333 # Reset the global identity client 

334 nova.limit.utils.IDENTITY_CLIENT = None 

335 

336 def _setup_cells(self): 

337 """Setup a normal cellsv2 environment. 

338 

339 This sets up the CellDatabase fixture with two cells, one cell0 

340 and one normal cell. CellMappings are created for both so that 

341 cells-aware code can find those two databases. 

342 """ 

343 celldbs = nova_fixtures.CellDatabases() 

344 

345 ctxt = context.get_context() 

346 fake_transport = 'fake://nowhere/' 

347 

348 c0 = objects.CellMapping( 

349 context=ctxt, 

350 uuid=objects.CellMapping.CELL0_UUID, 

351 name='cell0', 

352 transport_url=fake_transport, 

353 database_connection=objects.CellMapping.CELL0_UUID) 

354 c0.create() 

355 self.cell_mappings[c0.name] = c0 

356 celldbs.add_cell_database(objects.CellMapping.CELL0_UUID) 

357 

358 for x in range(self.NUMBER_OF_CELLS): 

359 name = 'cell%i' % (x + 1) 

360 uuid = getattr(uuids, name) 

361 cell = objects.CellMapping( 

362 context=ctxt, 

363 uuid=uuid, 

364 name=name, 

365 transport_url=fake_transport, 

366 database_connection=uuid) 

367 cell.create() 

368 self.cell_mappings[name] = cell 

369 # cell1 is the default cell 

370 celldbs.add_cell_database(uuid, default=(x == 0)) 

371 

372 self.useFixture(celldbs) 

373 

374 def _restore_obj_registry(self): 

375 objects_base.NovaObjectRegistry._registry._obj_classes = \ 

376 self._base_test_obj_backup 

377 

378 def _clear_attrs(self): 

379 # Delete attributes that don't start with _ so they don't pin 

380 # memory around unnecessarily for the duration of the test 

381 # suite 

382 for key in [k for k in self.__dict__.keys() if k[0] != '_']: 

383 # NOTE(gmann): Skip attribute 'id' because if tests are being 

384 # generated using testscenarios then, 'id' attribute is being 

385 # added during cloning the tests. And later that 'id' attribute 

386 # is being used by test suite to generate the results for each 

387 # newly generated tests by testscenarios. 

388 if key != 'id': 

389 del self.__dict__[key] 

390 

391 def stub_out(self, old, new): 

392 """Replace a function for the duration of the test. 

393 

394 Use the monkey patch fixture to replace a function for the 

395 duration of a test. Useful when you want to provide fake 

396 methods instead of mocks during testing. 

397 """ 

398 self.useFixture(fixtures.MonkeyPatch(old, new)) 

399 

400 @staticmethod 

401 def patch_exists(patched_path, result, other=None): 

402 """Provide a static method version of patch_exists(), which if you 

403 haven't already imported nova.test can be slightly easier to 

404 use as a context manager within a test method via: 

405 

406 def test_something(self): 

407 with self.patch_exists(path, True): 

408 ... 

409 """ 

410 return patch_exists(patched_path, result, other) 

411 

412 @staticmethod 

413 def patch_open(patched_path, read_data): 

414 """Provide a static method version of patch_open() which is easier to 

415 use as a context manager within a test method via: 

416 

417 def test_something(self): 

418 with self.patch_open(path, "fake contents of file"): 

419 ... 

420 """ 

421 return patch_open(patched_path, read_data) 

422 

423 def flags(self, **kw): 

424 """Override flag variables for a test.""" 

425 group = kw.pop('group', None) 

426 for k, v in kw.items(): 

427 CONF.set_override(k, v, group) 

428 

429 def reset_flags(self, *k, **kw): 

430 """Reset flag variables for a test.""" 

431 group = kw.pop('group') 

432 for flag in k: 

433 CONF.clear_override(flag, group) 

434 

435 def enforce_fk_constraints(self, engine=None): 

436 if engine is None: 436 ↛ 437line 436 didn't jump to line 437 because the condition on line 436 was never true

437 engine = db_api.get_engine() 

438 dialect = engine.url.get_dialect() 

439 if dialect == sqlite.dialect: 439 ↛ exitline 439 didn't return from function 'enforce_fk_constraints' because the condition on line 439 was always true

440 engine.connect().exec_driver_sql("PRAGMA foreign_keys = ON") 

441 

442 def start_service(self, name, host=None, cell_name=None, **kwargs): 

443 # Disallow starting multiple scheduler services 

444 if name == 'scheduler' and self._service_fixture_count[name]: 444 ↛ 445line 444 didn't jump to line 445 because the condition on line 444 was never true

445 raise TestingException("Duplicate start_service(%s)!" % name) 

446 

447 cell = None 

448 # if the host is None then the CONF.host remains defaulted to 

449 # 'fake-mini' (originally done in ConfFixture) 

450 if host is not None: 

451 # Make sure that CONF.host is relevant to the right hostname 

452 self.useFixture(nova_fixtures.ConfPatcher(host=host)) 

453 

454 if name == 'compute' and self.USES_DB: 

455 # NOTE(danms): We need to create the HostMapping first, because 

456 # otherwise we'll fail to update the scheduler while running 

457 # the compute node startup routines below. 

458 ctxt = context.get_context() 

459 cell_name = cell_name or CELL1_NAME 

460 cell = self.cell_mappings[cell_name] 

461 if (host or name) not in self.host_mappings: 461 ↛ 469line 461 didn't jump to line 469 because the condition on line 461 was always true

462 # NOTE(gibi): If the HostMapping does not exists then this is 

463 # the first start of the service so we create the mapping. 

464 hm = objects.HostMapping(context=ctxt, 

465 host=host or name, 

466 cell_mapping=cell) 

467 hm.create() 

468 self.host_mappings[hm.host] = hm 

469 svc = self.useFixture( 

470 nova_fixtures.ServiceFixture(name, host, cell=cell, **kwargs)) 

471 

472 # Keep track of how many instances of this service are running. 

473 self._service_fixture_count[name] += 1 

474 real_stop = svc.service.stop 

475 

476 # Make sure stopping the service decrements the active count, so that 

477 # start,stop,start doesn't trigger the "Duplicate start_service" 

478 # exception. 

479 def patch_stop(*a, **k): 

480 self._service_fixture_count[name] -= 1 

481 return real_stop(*a, **k) 

482 self.useFixture(fixtures.MockPatchObject( 

483 svc.service, 'stop', patch_stop)) 

484 

485 return svc.service 

486 

487 def _start_compute(self, host, cell_name=None): 

488 """Start a nova compute service on the given host 

489 

490 :param host: the name of the host that will be associated to the 

491 compute service. 

492 :param cell_name: optional name of the cell in which to start the 

493 compute service 

494 :return: the nova compute service object 

495 """ 

496 compute = self.start_service('compute', host=host, cell_name=cell_name) 

497 self.computes[host] = compute 

498 return compute 

499 

500 def _run_periodics(self, raise_on_error=False): 

501 """Run the update_available_resource task on every compute manager 

502 

503 This runs periodics on the computes in an undefined order; some child 

504 class redefine this function to force a specific order. 

505 """ 

506 

507 ctx = context.get_admin_context() 

508 for host, compute in self.computes.items(): 

509 LOG.info('Running periodic for compute (%s)', host) 

510 # Make sure the context is targeted to the proper cell database 

511 # for multi-cell tests. 

512 with context.target_cell( 

513 ctx, self.host_mappings[host].cell_mapping) as cctxt: 

514 compute.manager.update_available_resource(cctxt) 

515 

516 if raise_on_error: 

517 if 'Traceback (most recent call last' in self.stdlog.logger.output: 

518 # Get the last line of the traceback, for example: 

519 # TypeError: virNodeDeviceLookupByName() argument 2 must be 

520 # str or None, not Proxy 

521 last_tb_line = self.stdlog.logger.output.splitlines()[-1] 

522 raise TestingException(last_tb_line) 

523 

524 LOG.info('Finished with periodics') 

525 

526 def restart_compute_service(self, compute, keep_hypervisor_state=True): 

527 """Stops the service and starts a new one to have realistic restart 

528 

529 :param:compute: the nova-compute service to be restarted 

530 :param:keep_hypervisor_state: If true then already defined instances 

531 will survive the compute service restart. 

532 If false then the new service will see 

533 an empty hypervisor 

534 :returns: a new compute service instance serving the same host and 

535 and node 

536 """ 

537 

538 # NOTE(gibi): The service interface cannot be used to simulate a real 

539 # service restart as the manager object will not be recreated after a 

540 # service.stop() and service.start() therefore the manager state will 

541 # survive. For example the resource tracker will not be recreated after 

542 # a stop start. The service.kill() call cannot help as it deletes 

543 # the service from the DB which is unrealistic and causes that some 

544 # operation that refers to the killed host (e.g. evacuate) fails. 

545 # So this helper method will stop the original service and then starts 

546 # a brand new compute service for the same host and node. This way 

547 # a new ComputeManager instance will be created and initialized during 

548 # the service startup. 

549 compute.stop() 

550 

551 # this service was running previously so we have to make sure that 

552 # we restart it in the same cell 

553 cell_name = self.host_mappings[compute.host].cell_mapping.name 

554 

555 if keep_hypervisor_state: 

556 # NOTE(gibi): FakeDriver does not provide a meaningful way to 

557 # define some servers that exists already on the hypervisor when 

558 # the driver is (re)created during the service startup. This means 

559 # that we cannot simulate that the definition of a server 

560 # survives a nova-compute service restart on the hypervisor. 

561 # Instead here we save the FakeDriver instance that knows about 

562 # the defined servers and inject that driver into the new Manager 

563 # class during the startup of the compute service. 

564 old_driver = compute.manager.driver 

565 with mock.patch( 

566 'nova.virt.driver.load_compute_driver') as load_driver: 

567 load_driver.return_value = old_driver 

568 new_compute = self.start_service( 

569 'compute', host=compute.host, cell_name=cell_name) 

570 else: 

571 new_compute = self.start_service( 

572 'compute', host=compute.host, cell_name=cell_name) 

573 

574 return new_compute 

575 

576 def assertJsonEqual(self, expected, observed, message=''): 

577 """Asserts that 2 complex data structures are json equivalent. 

578 

579 We use data structures which serialize down to json throughout 

580 the code, and often times we just need to know that these are 

581 json equivalent. This means that list order is not important, 

582 and should be sorted. 

583 

584 Because this is a recursive set of assertions, when failure 

585 happens we want to expose both the local failure and the 

586 global view of the 2 data structures being compared. So a 

587 MismatchError which includes the inner failure as the 

588 mismatch, and the passed in expected / observed as matchee / 

589 matcher. 

590 

591 """ 

592 if isinstance(expected, str): 

593 expected = jsonutils.loads(expected) 

594 if isinstance(observed, str): 

595 observed = jsonutils.loads(observed) 

596 

597 def sort_key(x): 

598 if isinstance(x, (set, list)) or isinstance(x, datetime.datetime): 

599 return str(x) 

600 if isinstance(x, dict): 

601 items = ((sort_key(key), sort_key(value)) 

602 for key, value in x.items()) 

603 return sorted(items) 

604 return x 

605 

606 def inner(expected, observed, path='root'): 

607 if isinstance(expected, dict) and isinstance(observed, dict): 

608 self.assertEqual( 

609 len(expected), len(observed), 

610 ('path: %s. Different dict key sets\n' 

611 'expected=%s\n' 

612 'observed=%s\n' 

613 'difference=%s') % 

614 (path, 

615 sorted(expected.keys()), 

616 sorted(observed.keys()), 

617 list(set(expected.keys()).symmetric_difference( 

618 set(observed.keys()))))) 

619 expected_keys = sorted(expected) 

620 observed_keys = sorted(observed) 

621 self.assertEqual( 

622 expected_keys, observed_keys, 

623 'path: %s. Dict keys are not equal' % path) 

624 for key in expected: 

625 inner(expected[key], observed[key], path + '.%s' % key) 

626 elif (isinstance(expected, (list, tuple, set)) and 

627 isinstance(observed, (list, tuple, set))): 

628 self.assertEqual( 

629 len(expected), len(observed), 

630 ('path: %s. Different list items\n' 

631 'expected=%s\n' 

632 'observed=%s\n' 

633 'difference=%s') % 

634 (path, 

635 sorted(expected, key=sort_key), 

636 sorted(observed, key=sort_key), 

637 [a for a in itertools.chain(expected, observed) if 

638 (a not in expected) or (a not in observed)])) 

639 

640 expected_values_iter = iter(sorted(expected, key=sort_key)) 

641 observed_values_iter = iter(sorted(observed, key=sort_key)) 

642 

643 for i in range(len(expected)): 

644 inner(next(expected_values_iter), 

645 next(observed_values_iter), path + '[%s]' % i) 

646 else: 

647 self.assertEqual(expected, observed, 'path: %s' % path) 

648 

649 try: 

650 inner(expected, observed) 

651 except testtools.matchers.MismatchError as e: 

652 difference = e.mismatch.describe() 

653 if message: 

654 message = 'message: %s\n' % message 

655 msg = "\nexpected:\n%s\nactual:\n%s\ndifference:\n%s\n%s" % ( 

656 pprint.pformat(expected), 

657 pprint.pformat(observed), 

658 difference, 

659 message) 

660 error = AssertionError(msg) 

661 error.expected = expected 

662 error.observed = observed 

663 error.difference = difference 

664 raise error 

665 

666 def assertXmlEqual(self, expected, observed, **options): 

667 self.assertThat(observed, matchers.XMLMatches(expected, **options)) 

668 

669 def assertPublicAPISignatures(self, baseinst, inst): 

670 def get_public_apis(inst): 

671 methods = {} 

672 

673 def findmethods(object): 

674 return inspect.ismethod(object) or inspect.isfunction(object) 

675 

676 for (name, value) in inspect.getmembers(inst, findmethods): 

677 if name.startswith("_"): 

678 continue 

679 methods[name] = value 

680 return methods 

681 

682 baseclass = baseinst.__class__.__name__ 

683 basemethods = get_public_apis(baseinst) 

684 implmethods = get_public_apis(inst) 

685 

686 extranames = [] 

687 for name in sorted(implmethods.keys()): 

688 if name not in basemethods: 688 ↛ 689line 688 didn't jump to line 689 because the condition on line 688 was never true

689 extranames.append(name) 

690 

691 self.assertEqual([], extranames, 

692 "public APIs not listed in base class %s" % 

693 baseclass) 

694 

695 for name in sorted(implmethods.keys()): 

696 # NOTE(stephenfin): We ignore type annotations 

697 baseargs = inspect.getfullargspec(basemethods[name])[:-1] 

698 implargs = inspect.getfullargspec(implmethods[name])[:-1] 

699 

700 self.assertEqual(baseargs, implargs, 

701 "%s args don't match base class %s" % 

702 (name, baseclass)) 

703 

704 

705class APICoverage(object): 

706 

707 cover_api = None 

708 

709 def test_api_methods(self): 

710 self.assertIsNotNone(self.cover_api) 

711 api_methods = [x for x in dir(self.cover_api) 

712 if not x.startswith('_')] 

713 test_methods = [x[5:] for x in dir(self) 

714 if x.startswith('test_')] 

715 self.assertThat( 

716 test_methods, 

717 testtools.matchers.ContainsAll(api_methods)) 

718 

719 

720class SubclassSignatureTestCase(testtools.TestCase, metaclass=abc.ABCMeta): 

721 """Ensure all overridden methods of all subclasses of the class 

722 under test exactly match the signature of the base class. 

723 

724 A subclass of SubclassSignatureTestCase should define a method 

725 _get_base_class which: 

726 

727 * Returns a base class whose subclasses will all be checked 

728 * Ensures that all subclasses to be tested have been imported 

729 

730 SubclassSignatureTestCase defines a single test, test_signatures, 

731 which does a recursive, depth-first check of all subclasses, ensuring 

732 that their method signatures are identical to those of the base class. 

733 """ 

734 @abc.abstractmethod 

735 def _get_base_class(self): 

736 raise NotImplementedError() 

737 

738 def setUp(self): 

739 self.useFixture(nova_fixtures.ConfFixture(CONF)) 

740 self.base = self._get_base_class() 

741 

742 super(SubclassSignatureTestCase, self).setUp() 

743 

744 @staticmethod 

745 def _get_argspecs(cls): 

746 """Return a dict of method_name->argspec for every method of cls.""" 

747 argspecs = {} 

748 

749 # getmembers returns all members, including members inherited from 

750 # the base class. It's redundant for us to test these, but as 

751 # they'll always pass it's not worth the complexity to filter them out. 

752 for (name, method) in inspect.getmembers(cls, inspect.isfunction): 

753 # Subclass __init__ methods can usually be legitimately different 

754 if name == '__init__': 

755 continue 

756 

757 # Skip subclass private functions 

758 if name.startswith('_'): 

759 continue 

760 

761 while hasattr(method, '__wrapped__'): 

762 # This is a wrapped function. The signature we're going to 

763 # see here is that of the wrapper, which is almost certainly 

764 # going to involve varargs and kwargs, and therefore is 

765 # unlikely to be what we want. If the wrapper manipulates the 

766 # arguments taken by the wrapped function, the wrapped function 

767 # isn't what we want either. In that case we're just stumped: 

768 # if it ever comes up, add more knobs here to work round it (or 

769 # stop using a dynamic language). 

770 # 

771 # Here we assume the wrapper doesn't manipulate the arguments 

772 # to the wrapped function and inspect the wrapped function 

773 # instead. 

774 method = getattr(method, '__wrapped__') 

775 

776 argspecs[name] = inspect.getfullargspec(method) 

777 

778 return argspecs 

779 

780 @staticmethod 

781 def _clsname(cls): 

782 """Return the fully qualified name of cls.""" 

783 return "%s.%s" % (cls.__module__, cls.__name__) 

784 

785 def _test_signatures_recurse(self, base, base_argspecs): 

786 for sub in base.__subclasses__(): 

787 sub_argspecs = self._get_argspecs(sub) 

788 

789 # Check that each subclass method matches the signature of the 

790 # base class 

791 for (method, sub_argspec) in sub_argspecs.items(): 

792 # Methods which don't override methods in the base class 

793 # are good. 

794 if method in base_argspecs: 794 ↛ 791line 794 didn't jump to line 791 because the condition on line 794 was always true

795 self.assertEqual(base_argspecs[method], sub_argspec, 

796 'Signature of %(sub)s.%(method)s ' 

797 'differs from superclass %(base)s' % 

798 {'base': self._clsname(base), 

799 'sub': self._clsname(sub), 

800 'method': method}) 

801 

802 # Recursively check this subclass 

803 self._test_signatures_recurse(sub, sub_argspecs) 

804 

805 def test_signatures(self): 

806 self._test_signatures_recurse(self.base, self._get_argspecs(self.base)) 

807 

808 

809class TimeOverride(fixtures.Fixture): 

810 """Fixture to start and remove time override.""" 

811 

812 def __init__(self, override_time=None): 

813 self.override_time = override_time 

814 

815 def setUp(self): 

816 super(TimeOverride, self).setUp() 

817 timeutils.set_time_override(override_time=self.override_time) 

818 self.addCleanup(timeutils.clear_time_override) 

819 

820 

821class NoDBTestCase(TestCase): 

822 """`NoDBTestCase` differs from TestCase in that DB access is not supported. 

823 This makes tests run significantly faster. If possible, all new tests 

824 should derive from this class. 

825 """ 

826 USES_DB = False 

827 

828 

829class MatchType(object): 

830 """Matches any instance of a specified type 

831 

832 The MatchType class is a helper for use with the mock.assert_called_with() 

833 method that lets you assert that a particular parameter has a specific data 

834 type. It enables stricter checking than the built in mock.ANY helper. 

835 

836 Example usage could be: 

837 

838 mock_some_method.assert_called_once_with( 

839 "hello", 

840 MatchType(objects.Instance), 

841 mock.ANY, 

842 "world", 

843 MatchType(objects.KeyPair)) 

844 """ 

845 

846 def __init__(self, wanttype): 

847 self.wanttype = wanttype 

848 

849 def __eq__(self, other): 

850 return type(other) is self.wanttype 

851 

852 def __ne__(self, other): 

853 return type(other) is not self.wanttype 

854 

855 def __repr__(self): 

856 return "<MatchType:" + str(self.wanttype) + ">" 

857 

858 

859class MatchObjPrims(object): 

860 """Matches objects with equal primitives.""" 

861 

862 def __init__(self, want_obj): 

863 self.want_obj = want_obj 

864 

865 def __eq__(self, other): 

866 return objects_base.obj_equal_prims(other, self.want_obj) 

867 

868 def __ne__(self, other): 

869 return not other == self.want_obj 

870 

871 def __repr__(self): 

872 return '<MatchObjPrims:' + str(self.want_obj) + '>' 

873 

874 

875class ContainKeyValue(object): 

876 """Checks whether a key/value pair is in a dict parameter. 

877 

878 The ContainKeyValue class is a helper for use with the mock.assert_*() 

879 method that lets you assert that a particular dict contain a key/value 

880 pair. It enables stricter checking than the built in mock.ANY helper. 

881 

882 Example usage could be: 

883 

884 mock_some_method.assert_called_once_with( 

885 "hello", 

886 ContainKeyValue('foo', bar), 

887 mock.ANY, 

888 "world", 

889 ContainKeyValue('hello', world)) 

890 """ 

891 

892 def __init__(self, wantkey, wantvalue): 

893 self.wantkey = wantkey 

894 self.wantvalue = wantvalue 

895 

896 def __eq__(self, other): 

897 try: 

898 return other[self.wantkey] == self.wantvalue 

899 except (KeyError, TypeError): 

900 return False 

901 

902 def __ne__(self, other): 

903 try: 

904 return other[self.wantkey] != self.wantvalue 

905 except (KeyError, TypeError): 

906 return True 

907 

908 def __repr__(self): 

909 return "<ContainKeyValue: key " + str(self.wantkey) + \ 

910 " and value " + str(self.wantvalue) + ">" 

911 

912 

913@contextlib.contextmanager 

914def patch_exists(patched_path, result, other=None): 

915 """Selectively patch os.path.exists() so that if it's called with 

916 patched_path, return result. Calls with any other path are passed 

917 through to the real os.path.exists() function if other is not provided. 

918 If other is provided then that will be the result of the call on paths 

919 other than patched_path. 

920 

921 Either import and use as a decorator / context manager, or use the 

922 nova.TestCase.patch_exists() static method as a context manager. 

923 

924 Currently it is *not* recommended to use this if any of the 

925 following apply: 

926 

927 - You want to patch via decorator *and* make assertions about how the 

928 mock is called (since using it in the decorator form will not make 

929 the mock available to your code). 

930 

931 - You want the result of the patched exists() call to be determined 

932 programmatically (e.g. by matching substrings of patched_path). 

933 

934 - You expect exists() to be called multiple times on the same path 

935 and return different values each time. 

936 

937 Additionally within unit tests which only test a very limited code 

938 path, it may be possible to ensure that the code path only invokes 

939 exists() once, in which case it's slightly overkill to do 

940 selective patching based on the path. In this case something like 

941 like this may be more appropriate: 

942 

943 @mock.patch('os.path.exists', return_value=True) 

944 def test_my_code(self, mock_exists): 

945 ... 

946 mock_exists.assert_called_once_with(path) 

947 """ 

948 real_exists = os.path.exists 

949 

950 def fake_exists(path): 

951 if path == patched_path: 

952 return result 

953 elif other is not None: 

954 return other 

955 else: 

956 return real_exists(path) 

957 

958 with mock.patch.object(os.path, "exists") as mock_exists: 

959 mock_exists.side_effect = fake_exists 

960 yield mock_exists 

961 

962 

963@contextlib.contextmanager 

964def patch_open(patched_path, read_data): 

965 """Selectively patch open() so that if it's called with patched_path, 

966 return a mock which makes it look like the file contains 

967 read_data. Calls with any other path are passed through to the 

968 real open() function. 

969 

970 Either import and use as a decorator, or use the 

971 nova.TestCase.patch_open() static method as a context manager. 

972 

973 Currently it is *not* recommended to use this if any of the 

974 following apply: 

975 

976 - The code under test will attempt to write to patched_path. 

977 

978 - You want to patch via decorator *and* make assertions about how the 

979 mock is called (since using it in the decorator form will not make 

980 the mock available to your code). 

981 

982 - You want the faked file contents to be determined 

983 programmatically (e.g. by matching substrings of patched_path). 

984 

985 - You expect open() to be called multiple times on the same path 

986 and return different file contents each time. 

987 

988 Additionally within unit tests which only test a very limited code 

989 path, it may be possible to ensure that the code path only invokes 

990 open() once, in which case it's slightly overkill to do 

991 selective patching based on the path. In this case something like 

992 like this may be more appropriate: 

993 

994 @mock.patch('builtins.open') 

995 def test_my_code(self, mock_open): 

996 ... 

997 mock_open.assert_called_once_with(path) 

998 """ 

999 real_open = builtins.open 

1000 m = mock.mock_open(read_data=read_data) 

1001 

1002 def selective_fake_open(path, *args, **kwargs): 

1003 if path == patched_path: 

1004 return m(patched_path) 

1005 return real_open(path, *args, **kwargs) 

1006 

1007 with mock.patch('builtins.open') as mock_open: 

1008 mock_open.side_effect = selective_fake_open 

1009 yield m