Prechádzať zdrojové kódy

Merge branch 'tox_pytest'

Thomas Kluyver 6 rokov pred
rodič
commit
8e1a937aef

+ 2 - 1
.gitignore

@@ -4,7 +4,8 @@ build/
 MANIFEST
 dist/
 doc/_build
-pynsist_pkgs/
+pynsist_pkgs
+.tox
 
 # This is a big file, so let it sit here after download
 examples/pygi_mpl_numpy/pygi.exe

+ 6 - 5
.travis.yml

@@ -1,12 +1,13 @@
+# Enable new Travis stack, should speed up builds
+sudo: false
 language: python
 python:
   - "3.6"
   - "3.5"
-# command to run tests
-script: nosetests
+
 # Ensure dependencies are installed
 install:
-  - pip install requests requests_download jinja2 yarg win_cli_launchers testpath
+  - pip install tox-travis
 
-# Enable new Travis stack, should speed up builds
-sudo: false
+# Command to run tests
+script: tox -e python

+ 2 - 2
appveyor.yml

@@ -7,9 +7,9 @@ environment:
 
 install:
   - cinst nsis
-  - "%PYTHON%\\python.exe -m pip install requests requests_download jinja2 yarg nose win_cli_launchers testpath"
+  - "%PYTHON%\\python.exe -m pip install tox --no-warn-script-location"
 
 build: off
 
 test_script:
-  - "%PYTHON%\\python.exe -m nose"
+  - "%PYTHON%\\python.exe -m tox -e python"

+ 17 - 22
nsist/tests/test_commands.py

@@ -1,33 +1,28 @@
 import io
-from nose.tools import *
+
 try:
     from pathlib import Path
 except ImportError:
     from pathlib2 import Path  # Backport
-from testpath.tempdir import TemporaryDirectory
-from testpath import assert_isfile
 
 from nsist import commands, _rewrite_shebangs
+from .utils import assert_is_file
 
-cmds = {'acommand': {
-                'entry_point': 'somemod:somefunc',
-                'extra_preamble': io.StringIO(u'import extra')
-           }}
+cmds = {'acommand': {'entry_point': 'somemod:somefunc',
+                     'extra_preamble': io.StringIO(u'import extra')}}
 
-def test_prepare_bin_dir():
-    with TemporaryDirectory() as td:
-        td = Path(td)
-        commands.prepare_bin_directory(td, cmds)
-        assert_isfile(str(td / 'acommand.exe'))
-        script_file = td / 'acommand-script.py'
-        assert_isfile(str(script_file))
+def test_prepare_bin_dir(tmpdir):
+    commands.prepare_bin_directory(tmpdir, cmds)
+    assert_is_file(str(tmpdir / 'acommand.exe'))
+    script_file = tmpdir / 'acommand-script.py'
+    assert_is_file(str(script_file))
 
-        with script_file.open() as f:
-            script_contents = f.read()
-        assert script_contents.startswith("#!python")
-        assert_in('import extra', script_contents)
-        assert_in('somefunc()', script_contents)
+    with script_file.open() as f:
+        script_contents = f.read()
+    assert script_contents.startswith("#!python")
+    assert 'import extra' in script_contents
+    assert 'somefunc()' in script_contents
 
-        _rewrite_shebangs.main(['_rewrite_shebangs.py', str(td)])
-        with script_file.open() as f:
-            assert f.read().startswith('#!"')
+    _rewrite_shebangs.main(['_rewrite_shebangs.py', str(tmpdir)])
+    with script_file.open() as f:
+        assert f.read().startswith('#!"')

+ 17 - 17
nsist/tests/test_configuration_validator.py

@@ -1,7 +1,7 @@
 import configparser
 import os
 
-from nose.tools import *
+import pytest
 
 from .. import configreader
 
@@ -17,7 +17,7 @@ def test_valid_config():
 
 def test_valid_config_with_shortcut():
     configfile = os.path.join(DATA_FILES, 'valid_config_with_shortcut.cfg')
