[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [stem/master] BandwidthFile creation
commit 3dcc573e7aca39f6b12d74885d51b1d01b483f90
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date: Sun Jan 20 15:41:42 2019 -0800
BandwidthFile creation
Nothing fancy, just implementing Descriptor's create() and content() method.
---
stem/descriptor/bandwidth_file.py | 60 ++++++++++++++++++++-
test/unit/descriptor/bandwidth_file.py | 98 ++++++++++++++++++++++++++++++++--
2 files changed, 152 insertions(+), 6 deletions(-)
diff --git a/stem/descriptor/bandwidth_file.py b/stem/descriptor/bandwidth_file.py
index 97210d7b..0b2b211a 100644
--- a/stem/descriptor/bandwidth_file.py
+++ b/stem/descriptor/bandwidth_file.py
@@ -15,11 +15,14 @@ Parsing for Bandwidth Authority metrics as described in Tor's
"""
import datetime
+import time
import stem.util.str_tools
from stem.descriptor import Descriptor
+HEADER_DIV = '====='
+
# Converters header attributes to a given type. Malformed fields should be
# ignored according to the spec.
@@ -93,7 +96,7 @@ def _parse_header(descriptor, entries):
lines = lines[1:]
for line in lines:
- if line == '=====':
+ if line == HEADER_DIV:
break
elif line.startswith('node_id='):
break # version 1.0 measurement
@@ -155,8 +158,61 @@ class BandwidthFile(Descriptor):
ATTRIBUTES.update(dict([(k, (None, _parse_header)) for k in HEADER_ATTR.keys()]))
+ @classmethod
+ def content(cls, attr = None, exclude = (), sign = False):
+ """
+ Creates descriptor content with the given attributes. This descriptor type
+ differs somewhat from others and treats our attr/exclude attributes as
+ follows...
+
+ * 'timestamp' is a reserved key for our mandatory header unix timestamp.
+
+ * 'content' is a reserved key for a list of our bandwidth measurements.
+
+ * All other keys are treated as header fields.
+
+ For example...
+
+ ::
+
+ BandwidthFile.content({
+ 'timestamp': '12345',
+ 'version': '1.2.0',
+ 'content': [],
+ })
+ """
+
+ if sign:
+ raise NotImplementedError('Signing of %s not implemented' % cls.__name__)
+
+ header = dict(attr) if attr is not None else {}
+ timestamp = header.pop('timestamp', str(int(time.time())))
+ content = header.pop('content', [])
+ version = header.get('version', HEADER_DEFAULT.get('version'))
+
+ lines = []
+
+ if 'timestamp' not in exclude:
+ lines.append(timestamp)
+
+ if version == '1.0.0' and header:
+ raise ValueError('Headers require BandwidthFile version 1.1 or later')
+ elif version != '1.0.0':
+ for k, v in header.items():
+ lines.append('%s=%s' % (k, v))
+
+ lines.append(HEADER_DIV)
+
+ for measurement in content:
+ lines.append(measurement) # TODO: replace when we have a measurement struct
+
+ return '\n'.join(lines)
+
def __init__(self, raw_content, validate = False):
super(BandwidthFile, self).__init__(raw_content, lazy_load = not validate)
+ self.content = [] # TODO: implement
+
if validate:
- pass # TODO: implement eager load
+ _parse_timestamp(self, None)
+ _parse_header(self, None)
diff --git a/test/unit/descriptor/bandwidth_file.py b/test/unit/descriptor/bandwidth_file.py
index b8fdf01b..e1f8ffb4 100644
--- a/test/unit/descriptor/bandwidth_file.py
+++ b/test/unit/descriptor/bandwidth_file.py
@@ -6,9 +6,22 @@ import datetime
import unittest
import stem.descriptor
-import stem.descriptor.bandwidth_file
-import test.unit.descriptor
+from stem.descriptor.bandwidth_file import BandwidthFile
+from test.unit.descriptor import get_resource
+
+try:
+ # added in python 3.3
+ from unittest.mock import Mock, patch
+except ImportError:
+ from mock import Mock, patch
+
+EXPECTED_NEW_HEADER_CONTENT = """
+1410723598
+version=1.1.0
+new_header=neat stuff
+=====
+""".strip()
class TestBandwidthFile(unittest.TestCase):
@@ -17,7 +30,7 @@ class TestBandwidthFile(unittest.TestCase):
Parse version 1.0 formatted files.
"""
- desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bandwidth_file_v1.0'), 'badnwidth-file 1.0'))[0]
+ desc = list(stem.descriptor.parse_file(get_resource('bandwidth_file_v1.0'), 'badnwidth-file 1.0'))[0]
self.assertEqual(datetime.datetime(2019, 1, 14, 17, 41, 29), desc.timestamp)
self.assertEqual('1.0.0', desc.version)
@@ -41,7 +54,7 @@ class TestBandwidthFile(unittest.TestCase):
Parse version 1.2 formatted files.
"""
- desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bandwidth_file_v1.2'), 'badnwidth-file 1.2'))[0]
+ desc = list(stem.descriptor.parse_file(get_resource('bandwidth_file_v1.2'), 'badnwidth-file 1.2'))[0]
self.assertEqual(datetime.datetime(2019, 1, 14, 5, 34, 59), desc.timestamp)
self.assertEqual('1.2.0', desc.version)
@@ -59,3 +72,80 @@ class TestBandwidthFile(unittest.TestCase):
self.assertEqual(96, desc.eligible_percent)
self.assertEqual(3908, desc.min_count)
self.assertEqual(60, desc.min_percent)
+
+ @patch('time.time', Mock(return_value = 1410723598.276578))
+ def test_minimal_bandwidth_file(self):
+ """
+ Basic sanity check that we can parse a bandwidth file with minimal
+ attributes.
+ """
+
+ desc = BandwidthFile.create()
+
+ self.assertEqual('1410723598', str(desc))
+
+ self.assertEqual(datetime.datetime(2014, 9, 14, 19, 39, 58), desc.timestamp)
+ self.assertEqual('1.0.0', desc.version)
+
+ self.assertEqual(None, desc.software)
+ self.assertEqual(None, desc.software_version)
+
+ self.assertEqual(None, desc.earliest_bandwidth)
+ self.assertEqual(None, desc.latest_bandwidth)
+ self.assertEqual(None, desc.created_at)
+ self.assertEqual(None, desc.generated_at)
+
+ self.assertEqual(None, desc.consensus_size)
+ self.assertEqual(None, desc.eligible_count)
+ self.assertEqual(None, desc.eligible_percent)
+ self.assertEqual(None, desc.min_count)
+ self.assertEqual(None, desc.min_percent)
+
+ self.assertEqual({}, desc.header)
+
+ def test_content_example(self):
+ """
+ Exercise the example in our content method's pydoc.
+ """
+
+ content = BandwidthFile.content({
+ 'timestamp': '12345',
+ 'version': '1.2.0',
+ 'content': [],
+ })
+
+ self.assertEqual('12345\nversion=1.2.0\n=====', content)
+
+ @patch('time.time', Mock(return_value = 1410723598.276578))
+ def test_new_header_attribute(self):
+ """
+ Include an unrecognized header field.
+ """
+
+ desc = BandwidthFile.create({'version': '1.1.0', 'new_header': 'neat stuff'})
+ self.assertEqual(EXPECTED_NEW_HEADER_CONTENT, str(desc))
+ self.assertEqual('1.1.0', desc.version)
+ self.assertEqual({'version': '1.1.0', 'new_header': 'neat stuff'}, desc.header)
+
+ def test_header_for_v1(self):
+ """
+ Document version 1.0 predates headers, and as such should be prohibited.
+ """
+
+ self.assertRaisesWith(ValueError, 'Headers require BandwidthFile version 1.1 or later', BandwidthFile.create, {'new_header': 'neat stuff'})
+
+ def test_invalid_timestamp(self):
+ """
+ Invalid timestamp values.
+ """
+
+ test_values = (
+ '',
+ 'boo',
+ '123.4',
+ '-123',
+ )
+
+ for value in test_values:
+ expected_exc = "First line should be a unix timestamp, but was '%s'" % value
+ self.assertRaisesWith(ValueError, expected_exc, BandwidthFile.create, {'timestamp': value})
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits