1
0

test-appimage.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. #! /usr/bin/env python3
  2. import argparse
  3. import inspect
  4. import os
  5. from pathlib import Path
  6. import shutil
  7. import subprocess
  8. import tempfile
  9. from typing import NamedTuple
  10. from python_appimage.manylinux import PythonVersion
  11. ARGS = None
  12. def assert_eq(expected, found):
  13. if expected != found:
  14. raise AssertionError('expected "{}", found "{}"'.format(
  15. expected, found))
  16. class Script(NamedTuple):
  17. '''Python script wrapper'''
  18. content: str
  19. def run(self, appimage: Path):
  20. '''Run the script through an appimage'''
  21. with tempfile.TemporaryDirectory() as tmpdir:
  22. script = f'{tmpdir}/script.py'
  23. with open(script, 'w') as f:
  24. f.write(inspect.getsource(assert_eq))
  25. f.write(os.linesep)
  26. f.write(self.content)
  27. return system(f'{appimage} {script}')
  28. def system(cmd):
  29. '''Run a system command'''
  30. r = subprocess.run(cmd, capture_output=True, shell=True)
  31. if r.returncode != 0:
  32. raise ValueError(r.stderr.decode())
  33. else:
  34. return r.stdout.decode()
  35. def test():
  36. '''Test Python AppImage(s)'''
  37. for appimage in ARGS.appimage:
  38. # Guess python version from appimage name.
  39. version, _, abi, *_ = appimage.name.split('-', 3)
  40. version = version[6:]
  41. if abi.endswith('t'):
  42. version += '-nogil'
  43. version = PythonVersion.from_str(version)
  44. # Get some specific AppImage env variables.
  45. env = eval(Script('''
  46. import os
  47. appdir = os.environ['APPDIR']
  48. env = {}
  49. for var in ('SSL_CERT_FILE', 'TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'):
  50. env[var] = os.environ[var].replace(appdir, '$APPDIR')
  51. print(env)
  52. ''').run(appimage))
  53. # Extract the AppImage.
  54. tmpdir = tempfile.TemporaryDirectory()
  55. dst = Path(tmpdir.name) / appimage.name
  56. shutil.copy(appimage, dst)
  57. system(f'cd {tmpdir.name} && ./{appimage.name} --appimage-extract')
  58. appdir = Path(tmpdir.name) / 'squashfs-root'
  59. def list_content(path=None):
  60. path = appdir if path is None else appdir / path
  61. return sorted(os.listdir(path))
  62. # Check the appimage root content.
  63. content = list_content()
  64. expected = ['.DirIcon', 'AppRun', 'opt', 'python.png',
  65. f'python{version.long()}.desktop', 'usr']
  66. assert_eq(expected, content)
  67. # Check the appimage python content.
  68. prefix = f'opt/python{version.flavoured()}'
  69. content = list_content(prefix)
  70. assert_eq(['bin', 'include', 'lib'], content)
  71. content = list_content(f'{prefix}/bin')
  72. assert_eq(
  73. [f'pip{version.short()}', f'python{version.flavoured()}'],
  74. content
  75. )
  76. content = list_content(f'{prefix}/include')
  77. assert_eq([f'python{version.flavoured()}'], content)
  78. content = list_content(f'{prefix}/lib')
  79. assert_eq([f'python{version.flavoured()}'], content)
  80. # Check the appimage system content.
  81. content = list_content('usr')
  82. assert_eq(['bin', 'lib', 'share'], content)
  83. content = list_content('usr/bin')
  84. expected = ['pip', f'pip{version.major}', f'pip{version.short()}',
  85. 'python', f'python{version.major}',
  86. f'python{version.short()}']
  87. assert_eq(expected, content)
  88. # Check Tcl/Tk bundling.
  89. for var in ('TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'):
  90. assert Path(env[var].replace('$APPDIR', str(appdir))).exists()
  91. # Check SSL certs bundling.
  92. var = 'SSL_CERT_FILE'
  93. assert Path(env[var].replace('$APPDIR', str(appdir))).exists()
  94. # Check /usr/bin symlinks.
  95. assert_eq(
  96. (appdir /
  97. f'opt/python{version.flavoured()}/bin/pip{version.short()}'),
  98. (appdir / f'usr/bin/pip{version.short()}').resolve()
  99. )
  100. assert_eq(
  101. f'pip{version.short()}',
  102. str((appdir / f'usr/bin/pip{version.major}').readlink())
  103. )
  104. assert_eq(
  105. f'pip{version.major}',
  106. str((appdir / 'usr/bin/pip').readlink())
  107. )
  108. assert_eq(
  109. f'python{version.short()}',
  110. str((appdir / f'usr/bin/python{version.major}').readlink())
  111. )
  112. assert_eq(
  113. f'python{version.major}',
  114. str((appdir / 'usr/bin/python').readlink())
  115. )
  116. # Test the appimage hook.
  117. Script(f'''
  118. import os
  119. assert_eq(os.environ['APPIMAGE_COMMAND'], '{appimage}')
  120. import sys
  121. assert_eq('{appimage}', sys.executable)
  122. assert_eq('{appimage}', sys._base_executable)
  123. ''').run(appimage)
  124. # Test the python prefix.
  125. Script(f'''
  126. import os
  127. import sys
  128. expected = os.environ["APPDIR"] + '/opt/python{version.flavoured()}'
  129. assert_eq(expected, sys.prefix)
  130. ''').run(appimage)
  131. # Test SSL (see issue #24).
  132. if version.major > 2:
  133. Script('''
  134. from http import HTTPStatus
  135. import urllib.request
  136. with urllib.request.urlopen('https://wikipedia.org') as r:
  137. assert_eq(r.status, HTTPStatus.OK)
  138. ''').run(appimage)
  139. # Test pip installing to an extracted AppImage.
  140. r = system(f'{appdir}/AppRun -m pip install pip-install-test')
  141. assert('Successfully installed pip-install-test' in r)
  142. path = appdir / f'opt/python{version.flavoured()}/lib/python{version.flavoured()}/site-packages/pip_install_test'
  143. assert(path.exists())
  144. # Test tkinter (basic).
  145. tkinter = 'tkinter' if version.major > 2 else 'Tkinter'
  146. Script(f'''
  147. import {tkinter} as tkinter
  148. tkinter.Tk()
  149. ''').run(appimage)
  150. # Test venv.
  151. if version.major > 2:
  152. system(' && '.join((
  153. f'cd {tmpdir.name}',
  154. f'./{appimage.name} -m venv ENV',
  155. '. ENV/bin/activate',
  156. )))
  157. python = Path(f'{tmpdir.name}/ENV/bin/python')
  158. assert_eq(appimage.name, str(python.readlink()))
  159. if __name__ == '__main__':
  160. parser = argparse.ArgumentParser(description = test.__doc__)
  161. parser.add_argument('appimage',
  162. help = 'path to appimage(s)',
  163. nargs = '+',
  164. type = lambda x: Path(x).absolute()
  165. )
  166. ARGS = parser.parse_args()
  167. test()