Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
---
name: Tests
on: [push, pull_request]
on:
push:
branches: [master]
pull_request:

# Start with explicit read-only permissions.
permissions:
contents: read


# Cancel pending jobs if a new commit was pushed for the same branch.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true


jobs:
tests:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os-version }}
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
os-version:
- ubuntu-latest
- windows-latest
- macos-latest
python-version:
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- '3.13'

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip tox tox-factor
- name: Run tox targets for ${{ matrix.python-version }}
python -m pip install -r requirements_dev.txt

- name: Install project
run: |
python -m pip install -e .


- name: Run all tests for ${{ matrix.python-version }}
run: |
PYVERSION=$(python -c "import sys; print(''.join([str(sys.version_info.major), str(sys.version_info.minor)]))")
python -m tox -f "py${PYVERSION}"
python -m pytest
12 changes: 7 additions & 5 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
Contributing
============

Contributions are welcome, and they are greatly appreciated! Every little bit
helps, and credit will always be given.
Contributions are welcome, and they are greatly appreciated!
Every little bit helps, and credit will always be given.

You can contribute in many ways:

Expand Down Expand Up @@ -78,10 +78,12 @@ Ready to contribute? Here's how to set up `pyzipper` for local development.
Now you can make your changes locally.

5. When you're done making changes, check that your changes pass flake8 and the
tests, including testing other Python versions with tox::
tests.
You can run tests for other Python versions with tox.
When a PR is created, it will trigger GitHub Action tests for all the supported Python versions::


$ tox
$ pytest test
$ flake8

To get flake8 and tox, just pip install them into your virtualenv.

Expand Down
42 changes: 40 additions & 2 deletions pyzipper/zipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,7 @@ def from_file(cls, filename, arcname=None, *, strict_timestamps=True):
# Create ZipInfo instance to store file information
if arcname is None:
arcname = filename
arcname = os.path.normpath(os.path.splitdrive(arcname)[1])
arcname = os.path.normpath(compat_strip_drive(arcname))
while arcname[0] in (os.sep, os.altsep):
arcname = arcname[1:]
if isdir:
Expand Down Expand Up @@ -2135,7 +2135,7 @@ def _extract_member(self, member, targetpath, pwd):
arcname = arcname.replace(os.path.altsep, os.path.sep)
# interpret absolute pathname as relative, remove drive letter or
# UNC path, redundant separators, "." and ".." components.
arcname = os.path.splitdrive(arcname)[1]
arcname = compat_strip_drive(arcname)
invalid_path_parts = ('', os.path.curdir, os.path.pardir)
arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
if x not in invalid_path_parts)
Expand Down Expand Up @@ -2538,6 +2538,44 @@ def _compile(file, optimize=-1):
return (fname, archivename)


def compat_strip_drive(path):
"""
Get os.path.splitdrive to behave the same in any Python version.

This can be removed once we no longer support Python 3.10.
"""
if os.name != 'nt' or sys.version_info[:2] > (3,10):
return os.path.splitdrive(path)[1]

# We are on Windows and Python 3.10 or older.

if not (path.startswith('//') or path.startswith(r'\\')):
# Not a Windows Share path.
return os.path.splitdrive(path)[1]

# Simplify path handling by using only unix separators.
sane_path = path

if sane_path.startswith('///'):
# Windows Share path without server name.
sane_path = '//server-ignored/' + sane_path[3:]

if sane_path.startswith('\\\\\\'):
# Windows Share path without server name.
sane_path = '\\\\server-ignored\\' + sane_path[3:]

#import pdb; pdb.set_trace()
parts = sane_path.split('/')
if len(parts) > 3 and parts[3] == '':
return '/' + '/'.join(parts[4:])

parts = sane_path.split('\\')
if len(parts) > 3 and parts[3] == '':
return '\\' + '\\'.join(parts[4:])

return os.path.splitdrive(sane_path)[1]


