Kaynağa Gözat

Merge pull request #190 from takluyver/test-installer

Integration test: install and run a program
Thomas Kluyver 5 yıl önce
ebeveyn
işleme
58dfc13d70

+ 4 - 4
.travis.yml

@@ -1,13 +1,13 @@
-# Enable new Travis stack, should speed up builds
-sudo: false
 language: python
 python:
+  - "3.8"
+  - "3.7"
   - "3.6"
   - "3.5"
 
 # Ensure dependencies are installed
 install:
-  - pip install tox-travis
+  - pip install --upgrade tox-travis tox virtualenv
 
 # Command to run tests
-script: tox -e python
+script: tox -e python -- -v

+ 6 - 4
appveyor.yml

@@ -1,15 +1,17 @@
 environment:
   matrix:
     # For Python versions available on Appveyor, see
-    # http://www.appveyor.com/docs/installed-software#python
-    - PYTHON: "C:\\Python35-x64"
+    # https://www.appveyor.com/docs/windows-images-software/#python
+    - PYTHON: "C:\\Python38"
+    - PYTHON: "C:\\Python37-x64"
     - PYTHON: "C:\\Python36"
+    - PYTHON: "C:\\Python35-x64"
 
 install:
   - cinst nsis
-  - "%PYTHON%\\python.exe -m pip install tox --no-warn-script-location"
+  - "%PYTHON%\\python.exe -m pip install -U tox virtualenv --no-warn-script-location"
 
 build: off
 
 test_script:
-  - "%PYTHON%\\python.exe -m tox -e python"
+  - "%PYTHON%\\python.exe -m tox -e python -- -v"

+ 1 - 1
nsist/__init__.py

@@ -302,7 +302,7 @@ if __name__ == '__main__':
                 else:
                     shutil.copy2(sc['script'], self.build_dir)
 
-                target = '$INSTDIR\Python\python{}.exe'
+                target = '$INSTDIR\\Python\\python{}.exe'
                 sc['target'] = target.format('' if sc['console'] else 'w')
                 sc['parameters'] = '"%s"' % ntpath.join('$INSTDIR', sc['script'])
                 files.add(os.path.basename(sc['script']))

+ 16 - 0
nsist/pyapp.nsi

@@ -26,6 +26,7 @@ SetCompressor lzma
 !define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files
 [% endif %]
 !include MultiUser.nsh
+!include FileFunc.nsh
 
 [% block modernui %]
 ; Modern UI installer stuff
@@ -52,6 +53,8 @@ Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
 OutFile "${INSTALLER_NAME}"
 ShowInstDetails show
 
+Var cmdLineInstallDir
+
 Section -SETTINGS
   SetOutPath "$INSTDIR"
   SetOverwrite ifnewer
@@ -217,7 +220,20 @@ Function .onMouseOverSection
 FunctionEnd
 
 Function .onInit
+  ; Multiuser.nsh breaks /D command line parameter. Parse /INSTDIR instead.
+  ; Cribbing from https://nsis-dev.github.io/NSIS-Forums/html/t-299280.html
+  ${GetParameters} $0
+  ClearErrors
+  ${GetOptions} '$0' "/INSTDIR=" $1
+  IfErrors +2  ; Error means flag not found
+    StrCpy $cmdLineInstallDir $1
+  ClearErrors
+
   !insertmacro MULTIUSER_INIT
+
+  ; If cmd line included /INSTDIR, override the install dir set by MultiUser
+  StrCmp $cmdLineInstallDir "" +2
+    StrCpy $INSTDIR $cmdLineInstallDir
 FunctionEnd
 
 Function un.onInit

+ 0 - 28
nsist/tests/console_example/guessnumber.py

@@ -1,28 +0,0 @@
-"""A fun number guessing game!"""
-
-import random
-
-def main():
-    number = random.randint(1, 100)
-    guesses = 0
-
-    print("I'm thinking of a number, between 1 and 100. Can you guess what it is?")
-    while True:
-        guesses += 1
-        guess = input('= ')
-        try:
-            guess = int(guess)
-        except ValueError:
-            print("Base 10 integers only, please.")
-            continue
-
-        if guess > number:
-            print("Too high!")
-        elif guess <  number:
-            print("Too low!")
-        else:
-            print("That's right, {}. You got it in {} guesses.".format(number, guesses))
-            break
-
-    print()
-    input("Press enter to quit.")

