Skip to content

Commit

Permalink
begin testing db api as an interface
Browse files Browse the repository at this point in the history
  • Loading branch information
djmitche committed May 8, 2012
1 parent 325e697 commit 0b87908
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 112 deletions.
12 changes: 8 additions & 4 deletions master/buildbot/test/fake/fakedb.py
Expand Up @@ -67,6 +67,10 @@ def __init__(self, **kwargs):
setattr(self, col, {})
for col in kwargs.keys():
assert col in self.defaults, "%s is not a valid column" % col
# cast to unicode
for k, v in self.values.iteritems():
if isinstance(v, str):
self.values[k] = unicode(v)
# make the values appear as attributes
self.__dict__.update(self.values)

Expand Down Expand Up @@ -343,7 +347,7 @@ def insertTestData(self, rows):
ch = self.changes[row.changeid]
n, vs = row.property_name, row.property_value
v, s = json.loads(vs)
ch.properties.setProperty(n, v, s)
ch.properties[n] = (v, s)

elif isinstance(row, ChangeUser):
ch = self.changes[row.changeid]
Expand All @@ -354,7 +358,7 @@ def insertTestData(self, rows):
def addChange(self, author=None, files=None, comments=None, is_dir=0,
revision=None, when_timestamp=None, branch=None,
category=None, revlink='', properties={}, repository='',
project='', codebase='', uid=None):
codebase='', project='', uid=None):
if self.changes:
changeid = max(self.changes.iterkeys()) + 1
else:
Expand Down Expand Up @@ -383,9 +387,9 @@ def getLatestChangeid(self):
return defer.succeed(max(self.changes.iterkeys()))
return defer.succeed(None)

def getChange(self, changeid):
def getChange(self, key, no_cache=False):
try:
row = self.changes[changeid]
row = self.changes[key]
except KeyError:
return defer.succeed(None)

Expand Down
114 changes: 114 additions & 0 deletions master/buildbot/test/interfaces/test_db_changes.py
@@ -0,0 +1,114 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

from twisted.trial import unittest
from buildbot.test.fake import fakedb, fakemaster
from buildbot.test.util import interfaces, connector_component, dbtype
from buildbot.db import changes

change13 = [
fakedb.Change(changeid=13, author="dustin",
comments="fix spelling", is_dir=0, branch="master",
revision="deadbeef", when_timestamp=266738400,
revlink=None, category=None, repository='', codebase='',
project=''),

fakedb.ChangeFile(changeid=13, filename='master/README.txt'),
fakedb.ChangeFile(changeid=13, filename='slave/README.txt'),

fakedb.ChangeProperty(changeid=13, property_name='notest',
property_value='["no","Change"]'),
]

class Tests(interfaces.InterfaceTests):

def setUp(self):
raise NotImplementedError

def test_signature_addChange(self):
@self.assertArgSpecMatches(self.db.changes.addChange)
def addChange(self, author=None, files=None, comments=None, is_dir=0,
revision=None, when_timestamp=None, branch=None, category=None,
revlink='', properties={}, repository='', codebase='',
project='', uid=None):
pass

def test_signature_getChange(self):
@self.assertArgSpecMatches(self.db.changes.getChange)
def getChange(self, key, no_cache=False):
pass

def test_getChange_none(self):
d = self.db.changes.getChange(99)
@d.addCallback
def check(chdict):
self.assertEqual(chdict, None)
return d

def test_getChange_chdict(self):
d = self.db.changes.getChange(13)
@d.addCallback
def check(chdict):
dbtype.verifyChdict(self, chdict)
return d

def test_signature_getChangeUids(self):
@self.assertArgSpecMatches(self.db.changes.getChangeUids)
def getChangeUids(self, changeid):
pass

def test_signature_getRecentChanges(self):
@self.assertArgSpecMatches(self.db.changes.getRecentChanges)
def getRecentChanges(self, count):
pass

def test_signature_getLatestChangeid(self):
@self.assertArgSpecMatches(self.db.changes.getLatestChangeid)
def getLatestChangeid(self):
pass


class RealTests(Tests):

# tests that only "real" implementations will pass
pass

class TestFakeDB(unittest.TestCase, Tests):

def setUp(self):
self.master = fakemaster.make_master()
self.db = fakedb.FakeDBConnector(self)
return self.db.insertTestData(change13)

class TestRealDB(unittest.TestCase,
connector_component.ConnectorComponentMixin,
RealTests):

def setUp(self):
d = self.setUpConnectorComponent(
table_names=['changes', 'change_files',
'change_properties', 'scheduler_changes', 'objects',
'sourcestampsets', 'sourcestamps', 'sourcestamp_changes',
'patches', 'change_users', 'users'])

@d.addCallback
def finish_setup(_):
self.db.changes = changes.ChangesConnectorComponent(self.db)
return self.insertTestData(change13)
return d

def tearDown(self):
return self.tearDownConnectorComponent()

Expand Up @@ -14,15 +14,15 @@
# Copyright Buildbot Team Members

from twisted.trial import unittest
from buildbot.test.util import resourcetype
from buildbot.test.util import typeverifier

class ResourceTypeVerifier(unittest.TestCase):
class TypeVerifier(unittest.TestCase):

