Compare commits

..

22 Commits

Author SHA1 Message Date
Sebastian Lohff f668fc3fe6 Release 0.5.4 2023-01-23 23:40:14 +01:00
Sebastian Lohff 9784c82679 Drop python3.6 support
servefile still works with python3.6, we just no longer test this.
2023-01-23 23:40:14 +01:00
Sebastian Lohff f23dfd2a51 Ignore python3.11 cgi deprecation warning
The cgi module is marked as deprecated and will be removed in
python3.13. servefile uses the module for its FieldStorage class used in
the upload functionality. For now I will just ignore this, so servefile
doesn't print out the warning each time it is run, but soon this will
require either a rewrite of FieldStorage or an external library.

With this commit we also now officially support python3.10 and
python3.11.
2023-01-23 23:40:14 +01:00
Sebastian Lohff b1145af6bb Code formatting
* removed weird linebreaks for for-loop
 * no () for del statement
2023-01-23 23:40:14 +01:00
MasterofJOKers 0b010d5c10 Upload to uploaddir instead of /tmp
When uploading larger files, cgi.FieldStorage decides to store the files
in an unnamed temporary file in /tmp while parsing the form-data. This
is counter-intuitive and might not work, if the partition hosting /tmp/
is too small. Therefore, we overwrite FieldStorage's make_file() method
to use the targetDir as upload path.

While we're at it, we also use NamedTemporaryFile instead of
TemporaryFile, because that lets us use os.link() to create a "copy" of
the file-data without writing it to disk a second time. This does not
work for small data, because small data is kept in an BytesIO object and
thus never written to file automatically. For this case, we keep the old
code, that's writing down files manually.

We have to inline-define CustomFieldStorage, because FieldStorage will
instantiate a new FieldStorage instance for parsing the parts of a
multipart/form-data body and thus we cannot pass targetDir via
__init__() argument.

Signed-off-by: MasterofJOKers <joker@someserver.de>
2022-01-18 22:52:44 +01:00
Sebastian Lohff 4f3b916b9f Add pep8 check to tox and GitHub actions 2022-01-18 21:33:15 +01:00
Sebastian Lohff 5dcf364e0f Code formatting: Whitespace around operators 2022-01-18 21:33:15 +01:00
Sebastian Lohff aa54e8536a Further codeformatting
* break up some extra long lines
 * add a bit of noqa for warnings I don't want to have
 * rework formatting for ip addr / ifconfig part
2022-01-18 21:33:15 +01:00
Sebastian Lohff 96e9e76ff4 Code reformatting
* replace tabs with spaces
 * replace some of the != None with is not None etc.
 * more whitespace fixes
 * remove all the newline \
2022-01-18 21:33:15 +01:00
Sebastian Lohff c7af20388d Release v0.5.3 2021-11-18 00:32:41 +01:00
Sebastian Pipping 413ea76746 tox: Slightly increase pytest verbosity 2021-11-16 21:11:20 +01:00
Sebastian Pipping 8b16b7626c tests: Drop unused arguments 2021-11-16 21:08:56 +01:00
Sebastian Pipping 8f9ba0e387 tests: Replace hardcoded timeouts by retries
This (1) fixes test timeouts for some machines
and (2) speeds up test execution by 20% on my machine.
2021-11-16 21:05:39 +01:00
Sebastian Lohff cd28811fcf Release v0.5.2 2021-09-08 00:23:11 +02:00
Sebastian Lohff 46d4433a1d Explicitly set encoding for http requests in tests
Due to the upgrade to charset-normalizer 2.0.4 guessing the encoding
inside the tests did not work anymore and caused the umlaut tests to
fail. Explicitly specifying the encoding on the requests' response
object fixes this.
2021-09-07 23:24:57 +02:00
Sebastian Lohff d87a42cf8e Add PUT upload fix to changelog 2021-08-04 00:37:28 +02:00
Paweł Chojnacki 6537c054e5 Fix PUT uploads
PUT uploads were broken on python 3.9 and were lacking tests.
2021-07-14 00:11:16 +02:00
Sebastian Lohff 65fcac5c49 Fix encoding handling for file listing with py2
File listings with -l that contained files with umlauts or other special
chars could break the directory listing. Hopefully one of the last
python2 fixes before I drop support for this.
2021-06-08 23:46:30 +02:00
Sebastian Lohff 0334e74996 Add Github Actions workflow to run tox 2021-06-08 23:46:30 +02:00
Sebastian Lohff 8217034753 Drop python3.5 support 2021-06-08 23:46:30 +02:00
Sebastian Lohff 9fa4ed0026 Quote filenames in Location header on redirect
When we redirect the user to the "correct" file name this name should
end up quoted in the header, else we would end up in an infinite
redirect loop.
2021-06-08 23:46:30 +02:00
Sebastian Lohff 1f451e0f29 Allow ports for tests to be specified via env
SERVEFILE_DEFAULT_PORT and SERVEFILE_SECONDARY_PORT can be used to
specify ports used in the servefile tests. This can be useful if the
default port 8080 and the secondary port 8081 (for the -p test) are
already in use. To allow automatic choosing of a free port 0 can be
specified to tell the test code to automatically select a free port.
2021-04-21 01:04:20 +02:00
7 changed files with 1172 additions and 997 deletions