+ 5 - 5
nsist/tests/console_example/installer.cfg

@@ -1,7 +1,7 @@
 [Application]
-name=Guess the Number
+name=Sample printer
 version=1.0
-entry_point=guessnumber:main
+entry_point=sample_printer:main
 # We need to set this to get a console:
 console=true
 
@@ -11,9 +11,9 @@ bitness=64
 format=bundled
 
 [Include]
-packages=guessnumber
+packages=sample_printer
 
 # This optional section adds a command which can be run from the Windows
 # command prompt.
-[Command guessnumber]
-entry_point=guessnumber:main
+[Command pynsist-test-print]
+entry_point=sample_printer:main

+ 17 - 0
nsist/tests/console_example/sample_printer/__init__.py

@@ -0,0 +1,17 @@
+import json
+import os.path
+import sys
+
+def main():
+    data_file = os.path.join(os.path.dirname(__file__), 'data.txt')
+    with open(data_file, 'r', encoding='utf-8') as f:
+        data_from_file = f.read().strip()
+
+    info = {
+        'py_executable': sys.executable,
+        'py_version': sys.version,
+        'data_file_path': data_file,
+        'data_file_content': data_from_file,
+    }
+
+    print(json.dumps(info, indent=True))

+ 1 - 0
nsist/tests/console_example/sample_printer/data.txt

@@ -0,0 +1 @@
+newt

+ 103 - 18
nsist/tests/test_main.py

@@ -1,18 +1,27 @@
 import io
+import json
 from pathlib import Path
+import pytest
 import re
 import responses
-from shutil import copy
-from testpath import MockCommand, modified_env, assert_isfile, assert_isdir
-from testpath.tempdir import TemporaryWorkingDirectory
+from shutil import copytree
+from subprocess import run, PIPE
+import sys
+from testpath import MockCommand, assert_isfile, assert_isdir
 from zipfile import ZipFile
 
 from nsist import main
 from nsist.util import CACHE_ENV_VAR
-from .utils import test_dir
+from .utils import test_dir, only_on_windows
 
 example_dir = Path(test_dir, 'console_example')
 
+@pytest.fixture()
+def console_eg_copy(tmp_path):
+    dst = tmp_path / 'example'
+    copytree(str(example_dir), str(dst))
+    return dst
+
 def respond_python_zip(req):
     buf = io.BytesIO()
     with ZipFile(buf, 'w') as zf:
@@ -20,25 +29,101 @@ def respond_python_zip(req):
     return 200, {}, buf.getvalue()
 
 @responses.activate
-def test_console_example():
+def test_console_example(tmp_path, console_eg_copy, monkeypatch):
     responses.add_callback('GET', re.compile(r'https://www.python.org/ftp/.*'),
         callback=respond_python_zip, content_type='application/zip',
     )
 
