瀏覽代碼

Appimage test script

Valentin Niess 7 月之前
父節點
當前提交
fa3ec89228
共有 2 個文件被更改,包括 205 次插入14 次删除
  1. 0 14
      python_appimage/manylinux/extract.py
  2. 205 0
      scripts/test-appimage.py

+ 0 - 14
python_appimage/manylinux/extract.py

@@ -153,13 +153,6 @@ class PythonExtractor:
         (python_dest / 'bin').mkdir(exist_ok=True, parents=True)
         shutil.copy(self.python_prefix / runtime, python_dest / runtime)
 
-        short = Path(python_dest / f'bin/python{self.version.major}')
-        short.unlink(missing_ok=True)
-        short.symlink_to(flavoured_python)
-        short = Path(python_dest / 'bin/python')
-        short.unlink(missing_ok=True)
-        short.symlink_to(f'python{self.version.major}')
-
         # Clone pip wrapper.
         with open(self.python_prefix / pip) as f:
             f.readline() # Skip shebang.
@@ -176,13 +169,6 @@ class PythonExtractor:
             f.write(body)
         shutil.copymode(self.python_prefix / pip, python_dest / pip)
 
-        short = Path(python_dest / f'bin/pip{self.version.major}')
-        short.unlink(missing_ok=True)
-        short.symlink_to(f'pip{self.version.short()}')
-        short = Path(python_dest / 'bin/pip')
-        short.unlink(missing_ok=True)
-        short.symlink_to(f'pip{self.version.major}')
-
         # Clone Python packages.
         for folder in (packages, include):
             shutil.copytree(self.python_prefix / folder, python_dest / folder,

+ 205 - 0
scripts/test-appimage.py

@@ -0,0 +1,205 @@
+#! /usr/bin/env python3
+import argparse
+import inspect
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import tempfile
+from typing import NamedTuple
+
+
+from python_appimage.manylinux import PythonVersion
+
+
+ARGS = None
+
+
+def assert_eq(expected, found):
+    if expected != found:
+        raise AssertionError('expected "{}", found "{}"'.format(
+            expected, found))
+
+
+class Script(NamedTuple):
+    '''Python script wrapper'''
+
+    content: str
+
+    def run(self, appimage: Path):
+        '''Run the script through an appimage'''
+
+        with tempfile.TemporaryDirectory() as tmpdir:
+            script = f'{tmpdir}/script.py'
+            with open(script, 'w') as f:
+                f.write(inspect.getsource(assert_eq))
+                f.write(os.linesep)
+                f.write(self.content)
+            return system(f'{appimage} {script}')
+
+
+def system(cmd):
+    '''Run a system command'''
+
+    r = subprocess.run(cmd, capture_output=True, shell=True)
+
+    if r.returncode != 0:
+        raise ValueError(r.stderr.decode())
+    else:
+        return r.stdout.decode()
+
+
+def test():
+    '''Test Python AppImage(s)'''
+
+    for appimage in ARGS.appimage:
+
+        # Guess python version from appimage name.
+        version, _, abi, *_ = appimage.name.split('-', 3)
+        version = version[6:]
+        if abi.endswith('t'):
+            version += '-nogil'
+        version = PythonVersion.from_str(version)
+
+        # Get some specific AppImage env variables.
+        env = eval(Script('''
+import os
+appdir = os.environ['APPDIR']
+env = {}
+for var in ('SSL_CERT_FILE', 'TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'):
+    env[var] = os.environ[var].replace(appdir, '$APPDIR')
+print(env)
+        ''').run(appimage))
+
+        # Extract the AppImage.
+        tmpdir = tempfile.TemporaryDirectory()
+        dst = Path(tmpdir.name) / appimage.name
+        shutil.copy(appimage, dst)
+        system(f'cd {tmpdir.name} && ./{appimage.name} --appimage-extract')
+        appdir = Path(tmpdir.name) / 'squashfs-root'
+
+        def list_content(path=None):
+            path = appdir if path is None else appdir / path
+            return sorted(os.listdir(path))
+
+        # Check the appimage root content.
+        content = list_content()
+        expected = ['.DirIcon', 'AppRun', 'opt', 'python.png',
+                    f'python{version.long()}.desktop', 'usr']
+        assert_eq(expected, content)
+
+        # Check the appimage python content.
+        prefix = f'opt/python{version.flavoured()}'
+        content = list_content(prefix)
+        assert_eq(['bin', 'include', 'lib'], content)
+        content = list_content(f'{prefix}/bin')
+        assert_eq(
+            [f'pip{version.short()}', f'python{version.flavoured()}'],
+            content
+        )
+        content = list_content(f'{prefix}/include')
+        assert_eq([f'python{version.flavoured()}'], content)
+        content = list_content(f'{prefix}/lib')
+        assert_eq([f'python{version.flavoured()}'], content)
+
+        # Check the appimage system content.
+        content = list_content('usr')
+        assert_eq(['bin', 'lib', 'share'], content)
+        content = list_content('usr/bin')
+        expected = ['pip', f'pip{version.major}', f'pip{version.short()}',
+                    'python', f'python{version.major}',
+                    f'python{version.short()}']
+        assert_eq(expected, content)
+
+        # Check Tcl/Tk bundling.
+        for var in ('TCL_LIBRARY', 'TK_LIBRARY', 'TKPATH'):
+            assert Path(env[var].replace('$APPDIR', str(appdir))).exists()
+
+        # Check SSL certs bundling.
+        var = 'SSL_CERT_FILE'
+        assert Path(env[var].replace('$APPDIR', str(appdir))).exists()
+
+        # Check /usr/bin symlinks.
+        assert_eq(
+            (appdir /
+             f'opt/python{version.flavoured()}/bin/pip{version.short()}'),
+            (appdir / f'usr/bin/pip{version.short()}').resolve()
+        )
+        assert_eq(
+            f'pip{version.short()}',
+            str((appdir / f'usr/bin/pip{version.major}').readlink())
+        )
+        assert_eq(
+            f'pip{version.major}',
+            str((appdir / 'usr/bin/pip').readlink())
+        )
+        assert_eq(
+            f'python{version.short()}',
+            str((appdir / f'usr/bin/python{version.major}').readlink())
+        )
+        assert_eq(
+            f'python{version.major}',
+            str((appdir / 'usr/bin/python').readlink())
+        )
+
+        # Test the appimage hook.
+        Script(f'''
+import os
+assert_eq(os.environ['APPIMAGE_COMMAND'], '{appimage}')
+
+import sys
+assert_eq('{appimage}', sys.executable)
+assert_eq('{appimage}', sys._base_executable)
+        ''').run(appimage)
+
+        # Test the python prefix.
+        Script(f'''
+import os
+import sys
+expected = os.environ["APPDIR"] + '/opt/python{version.flavoured()}'
+assert_eq(expected, sys.prefix)
+        ''').run(appimage)
+
+        # Test SSL (see issue #24).
+        if version.major > 2:
+            Script(f'''
+from http import HTTPStatus
+import urllib.request
+with urllib.request.urlopen('https://wikipedia.org') as r:
+    assert_eq(r.status, HTTPStatus.OK)
+            ''').run(appimage)
+
+        # Test pip installing to an extracted AppImage.
+        r = system(f'{appdir}/AppRun -m pip install pip-install-test')
+        assert('Successfully installed pip-install-test' in r)
+        path = appdir / f'opt/python{version.flavoured()}/lib/python{version.flavoured()}/site-packages/pip_install_test'
+        assert(path.exists())
+
+        # Test tkinter (basic).
+        tkinter = 'tkinter' if version.major > 2 else 'Tkinter'
+        Script(f'''
+import {tkinter} as tkinter
+tkinter.Tk()
+        ''').run(appimage)
+
+        # Test venv.
+        if version.major > 2:
+            system(' && '.join((
+                f'cd {tmpdir.name}',
+                f'./{appimage.name} -m venv ENV',
+                '. ENV/bin/activate',
+            )))
+            python = Path(f'{tmpdir.name}/ENV/bin/python')
+            assert_eq(appimage.name, str(python.readlink()))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(description = test.__doc__)
+    parser.add_argument('appimage',
+        help = 'path to appimage(s)',
+        nargs = '+',
+        type = lambda x: Path(x).absolute()
+    )
+
+    ARGS = parser.parse_args()
+    test()