Skip to content

Commit

Permalink
Merge pull request #13 from upb-lea/unit_tests
Browse files Browse the repository at this point in the history
Add example unit test
  • Loading branch information
gituser789 authored Dec 2, 2024
2 parents 2f72e05 + f71ef68 commit 28a55db
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 16 deletions.
14 changes: 13 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,22 @@ jobs:
ruff check $(git ls-files '*.py')
pip install pylint
pylint $(git ls-files '*.py')
pylint $(git ls-files '*.py')
- name: check linting using pycodestyle
run: |
# try to install tkinter
sudo apt install python3-tk
- name: install pySignalScope package
run: |
pip install -e .
# Runs a set of commands using the runners shell
- name: Run pytests
run: |
pip install pytest
echo Start testing...
pytest tests
echo tests finished.
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = ["numpy",
"matplotlib"]
"matplotlib",
"lecroyutils"]

[project.urls]
Homepage = "https://github.com/upb-lea/pySignalScope"
Expand All @@ -42,7 +43,7 @@ target-version = "py39"
select = ["E4", "E7", "E9", "F", "B", "D", "D417"]
# extend-select = ["D417"] deactivated by default in case of pep257 codestyle.
# see also: https://docs.astral.sh/ruff/rules/undocumented-param/
ignore = ["B008", "D107", "D203", "D212", "D213", "D402", "D413", "D415", "D416", "E722", "E731", "F403", "F405", "F841",]
ignore = ["B008", "D107", "D203", "D212", "D213", "D402", "D413", "D415", "D416", "E722", "E731", "F403", "F405", "F841", "E402"]
fixable = ["ALL"]
unfixable = []
# ignore list in docstring according to numpy codestyles for Dxxx.
Expand Down
7 changes: 6 additions & 1 deletion pysignalscope/channelshift.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Class supports shifting channels within displayed plot."""
from enum import Enum
from typing import List, Tuple
import os
# 3rd party libraries
from matplotlib import pyplot as plt
# own libraries
Expand All @@ -14,7 +15,11 @@
from matplotlib.widgets import Button, TextBox
from matplotlib.widgets import Slider

matplotlib.use('TkAgg')
# in case of GitHubs CI, use Agg instead of TkAgg (normal usage)
if "IS_TEST" in os.environ:
matplotlib.use('Agg')
else:
matplotlib.use('TkAgg')

# - Logging setup ---------------------------------------------------------------------------------
setup_logging()
Expand Down
24 changes: 16 additions & 8 deletions pysignalscope/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ def generate_scope_object(channel_time: Union[List[float], np.ndarray], channel_
channel_data = channel_data
else:
raise TypeError("channel_data must be type list or ArrayLike")
if np.any(np.isnan(channel_data)):
raise ValueError("NaN is not allowed in channel_data.")
if np.any(np.isinf(channel_data)):
raise ValueError("inf is not allowed in channel_data.")
# check if channel_time and channel_data have the same length
if len(channel_time) != len(channel_data):
raise ValueError("channel_time and channel_data must be same lenght.")
# check if channel_time is strictly increasing
if not np.all(np.diff(channel_time) > 0):
raise ValueError("channel time not strictly increasing.")

# check channel_label for a valid type
if isinstance(channel_label, str) or channel_label is None:
channel_label = channel_label
Expand Down Expand Up @@ -504,7 +515,7 @@ def from_lecroy_remote(channel_number: int, ip_address: str, channel_label: str)

@staticmethod
def from_numpy(period_vector_t_i: np.ndarray, mode: str = 'rad', f0: Union[float, None] = None,
channel_label=None, channel_unit=None) -> 'Scope':
channel_label: Optional[str] = None, channel_unit: Optional[str] = None) -> 'Scope':
"""
Bring a numpy or list array to an instance of Channel.
Expand Down Expand Up @@ -534,9 +545,6 @@ def from_numpy(period_vector_t_i: np.ndarray, mode: str = 'rad', f0: Union[float
# check for correct input parameter
if (mode == 'rad' or mode == 'deg') and f0 is None:
raise ValueError("if mode is 'rad' or 'deg', a fundamental frequency f0 must be set")
# check for input is list. Convert to numpy-array
if isinstance(period_vector_t_i, List):
period_vector_t_i = np.array(period_vector_t_i)

# mode pre-calculation
if mode == 'rad' and f0 is not None:
Expand All @@ -546,12 +554,12 @@ def from_numpy(period_vector_t_i: np.ndarray, mode: str = 'rad', f0: Union[float
elif mode != 'time':
raise ValueError("Mode not available. Choose: 'rad', 'deg', 'time'")

single_dataset_channel = Scope(period_vector_t_i[0], period_vector_t_i[1],
channel_label=channel_label, channel_unit=channel_unit, channel_color=None,
channel_linestyle=None, modulename=class_modulename, channel_source=None)
single_dataset_channel = HandleScope.generate_scope_object(period_vector_t_i[0], period_vector_t_i[1],
channel_label=channel_label, channel_unit=channel_unit, channel_color=None,
channel_linestyle=None, channel_source=None)

# Log flow control
logging.debug(f"{class_modulename} :FlCtl Amount of Data: {len(single_dataset_channel)}")
logging.debug(f"{class_modulename} :FlCtl Amount of Data: 1")

return single_dataset_channel

Expand Down
3 changes: 0 additions & 3 deletions requirements.txt

This file was deleted.

168 changes: 168 additions & 0 deletions tests/test_scope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"""Unit tests for the scope module."""

# python libraries
import pytest
import os

# 3rd party libraries
import numpy as np
import numpy.testing

os.environ["IS_TEST"] = "True"

# own libraries
import pysignalscope as pss

def test_generate_scope_object():
"""Test generate_scope_object() method."""
# no input or missing input channel_time and channel_data - raise type error
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object()
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3])
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_data=[1, 2, 3])
# different length of time and data must raise value error
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, 2])
# invalid time data positive values
with pytest.raises(ValueError):
scope_object = pss.HandleScope.generate_scope_object(channel_time=[3, 2, 1], channel_data=[1, 2, 3])
# invalid time data negative values. Time values in wrong order.
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[-1, -2, -3], channel_data=[1, 2, 3])
# invalid time data negative and positive values. Time values in wrong order.
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[-1, -3, 1], channel_data=[1, 2, 3])

