[Checkins] SVN: m01.mongofake/trunk/ - include m01.stub for testing mongofake components and compare with real
Roger Ineichen
cvs-admin at zope.org
Sun Dec 16 07:48:54 UTC 2012
Log message for revision 128680:
- include m01.stub for testing mongofake components and compare with real
pymongo output
- implemented some basic tests and run them with m01.stub and m01.mongofake
libraries
- implemented correct response for collection update method based on new
pymongo 2.4. More will follow if more tests get implemented
- switch from lovely.importchecker to p01.recipe.setup importchecker which
doesn't warn about bad svn url during buildout process
- update TODO.txt
- prepare release
Changed:
U m01.mongofake/trunk/CHANGES.txt
U m01.mongofake/trunk/TODO.txt
U m01.mongofake/trunk/buildout.cfg
U m01.mongofake/trunk/setup.py
U m01.mongofake/trunk/src/m01/mongofake/__init__.py
A m01.mongofake/trunk/src/m01/mongofake/testing/
A m01.mongofake/trunk/src/m01/mongofake/testing/__init__.py
A m01.mongofake/trunk/src/m01/mongofake/testing.txt
U m01.mongofake/trunk/src/m01/mongofake/tests.py
-=-
Modified: m01.mongofake/trunk/CHANGES.txt
===================================================================
--- m01.mongofake/trunk/CHANGES.txt 2012-12-15 19:28:08 UTC (rev 128679)
+++ m01.mongofake/trunk/CHANGES.txt 2012-12-16 07:48:53 UTC (rev 128680)
@@ -2,12 +2,21 @@
CHANGES
=======
-0.2.0 (unreleased)
+0.2.0 (2012-12-16)
------------------
- feature: support pymongo 2.4 API and support new safe write concern
+- implemented some basic tests and run them with m01.stub and m01.mongofake
+ libraries
+- implemented correct response for collection update method based on new
+ pymongo 2.4. More will follow if more tests get implemented
+
+- switch from lovely.importchecker to p01.recipe.setup importchecker which
+ doesn't warn about bad svn url during buildout process
+
+
0.1.1 (2012-12-10)
------------------
Modified: m01.mongofake/trunk/TODO.txt
===================================================================
--- m01.mongofake/trunk/TODO.txt 2012-12-15 19:28:08 UTC (rev 128679)
+++ m01.mongofake/trunk/TODO.txt 2012-12-16 07:48:53 UTC (rev 128680)
@@ -2,4 +2,8 @@
TODO
====
-- add some fake mongodb tests
\ No newline at end of file
+- implement more pymongo methods
+
+- implement m01.mongofake response similar then given from pymongo
+
+- add more tests and compare output from m01.mongofake and pymongo
Modified: m01.mongofake/trunk/buildout.cfg
===================================================================
--- m01.mongofake/trunk/buildout.cfg 2012-12-15 19:28:08 UTC (rev 128679)
+++ m01.mongofake/trunk/buildout.cfg 2012-12-16 07:48:53 UTC (rev 128680)
@@ -6,17 +6,17 @@
[test]
recipe = zc.recipe.testrunner
-eggs = m01.mongofake
+eggs = m01.mongofake [test]
[checker]
-recipe = lovely.recipe:importchecker
+recipe = p01.recipe.setup:importchecker
path = src/m01.mongofake
[coverage]
recipe = zc.recipe.testrunner
-eggs = m01.mongofake
+eggs = m01.mongofake [test]
defaults = ['--all', '--coverage', '../../coverage']
Modified: m01.mongofake/trunk/setup.py
===================================================================
--- m01.mongofake/trunk/setup.py 2012-12-15 19:28:08 UTC (rev 128679)
+++ m01.mongofake/trunk/setup.py 2012-12-16 07:48:53 UTC (rev 128680)
@@ -21,7 +21,7 @@
setup(
name='m01.mongofake',
- version='0.2.0dev',
+ version='0.2.0',
author='Zope Foundation and Contributors',
author_email='zope-dev at zope.org',
description="Fake MongoDB implementation",
@@ -45,7 +45,11 @@
packages=find_packages('src'),
include_package_data=True,
package_dir={'': 'src'},
- extras_require=dict(),
+ extras_require=dict(
+ test=[
+ 'm01.stub',
+ 'zope.testing',
+ ]),
install_requires=[
'setuptools',
'pymongo',
Modified: m01.mongofake/trunk/src/m01/mongofake/__init__.py
===================================================================
--- m01.mongofake/trunk/src/m01/mongofake/__init__.py 2012-12-15 19:28:08 UTC (rev 128679)
+++ m01.mongofake/trunk/src/m01/mongofake/__init__.py 2012-12-16 07:48:53 UTC (rev 128680)
@@ -16,14 +16,18 @@
"""
__docformat__ = "reStructuredText"
+import calendar
import copy
+import pprint as pp
import re
+import struct
+import time
import types
-import pprint as pp
import bson.objectid
import bson.son
-import pymongo.cursor
+import pymongo.cursor
+import pymongo.database
###############################################################################
@@ -118,6 +122,31 @@
])
+def getObjectId(secs=0):
+ """Knows how to generate similar ObjectId based on integer (counter)
+
+ Note: this method can get used if you need to define similar ObjectId
+ in a non persistent environment if need to bootstrap mongo containers.
+ """
+ time_tuple = time.gmtime(secs)
+ ts = calendar.timegm(time_tuple)
+ oid = struct.pack(">i", int(ts)) + "\x00" * 8
+ return bson.objectid.ObjectId(oid)
+
+
+def getObjectIdByTimeStr(tStr, format="%Y-%m-%d %H:%M:%S"):
+ """Knows how to generate similar ObjectId based on a time string
+
+ The time string format used by default is ``%Y-%m-%d %H:%M:%S``.
+ Use the current development time which could prevent duplicated
+ ObjectId. At least some kind of ;-)
+ """
+ time.strptime(tStr, "%Y-%m-%d %H:%M:%S")
+ ts = time.mktime(tStr)
+ oid = struct.pack(">i", int(ts)) + "\x00" * 8
+ return bson.objectid.ObjectId(oid)
+
+
###############################################################################
#
# fake MongoDB
@@ -329,7 +358,7 @@
def __init__(self, database, name):
self.database = database
- self.name = name
+ self.name = unicode(name)
self.full_name = '%s.%s' % (database, name)
self.docs = OrderedData()
@@ -353,18 +382,37 @@
if not isinstance(upsert, types.BooleanType):
raise TypeError("upsert must be an instance of bool")
+ existing = False
+ counter = 0
for key, doc in list(self.docs.items()):
+ if (counter > 0 and not multi):
+ break
for k, v in spec.items():
if k in doc and v == doc[k]:
setData = document.get('$set')
if setData is not None:
# do a partial update based on $set data
for pk, pv in setData.items():
- doc[pk] = pv
+ doc[unicode(pk)] = pv
+ counter += 1
+ existing = True
else:
- self.docs[key] = document
+ d = {}
+ for k, v in list(document.items()):
+ # use unicode keys as mongodb does
+ d[unicode(k)] = v
+ self.docs[unicode(key)] = d
+ existing = True
+ counter += 1
break
+ cid = 42
+ ok = 1.0
+ err = None
+ return {u'updatedExisting': existing, u'connectionId': cid, u'ok': ok,
+ u'err': err, u'n': counter}
+
+
def save(self, to_save, manipulate=True, safe=None, check_keys=True,
**kwargs):
if not isinstance(to_save, types.DictType):
@@ -470,20 +518,36 @@
as_dict[field] = 1
return as_dict
+ def __repr__(self):
+ return "%s(%r, %r)" % (self.__class__.__name__, self.database,
+ self.name)
+
class FakeDatabase(object):
"""Fake mongoDB database."""
def __init__(self, connection, name):
- self.connection = connection
- self.name = name
+ pymongo.database._check_name(name)
+ self.__name = unicode(name)
+ self.__connection = connection
self.cols = {}
+ @property
+ def connection(self):
+ return self.__connection
+
+ @property
+ def name(self):
+ return self.__name
+
def clear(self):
for k, col in self.cols.items():
col.clear()
del self.cols[k]
+ def create_collection(self, name, **kw):
+ return True
+
def collection_names(self):
return list(self.cols.keys())
@@ -497,33 +561,163 @@
def __getitem__(self, name):
return self.__getattr__(name)
- def create_collection(self, name, **kw):
- return True
+ def __iter__(self):
+ return self
+ def next(self):
+ raise TypeError("'Database' object is not iterable")
+ def __call__(self, *args, **kwargs):
+ """This is only here so that some API misusages are easier to debug.
+ """
+ raise TypeError("'Database' object is not callable. If you meant to "
+ "call the '%s' method on a '%s' object it is "
+ "failing because no such method exists." % (
+ self.__name, self.__connection.__class__.__name__))
+
+ def __repr__(self):
+ return "%s(%r, %r)" % (self.__class__.__name__, self.__connection,
+ self.__name)
+
+
class FakeMongoClient(object):
"""Fake MongoDB MongoClient."""
+ HOST = 'localhost'
+ POST = 27017
+
+ __max_bson_size = 4 * 1024 * 1024
+
def __init__(self):
- self.dbs = {}
+ self.__dbs = {}
+ self.__host = None
+ self.__port = None
+ self.__max_pool_size = 10
+ self.__document_class = {}
+ self.__tz_aware = False
+ self.__nodes = []
- def __call__(self, host='localhost', port=27017, tz_aware=True):
+ @property
+ def dbs(self):
+ return self.__dbs
+
+ def __call__(self, host=None, port=None, max_pool_size=10,
+ document_class=dict, tz_aware=False, _connect=True, **kwargs):
+ if host is None:
+ host = self.HOST
+ if isinstance(host, basestring):
+ host = [host]
+ if port is None:
+ port = self.PORT
+ if not isinstance(port, int):
+ raise TypeError("port must be an instance of int")
+
+ self.__max_pool_size = max_pool_size
+ self.__document_class = document_class
+ self.__tz_aware = tz_aware
+
+ seeds = set()
+ username = None
+ password = None
+ db = None
+ opts = {}
+ for entity in host:
+ if "://" in entity:
+ if entity.startswith("mongodb://"):
+ res = pymongo.uri_parser.parse_uri(entity, port)
+ seeds.update(res["nodelist"])
+ username = res["username"] or username
+ password = res["password"] or password
+ db = res["database"] or db
+ opts = res["options"]
+ else:
+ idx = entity.find("://")
+ raise pymongo.errors.InvalidURI("Invalid URI scheme: %s" % (
+ entity[:idx],))
+ else:
+ seeds.update(pymongo.uri_parser.split_hosts(entity, port))
+ if not seeds:
+ raise pymongo.errors.ConfigurationError(
+ "need to specify at least one host")
+
+ self.__nodes = seeds
+ self.__host = None
+ self.__port = None
+
+ if _connect:
+ # _connect=False is not supported yet because we need to implement
+ # some fake host, port setup concept first
+ try:
+ self.__find_node(seeds)
+ except pymongo.errors.AutoReconnect, e:
+ # ConnectionFailure makes more sense here than AutoReconnect
+ raise pymongo.errors.ConnectionFailure(str(e))
+
return self
+ def __find_node(self, seeds=None):
+ # very simple find node implementation
+ errors = []
+ mongos_candidates = []
+ candidates = seeds or self.__nodes.copy()
+ for candidate in candidates:
+ node, ismaster, isdbgrid, res_time = self.__try_node(candidate)
+ return node
+
+ # couldn't find a suitable host.
+ self.disconnect()
+ raise pymongo.errors.AutoReconnect(', '.join(errors))
+
+ def __try_node(self, node):
+ self.disconnect()
+ self.__host, self.__port = node
+ # return node and some fake data
+ ismaster = True
+ isdbgrid = False
+ res_time = None
+ return node, ismaster, isdbgrid, res_time
+
+ @property
+ def host(self):
+ return self.__host
+
+ @property
+ def port(self):
+ return self.__port
+
+ @property
+ def tz_aware(self):
+ return self.__tz_aware
+
+ @property
+ def max_bson_size(self):
+ return self.__max_bson_size
+
+ @property
+ def nodes(self):
+ """List of all known nodes."""
+ return self.__nodes
+
def drop_database(self, name):
- db = self.dbs.get(name)
+ db = self.__dbs.get(name)
if db is not None:
db.clear()
- del self.dbs[name]
+ del self.__dbs[name]
+ def database_names(self):
+ return list(self.__dbs.keys())
+
def disconnect(self):
pass
- def database_names(self):
- return list(self.dbs.keys())
+ def close(self):
+ self.disconnect()
+ def alive(self):
+ return True
+
def __getattr__(self, name):
- db = self.dbs.get(name)
+ db = self.__dbs.get(name)
if db is None:
db = FakeDatabase(self, name)
self.dbs[name] = db
@@ -532,6 +726,26 @@
def __getitem__(self, name):
return self.__getattr__(name)
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.disconnect()
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ raise TypeError("'%s' object is not iterable" % self.__class__.__name__)
+
+ def __repr__(self):
+ if len(self.__nodes) == 1:
+ return "%s(%r, %r)" % (self.__class__.__name__, self.__host, self.__port)
+ else:
+ nodes = ["%s:%d" % n for n in self.__nodes]
+ return "%s(%r)" % (self.__class__.__name__, nodes)
+
+
class FakeMongoConnection(FakeMongoClient):
"""BBB: support old FakeMongoConnection class"""
@@ -540,7 +754,7 @@
fakeMongoClient = FakeMongoClient()
# BBB: support
-fakeMongoConnection = fakeMongoClient
+fakeMongoConnection = FakeMongoConnection()
class FakeMongoConnectionPool(object):
Property changes on: m01.mongofake/trunk/src/m01/mongofake/testing
___________________________________________________________________
Added: svn:ignore
+ sandbox
Added: m01.mongofake/trunk/src/m01/mongofake/testing/__init__.py
===================================================================
--- m01.mongofake/trunk/src/m01/mongofake/testing/__init__.py (rev 0)
+++ m01.mongofake/trunk/src/m01/mongofake/testing/__init__.py 2012-12-16 07:48:53 UTC (rev 128680)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2012 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+import os
+import unittest
+
+import pymongo
+
+import m01.stub.testing
+
+import m01.mongofake
+
+
+# mongo db name used for testing
+TEST_DB_NAME = 'm01_mongofake_database'
+
+
+###############################################################################
+#
+# test helper methods
+#
+###############################################################################
+
+_testClient = None
+
+def getTestClient():
+ return _testClient
+
+
+def getTestDatabase():
+ client = getTestClient()
+ return client[TEST_DB_NAME]
+
+
+def getTestCollection(collectionName='test'):
+ client = getTestClient()
+ db = client[TEST_DB_NAME]
+ return db[collectionName]
+
+
+def dropTestDatabase():
+ client = getTestClient()
+ client.drop_database(TEST_DB_NAME)
+
+
+###############################################################################
+#
+# test setup methods
+#
+###############################################################################
+
+# fake mongodb setup
+def setUpFakeMongo(test=None):
+ """Setup fake (singleton) mongo client"""
+ global _testClient
+ host = 'localhost'
+ port = 45017
+ _testClient = m01.mongofake.fakeMongoClient(host, port)
+
+
+def tearDownFakeMongo(test=None):
+ """Tear down fake mongo client"""
+ global _testClient
+ _testClient = None
+
+
+# stub mongodb server
+def setUpStubMongo(test=None):
+ """Setup real empty mongodb"""
+ host = 'localhost'
+ port = 45017
+ sandBoxDir = os.path.join(os.path.dirname(__file__), 'sandbox')
+ m01.stub.testing.startMongoDBServer(host, port, sandBoxDir=sandBoxDir)
+ # ensure that we use a a real MongoClient
+ global _testClient
+ _testClient = pymongo.MongoClient(host, port)
+
+
+def tearDownStubMongo(test=None):
+ """Tear down real mongodb"""
+ sleep = 0.5
+ m01.stub.testing.stopMongoDBServer(sleep)
Property changes on: m01.mongofake/trunk/src/m01/mongofake/testing/__init__.py
___________________________________________________________________
Added: svn:keywords
+ Date Author Id Revision
Added: svn:eol-style
+ native
Added: m01.mongofake/trunk/src/m01/mongofake/testing.txt
===================================================================
--- m01.mongofake/trunk/src/m01/mongofake/testing.txt (rev 0)
+++ m01.mongofake/trunk/src/m01/mongofake/testing.txt 2012-12-16 07:48:53 UTC (rev 128680)
@@ -0,0 +1,112 @@
+=======
+pymongo
+=======
+
+This test, if running with --all or level 2 will donaload and start a real
+mongodb instance on localhost:45017. See testing setup for more information.
+
+ >>> from m01.mongofake import getObjectId
+ >>> from m01.mongofake import pprint
+
+helper
+------
+
+We can simply use our test helper methods for get the current mongodb test
+client, database or collection.
+
+ >>> from m01.mongofake.testing import getTestClient
+ >>> getTestClient()
+ MongoClient('localhost', 45017)
+
+ >>> from m01.mongofake.testing import getTestDatabase
+ >>> getTestDatabase()
+ Database(MongoClient('localhost', 45017), u'm01_mongofake_database')
+
+ >>> from m01.mongofake.testing import getTestCollection
+ >>> getTestCollection()
+ Collection(Database(MongoClient('localhost', 45017), u'm01_mongofake_database'), u'test')
+
+
+pymongo
+-------
+
+Let's test some simple pymongo methods. Setup up some objects and get them
+back. First add a new collection:
+
+ >>> client = getTestClient()
+ >>> db = client.m01_mongofake_database
+ >>> collection = db.fruits
+ >>> data = {'_id': getObjectId(1),
+ ... 'name': u'apple',
+ ... 'color': u'green'}
+ >>> oid = collection.insert(data)
+ >>> oid
+ ObjectId('000000010000000000000000')
+
+find_one:
+
+ >>> data = client.m01_mongofake_database.fruits.find_one({'_id': oid})
+ >>> pprint(data)
+ {u'_id': ObjectId('000000010000000000000000'),
+ u'color': u'green',
+ u'name': u'apple'}
+
+update:
+
+ >>> data['fresh'] = True
+ >>> client.m01_mongofake_database.fruits.update({'_id': oid}, data)
+ {u'updatedExisting': True, u'connectionId': 2, u'ok': 1.0, u'err': None, u'n': 1}
+
+find:
+
+ >>> for doc in client.m01_mongofake_database.fruits.find({'_id': oid}):
+ ... pprint(doc)
+ {u'_id': ObjectId('000000010000000000000000'),
+ u'color': u'green',
+ u'fresh': True,
+ u'name': u'apple'}
+
+
+add more apples:
+
+ >>> data = {'_id': getObjectId(2),
+ ... 'name': u'apple',
+ ... 'color': u'red',
+ ... 'fresh': True,}
+ >>> db.fruits.insert(data)
+ ObjectId('000000020000000000000000')
+
+find (all):
+
+ >>> for doc in client.m01_mongofake_database.fruits.find({'fresh': True}):
+ ... pprint(doc)
+ {u'_id': ObjectId('000000010000000000000000'),
+ u'color': u'green',
+ u'fresh': True,
+ u'name': u'apple'}
+ {u'_id': ObjectId('000000020000000000000000'),
+ u'color': u'red',
+ u'fresh': True,
+ u'name': u'apple'}
+
+update multi (update response):
+
+ >>> data = {'$set': {'fresh': False}}
+ >>> client.m01_mongofake_database.fruits.update({'fresh': True}, data,
+ ... multi=True)
+ {u'updatedExisting': True, u'connectionId': 2, u'ok': 1.0, u'err': None, u'n': 2}
+
+ >>> cursor = client.m01_mongofake_database.fruits.find({'fresh': False})
+ >>> cursor.count()
+ 2
+
+ >>> for doc in client.m01_mongofake_database.fruits.find({'fresh': False}):
+ ... pprint(doc)
+ {u'_id': ObjectId('000000010000000000000000'),
+ u'color': u'green',
+ u'fresh': False,
+ u'name': u'apple'}
+ {u'_id': ObjectId('000000020000000000000000'),
+ u'color': u'red',
+ u'fresh': False,
+ u'name': u'apple'}
Property changes on: m01.mongofake/trunk/src/m01/mongofake/testing.txt
___________________________________________________________________
Added: svn:keywords
+ Date Author Id Revision
Added: svn:eol-style
+ native
Modified: m01.mongofake/trunk/src/m01/mongofake/tests.py
===================================================================
--- m01.mongofake/trunk/src/m01/mongofake/tests.py 2012-12-15 19:28:08 UTC (rev 128679)
+++ m01.mongofake/trunk/src/m01/mongofake/tests.py 2012-12-16 07:48:53 UTC (rev 128680)
@@ -16,17 +16,68 @@
"""
__docformat__ = "reStructuredText"
+import re
import unittest
import doctest
+from zope.testing.renormalizing import RENormalizing
+import m01.mongofake.testing
+
+
+CHECKER = RENormalizing([
+ (re.compile('connectionId'), '...'),
+ ])
+
+
+FAKE_CHECKER = RENormalizing([
+ (re.compile('FakeMongoClient'), 'MongoClient'),
+ (re.compile('FakeDatabase'), 'Database'),
+ (re.compile('FakeCollection'), 'Collection'),
+ (re.compile("'connectionId': [0-9]+"), r"'connectionId': ..."),
+ ])
+
+
def test_suite():
- return unittest.TestSuite((
+ """This test suite will run the tests with the fake and a real mongodb and
+ make sure both output are the same.
+ """
+ suites = []
+ append = suites.append
+
+ # real mongo database tests using m01.stub using level 2 tests (--all)
+ testNames = ['testing.txt',
+ ]
+ for name in testNames:
+ suite = unittest.TestSuite((
+ doctest.DocFileSuite(name,
+ setUp=m01.mongofake.testing.setUpStubMongo,
+ tearDown=m01.mongofake.testing.tearDownStubMongo,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ checker=CHECKER),
+ ))
+ suite.level = 2
+ append(suite)
+
+ # fake mongo database tests using FakeMongoClient
+ for name in testNames:
+ append(
+ doctest.DocFileSuite(name,
+ setUp=m01.mongofake.testing.setUpFakeMongo,
+ tearDown=m01.mongofake.testing.tearDownFakeMongo,
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ checker=FAKE_CHECKER),
+ )
+
+ # additional non mongodb tests
+ append(
doctest.DocFileSuite('README.txt',
- optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
- ),
- ))
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
+ )
+ # return test suite
+ return unittest.TestSuite(suites)
+
if __name__=='__main__':
unittest.main(defaultTest='test_suite')
More information about the checkins
mailing list