Compare commits
	
		
			36 Commits
		
	
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 22210ca352 | |
|  | 42e96b13dd | |
|  | cadc68b5ae | |
|  | db46af8bcd | |
|  | c58ad3367b | |
|  | 266e53c256 | |
|  | 7ebb9965dd | |
|  | 2658bd50b4 | |
|  | 81bba6070c | |
|  | 681cdf20a5 | |
|  | cdf6002865 | |
|  | 9af203652b | |
|  | 2d5a9ef364 | |
|  | 3109fcf407 | |
|  | 69ddff1a11 | |
|  | fb0569946d | |
|  | f356c7b02d | |
|  | 6cf43b2ff7 | |
|  | 217ce8eff1 | |
|  | b43334f1ec | |
|  | b0b826c870 | |
|  | 9ac1091eae | |
|  | ca2250f700 | |
|  | ddb8db0c94 | |
|  | 72bf53affa | |
|  | a8a3265001 | |
|  | b2654de410 | |
|  | 72ed585c6f | |
|  | 41e599f9c3 | |
|  | c7530bb143 | |
|  | 4ceae77401 | |
|  | 9e185e8f88 | |
|  | 9c2977acfc | |
|  | f8aa20ecdf | |
|  | 8ff923b0fb | |
|  | 35c3ee20ed | 
							
								
								
									
										12
									
								
								.travis.yml
								
								
								
								
							
							
						
						
									
										12
									
								
								.travis.yml
								
								
								
								
							|  | @ -1,4 +1,6 @@ | ||||||
| language: python | language: python | ||||||
|  | sudo: required | ||||||
|  | dist: xenial | ||||||
| 
 | 
 | ||||||
| addons: | addons: | ||||||
|   apt: |   apt: | ||||||
|  | @ -33,8 +35,13 @@ matrix: | ||||||
|       env: TOXENV=py35 |       env: TOXENV=py35 | ||||||
|     - python: 3.6 |     - python: 3.6 | ||||||
|       env: TOXENV=py36 |       env: TOXENV=py36 | ||||||
|  |     - python: 3.7 | ||||||
|  |       env: TOXENV=py37 | ||||||
|  |     - python: 3.8-dev | ||||||
|  |       env: TOXENV=py38 | ||||||
|     - python: pypy |     - python: pypy | ||||||
|       env: TOXENV=pypy |       env: TOXENV=pypy | ||||||
|  |       dist: trusty | ||||||
| 
 | 
 | ||||||
| before_install: | before_install: | ||||||
|   - 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; | ||||||
|  | @ -42,7 +49,10 @@ before_install: | ||||||
| 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 "(py2[45]|py3[12])") == 0 ]]; then pip install tox; 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[456]|py3[123])") == 0 ]]; then pip install tox; fi; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| script: | script: | ||||||
|   - tox |   - tox | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								README.rst
								
								
								
								
							
							
						
						
									
										17
									
								
								README.rst
								
								
								
								
							|  | @ -51,7 +51,8 @@ or | ||||||
| :: | :: | ||||||
| 
 | 
 | ||||||
|     git clone https://github.com/sivel/speedtest-cli.git |     git clone https://github.com/sivel/speedtest-cli.git | ||||||
|     python speedtest-cli/setup.py install |     cd speedtest-cli | ||||||
|  |     python setup.py install | ||||||
| 
 | 
 | ||||||
| Just download (Like the way it used to be) | Just download (Like the way it used to be) | ||||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  | @ -74,12 +75,12 @@ Usage | ||||||
| :: | :: | ||||||
| 
 | 
 | ||||||
|     $ speedtest-cli -h |     $ speedtest-cli -h | ||||||
|     usage: speedtest-cli [-h] [--no-download] [--no-upload] [--bytes] [--share] |     usage: speedtest-cli [-h] [--no-download] [--no-upload] [--single] [--bytes] | ||||||
|                          [--simple] [--csv] [--csv-delimiter CSV_DELIMITER] |                          [--share] [--simple] [--csv] | ||||||
|                          [--csv-header] [--json] [--list] [--server SERVER] |                          [--csv-delimiter CSV_DELIMITER] [--csv-header] [--json] | ||||||
|                          [--exclude EXCLUDE] [--mini MINI] [--source SOURCE] |                          [--list] [--server SERVER] [--exclude EXCLUDE] | ||||||
|                          [--timeout TIMEOUT] [--secure] [--no-pre-allocate] |                          [--mini MINI] [--source SOURCE] [--timeout TIMEOUT] | ||||||
|                          [--version] |                          [--secure] [--no-pre-allocate] [--version] | ||||||
| 
 | 
 | ||||||
