/
test_basic.py
593 lines (468 loc) · 19.5 KB
/
test_basic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# -*- coding: utf-8 -*-
#-----------------------------------------------------------------------------
# Copyright (c) 2005-2020, PyInstaller Development Team.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------
# Library imports
# ---------------
import locale
import os
import sys
# Third-party imports
# -------------------
import pytest
# Local imports
# -------------
from PyInstaller.compat import is_darwin, is_win, is_py37
from PyInstaller.utils.tests import importorskip, skipif, skipif_win, \
skipif_winorosx, skipif_notwin, skipif_notosx, skipif_no_compiler, \
skipif_notlinux, xfail
def test_run_from_path_environ(pyi_builder):
pyi_builder.test_script('pyi_absolute_python_path.py', run_from_path=True)
@skipif_winorosx
def test_absolute_ld_library_path(pyi_builder):
pyi_builder.test_script('pyi_absolute_ld_library_path.py')
def test_absolute_python_path(pyi_builder):
pyi_builder.test_script('pyi_absolute_python_path.py')
@skipif_notlinux
@skipif(not os.path.exists('/proc/self/status'),
reason='/proc/self/status does not exist')
@pytest.mark.parametrize("symlink_name",
["symlink",
"very_long_name_in_symlink",
"sub/dir/progam"])
def test_symlink_basename_is_kept(pyi_builder_spec, symlink_name,
tmpdir, SPEC_DIR, SCRIPT_DIR):
def patch(spec_name, symlink_name):
content = SPEC_DIR.join(spec_name).read_text(encoding="utf-8")
content = content.replace("@SYMLINKNAME@", symlink_name)
content = content.replace("@SCRIPTDIR@", str(SCRIPT_DIR))
outspec = tmpdir.join(spec_name)
outspec.write_text(content, encoding="utf-8", ensure=True)
return outspec
specfile = patch("symlink_basename_is_kept.spec", symlink_name)
pyi_builder_spec.test_spec(str(specfile), app_name=symlink_name)
def test_pyz_as_external_file(pyi_builder, monkeypatch):
# This tests the not well documented and seldom used feature of
# having the PYZ-archive in a separate file (.pkg).
def MyEXE(*args, **kwargs):
kwargs['append_pkg'] = False
return EXE(*args, **kwargs)
# :todo: find a better way to not even run this test in onefile-mode
if pyi_builder._mode == 'onefile':
pytest.skip('only --onedir')
import PyInstaller.building.build_main
EXE = PyInstaller.building.build_main.EXE
monkeypatch.setattr('PyInstaller.building.build_main.EXE', MyEXE)
pyi_builder.test_source("print('Hello Python!')")
def test_base_modules_regex(pyi_builder):
"""
Verify that the regex for excluding modules listed in
PY3_BASE_MODULES does not exclude other modules.
"""
pyi_builder.test_source(
"""
import resources_testmod
print('OK')
""")
def test_celementtree(pyi_builder):
pyi_builder.test_source(
"""
from xml.etree.cElementTree import ElementTree
print('OK')
""")
# Test a build with some complexity with the ``noarchive`` debug option.
def test_noarchive(pyi_builder):
pyi_builder.test_source("from xml.etree.cElementTree import ElementTree",
pyi_args=['--debug=noarchive'])
@importorskip('codecs')
def test_codecs(pyi_builder):
pyi_builder.test_script('pyi_codecs.py')
def test_compiled_filenames(pyi_builder):
pyi_builder.test_source("""
import pyi_dummy_module
from os.path import isabs
assert not isabs(pyi_dummy_module.dummy.__code__.co_filename), "pyi_dummy_module.dummy.__code__.co_filename has compiled filename: %s" % (pyi_dummy_module.dummy.__code__.co_filename,)
assert not isabs(pyi_dummy_module.DummyClass.dummyMethod.__code__.co_filename), "pyi_dummy_module.DummyClass.dummyMethod.__code__.co_filename has compiled filename: %s" % (pyi_dummy_module.DummyClass.dummyMethod.__code__.co_filename,)
""")
def test_decoders_ascii(pyi_builder):
pyi_builder.test_source(
"""
# Convert type 'bytes' to type 'str'.
assert b'foo'.decode('ascii') == 'foo'
""")
def test_distutils_submod(pyi_builder):
# Test import of submodules of distutils package
# PyI fails to include `distutils.version` when running from virtualenv
pyi_builder.test_source(
"""
from distutils.version import LooseVersion
""")
def test_dynamic_module(pyi_builder):
pyi_builder.test_source(
"""
import pyi_testmod_dynamic
# The value 'foo' should not be None.
print("'foo' value: %s" % pyi_testmod_dynamic.foo)
assert pyi_testmod_dynamic.foo is not None
assert pyi_testmod_dynamic.foo == 'A new value!'
""")
def test_email(pyi_builder):
pyi_builder.test_source(
"""
from email import utils
from email.header import Header
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.nonmultipart import MIMENonMultipart
""")
@importorskip('tinyaes')
def test_feature_crypto(pyi_builder):
pyi_builder.test_source(
"""
from pyimod00_crypto_key import key
from pyimod02_archive import CRYPT_BLOCK_SIZE
# Test against issue #1663: importing a package in the bootstrap
# phase should not interfere with subsequent imports.
import tinyaes
assert type(key) is str
# The test runner uses 'test_key' as key.
assert key == 'test_key'.zfill(CRYPT_BLOCK_SIZE)
""",
pyi_args=['--key=test_key'])
def test_feature_nocrypto(pyi_builder):
pyi_builder.test_source(
"""
try:
import pyimod00_crypto_key
raise AssertionError('The pyimod00_crypto_key module must NOT be there if crypto is disabled.')
except ImportError:
pass
""")
def test_filename(pyi_builder):
pyi_builder.test_script('pyi_filename.py')
def test_getfilesystemencoding(pyi_builder):
pyi_builder.test_script('pyi_getfilesystemencoding.py')
def test_helloworld(pyi_builder):
pyi_builder.test_source("print('Hello Python!')")
def test_module__file__attribute(pyi_builder):
pyi_builder.test_script('pyi_module__file__attribute.py')
def test_module_attributes(tmpdir, pyi_builder):
# Create file in tmpdir with path to python executable and if it is running
# in debug mode.
# Test script uses python interpreter to compare module attributes.
with open(os.path.join(tmpdir.strpath, 'python_exe.build'), 'w') as f:
f.write(sys.executable + "\n")
f.write('debug=%s' % __debug__ + '\n')
# On Windows we need to preserve systme PATH for subprocesses in tests.
f.write(os.environ.get('PATH') + '\n')
pyi_builder.test_script('pyi_module_attributes.py')
@xfail(is_darwin, reason='Issue #1895.')
def test_module_reload(pyi_builder):
pyi_builder.test_script('pyi_module_reload.py')
# TODO test it on OS X.
@skipif_no_compiler
def test_load_dll_using_ctypes(monkeypatch, pyi_builder, compiled_dylib):
# Note that including the data_dir fixture copies files needed by this test.
#
# TODO Make sure PyInstaller is able to find the library and bundle it with the app.
# # If the required dylib does not reside in the current directory, the Analysis
# # class machinery, based on ctypes.util.find_library, will not find it. This
# # was done on purpose for this test, to show how to give Analysis class
# # a clue.
# if is_win:
# os.environ['PATH'] = os.path.abspath(CTYPES_DIR) + ';' + os.environ['PATH']
# else:
# os.environ['LD_LIBRARY_PATH'] = CTYPES_DIR
# os.environ['DYLD_LIBRARY_PATH'] = CTYPES_DIR
# os.environ['LIBPATH'] = CTYPES_DIR
# Build and run the app.
pyi_builder.test_script('pyi_load_dll_using_ctypes.py')
def test_get_meipass_value(pyi_builder):
pyi_builder.test_script('pyi_get_meipass_value.py')
def test_chdir_meipass(pyi_builder):
# Ensure meipass dir exists.
pyi_builder.test_source(
"""
import os, sys
os.chdir(sys._MEIPASS)
print(os.getcwd())
""")
def test_option_exclude_module(pyi_builder):
"""
Test to ensure that when using option --exclude-module=xml.sax
the module 'xml.sax' won't be bundled.
"""
pyi_builder.test_source(
"""
try:
import xml.sax
# Option --exclude-module=xml.sax did not work and the module
# was successfully imported.
raise SystemExit('Module xml.sax was excluded but it is '
'bundled with the executable.')
except ImportError:
# The Import error is expected since PyInstaller should
# not bundle 'xml.sax' module.
pass
""",
pyi_args=['--exclude-module', 'xml.sax'])
def test_option_verbose(pyi_builder, monkeypatch):
"Test to ensure that option V can be set and has effect."
# This option is like 'python -v' - trace import statements.
# 'None' should be allowed or '' also.
def MyEXE(*args, **kwargs):
args = list(args)
args.append([('v', None, 'OPTION')])
return EXE(*args, **kwargs)
import PyInstaller.building.build_main
EXE = PyInstaller.building.build_main.EXE
monkeypatch.setattr('PyInstaller.building.build_main.EXE', MyEXE)
pyi_builder.test_source(
"""
print('test - PYTHONVERBOSE - trace import statements')
import re # just import anything
print('test - done')
""")
def test_option_w_unset(pyi_builder):
"Test to ensure that option W is not set by default."
pyi_builder.test_source(
"""
import sys
assert 'ignore' not in sys.warnoptions
""")
def test_option_w_ignore(pyi_builder, monkeypatch, capsys):
"Test to ensure that option W can be set."
def MyEXE(*args, **kwargs):
args = list(args)
args.append([('W ignore', '', 'OPTION')])
return EXE(*args, **kwargs)
import PyInstaller.building.build_main
EXE = PyInstaller.building.build_main.EXE
monkeypatch.setattr('PyInstaller.building.build_main.EXE', MyEXE)
pyi_builder.test_source(
"""
import sys
assert 'ignore' in sys.warnoptions
""")
_, err = capsys.readouterr()
assert "'import warnings' failed" not in err
@pytest.mark.parametrize("distutils", ["", "from distutils "])
def test_python_makefile(pyi_builder, distutils):
"""Tests hooks for ``sysconfig`` and its near-duplicate
``distutils.sysconfig``. Should raise a distutils error if it needed the
``pyconfig.h`` and ``makefile`` and didn't get them. Or an import error if
we failed to include the special extension module that replaced those
files in more modern Python versions.
"""
pyi_builder.test_source("""
{}import sysconfig
from pprint import pprint
pprint(sysconfig.get_config_vars())
""".format(distutils))
def test_set_icon(pyi_builder, data_dir):
if is_win:
args = ['--icon', os.path.join(data_dir.strpath, 'pyi_icon.ico')]
elif is_darwin:
# On OS X icon is applied only for windowed mode.
args = ['--windowed', '--icon', os.path.join(data_dir.strpath, 'pyi_icon.icns')]
else:
pytest.skip('option --icon works only on Windows and Mac OS X')
pyi_builder.test_source("print('Hello Python!')", pyi_args=args)
def test_python_home(pyi_builder):
pyi_builder.test_script('pyi_python_home.py')
def test_stderr_encoding(tmpdir, pyi_builder):
# NOTE: '-s' option to pytest disables output capturing, changing this test's result:
# without -s: py.test process changes its own stdout encoding to 'UTF-8' to
# capture output. subprocess spawned by py.test has stdout encoding
# 'cp1252', which is an ANSI codepage. test fails as they do not match.
# with -s: py.test process has stdout encoding from windows terminal, which is an
# OEM codepage. spawned subprocess has the same encoding. test passes.
#
with open(os.path.join(tmpdir.strpath, 'stderr_encoding.build'), 'w') as f:
if sys.stderr.isatty():
enc = str(sys.stderr.encoding)
else:
# For non-interactive stderr use locale encoding - ANSI codepage.
# This fixes the test when running with py.test and capturing output.
enc = locale.getpreferredencoding(False)
f.write(enc)
pyi_builder.test_script('pyi_stderr_encoding.py')
def test_stdout_encoding(tmpdir, pyi_builder):
with open(os.path.join(tmpdir.strpath, 'stdout_encoding.build'), 'w') as f:
if sys.stdout.isatty():
enc = str(sys.stdout.encoding)
else:
# For non-interactive stderr use locale encoding - ANSI codepage.
# This fixes the test when running with py.test and capturing output.
enc = locale.getpreferredencoding(False)
f.write(enc)
pyi_builder.test_script('pyi_stdout_encoding.py')
def test_site_module_disabled(pyi_builder):
pyi_builder.test_script('pyi_site_module_disabled.py')
def test_time_module(pyi_builder):
pyi_builder.test_source(
"""
import time
print(time.strptime(time.ctime()))
""")
@skipif_win
def test_time_module_localized(pyi_builder, monkeypatch):
# This checks that functions 'time.ctime()' and 'time.strptime()'
# use the same locale. There was an issue with bootloader where
# every function was using different locale:
# time.ctime was using 'C'
# time.strptime was using 'xx_YY' from the environment.
monkeypatch.setenv('LC_ALL', 'cs_CZ.UTF-8')
pyi_builder.test_source(
"""
import time
print(time.strptime(time.ctime()))
""")
def test_xmldom_module(pyi_builder):
pyi_builder.test_source(
"""
print('Importing xml.dom')
from xml.dom import pulldom
print('Importing done')
""")
def test_threading_module(pyi_builder):
pyi_builder.test_source(
"""
import threading
import sys
print('See stderr for messages')
def print_(*args): print(*args, file=sys.stderr)
def doit(nm):
print_(nm, 'started')
import pyi_testmod_threading
try:
print_(nm, pyi_testmod_threading.x)
finally:
print_(nm, pyi_testmod_threading)
t1 = threading.Thread(target=doit, args=('t1',))
t2 = threading.Thread(target=doit, args=('t2',))
t1.start()
t2.start()
doit('main')
t1.join() ; print_('t1 joined')
t2.join() ; print_('t2 joined')
print_('finished.')
""")
def test_threading_module2(pyi_builder):
pyi_builder.test_script('pyi_threading_module2.py')
def test_argument(pyi_builder):
pyi_builder.test_source(
'''
import sys
assert sys.argv[1] == "--argument", "sys.argv[1] was %r, expected %r" % (sys.argv[1], "--argument")
''',
app_args=["--argument"])
@importorskip('win32com')
def test_pywin32_win32com(pyi_builder):
pyi_builder.test_source(
"""
# Test importing some modules from pywin32 package.
# All modules from pywin32 depens on module pywintypes.
# This module should be also included.
import win32com
import win32com.client
import win32com.server
""")
#@pytest.mark.xfail(reason="Requires post-create-package hooks (issue #1322)")
@importorskip('win32com')
def test_pywin32_comext(pyi_builder):
pyi_builder.test_source(
"""
# Test importing modules from win32com that are actually present in
# win32comext, and made available by __path__ changes in win32com.
from win32com.shell import shell
from win32com.propsys import propsys
from win32com.bits import bits
""")
@importorskip('win32ui')
def test_pywin32_win32ui(pyi_builder):
pyi_builder.test_source(
"""
# Test importing some modules from pywin32 package.
# All modules from pywin32 depens on module pywintypes.
# This module should be also included.
import win32ui
from pywin.mfc.dialog import Dialog
d = Dialog(win32ui.IDD_SIMPLE_INPUT)
""")
@skipif_notwin
def test_renamed_exe(pyi_builder):
_old_find_executables = pyi_builder._find_executables
def _find_executables(name):
oldexes = _old_find_executables(name)
newexes = []
for old in oldexes:
new = os.path.join(os.path.dirname(old), "renamed_" + os.path.basename(old))
os.rename(old, new)
newexes.append(new)
return newexes
pyi_builder._find_executables = _find_executables
pyi_builder.test_source("print('Hello Python!')")
def test_spec_with_utf8(pyi_builder_spec):
pyi_builder_spec.test_spec('spec-with-utf8.spec')
@skipif_notosx
def test_osx_override_info_plist(pyi_builder_spec):
pyi_builder_spec.test_spec('pyi_osx_override_info_plist.spec')
def test_hook_collect_submodules(pyi_builder, script_dir):
# This is designed to test the operation of
# PyInstaller.utils.hook.collect_submodules. To do so:
#
# 1. It imports the dummy module pyi_collect_submodules_mod, which
# contains nothing.
# 2. This causes hook-pyi_collect_submodules_mod.py to be run,
# which collects some dummy submodules. In this case, it
# collects from modules/pyi_testmod_relimp.
# 3. Therefore, we should be able to find hidden imports under
# pyi_testmod_relimp.
pyi_builder.test_source(
"""
import pyi_collect_submodules_mod
__import__('pyi_testmod_relimp.B.C')
""",
['--additional-hooks-dir=%s' % script_dir.join('pyi_hooks')])
# Test that PyInstaller can handle a script with an arbitrary extension.
def test_arbitrary_ext(pyi_builder):
pyi_builder.test_script('pyi_arbitrary_ext.foo')
def test_option_runtime_tmpdir(pyi_builder):
"Test to ensure that option `runtime_tmpdir` can be set and has effect."
pyi_builder.test_source(
"""
print('test - runtime_tmpdir - custom runtime temporary directory')
import os
import sys
if sys.platform == 'win32':
import win32api
cwd = os.path.abspath(os.getcwd())
runtime_tmpdir = os.path.abspath(sys._MEIPASS)
# for onedir mode, runtime_tmpdir == cwd
# for onefile mode, os.path.dirname(runtime_tmpdir) == cwd
if not runtime_tmpdir == cwd and not os.path.dirname(runtime_tmpdir) == cwd:
raise SystemExit('Expected sys._MEIPASS to be under current working dir.'
' sys._MEIPASS = ' + runtime_tmpdir + ', cwd = ' + cwd)
print('test - done')
""",
['--runtime-tmpdir=.']) # set runtime-tmpdir to current working dir
@xfail(reason='Issue #3037 - all scripts share the same global vars')
def test_several_scripts1(pyi_builder_spec):
"""Verify each script has it's own global vars (original case, see issue
#2949).
"""
pyi_builder_spec.test_spec('several-scripts1.spec')
@xfail(reason='Issue #3037 - all scripts share the same global vars')
def test_several_scripts2(pyi_builder_spec):
"""
Verify each script has it's own global vars (basic test).
"""
pyi_builder_spec.test_spec('several-scripts2.spec')