-    with TemporaryWorkingDirectory() as td:
-        for src in example_dir.iterdir():
-            copy(str(src), td)
+    monkeypatch.chdir(console_eg_copy)
+    monkeypatch.setenv(CACHE_ENV_VAR, str(tmp_path / 'cache'))
+
+    with MockCommand('makensis') as makensis:
+        ec = main(['installer.cfg'])
+
+    assert ec == 0
+    assert makensis.get_calls()[0]['argv'][1].endswith('installer.nsi')
+
+    build_dir = console_eg_copy / 'build' / 'nsis'
+    assert_isdir(build_dir)
+    assert_isfile(build_dir / 'Python' / 'python.exe')
+    assert_isfile(build_dir / 'pkgs' / 'sample_printer' / '__init__.py')
+    assert_isfile(build_dir / 'Sample_printer.launch.py')
+
+# To exclude tests requiring network on an unplugged machine, use: pytest -m "not network"
+
+@only_on_windows
+@pytest.mark.network
+def test_installer(console_eg_copy, tmp_path):
+    # Create installer
+    run(
+        [sys.executable, '-m', 'nsist', 'installer.cfg'],
+        cwd=str(console_eg_copy),
+        check=True,
+    )
+
+    installer = console_eg_copy / 'build' / 'nsis' / 'Sample_printer_1.0.exe'
+    inst_dir = tmp_path / 'inst'
+
+    # Run installer
+    run([str(installer), '/S', '/INSTDIR={}'.format(inst_dir)], check=True)
+    import os
+    print(os.listdir(str(inst_dir)))
+    inst_python = inst_dir / 'Python' / 'python.exe'
+    inst_launch_script = inst_dir / 'Sample_printer.launch.py'
+    inst_exe_wrapper = inst_dir / 'bin' / 'pynsist-test-print.exe'
+
+    assert_isfile(inst_python)
+    assert_isfile(inst_launch_script)
+    assert_isfile(inst_exe_wrapper)
+    assert_isfile(inst_dir / 'pkgs' / 'sample_printer' / '__init__.py')
+
+
+    # Run installed program
+    res = run([str(inst_python), str(inst_launch_script)],
+              check=True, stdout=PIPE)
+    json_res = json.loads(res.stdout.decode('utf-8', 'replace'))
+
+    assert json_res['py_executable'] == str(inst_python)
+    assert json_res['py_version'].startswith('3.6.3')  # Set in installer.cfg
+    assert json_res['data_file_path'].endswith('data.txt')
+    assert json_res['data_file_content'] == 'newt'
+
+    # Run through command-line wrapper
+    res2 = run([str(inst_exe_wrapper)], check=True, stdout=PIPE)
+    json_res2 = json.loads(res2.stdout.decode('utf-8', 'replace'))
+    assert json_res2 == json_res
+
+    # Check command-line wrapper is on PATH
+    assert str(inst_exe_wrapper.parent) in get_registry_path()
+
+
+def get_registry_path():
+    """Read the PATH environment variable from the Windows registry"""
+    import winreg
 
+    res = []
 
-        with modified_env({CACHE_ENV_VAR: td}), \
-             MockCommand('makensis') as makensis:
-            ec = main(['installer.cfg'])
+    # System-wide part of PATH
+    with winreg.CreateKey(
+        winreg.HKEY_LOCAL_MACHINE,
+        r"System\CurrentControlSet\Control\Session Manager\Environment"
+    ) as key:
+        try:
+            path = winreg.QueryValueEx(key, "PATH")[0]
+        except WindowsError:
+            # No value called PATH
+            pass
+        else:
+            res.extend(path.split(';'))
 
-        assert ec == 0
-        assert makensis.get_calls()[0]['argv'][1].endswith('installer.nsi')
+    # User part of PATH
+    with winreg.CreateKey(winreg.HKEY_CURRENT_USER, r"Environment") as key:
+        try:
+            path = winreg.QueryValueEx(key, "PATH")[0]
+        except WindowsError:
+            # No value called PATH
+            pass
+        else:
+            res.extend(path.split(';'))
 
-        build_dir = Path(td, 'build', 'nsis')
-        assert_isdir(build_dir)
-        assert_isfile(build_dir / 'Python' / 'python.exe')
-        assert_isfile(build_dir / 'pkgs' / 'guessnumber.py')
-        assert_isfile(build_dir / 'Guess_the_Number.launch.py')
+    return res

+ 2 - 0
pytest.ini

@@ -1,2 +1,4 @@
 [pytest]
 norecursedirs = .git examples doc
+markers =
+    network: tests requiring network access (deselect with '-m "not network"')

+ 3 - 3
tox.ini

@@ -1,5 +1,5 @@
 [tox]
-skipsdist = true
+isolated_build = True
 envlist = python
 
 [testenv]
@@ -11,7 +11,7 @@ deps = pytest
        yarg
        testpath
        responses
-commands = pytest nsist/tests
+commands = pytest nsist/tests {posargs}
 
 [testenv:notnetwork]
-commands = pytest -m "not network" nsist/tests
+commands = pytest -m "not network" nsist/tests {posargs}