|     Command line interface for testing internet bandwidth using speedtest.net. |     Command line interface for testing internet bandwidth using speedtest.net. | ||||||
|     -------------------------------------------------------------------------- |     -------------------------------------------------------------------------- | ||||||
|  | @ -89,6 +90,8 @@ Usage | ||||||
|       -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 | ||||||
|       --no-upload           Do not perform upload test |       --no-upload           Do not perform upload test | ||||||
|  |       --single              Only use a single connection instead of multiple. This | ||||||
|  |                             simulates a typical file transfer. | ||||||
|       --bytes               Display values in bytes instead of bits. Does not |       --bytes               Display values in bytes instead of bits. Does not | ||||||
|                             affect the image generated by --share, nor output from |                             affect the image generated by --share, nor output from | ||||||
|                             --json or --csv |                             --json or --csv | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										5
									
								
								setup.py
								
								
								
								
							|  | @ -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 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 | ||||||
|  | @ -92,5 +92,8 @@ setup( | ||||||
|         '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', |         'Programming Language :: Python :: 3.7', | ||||||
|  |         'Programming Language :: Python :: 3.8', | ||||||
|  |         'Programming Language :: Python :: 3.9', | ||||||
|  |         'Programming Language :: Python :: 3.10', | ||||||
|     ] |     ] | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -33,6 +33,11 @@ Do not perform download test | ||||||
| Do not perform upload test | Do not perform upload test | ||||||
| .RE | .RE | ||||||
| 
 | 
 | ||||||
|  | \fB\-\-single\fR | ||||||
|  | .RS | ||||||
|  | Only use a single connection instead of multiple. This simulates a typical file transfer. | ||||||
|  | .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, nor output from \-\-json or \-\-csv | ||||||
|  |  | ||||||
							
								
								
									
										320
									
								
								speedtest.py
								
								
								
								
							
							
						
						
									
										320
									
								
								speedtest.py
								
								
								
								
							|  | @ -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 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,18 +15,18 @@ | ||||||
| #    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 os |  | ||||||
| import re |  | ||||||
| import csv | import csv | ||||||
| import sys | import datetime | ||||||
| import math |  | ||||||
| import errno | import errno | ||||||
|  | import math | ||||||
|  | import os | ||||||
|  | import platform | ||||||
|  | import re | ||||||
| import signal | import signal | ||||||
| import socket | import socket | ||||||
| import timeit | import sys | ||||||
| import datetime |  | ||||||
| import platform |  | ||||||
| import threading | import threading | ||||||
|  | import timeit | ||||||
| import xml.parsers.expat | import xml.parsers.expat | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|  | @ -36,7 +36,7 @@ except ImportError: | ||||||
|     gzip = None |     gzip = None | ||||||
|     GZIP_BASE = object |     GZIP_BASE = object | ||||||
| 
 | 
 | ||||||
| __version__ = '2.0.0' | __version__ = '2.1.4b1' | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class FakeShutdownEvent(object): | class FakeShutdownEvent(object): | ||||||
|  | @ -49,10 +49,16 @@ class FakeShutdownEvent(object): | ||||||
|         "Dummy method to always return false""" |         "Dummy method to always return false""" | ||||||
|         return False |         return False | ||||||
| 
 | 
 | ||||||
|  |     is_set = isSet | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # Some global variables we use | # Some global variables we use | ||||||
| DEBUG = False | DEBUG = False | ||||||
| _GLOBAL_DEFAULT_TIMEOUT = object() | _GLOBAL_DEFAULT_TIMEOUT = object() | ||||||
|  | PY25PLUS = sys.version_info[:2] >= (2, 5) | ||||||
|  | PY26PLUS = sys.version_info[:2] >= (2, 6) | ||||||
|  | PY32PLUS = sys.version_info[:2] >= (3, 2) | ||||||
|  | PY310PLUS = sys.version_info[:2] >= (3, 10) | ||||||
| 
 | 
 | ||||||
| # Begin import game to handle Python 2 and Python 3 | # Begin import game to handle Python 2 and Python 3 | ||||||
| try: | try: | ||||||
|  | @ -63,13 +69,15 @@ except ImportError: | ||||||
|     except ImportError: |     except ImportError: | ||||||
|         json = None |         json = None | ||||||
| 
 | 
 | ||||||
| try: |  | ||||||
|     import xml.etree.cElementTree as ET |  | ||||||
| except ImportError: |  | ||||||
| try: | try: | ||||||
|     import xml.etree.ElementTree as ET |     import xml.etree.ElementTree as ET | ||||||
|  |     try: | ||||||
|  |         from xml.etree.ElementTree import _Element as ET_Element | ||||||
|  |     except ImportError: | ||||||
|  |         pass | ||||||
| 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: | ||||||
|  | @ -84,9 +92,9 @@ except ImportError: | ||||||
|                                 HTTPErrorProcessor, OpenerDirector) |                                 HTTPErrorProcessor, OpenerDirector) | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|     from httplib import HTTPConnection |     from httplib import HTTPConnection, BadStatusLine | ||||||
| except ImportError: | except ImportError: | ||||||
|     from http.client import HTTPConnection |     from http.client import HTTPConnection, BadStatusLine | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|     from httplib import HTTPSConnection |     from httplib import HTTPSConnection | ||||||
|  | @ -96,6 +104,11 @@ except ImportError: | ||||||
|     except ImportError: |     except ImportError: | ||||||
|         HTTPSConnection = None |         HTTPSConnection = None | ||||||
| 
 | 
 | ||||||
