Skip to content

Commit

Permalink
Add trait validation testing (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
kylebarron authored Oct 10, 2023
1 parent 5a33dce commit e653254
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ jobs:
- name: Install root project
run: poetry install --no-interaction

# - name: Run tests
# run: pytest
- name: Run tests
run: poetry run pytest

- name: Cache pre-commit virtualenvs
uses: actions/cache@v3
Expand Down
7 changes: 7 additions & 0 deletions lonboard/traits.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ def validate(
info="expected all values to be integers if passed a tuple or list",
)

if any(v < 0 or v > 255 for v in value):
self.error(
obj,
value,
info="expected values between 0 and 255",
)

return value

if isinstance(value, np.ndarray):
Expand Down
50 changes: 49 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jupyterlab = "^4.0.5"
watchfiles = "^0.20.0"
pre-commit = "^3.4.0"
black = "^23.9.1"
pytest = "^7.4.2"

[tool.poetry.group.docs.dependencies]
mkdocs = "^1.4.3"
Expand Down
113 changes: 113 additions & 0 deletions tests/test_traits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import numpy as np
import pyarrow as pa
import pytest
from ipywidgets import Widget
from traitlets import TraitError

from lonboard.traits import ColorAccessor, FloatAccessor


class ColorAccessorWidget(Widget):
color = ColorAccessor()


def test_color_accessor_validation_list_length():
# tuple or list must have 3 or 4 elements
with pytest.raises(TraitError):
ColorAccessorWidget(color=())

with pytest.raises(TraitError):
ColorAccessorWidget(color=(1, 2))

with pytest.raises(TraitError):
ColorAccessorWidget(color=(1, 2, 3, 4, 5))

ColorAccessorWidget(color=(1, 2, 3))
ColorAccessorWidget(color=(1, 2, 3, 255))


def test_color_accessor_validation_list_type():
# tuple or list must have int values
with pytest.raises(TraitError):
ColorAccessorWidget(color=(1.0, 2.0, 4.0))


def test_color_accessor_validation_list_range():
# tuple or list must have values between 0-255
with pytest.raises(TraitError):
ColorAccessorWidget(color=(-1, 2, 4))

with pytest.raises(TraitError):
ColorAccessorWidget(color=(1, 2, 300))


def test_color_accessor_validation_dim_shape_np_arr():
# must be two dimensions
with pytest.raises(TraitError):
ColorAccessorWidget(color=np.array([1, 2, 3], dtype=np.uint8).reshape(-1, 3, 1))

# Second dim must be 3 or 4
with pytest.raises(TraitError):
ColorAccessorWidget(color=np.array([1, 2, 3], dtype=np.uint8).reshape(-1, 1))

with pytest.raises(TraitError):
ColorAccessorWidget(color=np.array([1, 2, 3, 4], dtype=np.uint8).reshape(-1, 2))

with pytest.raises(TraitError):
ColorAccessorWidget(
color=np.array([1, 2, 3, 4, 5], dtype=np.uint8).reshape(-1, 5)
)

ColorAccessorWidget(color=np.array([1, 2, 3], dtype=np.uint8).reshape(-1, 3))
ColorAccessorWidget(color=np.array([1, 2, 3, 255], dtype=np.uint8).reshape(-1, 4))


def test_color_accessor_validation_np_dtype():
# must be np.uint8
with pytest.raises(TraitError):
ColorAccessorWidget(color=np.array([1, 2, 3]).reshape(-1, 3))

ColorAccessorWidget(color=np.array([1, 2, 3], dtype=np.uint8).reshape(-1, 3))


def test_color_accessor_validation_pyarrow_array_type():
# array type must be FixedSizeList
with pytest.raises(TraitError):
ColorAccessorWidget(color=pa.array(np.array([1, 2, 3], dtype=np.float64)))

np_arr = np.array([1, 2, 3], dtype=np.uint8)
ColorAccessorWidget(color=pa.FixedSizeListArray.from_arrays(np_arr, 3))

np_arr = np.array([1, 2, 3, 255], dtype=np.uint8)
ColorAccessorWidget(color=pa.FixedSizeListArray.from_arrays(np_arr, 4))

# array type must have uint8 child
np_arr = np.array([1, 2, 3, 255], dtype=np.uint64)
with pytest.raises(TraitError):
ColorAccessorWidget(color=pa.FixedSizeListArray.from_arrays(np_arr, 4))


class FloatAccessorWidget(Widget):
value = FloatAccessor()


def test_float_accessor_validation_type():
# must be int or float scalar
with pytest.raises(TraitError):
FloatAccessorWidget(value=())

with pytest.raises(TraitError):
FloatAccessorWidget(value="2")

FloatAccessorWidget(value=2)
FloatAccessorWidget(value=2.0)
FloatAccessorWidget(value=np.array([2, 3, 4]))
FloatAccessorWidget(value=np.array([2, 3, 4], dtype=np.float32))
FloatAccessorWidget(value=np.array([2, 3, 4], dtype=np.float64))

# Must be floating-point array type
with pytest.raises(TraitError):
FloatAccessorWidget(value=pa.array(np.array([2, 3, 4])))

FloatAccessorWidget(value=pa.array(np.array([2, 3, 4], dtype=np.float32)))
FloatAccessorWidget(value=pa.array(np.array([2, 3, 4], dtype=np.float64)))

0 comments on commit e653254

Please sign in to comment.