diff --git a/.travis.yml b/.travis.yml index c40859c..dc2c5fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,12 @@ language: python -sudo: required -dist: xenial addons: apt: sources: - deadsnakes packages: - - python2.4 - - python2.5 + # - python2.4 + # - python2.5 - python2.6 - python3.2 - python3.3 @@ -17,10 +15,10 @@ matrix: include: - python: 2.7 env: TOXENV=flake8 - - python: 2.7 - env: TOXENV=py24 - - python: 2.7 - env: TOXENV=py25 + # - python: 2.7 + # env: TOXENV=py24 + # - python: 2.7 + # env: TOXENV=py25 - python: 2.7 env: TOXENV=py26 - python: 2.7 @@ -35,13 +33,8 @@ matrix: env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 - - python: 3.7 - env: TOXENV=py37 - - python: 3.8-dev - env: TOXENV=py38 - python: pypy env: TOXENV=pypy - dist: trusty before_install: - if [[ $(echo "$TOXENV" | egrep -c "py35") != 0 ]]; then pyenv global system 3.5; fi; diff --git a/setup.py b/setup.py index f3d21ad..a617be4 100644 --- a/setup.py +++ b/setup.py @@ -92,8 +92,5 @@ setup( 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', ] ) diff --git a/speedtest.py b/speedtest.py index 186b529..be93123 100755 --- a/speedtest.py +++ b/speedtest.py @@ -15,18 +15,18 @@ # License for the specific language governing permissions and limitations # under the License. -import csv -import datetime -import errno -import math import os -import platform import re +import csv +import sys +import math +import errno import signal import socket -import sys -import threading import timeit +import datetime +import platform +import threading import xml.parsers.expat try: @@ -36,7 +36,7 @@ except ImportError: gzip = None GZIP_BASE = object -__version__ = '2.1.4b1' +__version__ = '2.1.0' class FakeShutdownEvent(object): @@ -49,16 +49,10 @@ class FakeShutdownEvent(object): "Dummy method to always return false""" return False - is_set = isSet - # Some global variables we use DEBUG = False _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 try: @@ -70,15 +64,14 @@ except ImportError: json = None try: - import xml.etree.ElementTree as ET - try: - from xml.etree.ElementTree import _Element as ET_Element - except ImportError: - pass + import xml.etree.cElementTree as ET except ImportError: - from xml.dom import minidom as DOM - from xml.parsers.expat import ExpatError - ET = None + try: + import xml.etree.ElementTree as ET + except ImportError: + from xml.dom import minidom as DOM + from xml.parsers.expat import ExpatError + ET = None try: from urllib2 import (urlopen, Request, HTTPError, URLError, @@ -104,11 +97,6 @@ except ImportError: except ImportError: HTTPSConnection = None -try: - from httplib import FakeSocket -except ImportError: - FakeSocket = None - try: from Queue import Queue except ImportError: @@ -269,6 +257,7 @@ else: write(arg) write(end) + # Exception "constants" to support Python 2 through Python 3 try: import ssl @@ -285,23 +274,6 @@ except ImportError: 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): """Base exception for this module""" @@ -422,8 +394,6 @@ class SpeedtestHTTPConnection(HTTPConnection): source_address = kwargs.pop('source_address', None) timeout = kwargs.pop('timeout', 10) - self._tunnel_host = None - HTTPConnection.__init__(self, *args, **kwargs) self.source_address = source_address @@ -444,23 +414,17 @@ class SpeedtestHTTPConnection(HTTPConnection): self.source_address ) - if self._tunnel_host: - self._tunnel() - if HTTPSConnection: - class SpeedtestHTTPSConnection(HTTPSConnection): + class SpeedtestHTTPSConnection(HTTPSConnection, + SpeedtestHTTPConnection): """Custom HTTPSConnection to support source_address across 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 @@ -468,30 +432,14 @@ if HTTPSConnection: def connect(self): "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 - ) - if self._tunnel_host: - self._tunnel() + SpeedtestHTTPConnection.connect(self) if ssl: try: kwargs = {} 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) except AttributeError: self.sock = ssl.wrap_socket(self.sock) @@ -499,20 +447,6 @@ if HTTPSConnection: 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): @@ -778,7 +712,7 @@ def print_dots(shutdown_event): status """ def inner(current, total, start=False, end=False): - if event_is_set(shutdown_event): + if shutdown_event.isSet(): return sys.stdout.write('.') @@ -817,7 +751,7 @@ class HTTPDownloader(threading.Thread): try: if (timeit.default_timer() - self.starttime) <= self.timeout: f = self._opener(self.request) - while (not event_is_set(self._shutdown_event) and + while (not self._shutdown_event.isSet() and (timeit.default_timer() - self.starttime) <= self.timeout): self.result.append(len(f.read(10240))) @@ -826,8 +760,6 @@ class HTTPDownloader(threading.Thread): f.close() except IOError: pass - except HTTP_ERRORS: - pass class HTTPUploaderData(object): @@ -873,7 +805,7 @@ class HTTPUploaderData(object): def read(self, n=10240): if ((timeit.default_timer() - self.start) <= self.timeout and - not event_is_set(self._shutdown_event)): + not self._shutdown_event.isSet()): chunk = self.data.read(n) self.total.append(len(chunk)) return chunk @@ -893,7 +825,7 @@ class HTTPUploader(threading.Thread): self.request = request self.request.data.start = self.starttime = start self.size = size - self.result = 0 + self.result = None self.timeout = timeout self.i = i @@ -911,7 +843,7 @@ class HTTPUploader(threading.Thread): request = self.request try: if ((timeit.default_timer() - self.starttime) <= self.timeout and - not event_is_set(self._shutdown_event)): + not self._shutdown_event.isSet()): try: f = self._opener(request) except TypeError: @@ -928,8 +860,6 @@ class HTTPUploader(threading.Thread): self.result = 0 except (IOError, SpeedtestUploadTimeout): self.result = sum(self.request.data.total) - except HTTP_ERRORS: - self.result = 0 class SpeedtestResults(object): @@ -1183,9 +1113,9 @@ class Speedtest(object): # times = get_attributes_by_tag_name(root, 'times') client = get_attributes_by_tag_name(root, 'client') - ignore_servers = [ - int(i) for i in server_config['ignoreids'].split(',') if i - ] + ignore_servers = list( + map(int, server_config['ignoreids'].split(',')) + ) ratio = int(upload['ratio']) upload_max = int(upload['maxchunkcount']) @@ -1313,7 +1243,7 @@ class Speedtest(object): raise SpeedtestServersError( 'Malformed speedtest.net server list: %s' % e ) - elements = etree_iter(root, 'server') + elements = root.getiterator('server') except AttributeError: try: root = DOM.parseString(serversxml) @@ -1533,9 +1463,6 @@ class Speedtest(object): 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): for i, request in enumerate(requests): thread = HTTPDownloader( @@ -1546,26 +1473,21 @@ class Speedtest(object): opener=self._opener, shutdown_event=self._shutdown_event ) - while in_flight['threads'] >= max_threads: - timeit.time.sleep(0.001) thread.start() q.put(thread, True) - in_flight['threads'] += 1 callback(i, request_count, start=True) finished = [] def consumer(q, request_count): - _is_alive = thread_is_alive while len(finished) < request_count: thread = q.get(True) - while _is_alive(thread): - thread.join(timeout=0.001) - in_flight['threads'] -= 1 + while thread.isAlive(): + thread.join(timeout=0.1) finished.append(sum(thread.result)) callback(thread.i, request_count, end=True) - q = Queue(max_threads) + q = Queue(threads or self.config['threads']['download']) prod_thread = threading.Thread(target=producer, args=(q, requests, request_count)) cons_thread = threading.Thread(target=consumer, @@ -1573,11 +1495,10 @@ class Speedtest(object): start = timeit.default_timer() prod_thread.start() cons_thread.start() - _is_alive = thread_is_alive - while _is_alive(prod_thread): - prod_thread.join(timeout=0.001) - while _is_alive(cons_thread): - cons_thread.join(timeout=0.001) + while prod_thread.isAlive(): + prod_thread.join(timeout=0.1) + while cons_thread.isAlive(): + cons_thread.join(timeout=0.1) stop = timeit.default_timer() self.results.bytes_received = sum(finished) @@ -1626,9 +1547,6 @@ class Speedtest(object): ) ) - max_threads = threads or self.config['threads']['upload'] - in_flight = {'threads': 0} - def producer(q, requests, request_count): for i, request in enumerate(requests[:request_count]): thread = HTTPUploader( @@ -1640,22 +1558,17 @@ class Speedtest(object): opener=self._opener, shutdown_event=self._shutdown_event ) - while in_flight['threads'] >= max_threads: - timeit.time.sleep(0.001) thread.start() q.put(thread, True) - in_flight['threads'] += 1 callback(i, request_count, start=True) finished = [] def consumer(q, request_count): - _is_alive = thread_is_alive while len(finished) < request_count: thread = q.get(True) - while _is_alive(thread): - thread.join(timeout=0.001) - in_flight['threads'] -= 1 + while thread.isAlive(): + thread.join(timeout=0.1) finished.append(thread.result) callback(thread.i, request_count, end=True) @@ -1667,10 +1580,9 @@ class Speedtest(object): start = timeit.default_timer() prod_thread.start() cons_thread.start() - _is_alive = thread_is_alive - while _is_alive(prod_thread): + while prod_thread.isAlive(): prod_thread.join(timeout=0.1) - while _is_alive(cons_thread): + while cons_thread.isAlive(): cons_thread.join(timeout=0.1) stop = timeit.default_timer()