|  | try: | ||||||
|  |     from httplib import FakeSocket | ||||||
|  | except ImportError: | ||||||
|  |     FakeSocket = None | ||||||
|  | 
 | ||||||
| try: | try: | ||||||
|     from Queue import Queue |     from Queue import Queue | ||||||
| except ImportError: | except ImportError: | ||||||
|  | @ -256,7 +269,6 @@ else: | ||||||
|             write(arg) |             write(arg) | ||||||
|         write(end) |         write(end) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| # Exception "constants" to support Python 2 through Python 3 | # Exception "constants" to support Python 2 through Python 3 | ||||||
| try: | try: | ||||||
|     import ssl |     import ssl | ||||||
|  | @ -265,10 +277,30 @@ try: | ||||||
|     except AttributeError: |     except AttributeError: | ||||||
|         CERT_ERROR = tuple() |         CERT_ERROR = tuple() | ||||||
| 
 | 
 | ||||||
|     HTTP_ERRORS = ((HTTPError, URLError, socket.error, ssl.SSLError) + |     HTTP_ERRORS = ( | ||||||
|                    CERT_ERROR) |         (HTTPError, URLError, socket.error, ssl.SSLError, BadStatusLine) + | ||||||
|  |         CERT_ERROR | ||||||
|  |     ) | ||||||
| except ImportError: | except ImportError: | ||||||
|     HTTP_ERRORS = (HTTPError, URLError, socket.error) |     ssl = None | ||||||
|  |     HTTP_ERRORS = (HTTPError, URLError, socket.error, BadStatusLine) | ||||||
|  | 
 | ||||||
|  | if PY32PLUS: | ||||||
|  |     etree_iter = ET.Element.iter | ||||||
|  | elif PY25PLUS: | ||||||
|  |     etree_iter = ET_Element.getiterator | ||||||
|  | 
 | ||||||
|  | if PY26PLUS: | ||||||
|  |     thread_is_alive = threading.Thread.is_alive | ||||||
|  | else: | ||||||
|  |     thread_is_alive = threading.Thread.isAlive | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def event_is_set(event): | ||||||
|  |     try: | ||||||
|  |         return event.is_set() | ||||||
|  |     except AttributeError: | ||||||
|  |         return event.isSet() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SpeedtestException(Exception): | class SpeedtestException(Exception): | ||||||
|  | @ -284,7 +316,11 @@ class SpeedtestHTTPError(SpeedtestException): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SpeedtestConfigError(SpeedtestException): | class SpeedtestConfigError(SpeedtestException): | ||||||
|     """Configuration provided is invalid""" |     """Configuration XML is invalid""" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SpeedtestServersError(SpeedtestException): | ||||||
|  |     """Servers XML is invalid""" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ConfigRetrievalError(SpeedtestHTTPError): | class ConfigRetrievalError(SpeedtestHTTPError): | ||||||
|  | @ -384,13 +420,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) | ||||||
| 
 | 
 | ||||||
|  |         self._tunnel_host = None | ||||||
|  | 
 | ||||||
|         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): | ||||||
|  | @ -408,23 +444,75 @@ class SpeedtestHTTPConnection(HTTPConnection): | ||||||
|                 self.source_address |                 self.source_address | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |         if self._tunnel_host: | ||||||
|  |             self._tunnel() | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| if HTTPSConnection: | if HTTPSConnection: | ||||||
|     class SpeedtestHTTPSConnection(HTTPSConnection, |     class SpeedtestHTTPSConnection(HTTPSConnection): | ||||||
|                                    SpeedtestHTTPConnection): |  | ||||||
|         """Custom HTTPSConnection to support source_address across |         """Custom HTTPSConnection to support source_address across | ||||||
|         Python 2.4 - Python 3 |         Python 2.4 - Python 3 | ||||||
|         """ |         """ | ||||||
|  |         default_port = 443 | ||||||
|  | 
 | ||||||
|  |         def __init__(self, *args, **kwargs): | ||||||
|  |             source_address = kwargs.pop('source_address', None) | ||||||
|  |             timeout = kwargs.pop('timeout', 10) | ||||||
|  | 
 | ||||||
|  |             self._tunnel_host = None | ||||||
|  | 
 | ||||||
|  |             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." | ||||||
|  |             try: | ||||||
|  |                 self.sock = socket.create_connection( | ||||||
|  |                     (self.host, self.port), | ||||||
|  |                     self.timeout, | ||||||
|  |                     self.source_address | ||||||
|  |                 ) | ||||||
|  |             except (AttributeError, TypeError): | ||||||
|  |                 self.sock = create_connection( | ||||||
|  |                     (self.host, self.port), | ||||||
|  |                     self.timeout, | ||||||
|  |                     self.source_address | ||||||
|  |                 ) | ||||||
| 
 | 
 | ||||||
|             SpeedtestHTTPConnection.connect(self) |             if self._tunnel_host: | ||||||
|  |                 self._tunnel() | ||||||
| 
 | 
 | ||||||
|  |             if ssl: | ||||||
|  |                 try: | ||||||
|                     kwargs = {} |                     kwargs = {} | ||||||
|                     if hasattr(ssl, 'SSLContext'): |                     if hasattr(ssl, 'SSLContext'): | ||||||
|  |                         if self._tunnel_host: | ||||||
|  |                             kwargs['server_hostname'] = self._tunnel_host | ||||||
|  |                         else: | ||||||
|                             kwargs['server_hostname'] = self.host |                             kwargs['server_hostname'] = self.host | ||||||
| 
 |  | ||||||
|                     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) | ||||||
|  |                     try: | ||||||
|  |                         self.sock.server_hostname = self.host | ||||||
|  |                     except AttributeError: | ||||||
|  |                         pass | ||||||
|  |             elif FakeSocket: | ||||||
|  |                 # Python 2.4/2.5 support | ||||||
|  |                 try: | ||||||
|  |                     self.sock = FakeSocket(self.sock, socket.ssl(self.sock)) | ||||||
|  |                 except AttributeError: | ||||||
|  |                     raise SpeedtestException( | ||||||
|  |                         'This version of Python does not support HTTPS/SSL ' | ||||||
|  |                         'functionality' | ||||||
|  |                     ) | ||||||
|  |             else: | ||||||
|  |                 raise SpeedtestException( | ||||||
|  |                     'This version of Python does not support HTTPS/SSL ' | ||||||
|  |                     'functionality' | ||||||
|  |                 ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def _build_connection(connection, source_address, timeout, context=None): | def _build_connection(connection, source_address, timeout, context=None): | ||||||
|  | @ -589,7 +677,8 @@ def build_user_agent(): | ||||||
| 
 | 
 | ||||||
|     ua_tuple = ( |     ua_tuple = ( | ||||||
|         'Mozilla/5.0', |         'Mozilla/5.0', | ||||||
|         '(%s; U; %s; en-us)' % (platform.system(), platform.architecture()[0]), |         '(%s; U; %s; en-us)' % (platform.platform(), | ||||||
|  |                                 platform.architecture()[0]), | ||||||
|         'Python/%s' % platform.python_version(), |         'Python/%s' % platform.python_version(), | ||||||
|         '(KHTML, like Gecko)', |         '(KHTML, like Gecko)', | ||||||
|         'speedtest-cli/%s' % __version__ |         'speedtest-cli/%s' % __version__ | ||||||
|  | @ -648,6 +737,8 @@ def catch_request(request, opener=None): | ||||||
| 
 | 
 | ||||||
|     try: |     try: | ||||||
|         uh = _open(request) |         uh = _open(request) | ||||||
|  |         if request.get_full_url() != uh.geturl(): | ||||||
|  |             printer('Redirected to %s' % uh.geturl(), debug=True) | ||||||
|         return uh, False |         return uh, False | ||||||
|     except HTTP_ERRORS: |     except HTTP_ERRORS: | ||||||
|         e = get_exception() |         e = get_exception() | ||||||
|  | @ -687,7 +778,7 @@ def print_dots(shutdown_event): | ||||||
|     status |     status | ||||||
|     """ |     """ | ||||||
|     def inner(current, total, start=False, end=False): |     def inner(current, total, start=False, end=False): | ||||||
|         if shutdown_event.isSet(): |         if event_is_set(shutdown_event): | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         sys.stdout.write('.') |         sys.stdout.write('.') | ||||||
|  | @ -726,7 +817,7 @@ class HTTPDownloader(threading.Thread): | ||||||
|         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 event_is_set(self._shutdown_event) 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))) | ||||||
|  | @ -735,6 +826,8 @@ class HTTPDownloader(threading.Thread): | ||||||
|                 f.close() |                 f.close() | ||||||
|         except IOError: |         except IOError: | ||||||
|             pass |             pass | ||||||
|  |         except HTTP_ERRORS: | ||||||
|  |             pass | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class HTTPUploaderData(object): | class HTTPUploaderData(object): | ||||||
|  | @ -780,7 +873,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 event_is_set(self._shutdown_event)): | ||||||
|             chunk = self.data.read(n) |             chunk = self.data.read(n) | ||||||
|             self.total.append(len(chunk)) |             self.total.append(len(chunk)) | ||||||
|             return chunk |             return chunk | ||||||
|  | @ -800,7 +893,7 @@ class HTTPUploader(threading.Thread): | ||||||
|         self.request = request |         self.request = request | ||||||
|         self.request.data.start = self.starttime = start |         self.request.data.start = self.starttime = start | ||||||
|         self.size = size |         self.size = size | ||||||
|         self.result = None |         self.result = 0 | ||||||
|         self.timeout = timeout |         self.timeout = timeout | ||||||
|         self.i = i |         self.i = i | ||||||
| 
 | 
 | ||||||
