test_lmdb_store.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. import pytest
  2. from shutil import rmtree
  3. from rdflib import Namespace, URIRef
  4. from rdflib.namespace import RDF, RDFS
  5. from lakesuperior.store_layouts.ldp_rs.lmdb_store import LmdbStore, TxnManager
  6. @pytest.fixture(scope='class')
  7. def store():
  8. store = LmdbStore('/tmp/test_lmdbstore')
  9. yield store
  10. store.close()
  11. rmtree('/tmp/test_lmdbstore')
  12. def _clean(res):
  13. return {r[0] for r in res}
  14. @pytest.mark.usefixtures('store')
  15. class TestStoreInit:
  16. '''
  17. Tests for intializing and shutting down store and transactions.
  18. '''
  19. def test_open_close(self, store):
  20. '''
  21. Test opening and closing a store.
  22. '''
  23. tmpstore = LmdbStore('/tmp/test_lmdbstore_init')
  24. assert tmpstore.is_open
  25. tmpstore.close()
  26. assert not tmpstore.is_open
  27. def test_txn(self, store):
  28. '''
  29. Test opening and closing the main transaction.
  30. '''
  31. store.begin(True)
  32. assert store.is_txn_open
  33. store.commit()
  34. assert not store.is_txn_open
  35. store.begin(True)
  36. store.rollback()
  37. assert not store.is_txn_open
  38. def test_ctx_mgr(self, store):
  39. '''
  40. Test enclosing a transaction in a context.
  41. '''
  42. with TxnManager(store) as txn:
  43. pass
  44. assert not store.is_txn_open
  45. try:
  46. with TxnManager(store) as txn:
  47. raise RuntimeError
  48. except RuntimeError:
  49. assert not store.is_txn_open
  50. def test_rollback(self, store):
  51. '''
  52. Test rolling back a transaction.
  53. '''
  54. try:
  55. with TxnManager(store, True) as txn:
  56. store.add((
  57. URIRef('urn:nogo:s'), URIRef('urn:nogo:p'),
  58. URIRef('urn:nogo:o')))
  59. raise RuntimeError # This should roll back the transaction.
  60. except RuntimeError:
  61. pass
  62. with TxnManager(store) as txn:
  63. res = set(store.triples((None, None, None)))
  64. assert len(res) == 0
  65. @pytest.mark.usefixtures('store')
  66. class TestBasicOps:
  67. '''
  68. High-level tests for basic operations.
  69. '''
  70. def test_create_triple(self, store):
  71. '''
  72. Test creation of a single triple.
  73. '''
  74. trp = (
  75. URIRef('urn:test:s'), URIRef('urn:test:p'), URIRef('urn:test:o'))
  76. with TxnManager(store, True) as txn:
  77. store.add(trp)
  78. res1 = set(store.triples((None, None, None)))
  79. res2 = set(store.triples(trp))
  80. assert len(res1) == 1
  81. assert len(res2) == 1
  82. clean_res1 = _clean(res1)
  83. clean_res2 = _clean(res2)
  84. assert trp in clean_res1 & clean_res2
  85. def test_triple_match_1bound(self, store):
  86. '''
  87. Test triple patterns matching one bound term.
  88. '''
  89. with TxnManager(store) as txn:
  90. res1 = set(store.triples((URIRef('urn:test:s'), None, None)))
  91. res2 = set(store.triples((None, URIRef('urn:test:p'), None)))
  92. res3 = set(store.triples((None, None, URIRef('urn:test:o'))))
  93. assert _clean(res1) == {(
  94. URIRef('urn:test:s'), URIRef('urn:test:p'),
  95. URIRef('urn:test:o'))}
  96. assert _clean(res2) == _clean(res1)
  97. assert _clean(res3) == _clean(res2)
  98. def test_triple_match_2bound(self, store):
  99. '''
  100. Test triple patterns matching two bound terms.
  101. '''
  102. with TxnManager(store) as txn:
  103. res1 = set(store.triples(
  104. (URIRef('urn:test:s'), URIRef('urn:test:p'), None)))
  105. res2 = set(store.triples(
  106. (URIRef('urn:test:s'), None, URIRef('urn:test:o'))))
  107. res3 = set(store.triples(
  108. (None, URIRef('urn:test:p'), URIRef('urn:test:o'))))
  109. assert _clean(res1) == {(
  110. URIRef('urn:test:s'), URIRef('urn:test:p'), URIRef('urn:test:o'))}
  111. assert _clean(res2) == _clean(res1)
  112. assert _clean(res3) == _clean(res2)
  113. def test_triple_no_match(self, store):
  114. '''
  115. Test various mismatches.
  116. '''
  117. with TxnManager(store, True) as txn:
  118. store.add((
  119. URIRef('urn:test:s'),
  120. URIRef('urn:test:p2'), URIRef('urn:test:o2')))
  121. store.add((
  122. URIRef('urn:test:s3'),
  123. URIRef('urn:test:p3'), URIRef('urn:test:o3')))
  124. res1 = set(store.triples((None, None, None)))
  125. assert len(res1) == 3
  126. res1 = set(store.triples(
  127. (URIRef('urn:test:s2'), URIRef('urn:test:p'), None)))
  128. res2 = set(store.triples(
  129. (URIRef('urn:test:s3'), None, URIRef('urn:test:o'))))
  130. res3 = set(store.triples(
  131. (None, URIRef('urn:test:p3'), URIRef('urn:test:o2'))))
  132. assert len(res1) == len(res2) == len(res3) == 0
  133. def test_remove(self, store):
  134. '''
  135. Test removing one or more triples.
  136. '''
  137. with TxnManager(store, True) as txn:
  138. store.remove((URIRef('urn:test:s3'),
  139. URIRef('urn:test:p3'), URIRef('urn:test:o3')))
  140. res1 = set(store.triples((None, None, None)))
  141. assert len(res1) == 2
  142. store.remove((URIRef('urn:test:s'), None, None))
  143. res2 = set(store.triples((None, None, None)))
  144. assert len(res2) == 0
  145. @pytest.mark.usefixtures('store')
  146. class TestBindings:
  147. '''
  148. Tests for namespace bindings.
  149. '''
  150. @pytest.fixture
  151. def bindings(self):
  152. return (
  153. ('ns1', Namespace('http://test.org/ns#')),
  154. ('ns2', Namespace('http://my_org.net/ns#')),
  155. ('ns3', Namespace('urn:test:')),
  156. ('ns4', Namespace('info:myinst/graph#')),
  157. )
  158. def test_ns(self, store, bindings):
  159. '''
  160. Test namespace bindings.
  161. '''
  162. with TxnManager(store, True) as txn:
  163. for b in bindings:
  164. store.bind(*b)
  165. nslist = list(store.namespaces())
  166. assert len(nslist) == len(bindings)
  167. for i in range(len(bindings)):
  168. assert nslist[i] == bindings[i]
  169. def test_ns2pfx(self, store, bindings):
  170. '''
  171. Test namespace to prefix conversion.
  172. '''
  173. with TxnManager(store, True) as txn:
  174. for b in bindings:
  175. pfx, ns = b
  176. assert store.namespace(pfx) == ns
  177. def test_pfx2ns(self, store, bindings):
  178. '''
  179. Test namespace to prefix conversion.
  180. '''
  181. with TxnManager(store, True) as txn:
  182. for b in bindings:
  183. pfx, ns = b
  184. assert store.prefix(ns) == pfx
  185. @pytest.mark.usefixtures('store')
  186. class TestContext:
  187. '''
  188. Tests for context handling.
  189. '''
  190. def test_add_graph(self, store):
  191. '''
  192. Test creating an empty and a non-empty graph.
  193. '''
  194. gr_uri = URIRef('urn:bogus:graph#a')
  195. with TxnManager(store, True) as txn:
  196. store.add_graph(gr_uri)
  197. assert gr_uri in set(store.contexts())
  198. def test_empty_context(self, store):
  199. '''
  200. Test creating and deleting empty contexts.
  201. '''
  202. gr_uri = URIRef('urn:bogus:empty#a')
  203. with TxnManager(store, True) as txn:
  204. store.add_graph(gr_uri)
  205. assert gr_uri in store.contexts()
  206. store.remove_graph(gr_uri)
  207. assert gr_uri not in store.contexts()
  208. def test_add_trp_to_ctx(self, store):
  209. '''
  210. Test adding triples to a graph.
  211. '''
  212. gr_uri = URIRef('urn:bogus:graph#a') # From previous test
  213. gr2_uri = URIRef('urn:bogus:graph#b') # Never created before
  214. trp1 = (URIRef('urn:s:1'), URIRef('urn:p:1'), URIRef('urn:o:1'))
  215. trp2 = (URIRef('urn:s:2'), URIRef('urn:p:2'), URIRef('urn:o:2'))
  216. trp3 = (URIRef('urn:s:3'), URIRef('urn:p:3'), URIRef('urn:o:3'))
  217. with TxnManager(store, True) as txn:
  218. store.add(trp1, gr_uri)
  219. store.add(trp2, gr_uri)
  220. store.add(trp2, store.DEFAULT_GRAPH_URI)
  221. store.add(trp3, gr2_uri)
  222. store.add(trp3)
  223. assert len(set(store.triples((None, None, None)))) == 3
  224. assert len(set(store.triples((None, None, None),
  225. store.DEFAULT_GRAPH_URI))) == 2
  226. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  227. assert len(set(store.triples((None, None, None), gr2_uri))) == 1
  228. assert gr2_uri in store.contexts()
  229. assert trp1 in _clean(store.triples((None, None, None)))
  230. assert trp1 not in _clean(store.triples((None, None, None),
  231. store.DEFAULT_GRAPH_URI))
  232. assert trp2 in _clean(store.triples((None, None, None), gr_uri))
  233. assert trp2 in _clean(store.triples((None, None, None)))
  234. assert trp3 in _clean(store.triples((None, None, None), gr2_uri))
  235. assert trp3 in _clean(store.triples((None, None, None),
  236. store.DEFAULT_GRAPH_URI))
  237. def test_delete_from_ctx(self, store):
  238. '''
  239. Delete triples from a named graph and from the default graph.
  240. '''
  241. gr_uri = URIRef('urn:bogus:graph#a')
  242. gr2_uri = URIRef('urn:bogus:graph#b')
  243. with TxnManager(store, True) as txn:
  244. store.remove((None, None, None))
  245. store.remove((None, None, None), gr2_uri)
  246. assert len(set(store.triples((None, None, None)))) == 0
  247. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  248. assert len(set(store.triples((None, None, None), gr2_uri))) == 0
  249. @pytest.mark.usefixtures('store')
  250. class TestTransactions:
  251. '''
  252. Tests for transaction handling.
  253. '''
  254. # @TODO Test concurrent reads and writes.
  255. pass
  256. #@pytest.mark.usefixtures('store')
  257. #class TestRdflib:
  258. # '''
  259. # Test case adapted from
  260. # http://rdflib.readthedocs.io/en/stable/univrdfstore.html#interface-test-cases
  261. # '''
  262. #
  263. # @pytest.fixture
  264. # def sample_gr(self):
  265. # return Graph().parse('''
  266. # @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
  267. # @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  268. # @prefix : <http://test/> .
  269. # {:a :b :c; a :foo} => {:a :d :c} .
  270. # _:foo a rdfs:Class .
  271. # :a :d :c .
  272. # ''', format='n3')
  273. #
  274. # def _test_basic(self, sample_gr):
  275. # with TxnManager as txn:
  276. # implies = URIRef("http://www.w3.org/2000/10/swap/log#implies")
  277. # a = URIRef('http://test/a')
  278. # b = URIRef('http://test/b')
  279. # c = URIRef('http://test/c')
  280. # d = URIRef('http://test/d')
  281. # for s,p,o in g.triples((None,implies,None)):
  282. # formulaA = s
  283. # formulaB = o
  284. #
  285. # #contexts test
  286. # assert len(list(g.contexts()))==3
  287. #
  288. # #contexts (with triple) test
  289. # assert len(list(g.contexts((a,d,c))))==2
  290. #
  291. # #triples test cases
  292. # assert type(list(g.triples(
  293. # (None,RDF.type,RDFS.Class)))[0][0]) == BNode
  294. # assert len(list(g.triples((None,implies,None))))==1
  295. # assert len(list(g.triples((None,RDF.type,None))))==3
  296. # assert len(list(g.triples((None,RDF.type,None),formulaA)))==1
  297. # assert len(list(g.triples((None,None,None),formulaA)))==2
  298. # assert len(list(g.triples((None,None,None),formulaB)))==1
  299. # assert len(list(g.triples((None,None,None))))==5
  300. # assert len(list(g.triples(
  301. # (None,URIRef('http://test/d'),None),formulaB)))==1
  302. # assert len(list(g.triples(
  303. # (None,URIRef('http://test/d'),None))))==1
  304. #
  305. # #Remove test cases
  306. # g.remove((None,implies,None))
  307. # assert len(list(g.triples((None,implies,None))))==0
  308. # assert len(list(g.triples((None,None,None),formulaA)))==2
  309. # assert len(list(g.triples((None,None,None),formulaB)))==1
  310. # g.remove((None,b,None),formulaA)
  311. # assert len(list(g.triples((None,None,None),formulaA)))==1
  312. # g.remove((None,RDF.type,None),formulaA)
  313. # assert len(list(g.triples((None,None,None),formulaA)))==0
  314. # g.remove((None,RDF.type,RDFS.Class))
  315. #
  316. # #remove_context tests
  317. # formulaBContext=Context(g,formulaB)
  318. # g.remove_context(formulaB)
  319. # assert len(list(g.triples((None,RDF.type,None))))==2
  320. # assert len(g)==3 assert len(formulaBContext)==0
  321. # g.remove((None,None,None))
  322. # assert len(g)==0