37
.github/workflows/run-tox.yml vendored Normal file
View File

@ -0,0 +1,37 @@
name: Run Tox
on:
push:
branches:
- master
pull_request:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python: [2.7, 3.7, 3.8, 3.9, "3.10", 3.11]
steps:
- uses: actions/checkout@v2
- name: Setup Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install Tox
run: pip install tox
- name: Run Tox
run: tox -e py
pep8:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup python
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install Tox
run: pip install tox
- name: Run Tox pep8
run: "tox -e pep8"

View File

@ -1,15 +1,41 @@
servefile changelog servefile changelog
=================== ===================
2023-01-23 v0.5.4
-----------------
Unreleased 0.5.4 released
----------
* code reformatting for better maintainability
* upload to uploaddir instead of /tmp for large files
* add python3.10 / python3.11 support
* drop python3.6 support
2021-11-18 v0.5.3
-----------------
0.5.3 released
* improved test performance
2021-09-08 v0.5.2
-----------------
0.5.2 released
* fixed bug where exception was shown on transmission abort with python3 * fixed bug where exception was shown on transmission abort with python3
* fixed wrong/outdated pyopenssl package names * fixed wrong/outdated pyopenssl package names
* ports for running servefile tests can now be specified via the * tests are now using a free non-default port to avoid clashes; if
wished the ports can be set from outside by specifying the
environment variables SERVEFILE_DEFAULT_PORT and environment variables SERVEFILE_DEFAULT_PORT and
SERVEFILE_SECONDARY_PORT, use 0 for automatic selection SERVEFILE_SECONDARY_PORT
* fixed broken redirect when filename contained umlauts or other characters
that should have been quoted
* fixed broken special char handling in directory listing for python2
* drop python3.5 support
* fixed PUT uploads with python3 and documented PUT-uploads with curl
2020-10-30 v0.5.1 2020-10-30 v0.5.1

View File

