Compare commits

..

25 Commits

Author SHA1 Message Date
Matt Martz
eb5c4ddd08 Make sure we only check the length of args.server if it's truthy 2017-11-23 10:07:14 -06:00
Matt Martz
d7f9156ddc py35 work around 2017-11-23 09:57:59 -06:00
Matt Martz
e15a8636ff Install python3.2/3.3 from deadsnakes 2017-11-23 09:49:44 -06:00
Matt Martz
45e4b38ac3 No bare except 2017-11-23 09:49:26 -06:00
Matt Martz
9b0e6d75c6 Move the majority of the csv_header functionality to SpeedtestResults 2017-11-23 09:46:55 -06:00
Matt Martz
b13450e22a Support csv-delimiter for csv-header 2017-11-23 09:44:16 -06:00
Matt Martz
f5c34eb03c Output a different message when only 1 server is provided 2017-11-23 09:43:59 -06:00
Matt Martz
bf8164fb1f Add additional information to machine parsable outputs 2017-11-23 09:43:10 -06:00
Matt Martz
48301deac9 Attempt to catch MemoryError if possible 2017-10-16 09:26:47 -05:00
Matt Martz
afa79f263a Print errors to stderr 2017-10-16 09:26:28 -05:00
Matt Martz
bdaad9197d Always flush in py2 print_ 2017-06-02 09:56:45 -05:00
Matt Martz
3bf0036560 Allow timeout to be a float 2017-05-12 14:55:23 -05:00
Matt Martz
7df7c56a23 Add option to exclude servers, and allow --server and --exclude to be specified multiple times 2017-05-12 13:03:41 -05:00
Matt Martz
fca2006060 Create a getter for Speedtest.best to raise an exception is get_best_server has not found a best server 2017-05-12 13:01:59 -05:00
Matt Martz
36d8327b39 Indicate speedtest-cli supports python 3.6, and ensure py3.2 has an appropriate setuptools version 2017-05-05 10:50:44 -05:00
Matt Martz
203da2cd71 More and better debugging 2017-05-03 17:17:00 -05:00
Matt Martz
6f3ba24e92 Revert "Test failing --source"
This reverts commit be7d7f6a1c.
2017-05-03 11:02:35 -05:00
Matt Martz
be7d7f6a1c Test failing --source 2017-05-03 10:56:54 -05:00
Matt Martz
5cb3a19cd3 Switch to using matrix for travis 2017-05-03 10:44:46 -05:00
Matt Martz
d2a46ac897 Remove debug print 2017-05-02 12:51:26 -05:00
Matt Martz
d30a415f12 Docstrings and version bump 2017-05-02 12:38:33 -05:00
Matt Martz
cab2d55c51 Remove SCHEME global 2017-05-02 12:29:54 -05:00
Matt Martz
0614e07eb9 flake8 fixes 2017-05-02 11:08:32 -05:00
Matt Martz
b7a3decad9 Use vendored create_connection when socket doesn't have it, or socket.create_connection is too old 2017-05-02 11:08:22 -05:00
Matt Martz
bd390a36ae Don't override socket.socket for binding, eliminiate globals SOURCE and USER_AGENT 2017-05-02 10:56:31 -05:00
7 changed files with 114 additions and 259 deletions

View File

@ -37,15 +37,13 @@ matrix:
env: TOXENV=pypy env: TOXENV=pypy
before_install: before_install:
- pyenv versions
- if [[ $(echo "$TOXENV" | egrep -c "py35") != 0 ]]; then pyenv global system 3.5; fi; - if [[ $(echo "$TOXENV" | egrep -c "py35") != 0 ]]; then pyenv global system 3.5; fi;
install: install:
- if [[ $(echo "$TOXENV" | egrep -c "py32") != 0 ]]; then pip install setuptools==17.1.1; fi; - if [[ $(echo "$TOXENV" | egrep -c "py32") != 0 ]]; then pip install setuptools==17.1.1; fi;
- if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[12])") != 0 ]]; then pip install virtualenv==1.7.2 tox==1.3; fi; - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[12])") != 0 ]]; then pip install virtualenv==1.7.2 tox==1.3; fi;
- if [[ $(echo "$TOXENV" | egrep -c "(py26|py33)") != 0 ]]; then pip install virtualenv==15.2.0 tox==2.9.1; fi; - if [[ $(echo "$TOXENV" | egrep -c "(py2[45]|py3[12])") == 0 ]]; then pip install tox; fi;
- if [[ $(echo "$TOXENV" | egrep -c "(py2[456]|py3[123])") == 0 ]]; then pip install tox; fi;
script: script:
- tox - tox

