test_lmdb_store.py 15 KB

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