|  | @ -818,7 +911,7 @@ class HTTPUploader(threading.Thread): | ||||||
|         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 event_is_set(self._shutdown_event)): | ||||||
|                 try: |                 try: | ||||||
|                     f = self._opener(request) |                     f = self._opener(request) | ||||||
|                 except TypeError: |                 except TypeError: | ||||||
|  | @ -835,6 +928,8 @@ class HTTPUploader(threading.Thread): | ||||||
|                 self.result = 0 |                 self.result = 0 | ||||||
|         except (IOError, SpeedtestUploadTimeout): |         except (IOError, SpeedtestUploadTimeout): | ||||||
|             self.result = sum(self.request.data.total) |             self.result = sum(self.request.data.total) | ||||||
|  |         except HTTP_ERRORS: | ||||||
|  |             self.result = 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class SpeedtestResults(object): | class SpeedtestResults(object): | ||||||
|  | @ -1023,10 +1118,7 @@ class Speedtest(object): | ||||||
|     @property |     @property | ||||||
|     def best(self): |     def best(self): | ||||||
|         if not self._best: |         if not self._best: | ||||||
|             raise SpeedtestMissingBestServer( |             self.get_best_server() | ||||||
|                 'get_best_server not called or not able to determine best ' |  | ||||||
|                 'server' |  | ||||||
|             ) |  | ||||||
|         return self._best |         return self._best | ||||||
| 
 | 
 | ||||||
|     def get_config(self): |     def get_config(self): | ||||||
|  | @ -1042,16 +1134,16 @@ 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 = [] |         configxml_list = [] | ||||||
| 
 | 
 | ||||||
|         stream = get_response_stream(uh) |         stream = get_response_stream(uh) | ||||||
| 
 | 
 | ||||||
|         while 1: |         while 1: | ||||||
|             try: |             try: | ||||||
|                 configxml.append(stream.read(1024)) |                 configxml_list.append(stream.read(1024)) | ||||||
|             except (OSError, EOFError): |             except (OSError, EOFError): | ||||||
|                 raise ConfigRetrievalError(get_exception()) |                 raise ConfigRetrievalError(get_exception()) | ||||||
|             if len(configxml[-1]) == 0: |             if len(configxml_list[-1]) == 0: | ||||||
|                 break |                 break | ||||||
|         stream.close() |         stream.close() | ||||||
|         uh.close() |         uh.close() | ||||||
|  | @ -1059,10 +1151,18 @@ class Speedtest(object): | ||||||
|         if int(uh.code) != 200: |         if int(uh.code) != 200: | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|         printer('Config XML:\n%s' % ''.encode().join(configxml), debug=True) |         configxml = ''.encode().join(configxml_list) | ||||||
|  | 
 | ||||||
|  |         printer('Config XML:\n%s' % configxml, debug=True) | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|             root = ET.fromstring(''.encode().join(configxml)) |             try: | ||||||
|  |                 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 | ||||||
|  | @ -1070,16 +1170,22 @@ class Speedtest(object): | ||||||
|             client = root.find('client').attrib |             client = root.find('client').attrib | ||||||
| 
 | 
 | ||||||
|         except AttributeError: |         except AttributeError: | ||||||
|             root = DOM.parseString(''.join(configxml)) |             try: | ||||||
|  |                 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') | ||||||
|             # times = get_attributes_by_tag_name(root, 'times') |             # times = get_attributes_by_tag_name(root, 'times') | ||||||
|             client = get_attributes_by_tag_name(root, 'client') |             client = get_attributes_by_tag_name(root, 'client') | ||||||
| 
 | 
 | ||||||
|         ignore_servers = list( |         ignore_servers = [ | ||||||
|             map(int, server_config['ignoreids'].split(',')) |             int(i) for i in server_config['ignoreids'].split(',') if i | ||||||
|         ) |         ] | ||||||
| 
 | 
 | ||||||
|         ratio = int(upload['ratio']) |         ratio = int(upload['ratio']) | ||||||
|         upload_max = int(upload['maxchunkcount']) |         upload_max = int(upload['maxchunkcount']) | ||||||
|  | @ -1119,7 +1225,13 @@ 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) | ||||||
| 
 | 
 | ||||||
|  | @ -1173,13 +1285,13 @@ class Speedtest(object): | ||||||
| 
 | 
 | ||||||
|                 stream = get_response_stream(uh) |                 stream = get_response_stream(uh) | ||||||
| 
 | 
 | ||||||
|                 serversxml = [] |                 serversxml_list = [] | ||||||
|                 while 1: |                 while 1: | ||||||
|                     try: |                     try: | ||||||
|                         serversxml.append(stream.read(1024)) |                         serversxml_list.append(stream.read(1024)) | ||||||
|                     except (OSError, EOFError): |                     except (OSError, EOFError): | ||||||
|                         raise ServersRetrievalError(get_exception()) |                         raise ServersRetrievalError(get_exception()) | ||||||
|                     if len(serversxml[-1]) == 0: |                     if len(serversxml_list[-1]) == 0: | ||||||
|                         break |                         break | ||||||
| 
 | 
 | ||||||
|                 stream.close() |                 stream.close() | ||||||
|  | @ -1188,15 +1300,28 @@ class Speedtest(object): | ||||||
|                 if int(uh.code) != 200: |                 if int(uh.code) != 200: | ||||||
|                     raise ServersRetrievalError() |                     raise ServersRetrievalError() | ||||||
| 
 | 
 | ||||||
