notmuch/bindings/python-cffi/tests/test_message.py
Floris Bruynooghe 83c2d15898 Introduce CFFI-based python bindings
This introduces CFFI-based Python3-only bindings.
The bindings aim at:
- Better performance on pypy
- Easier to use Python-C interface
- More "pythonic"
  - The API should not allow invalid operations
  - Use native object protocol where possible
- Memory safety; whatever you do from python, it should not coredump.
2019-12-03 08:12:30 -04:00

226 lines
6.5 KiB
Python

import collections.abc
import time
import pathlib
import pytest
import notdb
class TestMessage:
MaildirMsg = collections.namedtuple('MaildirMsg', ['msgid', 'path'])
@pytest.fixture
def maildir_msg(self, maildir):
msgid, path = maildir.deliver()
return self.MaildirMsg(msgid, path)
@pytest.fixture
def db(self, maildir):
with notdb.Database.create(maildir.path) as db:
yield db
@pytest.fixture
def msg(self, db, maildir_msg):
msg, dup = db.add(maildir_msg.path, sync_flags=False)
yield msg
def test_type(self, msg):
assert isinstance(msg, notdb.NotmuchObject)
assert isinstance(msg, notdb.Message)
def test_alive(self, msg):
assert msg.alive
def test_hash(self, msg):
assert hash(msg)
def test_eq(self, db, msg):
copy = db.get(msg.path)
assert msg == copy
def test_messageid_type(self, msg):
assert isinstance(msg.messageid, str)
assert isinstance(msg.messageid, notdb.BinString)
assert isinstance(bytes(msg.messageid), bytes)
def test_messageid(self, msg, maildir_msg):
assert msg.messageid == maildir_msg.msgid
def test_messageid_find(self, db, msg):
copy = db.find(msg.messageid)
assert msg.messageid == copy.messageid
def test_threadid_type(self, msg):
assert isinstance(msg.threadid, str)
assert isinstance(msg.threadid, notdb.BinString)
assert isinstance(bytes(msg.threadid), bytes)
def test_path_type(self, msg):
assert isinstance(msg.path, pathlib.Path)
def test_path(self, msg, maildir_msg):
assert msg.path == maildir_msg.path
def test_pathb_type(self, msg):
assert isinstance(msg.pathb, bytes)
def test_pathb(self, msg, maildir_msg):
assert msg.path == maildir_msg.path
def test_filenames_type(self, msg):
ifn = msg.filenames()
assert isinstance(ifn, collections.abc.Iterator)
def test_filenames(self, msg):
ifn = msg.filenames()
fn = next(ifn)
assert fn == msg.path
assert isinstance(fn, pathlib.Path)
with pytest.raises(StopIteration):
next(ifn)
assert list(msg.filenames()) == [msg.path]
def test_filenamesb_type(self, msg):
ifn = msg.filenamesb()
assert isinstance(ifn, collections.abc.Iterator)
def test_filenamesb(self, msg):
ifn = msg.filenamesb()
fn = next(ifn)
assert fn == msg.pathb
assert isinstance(fn, bytes)
with pytest.raises(StopIteration):
next(ifn)
assert list(msg.filenamesb()) == [msg.pathb]
def test_ghost_no(self, msg):
assert not msg.ghost
def test_date(self, msg):
# XXX Someone seems to treat things as local time instead of
# UTC or the other way around.
now = int(time.time())
assert abs(now - msg.date) < 3600*24
def test_header(self, msg):
assert msg.header('from') == 'src@example.com'
def test_header_not_present(self, msg):
with pytest.raises(LookupError):
msg.header('foo')
def test_freeze(self, msg):
with msg.frozen():
msg.tags.add('foo')
msg.tags.add('bar')
msg.tags.discard('foo')
assert 'foo' not in msg.tags
assert 'bar' in msg.tags
def test_freeze_err(self, msg):
msg.tags.add('foo')
try:
with msg.frozen():
msg.tags.clear()
raise Exception('oops')
except Exception:
assert 'foo' in msg.tags
else:
pytest.fail('Context manager did not raise')
def test_replies_type(self, msg):
assert isinstance(msg.replies(), collections.abc.Iterator)
def test_replies(self, msg):
with pytest.raises(StopIteration):
next(msg.replies())
class TestProperties:
@pytest.fixture
def props(self, maildir):
msgid, path = maildir.deliver()
with notdb.Database.create(maildir.path) as db:
msg, dup = db.add(path, sync_flags=False)
yield msg.properties
def test_type(self, props):
assert isinstance(props, collections.abc.MutableMapping)
def test_add_single(self, props):
props['foo'] = 'bar'
assert props['foo'] == 'bar'
props.add('bar', 'baz')
assert props['bar'] == 'baz'
def test_add_dup(self, props):
props.add('foo', 'bar')
props.add('foo', 'baz')
assert props['foo'] == 'bar'
assert (set(props.getall('foo', exact=True))
== {('foo', 'bar'), ('foo', 'baz')})
def test_len(self, props):
props.add('foo', 'a')
props.add('foo', 'b')
props.add('bar', 'a')
assert len(props) == 3
assert len(props.keys()) == 2
assert len(props.values()) == 2
assert len(props.items()) == 3
def test_del(self, props):
props.add('foo', 'a')
props.add('foo', 'b')
del props['foo']
with pytest.raises(KeyError):
props['foo']
def test_remove(self, props):
props.add('foo', 'a')
props.add('foo', 'b')
props.remove('foo', 'a')
assert props['foo'] == 'b'
def test_view_abcs(self, props):
assert isinstance(props.keys(), collections.abc.KeysView)
assert isinstance(props.values(), collections.abc.ValuesView)
assert isinstance(props.items(), collections.abc.ItemsView)
def test_pop(self, props):
props.add('foo', 'a')
props.add('foo', 'b')
val = props.pop('foo')
assert val == 'a'
def test_pop_default(self, props):
with pytest.raises(KeyError):
props.pop('foo')
assert props.pop('foo', 'default') == 'default'
def test_popitem(self, props):
props.add('foo', 'a')
assert props.popitem() == ('foo', 'a')
with pytest.raises(KeyError):
props.popitem()
def test_clear(self, props):
props.add('foo', 'a')
props.clear()
assert len(props) == 0
def test_getall(self, props):
props.add('foo', 'a')
assert set(props.getall('foo')) == {('foo', 'a')}
def test_getall_prefix(self, props):
props.add('foo', 'a')
props.add('foobar', 'b')
assert set(props.getall('foo')) == {('foo', 'a'), ('foobar', 'b')}
def test_getall_exact(self, props):
props.add('foo', 'a')
props.add('foobar', 'b')
assert set(props.getall('foo', exact=True)) == {('foo', 'a')}