1from couchstore import CouchStore, DocumentInfo, SizedBuf
2import os
3import struct
4import unittest
5
6
7def removeIfExists(path):
8    try:
9        os.remove(path)
10    except OSError:
11        pass
12
13
14class NonexistentCouchStoreTest (unittest.TestCase):
15    def testNonexistent(self):
16        removeIfExists("/tmp/nonexistent.couch")
17        self.assertRaises(OSError, CouchStore, "/tmp/nonexistent.couch")
18
19
20class CouchStoreTest (unittest.TestCase):
21    def setUp(self):
22        removeIfExists("/tmp/test.couch")
23        self.store = CouchStore("/tmp/test.couch", 'c')
24
25    def tearDown(self):
26        self.store.close()
27        os.remove("/tmp/test.couch")
28
29    def testBasicSave(self):
30        sequence = self.store.save("foo", "value of foo")
31        self.assertEqual(sequence, 1)
32        self.store.commit()
33        value = self.store.get("foo")
34        self.assertEqual(value, "value of foo")
35
36    def testMissingKey(self):
37        self.assertRaises(KeyError, self.store.get, "nonexistent")
38
39    def testBadKey(self):
40        self.assertRaises(TypeError, self.store.get, 0)
41        self.assertRaises(TypeError, self.store.get, None)
42        self.assertRaises(TypeError, self.store.get, [123])
43
44    def testInfo(self):
45        value = "value"
46        sequence = self.store.save("foo", value)
47        self.assertEqual(sequence, 1)
48        info = self.store.getInfo("foo")
49        self.assertEqual(info.id, "foo")
50        self.assertEqual(info.sequence, sequence)
51        self.assertFalse(info.deleted)
52        #self.assertEqual(info.size, len(value))   #FIXME: Not currently equal, due to bug in CouchStore itself
53        self.assertEqual(info.getContents(), value)
54
55    def testInfoBySequence(self):
56        value = "value"
57        sequence = self.store.save("foo", value)
58        self.assertEqual(sequence, 1)
59        info = self.store.getInfoBySequence(sequence)
60        self.assertEqual(info.id, "foo")
61        self.assertEqual(info.sequence, sequence)
62        self.assertFalse(info.deleted)
63        #self.assertEqual(info.size, len(value))   #FIXME: Not currently equal, due to bug in CouchStore itself
64        self.assertEqual(info.getContents(), value)
65
66    def testMissingSequence(self):
67        self.store.save("foo", "value")
68        self.assertRaises(KeyError, self.store.getInfoBySequence, 99999)
69        self.assertRaises(TypeError, self.store.getInfoBySequence, "huh")
70
71    def testNoContents(self):
72        info = DocumentInfo("howdy")
73        self.assertRaises(Exception, info.getContents)
74
75    def testMetadata(self):
76        info = DocumentInfo("meta")
77        info.revSequence = 23
78        info.revMeta = "fancy metadata here"
79        info.contentType = DocumentInfo.INVALID_JSON
80        self.store[info] = "the regular non-meta data"
81
82        gotInfo = self.store.getInfo("meta")
83        self.assertEquals(gotInfo.id, "meta")
84        self.assertEquals(gotInfo.revSequence, info.revSequence)
85        self.assertEquals(gotInfo.revMeta, info.revMeta)
86        self.assertEquals(gotInfo.contentType, info.contentType)
87        self.assertFalse(gotInfo.compressed)
88
89    def testMetadataSave(self):
90        info = DocumentInfo("meta")
91        info.revSequence = 23
92        info.revMeta = "fancy metadata here"
93        info.contentType = DocumentInfo.INVALID_JSON
94        self.store[info] = "the regular non-meta data"
95
96        self.store.commit()
97        self.store.close()
98        self.store = CouchStore("/tmp/test.couch", 'r')
99
100        gotInfo = self.store.getInfo("meta")
101        self.assertEquals(gotInfo.id, "meta")
102        self.assertEquals(gotInfo.revSequence, info.revSequence)
103        self.assertEquals(gotInfo.revMeta, info.revMeta)
104        self.assertEquals(gotInfo.contentType, info.contentType)
105        self.assertFalse(gotInfo.compressed)
106
107    def testCompression(self):
108        value = "this value is text and text is valued"
109        self.store.save("key", value, CouchStore.COMPRESS)
110        self.assertEqual(self.store.get("key", CouchStore.DECOMPRESS), value)
111        info = self.store.getInfo("key")
112        self.assertTrue(info.compressed)
113
114    def expectedKey(self, i):
115        return "key_%2d" % (i + 1)
116
117    def expectedValue(self, i):
118        return "Hi there! I'm value #%d!" % (i + 1)
119
120    def addDocs(self, n):
121        for i in xrange(n):
122            self.store.save(self.expectedKey(i), self.expectedValue(i))
123
124    def addBulkDocs(self, n):
125        ids = [self.expectedKey(i) for i in xrange(n)]
126        datas = [self.expectedValue(i) for i in xrange(n)]
127        self.store.saveMultiple(ids, datas)
128
129    def testMultipleDocs(self):
130        self.addDocs(1000)
131        for i in xrange(1000):
132            self.assertEqual(self.store[self.expectedKey(i)], self.expectedValue(i))
133
134        info = self.store.getDbInfo()
135        self.assertEquals(info.filename, "/tmp/test.couch")
136        self.assertEquals(info.last_sequence, 1000)
137        self.assertEquals(info.doc_count, 1000)
138        self.assertEquals(info.deleted_count, 0)
139
140    def testBulkDocs(self):
141        self.addBulkDocs(1000)
142        for i in xrange(1000):
143            self.assertEqual(self.store[self.expectedKey(i)], self.expectedValue(i))
144
145    def testDelete(self):
146        self.store["key"] = "value"
147        del self.store["key"]
148        self.assertRaises(KeyError, self.store.get, "key")
149        info = self.store.getInfo("key")
150        self.assertTrue(info.deleted)
151        self.assertEqual(info.id, "key")
152
153        info = self.store.getDbInfo()
154        self.assertEquals(info.last_sequence, 2)
155        self.assertEquals(info.doc_count, 0)
156        self.assertEquals(info.deleted_count, 1)
157
158    def testChangesSince(self):
159        self.addDocs(50)
160        changes = self.store.changesSince(0)
161        self.assertEqual(len(changes), 50)
162        for i in xrange(50):
163            self.assertEqual(changes[i].id, self.expectedKey(i))
164
165    def testForAllDocs(self):
166        self.addDocs(50)
167        docCount = [0]
168
169        def checkDoc(docInfo):
170            self.assertEquals(docInfo.id, self.expectedKey(docCount[0]))
171            docCount[0] += 1
172
173        self.store.forEachDoc(None, None, checkDoc)
174        self.assertEqual(docCount[0], 50)
175
176    def testDocumentInfoRepr(self):
177        self.addDocs(1)
178
179        def checkDoc(docInfo):
180            expected = "DocumentInfo('%s', %d bytes)" % (docInfo.id,
181                                                         docInfo.physSize)
182            self.assertEquals(str(docInfo), expected)
183            self.assertEquals(repr(docInfo), expected)
184
185        self.store.forEachDoc(None, None, checkDoc)
186
187    def testForSomeDocs(self):
188        self.addDocs(50)
189        docCount = [0]
190
191        def checkDoc(docInfo):
192            self.assertEquals(docInfo.id, self.expectedKey(docCount[0]))
193            docCount[0] += 1
194
195        self.store.forEachDoc(None, self.expectedKey(10), checkDoc)
196        self.assertEqual(docCount[0], 11)
197
198        docCount = [10]
199        self.store.forEachDoc(self.expectedKey(10), None, checkDoc)
200        self.assertEqual(docCount[0], 50)
201
202        docCount = [10]
203        self.store.forEachDoc(self.expectedKey(10), self.expectedKey(20), checkDoc)
204        self.assertEqual(docCount[0], 21)
205
206    def testLocalDocs(self):
207        locals = self.store.localDocs
208        self.assertRaises(KeyError, locals.__getitem__, "hello")
209        locals["hello"] = "goodbye"
210        self.assertEqual(locals["hello"], "goodbye")
211        locals["hello"] = "bonjour"
212        self.assertEqual(locals["hello"], "bonjour")
213        del locals["hello"]
214        self.assertRaises(KeyError, locals.__getitem__, "hello")
215
216    def testSizedBuf(self):
217        # Converting Python strings to/from SizedBufs is tricky enough (when
218        # the strings might contain null bytes) that it's worth a unit test of
219        # its own.
220        data = "foooooobarrrr"
221        buf = SizedBuf(data)
222        self.assertEqual(buf.size, len(data))
223        self.assertEqual(str(buf), data)
224        # Now try some binary data with nul bytes in it:
225        data = "foo\000bar"
226        buf = SizedBuf(data)
227        self.assertEqual(buf.size, len(data))
228        self.assertEqual(str(buf), data)
229
230    def testBinaryMeta(self):
231        # Make sure binary data, as produced by Python's struct module, works
232        # in revMeta.
233        packed = struct.pack(">QII", 0, 1, 2)
234        d = DocumentInfo("bin")
235        d.revMeta = packed
236        self.store[d] = "value"
237
238        doc_info = self.store.getInfo("bin")
239        self.assertEqual(doc_info.revMeta, packed)
240        i1, i2, i3 = struct.unpack(">QII", doc_info.revMeta)
241        self.assertEqual(i1, 0)
242        self.assertEqual(i2, 1)
243        self.assertEqual(i3, 2)
244
245    def testMultipleMeta(self):
246        k = []
247        v = []
248        for i in range(1000):
249            d = DocumentInfo(str(i))
250            d.revMeta = "hello-%s" % i
251            k.append(d)
252            v.append("world-%s" % i)
253        self.store.saveMultiple(k, v)
254        self.store.commit()
255        self.store.close()
256        self.store = CouchStore("/tmp/test.couch", 'r')
257        for doc_info in self.store.changesSince(0):
258            i = int(doc_info.id)
259            self.assertEqual(doc_info.revMeta, "hello-%s" % i)
260            doc_contents = doc_info.getContents()
261            self.assertEqual(doc_contents, "world-%s" % i)
262
263    def testMultipleMetaStruct(self):
264        k = []
265        v = []
266        for i in range(1000):
267            d = DocumentInfo(str(i))
268            d.revMeta = struct.pack(">QII", i * 3, i * 2, i)
269            k.append(d)
270            v.append("world-%s" % i)
271        self.store.saveMultiple(k, v)
272        self.store.commit()
273        self.store.close()
274        self.store = CouchStore("/tmp/test.couch", 'r')
275        for doc_info in self.store.changesSince(0):
276            i = int(doc_info.id)
277            i3, i2, i1 = struct.unpack(">QII", doc_info.revMeta)
278            self.assertEqual(i3, i * 3)
279            self.assertEqual(i2, i * 2)
280            self.assertEqual(i1, i * 1)
281            doc_contents = doc_info.getContents()
282            self.assertEqual(doc_contents, "world-%s" % doc_info.id)
283
284
285if __name__ == '__main__':
286    unittest.main()
287