def main(args=None):
import argparse

Expand Down
21 changes: 0 additions & 21 deletions run_tests.py

This file was deleted.

37 changes: 10 additions & 27 deletions test/support/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
"""Supporting definitions for the Python regression tests."""
"""
Supporting definitions for the Python regression tests.

Copied from a very old stdblib.
Code not supported on latest Python version was removed.

The goal is to move pyzipper tests to pytest so that we will
no longer need the code from here.
"""

if __name__ != 'test.support':
raise ImportError('support must be imported from the test package')

import asyncio.events
import collections.abc
import contextlib
import datetime
import errno
import faulthandler
import fnmatch
Expand All @@ -16,7 +23,6 @@
import importlib.util
import io
import logging.handlers
import nntplib
import os
import platform
import re
Expand Down Expand Up @@ -296,7 +302,7 @@ def get_attribute(obj, name):
return attribute

verbose = 1 # Flag set to 0 by regrtest.py
use_resources = None # Flag set to [] by regrtest.py
use_resources = [] # Flag set to [] by regrtest.py
max_memuse = 0 # Disable bigmem tests (they will still be run with
# small sizes, to make sure they work.)
real_max_memuse = 0
Expand Down Expand Up @@ -1492,10 +1498,6 @@ def filter_error(err):
if timeout is not None:
socket.setdefaulttimeout(timeout)
yield
except nntplib.NNTPTemporaryError as err:
if verbose:
sys.stderr.write(denied.args[0] + "\n")
raise denied from err
except OSError as err:
# urllib can wrap original socket errors multiple times (!), we must
# unwrap to get at the original error.
Expand All @@ -1522,7 +1524,6 @@ def filter_error(err):
def captured_output(stream_name):
"""Return a context manager used by captured_stdout/stdin/stderr
that temporarily replaces the sys stream *stream_name* with a StringIO."""
import io
orig_stdout = getattr(sys, stream_name)
setattr(sys, stream_name, io.StringIO())
try:
Expand Down Expand Up @@ -2006,24 +2007,6 @@ def match_test_regex(test_id):
_match_test_func = func



def run_unittest(*classes):
"""Run tests from unittest.TestCase-derived classes."""
valid_types = (unittest.TestSuite, unittest.TestCase)
suite = unittest.TestSuite()
for cls in classes:
if isinstance(cls, str):
if cls in sys.modules:
suite.addTest(unittest.findTestCases(sys.modules[cls]))
else:
raise ValueError("str arguments must be keys in sys.modules")
elif isinstance(cls, valid_types):
suite.addTest(cls)
else:
suite.addTest(unittest.makeSuite(cls))
_filter_suite(suite, match_test)
_run_suite(suite)

#=======================================================================
# Check for the presence of docstrings.

