43
43
warnings .filterwarnings ("ignore" , r"Not importing directory '.*gen_py': missing __init__.py" , ImportWarning )
44
44
45
45
from btcrecover import btcrpass
46
- import os , unittest , cPickle , tempfile , shutil , multiprocessing , filecmp , sys
46
+ import os , unittest , cPickle , tempfile , shutil , multiprocessing , gc , filecmp , sys
47
47
48
48
49
49
class NonClosingBase (object ):
50
50
pass
51
51
52
- # Enables either ANSI or Unicode mode for all tests based on either
52
+ # Enables either ASCII or Unicode mode for all tests based on either
53
53
# the value of tstr or the value of the BTCR_CHAR_MODE env. variable
54
54
tstr = None
55
55
def setUpModule ():
@@ -72,7 +72,7 @@ def close(self): pass
72
72
tstr = str
73
73
tchr = chr
74
74
utf8_opt = ""
75
- print ("** Testing in ANSI character mode **" )
75
+ print ("** Testing in ASCII character mode **" )
76
76
77
77
else :
78
78
import io
@@ -918,9 +918,8 @@ def wallet_tester(self, wallet_filename,
918
918
force_purepython = False , force_kdf_purepython = False , force_bsddb_purepython = False ,
919
919
correct_pass = None , blockchain_mainpass = None , android_backuppass = None ):
920
920
wallet_filename = os .path .join (WALLET_DIR , wallet_filename )
921
-
922
- temp_dir = tempfile .mkdtemp ("-test-btcr" )
923
- pool = None
921
+ temp_dir = tempfile .mkdtemp ("-test-btcr" )
922
+ parent_process = True # bug workaround, see finally block below for details
924
923
try :
925
924
temp_wallet_filename = os .path .join (temp_dir , os .path .basename (wallet_filename ))
926
925
shutil .copyfile (wallet_filename , temp_wallet_filename )
@@ -951,21 +950,26 @@ def wallet_tester(self, wallet_filename,
951
950
(tstr ("btcr-wrong-password-3" ), correct_pass , tstr ("btcr-wrong-password-4" ))), (correct_pass , 2 ))
952
951
953
952
# Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded
953
+ parent_process = False
954
954
pool = multiprocessing .Pool (1 , init_worker , (wallet , tstr , force_purepython , force_kdf_purepython ))
955
+ parent_process = True
955
956
password_found_iterator = pool .imap (btcrpass .return_verified_password_or_false ,
956
957
( ( tstr ("btcr-wrong-password-1" ), tstr ("btcr-wrong-password-2" ) ),
957
958
( tstr ("btcr-wrong-password-3" ), correct_pass , tstr ("btcr-wrong-password-4" ) ) ))
958
959
self .assertEqual (password_found_iterator .next (), (False , 2 ))
959
960
self .assertEqual (password_found_iterator .next (), (correct_pass , 2 ))
960
961
self .assertRaises (StopIteration , password_found_iterator .next )
961
962
pool .close ()
962
- pool = None
963
+ pool . join ()
963
964
964
965
del wallet
966
+ gc .collect ()
965
967
self .assertTrue (filecmp .cmp (wallet_filename , temp_wallet_filename , False )) # False == always compare file contents
966
968
finally :
967
- shutil .rmtree (temp_dir )
968
- if pool : pool .terminate ()
969
+ # There's a bug which only occurs when combining unittest, multiprocessing, and "real"
970
+ # forking (Linux/BSD/WSL); only remove the temp dir if we're sure this is the parent process
971
+ if parent_process :
972
+ shutil .rmtree (temp_dir )
969
973
970
974
def test_armory (self ):
971
975
if not can_load_armory (): self .skipTest ("requires Armory and ASCII mode" )
@@ -1003,9 +1007,11 @@ def test_electrum27_upgradedfrom_electrum1(self):
1003
1007
1004
1008
@unittest .skipUnless (btcrpass .load_aes256_library ().__name__ == b"Crypto" , "requires PyCrypto" )
1005
1009
def test_electrum28 (self ):
1010
+ if not can_load_armory (permit_unicode = True ): self .skipTest ("requires Armory" )
1006
1011
self .wallet_tester ("electrum28-wallet" )
1007
1012
1008
1013
def test_electrum28_pp (self ):
1014
+ if not can_load_armory (permit_unicode = True ): self .skipTest ("requires Armory" )
1009
1015
self .wallet_tester ("electrum28-wallet" , force_purepython = True )
1010
1016
1011
1017
@unittest .skipUnless (btcrpass .load_aes256_library ().__name__ == b"Crypto" , "requires PyCrypto" )
@@ -1174,19 +1180,15 @@ def bip39_tester(self, force_purepython = False, unicode_pw = False, *args, **kw
1174
1180
(tstr ("btcr-wrong-password-3" ), correct_pass , tstr ("btcr-wrong-password-4" ))), (correct_pass , 2 ))
1175
1181
1176
1182
# Perform the tests in a child process to ensure the wallet can be pickled and all libraries reloaded
1177
- pool = None
1178
- try :
1179
- pool = multiprocessing .Pool (1 , init_worker , (wallet , tstr , force_purepython , False ))
1180
- password_found_iterator = pool .imap (btcrpass .return_verified_password_or_false ,
1181
- ( ( tstr ("btcr-wrong-password-1" ), tstr ("btcr-wrong-password-2" ) ),
1182
- ( tstr ("btcr-wrong-password-3" ), correct_pass , tstr ("btcr-wrong-password-4" ) ) ))
1183
- self .assertEqual (password_found_iterator .next (), (False , 2 ))
1184
- self .assertEqual (password_found_iterator .next (), (correct_pass , 2 ))
1185
- self .assertRaises (StopIteration , password_found_iterator .next )
1186
- pool .close ()
1187
- pool = None
1188
- finally :
1189
- if pool : pool .terminate ()
1183
+ pool = multiprocessing .Pool (1 , init_worker , (wallet , tstr , force_purepython , False ))
1184
+ password_found_iterator = pool .imap (btcrpass .return_verified_password_or_false ,
1185
+ ( ( tstr ("btcr-wrong-password-1" ), tstr ("btcr-wrong-password-2" ) ),
1186
+ ( tstr ("btcr-wrong-password-3" ), correct_pass , tstr ("btcr-wrong-password-4" ) ) ))
1187
+ self .assertEqual (password_found_iterator .next (), (False , 2 ))
1188
+ self .assertEqual (password_found_iterator .next (), (correct_pass , 2 ))
1189
+ self .assertRaises (StopIteration , password_found_iterator .next )
1190
+ pool .close ()
1191
+ pool .join ()
1190
1192
1191
1193
@unittest .skipUnless (btcrpass .load_pbkdf2_library ().__name__ == b"hashlib" ,
1192
1194
"requires Python 2.7.8+" )
@@ -1696,6 +1698,8 @@ def test_end_to_end(self):
1696
1698
data_extract = self .E2E_DATA_EXTRACT ,
1697
1699
autosave = autosave_file )
1698
1700
self .assertEqual ("btcr-test-password" , btcrpass .main ()[0 ])
1701
+ for process in multiprocessing .active_children ():
1702
+ process .join () # wait for any remaining child processes to exit cleanly
1699
1703
1700
1704
# Verify the exact password number where it was found to ensure password ordering hasn't changed
1701
1705
autosave_file .seek (SAVESLOT_SIZE )
@@ -1721,6 +1725,8 @@ def test_skip(self):
1721
1725
data_extract = self .E2E_DATA_EXTRACT ,
1722
1726
autosave = autosave_file )
1723
1727
self .assertIn ("Password search exhausted" , btcrpass .main ()[1 ])
1728
+ for process in multiprocessing .active_children ():
1729
+ process .join () # wait for any remaining child processes to exit cleanly
1724
1730
1725
1731
# Verify the password number where the search started
1726
1732
autosave_file .seek (0 )
0 commit comments