unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
blob 55069b5e35df85bb0739a1a0d865302c6693af32 10591 bytes (raw)
name: bindings/python-cffi/tests/test_database.py 	 # note: path name is non-authoritative(*)

  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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
 
import collections
import configparser
import os
import pathlib

import pytest

import notmuch2
import notmuch2._errors as errors
import notmuch2._database as dbmod
import notmuch2._message as message


@pytest.fixture
def db(maildir):
    with dbmod.Database.create(maildir.path) as db:
        yield db


class TestDefaultDb:
    """Tests for reading the default database.

    The error cases are fairly undefined, some relevant Python error
    will come out if you give it a bad filename or if the file does
    not parse correctly.  So we're not testing this too deeply.
    """

    def test_config_pathname_default(self, monkeypatch):
        monkeypatch.delenv('NOTMUCH_CONFIG', raising=False)
        user = pathlib.Path('~/.notmuch-config').expanduser()
        assert dbmod._config_pathname() == user

    def test_config_pathname_env(self, monkeypatch):
        monkeypatch.setenv('NOTMUCH_CONFIG', '/some/random/path')
        assert dbmod._config_pathname() == pathlib.Path('/some/random/path')

    def test_default_path_nocfg(self, monkeypatch, tmppath):
        monkeypatch.setenv('NOTMUCH_CONFIG', str(tmppath/'foo'))
        with pytest.raises(FileNotFoundError):
            dbmod.Database.default_path()

    def test_default_path_cfg_is_dir(self, monkeypatch, tmppath):
        monkeypatch.setenv('NOTMUCH_CONFIG', str(tmppath))
        with pytest.raises(IsADirectoryError):
            dbmod.Database.default_path()

    def test_default_path_parseerr(self, monkeypatch, tmppath):
        cfg = tmppath / 'notmuch-config'
        with cfg.open('w') as fp:
            fp.write('invalid')
        monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg))
        with pytest.raises(configparser.Error):
            dbmod.Database.default_path()

    def test_default_path_parse(self, monkeypatch, tmppath):
        cfg = tmppath / 'notmuch-config'
        with cfg.open('w') as fp:
            fp.write('[database]\n')
            fp.write('path={!s}'.format(tmppath))
        monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg))
        assert dbmod.Database.default_path() == tmppath

    def test_default_path_param(self, monkeypatch, tmppath):
        cfg_dummy = tmppath / 'dummy'
        monkeypatch.setenv('NOTMUCH_CONFIG', str(cfg_dummy))
        cfg_real = tmppath / 'notmuch_config'
        with cfg_real.open('w') as fp:
            fp.write('[database]\n')
            fp.write('path={!s}'.format(cfg_real/'mail'))
        assert dbmod.Database.default_path(cfg_real) == cfg_real/'mail'


class TestCreate:

    def test_create(self, tmppath, db):
        assert tmppath.joinpath('.notmuch/xapian/').exists()

    def test_create_already_open(self, tmppath, db):
        with pytest.raises(errors.NotmuchError):
            db.create(tmppath)

    def test_create_existing(self, tmppath, db):
        with pytest.raises(errors.FileError):
            dbmod.Database.create(path=tmppath)

    def test_close(self, db):
        db.close()

    def test_del_noclose(self, db):
        del db

    def test_close_del(self, db):
        db.close()
        del db

    def test_closed_attr(self, db):
        assert not db.closed
        db.close()
        assert db.closed

    def test_ctx(self, db):
        with db as ctx:
            assert ctx is db
            assert not db.closed
        assert db.closed

    def test_path(self, db, tmppath):
        assert db.path == tmppath

    def test_version(self, db):
        assert db.version > 0

    def test_needs_upgrade(self, db):
        assert db.needs_upgrade in (True, False)


class TestAtomic:

    def test_exit_early(self, db):
        with pytest.raises(errors.UnbalancedAtomicError):
            with db.atomic() as ctx:
                ctx.force_end()

    def test_exit_late(self, db):
        with db.atomic() as ctx:
            pass
        with pytest.raises(errors.UnbalancedAtomicError):
            ctx.force_end()


class TestRevision:

    def test_single_rev(self, db):
        r = db.revision()
        assert isinstance(r, dbmod.DbRevision)
        assert isinstance(r.rev, int)
        assert isinstance(r.uuid, bytes)
        assert r is r
        assert r == r
        assert r <= r
        assert r >= r
        assert not r < r
        assert not r > r

    def test_diff_db(self, tmppath):
        dbpath0 = tmppath.joinpath('db0')
        dbpath0.mkdir()
        dbpath1 = tmppath.joinpath('db1')
        dbpath1.mkdir()
        db0 = dbmod.Database.create(path=dbpath0)
        db1 = dbmod.Database.create(path=dbpath1)
        r_db0 = db0.revision()
        r_db1 = db1.revision()
        assert r_db0 != r_db1
        assert r_db0.uuid != r_db1.uuid

    def test_cmp(self, db, maildir):
        rev0 = db.revision()
        _, pathname = maildir.deliver()
        db.add(pathname, sync_flags=False)
        rev1 = db.revision()
        assert rev0 < rev1
        assert rev0 <= rev1
        assert not rev0 > rev1
        assert not rev0 >= rev1
        assert not rev0 == rev1
        assert rev0 != rev1

    # XXX add tests for revisions comparisons

