mirror of
https://github.com/internetarchive/warcprox.git
synced 2025-01-18 13:22:09 +01:00
split tests into different source files
This commit is contained in:
parent
93cdf59dec
commit
aa177f94b8
2
setup.py
2
setup.py
@ -3,7 +3,7 @@ import codecs
|
|||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name='doublethink',
|
name='doublethink',
|
||||||
version='0.2.0.dev67',
|
version='0.2.0.dev68',
|
||||||
packages=['doublethink'],
|
packages=['doublethink'],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
|
278
tests/test_orm.py
Normal file
278
tests/test_orm.py
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
'''
|
||||||
|
tests_orm.py - unit tests for doublethink ORM
|
||||||
|
|
||||||
|
Copyright (C) 2015-2017 Internet Archive
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import doublethink
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import pytest
|
||||||
|
import rethinkdb as r
|
||||||
|
|
||||||
|
logging.basicConfig(stream=sys.stderr, level=logging.INFO,
|
||||||
|
format="%(asctime)s %(process)d %(levelname)s %(threadName)s %(name)s.%(funcName)s(%(filename)s:%(lineno)d) %(message)s")
|
||||||
|
|
||||||
|
class RethinkerForTesting(doublethink.Rethinker):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(RethinkerForTesting, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _random_server_connection(self):
|
||||||
|
self.last_conn = super(RethinkerForTesting, self)._random_server_connection()
|
||||||
|
# logging.info("self.last_conn=%s", self.last_conn)
|
||||||
|
return self.last_conn
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def rr():
|
||||||
|
rr = RethinkerForTesting()
|
||||||
|
try:
|
||||||
|
rr.db_drop("doublethink_test_db").run()
|
||||||
|
except r.errors.ReqlOpFailedError:
|
||||||
|
pass
|
||||||
|
result = rr.db_create("doublethink_test_db").run()
|
||||||
|
assert not rr.last_conn.is_open()
|
||||||
|
assert result["dbs_created"] == 1
|
||||||
|
return RethinkerForTesting(db="doublethink_test_db")
|
||||||
|
|
||||||
|
def test_orm(rr):
|
||||||
|
class SomeDoc(doublethink.Document):
|
||||||
|
table = 'some_doc'
|
||||||
|
|
||||||
|
SomeDoc.table_create(rr)
|
||||||
|
SomeDoc.table_ensure(rr)
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
SomeDoc.table_create(rr)
|
||||||
|
|
||||||
|
# test that overriding Document.table works
|
||||||
|
assert 'some_doc' in rr.table_list().run()
|
||||||
|
assert not 'somedoc' in rr.table_list().run()
|
||||||
|
|
||||||
|
d = SomeDoc(rr, d={
|
||||||
|
'a': 'b',
|
||||||
|
'c': {'d': 'e'},
|
||||||
|
'f': ['g', 'h'],
|
||||||
|
'i': ['j', {'k': 'l'}]})
|
||||||
|
d.save()
|
||||||
|
|
||||||
|
assert d._updates == {}
|
||||||
|
d.m = 'n'
|
||||||
|
assert d._updates == {'m': 'n'}
|
||||||
|
d['c']['o'] = 'p'
|
||||||
|
assert d._updates == {'m': 'n', 'c': {'d': 'e', 'o': 'p'}}
|
||||||
|
d.f[0] = 'q'
|
||||||
|
assert d._updates == {'m': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h']}
|
||||||
|
d['i'][1]['k'] = 's'
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n',
|
||||||
|
'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['q', 'h'],
|
||||||
|
'i': ['j', {'k': 's'}]}
|
||||||
|
|
||||||
|
del d['i']
|
||||||
|
assert d._deletes == {'i'}
|
||||||
|
assert d._updates == {'m': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h']}
|
||||||
|
|
||||||
|
d.i = 't'
|
||||||
|
assert d._deletes == set()
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h'], 'i': 't'}
|
||||||
|
|
||||||
|
# list manipulations
|
||||||
|
d.f.append(['sublist'])
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['q', 'h', ['sublist']], 'i': 't'}
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
d.f[2].clear()
|
||||||
|
|
||||||
|
result = d.f.pop()
|
||||||
|
assert result == ['sublist']
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['q', 'h'], 'i': 't'}
|
||||||
|
|
||||||
|
del d.f[0]
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['h'], 'i': 't'}
|
||||||
|
|
||||||
|
d.f.insert(0, 'u')
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['u', 'h'], 'i': 't'}
|
||||||
|
|
||||||
|
d.f.extend(('v', {'w': 'x'}))
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['u', 'h', 'v', {'w': 'x'}], 'i': 't'}
|
||||||
|
|
||||||
|
# check that stuff added by extend() is watched properly
|
||||||
|
d.f[3]['y'] = 'z'
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['u', 'h', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
d.f.remove('h')
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
# more nested field dict operations
|
||||||
|
del d['c']['d']
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'o': 'p'},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
d['c'].clear()
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
assert d['c'].setdefault('aa') is None
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'aa': None},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
d['c'].setdefault('aa', 'bb') is None
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'aa': None},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
d['c'].setdefault('cc', 'dd') == 'dd'
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
d['c'].setdefault('cc') == 'dd'
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
d['c'].setdefault('cc', 'ee') == 'dd'
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
assert d['c'].pop('cc') == 'dd'
|
||||||
|
assert d._updates == {
|
||||||
|
'm': 'n', 'c': {'aa': None},
|
||||||
|
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
||||||
|
|
||||||
|
assert d['f'][2].popitem()
|
||||||
|
assert d._updates['f'][2] in ({'w':'x'}, {'y':'z'})
|
||||||
|
|
||||||
|
d.save()
|
||||||
|
assert d._updates == {}
|
||||||
|
assert d._deletes == set()
|
||||||
|
|
||||||
|
d_copy = SomeDoc.load(rr, d.id)
|
||||||
|
assert d == d_copy
|
||||||
|
d['zuh'] = 'toot'
|
||||||
|
d.save()
|
||||||
|
assert d != d_copy
|
||||||
|
d_copy.refresh()
|
||||||
|
assert d == d_copy
|
||||||
|
|
||||||
|
# top level dict operations
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
d.clear()
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
d.pop('m')
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
d.popitem()
|
||||||
|
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
d.update({'x':'y'})
|
||||||
|
|
||||||
|
assert d.setdefault('ee') is None
|
||||||
|
assert d._updates == {'ee': None}
|
||||||
|
|
||||||
|
d.setdefault('ee', 'ff') is None
|
||||||
|
assert d._updates == {'ee': None}
|
||||||
|
|
||||||
|
d.setdefault('gg', 'hh') == 'hh'
|
||||||
|
assert d._updates == {'ee': None, 'gg': 'hh'}
|
||||||
|
|
||||||
|
d.setdefault('gg') == 'hh'
|
||||||
|
assert d._updates == {'ee': None, 'gg': 'hh'}
|
||||||
|
|
||||||
|
d.setdefault('gg', 'ii') == 'hh'
|
||||||
|
assert d._updates == {'ee': None, 'gg': 'hh'}
|
||||||
|
|
||||||
|
d.save()
|
||||||
|
assert d._updates == {}
|
||||||
|
assert d._deletes == set()
|
||||||
|
|
||||||
|
d_copy = SomeDoc.load(rr, d.id)
|
||||||
|
assert d == d_copy
|
||||||
|
d['yuh'] = 'soot'
|
||||||
|
d.save()
|
||||||
|
assert d != d_copy
|
||||||
|
d_copy.refresh()
|
||||||
|
assert d == d_copy
|
||||||
|
|
||||||
|
def test_orm_pk(rr):
|
||||||
|
class NonstandardPrimaryKey(doublethink.Document):
|
||||||
|
@classmethod
|
||||||
|
def table_create(cls, rethinker):
|
||||||
|
rethinker.table_create(cls.table, primary_key='not_id').run()
|
||||||
|
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
NonstandardPrimaryKey.load(rr, 'no_such_thing')
|
||||||
|
|
||||||
|
NonstandardPrimaryKey.table_ensure(rr)
|
||||||
|
|
||||||
|
# new empty doc
|
||||||
|
f = NonstandardPrimaryKey(rr, {})
|
||||||
|
f.save()
|
||||||
|
assert f.pk_value
|
||||||
|
assert 'not_id' in f
|
||||||
|
assert f.not_id == f.pk_value
|
||||||
|
assert len(f.keys()) == 1
|
||||||
|
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
NonstandardPrimaryKey.load(rr, 'no_such_thing')
|
||||||
|
|
||||||
|
# new doc with (only) primary key
|
||||||
|
d = NonstandardPrimaryKey(rr, {'not_id': 1})
|
||||||
|
assert d.not_id == 1
|
||||||
|
assert d.pk_value == 1
|
||||||
|
d.save()
|
||||||
|
|
||||||
|
d_copy = NonstandardPrimaryKey.load(rr, 1)
|
||||||
|
assert d == d_copy
|
||||||
|
|
||||||
|
# new doc with something in it
|
||||||
|
e = NonstandardPrimaryKey(rr, {'some_field': 'something'})
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
e['not_id']
|
||||||
|
assert e.not_id is None
|
||||||
|
assert e.get('not_id') is None
|
||||||
|
e.save()
|
||||||
|
assert e.not_id
|
||||||
|
|
||||||
|
e_copy = NonstandardPrimaryKey.load(rr, e.not_id)
|
||||||
|
assert e == e_copy
|
||||||
|
e_copy['blah'] = 'toot'
|
||||||
|
e_copy.save()
|
||||||
|
|
||||||
|
e.refresh()
|
||||||
|
assert e['blah'] == 'toot'
|
||||||
|
assert e == e_copy
|
||||||
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
|||||||
'''
|
'''
|
||||||
tests_rethinker.py - unit tests for doublethink
|
tests_rethinker.py - unit tests for doublethink connection manager
|
||||||
|
|
||||||
Copyright (C) 2015-2017 Internet Archive
|
Copyright (C) 2015-2017 Internet Archive
|
||||||
|
|
||||||
@ -23,9 +23,6 @@ import types
|
|||||||
import gc
|
import gc
|
||||||
import pytest
|
import pytest
|
||||||
import rethinkdb as r
|
import rethinkdb as r
|
||||||
import time
|
|
||||||
import socket
|
|
||||||
import os
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.INFO,
|
logging.basicConfig(stream=sys.stderr, level=logging.INFO,
|
||||||
@ -115,154 +112,6 @@ def test_slice(rr, my_table):
|
|||||||
assert not rr.last_conn.is_open()
|
assert not rr.last_conn.is_open()
|
||||||
assert n == 5
|
assert n == 5
|
||||||
|
|
||||||
def test_service_registry(rr):
|
|
||||||
svcreg = doublethink.ServiceRegistry(rr)
|
|
||||||
assert svcreg.available_service("yes-such-role") == None
|
|
||||||
assert svcreg.available_services("yes-such-role") == []
|
|
||||||
assert svcreg.available_services() == []
|
|
||||||
svc0 = {
|
|
||||||
"role": "yes-such-role",
|
|
||||||
"load": 100.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc1 = {
|
|
||||||
"role": "yes-such-role",
|
|
||||||
"load": 200.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc0 = svcreg.heartbeat(svc0)
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
assert "id" in svc0
|
|
||||||
assert "id" in svc1
|
|
||||||
assert svc0["id"] != svc1["id"]
|
|
||||||
|
|
||||||
assert svc0["host"] == socket.gethostname()
|
|
||||||
assert svc1["host"] == socket.gethostname()
|
|
||||||
|
|
||||||
assert "pid" in svc0
|
|
||||||
assert "pid" in svc1
|
|
||||||
assert svc0["pid"] == os.getpid()
|
|
||||||
assert svc1["pid"] == os.getpid()
|
|
||||||
assert "first_heartbeat" in svc0
|
|
||||||
assert "first_heartbeat" in svc1
|
|
||||||
assert "last_heartbeat" in svc0
|
|
||||||
assert "last_heartbeat" in svc1
|
|
||||||
|
|
||||||
time.sleep(0.2)
|
|
||||||
assert svcreg.available_service("no-such-role") == None
|
|
||||||
assert svcreg.available_services("no-such-role") == []
|
|
||||||
# svc0 has less load
|
|
||||||
assert svcreg.available_service("yes-such-role")["id"] == svc0["id"]
|
|
||||||
assert len(svcreg.available_services("yes-such-role")) == 2
|
|
||||||
assert len(svcreg.available_services()) == 2
|
|
||||||
|
|
||||||
svc1["load"] = 50.0
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
time.sleep(0.2)
|
|
||||||
assert svcreg.available_service("no-such-role") == None
|
|
||||||
# now svc1 has less load
|
|
||||||
assert svcreg.available_service("yes-such-role")["id"] == svc1["id"]
|
|
||||||
assert len(svcreg.available_services("yes-such-role")) == 2
|
|
||||||
assert len(svcreg.available_services()) == 2
|
|
||||||
|
|
||||||
svc1["load"] = 200.0
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
time.sleep(0.2)
|
|
||||||
assert svcreg.available_service("no-such-role") == None
|
|
||||||
# now svc0 has less load again
|
|
||||||
assert svcreg.available_service("yes-such-role")["id"] == svc0["id"]
|
|
||||||
assert len(svcreg.available_services("yes-such-role")) == 2
|
|
||||||
assert len(svcreg.available_services()) == 2
|
|
||||||
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
time.sleep(0.2)
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
time.sleep(0.7)
|
|
||||||
assert svcreg.available_service("no-such-role") == None
|
|
||||||
# now it's been too long since the last heartbeat from svc0
|
|
||||||
assert svcreg.available_service("yes-such-role")["id"] == svc1["id"]
|
|
||||||
assert len(svcreg.available_services("yes-such-role")) == 1
|
|
||||||
assert len(svcreg.available_services()) == 1
|
|
||||||
|
|
||||||
svcreg.unregister(svc1["id"])
|
|
||||||
time.sleep(0.2)
|
|
||||||
assert svcreg.available_service("no-such-role") == None
|
|
||||||
assert svcreg.available_service("yes-such-role") == None
|
|
||||||
assert svcreg.available_services("yes-such-role") == []
|
|
||||||
assert svcreg.available_services() == []
|
|
||||||
|
|
||||||
svc0 = {
|
|
||||||
"role": "yes-such-role",
|
|
||||||
"load": 100.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc1 = {
|
|
||||||
"role": "yes-such-role",
|
|
||||||
"load": 200.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc0 = svcreg.heartbeat(svc0)
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
assert len(svcreg.available_services("yes-such-role")) == 2
|
|
||||||
assert len(svcreg.available_services()) == 2
|
|
||||||
svcreg.unregister(svc0["id"])
|
|
||||||
svcreg.unregister(svc1["id"])
|
|
||||||
|
|
||||||
svc0 = {
|
|
||||||
"role": "yes-such-role",
|
|
||||||
"load": 100.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc1 = {
|
|
||||||
"role": "yes-such-role",
|
|
||||||
"load": 200.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc2 = {
|
|
||||||
"role": "another-such-role",
|
|
||||||
"load": 200.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc3 = {
|
|
||||||
"role": "yet-another-such-role",
|
|
||||||
"load": 200.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
svc0 = svcreg.heartbeat(svc0)
|
|
||||||
svc1 = svcreg.heartbeat(svc1)
|
|
||||||
svc2 = svcreg.heartbeat(svc2)
|
|
||||||
svc3 = svcreg.heartbeat(svc3)
|
|
||||||
assert len(svcreg.available_services("yes-such-role")) == 2
|
|
||||||
assert len(svcreg.available_services()) == 4
|
|
||||||
|
|
||||||
def test_svcreg_heartbeat_server_down(rr):
|
|
||||||
class MockRethinker:
|
|
||||||
def table(self, *args, **kwargs):
|
|
||||||
raise Exception('catch me if you can')
|
|
||||||
|
|
||||||
class SortOfFakeServiceRegistry(doublethink.ServiceRegistry):
|
|
||||||
def __init__(self, rethinker):
|
|
||||||
self.rr = rethinker
|
|
||||||
# self._ensure_table() # not doing this here
|
|
||||||
|
|
||||||
# no such rethinkdb server
|
|
||||||
rr = MockRethinker()
|
|
||||||
svcreg = SortOfFakeServiceRegistry(rr)
|
|
||||||
svc0 = {
|
|
||||||
"role": "role-foo",
|
|
||||||
"load": 100.0,
|
|
||||||
"heartbeat_interval": 0.4,
|
|
||||||
}
|
|
||||||
# no exception thrown
|
|
||||||
svc0 = svcreg.heartbeat(svc0)
|
|
||||||
|
|
||||||
# check that status_info was *not* updated
|
|
||||||
assert not 'id' in svc0
|
|
||||||
assert not 'last_heartbeat' in svc0
|
|
||||||
assert not 'first_heartbeat' in svc0
|
|
||||||
assert not 'host' in svc0
|
|
||||||
assert not 'pid' in svc0
|
|
||||||
|
|
||||||
def test_utcnow():
|
def test_utcnow():
|
||||||
now_notz = datetime.datetime.utcnow() # has no timezone :(
|
now_notz = datetime.datetime.utcnow() # has no timezone :(
|
||||||
assert not now_notz.tzinfo
|
assert not now_notz.tzinfo
|
||||||
@ -279,233 +128,3 @@ def test_utcnow():
|
|||||||
|
|
||||||
## XXX what else can we test without jumping through hoops?
|
## XXX what else can we test without jumping through hoops?
|
||||||
|
|
||||||
def test_orm(rr):
|
|
||||||
class SomeDoc(doublethink.Document):
|
|
||||||
table = 'some_doc'
|
|
||||||
|
|
||||||
SomeDoc.table_create(rr)
|
|
||||||
SomeDoc.table_ensure(rr)
|
|
||||||
with pytest.raises(Exception):
|
|
||||||
SomeDoc.table_create(rr)
|
|
||||||
|
|
||||||
# test that overriding Document.table works
|
|
||||||
assert 'some_doc' in rr.table_list().run()
|
|
||||||
assert not 'somedoc' in rr.table_list().run()
|
|
||||||
|
|
||||||
d = SomeDoc(rr, d={
|
|
||||||
'a': 'b',
|
|
||||||
'c': {'d': 'e'},
|
|
||||||
'f': ['g', 'h'],
|
|
||||||
'i': ['j', {'k': 'l'}]})
|
|
||||||
d.save()
|
|
||||||
|
|
||||||
assert d._updates == {}
|
|
||||||
d.m = 'n'
|
|
||||||
assert d._updates == {'m': 'n'}
|
|
||||||
d['c']['o'] = 'p'
|
|
||||||
assert d._updates == {'m': 'n', 'c': {'d': 'e', 'o': 'p'}}
|
|
||||||
d.f[0] = 'q'
|
|
||||||
assert d._updates == {'m': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h']}
|
|
||||||
d['i'][1]['k'] = 's'
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n',
|
|
||||||
'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['q', 'h'],
|
|
||||||
'i': ['j', {'k': 's'}]}
|
|
||||||
|
|
||||||
del d['i']
|
|
||||||
assert d._deletes == {'i'}
|
|
||||||
assert d._updates == {'m': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h']}
|
|
||||||
|
|
||||||
d.i = 't'
|
|
||||||
assert d._deletes == set()
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'}, 'f': ['q', 'h'], 'i': 't'}
|
|
||||||
|
|
||||||
# list manipulations
|
|
||||||
d.f.append(['sublist'])
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['q', 'h', ['sublist']], 'i': 't'}
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
d.f[2].clear()
|
|
||||||
|
|
||||||
result = d.f.pop()
|
|
||||||
assert result == ['sublist']
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['q', 'h'], 'i': 't'}
|
|
||||||
|
|
||||||
del d.f[0]
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['h'], 'i': 't'}
|
|
||||||
|
|
||||||
d.f.insert(0, 'u')
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['u', 'h'], 'i': 't'}
|
|
||||||
|
|
||||||
d.f.extend(('v', {'w': 'x'}))
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['u', 'h', 'v', {'w': 'x'}], 'i': 't'}
|
|
||||||
|
|
||||||
# check that stuff added by extend() is watched properly
|
|
||||||
d.f[3]['y'] = 'z'
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['u', 'h', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
d.f.remove('h')
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'d': 'e', 'o': 'p'},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
# more nested field dict operations
|
|
||||||
del d['c']['d']
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'o': 'p'},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
d['c'].clear()
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
assert d['c'].setdefault('aa') is None
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'aa': None},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
d['c'].setdefault('aa', 'bb') is None
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'aa': None},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
d['c'].setdefault('cc', 'dd') == 'dd'
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
d['c'].setdefault('cc') == 'dd'
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
d['c'].setdefault('cc', 'ee') == 'dd'
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'aa': None, 'cc': 'dd'},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
assert d['c'].pop('cc') == 'dd'
|
|
||||||
assert d._updates == {
|
|
||||||
'm': 'n', 'c': {'aa': None},
|
|
||||||
'f': ['u', 'v', {'w': 'x', 'y': 'z'}], 'i': 't'}
|
|
||||||
|
|
||||||
assert d['f'][2].popitem()
|
|
||||||
assert d._updates['f'][2] in ({'w':'x'}, {'y':'z'})
|
|
||||||
|
|
||||||
d.save()
|
|
||||||
assert d._updates == {}
|
|
||||||
assert d._deletes == set()
|
|
||||||
|
|
||||||
d_copy = SomeDoc.load(rr, d.id)
|
|
||||||
assert d == d_copy
|
|
||||||
d['zuh'] = 'toot'
|
|
||||||
d.save()
|
|
||||||
assert d != d_copy
|
|
||||||
d_copy.refresh()
|
|
||||||
assert d == d_copy
|
|
||||||
|
|
||||||
# top level dict operations
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
d.clear()
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
d.pop('m')
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
d.popitem()
|
|
||||||
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
d.update({'x':'y'})
|
|
||||||
|
|
||||||
assert d.setdefault('ee') is None
|
|
||||||
assert d._updates == {'ee': None}
|
|
||||||
|
|
||||||
d.setdefault('ee', 'ff') is None
|
|
||||||
assert d._updates == {'ee': None}
|
|
||||||
|
|
||||||
d.setdefault('gg', 'hh') == 'hh'
|
|
||||||
assert d._updates == {'ee': None, 'gg': 'hh'}
|
|
||||||
|
|
||||||
d.setdefault('gg') == 'hh'
|
|
||||||
assert d._updates == {'ee': None, 'gg': 'hh'}
|
|
||||||
|
|
||||||
d.setdefault('gg', 'ii') == 'hh'
|
|
||||||
assert d._updates == {'ee': None, 'gg': 'hh'}
|
|
||||||
|
|
||||||
d.save()
|
|
||||||
assert d._updates == {}
|
|
||||||
assert d._deletes == set()
|
|
||||||
|
|
||||||
d_copy = SomeDoc.load(rr, d.id)
|
|
||||||
assert d == d_copy
|
|
||||||
d['yuh'] = 'soot'
|
|
||||||
d.save()
|
|
||||||
assert d != d_copy
|
|
||||||
d_copy.refresh()
|
|
||||||
assert d == d_copy
|
|
||||||
|
|
||||||
def test_orm_pk(rr):
|
|
||||||
class NonstandardPrimaryKey(doublethink.Document):
|
|
||||||
@classmethod
|
|
||||||
def table_create(cls, rethinker):
|
|
||||||
rethinker.table_create(cls.table, primary_key='not_id').run()
|
|
||||||
|
|
||||||
with pytest.raises(Exception):
|
|
||||||
NonstandardPrimaryKey.load(rr, 'no_such_thing')
|
|
||||||
|
|
||||||
NonstandardPrimaryKey.table_ensure(rr)
|
|
||||||
|
|
||||||
# new empty doc
|
|
||||||
f = NonstandardPrimaryKey(rr, {})
|
|
||||||
f.save()
|
|
||||||
assert f.pk_value
|
|
||||||
assert 'not_id' in f
|
|
||||||
assert f.not_id == f.pk_value
|
|
||||||
assert len(f.keys()) == 1
|
|
||||||
|
|
||||||
with pytest.raises(KeyError):
|
|
||||||
NonstandardPrimaryKey.load(rr, 'no_such_thing')
|
|
||||||
|
|
||||||
# new doc with (only) primary key
|
|
||||||
d = NonstandardPrimaryKey(rr, {'not_id': 1})
|
|
||||||
assert d.not_id == 1
|
|
||||||
assert d.pk_value == 1
|
|
||||||
d.save()
|
|
||||||
|
|
||||||
d_copy = NonstandardPrimaryKey.load(rr, 1)
|
|
||||||
assert d == d_copy
|
|
||||||
|
|
||||||
# new doc with something in it
|
|
||||||
e = NonstandardPrimaryKey(rr, {'some_field': 'something'})
|
|
||||||
with pytest.raises(KeyError):
|
|
||||||
e['not_id']
|
|
||||||
assert e.not_id is None
|
|
||||||
assert e.get('not_id') is None
|
|
||||||
e.save()
|
|
||||||
assert e.not_id
|
|
||||||
|
|
||||||
e_copy = NonstandardPrimaryKey.load(rr, e.not_id)
|
|
||||||
assert e == e_copy
|
|
||||||
e_copy['blah'] = 'toot'
|
|
||||||
e_copy.save()
|
|
||||||
|
|
||||||
e.refresh()
|
|
||||||
assert e['blah'] == 'toot'
|
|
||||||
assert e == e_copy
|
|
||||||
|
|
||||||
|
|
||||||
|
202
tests/test_svcreg.py
Normal file
202
tests/test_svcreg.py
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
'''
|
||||||
|
tests_rethinker.py - unit tests for doublethink
|
||||||
|
|
||||||
|
Copyright (C) 2015-2017 Internet Archive
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import doublethink
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import gc
|
||||||
|
import pytest
|
||||||
|
import rethinkdb as r
|
||||||
|
import time
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
logging.basicConfig(stream=sys.stderr, level=logging.INFO,
|
||||||
|
format="%(asctime)s %(process)d %(levelname)s %(threadName)s %(name)s.%(funcName)s(%(filename)s:%(lineno)d) %(message)s")
|
||||||
|
|
||||||
|
class RethinkerForTesting(doublethink.Rethinker):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(RethinkerForTesting, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _random_server_connection(self):
|
||||||
|
self.last_conn = super(RethinkerForTesting, self)._random_server_connection()
|
||||||
|
# logging.info("self.last_conn=%s", self.last_conn)
|
||||||
|
return self.last_conn
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def rr():
|
||||||
|
rr = RethinkerForTesting()
|
||||||
|
try:
|
||||||
|
rr.db_drop("doublethink_test_db").run()
|
||||||
|
except r.errors.ReqlOpFailedError:
|
||||||
|
pass
|
||||||
|
result = rr.db_create("doublethink_test_db").run()
|
||||||
|
assert not rr.last_conn.is_open()
|
||||||
|
assert result["dbs_created"] == 1
|
||||||
|
return RethinkerForTesting(db="doublethink_test_db")
|
||||||
|
|
||||||
|
def test_service_registry(rr):
|
||||||
|
svcreg = doublethink.ServiceRegistry(rr)
|
||||||
|
assert svcreg.available_service("yes-such-role") == None
|
||||||
|
assert svcreg.available_services("yes-such-role") == []
|
||||||
|
assert svcreg.available_services() == []
|
||||||
|
svc0 = {
|
||||||
|
"role": "yes-such-role",
|
||||||
|
"load": 100.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc1 = {
|
||||||
|
"role": "yes-such-role",
|
||||||
|
"load": 200.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc0 = svcreg.heartbeat(svc0)
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
assert "id" in svc0
|
||||||
|
assert "id" in svc1
|
||||||
|
assert svc0["id"] != svc1["id"]
|
||||||
|
|
||||||
|
assert svc0["host"] == socket.gethostname()
|
||||||
|
assert svc1["host"] == socket.gethostname()
|
||||||
|
|
||||||
|
assert "pid" in svc0
|
||||||
|
assert "pid" in svc1
|
||||||
|
assert svc0["pid"] == os.getpid()
|
||||||
|
assert svc1["pid"] == os.getpid()
|
||||||
|
assert "first_heartbeat" in svc0
|
||||||
|
assert "first_heartbeat" in svc1
|
||||||
|
assert "last_heartbeat" in svc0
|
||||||
|
assert "last_heartbeat" in svc1
|
||||||
|
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert svcreg.available_service("no-such-role") == None
|
||||||
|
assert svcreg.available_services("no-such-role") == []
|
||||||
|
# svc0 has less load
|
||||||
|
assert svcreg.available_service("yes-such-role")["id"] == svc0["id"]
|
||||||
|
assert len(svcreg.available_services("yes-such-role")) == 2
|
||||||
|
assert len(svcreg.available_services()) == 2
|
||||||
|
|
||||||
|
svc1["load"] = 50.0
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert svcreg.available_service("no-such-role") == None
|
||||||
|
# now svc1 has less load
|
||||||
|
assert svcreg.available_service("yes-such-role")["id"] == svc1["id"]
|
||||||
|
assert len(svcreg.available_services("yes-such-role")) == 2
|
||||||
|
assert len(svcreg.available_services()) == 2
|
||||||
|
|
||||||
|
svc1["load"] = 200.0
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert svcreg.available_service("no-such-role") == None
|
||||||
|
# now svc0 has less load again
|
||||||
|
assert svcreg.available_service("yes-such-role")["id"] == svc0["id"]
|
||||||
|
assert len(svcreg.available_services("yes-such-role")) == 2
|
||||||
|
assert len(svcreg.available_services()) == 2
|
||||||
|
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
time.sleep(0.2)
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
time.sleep(0.7)
|
||||||
|
assert svcreg.available_service("no-such-role") == None
|
||||||
|
# now it's been too long since the last heartbeat from svc0
|
||||||
|
assert svcreg.available_service("yes-such-role")["id"] == svc1["id"]
|
||||||
|
assert len(svcreg.available_services("yes-such-role")) == 1
|
||||||
|
assert len(svcreg.available_services()) == 1
|
||||||
|
|
||||||
|
svcreg.unregister(svc1["id"])
|
||||||
|
time.sleep(0.2)
|
||||||
|
assert svcreg.available_service("no-such-role") == None
|
||||||
|
assert svcreg.available_service("yes-such-role") == None
|
||||||
|
assert svcreg.available_services("yes-such-role") == []
|
||||||
|
assert svcreg.available_services() == []
|
||||||
|
|
||||||
|
svc0 = {
|
||||||
|
"role": "yes-such-role",
|
||||||
|
"load": 100.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc1 = {
|
||||||
|
"role": "yes-such-role",
|
||||||
|
"load": 200.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc0 = svcreg.heartbeat(svc0)
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
assert len(svcreg.available_services("yes-such-role")) == 2
|
||||||
|
assert len(svcreg.available_services()) == 2
|
||||||
|
svcreg.unregister(svc0["id"])
|
||||||
|
svcreg.unregister(svc1["id"])
|
||||||
|
|
||||||
|
svc0 = {
|
||||||
|
"role": "yes-such-role",
|
||||||
|
"load": 100.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc1 = {
|
||||||
|
"role": "yes-such-role",
|
||||||
|
"load": 200.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc2 = {
|
||||||
|
"role": "another-such-role",
|
||||||
|
"load": 200.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc3 = {
|
||||||
|
"role": "yet-another-such-role",
|
||||||
|
"load": 200.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
svc0 = svcreg.heartbeat(svc0)
|
||||||
|
svc1 = svcreg.heartbeat(svc1)
|
||||||
|
svc2 = svcreg.heartbeat(svc2)
|
||||||
|
svc3 = svcreg.heartbeat(svc3)
|
||||||
|
assert len(svcreg.available_services("yes-such-role")) == 2
|
||||||
|
assert len(svcreg.available_services()) == 4
|
||||||
|
|
||||||
|
def test_svcreg_heartbeat_server_down(rr):
|
||||||
|
class MockRethinker:
|
||||||
|
def table(self, *args, **kwargs):
|
||||||
|
raise Exception('catch me if you can')
|
||||||
|
|
||||||
|
class SortOfFakeServiceRegistry(doublethink.ServiceRegistry):
|
||||||
|
def __init__(self, rethinker):
|
||||||
|
self.rr = rethinker
|
||||||
|
# self._ensure_table() # not doing this here
|
||||||
|
|
||||||
|
# no such rethinkdb server
|
||||||
|
rr = MockRethinker()
|
||||||
|
svcreg = SortOfFakeServiceRegistry(rr)
|
||||||
|
svc0 = {
|
||||||
|
"role": "role-foo",
|
||||||
|
"load": 100.0,
|
||||||
|
"heartbeat_interval": 0.4,
|
||||||
|
}
|
||||||
|
# no exception thrown
|
||||||
|
svc0 = svcreg.heartbeat(svc0)
|
||||||
|
|
||||||
|
# check that status_info was *not* updated
|
||||||
|
assert not 'id' in svc0
|
||||||
|
assert not 'last_heartbeat' in svc0
|
||||||
|
assert not 'first_heartbeat' in svc0
|
||||||
|
assert not 'host' in svc0
|
||||||
|
assert not 'pid' in svc0
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user