|                 printer('Servers XML:\n%s' % ''.encode().join(serversxml), |                 serversxml = ''.encode().join(serversxml_list) | ||||||
|                         debug=True) | 
 | ||||||
|  |                 printer('Servers XML:\n%s' % serversxml, debug=True) | ||||||
| 
 | 
 | ||||||
|                 try: |                 try: | ||||||
|                     try: |                     try: | ||||||
|                         root = ET.fromstring(''.encode().join(serversxml)) |                         try: | ||||||
|                         elements = root.getiterator('server') |                             root = ET.fromstring(serversxml) | ||||||
|  |                         except ET.ParseError: | ||||||
|  |                             e = get_exception() | ||||||
|  |                             raise SpeedtestServersError( | ||||||
|  |                                 'Malformed speedtest.net server list: %s' % e | ||||||
|  |                             ) | ||||||
|  |                         elements = etree_iter(root, 'server') | ||||||
|                     except AttributeError: |                     except AttributeError: | ||||||
|                         root = DOM.parseString(''.join(serversxml)) |                         try: | ||||||
|  |                             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() | ||||||
|  | @ -1388,8 +1513,12 @@ class Speedtest(object): | ||||||
|         printer('Best Server:\n%r' % best, debug=True) |         printer('Best Server:\n%r' % best, debug=True) | ||||||
|         return best |         return best | ||||||
| 
 | 
 | ||||||
|     def download(self, callback=do_nothing): |     def download(self, callback=do_nothing, threads=None): | ||||||
|         """Test download speed against speedtest.net""" |         """Test download speed against speedtest.net | ||||||
|  | 
 | ||||||
|  |         A ``threads`` value of ``None`` will fall back to those dictated | ||||||
|  |         by the speedtest.net configuration | ||||||
|  |         """ | ||||||
| 
 | 
 | ||||||
|         urls = [] |         urls = [] | ||||||
|         for size in self.config['sizes']['download']: |         for size in self.config['sizes']['download']: | ||||||
|  | @ -1404,6 +1533,9 @@ class Speedtest(object): | ||||||
|                 build_request(url, bump=i, secure=self._secure) |                 build_request(url, bump=i, secure=self._secure) | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |         max_threads = threads or self.config['threads']['download'] | ||||||
|  |         in_flight = {'threads': 0} | ||||||
|  | 
 | ||||||
|         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( | ||||||
|  | @ -1414,21 +1546,26 @@ class Speedtest(object): | ||||||
|                     opener=self._opener, |                     opener=self._opener, | ||||||
|                     shutdown_event=self._shutdown_event |                     shutdown_event=self._shutdown_event | ||||||
|                 ) |                 ) | ||||||
|  |                 while in_flight['threads'] >= max_threads: | ||||||
|  |                     timeit.time.sleep(0.001) | ||||||
|                 thread.start() |                 thread.start() | ||||||
|                 q.put(thread, True) |                 q.put(thread, True) | ||||||
|  |                 in_flight['threads'] += 1 | ||||||
|                 callback(i, request_count, start=True) |                 callback(i, request_count, start=True) | ||||||
| 
 | 
 | ||||||
|         finished = [] |         finished = [] | ||||||
| 
 | 
 | ||||||
|         def consumer(q, request_count): |         def consumer(q, request_count): | ||||||
|  |             _is_alive = thread_is_alive | ||||||
|             while len(finished) < request_count: |             while len(finished) < request_count: | ||||||
|                 thread = q.get(True) |                 thread = q.get(True) | ||||||
|                 while thread.isAlive(): |                 while _is_alive(thread): | ||||||
|                     thread.join(timeout=0.1) |                     thread.join(timeout=0.001) | ||||||
|  |                 in_flight['threads'] -= 1 | ||||||
|                 finished.append(sum(thread.result)) |                 finished.append(sum(thread.result)) | ||||||
|                 callback(thread.i, request_count, end=True) |                 callback(thread.i, request_count, end=True) | ||||||
| 
 | 
 | ||||||