class TestMessages:

    def test_add_message(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg, dup = db.add(pathname, sync_flags=False)
        assert isinstance(msg, message.Message)
        assert msg.path == pathname
        assert msg.messageid == msgid

    def test_add_message_str(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg, dup = db.add(str(pathname), sync_flags=False)

    def test_add_message_bytes(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg, dup = db.add(os.fsencode(bytes(pathname)), sync_flags=False)

    def test_remove_message(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg, dup = db.add(pathname, sync_flags=False)
        assert db.find(msgid)
        dup = db.remove(pathname)
        with pytest.raises(LookupError):
            db.find(msgid)

    def test_remove_message_str(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg, dup = db.add(pathname, sync_flags=False)
        assert db.find(msgid)
        dup = db.remove(str(pathname))
        with pytest.raises(LookupError):
            db.find(msgid)

    def test_remove_message_bytes(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg, dup = db.add(pathname, sync_flags=False)
        assert db.find(msgid)
        dup = db.remove(os.fsencode(bytes(pathname)))
        with pytest.raises(LookupError):
            db.find(msgid)

    def test_find_message(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg0, dup = db.add(pathname, sync_flags=False)
        msg1 = db.find(msgid)
        assert isinstance(msg1, message.Message)
        assert msg1.messageid == msgid == msg0.messageid
        assert msg1.path == pathname == msg0.path

    def test_find_message_notfound(self, db):
        with pytest.raises(LookupError):
            db.find('foo')

    def test_get_message(self, db, maildir):
        msgid, pathname = maildir.deliver()
        msg0, _ = db.add(pathname, sync_flags=False)
        msg1 = db.get(pathname)
        assert isinstance(msg1, message.Message)
        assert msg1.messageid == msgid == msg0.messageid
        assert msg1.path == pathname == msg0.path

    def test_get_message_str(self, db, maildir):
        msgid, pathname = maildir.deliver()
        db.add(pathname, sync_flags=False)
        msg = db.get(str(pathname))
        assert msg.messageid == msgid

    def test_get_message_bytes(self, db, maildir):
        msgid, pathname = maildir.deliver()
        db.add(pathname, sync_flags=False)
        msg = db.get(os.fsencode(bytes(pathname)))
        assert msg.messageid == msgid


class TestTags:
    # We just want to test this behaves like a set at a hight level.
    # The set semantics are tested in detail in the test_tags module.

    def test_type(self, db):
        assert isinstance(db.tags, collections.abc.Set)

    def test_none(self, db):
        itags = iter(db.tags)
        with pytest.raises(StopIteration):
            next(itags)
        assert len(db.tags) == 0
        assert not db.tags

    def test_some(self, db, maildir):
        _, pathname = maildir.deliver()
        msg, _ = db.add(pathname, sync_flags=False)
        msg.tags.add('hello')
        itags = iter(db.tags)
        assert next(itags) == 'hello'
        with pytest.raises(StopIteration):
            next(itags)
        assert 'hello' in msg.tags

    def test_cache(self, db):
        assert db.tags is db.tags

    def test_iters(self, db):
        i1 = iter(db.tags)
        i2 = iter(db.tags)
        assert i1 is not i2


class TestQuery:

    @pytest.fixture
    def db(self, maildir, notmuch):
        """Return a read-only notmuch2.Database.

        The database will have 3 messages, 2 threads.
        """
        msgid, _ = maildir.deliver(body='foo')
        maildir.deliver(body='bar')
        maildir.deliver(body='baz',
                        headers=[('In-Reply-To', '<{}>'.format(msgid))])
        notmuch('new')
        with dbmod.Database(maildir.path, 'rw') as db:
            yield db

    def test_count_messages(self, db):
        assert db.count_messages('*') == 3

    def test_messages_type(self, db):
        msgs = db.messages('*')
        assert isinstance(msgs, collections.abc.Iterator)

    def test_message_no_results(self, db):
        msgs = db.messages('not_a_matching_query')
        with pytest.raises(StopIteration):
            next(msgs)

    def test_message_match(self, db):
        msgs = db.messages('*')
        msg = next(msgs)
        assert isinstance(msg, notmuch2.Message)

    def test_count_threads(self, db):
        assert db.count_threads('*') == 2

    def test_threads_type(self, db):
        threads = db.threads('*')
        assert isinstance(threads, collections.abc.Iterator)

    def test_threads_no_match(self, db):
        threads = db.threads('not_a_matching_query')
        with pytest.raises(StopIteration):
            next(threads)

    def test_threads_match(self, db):
        threads = db.threads('*')
        thread = next(threads)
        assert isinstance(thread, notmuch2.Thread)

    def test_use_threaded_message_twice(self, db):
        thread = next(db.threads('*'))
        for msg in thread.toplevel():
            assert isinstance(msg, notmuch2.Message)
            assert msg.alive
            del msg
        for msg in thread:
            assert isinstance(msg, notmuch2.Message)
            assert msg.alive
            del msg

debug log:

solving 55069b5e ...
found 55069b5e in https://yhetil.org/notmuch.git/

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).