View File

@ -1,7 +1,3 @@
#Usage
$ curl -s https://git.spectre5.com/adamcruz/speedtest-cli-pub/raw/branch/master/speedtest.py | python -
speedtest-cli speedtest-cli
============= =============
@ -11,9 +7,9 @@ speedtest.net
.. image:: https://img.shields.io/pypi/v/speedtest-cli.svg .. image:: https://img.shields.io/pypi/v/speedtest-cli.svg
:target: https://pypi.python.org/pypi/speedtest-cli/ :target: https://pypi.python.org/pypi/speedtest-cli/
:alt: Latest Version :alt: Latest Version
.. image:: https://img.shields.io/travis/sivel/speedtest-cli.svg .. image:: https://img.shields.io/pypi/dm/speedtest-cli.svg
:target: https://pypi.python.org/pypi/speedtest-cli/ :target: https://pypi.python.org/pypi/speedtest-cli/
:alt: Travis :alt: Downloads
.. image:: https://img.shields.io/pypi/l/speedtest-cli.svg .. image:: https://img.shields.io/pypi/l/speedtest-cli.svg
:target: https://pypi.python.org/pypi/speedtest-cli/ :target: https://pypi.python.org/pypi/speedtest-cli/
:alt: License :alt: License
@ -21,7 +17,7 @@ speedtest.net
Versions Versions
-------- --------
speedtest-cli works with Python 2.4-3.7 speedtest-cli works with Python 2.4-3.6
.. image:: https://img.shields.io/pypi/pyversions/speedtest-cli.svg .. image:: https://img.shields.io/pypi/pyversions/speedtest-cli.svg
:target: https://pypi.python.org/pypi/speedtest-cli/ :target: https://pypi.python.org/pypi/speedtest-cli/
@ -81,14 +77,13 @@ Usage
usage: speedtest-cli [-h] [--no-download] [--no-upload] [--bytes] [--share] usage: speedtest-cli [-h] [--no-download] [--no-upload] [--bytes] [--share]
[--simple] [--csv] [--csv-delimiter CSV_DELIMITER] [--simple] [--csv] [--csv-delimiter CSV_DELIMITER]
[--csv-header] [--json] [--list] [--server SERVER] [--csv-header] [--json] [--list] [--server SERVER]
[--exclude EXCLUDE] [--mini MINI] [--source SOURCE] [--mini MINI] [--source SOURCE] [--timeout TIMEOUT]
[--timeout TIMEOUT] [--secure] [--no-pre-allocate] [--secure] [--no-pre-allocate] [--version]
[--version]
Command line interface for testing internet bandwidth using speedtest.net. Command line interface for testing internet bandwidth using speedtest.net.
-------------------------------------------------------------------------- --------------------------------------------------------------------------
https://github.com/sivel/speedtest-cli https://github.com/sivel/speedtest-cli
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--no-download Do not perform download test --no-download Do not perform download test
@ -111,10 +106,7 @@ Usage
affected by --bytes affected by --bytes
--list Display a list of speedtest.net servers sorted by --list Display a list of speedtest.net servers sorted by
distance distance
--server SERVER Specify a server ID to test against. Can be supplied --server SERVER Specify a server ID to test against
multiple times
--exclude EXCLUDE Exclude a server from selection. Can be supplied
multiple times
--mini MINI URL of the Speedtest Mini server --mini MINI URL of the Speedtest Mini server
--source SOURCE Source IP address to bind to --source SOURCE Source IP address to bind to
--timeout TIMEOUT HTTP timeout in seconds. Default 10 --timeout TIMEOUT HTTP timeout in seconds. Default 10

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2012-2018 Matt Martz # Copyright 2012-2016 Matt Martz
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -66,7 +66,7 @@ setup(
author_email='matt@sivel.net', author_email='matt@sivel.net',
url='https://github.com/sivel/speedtest-cli', url='https://github.com/sivel/speedtest-cli',
license='Apache License, Version 2.0', license='Apache License, Version 2.0',
py_modules=['speedtest'], py_modules=['speedtest', 'speedtest_cli'],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'speedtest=speedtest:main', 'speedtest=speedtest:main',
@ -91,6 +91,5 @@ setup(
'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
] ]
) )

View File