@ -1,4 +1,4 @@
.TH SERVEFILE 1 "September 2020" "servefile 0.5.1" "User Commands" .TH SERVEFILE 1 "January 2023" "servefile 0.5.4" "User Commands"
.SH NAME .SH NAME
servefile \- small HTTP-Server for temporary file transfer servefile \- small HTTP-Server for temporary file transfer

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ setup(
long_description=long_description, long_description=long_description,
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
platforms='posix', platforms='posix',
version='0.5.1', version='0.5.4',
license='GPLv3 or later', license='GPLv3 or later',
url='https://github.com/sebageek/servefile/', url='https://github.com/sebageek/servefile/',
author='Sebastian Lohff', author='Sebastian Lohff',
@ -38,10 +38,11 @@ setup(
'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Topic :: Communications', 'Topic :: Communications',
'Topic :: Communications :: File Sharing', 'Topic :: Communications :: File Sharing',
'Topic :: Internet', 'Topic :: Internet',

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import io import io
import os import os
import pytest import pytest
@ -8,15 +9,18 @@ import sys
import tarfile import tarfile
import time import time
import urllib3 import urllib3
from requests.exceptions import ConnectionError
# crudly written to learn more about pytest and to have a base for refactoring # crudly written to learn more about pytest and to have a base for refactoring
if sys.version_info.major >= 3: if sys.version_info.major >= 3:
from pathlib import Path from pathlib import Path
from urllib.parse import quote
connrefused_exc = ConnectionRefusedError connrefused_exc = ConnectionRefusedError
else: else:
from pathlib2 import Path from pathlib2 import Path
from urllib import quote
connrefused_exc = socket.error connrefused_exc = socket.error
@ -31,8 +35,8 @@ def _get_port_from_env(var_name, default):
return port return port
SERVEFILE_DEFAULT_PORT = _get_port_from_env('SERVEFILE_DEFAULT_PORT', 8080) SERVEFILE_DEFAULT_PORT = _get_port_from_env('SERVEFILE_DEFAULT_PORT', 0)
SERVEFILE_SECONDARY_PORT = _get_port_from_env('SERVEFILE_SECONDARY_PORT', 8081) SERVEFILE_SECONDARY_PORT = _get_port_from_env('SERVEFILE_SECONDARY_PORT', 0)
@pytest.fixture @pytest.fixture
@ -50,12 +54,11 @@ def run_servefile():
servefile_path = ['-m', 'servefile'] servefile_path = ['-m', 'servefile']
# use non-default default port, if one is given via env (and none via args) # use non-default default port, if one is given via env (and none via args)
if '-p' not in args and SERVEFILE_DEFAULT_PORT != 8080: if '-p' not in args and '--port' not in args:
args.extend(['-p', str(SERVEFILE_DEFAULT_PORT)]) args.extend(['-p', str(SERVEFILE_DEFAULT_PORT)])
print("running {} with args {}".format(", ".join(servefile_path), args)) print("running {} with args {}".format(", ".join(servefile_path), args))
p = subprocess.Popen([sys.executable] + servefile_path + args, **kwargs) p = subprocess.Popen([sys.executable] + servefile_path + args, **kwargs)
time.sleep(kwargs.get('timeout', 0.3))
instances.append(p) instances.append(p)
return p return p
@ -81,23 +84,26 @@ def datadir(tmp_path):
_datadir(v, new_path) _datadir(v, new_path)
else: else:
if hasattr(v, 'decode'): if hasattr(v, 'decode'):
v = v.decode() # python2 compability v = v.decode('utf-8') # python2 compability
(path / k).write_text(v) (path / k).write_text(v)
return path return path
return _datadir return _datadir
def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method='get', protocol='http', timeout=5, def make_request(path='/', host='localhost', port=SERVEFILE_DEFAULT_PORT, method='get', protocol='http',
**kwargs): encoding='utf-8', **kwargs):
url = '{}://{}:{}{}'.format(protocol, host, port, path) url = '{}://{}:{}{}'.format(protocol, host, port, path)
print('Calling {} on {} with {}'.format(method, url, kwargs)) print('Calling {} on {} with {}'.format(method, url, kwargs))
r = getattr(requests, method)(url, **kwargs) r = getattr(requests, method)(url, **kwargs)
if r.encoding is None and encoding:
r.encoding = encoding
return r return r
def check_download(expected_data=None, path='/', fname=None, status_code=200, **kwargs): def check_download(expected_data=None, path='/', fname=None, **kwargs):
if fname is None: if fname is None:
fname = os.path.basename(path) fname = os.path.basename(path)
r = make_request(path, **kwargs) r = make_request(path, **kwargs)
@ -111,6 +117,22 @@ def check_download(expected_data=None, path='/', fname=None, status_code=200, **
return r # for additional tests return r # for additional tests
def _retry_while(exception, function, timeout=2):
now = time.time # float seconds since epoch
def wrapped(*args, **kwargs):
timeout_after = now() + timeout
while True:
try:
return function(*args, **kwargs)
except exception:
if now() >= timeout_after:
raise
time.sleep(0.1)
return wrapped
def _test_version(run_servefile, standalone): def _test_version(run_servefile, standalone):
# we expect the version on stdout (python3.4+) or stderr(python2.6-3.3) # we expect the version on stdout (python3.4+) or stderr(python2.6-3.3)
s = run_servefile('--version', standalone=standalone, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) s = run_servefile('--version', standalone=standalone, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
@ -124,7 +146,7 @@ def _test_version(run_servefile, standalone):
version = s.stdout.readline().decode().strip() version = s.stdout.readline().decode().strip()
# hardcode version as string until servefile is a module # hardcode version as string until servefile is a module
assert version == 'servefile 0.5.1' assert version == 'servefile 0.5.4'
def test_version(run_servefile): def test_version(run_servefile):
@ -141,7 +163,7 @@ def test_correct_headers(run_servefile, datadir):
p = datadir({'testfile': data}) / 'testfile' p = datadir({'testfile': data}) / 'testfile'
run_servefile(str(p)) run_servefile(str(p))
r = make_request() r = _retry_while(ConnectionError, make_request)()
assert r.status_code == 200 assert r.status_code == 200
assert r.headers.get('Content-Type') == 'application/octet-stream' assert r.headers.get('Content-Type') == 'application/octet-stream'
assert r.headers.get('Content-Disposition') == 'attachment; filename="testfile"' assert r.headers.get('Content-Disposition') == 'attachment; filename="testfile"'
@ -154,7 +176,7 @@ def test_redirect_and_download(run_servefile, datadir):
run_servefile(str(p)) run_servefile(str(p))
# redirect # redirect
r = make_request(allow_redirects=False) r = _retry_while(ConnectionError, make_request)(allow_redirects=False)
assert r.status_code == 302 assert r.status_code == 302
assert r.headers.get('Location') == '/testfile' assert r.headers.get('Location') == '/testfile'
@ -162,12 +184,29 @@ def test_redirect_and_download(run_servefile, datadir):
check_download(data, fname='testfile') check_download(data, fname='testfile')
def test_redirect_and_download_with_umlaut(run_servefile, datadir):
data = "NÖÖT NÖÖT"
filename = "tästføile"
p = datadir({filename: data}) / filename
run_servefile(str(p))
# redirect
r = _retry_while(ConnectionError, make_request)(allow_redirects=False)
assert r.status_code == 302
assert r.headers.get('Location') == '/{}'.format(quote(filename))
# normal download
if sys.version_info.major < 3:
data = unicode(data, 'utf-8')
check_download(data, fname=filename)
def test_specify_port(run_servefile, datadir): def test_specify_port(run_servefile, datadir):
data = "NOOT NOOT" data = "NOOT NOOT"
p = datadir({'testfile': data}) / 'testfile' p = datadir({'testfile': data}) / 'testfile'
run_servefile([str(p), '-p', str(SERVEFILE_SECONDARY_PORT)]) run_servefile([str(p), '-p', str(SERVEFILE_SECONDARY_PORT)])
check_download(data, fname='testfile', port=SERVEFILE_SECONDARY_PORT) _retry_while(ConnectionError, check_download)(data, fname='testfile', port=SERVEFILE_SECONDARY_PORT)
def test_ipv4_only(run_servefile, datadir): def test_ipv4_only(run_servefile, datadir):
@ -175,7 +214,7 @@ def test_ipv4_only(run_servefile, datadir):
p = datadir({'testfile': data}) / 'testfile' p = datadir({'testfile': data}) / 'testfile'
run_servefile([str(p), '-4']) run_servefile([str(p), '-4'])
check_download(data, fname='testfile', host='127.0.0.1') _retry_while(ConnectionError, check_download)(data, fname='testfile', host='127.0.0.1')
sock = socket.socket(socket.AF_INET6) sock = socket.socket(socket.AF_INET6)
with pytest.raises(connrefused_exc): with pytest.raises(connrefused_exc):
@ -188,7 +227,7 @@ def test_big_download(run_servefile, datadir):
p = datadir({'testfile': data}) / 'testfile' p = datadir({'testfile': data}) / 'testfile'
run_servefile(str(p)) run_servefile(str(p))
check_download(data, fname='testfile') _retry_while(ConnectionError, check_download)(data, fname='testfile')
def test_authentication(run_servefile, datadir): def test_authentication(run_servefile, datadir):
@ -197,11 +236,11 @@ def test_authentication(run_servefile, datadir):
run_servefile([str(p), '-a', 'user:password']) run_servefile([str(p), '-a', 'user:password'])
for auth in [('foo', 'bar'), ('user', 'wrong'), ('unknown', 'password')]: for auth in [('foo', 'bar'), ('user', 'wrong'), ('unknown', 'password')]:
r = make_request(auth=auth) r = _retry_while(ConnectionError, make_request)(auth=auth)
assert '401 - Unauthorized' in r.text assert '401 - Unauthorized' in r.text
assert r.status_code == 401 assert r.status_code == 401
check_download(data, fname='testfile', auth=('user', 'password')) _retry_while(ConnectionError, check_download)(data, fname='testfile', auth=('user', 'password'))
def test_serve_directory(run_servefile, datadir): def test_serve_directory(run_servefile, datadir):
@ -210,6 +249,7 @@ def test_serve_directory(run_servefile, datadir):
'bar': {'thisisaverylongfilenamefortestingthatthisstillworksproperly': 'jup!'}, 'bar': {'thisisaverylongfilenamefortestingthatthisstillworksproperly': 'jup!'},
'noot': 'still data in here', 'noot': 'still data in here',
'bigfile': 'x' * (10 * 1024 ** 2), 'bigfile': 'x' * (10 * 1024 ** 2),
'möwe': 'KRAKRAKRAKA',
} }
p = datadir(d) p = datadir(d)
run_servefile([str(p), '-l']) run_servefile([str(p), '-l'])
@ -217,12 +257,12 @@ def test_serve_directory(run_servefile, datadir):
# check if all files are in directory listing # check if all files are in directory listing
# (could be made more sophisticated with beautifulsoup) # (could be made more sophisticated with beautifulsoup)
for path in '/', '/../': for path in '/', '/../':
r = make_request(path) r = _retry_while(ConnectionError, make_request)(path)
for k in d: for k in d:
assert k in r.text assert quote(k) in r.text
for fname, content in d['foo'].items(): for fname, content in d['foo'].items():
check_download(content, '/foo/' + fname) _retry_while(ConnectionError, check_download)(content, '/foo/' + fname)
r = make_request('/unknown') r = make_request('/unknown')
assert r.status_code == 404 assert r.status_code == 404
@ -244,7 +284,7 @@ def test_serve_relative_directory(run_servefile, datadir):
# check if all files are in directory listing # check if all files are in directory listing
# (could be made more sophisticated with beautifulsoup) # (could be made more sophisticated with beautifulsoup)
for path in '/', '/../': for path in '/', '/../':
r = make_request(path) r = _retry_while(ConnectionError, make_request)(path)
for k in d: for k in d:
assert k in r.text assert k in r.text
@ -268,14 +308,14 @@ def test_upload(run_servefile, tmp_path):
run_servefile(['-u', str(uploaddir)]) run_servefile(['-u', str(uploaddir)])
# check that servefile created the directory
assert uploaddir.is_dir()
# check upload form present # check upload form present
r = make_request() r = _retry_while(ConnectionError, make_request)()
assert r.status_code == 200 assert r.status_code == 200
assert 'multipart/form-data' in r.text assert 'multipart/form-data' in r.text
# check that servefile created the directory
assert uploaddir.is_dir()
# upload file # upload file
files = {'file': ('haiku.txt', data)} files = {'file': ('haiku.txt', data)}
r = make_request(method='post', files=files) r = make_request(method='post', files=files)
@ -291,6 +331,13 @@ def test_upload(run_servefile, tmp_path):
with open(str(uploaddir / 'haiku.txt(1)')) as f: with open(str(uploaddir / 'haiku.txt(1)')) as f:
assert f.read() == data assert f.read() == data
# upload file using PUT
r = make_request("/haiku.txt", method='put', data=data)
assert r.status_code == 201
assert 'OK!' in r.text
with open(str(uploaddir / 'haiku.txt(2)')) as f:
assert f.read() == data
def test_upload_size_limit(run_servefile, tmp_path): def test_upload_size_limit(run_servefile, tmp_path):
uploaddir = tmp_path / 'upload' uploaddir = tmp_path / 'upload'
@ -298,7 +345,7 @@ def test_upload_size_limit(run_servefile, tmp_path):
# upload file that is too big # upload file that is too big
files = {'file': ('toobig', "x" * 2049)} files = {'file': ('toobig', "x" * 2049)}
r = make_request(method='post', files=files) r = _retry_while(ConnectionError, make_request)(method='post', files=files)
assert 'Your file was too big' in r.text assert 'Your file was too big' in r.text
assert r.status_code == 413 assert r.status_code == 413
assert not (uploaddir / 'toobig').exists() assert not (uploaddir / 'toobig').exists()
@ -310,6 +357,20 @@ def test_upload_size_limit(run_servefile, tmp_path):
assert r.status_code == 200 assert r.status_code == 200
def test_upload_large_file(run_servefile, tmp_path):
# small files end up in BytesIO while large files get temporary files. this
# test makes sure we hit the large file codepath at least once
uploaddir = tmp_path / 'upload'
run_servefile(['-u', str(uploaddir)])
data = "asdf" * 1024
files = {'file': ('more_data.txt', data)}
r = _retry_while(ConnectionError, make_request)(method='post', files=files)
assert r.status_code == 200
with open(str(uploaddir / 'more_data.txt')) as f:
assert f.read() == data
def test_tar_mode(run_servefile, datadir): def test_tar_mode(run_servefile, datadir):
d = { d = {
'foo': { 'foo': {
@ -323,7 +384,7 @@ def test_tar_mode(run_servefile, datadir):
# test redirect? # test redirect?
# test contents of tar file # test contents of tar file
r = make_request() r = _retry_while(ConnectionError, make_request)()
assert r.status_code == 200 assert r.status_code == 200
tar = tarfile.open(fileobj=io.BytesIO(r.content)) tar = tarfile.open(fileobj=io.BytesIO(r.content))
assert len(tar.getmembers()) == 3 assert len(tar.getmembers()) == 3
@ -339,7 +400,7 @@ def test_tar_compression(run_servefile, datadir):
p = datadir(d) p = datadir(d)
run_servefile(['-c', 'gzip', '-t', str(p / 'foo')]) run_servefile(['-c', 'gzip', '-t', str(p / 'foo')])
r = make_request() r = _retry_while(ConnectionError, make_request)()
assert r.status_code == 200 assert r.status_code == 200
tar = tarfile.open(fileobj=io.BytesIO(r.content), mode='r:gz') tar = tarfile.open(fileobj=io.BytesIO(r.content), mode='r:gz')
assert len(tar.getmembers()) == 1 assert len(tar.getmembers()) == 1
@ -349,7 +410,6 @@ def test_https(run_servefile, datadir):
data = "NOOT NOOT" data = "NOOT NOOT"
p = datadir({'testfile': data}) / 'testfile' p = datadir({'testfile': data}) / 'testfile'
run_servefile(['--ssl', str(p)]) run_servefile(['--ssl', str(p)])
time.sleep(0.2) # time for generating ssl certificates
# fingerprint = None # fingerprint = None
# while not fingerprint: # while not fingerprint:
@ -364,7 +424,7 @@ def test_https(run_servefile, datadir):
# assert fingerprint # assert fingerprint
urllib3.disable_warnings() urllib3.disable_warnings()
check_download(data, protocol='https', verify=False) _retry_while(ConnectionError, check_download)(data, protocol='https', verify=False)
def test_https_big_download(run_servefile, datadir): def test_https_big_download(run_servefile, datadir):
@ -372,10 +432,9 @@ def test_https_big_download(run_servefile, datadir):
data = "x" * (10 * 1024 ** 2) data = "x" * (10 * 1024 ** 2)
p = datadir({'testfile': data}) / 'testfile' p = datadir({'testfile': data}) / 'testfile'
run_servefile(['--ssl', str(p)]) run_servefile(['--ssl', str(p)])
time.sleep(0.2) # time for generating ssl certificates
urllib3.disable_warnings() urllib3.disable_warnings()
check_download(data, protocol='https', verify=False) _retry_while(ConnectionError, check_download)(data, protocol='https', verify=False)
def test_abort_download(run_servefile, datadir): def test_abort_download(run_servefile, datadir):
@ -388,7 +447,7 @@ def test_abort_download(run_servefile, datadir):
# provoke a connection abort # provoke a connection abort
# hopefully the buffers will not fill up with all of the 10mb # hopefully the buffers will not fill up with all of the 10mb
sock = socket.socket(socket.AF_INET) sock = socket.socket(socket.AF_INET)
sock.connect(("localhost", SERVEFILE_DEFAULT_PORT)) _retry_while(connrefused_exc, sock.connect)(("localhost", SERVEFILE_DEFAULT_PORT))
sock.send(b"GET /testfile HTTP/1.0\n\n") sock.send(b"GET /testfile HTTP/1.0\n\n")
resp = sock.recv(100) resp = sock.recv(100)
assert resp != b'' assert resp != b''

14
tox.ini
View File

@ -1,9 +1,19 @@
[tox] [tox]
envlist = py27,py36,py37,py38 envlist = py27,py37,py38,py39,py310,py311,pep8
[testenv] [testenv]
deps = deps =
pathlib2; python_version<"3" pathlib2; python_version<"3"
pytest pytest
requests requests
commands = pytest --tb=short {posargs} flake8
commands = pytest -v --tb=short {posargs}
[testenv:pep8]
commands = flake8 servefile/ {posargs}
[flake8]
show-source = True
max-line-length = 120
ignore = E123,E125,E241,E402,E741,W503,W504,H301
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build