|         q = Queue(self.config['threads']['download']) |         q = Queue(max_threads) | ||||||
|         prod_thread = threading.Thread(target=producer, |         prod_thread = threading.Thread(target=producer, | ||||||
|                                        args=(q, requests, request_count)) |                                        args=(q, requests, request_count)) | ||||||
|         cons_thread = threading.Thread(target=consumer, |         cons_thread = threading.Thread(target=consumer, | ||||||
|  | @ -1436,10 +1573,11 @@ class Speedtest(object): | ||||||
|         start = timeit.default_timer() |         start = timeit.default_timer() | ||||||
|         prod_thread.start() |         prod_thread.start() | ||||||
|         cons_thread.start() |         cons_thread.start() | ||||||
|         while prod_thread.isAlive(): |         _is_alive = thread_is_alive | ||||||
|             prod_thread.join(timeout=0.1) |         while _is_alive(prod_thread): | ||||||
|         while cons_thread.isAlive(): |             prod_thread.join(timeout=0.001) | ||||||
|             cons_thread.join(timeout=0.1) |         while _is_alive(cons_thread): | ||||||
|  |             cons_thread.join(timeout=0.001) | ||||||
| 
 | 
 | ||||||
|         stop = timeit.default_timer() |         stop = timeit.default_timer() | ||||||
|         self.results.bytes_received = sum(finished) |         self.results.bytes_received = sum(finished) | ||||||
|  | @ -1450,8 +1588,12 @@ class Speedtest(object): | ||||||
|             self.config['threads']['upload'] = 8 |             self.config['threads']['upload'] = 8 | ||||||
|         return self.results.download |         return self.results.download | ||||||
| 
 | 
 | ||||||
|     def upload(self, callback=do_nothing, pre_allocate=True): |     def upload(self, callback=do_nothing, pre_allocate=True, threads=None): | ||||||
|         """Test upload speed against speedtest.net""" |         """Test upload speed against speedtest.net | ||||||
|  | 
 | ||||||
|  |         A ``threads`` value of ``None`` will fall back to those dictated | ||||||
|  |         by the speedtest.net configuration | ||||||
|  |         """ | ||||||
| 
 | 
 | ||||||
|         sizes = [] |         sizes = [] | ||||||
| 
 | 
 | ||||||
|  | @ -1474,13 +1616,19 @@ class Speedtest(object): | ||||||
|             ) |             ) | ||||||
|             if pre_allocate: |             if pre_allocate: | ||||||
|                 data.pre_allocate() |                 data.pre_allocate() | ||||||
|  | 
 | ||||||
