Scheduled service maintenance on November 22


On Friday, November 22, 2024, between 06:00 CET and 18:00 CET, GIN services will undergo planned maintenance. Extended service interruptions should be expected. We will try to keep downtimes to a minimum, but recommend that users avoid critical tasks, large data uploads, or DOI requests during this time.

We apologize for any inconvenience.

common_io_test.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. # -*- coding: utf-8 -*-
  2. '''
  3. Common tests for IOs:
  4. * check presence of all necessary attr
  5. * check types
  6. * write/read consistency
  7. See BaseTestIO.
  8. The public URL is in url_for_tests.
  9. To deposite new testing files, please create a account at
  10. gin.g-node.org and upload files at NeuralEnsemble/ephy_testing_data
  11. data repo.
  12. '''
  13. # needed for python 3 compatibility
  14. from __future__ import absolute_import
  15. __test__ = False
  16. # url_for_tests = "https://portal.g-node.org/neo/" #This is the old place
  17. url_for_tests = "https://web.gin.g-node.org/NeuralEnsemble/ephy_testing_data/raw/master/"
  18. import os
  19. from copy import copy
  20. try:
  21. import unittest2 as unittest
  22. except ImportError:
  23. import unittest
  24. from neo.core import Block, Segment
  25. from neo.test.tools import (assert_same_sub_schema,
  26. assert_neo_object_is_compliant,
  27. assert_sub_schema_is_lazy_loaded,
  28. assert_lazy_sub_schema_can_be_loaded,
  29. assert_children_empty)
  30. from neo.rawio.tests.tools import (can_use_network, make_all_directories,
  31. download_test_file, create_local_temp_dir)
  32. from neo.test.iotest.tools import (cleanup_test_file,
  33. close_object_safe, create_generic_io_object,
  34. create_generic_reader,
  35. create_generic_writer,
  36. iter_generic_io_objects,
  37. iter_generic_readers, iter_read_objects,
  38. read_generic,
  39. write_generic)
  40. from neo.test.generate_datasets import generate_from_supported_objects
  41. class BaseTestIO(object):
  42. '''
  43. This class make common tests for all IOs.
  44. Several startegies:
  45. * for IO able to read write : test_write_then_read
  46. * for IO able to read write with hash conservation (optional):
  47. test_read_then_write
  48. * for all IOs : test_assert_readed_neo_object_is_compliant
  49. 2 cases:
  50. * files are at G-node and downloaded:
  51. download_test_files_if_not_present
  52. * files are generated by MyIO.write()
  53. '''
  54. # ~ __test__ = False
  55. # all IO test need to modify this:
  56. ioclass = None # the IOclass to be tested
  57. files_to_test = [] # list of files to test compliances
  58. files_to_download = [] # when files are at G-Node
  59. # when reading then writing produces files with identical hashes
  60. hash_conserved_when_write_read = False
  61. # when writing then reading creates an identical neo object
  62. read_and_write_is_bijective = True
  63. # allow environment to tell avoid using network
  64. use_network = can_use_network()
  65. local_test_dir = None
  66. def setUp(self):
  67. '''
  68. Set up the test fixture. This is run for every test
  69. '''
  70. self.files_to_test = copy(self.__class__.files_to_test)
  71. self.higher = self.ioclass.supported_objects[0]
  72. self.shortname = self.ioclass.__name__.lower().rstrip('io')
  73. # these objects can both be written and read
  74. self.io_readandwrite = list(set(self.ioclass.readable_objects) &
  75. set(self.ioclass.writeable_objects))
  76. # these objects can be either written or read
  77. self.io_readorwrite = list(set(self.ioclass.readable_objects) |
  78. set(self.ioclass.writeable_objects))
  79. self.create_local_dir_if_not_exists()
  80. self.download_test_files_if_not_present()
  81. self.files_generated = []
  82. self.generate_files_for_io_able_to_write()
  83. self.files_to_test.extend(self.files_generated)
  84. def create_local_dir_if_not_exists(self):
  85. '''
  86. Create a local directory to store testing files and return it.
  87. The directory path is also written to self.local_test_dir
  88. '''
  89. self.local_test_dir = create_local_temp_dir(
  90. self.shortname, directory=os.environ.get("NEO_TEST_FILE_DIR", None))
  91. return self.local_test_dir
  92. def download_test_files_if_not_present(self):
  93. '''
  94. Download %s file at G-node for testing
  95. url_for_tests is global at beginning of this file.
  96. ''' % self.ioclass.__name__
  97. if not self.use_network:
  98. raise unittest.SkipTest("Requires download of data from the web")
  99. url = url_for_tests + self.shortname
  100. try:
  101. make_all_directories(self.files_to_download, self.local_test_dir)
  102. download_test_file(self.files_to_download,
  103. self.local_test_dir, url)
  104. except IOError as exc:
  105. raise unittest.TestCase.failureException(exc)
  106. download_test_files_if_not_present.__test__ = False
  107. def cleanup_file(self, path):
  108. '''
  109. Remove test files or directories safely.
  110. '''
  111. cleanup_test_file(self.ioclass, path, directory=self.local_test_dir)
  112. def able_to_write_or_read(self, writeread=False, readwrite=False):
  113. '''
  114. Return True if generalized writing or reading is possible.
  115. If writeread=True, return True if writing then reading is
  116. possible and produces identical neo objects.
  117. If readwrite=True, return True if reading then writing is possible
  118. and produces files with identical hashes.
  119. '''
  120. # Find the highest object that is supported by the IO
  121. # Test only if it is a Block or Segment, and if it can both read
  122. # and write this object.
  123. if self.higher not in self.io_readandwrite:
  124. return False
  125. if self.higher not in [Block, Segment]:
  126. return False
  127. # when io need external knowldge for writting or read such as
  128. # sampling_rate (RawBinaryIO...) the test is too much complex to design
  129. # genericaly.
  130. if (self.higher in self.ioclass.read_params and
  131. len(self.ioclass.read_params[self.higher]) != 0):
  132. return False
  133. # handle cases where the test should write then read
  134. if writeread and not self.read_and_write_is_bijective:
  135. return False
  136. # handle cases where the test should read then write
  137. if readwrite and not self.hash_conserved_when_write_read:
  138. return False
  139. return True
  140. def get_filename_path(self, filename):
  141. '''
  142. Get the path to a filename in the current temporary file directory
  143. '''
  144. return os.path.join(self.local_test_dir, filename)
  145. def generic_io_object(self, filename=None, return_path=False, clean=False):
  146. '''
  147. Create an io object in a generic way that can work with both
  148. file-based and directory-based io objects.
  149. If filename is None, create a filename (default).
  150. If return_path is True, return the full path of the file along with
  151. the io object. return ioobj, path. Default is False.
  152. If clean is True, try to delete existing versions of the file
  153. before creating the io object. Default is False.
  154. '''
  155. return create_generic_io_object(ioclass=self.ioclass,
  156. filename=filename,
  157. directory=self.local_test_dir,
  158. return_path=return_path,
  159. clean=clean)
  160. def create_file_reader(self, filename=None, return_path=False,
  161. clean=False, target=None, readall=False):
  162. '''
  163. Create a function that can read from the specified filename.
  164. If filename is None, create a filename (default).
  165. If return_path is True, return the full path of the file along with
  166. the reader function. return reader, path. Default is False.
  167. If clean is True, try to delete existing versions of the file
  168. before creating the io object. Default is False.
  169. If target is None, use the first supported_objects from ioobj
  170. If target is False, use the 'read' method.
  171. If target is the Block or Segment class, use read_block or
  172. read_segment, respectively.
  173. If target is a string, use 'read_'+target.
  174. If readall is True, use the read_all_ method instead of the read_
  175. method. Default is False.
  176. '''
  177. ioobj, path = self.generic_io_object(filename=filename,
  178. return_path=True, clean=clean)
  179. res = create_generic_reader(ioobj, target=target, readall=readall)
  180. if return_path:
  181. return res, path
  182. return res
  183. def create_file_writer(self, filename=None, return_path=False,
  184. clean=False, target=None):
  185. '''
  186. Create a function that can write from the specified filename.
  187. If filename is None, create a filename (default).
  188. If return_path is True, return the full path of the file along with
  189. the writer function. return writer, path. Default is False.
  190. If clean is True, try to delete existing versions of the file
  191. before creating the io object. Default is False.
  192. If target is None, use the first supported_objects from ioobj
  193. If target is False, use the 'write' method.
  194. If target is the Block or Segment class, use write_block or
  195. write_segment, respectively.
  196. If target is a string, use 'write_'+target.
  197. '''
  198. ioobj, path = self.generic_io_object(filename=filename,
  199. return_path=True, clean=clean)
  200. res = create_generic_writer(ioobj, target=target)
  201. if return_path:
  202. return res, path
  203. return res
  204. def read_file(self, filename=None, return_path=False, clean=False,
  205. target=None, readall=False, lazy=False):
  206. '''
  207. Read from the specified filename.
  208. If filename is None, create a filename (default).
  209. If return_path is True, return the full path of the file along with
  210. the object. return obj, path. Default is False.
  211. If clean is True, try to delete existing versions of the file
  212. before creating the io object. Default is False.
  213. If target is None, use the first supported_objects from ioobj
  214. If target is False, use the 'read' method.
  215. If target is the Block or Segment class, use read_block or
  216. read_segment, respectively.
  217. If target is a string, use 'read_'+target.
  218. The lazy parameter is passed to the reader. Defaults is True.
  219. If readall is True, use the read_all_ method instead of the read_
  220. method. Default is False.
  221. '''
  222. ioobj, path = self.generic_io_object(filename=filename,
  223. return_path=True, clean=clean)
  224. obj = read_generic(ioobj, target=target, lazy=lazy,
  225. readall=readall, return_reader=False)
  226. if return_path:
  227. return obj, path
  228. return obj
  229. def write_file(self, obj=None, filename=None, return_path=False,
  230. clean=False, target=None):
  231. '''
  232. Write the target object to a file using the given neo io object ioobj.
  233. If filename is None, create a filename (default).
  234. If return_path is True, return the full path of the file along with
  235. the object. return obj, path. Default is False.
  236. If clean is True, try to delete existing versions of the file
  237. before creating the io object. Default is False.
  238. If target is None, use the first supported_objects from ioobj
  239. If target is False, use the 'read' method.
  240. If target is the Block or Segment class, use read_block or
  241. read_segment, respectively.
  242. If target is a string, use 'read_'+target.
  243. obj is the object to write. If obj is None, an object is created
  244. automatically for the io class.
  245. '''
  246. ioobj, path = self.generic_io_object(filename=filename,
  247. return_path=True, clean=clean)
  248. obj = write_generic(ioobj, target=target, return_reader=False)
  249. if return_path:
  250. return obj, path
  251. return obj
  252. def iter_io_objects(self, return_path=False, clean=False):
  253. '''
  254. Return an iterable over the io objects created from files_to_test
  255. If return_path is True, yield the full path of the file along with
  256. the io object. yield ioobj, path Default is False.
  257. If clean is True, try to delete existing versions of the file
  258. before creating the io object. Default is False.
  259. '''
  260. return iter_generic_io_objects(ioclass=self.ioclass,
  261. filenames=self.files_to_test,
  262. directory=self.local_test_dir,
  263. return_path=return_path,
  264. clean=clean)
  265. def iter_readers(self, target=None, readall=False,
  266. return_path=False, return_ioobj=False, clean=False):
  267. '''
  268. Return an iterable over readers created from files_to_test.
  269. If return_path is True, return the full path of the file along with
  270. the reader object. return reader, path.
  271. If return_ioobj is True, return the io object as well as the reader.
  272. return reader, ioobj. Default is False.
  273. If both return_path and return_ioobj is True,
  274. return reader, path, ioobj. Default is False.
  275. If clean is True, try to delete existing versions of the file
  276. before creating the io object. Default is False.
  277. If readall is True, use the read_all_ method instead of the
  278. read_ method. Default is False.
  279. '''
  280. return iter_generic_readers(ioclass=self.ioclass,
  281. filenames=self.files_to_test,
  282. directory=self.local_test_dir,
  283. return_path=return_path,
  284. return_ioobj=return_ioobj,
  285. target=target,
  286. clean=clean,
  287. readall=readall)
  288. def iter_objects(self, target=None, return_path=False, return_ioobj=False,
  289. return_reader=False, clean=False, readall=False,
  290. lazy=False):
  291. '''
  292. Iterate over objects read from the list of filenames in files_to_test.
  293. If target is None, use the first supported_objects from ioobj
  294. If target is False, use the 'read' method.
  295. If target is the Block or Segment class, use read_block or
  296. read_segment, respectively.
  297. If target is a string, use 'read_'+target.
  298. If return_path is True, yield the full path of the file along with
  299. the object. yield obj, path.
  300. If return_ioobj is True, yield the io object as well as the object.
  301. yield obj, ioobj. Default is False.
  302. If return_reader is True, yield the io reader function as well as the
  303. object. yield obj, reader. Default is False.
  304. If some combination of return_path, return_ioobj, and return_reader
  305. is True, they are yielded in the order: obj, path, ioobj, reader.
  306. If clean is True, try to delete existing versions of the file
  307. before creating the io object. Default is False.
  308. The lazy parameters is passed to the reader. Defaults is True.
  309. If readall is True, use the read_all_ method instead of the read_
  310. method. Default is False.
  311. '''
  312. return iter_read_objects(ioclass=self.ioclass,
  313. filenames=self.files_to_test,
  314. directory=self.local_test_dir,
  315. target=target,
  316. return_path=return_path,
  317. return_ioobj=return_ioobj,
  318. return_reader=return_reader,
  319. clean=clean, readall=readall,
  320. lazy=lazy)
  321. def generate_files_for_io_able_to_write(self):
  322. '''
  323. Write files for use in testing.
  324. '''
  325. self.files_generated = []
  326. if not self.able_to_write_or_read():
  327. return
  328. generate_from_supported_objects(self.ioclass.supported_objects)
  329. ioobj, path = self.generic_io_object(return_path=True, clean=True)
  330. if ioobj is None:
  331. return
  332. self.files_generated.append(path)
  333. write_generic(ioobj, target=self.higher)
  334. close_object_safe(ioobj)
  335. def test_write_then_read(self):
  336. '''
  337. Test for IO that are able to write and read - here %s:
  338. 1 - Generate a full schema with supported objects.
  339. 2 - Write to a file
  340. 3 - Read from the file
  341. 4 - Check the hierachy
  342. 5 - Check data
  343. Work only for IO for Block and Segment for the highest object
  344. (main cases).
  345. ''' % self.ioclass.__name__
  346. if not self.able_to_write_or_read(writeread=True):
  347. return
  348. ioobj1 = self.generic_io_object(clean=True)
  349. if ioobj1 is None:
  350. return
  351. ob1 = write_generic(ioobj1, target=self.higher)
  352. close_object_safe(ioobj1)
  353. ioobj2 = self.generic_io_object()
  354. # Read the highest supported object from the file
  355. obj_reader = create_generic_reader(ioobj2, target=False)
  356. ob2 = obj_reader()[0]
  357. if self.higher == Segment:
  358. ob2 = ob2.segments[0]
  359. # some formats (e.g. elphy) do not support double floating
  360. # point spiketrains
  361. try:
  362. assert_same_sub_schema(ob1, ob2, True, 1e-8)
  363. assert_neo_object_is_compliant(ob1)
  364. assert_neo_object_is_compliant(ob2)
  365. # intercept exceptions and add more information
  366. except BaseException as exc:
  367. raise
  368. close_object_safe(ioobj2)
  369. def test_read_then_write(self):
  370. '''
  371. Test for IO that are able to read and write, here %s:
  372. 1 - Read a file
  373. 2 Write object set in another file
  374. 3 Compare the 2 files hash
  375. NOTE: TODO: Not implemented yet
  376. ''' % self.ioclass.__name__
  377. if not self.able_to_write_or_read(readwrite=True):
  378. return
  379. # assert_file_contents_equal(a, b)
  380. def test_assert_readed_neo_object_is_compliant(self):
  381. '''
  382. Reading %s files in `files_to_test` produces compliant objects.
  383. Compliance test: neo.test.tools.assert_neo_object_is_compliant for
  384. lazy mode.
  385. ''' % self.ioclass.__name__
  386. # This is for files presents at G-Node or generated
  387. lazy_list = [False]
  388. if self.ioclass.support_lazy:
  389. lazy_list.append(True)
  390. for lazy in lazy_list:
  391. for obj, path in self.iter_objects(lazy=lazy, return_path=True):
  392. try:
  393. # Check compliance of the block
  394. assert_neo_object_is_compliant(obj)
  395. # intercept exceptions and add more information
  396. except BaseException as exc:
  397. exc.args += ('from %s with lazy=%s' %
  398. (os.path.basename(path), lazy),)
  399. raise
  400. def test_readed_with_lazy_is_compliant(self):
  401. '''
  402. Reading %s files in `files_to_test` with `lazy` is compliant.
  403. Test the reader with lazy = True. All objects derived from ndarray
  404. or Quantity should have a size of 0. Also, AnalogSignal,
  405. AnalogSignalArray, SpikeTrain, Epoch, and Event should
  406. contain the lazy_shape attribute.
  407. ''' % self.ioclass.__name__
  408. # This is for files presents at G-Node or generated
  409. if self.ioclass.support_lazy:
  410. for obj, path in self.iter_objects(lazy=True, return_path=True):
  411. try:
  412. assert_sub_schema_is_lazy_loaded(obj)
  413. # intercept exceptions and add more information
  414. except BaseException as exc:
  415. raise
  416. def test_load_lazy_objects(self):
  417. '''
  418. Reading %s files in `files_to_test` with `lazy` works.
  419. Test the reader with lazy = True. All objects derived from ndarray
  420. or Quantity should have a size of 0. Also, AnalogSignal,
  421. AnalogSignalArray, SpikeTrain, Epoch, and Event should
  422. contain the lazy_shape attribute.
  423. ''' % self.ioclass.__name__
  424. if not hasattr(self.ioclass, 'load_lazy_object'):
  425. return
  426. # This is for files presents at G-Node or generated
  427. for obj, path, ioobj in self.iter_objects(
  428. lazy=True,
  429. return_ioobj=True,
  430. return_path=True):
  431. try:
  432. assert_lazy_sub_schema_can_be_loaded(obj, ioobj)
  433. # intercept exceptions and add more information
  434. except BaseException as exc:
  435. raise