test_lmdb_store.py 14 KB

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