|
17 | 17 | import textwrap |
18 | 18 | import unittest |
19 | 19 | import weakref |
| 20 | +import os |
20 | 21 |
|
21 | 22 | try: |
22 | 23 | from StringIO import StringIO |
|
47 | 48 | from .testutils import assert_run_python_script |
48 | 49 |
|
49 | 50 |
|
| 51 | +_TEST_GLOBAL_VARIABLE = "default_value" |
| 52 | + |
| 53 | + |
50 | 54 | class RaiserOnPickle(object): |
51 | 55 |
|
52 | 56 | def __init__(self, exc): |
@@ -436,6 +440,74 @@ def method(self, x): |
436 | 440 | mod1, mod2 = pickle_depickle([mod, mod]) |
437 | 441 | self.assertEqual(id(mod1), id(mod2)) |
438 | 442 |
|
| 443 | + def test_load_dynamic_module_in_grandchild_process(self): |
| 444 | + # Make sure that when loaded, a dynamic module preserves its dynamic |
| 445 | + # property. Otherwise, this will lead to an ImportError if pickled in |
| 446 | + # the child process and reloaded in another one. |
| 447 | + |
| 448 | + # We create a new dynamic module |
| 449 | + mod = imp.new_module('mod') |
| 450 | + code = ''' |
| 451 | + x = 1 |
| 452 | + ''' |
| 453 | + exec(textwrap.dedent(code), mod.__dict__) |
| 454 | + |
| 455 | + # This script will be ran in a separate child process. It will import |
| 456 | + # the pickled dynamic module, and then re-pickle it under a new name. |
| 457 | + # Finally, it will create a child process that will load the re-pickled |
| 458 | + # dynamic module. |
| 459 | + parent_process_module_file = 'dynamic_module_from_parent_process.pkl' |
| 460 | + child_process_module_file = 'dynamic_module_from_child_process.pkl' |
| 461 | + child_process_script = ''' |
| 462 | + import pickle |
| 463 | + import textwrap |
| 464 | +
|
| 465 | + import cloudpickle |
| 466 | + from testutils import assert_run_python_script |
| 467 | +
|
| 468 | +
|
| 469 | + child_of_child_process_script = {child_of_child_process_script} |
| 470 | +
|
| 471 | + with open('{parent_process_module_file}', 'rb') as f: |
| 472 | + mod = pickle.load(f) |
| 473 | +
|
| 474 | + with open('{child_process_module_file}', 'wb') as f: |
| 475 | + cloudpickle.dump(mod, f) |
| 476 | +
|
| 477 | + assert_run_python_script(textwrap.dedent(child_of_child_process_script)) |
| 478 | + ''' |
| 479 | + |
| 480 | + # The script ran by the process created by the child process |
| 481 | + child_of_child_process_script = """ ''' |
| 482 | + import pickle |
| 483 | + with open('{child_process_module_file}','rb') as fid: |
| 484 | + mod = pickle.load(fid) |
| 485 | + ''' """ |
| 486 | + |
| 487 | + # Filling the two scripts with the pickled modules filepaths and, |
| 488 | + # for the first child process, the script to be executed by its |
| 489 | + # own child process. |
| 490 | + child_of_child_process_script = child_of_child_process_script.format( |
| 491 | + child_process_module_file=child_process_module_file) |
| 492 | + |
| 493 | + child_process_script = child_process_script.format( |
| 494 | + parent_process_module_file=parent_process_module_file, |
| 495 | + child_process_module_file=child_process_module_file, |
| 496 | + child_of_child_process_script=child_of_child_process_script) |
| 497 | + |
| 498 | + try: |
| 499 | + with open(parent_process_module_file, 'wb') as fid: |
| 500 | + cloudpickle.dump(mod, fid) |
| 501 | + |
| 502 | + assert_run_python_script(textwrap.dedent(child_process_script)) |
| 503 | + |
| 504 | + finally: |
| 505 | + # Remove temporary created files |
| 506 | + if os.path.exists(parent_process_module_file): |
| 507 | + os.unlink(parent_process_module_file) |
| 508 | + if os.path.exists(child_process_module_file): |
| 509 | + os.unlink(child_process_module_file) |
| 510 | + |
439 | 511 | def test_find_module(self): |
440 | 512 | import pickle # ensure this test is decoupled from global imports |
441 | 513 | _find_module('pickle') |
@@ -887,6 +959,40 @@ def f1(): |
887 | 959 | clone_func=clone_func) |
888 | 960 | assert_run_python_script(textwrap.dedent(code)) |
889 | 961 |
|
| 962 | + def test_closure_interacting_with_a_global_variable(self): |
| 963 | + global _TEST_GLOBAL_VARIABLE |
| 964 | + assert _TEST_GLOBAL_VARIABLE == "default_value" |
| 965 | + orig_value = _TEST_GLOBAL_VARIABLE |
| 966 | + try: |
| 967 | + def f0(): |
| 968 | + global _TEST_GLOBAL_VARIABLE |
| 969 | + _TEST_GLOBAL_VARIABLE = "changed_by_f0" |
| 970 | + |
| 971 | + def f1(): |
| 972 | + return _TEST_GLOBAL_VARIABLE |
| 973 | + |
| 974 | + cloned_f0 = cloudpickle.loads(cloudpickle.dumps( |
| 975 | + f0, protocol=self.protocol)) |
| 976 | + cloned_f1 = cloudpickle.loads(cloudpickle.dumps( |
| 977 | + f1, protocol=self.protocol)) |
| 978 | + pickled_f1 = cloudpickle.dumps(f1, protocol=self.protocol) |
| 979 | + |
| 980 | + # Change the value of the global variable |
| 981 | + cloned_f0() |
| 982 | + assert _TEST_GLOBAL_VARIABLE == "changed_by_f0" |
| 983 | + |
| 984 | + # Ensure that the global variable is the same for another function |
| 985 | + result_cloned_f1 = cloned_f1() |
| 986 | + assert result_cloned_f1 == "changed_by_f0", result_cloned_f1 |
| 987 | + assert f1() == result_cloned_f1 |
| 988 | + |
| 989 | + # Ensure that unpickling the global variable does not change its |
| 990 | + # value |
| 991 | + result_pickled_f1 = cloudpickle.loads(pickled_f1)() |
| 992 | + assert result_pickled_f1 == "changed_by_f0", result_pickled_f1 |
| 993 | + finally: |
| 994 | + _TEST_GLOBAL_VARIABLE = orig_value |
| 995 | + |
890 | 996 | @pytest.mark.skipif(sys.version_info >= (3, 0), |
891 | 997 | reason="hardcoded pickle bytes for 2.7") |
892 | 998 | def test_function_pickle_compat_0_4_0(self): |
|
0 commit comments