# channel_time: non-equidistant values and negative valid values
scope_object = pss.HandleScope.generate_scope_object(channel_time=[-3.3, -2.2, -1.1, 0, 1.2], channel_data=[-1, -2.1, -3.2, 4.4, -2.7])
numpy.testing.assert_equal(scope_object.channel_time, [-3.3, -2.2, -1.1, 0, 1.2])
numpy.testing.assert_equal(scope_object.channel_data, [-1, -2.1, -3.2, 4.4, -2.7])

# channel_time: same x-data, should fail.
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3, 3, 4, 5], channel_data=[-1, -2.1, -3.2, 4.4])

# valid positive data, mixed int and float
scope_object = pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, 2, 3.1])
numpy.testing.assert_equal(scope_object.channel_time, [1, 2, 3])
numpy.testing.assert_equal(scope_object.channel_data, [1, 2, 3.1])

# valid negative data, mixed int and float
scope_object = pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[-1, -2.1, -3.2])
numpy.testing.assert_equal(scope_object.channel_time, [1, 2, 3])
numpy.testing.assert_equal(scope_object.channel_data, [-1, -2.1, -3.2])

# valid mixed positive and negative data, mixed int and float
scope_object = pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2])
numpy.testing.assert_equal(scope_object.channel_time, [1, 2, 3])
numpy.testing.assert_equal(scope_object.channel_data, [1, -2.1, -3.2])

# very high, very low and very small mixed values
scope_object = pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1e25, -3.4e34, 3.1e-17])
numpy.testing.assert_equal(scope_object.channel_time, [1, 2, 3])
numpy.testing.assert_equal(scope_object.channel_data, [1e25, -3.4e34, 3.1e-17])

# invalid time value
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[np.nan, 2, 3], channel_data=[0, 2, -3.2])

