Skip to content

Commit

Permalink
Merge pull request #244 from enthought/enh/data-source-tests
Browse files Browse the repository at this point in the history
Improve tests for AbstractDataSource subclasses
  • Loading branch information
Jonathan Rocher committed Jan 18, 2015
2 parents be05ee7 + 99bea26 commit ec77213
Show file tree
Hide file tree
Showing 14 changed files with 708 additions and 82 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ python:
before_install:
- sudo apt-get update
- sudo apt-get install python-numpy swig
# Simlinks for PIL compilation
- sudo ln -s /usr/lib/`uname -i`-linux-gnu/libfreetype.so /usr/lib/
- sudo ln -s /usr/lib/`uname -i`-linux-gnu/libjpeg.so /usr/lib/
- sudo ln -s /usr/lib/`uname -i`-linux-gnu/libpng.so /usr/lib/
- sudo ln -s /usr/lib/`uname -i`-linux-gnu/libz.so /usr/lib/
- source .travis_before_install
install:
- pip install cython
Expand Down
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ Change summary since 4.5.0

New features/Improvements

* More comprehensive testing for AbstractDataSource subclasses. That
includes ArrayDataSource, FunctionDataSource, GridDataSource, ImageData,
MultiArrayDataSource (PR #244).

* Replaced chaco.base.bin_search by numpy.searchsorted-based routine for
5x speedup and remove use of zip in chaco.base.arg_find_runs in favour of
column_stack for 10x speedup in bad cases. (PR #263)
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
include chaco/*.h
include chaco/tests/data/PngSuite/*.png
include chaco/tests/data/PngSuite/LICENSE.txt
include chaco/tools/toolbars/images*.png
include chaco/tools/toolbars/images*.svg
include chaco/tools/toolbars/images*.txt
Expand Down
2 changes: 1 addition & 1 deletion chaco/array_data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ def _compute_bounds(self, data=None):
data_len = 0
try:
data_len = len(data)
except:
except Exception:
pass
if data_len == 0:
self._min_index = 0
Expand Down
260 changes: 228 additions & 32 deletions chaco/tests/arraydatasource_test_case.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,148 @@
"""
Test of basic dataseries behavior.
Tests of ArrayDataSource behavior.
"""

import unittest
import pickle

from numpy import arange, array, allclose, empty, isnan, nan
import unittest2 as unittest
from numpy import arange, array, allclose, empty, isnan, nan, ones
from numpy.testing import assert_array_equal
import numpy as np

from chaco.api import ArrayDataSource, PointDataSource
from traits.testing.unittest_tools import UnittestTools


class ArrayDataTestCase(unittest.TestCase):
def test_basic_set_get(self):
myarray = arange(10)
sd = ArrayDataSource(myarray)
self.assertTrue(allclose(myarray, sd._data))
self.assert_(sd.value_dimension == "scalar")
return
class ArrayDataSourceTestCase(UnittestTools, unittest.TestCase):

def setUp(self):
self.myarray = arange(10)
self.mymask = array([i % 2 for i in self.myarray], dtype=bool)
self.data_source = ArrayDataSource(self.myarray)

def test_init_defaults(self):
data_source = ArrayDataSource()
assert_array_equal(data_source._data, [])
self.assertEqual(data_source.value_dimension, "scalar")
self.assertEqual(data_source.index_dimension, "scalar")
self.assertEqual(data_source.sort_order, "none")
self.assertFalse(data_source.is_masked())
self.assertEqual(data_source.persist_data, True)

def test_basic_setup(self):
assert_array_equal(self.myarray, self.data_source._data)
self.assertEqual(self.data_source.value_dimension, "scalar")
self.assertEqual(self.data_source.sort_order, "none")
self.assertFalse(self.data_source.is_masked())

def test_set_data(self):
new_array = arange(0, 20, 2)

with self.assertTraitChanges(self.data_source, 'data_changed',
count=1):
self.data_source.set_data(new_array)

assert_array_equal(new_array, self.data_source._data)
self.assertEqual(self.data_source.get_bounds(), (0, 18))
self.assertEqual(self.data_source.sort_order, "none")

def test_set_data_ordered(self):
new_array = arange(20, 0, -2)

with self.assertTraitChanges(self.data_source, 'data_changed',
count=1):
self.data_source.set_data(new_array, sort_order='descending')

assert_array_equal(new_array, self.data_source._data)
self.assertEqual(self.data_source.get_bounds(), (2, 20))
self.assertEqual(self.data_source.sort_order, "descending")

def test_set_mask(self):
with self.assertTraitChanges(self.data_source, 'data_changed',
count=1):
self.data_source.set_mask(self.mymask)

assert_array_equal(self.myarray, self.data_source._data)
assert_array_equal(self.mymask, self.data_source._cached_mask)
self.assertTrue(self.data_source.is_masked())
self.assertEqual(self.data_source.get_bounds(), (0, 9))

def test_remove_mask(self):
self.data_source.set_mask(self.mymask)
self.assertTrue(self.data_source.is_masked())

with self.assertTraitChanges(self.data_source, 'data_changed',
count=1):
self.data_source.remove_mask()

assert_array_equal(self.myarray, self.data_source._data)
self.assertIsNone(self.data_source._cached_mask, None)
self.assertFalse(self.data_source.is_masked())
self.assertEqual(self.data_source.get_bounds(), (0, 9))

def test_get_data(self):
assert_array_equal(self.myarray, self.data_source.get_data())

def test_get_data_no_data(self):
data_source = ArrayDataSource(None)

assert_array_equal(data_source.get_data(), 0.0)

def test_get_data_mask(self):
self.data_source.set_mask(self.mymask)

data, mask = self.data_source.get_data_mask()
assert_array_equal(data, self.myarray)
assert_array_equal(mask, self.mymask)

@unittest.skip('get_data_mask() fails in this case')
def test_get_data_mask_no_data(self):
data_source = ArrayDataSource(None)

data, mask = data_source.get_data_mask()
assert_array_equal(data, 0.0)
assert_array_equal(mask, True)

def test_get_data_mask_no_mask(self):
data, mask = self.data_source.get_data_mask()
assert_array_equal(data, self.myarray)
assert_array_equal(mask, ones(shape=10, dtype=bool))

def test_bounds(self):
# ascending
myarray = arange(10)
sd = ArrayDataSource(myarray, sort_order="ascending")
bounds = sd.get_bounds()
self.assert_(bounds == (0,9))
bounds = self.data_source.get_bounds()
self.assertEqual(bounds, (0, 9))

# descending
myarray = arange(10)[::-1]
sd = ArrayDataSource(myarray, sort_order="descending")
bounds = sd.get_bounds()
self.assert_(bounds == (0,9))
data_source = ArrayDataSource(myarray, sort_order="descending")
bounds = data_source.get_bounds()
self.assertEqual(bounds, (0, 9))

# no order
myarray = array([12,3,0,9,2,18,3])
sd = ArrayDataSource(myarray, sort_order="none")
bounds = sd.get_bounds()
self.assert_(bounds == (0,18))
return
myarray = array([12, 3, 0, 9, 2, 18, 3])
data_source = ArrayDataSource(myarray, sort_order="none")
bounds = data_source.get_bounds()
self.assertEqual(bounds, (0, 18))

def test_data_size(self):
# We know that ScalarData always returns the exact length of its data
myarray = arange(913)
sd = ArrayDataSource(myarray)
self.assert_(len(myarray) == sd.get_size())
return
def test_bounds_length_one(self):
# this is special-cased in the code, so exercise the code path
data_source = ArrayDataSource(array([1.0]))
bounds = data_source.get_bounds()
self.assertEqual(bounds, (1.0, 1.0))

def test_bounds_length_zero(self):
# this is special-cased in the code, so exercise the code path
data_source = ArrayDataSource(array([]))
bounds = data_source.get_bounds()
# XXX this is sort of inconsistent with test_bounds_all_nans()
self.assertEqual(bounds, (0, 0))

def test_bounds_empty(self):
data_source = ArrayDataSource()
bounds = data_source.get_bounds()
# XXX this is sort of inconsistent with test_bounds_all_nans()
self.assertEqual(bounds, (0, 0))

def test_bounds_all_nans(self):
myarray = empty(10)
Expand All @@ -53,12 +152,108 @@ def test_bounds_all_nans(self):
self.assertTrue(isnan(bounds[0]))
self.assertTrue(isnan(bounds[1]))

def test_bounds_some_nan(self):
data_source = ArrayDataSource(array([np.nan, 3, 0, 9, np.nan, 18, 3]))
bounds = data_source.get_bounds()
self.assertEqual(bounds, (0, 18))

def test_bounds_negative_inf(self):
data_source = ArrayDataSource(array([12, 3, -np.inf, 9, 2, 18, 3]))
bounds = data_source.get_bounds()
self.assertEqual(bounds, (-np.inf, 18))

def test_bounds_positive_inf(self):
data_source = ArrayDataSource(array([12, 3, 0, 9, 2, np.inf, 3]))
bounds = data_source.get_bounds()
self.assertEqual(bounds, (0, np.inf))

def test_bounds_negative_positive_inf(self):
data_source = ArrayDataSource(array([12, 3, -np.inf, 9, 2, np.inf, 3]))
bounds = data_source.get_bounds()
self.assertEqual(bounds, (-np.inf, np.inf))

def test_bounds_non_numeric(self):
myarray = np.array([u'abc', u'foo', u'bar', u'def'], dtype=unicode)
sd = ArrayDataSource(myarray)
bounds = sd.get_bounds()
data_source = ArrayDataSource(myarray)
bounds = data_source.get_bounds()
self.assertEqual(bounds, (u'abc', u'def'))

def test_data_size(self):
# We know that ArrayDataTestCase always returns the exact length of
# its data
myarray = arange(913)
data_source = ArrayDataSource(myarray)
self.assertEqual(len(myarray), data_source.get_size())

def test_reverse_map(self):
# sort_order ascending
myarray = arange(10)
data_source = ArrayDataSource(myarray, sort_order='ascending')

self.assertEqual(data_source.reverse_map(4.0), 4)

# sort_order descending
myarray = arange(10)[::-1]
data_source = ArrayDataSource(myarray, sort_order='descending')

self.assertEqual(data_source.reverse_map(4.0), 5)

# sort_order none
myarray = array([12, 3, 0, 9, 2, 18, 3])
data_source = ArrayDataSource(myarray, sort_order='none')

with self.assertRaises(NotImplementedError):
data_source.reverse_map(3)

def test_metadata(self):
self.assertEqual(self.data_source.metadata,
{'annotations': [], 'selections': []})

def test_metadata_changed(self):
with self.assertTraitChanges(self.data_source, 'metadata_changed',
count=1):
self.data_source.metadata = {'new_metadata': True}

def test_metadata_items_changed(self):
with self.assertTraitChanges(self.data_source, 'metadata_changed',
count=1):
self.data_source.metadata['new_metadata'] = True

def test_serialization_state(self):
state = self.data_source.__getstate__()
self.assertNotIn('value_dimension', state)
self.assertNotIn('index_dimension', state)
self.assertNotIn('persist_data', state)

@unittest.skip("persist_data probably shouldn't be persisted")
def test_serialization_state_no_persist(self):
self.data_source.persist_data = False

state = self.data_source.__getstate__()
self.assertNotIn('value_dimension', state)
self.assertNotIn('index_dimension', state)
self.assertNotIn('persist_data', state)
for key in ["_data", "_cached_mask", "_cached_bounds", "_min_index",
"_max_index"]:
self.assertIn(key, state)

@unittest.skip("I think this is just broken")
def test_serialization_post_load(self):
self.data_source.set_mask(self.mymask)

pickled_data_source = pickle.dumps(self.data_source)
unpickled_data_source = pickle.loads(pickled_data_source)
unpickled_data_source._post_load()

self.assertEqual(unpickled_data_source._cached_bounds, ())
self.assertEqual(unpickled_data_source._cached_mask, None)

assert_array_equal(self.data_source.get_data(),
unpickled_data_source.get_data())

mask = unpickled_data_source.get_data_mask()[1]
assert_array_equal(mask, ones(10))


class PointDataTestCase(unittest.TestCase):
# Since PointData is mostly the same as ScalarData, the key things to
Expand All @@ -69,16 +264,17 @@ def create_array(self):
def test_basic_set_get(self):
myarray = self.create_array()
pd = PointDataSource(myarray)
self.assertTrue(allclose(myarray,pd._data))
self.assertTrue(allclose(myarray, pd._data))
self.assert_(pd.value_dimension == "point")
return

def test_bounds(self):
myarray = self.create_array()
pd = PointDataSource(myarray)
self.assertEqual(pd.get_bounds(),((0,0), (9,90)))
self.assertEqual(pd.get_bounds(), ((0, 0), (9, 90)))
return


if __name__ == '__main__':
import nose
nose.run()
8 changes: 8 additions & 0 deletions chaco/tests/data/PngSuite/LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
PngSuite
--------

Permission to use, copy, modify and distribute these images for any
purpose and without fee is hereby granted.


(c) Willem van Schaik, 1996, 2011
Binary file added chaco/tests/data/PngSuite/basi6a08.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added chaco/tests/data/PngSuite/basn2c08.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit ec77213

Please sign in to comment.