-    config = configreader.read_and_validate(configfile)
+    configreader.read_and_validate(configfile)
 
 def test_valid_config_with_values_starting_on_new_line():
     configfile = os.path.join(DATA_FILES, 'valid_config_value_newline.cfg')
@@ -43,7 +43,7 @@ def test_valid_config_with_values_starting_on_new_line():
 
     assert args['py_version'] == '3.6.0'
     assert args['py_bitness'] == 64
-    assert args['inc_msvcrt'] == True
+    assert args['inc_msvcrt'] is True
 
     assert args['build_dir'] == 'build/'
     assert args['nsi_template'] == 'template.nsi'
@@ -52,25 +52,25 @@ def test_valid_config_with_values_starting_on_new_line():
     assert args['pypi_wheel_reqs'] == ['html5lib']
     assert args['exclude'] == ['something']
     assert args['extra_files'] == [('', '$INSTDIR'),
-                                    ('LICENSE', '$INSTDIR'),
-                                    ('data_files/', '$INSTDIR')]
+                                   ('LICENSE', '$INSTDIR'),
+                                   ('data_files/', '$INSTDIR')]
 
-@raises(configreader.InvalidConfig)
 def test_invalid_config_keys():
-    configfile = os.path.join(DATA_FILES, 'invalid_config_section.cfg')
-    configreader.read_and_validate(configfile)
+    with pytest.raises(configreader.InvalidConfig):
+        configfile = os.path.join(DATA_FILES, 'invalid_config_section.cfg')
+        configreader.read_and_validate(configfile)
 
-@raises(configreader.InvalidConfig)
 def test_invalid_config_key():
-    configfile = os.path.join(DATA_FILES, 'invalid_config_key.cfg')
-    configreader.read_and_validate(configfile)
+    with pytest.raises(configreader.InvalidConfig):
+        configfile = os.path.join(DATA_FILES, 'invalid_config_key.cfg')
+        configreader.read_and_validate(configfile)
 
-@raises(configreader.InvalidConfig)
 def test_missing_config_key():
-    configfile = os.path.join(DATA_FILES, 'missing_config_key.cfg')
-    configreader.read_and_validate(configfile)
+    with pytest.raises(configreader.InvalidConfig):
+        configfile = os.path.join(DATA_FILES, 'missing_config_key.cfg')
+        configreader.read_and_validate(configfile)
 
-@raises(configparser.Error)
 def test_invalid_config_file():
-    configfile = os.path.join(DATA_FILES, 'not_a_config.cfg')
-    configreader.read_and_validate(configfile)
+    with pytest.raises(configparser.Error):
+        configfile = os.path.join(DATA_FILES, 'not_a_config.cfg')
+        configreader.read_and_validate(configfile)

+ 47 - 51
nsist/tests/test_copymodules.py

@@ -1,13 +1,12 @@
 import os
-import shutil
 import sys
-import tempfile
-import unittest
 
-pjoin = os.path.join
+import pytest
 
-from .utils import assert_is_file, assert_is_dir, test_dir
+from nsist.copymodules import copy_modules, ExtensionModuleMismatch
+from .utils import assert_is_file, assert_is_dir, test_dir, skip_on_windows, only_on_windows
 