# invalid data value
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[-np.nan, 2, -3.2])
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[-np.inf, 2, -3.2])
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[np.inf, 2, -3.2])

# check None inputs
scope_object = pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2])
assert scope_object.channel_label is None
assert scope_object.channel_unit is None
assert scope_object.channel_color is None
assert scope_object.channel_source is None
assert scope_object.channel_linestyle is None

# check inputs
scope_object = pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2],
channel_label="test 1", channel_unit="A", channel_color="red",
channel_source="scope 11", channel_linestyle="--")
assert scope_object.channel_label == "test 1"
assert scope_object.channel_unit == "A"
assert scope_object.channel_color == "red"
assert scope_object.channel_source == "scope 11"
assert scope_object.channel_linestyle == "--"

# wrong type inputs
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2], channel_label=100.1)
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2], channel_unit=100.1)
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2], channel_color=100.1)
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2], channel_source=100.1)
with pytest.raises(TypeError):
pss.HandleScope.generate_scope_object(channel_time=[1, 2, 3], channel_data=[1, -2.1, -3.2], channel_linestyle=100.1)

def test_from_numpy():
"""Test for the method from_numpy()."""
time = [0, 1, 2 * np.pi]
data = [2, 3, 2]
frequency = 20000

period_vector_t_i = np.array([time, data])

# mode time
scope_object = pss.HandleScope.from_numpy(period_vector_t_i, mode="time")
np.testing.assert_array_equal(scope_object.channel_time, time)
np.testing.assert_array_equal(scope_object.channel_data, data)

# no mode input, should behave same as radiant mode
scope_object = pss.HandleScope.from_numpy(period_vector_t_i, f0=frequency)
np.testing.assert_array_almost_equal(scope_object.channel_time, np.array(time) / 2 / np.pi / frequency)
np.testing.assert_array_equal(scope_object.channel_data, data)

# mode radiant
scope_object = pss.HandleScope.from_numpy(period_vector_t_i, mode="rad", f0=frequency)
np.testing.assert_array_almost_equal(scope_object.channel_time, np.array(time) / 2 / np.pi / frequency)
np.testing.assert_array_equal(scope_object.channel_data, data)

# mode degree
scope_object = pss.HandleScope.from_numpy(period_vector_t_i, mode="deg", f0=frequency)
np.testing.assert_array_almost_equal(scope_object.channel_time, np.array(time) / 360 / frequency)
np.testing.assert_array_equal(scope_object.channel_data, data)

# Check for non set labels
assert scope_object.channel_label is None
assert scope_object.channel_unit is None

# wrong input value for mode
with pytest.raises(ValueError):
pss.HandleScope.from_numpy(period_vector_t_i, "wrong_mode")

with pytest.raises(ValueError):
pss.HandleScope.from_numpy(period_vector_t_i, 100)

# wrong input data to see if generate_scope_object() is used
with pytest.raises(ValueError):
pss.HandleScope.generate_scope_object(channel_time=[-3.14, -4, 5.0], channel_data=[1, -2.1, -3.2], channel_linestyle=100.1)

# set label
label = "trail_label"
scope_object = pss.HandleScope.from_numpy(period_vector_t_i, mode="rad", f0=frequency, channel_label=label)
assert scope_object.channel_label == label

# set wrong label type
with pytest.raises(TypeError):
pss.HandleScope.from_numpy(period_vector_t_i, mode="rad", f0=frequency, channel_label=100)

# set unit
unit = "trial_unit"
scope_object = pss.HandleScope.from_numpy(period_vector_t_i, mode="rad", f0=frequency, channel_unit=unit)
assert scope_object.channel_unit == unit

# set wrong unit type
with pytest.raises(TypeError):
pss.HandleScope.from_numpy(period_vector_t_i, mode="rad", f0=frequency, channel_unit=100)
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[pycodestyle]
count = False
ignore = E226,E302,E71, W293, W291, E731, E502, E722, W605
ignore = E226,E302,E71, W293, W291, E731, E502, E722, W605, E402
max-line-length = 160
statistics = True

Expand Down

0 comments on commit 28a55db

Please sign in to comment.