forked from saymedia/python-simpledb
- Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest.py
More file actions
Latest commit
308 lines (249 loc) · 10.7 KB
/
Copy pathtest.py
File metadata and controls
308 lines (249 loc) · 10.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
"""
Basic simpledb tests. There's some setup involved in running them since you'll need
an Amazon AWS account that the tests can use. To make this work you'll need a settings.py
file in this directory with the appropriate authorization info. It should look like:
AWS_KEY = 'XXX'
AWS_SECRET = 'XXX'
Several test domains will be created during the tests. They should be removed during
test teardown, so they won't stick around long. If any of the domains that the tests
use already exist, an error will be raised and the tests will stop. This is to prevent
any accidental data corruption if there happens to be a name conflict with one of your
existing domains. If this happens you'll need to manually remove the conflicting domain
then re-run the tests.
Note also that tests sometimes fail because of SimpleDB's eventual consistency character-
istics. For example, if you insert a bunch of items and then do a count it may come up
short for some period of time after the inserts. I haven't come up with a good way around
this problem yet. Patches welcome.
"""
importunittest
importsimpledb
importsimplejson
importsettings
fromcollectionsimportdefaultdict
classDomainNameConflict(Exception): pass
classTransactionError(Exception): pass
classSimpleDBTransaction(object):
"""
A "transaction" simply registers modification events and allows you to
call rollback or finalize to reverse or commit your changes.
"""
def__init__(self, sdb, data):
self.sdb=sdb
self.data=data
self.created_domains=set()
self.modified_items=defaultdict(set)
self.modified_domains=set()
defregister_modified_item(self, domain, item):
ifisinstance(domain, simpledb.Domain):
domain_name=domain.name
else:
domain_name=domain
ifisinstance(item, simpledb.Item):
item_name=item.name
else:
item_name=item
# We only care about domains we're tracking.
ifdomain_nameinself.data.keys():
self.modified_items[domain_name].add(item_name)
defregister_created_domain(self, domain):
ifisinstance(domain, simpledb.Domain):
domain=domain.name
ifdomaininself.data.keys():
self.modified_domains.add(domain)
else:
self.created_domains.add(domain)
defregister_deleted_domain(self, domain):
ifisinstance(domain, simpledb.Domain):
domain=domain.name
ifdomaininself.data.keys():
self.modified_domains.add(domain)
elifdomaininself.created_domains:
self.created_domains.remove(domain)
defrollback(self):
fordomain, itemsinself.modified_items.iteritems():
ifdomaininself.created_domainsordomaininself.modified_domains:
# Don't bother rolling back items in domains we're
# going to delete or recreate from scratch.
continue
foriteminitems:
ifiteminself.data[domain]:
# If it's in data it was modified, so reverse changes.
self.sdb.delete_attributes(domain, item)
self.sdb.put_attributes(domain, item, self.data[domain][item])
else:
# Otherwise it was created, so delete it.
self.sdb.delete_attributes(domain, item)
# Delete created domains.
fordomaininself.created_domains:
delself.sdb[domain]
# Delete and recreate any modified domains.
fordomaininself.modified_domains:
self.sdb.create_domain(domain)
load_data(self.sdb, domain, self.data[domain])
deffinalize(self):
# Don't need to do anything.
pass
classSimpleDB(simpledb.SimpleDB):
"""
Subclass of SimpleDB that registers modifications so we can roll them back after
each test runs.
"""
transaction_stack= []
data= {}
defstart_transaction(self):
# Transactions need their own non-transaction SimpleDB connections.
sdb=simpledb.SimpleDB(self.aws_key, self.aws_secret)
self.transaction_stack.append(SimpleDBTransaction(sdb, self.data))
defend_transaction(self):
try:
transaction=self.transaction_stack.pop()
transaction.finalize()
exceptIndexError:
raiseTransactionError("Tried to end transaction, but no pending transactions exist.")
defrollback(self):
try:
transaction=self.transaction_stack.pop()
transaction.rollback()
exceptIndexError:
raiseTransactionError("Tried to end transaction, but no pending transactions exist.")
def_register_created_domain(self, domain):
try:
self.transaction_stack[-1].register_created_domain(domain)
exceptIndexError:
pass
def_register_modified_item(self, domain, item):
try:
self.transaction_stack[-1].register_modified_item(domain, item)
exceptIndexError:
pass
def_register_deleted_domain(self, domain):
try:
self.transaction_stack[-1].register_deleted_domain(domain)
exceptIndexError:
pass
defcreate_domain(self, name):
ifself.has_domain(name):
raiseDomainNameConflict("Domain called `%s` already exists! Abort!"%name)
self._register_created_domain(name)
returnsuper(SimpleDB, self).create_domain(name)
defdelete_domain(self, domain):
ifisinstance(domain, simpledb.Domain):
domain_name=domain.name
else:
domain_name=domain
self._register_deleted_domain(domain_name)
returnsuper(SimpleDB, self).delete_domain(domain)
defput_attributes(self, domain, item, attributes):
self._register_modified_item(domain, item)
returnsuper(SimpleDB, self).put_attributes(domain, item, attributes)
####################################
# Global SimpleDB connection object.
####################################
sdb=SimpleDB(settings.AWS_KEY, settings.AWS_SECRET)
classTransactionTestCase(unittest.TestCase):
sdb=sdb
def_pre_setup(self):
self.data=simplejson.load(open('fixture.json'))
# Start a transaction
self.sdb.start_transaction()
def_post_teardown(self):
# Reverse the transaction started in _pre_setup
self.sdb.rollback()
def__call__(self, result=None):
"""
Wrapper around default __call__ method to perform common test setup.
"""
try:
self._pre_setup()
except (KeyboardInterrupt, SystemExit):
raise
exceptException:
importsys
result.addError(self, sys.exc_info())
return
super(TransactionTestCase, self).__call__(result)
try:
self._post_teardown()
except (KeyboardInterrupt, SystemExit):
raise
exceptException:
importsys
result.addError(self, sys.exc_info())
return
classSimpleDBTests(TransactionTestCase):
deftest_count(self):
self.assertEquals(self.sdb['test_users'].count(), 100)
deftest_create_domain(self):
domain=self.sdb.create_domain('test_new_domain')
self.assertTrue(isinstance(domain, simpledb.Domain))
self.assertTrue(sdb.has_domain('test_new_domain'))
deftest_delete_domain(self):
domain=self.sdb.create_domain('test_new_domain')
self.assertTrue(sdb.has_domain('test_new_domain'))
delself.sdb['test_new_domain']
self.assertFalse(sdb.has_domain('test_new_domain'))
deftest_simpledb_dictionary(self):
users=self.sdb['test_users']
self.assertTrue(isinstance(users, simpledb.Domain))
self.assertTrue('test_users'in [d.namefordinself.sdb])
deftest_simpledb_domain_dictionary(self):
users=self.sdb['test_users']
katie=users['katie']
self.assertTrue(isinstance(katie, simpledb.Item))
self.assertEquals(katie['age'], '24')
deftest_domain_setitem(self):
mike= {'name': 'Mike', 'age': '25', 'location': 'San Francisco, CA'}
sdb['test_users']['mike'] =mike
forkey, valueinsdb['test_users']['mike'].iteritems():
self.assertEquals(mike[key], value)
deftest_delete(self):
users=self.sdb['test_users']
delusers['lacy']['age']
self.assertFalse('age'inusers['lacy'].keys())
delusers['lacy']
self.assertFalse('lacy'inusers.item_names())
delsdb['test_users']
self.assertFalse('test_users'in [d.namefordinself.sdb])
deftest_select(self):
users=self.sdb['test_users']
self.assertEquals(len(users.filter(simpledb.where(name='Fawn') |
simpledb.where(name='Katie'))), 2)
k_names= ['Katie', 'Kody', 'Kenya', 'Kim']
self.assertTrue(users.filter(name__like='K%').count(), len(k_names))
foriteminusers.filter(name__like='K%'):
self.assertTrue(item['name'] ink_names)
deftest_all(self):
all=self.sdb['test_users'].all()
self.assertEquals(len(set(i.nameforiinall) -set(self.data['test_users'].keys())), 0)
deftest_values(self):
users=self.sdb['test_users'].filter(age__lt='25').values('name', 'age')
under_25= [keyforkey, valueinself.data['test_users'].items() ifvalue['age'] <'25']
self.assertEquals(len(set(i.nameforiinusers) -set(under_25)), 0)
deftest_multiple_values(self):
katie=self.sdb['test_users']['katie']
locations= ['San Francisco, CA', 'Centreville, VA']
katie['location'] =locations
katie.save()
katie=self.sdb['test_users']['katie']
self.assertTrue(locations[0] inkatie['location'])
self.assertTrue(locations[1] inkatie['location'])
self.assertTrue(len(katie['location']), 2)
defload_data(sdb, domain, items):
domain=sdb.create_domain(domain)
items= [simpledb.Item(sdb, domain, name, attributes) for
name, attributesinitems.items()]
# Split into lists of 25 items each (max for BatchPutAttributes).
batches= [items[i:i+25] foriinxrange(0, len(items), 25)]
forbatchinbatches:
sdb.batch_put_attributes(domain, batch)
if__name__=='__main__':
sdb.start_transaction()
print"Loading fixtures..."
domains=simplejson.load(open('fixture.json'))
fordomain, itemsindomains.iteritems():
load_data(sdb, domain, items)
sdb.data=domains
# Run tests.
unittest.main()
# Roll back transaction (delete test domains).
sdb.rollback()