+pjoin = os.path.join
 running_python = '.'.join(str(x) for x in sys.version_info[:3])
 
 sample_path = [pjoin(test_dir, 'sample_pkgs'),
@@ -15,51 +14,48 @@ sample_path = [pjoin(test_dir, 'sample_pkgs'),
                pjoin(test_dir, 'sample_zip.egg/rootdir'),
               ]
 
-from nsist.copymodules import copy_modules, ExtensionModuleMismatch
 
+def test_copy_plain(tmpdir):
+    tmpdir = str(tmpdir)
+    copy_modules(['plainmod', 'plainpkg'], tmpdir, '3.3.5', sample_path)
+    assert_is_file(pjoin(tmpdir, 'plainmod.py'))
+    assert_is_dir(pjoin(tmpdir, 'plainpkg'))
+
+@skip_on_windows
+def test_copy_wrong_platform(tmpdir):
+    tmpdir = str(tmpdir)
+    with pytest.raises(ExtensionModuleMismatch, match="will not be usable on Windows"):
+        copy_modules(['unix_extmod'], tmpdir, '3.3.5', sample_path)
+
+    with pytest.raises(ExtensionModuleMismatch, match="will not be usable on Windows"):
+        copy_modules(['unix_extpkg'], tmpdir, '3.3.5', sample_path)
+
+@only_on_windows
+def test_copy_windows(tmpdir):
+    tmpdir = str(tmpdir)
+    copy_modules(['win_extmod', 'win_extpkg'], tmpdir, running_python, sample_path)
+    assert_is_file(pjoin(tmpdir, 'win_extmod.pyd'))
+    assert_is_dir(pjoin(tmpdir, 'win_extpkg'))
+
+@only_on_windows
+def test_copy_wrong_pyversion(tmpdir):
+    tmpdir = str(tmpdir)
+    with pytest.raises(ExtensionModuleMismatch, match="on Python 4"):
+        copy_modules(['win_extpkg'], tmpdir, '4.0.0', sample_path)
+
+    with pytest.raises(ExtensionModuleMismatch, match="on Python 4"):
+        copy_modules(['win_extmod'], tmpdir, '4.0.0', sample_path)
+
+def test_copy_from_zipfile(tmpdir):
+    tmpdir = str(tmpdir)
+    copy_modules(['zippedmod2', 'zippedpkg2'],
+                 tmpdir, running_python, sample_path)
+#        assert_is_file(pjoin(tmpdir, 'zippedmod.py'))
+#        assert_is_dir(pjoin(tmpdir, 'zippedpkg'))
+    assert_is_file(pjoin(tmpdir, 'zippedmod2.py'))
+    assert_is_dir(pjoin(tmpdir, 'zippedpkg2'))
 
-class TestCopyModules(unittest.TestCase):
-    def setUp(self):
-        self.target = tempfile.mkdtemp()
-    
-    def tearDown(self):
-        shutil.rmtree(self.target)
-    
-    def test_copy_plain(self):
-        copy_modules(['plainmod', 'plainpkg'], self.target, '3.3.5', sample_path)
-        assert_is_file(pjoin(self.target, 'plainmod.py'))
-        assert_is_dir(pjoin(self.target, 'plainpkg'))
-    
-    @unittest.skipIf(sys.platform.startswith("win"), "test for non-Windows platforms")
-    def test_copy_wrong_platform(self):
-        with self.assertRaisesRegexp(ExtensionModuleMismatch, "will not be usable on Windows"):
-            copy_modules(['unix_extmod'], self.target, '3.3.5', sample_path)
-        
-        with self.assertRaisesRegexp(ExtensionModuleMismatch, "will not be usable on Windows"):
-            copy_modules(['unix_extpkg'], self.target, '3.3.5', sample_path)
-    
-    @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
-    def test_copy_windows(self):
-        copy_modules(['win_extmod', 'win_extpkg'], self.target, running_python, sample_path)
-        assert_is_file(pjoin(self.target, 'win_extmod.pyd'))
-        assert_is_dir(pjoin(self.target, 'win_extpkg'))
-        
-    @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
-    def test_copy_wrong_pyversion(self):
-        with self.assertRaisesRegexp(ExtensionModuleMismatch, "on Python 4"):
-            copy_modules(['win_extpkg'], self.target, '4.0.0', sample_path)
-        
-        with self.assertRaisesRegexp(ExtensionModuleMismatch, "on Python 4"):
-            copy_modules(['win_extmod'], self.target, '4.0.0', sample_path)
-    
-    def test_copy_from_zipfile(self):
-        copy_modules(['zippedmod2','zippedpkg2'],
-                     self.target, running_python, sample_path)
-#        assert_is_file(pjoin(self.target, 'zippedmod.py'))
-#        assert_is_dir(pjoin(self.target, 'zippedpkg'))
-        assert_is_file(pjoin(self.target, 'zippedmod2.py'))
-        assert_is_dir(pjoin(self.target, 'zippedpkg2'))
-    
-    def test_module_not_found(self):
-        with self.assertRaises(ImportError):
-            copy_modules(['nonexistant'], self.target, '3.3.5', sample_path)
+def test_module_not_found(tmpdir):
+    tmpdir = str(tmpdir)
+    with pytest.raises(ImportError):
+        copy_modules(['nonexistant'], tmpdir, '3.3.5', sample_path)

+ 25 - 34
nsist/tests/test_installerbuilder.py

@@ -1,41 +1,32 @@
 import io
+
 from os.path import join as pjoin
-import shutil
-import tempfile
-import unittest
 
-from .utils import assert_is_file, test_dir
 from nsist import InstallerBuilder, DEFAULT_ICON
+from .utils import assert_is_file, test_dir
+
 
 sample_preamble = pjoin(test_dir, u'sample_preamble.py')
 
-class TestInstallerBuilder(unittest.TestCase):
-    def setUp(self):
-        self.target = tempfile.mkdtemp()
-    
-    def tearDown(self):
-        shutil.rmtree(self.target)
-    
-    def test_prepare_shortcuts(self):
-        shortcuts = {'sc1': {'entry_point': 'norwegian.blue:parrot',
-                             'icon': DEFAULT_ICON,
-                             'console': False,
-                             'extra_preamble': sample_preamble,
-                             }
-                    }
-        ib = InstallerBuilder("Test App", "1.0", shortcuts, build_dir=self.target)
-        ib.prepare_shortcuts()
-        
-        scfile = pjoin(self.target, 'sc1.launch.pyw')
-        assert_is_file(scfile)
-        
-        with io.open(scfile, 'r', encoding='utf-8') as f:
-            contents = f.read()
-        
-        last2lines = [l.strip() for l in contents.rstrip().splitlines()[-2:]]
-        assert last2lines == ['from norwegian.blue import parrot', 'parrot()']
-        
-        with io.open(sample_preamble, 'r', encoding='utf-8') as f:
-            preamble_contents = f.read().strip()
-        
-        assert preamble_contents in contents
+def test_prepare_shortcuts(tmpdir):
+    tmpdir = str(tmpdir)
+    shortcuts = {'sc1': {'entry_point': 'norwegian.blue:parrot',
+                         'icon': DEFAULT_ICON,
+                         'console': False,
+                         'extra_preamble': sample_preamble}}
+    ib = InstallerBuilder("Test App", "1.0", shortcuts, build_dir=tmpdir)
+    ib.prepare_shortcuts()
+
+    scfile = pjoin(tmpdir, 'sc1.launch.pyw')
+    assert_is_file(scfile)
+
+    with io.open(scfile, 'r', encoding='utf-8') as f:
+        contents = f.read()
+
+    last2lines = [l.strip() for l in contents.rstrip().splitlines()[-2:]]
+    assert last2lines == ['from norwegian.blue import parrot', 'parrot()']
+
+    with io.open(sample_preamble, 'r', encoding='utf-8') as f:
+        preamble_contents = f.read().strip()
+
+    assert preamble_contents in contents

+ 55 - 48
nsist/tests/test_local_wheels.py

@@ -1,68 +1,75 @@
-import unittest
+import glob
 import os
 import platform
 import subprocess
-import glob
 
-from testpath.tempdir import TemporaryDirectory
-from testpath import assert_isfile, assert_isdir
+import pytest
+
 from nsist.pypi import fetch_pypi_wheels
+from .utils import assert_is_dir, assert_is_file
+
+# To exclude tests requiring network on an unplugged machine, use: pytest -m "not network"
+
+@pytest.mark.network
+def test_matching_one_pattern(tmpdir):
+    td1 = str(tmpdir.mkdir('wheels'))
+    td2 = str(tmpdir.mkdir('pkgs'))
+
+    subprocess.call(['pip', 'wheel', 'requests==2.19.1', '-w', str(td1)])
+
+    fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
+
+    assert_is_dir(os.path.join(td2, 'requests'))
+    assert_is_file(os.path.join(td2, 'requests-2.19.1.dist-info', 'METADATA'))
 
-class TestLocalWheels(unittest.TestCase):
-    def test_matching_one_pattern(self):
-        with TemporaryDirectory() as td1:
-            subprocess.call(['pip', 'wheel', 'requests==2.19.1', '-w', td1])
+    assert_is_dir(os.path.join(td2, 'urllib3'))
+    assert glob.glob(os.path.join(td2, 'urllib3*.dist-info'))
 
-            with TemporaryDirectory() as td2:
-                fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
+@pytest.mark.network
+def test_duplicate_wheel_files_raise(tmpdir):
+    td1 = str(tmpdir.mkdir('wheels'))
+    td2 = str(tmpdir.mkdir('pkgs'))
 
-                assert_isdir(os.path.join(td2, 'requests'))
-                assert_isfile(os.path.join(td2, 'requests-2.19.1.dist-info', 'METADATA'))
+    subprocess.call(['pip', 'wheel', 'requests==2.19.1', '-w', str(td1)])
 
-                assert_isdir(os.path.join(td2, 'urllib3'))
-                self.assertTrue(glob.glob(os.path.join(td2, 'urllib3*.dist-info')))
+    with pytest.raises(ValueError, match='wheel distribution requests already included'):
+        fetch_pypi_wheels(['requests==2.19.1'],
+                          [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
 
-    def test_duplicate_wheel_files_raise(self):
-        with TemporaryDirectory() as td1:
-            subprocess.call(['pip', 'wheel', 'requests==2.19.1', '-w', td1])
+def test_invalid_wheel_file_raise(tmpdir):
+    td1 = str(tmpdir.mkdir('wheels'))
+    td2 = str(tmpdir.mkdir('pkgs'))
 
-            with TemporaryDirectory() as td2:
-                with self.assertRaisesRegex(ValueError, 'wheel distribution requests already included'):
-                    fetch_pypi_wheels(['requests==2.19.1'], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
+    open(os.path.join(td1, 'notawheel.txt'), 'w+')
 
-    def test_invalid_wheel_file_raise(self):
-        with TemporaryDirectory() as td1:
-            open(os.path.join(td1, 'notawheel.txt'), 'w+')
+    with pytest.raises(ValueError, match='Invalid wheel file name: notawheel.txt'):
+        fetch_pypi_wheels([], [os.path.join(td1, '*')], td2, platform.python_version(), 64)
 
-            with TemporaryDirectory() as td2:
-                with self.assertRaisesRegex(ValueError, 'Invalid wheel file name: notawheel.txt'):
-                    fetch_pypi_wheels([], [os.path.join(td1, '*')], td2, platform.python_version(), 64)
+def test_incompatible_plateform_wheel_file_raise(tmpdir):
+    td1 = str(tmpdir.mkdir('wheels'))
+    td2 = str(tmpdir.mkdir('pkgs'))
 
-    def test_incompatible_plateform_wheel_file_raise(self):
-        with TemporaryDirectory() as td1:
-            open(os.path.join(td1, 'incompatiblewheel-1.0.0-py2.py3-none-linux_x86_64.whl'), 'w+')
+    open(os.path.join(td1, 'incompatiblewheel-1.0.0-py2.py3-none-linux_x86_64.whl'), 'w+')
 
-            with TemporaryDirectory() as td2:
-                with self.assertRaisesRegex(ValueError, '{0} is not compatible with Python {1} for Windows'
-                .format('incompatiblewheel-1.0.0-py2.py3-none-linux_x86_64.whl', platform.python_version())):
-                    fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
+    with pytest.raises(ValueError, match='{0} is not compatible with Python {1} for Windows'
+                       .format('incompatiblewheel-1.0.0-py2.py3-none-linux_x86_64.whl',
+                               platform.python_version())):
+        fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
 
-    def test_incompatible_python_wheel_file_raise(self):
-        with TemporaryDirectory() as td1:
-            open(os.path.join(td1, 'incompatiblewheel-1.0.0-py26-none-any.whl'), 'w+')
+def test_incompatible_python_wheel_file_raise(tmpdir):
+    td1 = str(tmpdir.mkdir('wheels'))
+    td2 = str(tmpdir.mkdir('pkgs'))
 
-            with TemporaryDirectory() as td2:
-                with self.assertRaisesRegex(ValueError, '{0} is not compatible with Python {1} for Windows'
-                .format('incompatiblewheel-1.0.0-py26-none-any.whl', platform.python_version())):
-                    fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
+    open(os.path.join(td1, 'incompatiblewheel-1.0.0-py26-none-any.whl'), 'w+')
 
-    def test_useless_wheel_glob_path_raise(self):
-        with TemporaryDirectory() as td1:
-            with TemporaryDirectory() as td2:
-                with self.assertRaisesRegex(ValueError, 'does not match any wheel file'):
-                    fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
+    with pytest.raises(ValueError, match='{0} is not compatible with Python {1} for Windows'
+                       .format('incompatiblewheel-1.0.0-py26-none-any.whl',
+                               platform.python_version())):
+        fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)
 
+def test_useless_wheel_glob_path_raise(tmpdir):
+    td1 = str(tmpdir.mkdir('wheels'))
+    td2 = str(tmpdir.mkdir('pkgs'))
 
-# To exclude these, run:  nosetests -a '!network'
-TestLocalWheels.test_matching_one_pattern.network = 1
-TestLocalWheels.test_duplicate_wheel_files_raise.network = 1
+    with pytest.raises(ValueError, match='does not match any wheel file'):
+        fetch_pypi_wheels([], [os.path.join(td1, '*.whl')], td2, platform.python_version(), 64)

+ 65 - 68
nsist/tests/test_pypi.py

@@ -1,57 +1,53 @@
-from nose.tools import *
 from os.path import join as pjoin
 from pathlib import Path
-from testpath import assert_isfile, assert_isdir
-from testpath.tempdir import TemporaryDirectory
+import pytest
 
 from nsist.pypi import (
     WheelLocator, extract_wheel, CachedRelease, merge_dir_to, NoWheelError,
 )
+from .utils import assert_is_file
 
-def test_download():
+# To exclude tests requiring network on an unplugged machine, use: pytest -m "not network"
+
+@pytest.mark.network
+def test_download(tmpdir):
+    tmpdir = str(tmpdir)
     wd = WheelLocator("astsearch==0.1.2", "3.5.1", 64)
     wheel = wd.fetch()
-    assert_isfile(str(wheel))
+    assert_is_file(str(wheel))
 
-    with TemporaryDirectory() as td:
-        extract_wheel(wheel, target_dir=td)
-        assert_isfile(pjoin(td, 'astsearch.py'))
-        assert_isfile(pjoin(td, 'astsearch-0.1.2.dist-info', 'METADATA'))
+    extract_wheel(wheel, target_dir=tmpdir)
+    assert_is_file(pjoin(tmpdir, 'astsearch.py'))
+    assert_is_file(pjoin(tmpdir, 'astsearch-0.1.2.dist-info', 'METADATA'))
 
+@pytest.mark.network
 def test_bad_name():
     # Packages can't be named after stdlib modules like os
     wl = WheelLocator("os==1.0", "3.5.1", 64)
-    with assert_raises(NoWheelError):
+    with pytest.raises(NoWheelError):
         wl.get_from_pypi()
 
+@pytest.mark.network
 def test_bad_version():
     wl = WheelLocator("pynsist==0.99.99", "3.5.1", 64)
-    with assert_raises(NoWheelError):
+    with pytest.raises(NoWheelError):
         wl.get_from_pypi()
 
-# To exclude these, run:  nosetests -a '!network'
-test_download.network = 1
-test_bad_name.network = 1
-test_bad_version.network = 1
-
-def test_extra_sources():
-    with TemporaryDirectory() as td:
-        src1 = Path(td, 'src1')
-        src1.mkdir()
-        src2 = Path(td, 'src2')
-        src2.mkdir()
-
-        # First one found wins, even if a later one is more specific.
-        expected = (src1 / 'astsearch-0.1.2-py3-none-any.whl')
-        expected.touch()
-        (src2 / 'astsearch-0.1.2-py3-none-win_amd64.whl').touch()
-        wl = WheelLocator("astsearch==0.1.2", "3.5.1", 64,
-                          extra_sources=[src1, src2])
-        assert_equal(wl.check_extra_sources(), expected)
-
-        wl = WheelLocator("astsearch==0.2.0", "3.5.1", 64,
-                          extra_sources=[src1, src2])
-        assert_is(wl.check_extra_sources(), None)
+def test_extra_sources(tmpdir):
+    src1 = Path(str(tmpdir.mkdir('src1')))
+    src2 = Path(str(tmpdir.mkdir('src2')))
+
+    # First one found wins, even if a later one is more specific.
+    expected = (src1 / 'astsearch-0.1.2-py3-none-any.whl')
+    expected.touch()
+    (src2 / 'astsearch-0.1.2-py3-none-win_amd64.whl').touch()
+    wl = WheelLocator("astsearch==0.1.2", "3.5.1", 64,
+                      extra_sources=[src1, src2])
+    assert wl.check_extra_sources() == expected
+
+    wl = WheelLocator("astsearch==0.2.0", "3.5.1", 64,
+                      extra_sources=[src1, src2])
+    assert wl.check_extra_sources() is None
 
 def test_pick_best_wheel():
     wd = WheelLocator("astsearch==0.1.2", "3.5.1", 64)
@@ -64,83 +60,84 @@ def test_pick_best_wheel():
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-win_amd64.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[1])
+    assert wd.pick_best_wheel(releases) == releases[1]
 
     # Wrong Windows bitness
     releases = [
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-win_32.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[0])
+    assert wd.pick_best_wheel(releases) == releases[0]
 
     # Prefer more specific Python version
     releases = [
         CachedRelease('astsearch-0.1.2-cp35-none-any.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[0])
+    assert wd.pick_best_wheel(releases) == releases[0]
 
     # Prefer more specific Python version
     releases = [
         CachedRelease('astsearch-0.1.2-py34.py35-none-any.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[0])
+    assert wd.pick_best_wheel(releases) == releases[0]
 
     # Incompatible Python version
     releases = [
         CachedRelease('astsearch-0.1.2-cp33-none-any.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[1])
+    assert wd.pick_best_wheel(releases) == releases[1]
 
     # Prefer more specific ABI version
     releases = [
         CachedRelease('astsearch-0.1.2-py3-abi3-any.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[0])
+    assert wd.pick_best_wheel(releases) == releases[0]
 
     # Incompatible ABI version
     releases = [
         CachedRelease('astsearch-0.1.2-cp35-abi4-win_amd64.whl'),
         CachedRelease('astsearch-0.1.2-py3-none-any.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[1])
+    assert wd.pick_best_wheel(releases) == releases[1]
 
     # Platform has priority over other attributes
     releases = [
         CachedRelease('astsearch-0.1.2-cp35-abi3-any.whl'),
         CachedRelease('astsearch-0.1.2-py2.py3-none-win_amd64.whl'),
     ]
-    assert_equal(wd.pick_best_wheel(releases), releases[1])
-
-def test_merge_dir_to():
-    with TemporaryDirectory() as td1, TemporaryDirectory() as td2:
-        td1 = Path(td1)
-        td2 = Path(td2)
-        with (td1 / 'ab').open('w') as f:
-            f.write(u"original")
-        with (td2 / 'ab').open('w') as f:
-            f.write(u"alternate")
-
-        (td1 / 'subdir').mkdir()
-        with (td1 / 'subdir' / 'foo').open('w'): pass
-        (td2 / 'subdir').mkdir()
-        with (td2 / 'subdir' / 'bar').open('w'): pass
+    assert wd.pick_best_wheel(releases) == releases[1]
 
-        merge_dir_to(td2, td1)
+def test_merge_dir_to(tmpdir):
+    td1 = Path(str(tmpdir.mkdir('one')))
+    td2 = Path(str(tmpdir.mkdir('two')))
 
-        assert_isfile(str(td1 / 'subdir' / 'foo'))
-        assert_isfile(str(td1 / 'subdir' / 'bar'))
-        with (td1 / 'ab').open() as f:
-            assert_equal(f.read(), u"alternate")
+    with (td1 / 'ab').open('w') as f:
+        f.write(u"original")
+    with (td2 / 'ab').open('w') as f:
+        f.write(u"alternate")
 
-        # Test with conflicts
-        (td1 / 'conflict').mkdir()
-        with (td2 / 'conflict').open('w'): pass
+    (td1 / 'subdir').mkdir()
+    with (td1 / 'subdir' / 'foo').open('w'): pass
+    (td2 / 'subdir').mkdir()
+    with (td2 / 'subdir' / 'bar').open('w'): pass
 
-        with assert_raises(RuntimeError):
-            merge_dir_to(td2, td1)
-        with assert_raises(RuntimeError):
-            merge_dir_to(td1, td2)
+    merge_dir_to(td2, td1)
+
+    assert_is_file(str(td1 / 'subdir' / 'foo'))
+    assert_is_file(str(td1 / 'subdir' / 'bar'))
+    with (td1 / 'ab').open() as f:
+        assert f.read() == u"alternate"
+
+    # Test with conflicts
+    (td1 / 'conflict').mkdir()
+    with (td2 / 'conflict').open('w'):
+        pass
+
+    with pytest.raises(RuntimeError):
+        merge_dir_to(td2, td1)
+    with pytest.raises(RuntimeError):
+        merge_dir_to(td1, td2)

+ 13 - 0
nsist/tests/utils.py

@@ -1,3 +1,6 @@
+import unittest
+import sys
+
 from os.path import isfile, isdir, exists, dirname
 
 test_dir = dirname(__file__)
@@ -9,3 +12,13 @@ def assert_is_file(path):
 def assert_is_dir(path):
     assert exists(path), "%s does not exist"
     assert isdir(path), "%s exists but is not a directory."
+
+def skip_on_windows(function):
+    """Decorator to skip a test on Windows."""
+    return unittest.skipIf(sys.platform.startswith("win"),
+                           "Test for non-Windows platforms")(function)
+
+def only_on_windows(function):
+    """Decorator to skip a test on Windows."""
+    return unittest.skipUnless(sys.platform.startswith("win"),
+                               "Test requires Windows")(function)

+ 15 - 0
tox.ini

@@ -0,0 +1,15 @@
+[tox]
+skipsdist = true
+envlist = python
+
+[testenv]
+deps = pytest
+       requests 
+       requests_download
+       win_cli_launchers
+       jinja2
+       yarg
+commands = pytest nsist/tests
+
+[testenv:notnetwork]
+commands = pytest -m "not network" nsist/tests