test_lmdb_store.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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'),
  111. URIRef('urn:test:p'), URIRef('urn:test:o'))}
  112. assert _clean(res2) == _clean(res1)
  113. assert _clean(res3) == _clean(res2)
  114. def test_triple_no_match(self, store):
  115. '''
  116. Test various mismatches.
  117. '''
  118. with TxnManager(store, True) as txn:
  119. store.add((
  120. URIRef('urn:test:s'),
  121. URIRef('urn:test:p2'), URIRef('urn:test:o2')))
  122. store.add((
  123. URIRef('urn:test:s3'),
  124. URIRef('urn:test:p3'), URIRef('urn:test:o3')))
  125. res1 = set(store.triples((None, None, None)))
  126. assert len(res1) == 3
  127. res1 = set(store.triples(
  128. (URIRef('urn:test:s2'), URIRef('urn:test:p'), None)))
  129. res2 = set(store.triples(
  130. (URIRef('urn:test:s3'), None, URIRef('urn:test:o'))))
  131. res3 = set(store.triples(
  132. (None, URIRef('urn:test:p3'), URIRef('urn:test:o2'))))
  133. assert len(res1) == len(res2) == len(res3) == 0
  134. def test_remove(self, store):
  135. '''
  136. Test removing one or more triples.
  137. '''
  138. with TxnManager(store, True) as txn:
  139. store.remove((URIRef('urn:test:s3'),
  140. URIRef('urn:test:p3'), URIRef('urn:test:o3')))
  141. res1 = set(store.triples((None, None, None)))
  142. assert len(res1) == 2
  143. store.remove((URIRef('urn:test:s'), None, None))
  144. res2 = set(store.triples((None, None, None)))
  145. assert len(res2) == 0
  146. @pytest.mark.usefixtures('store')
  147. class TestBindings:
  148. '''
  149. Tests for namespace bindings.
  150. '''
  151. @pytest.fixture
  152. def bindings(self):
  153. return (
  154. ('ns1', Namespace('http://test.org/ns#')),
  155. ('ns2', Namespace('http://my_org.net/ns#')),
  156. ('ns3', Namespace('urn:test:')),
  157. ('ns4', Namespace('info:myinst/graph#')),
  158. )
  159. def test_ns(self, store, bindings):
  160. '''
  161. Test namespace bindings.
  162. '''
  163. with TxnManager(store, True) as txn:
  164. for b in bindings:
  165. store.bind(*b)
  166. nslist = list(store.namespaces())
  167. assert len(nslist) == len(bindings)
  168. for i in range(len(bindings)):
  169. assert nslist[i] == bindings[i]
  170. def test_ns2pfx(self, store, bindings):
  171. '''
  172. Test namespace to prefix conversion.
  173. '''
  174. with TxnManager(store, True) as txn:
  175. for b in bindings:
  176. pfx, ns = b
  177. assert store.namespace(pfx) == ns
  178. def test_pfx2ns(self, store, bindings):
  179. '''
  180. Test namespace to prefix conversion.
  181. '''
  182. with TxnManager(store, True) as txn:
  183. for b in bindings:
  184. pfx, ns = b
  185. assert store.prefix(ns) == pfx
  186. @pytest.mark.usefixtures('store')
  187. class TestContext:
  188. '''
  189. Tests for context handling.
  190. '''
  191. def test_add_graph(self, store):
  192. '''
  193. Test creating an empty and a non-empty graph.
  194. '''
  195. gr_uri = URIRef('urn:bogus:graph#a')
  196. with TxnManager(store, True) as txn:
  197. store.add_graph(gr_uri)
  198. assert gr_uri in {gr.identifier for gr in store.contexts()}
  199. def test_empty_context(self, store):
  200. '''
  201. Test creating and deleting empty contexts.
  202. '''
  203. gr_uri = URIRef('urn:bogus:empty#a')
  204. with TxnManager(store, True) as txn:
  205. store.add_graph(gr_uri)
  206. assert gr_uri in {gr.identifier for gr in store.contexts()}
  207. store.remove_graph(gr_uri)
  208. assert gr_uri not in {gr.identifier for gr in store.contexts()}
  209. def test_add_trp_to_ctx(self, store):
  210. '''
  211. Test adding triples to a graph.
  212. '''
  213. gr_uri = URIRef('urn:bogus:graph#a') # From previous test
  214. gr2_uri = URIRef('urn:bogus:graph#b') # Never created before
  215. trp1 = (URIRef('urn:s:1'), URIRef('urn:p:1'), URIRef('urn:o:1'))
  216. trp2 = (URIRef('urn:s:2'), URIRef('urn:p:2'), URIRef('urn:o:2'))
  217. trp3 = (URIRef('urn:s:3'), URIRef('urn:p:3'), URIRef('urn:o:3'))
  218. with TxnManager(store, True) as txn:
  219. store.add(trp1, gr_uri)
  220. store.add(trp2, gr_uri)
  221. store.add(trp2, store.DEFAULT_GRAPH_URI)
  222. store.add(trp3, gr2_uri)
  223. store.add(trp3)
  224. assert len(set(store.triples((None, None, None)))) == 3
  225. assert len(set(store.triples((None, None, None),
  226. store.DEFAULT_GRAPH_URI))) == 2
  227. assert len(set(store.triples((None, None, None), gr_uri))) == 2
  228. assert len(set(store.triples((None, None, None), gr2_uri))) == 1
  229. assert gr2_uri in {gr.identifier for gr in store.contexts()}
  230. assert trp1 in _clean(store.triples((None, None, None)))
  231. assert trp1 not in _clean(store.triples((None, None, None),
  232. store.DEFAULT_GRAPH_URI))
  233. assert trp2 in _clean(store.triples((None, None, None), gr_uri))
  234. assert trp2 in _clean(store.triples((None, None, None)))
  235. assert trp3 in _clean(store.triples((None, None, None), gr2_uri))
  236. assert trp3 in _clean(store.triples((None, None, None),
  237. store.DEFAULT_GRAPH_URI))
  238. #def test_delete_from_ctx(self, store):
  239. # '''
  240. # Delete triples from a named graph and from the default graph.
  241. # '''
  242. # gr_uri = URIRef('urn:bogus:graph#a')
  243. # gr2_uri = URIRef('urn:bogus:graph#b')
  244. # with TxnManager(store, True) as txn:
  245. # store.remove((None, None, None), gr2_uri)
  246. # assert len(set(store.triples((None, None, None), gr2_uri))) == 0
  247. # assert len(set(store.triples((None, None, None), gr_uri))) == 2
  248. # with TxnManager(store, True) as txn:
  249. # store.remove((None, None, None))
  250. # assert len(set(store.triples((None, None, None)))) == 0
  251. # assert len(set(store.triples((None, None, None), gr_uri))) == 0
  252. # assert len(store) == 0
  253. @pytest.mark.usefixtures('store')
  254. class TestTransactions:
  255. '''
  256. Tests for transaction handling.
  257. '''
  258. # @TODO Test concurrent reads and writes.
  259. pass
  260. #@pytest.mark.usefixtures('store')
  261. #class TestRdflib:
  262. # '''
  263. # Test case adapted from
  264. # http://rdflib.readthedocs.io/en/stable/univrdfstore.html#interface-test-cases
  265. # '''
  266. #
  267. # @pytest.fixture
  268. # def sample_gr(self):
  269. # return Graph().parse('''
  270. # @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
  271. # @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
  272. # @prefix : <http://test/> .
  273. # {:a :b :c; a :foo} => {:a :d :c} .
  274. # _:foo a rdfs:Class .
  275. # :a :d :c .
  276. # ''', format='n3')
  277. #
  278. # def _test_basic(self, sample_gr):
  279. # with TxnManager as txn:
  280. # implies = URIRef("http://www.w3.org/2000/10/swap/log#implies")
  281. # a = URIRef('http://test/a')
  282. # b = URIRef('http://test/b')
  283. # c = URIRef('http://test/c')
  284. # d = URIRef('http://test/d')
  285. # for s,p,o in g.triples((None,implies,None)):
  286. # formulaA = s
  287. # formulaB = o
  288. #
  289. # #contexts test
  290. # assert len(list(g.contexts()))==3
  291. #
  292. # #contexts (with triple) test
  293. # assert len(list(g.contexts((a,d,c))))==2
  294. #
  295. # #triples test cases
  296. # assert type(list(g.triples(
  297. # (None,RDF.type,RDFS.Class)))[0][0]) == BNode
  298. # assert len(list(g.triples((None,implies,None))))==1
  299. # assert len(list(g.triples((None,RDF.type,None))))==3
  300. # assert len(list(g.triples((None,RDF.type,None),formulaA)))==1
  301. # assert len(list(g.triples((None,None,None),formulaA)))==2
  302. # assert len(list(g.triples((None,None,None),formulaB)))==1
  303. # assert len(list(g.triples((None,None,None))))==5
  304. # assert len(list(g.triples(
  305. # (None,URIRef('http://test/d'),None),formulaB)))==1
  306. # assert len(list(g.triples(
  307. # (None,URIRef('http://test/d'),None))))==1
  308. #
  309. # #Remove test cases
  310. # g.remove((None,implies,None))
  311. # assert len(list(g.triples((None,implies,None))))==0
  312. # assert len(list(g.triples((None,None,None),formulaA)))==2
  313. # assert len(list(g.triples((None,None,None),formulaB)))==1
  314. # g.remove((None,b,None),formulaA)
  315. # assert len(list(g.triples((None,None,None),formulaA)))==1
  316. # g.remove((None,RDF.type,None),formulaA)
  317. # assert len(list(g.triples((None,None,None),formulaA)))==0
  318. # g.remove((None,RDF.type,RDFS.Class))
  319. #
  320. # #remove_context tests
  321. # formulaBContext=Context(g,formulaB)
  322. # g.remove_context(formulaB)
  323. # assert len(list(g.triples((None,RDF.type,None))))==2
  324. # assert len(g)==3 assert len(formulaBContext)==0
  325. # g.remove((None,None,None))
  326. # assert len(g)==0