|  |             headers = {'Content-length': size} | ||||||
|             requests.append( |             requests.append( | ||||||
|                 ( |                 ( | ||||||
|                     build_request(self.best['url'], data, secure=self._secure), |                     build_request(self.best['url'], data, secure=self._secure, | ||||||
|  |                                   headers=headers), | ||||||
|                     size |                     size | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|  |         max_threads = threads or self.config['threads']['upload'] | ||||||
|  |         in_flight = {'threads': 0} | ||||||
|  | 
 | ||||||
|         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( | ||||||
|  | @ -1492,21 +1640,26 @@ class Speedtest(object): | ||||||
|                     opener=self._opener, |                     opener=self._opener, | ||||||
|                     shutdown_event=self._shutdown_event |                     shutdown_event=self._shutdown_event | ||||||
|                 ) |                 ) | ||||||
|  |                 while in_flight['threads'] >= max_threads: | ||||||
|  |                     timeit.time.sleep(0.001) | ||||||
|                 thread.start() |                 thread.start() | ||||||
|                 q.put(thread, True) |                 q.put(thread, True) | ||||||
|  |                 in_flight['threads'] += 1 | ||||||
|                 callback(i, request_count, start=True) |                 callback(i, request_count, start=True) | ||||||
| 
 | 
 | ||||||
|         finished = [] |         finished = [] | ||||||
| 
 | 
 | ||||||
|         def consumer(q, request_count): |         def consumer(q, request_count): | ||||||
|  |             _is_alive = thread_is_alive | ||||||
|             while len(finished) < request_count: |             while len(finished) < request_count: | ||||||
|                 thread = q.get(True) |                 thread = q.get(True) | ||||||
|                 while thread.isAlive(): |                 while _is_alive(thread): | ||||||
|                     thread.join(timeout=0.1) |                     thread.join(timeout=0.001) | ||||||
|  |                 in_flight['threads'] -= 1 | ||||||
|                 finished.append(thread.result) |                 finished.append(thread.result) | ||||||
|                 callback(thread.i, request_count, end=True) |                 callback(thread.i, request_count, end=True) | ||||||
| 
 | 
 | ||||||
|         q = Queue(self.config['threads']['upload']) |         q = Queue(threads or self.config['threads']['upload']) | ||||||
|         prod_thread = threading.Thread(target=producer, |         prod_thread = threading.Thread(target=producer, | ||||||
|                                        args=(q, requests, request_count)) |                                        args=(q, requests, request_count)) | ||||||
|         cons_thread = threading.Thread(target=consumer, |         cons_thread = threading.Thread(target=consumer, | ||||||
|  | @ -1514,9 +1667,10 @@ class Speedtest(object): | ||||||
|         start = timeit.default_timer() |         start = timeit.default_timer() | ||||||
|         prod_thread.start() |         prod_thread.start() | ||||||
|         cons_thread.start() |         cons_thread.start() | ||||||
|         while prod_thread.isAlive(): |         _is_alive = thread_is_alive | ||||||
|  |         while _is_alive(prod_thread): | ||||||
|             prod_thread.join(timeout=0.1) |             prod_thread.join(timeout=0.1) | ||||||
|         while cons_thread.isAlive(): |         while _is_alive(cons_thread): | ||||||
|             cons_thread.join(timeout=0.1) |             cons_thread.join(timeout=0.1) | ||||||
| 
 | 
 | ||||||
|         stop = timeit.default_timer() |         stop = timeit.default_timer() | ||||||
|  | @ -1541,7 +1695,8 @@ def ctrl_c(shutdown_event): | ||||||
| def version(): | def version(): | ||||||
|     """Print the version""" |     """Print the version""" | ||||||
| 
 | 
 | ||||||
|     printer(__version__) |     printer('speedtest-cli %s' % __version__) | ||||||
|  |     printer('Python %s' % sys.version.replace('\n', '')) | ||||||
|     sys.exit(0) |     sys.exit(0) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1574,6 +1729,10 @@ def parse_args(): | ||||||
|     parser.add_argument('--no-upload', dest='upload', default=True, |     parser.add_argument('--no-upload', dest='upload', default=True, | ||||||
|                         action='store_const', const=False, |                         action='store_const', const=False, | ||||||
|                         help='Do not perform upload test') |                         help='Do not perform upload test') | ||||||
|  |     parser.add_argument('--single', default=False, action='store_true', | ||||||
|  |                         help='Only use a single connection instead of ' | ||||||
|  |                              'multiple. This simulates a typical file ' | ||||||
|  |                              'transfer.') | ||||||
|     parser.add_argument('--bytes', dest='units', action='store_const', |     parser.add_argument('--bytes', dest='units', action='store_const', | ||||||
|                         const=('byte', 8), default=('bit', 1), |                         const=('byte', 8), default=('bit', 1), | ||||||
|                         help='Display values in bytes instead of bits. Does ' |                         help='Display values in bytes instead of bits. Does ' | ||||||
|  | @ -1788,7 +1947,10 @@ def shell(): | ||||||
|     if args.download: |     if args.download: | ||||||
|         printer('Testing download speed', quiet, |         printer('Testing download speed', quiet, | ||||||
|                 end=('', '\n')[bool(debug)]) |                 end=('', '\n')[bool(debug)]) | ||||||
|         speedtest.download(callback=callback) |         speedtest.download( | ||||||
|  |             callback=callback, | ||||||
|  |             threads=(None, 1)[args.single] | ||||||
|  |         ) | ||||||
|         printer('Download: %0.2f M%s/s' % |         printer('Download: %0.2f M%s/s' % | ||||||
|                 ((results.download / 1000.0 / 1000.0) / args.units[1], |                 ((results.download / 1000.0 / 1000.0) / args.units[1], | ||||||
|                  args.units[0]), |                  args.units[0]), | ||||||
|  | @ -1799,7 +1961,11 @@ def shell(): | ||||||
|     if args.upload: |     if args.upload: | ||||||
|         printer('Testing upload speed', quiet, |         printer('Testing upload speed', quiet, | ||||||
|                 end=('', '\n')[bool(debug)]) |                 end=('', '\n')[bool(debug)]) | ||||||
|         speedtest.upload(callback=callback, pre_allocate=args.pre_allocate) |         speedtest.upload( | ||||||
|  |             callback=callback, | ||||||
|  |             pre_allocate=args.pre_allocate, | ||||||
|  |             threads=(None, 1)[args.single] | ||||||
|  |         ) | ||||||
|         printer('Upload: %0.2f M%s/s' % |         printer('Upload: %0.2f M%s/s' % | ||||||
|                 ((results.upload / 1000.0 / 1000.0) / args.units[1], |                 ((results.upload / 1000.0 / 1000.0) / args.units[1], | ||||||
|                  args.units[0]), |                  args.units[0]), | ||||||
|  | @ -1809,6 +1975,9 @@ def shell(): | ||||||
| 
 | 
 | ||||||
|     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' % |         printer('Ping: %s ms\nDownload: %0.2f M%s/s\nUpload: %0.2f M%s/s' % | ||||||
|                 (results.ping, |                 (results.ping, | ||||||
|  | @ -1819,8 +1988,6 @@ def shell(): | ||||||
|     elif args.csv: |     elif args.csv: | ||||||
|         printer(results.csv(delimiter=args.csv_delimiter)) |         printer(results.csv(delimiter=args.csv_delimiter)) | ||||||
|     elif args.json: |     elif args.json: | ||||||
|         if args.share: |  | ||||||
|             results.share() |  | ||||||
|         printer(results.json()) |         printer(results.json()) | ||||||
| 
 | 
 | ||||||
|     if args.share and not machine_format: |     if args.share and not machine_format: | ||||||
|  | @ -1836,7 +2003,10 @@ def main(): | ||||||
|         e = get_exception() |         e = get_exception() | ||||||
|         # Ignore a successful exit, or argparse exit |         # Ignore a successful exit, or argparse exit | ||||||
|         if getattr(e, 'code', 1) not in (0, 2): |         if getattr(e, 'code', 1) not in (0, 2): | ||||||
|             raise SystemExit('ERROR: %s' % e) |             msg = '%s' % e | ||||||
|  |             if not msg: | ||||||
|  |                 msg = '%r' % e | ||||||
|  |             raise SystemExit('ERROR: %s' % msg) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == '__main__': | if __name__ == '__main__': | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue