Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/run-tests-action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ jobs:
sudo apt-get install qtbase5-dev
- name: Test with xvfb
run: |
xvfb-run --auto-servernum pytest -n 2 --cov-report= --cov=Stoner --junitxml pytest.xml
xvfb-run --auto-servernum pytest -n 2 --cov-report= --cov=Stoner --junitxml pytest.xml || PYTEST_EXIT=$?
coverage xml
exit ${PYTEST_EXIT:-0}
env:
TZ: Europe/London
LC_CTYPE: en_GB.UTF-8
Expand Down
3 changes: 2 additions & 1 deletion Stoner/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,8 @@ def __getitem__(self, name: Union[str, RegExp]) -> Any:
key = name
(name, typehint) = self._get_name_(name)
name = self.__lookup__(name, True)
value = [super().__getitem__(nm) for nm in name]
_super = super()
value = [_super.__getitem__(nm) for nm in name]
if typehint is not None:
value = [self.__mungevalue(typehint, v) for v in value]
if len(value) == 0: # pylint: disable=len-as-condition
Expand Down
42 changes: 41 additions & 1 deletion tests/Stoner/core/test_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,53 @@
"""Test Stoner.core.exceptions module"""
import pytest

from Stoner.core.exceptions import assertion
from Stoner.core.exceptions import (
StonerAssertionError,
StonerLoadError,
StonerSetasError,
StonerUnrecognisedFormat,
assertion,
)


def test_assertion():
with pytest.raises(RuntimeError):
assertion(False, "Triggered an assertion")


def test_assertion_true_does_not_raise():
# Calling assertion with a truthy condition should not raise
assertion(True, "Should not raise")


def test_StonerLoadError_is_exception():
err = StonerLoadError("test load error")
assert isinstance(err, Exception), "StonerLoadError should be an Exception"
with pytest.raises(StonerLoadError):
raise StonerLoadError("could not load file")


def test_StonerUnrecognisedFormat_is_IOError():
err = StonerUnrecognisedFormat("unknown format")
assert isinstance(err, IOError), "StonerUnrecognisedFormat should be an IOError"
with pytest.raises(StonerUnrecognisedFormat):
raise StonerUnrecognisedFormat("no loader found")


def test_StonerSetasError_is_AttributeError():
err = StonerSetasError("setas not set")
assert isinstance(err, AttributeError), "StonerSetasError should be an AttributeError"
with pytest.raises(StonerSetasError):
raise StonerSetasError("column not accessible")


def test_StonerAssertionError_is_RuntimeError():
err = StonerAssertionError("assertion failed")
assert isinstance(err, RuntimeError), "StonerAssertionError should be a RuntimeError"
with pytest.raises(StonerAssertionError):
assertion(False)


if __name__ == "__main__":
pytest.main(["--pdb", __file__])

59 changes: 59 additions & 0 deletions tests/Stoner/core/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
"""Tests for Stoner.core.utils"""

import csv

import pytest

from Stoner.core.utils import Tab_Delimited, decode_string


def test_decode_string_simple():
assert decode_string("xxyy") == "xxyy", "decode_string with no patterns should return unchanged"


def test_decode_string_repeated_x():
assert decode_string("3x") == "xxx", "decode_string failed to expand 3x"


def test_decode_string_repeated_y():
assert decode_string("2y") == "yy", "decode_string failed to expand 2y"


def test_decode_string_mixed():
result = decode_string("x2yz")
assert result == "xyyz", "decode_string failed for mixed pattern x2yz"


def test_decode_string_dots_and_dashes():
assert decode_string("3.") == "...", "decode_string failed to expand 3."
assert decode_string("2-") == "--", "decode_string failed to expand 2-"


def test_decode_string_multiple_patterns():
result = decode_string("2x3y")
assert result == "xxyyy", "decode_string failed for multiple patterns 2x3y"


def test_Tab_Delimited_is_csv_dialect():
assert issubclass(Tab_Delimited, csv.Dialect), "Tab_Delimited should subclass csv.Dialect"
assert Tab_Delimited.delimiter == "\t", "Tab_Delimited delimiter should be a tab"
assert Tab_Delimited.quoting == csv.QUOTE_NONE, "Tab_Delimited quoting should be QUOTE_NONE"
assert Tab_Delimited.doublequote is False, "Tab_Delimited doublequote should be False"
assert Tab_Delimited.lineterminator == "\r\n", "Tab_Delimited lineterminator should be CRLF"


def test_Tab_Delimited_roundtrip():
import io

output = io.StringIO()
writer = csv.writer(output, dialect=Tab_Delimited)
writer.writerow(["a", "b", "c"])
output.seek(0)
reader = csv.reader(output, dialect=Tab_Delimited)
row = next(reader)
assert row == ["a", "b", "c"], "Tab_Delimited roundtrip failed"


if __name__ == "__main__":
pytest.main(["--pdb", __file__])
69 changes: 68 additions & 1 deletion tests/Stoner/tools/test_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from Stoner import Options
from Stoner import Data, Options
from Stoner.tools import classes


Expand Down Expand Up @@ -146,5 +146,72 @@ def test_Options():
assert repr(Options) == opt_repr, "Representation of Options failed"


def test_AttributeStore():
store = classes.AttributeStore({"x": 1, "y": 2})
assert store.x == 1, "AttributeStore getattr failed"
assert store["y"] == 2, "AttributeStore getitem failed"
store.z = 3
assert store["z"] == 3, "AttributeStore setattr failed"
try:
_ = store.missing
except AttributeError:
pass
else:
assert False, "AttributeStore getattr for missing key didn't raise AttributeError"


def test_AttributeStore_init_no_dict():
store = classes.AttributeStore()
store["key"] = "value"
assert store.key == "value", "AttributeStore set via dict key and get via attr failed"


def test_TypedList_missing_methods():
tl = classes.TypedList(int, (1, 2, 3))
# append
tl.append(4)
assert tl == [1, 2, 3, 4], "TypedList append failed"
try:
tl.append("bad")
except TypeError:
pass
else:
assert False, "TypedList append with bad type didn't raise TypeError"
# __len__
assert len(tl) == 4, "TypedList __len__ failed"
# __iter__
items = list(tl)
assert items == [1, 2, 3, 4], "TypedList __iter__ failed"
# count
tl.append(1)
assert tl.count(1) == 2, "TypedList count failed"
# remove
tl.remove(1)
assert tl.count(1) == 1, "TypedList remove failed"
# pop
val = tl.pop()
assert val == 1, "TypedList pop failed"
# reverse
tl2 = classes.TypedList(int, (1, 2, 3))
tl2.reverse()
assert tl2 == [3, 2, 1], "TypedList reverse failed"
# clear
tl2.clear()
assert len(tl2) == 0, "TypedList clear failed"


def test_copy_into():
import numpy as np

src = Data(np.column_stack((np.arange(5), np.arange(5) * 2.0)), column_headers=["x", "y"])
src.setas = "xy"
dest = Data()
result = classes.copy_into(src, dest)
assert result is dest, "copy_into should return the destination object"
assert list(dest.column_headers) == ["x", "y"], "copy_into failed to copy column headers"
assert len(dest) == 5, "copy_into failed to copy data rows"


if __name__ == "__main__":
pytest.main(["--pdb", __file__])

65 changes: 65 additions & 0 deletions tests/Stoner/tools/test_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
"""Tests for Stoner.tools.file (test_is_zip function)."""

import os
import tempfile
import zipfile

import pytest

from Stoner.tools.file import test_is_zip as is_zip_file


def test_is_zip_with_empty_string():
assert is_zip_file("") is False, "test_is_zip should return False for empty string"


def test_is_zip_with_none():
assert is_zip_file(None) is False, "test_is_zip should return False for None"


def test_is_zip_with_bytes_containing_null():
assert is_zip_file(b"data\x00more") is False, "test_is_zip should return False for bytes with null"


def test_is_zip_with_real_zip():
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
tmp_name = tmp.name
try:
with zipfile.ZipFile(tmp_name, "w") as zf:
zf.writestr("hello.txt", "Hello, world!")
result = is_zip_file(tmp_name)
assert result is not False, "test_is_zip should detect a real zip file"
assert result[0] == tmp_name, "test_is_zip should return the zip filename"
assert result[1] == "", "test_is_zip should return empty member for direct zip"
finally:
os.unlink(tmp_name)


def test_is_zip_with_non_zip_file():
with tempfile.NamedTemporaryFile(suffix=".txt", delete=False, mode="w") as tmp:
tmp.write("Not a zip file")
tmp_name = tmp.name
try:
result = is_zip_file(tmp_name)
assert result is False, "test_is_zip should return False for non-zip file"
finally:
os.unlink(tmp_name)


def test_is_zip_with_path_inside_zip():
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
tmp_name = tmp.name
try:
with zipfile.ZipFile(tmp_name, "w") as zf:
zf.writestr("subdir/data.txt", "content")
# Test with a path that includes the zip file + member path
result = is_zip_file(os.path.join(tmp_name, "subdir", "data.txt"))
assert result is not False, "test_is_zip should find zip when path goes through a zip"
assert result[0] == tmp_name, "test_is_zip should find the zip file path"
finally:
os.unlink(tmp_name)


if __name__ == "__main__":
pytest.main(["--pdb", __file__])
54 changes: 54 additions & 0 deletions tests/Stoner/tools/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,62 @@ def test_ordinal():
else:
assert False, "ordinal didn't raise a ValueError for non integer value"
assert formatting.ordinal(11).endswith("th"), "Failed special handling for 11th in ordinal"
assert formatting.ordinal(12).endswith("th"), "Failed special handling for 12th in ordinal"
assert formatting.ordinal(13).endswith("th"), "Failed special handling for 13th in ordinal"
assert formatting.ordinal(21).endswith("st"), "Failed to add st to 21st in ordinal"
assert formatting.ordinal(1).endswith("st"), "Failed to add st to 1st in ordinal"
assert formatting.ordinal(2).endswith("nd"), "Failed to add nd to 2nd in ordinal"
assert formatting.ordinal(3).endswith("rd"), "Failed to add rd to 3rd in ordinal"
assert formatting.ordinal(4).endswith("th"), "Failed to add th to 4th in ordinal"
assert formatting.ordinal(0).endswith("th"), "Failed to add th to 0th in ordinal"


def test_quantize():
assert formatting.quantize(1.23, 0.1) == pytest.approx(1.2), "quantize(1.23, 0.1) failed"
assert formatting.quantize(1.26, 0.1) == pytest.approx(1.3), "quantize(1.26, 0.1) failed"
assert formatting.quantize(7, 2) == pytest.approx(8), "quantize(7, 2) failed"
assert formatting.quantize(6, 2) == pytest.approx(6), "quantize(6, 2) failed"


def test_tex_escape():
assert formatting.tex_escape("&") == r"\&", "tex_escape & failed"
assert formatting.tex_escape("%") == r"\%", "tex_escape % failed"
assert formatting.tex_escape("$") == r"\$", "tex_escape $ failed"
assert formatting.tex_escape("#") == r"\#", "tex_escape # failed"
assert formatting.tex_escape("_") == r"\_", "tex_escape _ failed"
assert formatting.tex_escape("{") == r"\{", "tex_escape { failed"
assert formatting.tex_escape("}") == r"\}", "tex_escape } failed"
assert formatting.tex_escape("~") == r"\textasciitilde{}", "tex_escape ~ failed"
assert formatting.tex_escape("^") == r"\^{}", "tex_escape ^ failed"
assert formatting.tex_escape("\\") == r"\textbackslash{}", "tex_escape \\ failed"
assert formatting.tex_escape("<") == r"\textless", "tex_escape < failed"
assert formatting.tex_escape(">") == r"\textgreater", "tex_escape > failed"
assert formatting.tex_escape("hello") == "hello", "tex_escape plain text should be unchanged"


def test_format_val_modes():
value = 1.2345e-6
# eng mode text
result = formatting.format_val(value, fmt="text", mode="eng")
assert "u" in result or "1" in result, "format_val eng text mode failed"
# sci mode html
result = formatting.format_val(value, fmt="html", mode="sci")
assert "10" in result, "format_val sci html mode failed"
# sci mode latex
result = formatting.format_val(value, fmt="latex", mode="sci")
assert r"\times" in result, "format_val sci latex mode failed"
# float mode (default)
result = formatting.format_val(value, fmt="text", mode="float")
assert "1.2345" in result, "format_val float text mode failed"
# bad mode raises RuntimeError
try:
formatting.format_val(value, fmt="text", mode="bad")
except RuntimeError:
pass
else:
assert False, "format_val bad mode didn't raise RuntimeError"


if __name__ == "__main__":
pytest.main(["--pdb", __file__])

Loading
Loading