Expand Down
52 changes: 6 additions & 46 deletions test/test_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,10 @@ def test_temp_dir__forked_child(self):
# Run the test as an external script, because it uses fork.
script_helper.assert_python_ok("-c", textwrap.dedent("""
import os
import sys
# Make sure we don't load the `test` code from stdlib.
sys.path.insert(0, os.getcwd())
print(os.getcwd())
from test import support
with support.temp_cwd() as temp_path:
pid = os.fork()
Expand All @@ -192,7 +196,7 @@ def test_temp_dir__forked_child(self):
# directory.
if not os.path.isdir(temp_path):
raise AssertionError("Child removed temp_path.")
"""))
"""), __cwd=os.getcwd())

# Tests for change_cwd()

Expand Down Expand Up @@ -290,11 +294,6 @@ def test_check_syntax_error(self):
with self.assertRaises(AssertionError):
support.check_syntax_error(self, "x=1")

def test_CleanImport(self):
import importlib
with support.CleanImport("asyncore"):
importlib.import_module("asyncore")

def test_DirsOnSysPath(self):
with support.DirsOnSysPath('foo', 'bar'):
self.assertIn("foo", sys.path)
Expand Down Expand Up @@ -397,7 +396,7 @@ def test_check__all__(self):
extra=extra,
blacklist=blacklist)

extra = {'TextTestResult', 'installHandler'}
extra = {'TextTestResult', 'installHandler', 'IsolatedAsyncioTestCase'}
blacklist = {'load_tests', "TestProgram", "BaseTestSuite"}

support.check__all__(self,
Expand Down Expand Up @@ -469,37 +468,6 @@ def check_options(self, args, func):
self.assertEqual(proc.stdout.rstrip(), repr(args))
self.assertEqual(proc.returncode, 0)

def test_args_from_interpreter_flags(self):
# Test test.support.args_from_interpreter_flags()
for opts in (
# no option
[],
# single option
['-B'],
['-s'],
['-S'],
['-E'],
['-v'],
['-b'],
['-q'],
# same option multiple times
['-bb'],
['-vvv'],
# -W options
['-Wignore'],
# -X options
['-X', 'dev'],
['-Wignore', '-X', 'dev'],
['-X', 'faulthandler'],
['-X', 'importtime'],
['-X', 'showalloccount'],
['-X', 'showrefcount'],
['-X', 'tracemalloc'],
['-X', 'tracemalloc=3'],
):
with self.subTest(opts=opts):
self.check_options(opts, 'args_from_interpreter_flags')

def test_optim_args_from_interpreter_flags(self):
# Test test.support.optim_args_from_interpreter_flags()
for opts in (
Expand Down Expand Up @@ -605,11 +573,3 @@ def test_fd_count(self):
# can_symlink
# skip_unless_symlink
# SuppressCrashReport


def test_main():
tests = [TestSupport]
support.run_unittest(*tests)

if __name__ == '__main__':
test_main()
17 changes: 10 additions & 7 deletions test/test_zipfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1419,12 +1419,15 @@ def test_extract_hackers_arcnames_windows_only(self):
(r'C:/foo/bar', 'foo/bar'),
(r'C://foo/bar', 'foo/bar'),
(r'C:\foo\bar', 'foo/bar'),
(r'//conky/mountpoint/foo/bar', 'foo/bar'),
(r'\\conky\mountpoint\foo\bar', 'foo/bar'),
(r'///conky/mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
(r'\\\conky\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
(r'//conky//mountpoint/foo/bar', 'conky/mountpoint/foo/bar'),
(r'\\conky\\mountpoint\foo\bar', 'conky/mountpoint/foo/bar'),
# The server together with the share name are considered as the drive root.
(r'//server-name/share-name/foo/bar', 'foo/bar'),
(r'\\server-name\share-name\foo\bar', 'foo/bar'),
# Here there is no server name.
(r'///share-name/root-without-server-name/foo/bar', 'root-without-server-name/foo/bar'),
(r'\\\share-name\root-without-server-name\foo\bar', 'root-without-server-name/foo/bar'),
# Here there is no share name.
(r'//server-name//root-dir-without-share-name/foo/bar', 'root-dir-without-share-name/foo/bar'),
(r'\\server-name\\root-dir-without-share-name\foo\bar', 'root-dir-without-share-name/foo/bar'),
(r'//?/C:/foo/bar', 'foo/bar'),
(r'\\?\C:\foo\bar', 'foo/bar'),
(r'C:/../C:/foo/bar', 'C_/foo/bar'),
Expand Down Expand Up @@ -1459,7 +1462,7 @@ def _test_extract_hackers_arcnames(self, hacknames):
with zipfile.ZipFile(TESTFN2, 'r') as zipfp:
writtenfile = zipfp.extract(arcname, targetpath)
self.assertEqual(writtenfile, correctfile,
msg='extract %r: %r != %r' %
msg='extract %r\nactual : %r\nexpected: %r' %
(arcname, writtenfile, correctfile))
self.check_file(correctfile, content)
rmtree('target')
Expand Down
Loading