verifier = None
def setUp(self):
# just set this up once..
if not self.verifier:
verifier = resourcetype.ResourceTypeVerifier(
verifier = typeverifier.TypeVerifier(
'testtype',
attrs=dict(testid='integer', somestring='string',
string_or_none='string:none'))
Expand Down Expand Up @@ -62,7 +62,7 @@ def test_nonmatching_wrong_type(self):
'string_or_none' : None }))

def do_test_type(self, type, matching, nonmatching):
verif = resourcetype.ResourceTypeVerifier(type + '-test',
verif = typeverifier.TypeVerifier(type + '-test',
attrs=dict(x=type))
for v in matching:
verif(self, dict(x=v))
Expand Down
37 changes: 37 additions & 0 deletions master/buildbot/test/util/dbtype.py
@@ -0,0 +1,37 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

# concrete verifiers for documented resource types

from buildbot.test.util import typeverifier

verifyChdict = typeverifier.TypeVerifier('chdict',
attrs = dict(
changeid='integer',
author='string',
files='stringlist',
comments='string',
revision='string:none',
when_timestamp='datetime',
branch='string:none',
category='string:none',
revlink='string:none',
properties='sourcedProperties',
repository='string',
project='string',
codebase='string',
is_dir='integer',
))

22 changes: 20 additions & 2 deletions master/buildbot/test/util/interfaces.py
Expand Up @@ -20,9 +20,27 @@ class InterfaceTests(object):
# assertions

def assertArgSpecMatches(self, actual):
def filter(spec):
# the tricky thing here is to align args and defaults, since the
# defaults correspond to the *last* n elements of args. To make
# things easier, we go in reverse, and keep a separate counter for
# the defaults
args = spec[0]
defaults = list(spec[3] or [])
di = -1
for ai in xrange(len(args)-1, -1, -1):
arg = args[ai]
if arg.startswith('_') or (arg == 'self' and ai == 0):
del args[ai]
if -di <= len(defaults):
del defaults[di]
di -= 1

return (args, spec[1], spec[2], defaults or None)

def wrap(template):
actual_argspec = inspect.getargspec(actual)
template_argspec = inspect.getargspec(template)
actual_argspec = filter(inspect.getargspec(actual))
template_argspec = filter(inspect.getargspec(template))
if actual_argspec != template_argspec:
msg = "Expected: %s; got: %s" % (
inspect.formatargspec(*template_argspec),
Expand Down
105 changes: 3 additions & 102 deletions master/buildbot/test/util/resourcetype.py
Expand Up @@ -13,109 +13,9 @@
#
# Copyright Buildbot Team Members

from buildbot.util import json
from buildbot.data import base
from buildbot.test.util import typeverifier

class ResourceTypeVerifier(object):

def __init__(self, name, attrs={}):
"""
@param attrs: dict mapping name to type; types are given in
_makeValidators, below. any type can have a suffix ":none"
allowing its value to be None
"""
self.name = name
self.attrs = attrs

self.validators = self._makeValidators(attrs)

def __call__(self, testcase, res):
testcase.assertIsInstance(res, dict)

problems = []
res_keys = set(res)
exp_keys = set(self.attrs)
if res_keys != exp_keys:
unk_keys = res_keys - exp_keys
missing_keys = exp_keys - res_keys
if unk_keys:
problems.append("unknown key%s %s"
% ('s' if len(unk_keys) > 1 else '',
', '.join(map(repr, unk_keys))))
if missing_keys:
problems.append("missing key%s %s"
% ('s' if len(missing_keys) > 1 else '',
', '.join(map(repr, missing_keys))))

for k in res_keys & exp_keys:
if not self.validators[k](res[k]):
problems.append('value for "%s" does not match type %s' %
(k, self.attrs[k]))

if problems:
msg = "%r is not a %s: " % (res, self.name,)
msg += '; '.join(problems)
testcase.fail(msg)

def _makeValidators(self, attrs):
# helper functions
def orNone(valfn):
return lambda v : v is None or valfn(v)

def stringlist(value):
if not isinstance(value, list):
return False
for x in value:
if not isinstance(x, unicode):
return False
return True

def sourcedProperties(value):
if not isinstance(value, dict):
return False
for k, v in value.iteritems():
if not isinstance(k, unicode):
return False
if not isinstance(v, tuple):
return False
if not 2 == len(v):
return False
propval, propsrc = v
if not isinstance(propsrc, unicode):
return False
try:
json.dumps(propval)
except:
return False
return True

validators = {}
for name, typ in attrs.iteritems():
noneok = typ.endswith(':none')
if noneok:
typ = typ[:-5]

if typ == 'integer':
valfn = lambda v : isinstance(v, int)
elif typ == 'string':
# note that we intentionally *enforce* unicode!
valfn = lambda v : isinstance(v, unicode)
elif typ == 'stringlist':
valfn = stringlist
elif typ == 'sourcedProperties':
valfn = sourcedProperties
elif typ == 'Link':
valfn = lambda v : isinstance(v, base.Link)
else:
raise RuntimeError('invalid type %s' % typ)

validators[name] = orNone(valfn) if noneok else valfn

return validators

# concrete verifiers for documented resource types

verifyChange = ResourceTypeVerifier('change',
verifyChange = typeverifier.TypeVerifier('change',
attrs = dict(
changeid='integer',
author='string',
Expand All @@ -132,3 +32,4 @@ def sourcedProperties(value):
codebase='string',
link='Link',
))

0 comments on commit 0b87908

Please sign in to comment.