@ -1,4 +1,4 @@
.TH "speedtest-cli" 1 "2018-01-05" "speedtest-cli" .TH "speedtest-cli" 1 "2014-04-23" "speedtest-cli"
.SH NAME .SH NAME
speedtest\-cli \- Command line interface for testing internet bandwidth using speedtest.net speedtest\-cli \- Command line interface for testing internet bandwidth using speedtest.net
.SH SYNOPSIS .SH SYNOPSIS
@ -23,24 +23,14 @@ Displays usage for the tool.
.B Options .B Options
\fB\-\-no\-download\fR
.RS
Do not perform download test
.RE
\fB\-\-no\-upload\fR
.RS
Do not perform upload test
.RE
\fB\-\-bytes\fR \fB\-\-bytes\fR
.RS .RS
Display values in bytes instead of bits. Does not affect the image generated by \-\-share, nor output from \-\-json or \-\-csv Display values in bytes instead of bits. Does not affect the image generated by \-\-share
.RE .RE
\fB\-\-share\fR \fB\-\-share\fR
.RS .RS
Generate and provide a URL to the speedtest.net share results image, not displayed with \-\-csv Generate and provide a URL to the speedtest.net share results image
.RE .RE
\fB\-\-simple\fR \fB\-\-simple\fR
@ -53,12 +43,12 @@ Suppress verbose output, only show basic information
Suppress verbose output, only show basic information in CSV format. Speeds listed in bit/s and not affected by \-\-bytes Suppress verbose output, only show basic information in CSV format. Speeds listed in bit/s and not affected by \-\-bytes
.RE .RE
\fB\-\-csv\-delimiter CSV_DELIMITER\fR \fB\-\-csv-delimiter CSV_DELIMITER\fR
.RS .RS
Single character delimiter to use in CSV output. Default "," Single character delimiter to use in CSV output. Default ","
.RE .RE
\fB\-\-csv\-header\fR \fB\-\-csv-header\fR
.RS .RS
Print CSV headers Print CSV headers
.RE .RE
@ -75,12 +65,7 @@ Display a list of speedtest.net servers sorted by distance
\fB\-\-server SERVER\fR \fB\-\-server SERVER\fR
.RS .RS
Specify a server ID to test against. Can be supplied multiple times Specify a server ID to test against
.RE
\fB\-\-exclude EXCLUDE\fR
.RS
Exclude a server from selection. Can be supplied multiple times
.RE .RE
\fB\-\-mini MINI\fR \fB\-\-mini MINI\fR
@ -103,11 +88,6 @@ HTTP timeout in seconds. Default 10
Use HTTPS instead of HTTP when communicating with speedtest.net operated servers Use HTTPS instead of HTTP when communicating with speedtest.net operated servers
.RE .RE
\fB\-\-no\-pre\-allocate\fR
.RS
Do not pre allocate upload data. Pre allocation is enabled by default to improve upload performance. To support systems with insufficient memory, use this option to avoid a MemoryError
.RE
\fB\-\-version\fR \fB\-\-version\fR
.RS .RS
Show the version number and exit Show the version number and exit

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2012-2018 Matt Martz # Copyright 2012-2016 Matt Martz
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -36,7 +36,7 @@ except ImportError:
gzip = None gzip = None
GZIP_BASE = object GZIP_BASE = object
__version__ = '2.0.2' __version__ = '2.0.0a'
class FakeShutdownEvent(object): class FakeShutdownEvent(object):
@ -51,6 +51,7 @@ class FakeShutdownEvent(object):
# Some global variables we use # Some global variables we use
SHUTDOWN_EVENT = FakeShutdownEvent()
DEBUG = False DEBUG = False
_GLOBAL_DEFAULT_TIMEOUT = object() _GLOBAL_DEFAULT_TIMEOUT = object()
@ -70,7 +71,6 @@ except ImportError:
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
except ImportError: except ImportError:
from xml.dom import minidom as DOM from xml.dom import minidom as DOM
from xml.parsers.expat import ExpatError
ET = None ET = None
try: try:
@ -85,9 +85,9 @@ except ImportError:
HTTPErrorProcessor, OpenerDirector) HTTPErrorProcessor, OpenerDirector)
try: try:
from httplib import HTTPConnection, BadStatusLine from httplib import HTTPConnection
except ImportError: except ImportError:
from http.client import HTTPConnection, BadStatusLine from http.client import HTTPConnection
try: try:
from httplib import HTTPSConnection from httplib import HTTPSConnection
@ -166,14 +166,8 @@ except ImportError:
self.flush() self.flush()
_py3_print = getattr(builtins, 'print') _py3_print = getattr(builtins, 'print')
try: _py3_utf8_stdout = _Py3Utf8Output(sys.stdout)
_py3_utf8_stdout = _Py3Utf8Output(sys.stdout) _py3_utf8_stderr = _Py3Utf8Output(sys.stderr)
_py3_utf8_stderr = _Py3Utf8Output(sys.stderr)
except OSError:
# sys.stdout/sys.stderr is not a compatible stdout/stderr object
# just use it and hope things go ok
_py3_utf8_stdout = sys.stdout
_py3_utf8_stderr = sys.stderr
def to_utf8(v): def to_utf8(v):
"""No-op encode to utf-8 for py3""" """No-op encode to utf-8 for py3"""
@ -266,13 +260,10 @@ try:
except AttributeError: except AttributeError:
CERT_ERROR = tuple() CERT_ERROR = tuple()
HTTP_ERRORS = ( HTTP_ERRORS = ((HTTPError, URLError, socket.error, ssl.SSLError) +
(HTTPError, URLError, socket.error, ssl.SSLError, BadStatusLine) + CERT_ERROR)
CERT_ERROR
)
except ImportError: except ImportError:
ssl = None HTTP_ERRORS = (HTTPError, URLError, socket.error)
HTTP_ERRORS = (HTTPError, URLError, socket.error, BadStatusLine)
class SpeedtestException(Exception): class SpeedtestException(Exception):
@ -288,11 +279,7 @@ class SpeedtestHTTPError(SpeedtestException):
class SpeedtestConfigError(SpeedtestException): class SpeedtestConfigError(SpeedtestException):
"""Configuration XML is invalid""" """Configuration provided is invalid"""
class SpeedtestServersError(SpeedtestException):
"""Servers XML is invalid"""
class ConfigRetrievalError(SpeedtestHTTPError): class ConfigRetrievalError(SpeedtestHTTPError):
@ -392,11 +379,13 @@ class SpeedtestHTTPConnection(HTTPConnection):
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
source_address = kwargs.pop('source_address', None) source_address = kwargs.pop('source_address', None)
context = kwargs.pop('context', None)
timeout = kwargs.pop('timeout', 10) timeout = kwargs.pop('timeout', 10)
HTTPConnection.__init__(self, *args, **kwargs) HTTPConnection.__init__(self, *args, **kwargs)
self.source_address = source_address self.source_address = source_address
self._context = context
self.timeout = timeout self.timeout = timeout
def connect(self): def connect(self):
@ -421,28 +410,16 @@ if HTTPSConnection:
"""Custom HTTPSConnection to support source_address across """Custom HTTPSConnection to support source_address across
Python 2.4 - Python 3 Python 2.4 - Python 3
""" """
def __init__(self, *args, **kwargs):
source_address = kwargs.pop('source_address', None)
timeout = kwargs.pop('timeout', 10)
HTTPSConnection.__init__(self, *args, **kwargs)
self.timeout = timeout
self.source_address = source_address
def connect(self): def connect(self):
"Connect to a host on a given (SSL) port." "Connect to a host on a given (SSL) port."
SpeedtestHTTPConnection.connect(self) SpeedtestHTTPConnection.connect(self)
kwargs = {} kwargs = {}
if ssl: if hasattr(ssl, 'SSLContext'):
if hasattr(ssl, 'SSLContext'): kwargs['server_hostname'] = self.host
kwargs['server_hostname'] = self.host
try: self.sock = self._context.wrap_socket(self.sock, **kwargs)
self.sock = self._context.wrap_socket(self.sock, **kwargs)
except AttributeError:
self.sock = ssl.wrap_socket(self.sock, **kwargs)
def _build_connection(connection, source_address, timeout, context=None): def _build_connection(connection, source_address, timeout, context=None):
@ -700,19 +677,18 @@ def get_attributes_by_tag_name(dom, tag_name):
return dict(list(elem.attributes.items())) return dict(list(elem.attributes.items()))
def print_dots(shutdown_event): def print_dots(current, total, start=False, end=False):
"""Built in callback function used by Thread classes for printing """Built in callback function used by Thread classes for printing
status status
""" """
def inner(current, total, start=False, end=False):
if shutdown_event.isSet():
return
sys.stdout.write('.') if SHUTDOWN_EVENT.isSet():
if current + 1 == total and end is True: return
sys.stdout.write('\n')
sys.stdout.flush() sys.stdout.write('.')
return inner if current + 1 == total and end is True:
sys.stdout.write('\n')
sys.stdout.flush()
def do_nothing(*args, **kwargs): def do_nothing(*args, **kwargs):
@ -722,8 +698,7 @@ def do_nothing(*args, **kwargs):
class HTTPDownloader(threading.Thread): class HTTPDownloader(threading.Thread):
"""Thread class for retrieving a URL""" """Thread class for retrieving a URL"""
def __init__(self, i, request, start, timeout, opener=None, def __init__(self, i, request, start, timeout, opener=None):
shutdown_event=None):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.request = request self.request = request
self.result = [0] self.result = [0]
@ -735,16 +710,11 @@ class HTTPDownloader(threading.Thread):
else: else:
self._opener = urlopen self._opener = urlopen
if shutdown_event:
self._shutdown_event = shutdown_event
else:
self._shutdown_event = FakeShutdownEvent()
def run(self): def run(self):
try: try:
if (timeit.default_timer() - self.starttime) <= self.timeout: if (timeit.default_timer() - self.starttime) <= self.timeout:
f = self._opener(self.request) f = self._opener(self.request)
while (not self._shutdown_event.isSet() and while (not SHUTDOWN_EVENT.isSet() and
(timeit.default_timer() - self.starttime) <= (timeit.default_timer() - self.starttime) <=
self.timeout): self.timeout):
self.result.append(len(f.read(10240))) self.result.append(len(f.read(10240)))
@ -760,16 +730,11 @@ class HTTPUploaderData(object):
has been reached has been reached
""" """
def __init__(self, length, start, timeout, shutdown_event=None): def __init__(self, length, start, timeout):
self.length = length self.length = length
self.start = start self.start = start
self.timeout = timeout self.timeout = timeout
if shutdown_event:
self._shutdown_event = shutdown_event
else:
self._shutdown_event = FakeShutdownEvent()
self._data = None self._data = None
self.total = [0] self.total = [0]
@ -798,7 +763,7 @@ class HTTPUploaderData(object):
def read(self, n=10240): def read(self, n=10240):
if ((timeit.default_timer() - self.start) <= self.timeout and if ((timeit.default_timer() - self.start) <= self.timeout and
not self._shutdown_event.isSet()): not SHUTDOWN_EVENT.isSet()):
chunk = self.data.read(n) chunk = self.data.read(n)
self.total.append(len(chunk)) self.total.append(len(chunk))
return chunk return chunk
@ -812,8 +777,7 @@ class HTTPUploaderData(object):
class HTTPUploader(threading.Thread): class HTTPUploader(threading.Thread):
"""Thread class for putting a URL""" """Thread class for putting a URL"""
def __init__(self, i, request, start, size, timeout, opener=None, def __init__(self, i, request, start, size, timeout, opener=None):
shutdown_event=None):
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.request = request self.request = request
self.request.data.start = self.starttime = start self.request.data.start = self.starttime = start
@ -827,16 +791,11 @@ class HTTPUploader(threading.Thread):
else: else:
self._opener = urlopen self._opener = urlopen
if shutdown_event:
self._shutdown_event = shutdown_event
else:
self._shutdown_event = FakeShutdownEvent()
def run(self): def run(self):
request = self.request request = self.request
try: try:
if ((timeit.default_timer() - self.starttime) <= self.timeout and if ((timeit.default_timer() - self.starttime) <= self.timeout and
not self._shutdown_event.isSet()): not SHUTDOWN_EVENT.isSet()):
try: try:
f = self._opener(request) f = self._opener(request)
except TypeError: except TypeError:
@ -1010,7 +969,7 @@ class Speedtest(object):
"""Class for performing standard speedtest.net testing operations""" """Class for performing standard speedtest.net testing operations"""
def __init__(self, config=None, source_address=None, timeout=10, def __init__(self, config=None, source_address=None, timeout=10,
secure=False, shutdown_event=None): secure=False):
self.config = {} self.config = {}
self._source_address = source_address self._source_address = source_address
@ -1019,11 +978,6 @@ class Speedtest(object):
self._secure = secure self._secure = secure
if shutdown_event:
self._shutdown_event = shutdown_event
else:
self._shutdown_event = FakeShutdownEvent()
self.get_config() self.get_config()
if config is not None: if config is not None:
self.config.update(config) self.config.update(config)
@ -1060,16 +1014,13 @@ class Speedtest(object):
uh, e = catch_request(request, opener=self._opener) uh, e = catch_request(request, opener=self._opener)
if e: if e:
raise ConfigRetrievalError(e) raise ConfigRetrievalError(e)
configxml_list = [] configxml = []
stream = get_response_stream(uh) stream = get_response_stream(uh)
while 1: while 1:
try: configxml.append(stream.read(1024))
configxml_list.append(stream.read(1024)) if len(configxml[-1]) == 0:
except (OSError, EOFError):
raise ConfigRetrievalError(get_exception())
if len(configxml_list[-1]) == 0:
break break
stream.close() stream.close()
uh.close() uh.close()
@ -1077,18 +1028,10 @@ class Speedtest(object):
if int(uh.code) != 200: if int(uh.code) != 200:
return None return None
configxml = ''.encode().join(configxml_list) printer('Config XML:\n%s' % ''.encode().join(configxml), debug=True)
printer('Config XML:\n%s' % configxml, debug=True)
try: try:
try: root = ET.fromstring(''.encode().join(configxml))
root = ET.fromstring(configxml)
except ET.ParseError:
e = get_exception()
raise SpeedtestConfigError(
'Malformed speedtest.net configuration: %s' % e
)
server_config = root.find('server-config').attrib server_config = root.find('server-config').attrib
download = root.find('download').attrib download = root.find('download').attrib
upload = root.find('upload').attrib upload = root.find('upload').attrib
@ -1096,13 +1039,7 @@ class Speedtest(object):
client = root.find('client').attrib client = root.find('client').attrib
except AttributeError: except AttributeError:
try: root = DOM.parseString(''.join(configxml))
root = DOM.parseString(configxml)
except ExpatError:
e = get_exception()
raise SpeedtestConfigError(
'Malformed speedtest.net configuration: %s' % e
)
server_config = get_attributes_by_tag_name(root, 'server-config') server_config = get_attributes_by_tag_name(root, 'server-config')
download = get_attributes_by_tag_name(root, 'download') download = get_attributes_by_tag_name(root, 'download')
upload = get_attributes_by_tag_name(root, 'upload') upload = get_attributes_by_tag_name(root, 'upload')
@ -1151,13 +1088,7 @@ class Speedtest(object):
'upload_max': upload_count * size_count 'upload_max': upload_count * size_count
}) })
try: self.lat_lon = (float(client['lat']), float(client['lon']))
self.lat_lon = (float(client['lat']), float(client['lon']))
except ValueError:
raise SpeedtestConfigError(
'Unknown location: lat=%r lon=%r' %
(client.get('lat'), client.get('lon'))
)
printer('Config:\n%r' % self.config, debug=True) printer('Config:\n%r' % self.config, debug=True)
@ -1211,13 +1142,10 @@ class Speedtest(object):
stream = get_response_stream(uh) stream = get_response_stream(uh)
serversxml_list = [] serversxml = []
while 1: while 1:
try: serversxml.append(stream.read(1024))
serversxml_list.append(stream.read(1024)) if len(serversxml[-1]) == 0:
except (OSError, EOFError):
raise ServersRetrievalError(get_exception())
if len(serversxml_list[-1]) == 0:
break break
stream.close() stream.close()
@ -1226,28 +1154,15 @@ class Speedtest(object):
if int(uh.code) != 200: if int(uh.code) != 200:
raise ServersRetrievalError() raise ServersRetrievalError()
serversxml = ''.encode().join(serversxml_list) printer('Servers XML:\n%s' % ''.encode().join(serversxml),
debug=True)
printer('Servers XML:\n%s' % serversxml, debug=True)
try: try:
try: try:
try: root = ET.fromstring(''.encode().join(serversxml))
root = ET.fromstring(serversxml)
except ET.ParseError:
e = get_exception()
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)
elements = root.getiterator('server') elements = root.getiterator('server')
except AttributeError: except AttributeError:
try: root = DOM.parseString(''.join(serversxml))
root = DOM.parseString(serversxml)
except ExpatError:
e = get_exception()
raise SpeedtestServersError(
'Malformed speedtest.net server list: %s' % e
)
elements = root.getElementsByTagName('server') elements = root.getElementsByTagName('server')
except (SyntaxError, xml.parsers.expat.ExpatError): except (SyntaxError, xml.parsers.expat.ExpatError):
raise ServersRetrievalError() raise ServersRetrievalError()
@ -1457,14 +1372,9 @@ class Speedtest(object):
def producer(q, requests, request_count): def producer(q, requests, request_count):
for i, request in enumerate(requests): for i, request in enumerate(requests):
thread = HTTPDownloader( thread = HTTPDownloader(i, request, start,
i, self.config['length']['download'],
request, opener=self._opener)
start,
self.config['length']['download'],
opener=self._opener,
shutdown_event=self._shutdown_event
)
thread.start() thread.start()
q.put(thread, True) q.put(thread, True)
callback(i, request_count, start=True) callback(i, request_count, start=True)
@ -1517,12 +1427,7 @@ class Speedtest(object):
for i, size in enumerate(sizes): for i, size in enumerate(sizes):
# We set ``0`` for ``start`` and handle setting the actual # We set ``0`` for ``start`` and handle setting the actual
# ``start`` in ``HTTPUploader`` to get better measurements # ``start`` in ``HTTPUploader`` to get better measurements
data = HTTPUploaderData( data = HTTPUploaderData(size, 0, self.config['length']['upload'])
size,
0,
self.config['length']['upload'],
shutdown_event=self._shutdown_event
)
if pre_allocate: if pre_allocate:
data.pre_allocate() data.pre_allocate()
requests.append( requests.append(
@ -1534,15 +1439,9 @@ class Speedtest(object):
def producer(q, requests, request_count): def producer(q, requests, request_count):
for i, request in enumerate(requests[:request_count]): for i, request in enumerate(requests[:request_count]):
thread = HTTPUploader( thread = HTTPUploader(i, request[0], start, request[1],
i, self.config['length']['upload'],
request[0], opener=self._opener)
start,
request[1],
self.config['length']['upload'],
opener=self._opener,
shutdown_event=self._shutdown_event
)
thread.start() thread.start()
q.put(thread, True) q.put(thread, True)
callback(i, request_count, start=True) callback(i, request_count, start=True)
@ -1578,28 +1477,27 @@ class Speedtest(object):
return self.results.upload return self.results.upload
def ctrl_c(shutdown_event): def ctrl_c(signum, frame):
"""Catch Ctrl-C key sequence and set a SHUTDOWN_EVENT for our threaded """Catch Ctrl-C key sequence and set a SHUTDOWN_EVENT for our threaded
operations operations
""" """
def inner(signum, frame):
shutdown_event.set() SHUTDOWN_EVENT.set()
printer('\nCancelling...', error=True) print_('\nCancelling...')
sys.exit(0) sys.exit(0)
return inner
def version(): def version():
"""Print the version""" """Print the version"""
printer(__version__) print_(__version__)
sys.exit(0) sys.exit(0)
def csv_header(delimiter=','): def csv_header(delimiter=','):
"""Print the CSV Headers""" """Print the CSV Headers"""
printer(SpeedtestResults.csv_header(delimiter=delimiter)) print_(SpeedtestResults.csv_header(delimiter=delimiter))
sys.exit(0) sys.exit(0)
@ -1704,16 +1602,13 @@ def validate_optional_args(args):
def printer(string, quiet=False, debug=False, error=False, **kwargs): def printer(string, quiet=False, debug=False, error=False, **kwargs):
"""Helper function print a string with various features""" """Helper function to print a string only when not quiet"""
if debug and not DEBUG: if debug and not DEBUG:
return return
if debug: if debug:
if sys.stdout.isatty(): out = '\033[1;30mDEBUG: %s\033[0m' % string
out = '\033[1;30mDEBUG: %s\033[0m' % string
else:
out = 'DEBUG: %s' % string
else: else:
out = string out = string
@ -1727,10 +1622,10 @@ def printer(string, quiet=False, debug=False, error=False, **kwargs):
def shell(): def shell():
"""Run the full speedtest.net test""" """Run the full speedtest.net test"""
global DEBUG global SHUTDOWN_EVENT, DEBUG
shutdown_event = threading.Event() SHUTDOWN_EVENT = threading.Event()
signal.signal(signal.SIGINT, ctrl_c(shutdown_event)) signal.signal(signal.SIGINT, ctrl_c)
args = parse_args() args = parse_args()
@ -1770,7 +1665,7 @@ def shell():
if quiet or debug: if quiet or debug:
callback = do_nothing callback = do_nothing
else: else:
callback = print_dots(shutdown_event) callback = print_dots
printer('Retrieving speedtest.net configuration...', quiet) printer('Retrieving speedtest.net configuration...', quiet)
try: try:
@ -1795,7 +1690,7 @@ def shell():
line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s) ' line = ('%(id)5s) %(sponsor)s (%(name)s, %(country)s) '
'[%(d)0.2f km]' % server) '[%(d)0.2f km]' % server)
try: try:
printer(line) print_(line)
except IOError: except IOError:
e = get_exception() e = get_exception()
if e.errno != errno.EPIPE: if e.errno != errno.EPIPE:
@ -1845,7 +1740,7 @@ def shell():
args.units[0]), args.units[0]),
quiet) quiet)
else: else:
printer('Skipping download test', quiet) printer('Skipping download test')
if args.upload: if args.upload:
printer('Testing upload speed', quiet, printer('Testing upload speed', quiet,
@ -1856,24 +1751,23 @@ def shell():
args.units[0]), args.units[0]),
quiet) quiet)
else: else:
printer('Skipping upload test', quiet) printer('Skipping upload test')
printer('Results:\n%r' % results.dict(), debug=True) printer('Results:\n%r' % results.dict(), debug=True)
if not args.simple and args.share:
results.share()
if args.simple: if args.simple:
printer('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' % print_('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' %
(results.ping, (results.ping,
(results.download / 1000.0 / 1000.0) / args.units[1], (results.download / 1000.0 / 1000.0) / args.units[1],
args.units[0], args.units[0],
(results.upload / 1000.0 / 1000.0) / args.units[1], (results.upload / 1000.0 / 1000.0) / args.units[1],
args.units[0])) args.units[0]))
elif args.csv: elif args.csv:
printer(results.csv(delimiter=args.csv_delimiter)) print_(results.csv(delimiter=args.csv_delimiter))
elif args.json: elif args.json:
printer(results.json()) if args.share:
results.share()
print_(results.json())
if args.share and not machine_format: if args.share and not machine_format:
printer('Share results: %s' % results.share()) printer('Share results: %s' % results.share())
@ -1883,11 +1777,10 @@ def main():
try: try:
shell() shell()
except KeyboardInterrupt: except KeyboardInterrupt:
printer('\nCancelling...', error=True) print_('\nCancelling...')
except (SpeedtestException, SystemExit): except (SpeedtestException, SystemExit):
e = get_exception() e = get_exception()
# Ignore a successful exit, or argparse exit if getattr(e, 'code', 1) != 0:
if getattr(e, 'code', 1) not in (0, 2):
raise SystemExit('ERROR: %s' % e) raise SystemExit('ERROR: %s' % e)

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright 2018 Matt Martz # Copyright 2012-2016 Matt Martz
# All Rights Reserved. # All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -15,23 +15,20 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import sys import warnings
import subprocess
cmd = [sys.executable, 'speedtest.py', '--source', '127.0.0.1'] DEPRECATED_MSG = ('The file speedtest_cli.py has been deprecated in favor of '
'speedtest.py\nand is available for download at:\n\n'
'https://raw.githubusercontent.com/sivel/speedtest-cli/'
'master/speedtest.py')
p = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout, stderr = p.communicate() if __name__ == '__main__':
raise SystemExit(DEPRECATED_MSG)
if p.returncode != 1: else:
raise SystemExit('%s did not fail with exit code 1' % ' '.join(cmd)) try:
from speedtest import *
if 'Invalid argument'.encode() not in stderr: except ImportError:
raise SystemExit( raise SystemExit(DEPRECATED_MSG)
'"Invalid argument" not found in stderr:\n%s' % stderr.decode() else:
) warnings.warn(DEPRECATED_MSG, UserWarning)

View File

@ -6,8 +6,6 @@ commands =
{envpython} -V {envpython} -V
{envpython} -m compileall speedtest.py {envpython} -m compileall speedtest.py
{envpython} speedtest.py {envpython} speedtest.py
{envpython} speedtest.py --source 172.17.0.1
{envpython} tests/scripts/source.py
[testenv:flake8] [testenv:flake8]
basepython=python basepython=python
@ -21,5 +19,3 @@ commands =
pypy -V pypy -V
pypy -m compileall speedtest.py pypy -m compileall speedtest.py
pypy speedtest.py pypy speedtest.py
pypy speedtest.py --source 172.17.0.1
pypy tests/scripts/source.py