@@ -251,6 +251,205 @@ def test_issue16421_multiple_modules_in_one_dll(self):
251
251
with self .assertRaises (ImportError ):
252
252
imp .load_dynamic ('nonexistent' , pathname )
253
253
254
+ @requires_load_dynamic
255
+ def test_singlephase_variants (self ):
256
+ '''Exercise the most meaningful variants described in Python/import.c.'''
257
+ self .maxDiff = None
258
+
259
+ basename = '_testsinglephase'
260
+ fileobj , pathname , _ = imp .find_module (basename )
261
+ fileobj .close ()
262
+
263
+ modules = {}
264
+ def load (name ):
265
+ assert name not in modules
266
+ module = imp .load_dynamic (name , pathname )
267
+ self .assertNotIn (module , modules .values ())
268
+ modules [name ] = module
269
+ return module
270
+
271
+ def re_load (name , module ):
272
+ assert sys .modules [name ] is module
273
+ before = type (module )(module .__name__ )
274
+ before .__dict__ .update (vars (module ))
275
+
276
+ reloaded = imp .load_dynamic (name , pathname )
277
+
278
+ return before , reloaded
279
+
280
+ def check_common (name , module ):
281
+ summed = module .sum (1 , 2 )
282
+ lookedup = module .look_up_self ()
283
+ initialized = module .initialized ()
284
+ cached = sys .modules [name ]
285
+
286
+ # module.__name__ might not match, but the spec will.
287
+ self .assertEqual (module .__spec__ .name , name )
288
+ if initialized is not None :
289
+ self .assertIsInstance (initialized , float )
290
+ self .assertGreater (initialized , 0 )
291
+ self .assertEqual (summed , 3 )
292
+ self .assertTrue (issubclass (module .error , Exception ))
293
+ self .assertEqual (module .int_const , 1969 )
294
+ self .assertEqual (module .str_const , 'something different' )
295
+ self .assertIs (cached , module )
296
+
297
+ return lookedup , initialized , cached
298
+
299
+ def check_direct (name , module , lookedup ):
300
+ # The module has its own PyModuleDef, with a matching name.
301
+ self .assertEqual (module .__name__ , name )
302
+ self .assertIs (lookedup , module )
303
+
304
+ def check_indirect (name , module , lookedup , orig ):
305
+ # The module re-uses another's PyModuleDef, with a different name.
306
+ assert orig is not module
307
+ assert orig .__name__ != name
308
+ self .assertNotEqual (module .__name__ , name )
309
+ self .assertIs (lookedup , module )
310
+
311
+ def check_basic (module , initialized ):
312
+ init_count = module .initialized_count ()
313
+
314
+ self .assertIsNot (initialized , None )
315
+ self .assertIsInstance (init_count , int )
316
+ self .assertGreater (init_count , 0 )
317
+
318
+ return init_count
319
+
320
+ def check_common_reloaded (name , module , cached , before , reloaded ):
321
+ recached = sys .modules [name ]
322
+
323
+ self .assertEqual (reloaded .__spec__ .name , name )
324
+ self .assertEqual (reloaded .__name__ , before .__name__ )
325
+ self .assertEqual (before .__dict__ , module .__dict__ )
326
+ self .assertIs (recached , reloaded )
327
+
328
+ def check_basic_reloaded (module , lookedup , initialized , init_count ,
329
+ before , reloaded ):
330
+ relookedup = reloaded .look_up_self ()
331
+ reinitialized = reloaded .initialized ()
332
+ reinit_count = reloaded .initialized_count ()
333
+
334
+ self .assertIs (reloaded , module )
335
+ self .assertIs (reloaded .__dict__ , module .__dict__ )
336
+ # It only happens to be the same but that's good enough here.
337
+ # We really just want to verify that the re-loaded attrs
338
+ # didn't change.
339
+ self .assertIs (relookedup , lookedup )
340
+ self .assertEqual (reinitialized , initialized )
341
+ self .assertEqual (reinit_count , init_count )
342
+
343
+ def check_with_reinit_reloaded (module , lookedup , initialized ,
344
+ before , reloaded ):
345
+ relookedup = reloaded .look_up_self ()
346
+ reinitialized = reloaded .initialized ()
347
+
348
+ self .assertIsNot (reloaded , module )
349
+ self .assertIsNot (reloaded , module )
350
+ self .assertNotEqual (reloaded .__dict__ , module .__dict__ )
351
+ self .assertIs (relookedup , reloaded )
352
+ if initialized is None :
353
+ self .assertIs (reinitialized , None )
354
+ else :
355
+ self .assertGreater (reinitialized , initialized )
356
+
357
+ # Check the "basic" module.
358
+
359
+ name = basename
360
+ expected_init_count = 1
361
+ with self .subTest (name ):
362
+ mod = load (name )
363
+ lookedup , initialized , cached = check_common (name , mod )
364
+ check_direct (name , mod , lookedup )
365
+ init_count = check_basic (mod , initialized )
366
+ self .assertEqual (init_count , expected_init_count )
367
+
368
+ before , reloaded = re_load (name , mod )
369
+ check_common_reloaded (name , mod , cached , before , reloaded )
370
+ check_basic_reloaded (mod , lookedup , initialized , init_count ,
371
+ before , reloaded )
372
+ basic = mod
373
+
374
+ # Check its indirect variants.
375
+
376
+ name = f'{ basename } _basic_wrapper'
377
+ expected_init_count += 1
378
+ with self .subTest (name ):
379
+ mod = load (name )
380
+ lookedup , initialized , cached = check_common (name , mod )
381
+ check_indirect (name , mod , lookedup , basic )
382
+ init_count = check_basic (mod , initialized )
383
+ self .assertEqual (init_count , expected_init_count )
384
+
385
+ before , reloaded = re_load (name , mod )
386
+ check_common_reloaded (name , mod , cached , before , reloaded )
387
+ check_basic_reloaded (mod , lookedup , initialized , init_count ,
388
+ before , reloaded )
389
+
390
+ # Currently _PyState_AddModule() always replaces the cached module.
391
+ self .assertIs (basic .look_up_self (), mod )
392
+ self .assertEqual (basic .initialized_count (), expected_init_count )
393
+
394
+ # The cached module shouldn't be changed after this point.
395
+ basic_lookedup = mod
396
+
397
+ # Check its direct variant.
398
+
399
+ name = f'{ basename } _basic_copy'
400
+ expected_init_count += 1
401
+ with self .subTest (name ):
402
+ mod = load (name )
403
+ lookedup , initialized , cached = check_common (name , mod )
404
+ check_direct (name , mod , lookedup )
405
+ init_count = check_basic (mod , initialized )
406
+ self .assertEqual (init_count , expected_init_count )
407
+
408
+ before , reloaded = re_load (name , mod )
409
+ check_common_reloaded (name , mod , cached , before , reloaded )
410
+ check_basic_reloaded (mod , lookedup , initialized , init_count ,
411
+ before , reloaded )
412
+
413
+ # This should change the cached module for _testsinglephase.
414
+ self .assertIs (basic .look_up_self (), basic_lookedup )
415
+ self .assertEqual (basic .initialized_count (), expected_init_count )
416
+
417
+ # Check the non-basic variant that has no state.
418
+
419
+ name = f'{ basename } _with_reinit'
420
+ with self .subTest (name ):
421
+ mod = load (name )
422
+ lookedup , initialized , cached = check_common (name , mod )
423
+ self .assertIs (initialized , None )
424
+ check_direct (name , mod , lookedup )
425
+
426
+ before , reloaded = re_load (name , mod )
427
+ check_common_reloaded (name , mod , cached , before , reloaded )
428
+ check_with_reinit_reloaded (mod , lookedup , initialized ,
429
+ before , reloaded )
430
+
431
+ # This should change the cached module for _testsinglephase.
432
+ self .assertIs (basic .look_up_self (), basic_lookedup )
433
+ self .assertEqual (basic .initialized_count (), expected_init_count )
434
+
435
+ # Check the basic variant that has state.
436
+
437
+ name = f'{ basename } _with_state'
438
+ with self .subTest (name ):
439
+ mod = load (name )
440
+ lookedup , initialized , cached = check_common (name , mod )
441
+ self .assertIsNot (initialized , None )
442
+ check_direct (name , mod , lookedup )
443
+
444
+ before , reloaded = re_load (name , mod )
445
+ check_common_reloaded (name , mod , cached , before , reloaded )
446
+ check_with_reinit_reloaded (mod , lookedup , initialized ,
447
+ before , reloaded )
448
+
449
+ # This should change the cached module for _testsinglephase.
450
+ self .assertIs (basic .look_up_self (), basic_lookedup )
451
+ self .assertEqual (basic .initialized_count (), expected_init_count )
452
+
254
453
@requires_load_dynamic
255
454
def test_load_dynamic_ImportError_path (self ):
256
455
# Issue #1559549 added `name` and `path` attributes to ImportError
0 commit comments