diff --git a/.github/workflows/build-pipeline.yml b/.github/workflows/build-pipeline.yml index 27cd1c6..33e0c61 100644 --- a/.github/workflows/build-pipeline.yml +++ b/.github/workflows/build-pipeline.yml @@ -8,7 +8,7 @@ on: - main jobs: build: - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os }} env: PYTHONIOENCODING: "utf8" strategy: @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip @@ -29,6 +29,8 @@ jobs: - name: Run Linting run: | ruff check . + - name: Set PYTHONPATH + run: echo "PYTHONPATH=$(pwd)" >> $GITHUB_ENV - name: Run Tests run: | pytest tests/ diff --git a/pyproject.toml b/pyproject.toml index b587b28..ac40100 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,7 @@ Icon = "" [tool.pytest.ini_options] minversion = "8.0" +pythonpath = "src" testpaths = [ "tests", ] diff --git a/src/basic_data_handling/dict_nodes.py b/src/basic_data_handling/dict_nodes.py index 8ddf05e..0ef23bb 100644 --- a/src/basic_data_handling/dict_nodes.py +++ b/src/basic_data_handling/dict_nodes.py @@ -67,7 +67,7 @@ def INPUT_TYPES(cls): "key": ("STRING", {"default": ""}), }, "optional": { - "default_value": ("*", {}), + "default": ("*", {}), } } @@ -77,8 +77,8 @@ def INPUT_TYPES(cls): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get" - def get(self, input_dict: dict, key: str, default_value=None) -> tuple[Any]: - return (input_dict.get(key, default_value),) + def get(self, input_dict: dict, key: str, default=None) -> tuple[Any]: + return (input_dict.get(key, default),) class DictSet: @@ -297,28 +297,24 @@ def INPUT_TYPES(cls): }, "optional": { "default_value": ("*", {}), - "has_default": (["False", "True"], {"default": "False"}), } } - RETURN_TYPES = ("DICT", "*", "BOOLEAN") - RETURN_NAMES = ("dict", "value", "key_found") + RETURN_TYPES = ("DICT", "*") + RETURN_NAMES = ("dict", "value") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "pop" - def pop(self, input_dict: dict, key: str, default_value=None, has_default: str = "False") -> tuple[dict, Any, bool]: + def pop(self, input_dict: dict, key: str, default_value=None) -> tuple[dict, Any]: result = input_dict.copy() - has_default_bool = (has_default == "True") try: if key in result: value = result.pop(key) - return result, value, True - elif has_default_bool: - return result, default_value, False + return result, value else: - raise KeyError(f"Key '{key}' not found in dictionary") + return result, default_value except Exception as e: raise ValueError(f"Error popping key from dictionary: {str(e)}") @@ -376,7 +372,7 @@ def INPUT_TYPES(cls): } RETURN_TYPES = ("DICT", "*") - RETURN_NAMES = ("dict", "value") + RETURN_NAMES = ("DICT", "value") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "setdefault" @@ -605,7 +601,7 @@ def INPUT_TYPES(cls): "keys": ("LIST", {}), }, "optional": { - "default_value": ("*", {}), + "default": ("*", {}), } } @@ -615,8 +611,8 @@ def INPUT_TYPES(cls): DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "get_multiple" - def get_multiple(self, input_dict: dict, keys: list, default_value=None) -> tuple[list]: - values = [input_dict.get(key, default_value) for key in keys] + def get_multiple(self, input_dict: dict, keys: list, default=None) -> tuple[list]: + values = [input_dict.get(key, default) for key in keys] return (values,) @@ -736,40 +732,39 @@ def INPUT_TYPES(cls): } RETURN_TYPES = ("DICT", "BOOLEAN") - RETURN_NAMES = ("dict", "success") CATEGORY = "Basic/DICT" DESCRIPTION = cleandoc(__doc__ or "") FUNCTION = "convert" - def convert(self, input: Any) -> tuple[dict, bool]: + def convert(self, input: Any) -> tuple[dict]: try: if isinstance(input, dict): - return input.copy(), True + return (input.copy(),) # Try converting from items if hasattr(input, "items"): - return dict(input.items()), True + return (dict(input.items()),) # Try converting from sequence of pairs if hasattr(input, "__iter__") and not isinstance(input, str): try: result = dict(input) - return result, True + return (result,) except (TypeError, ValueError): pass # Check for to_dict or as_dict methods if hasattr(input, "to_dict") and callable(getattr(input, "to_dict")): - return input.to_dict(), True + return (input.to_dict(),) if hasattr(input, "as_dict") and callable(getattr(input, "as_dict")): - return input.as_dict(), True + return (input.as_dict(),) # Failed to convert - return {}, False + return ({},) except Exception: - return {}, False + return ({},) NODE_CLASS_MAPPINGS = { diff --git a/src/basic_data_handling/list_nodes.py b/src/basic_data_handling/list_nodes.py index c186227..99cd34f 100644 --- a/src/basic_data_handling/list_nodes.py +++ b/src/basic_data_handling/list_nodes.py @@ -347,9 +347,6 @@ def INPUT_TYPES(cls): FUNCTION = "slice" def slice(self, list: list[Any], start: int = 0, stop: int = -1, step: int = 1) -> tuple[list[Any]]: - if stop == -1: - stop = len(list) - return (list[start:stop:step],) diff --git a/tests/test_dict_nodes.py b/tests/test_dict_nodes.py new file mode 100644 index 0000000..8428aed --- /dev/null +++ b/tests/test_dict_nodes.py @@ -0,0 +1,193 @@ +#import pytest +from src.basic_data_handling.dict_nodes import ( + DictCreate, + DictCreateFromItems, + DictGet, + DictSet, + DictKeys, + DictValues, + DictItems, + DictContainsKey, + DictClear, + DictCopy, + DictFromKeys, + DictPop, + DictPopItem, + DictSetDefault, + DictUpdate, + DictLength, + DictMerge, + DictGetKeysValues, + DictRemove, + DictFilterByKeys, + DictExcludeKeys, + DictGetMultiple, + DictInvert, + DictCreateFromLists, + DictCompare, + AnyToDict, +) + +def test_dict_create(): + node = DictCreate() + assert node.create() == ({},) # Creates an empty dictionary + + +def test_dict_create_from_items(): + node = DictCreateFromItems() + items = [("key1", "value1"), ("key2", "value2")] + assert node.create_from_items(items) == ({"key1": "value1", "key2": "value2"},) + + +def test_dict_get(): + node = DictGet() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.get(my_dict, "key1") == ("value1",) + assert node.get(my_dict, "key3", default="default_value") == ("default_value",) + + +def test_dict_set(): + node = DictSet() + my_dict = {"key1": "value1"} + assert node.set(my_dict, "key2", "value2") == ({"key1": "value1", "key2": "value2"},) + + +def test_dict_keys(): + node = DictKeys() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.keys(my_dict) == (["key1", "key2"],) + + +def test_dict_values(): + node = DictValues() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.values(my_dict) == (["value1", "value2"],) + + +def test_dict_items(): + node = DictItems() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.items(my_dict) == ([("key1", "value1"), ("key2", "value2")],) + + +def test_dict_contains_key(): + node = DictContainsKey() + my_dict = {"key1": "value1"} + assert node.contains_key(my_dict, "key1") == (True,) + assert node.contains_key(my_dict, "key2") == (False,) + + +def test_dict_clear(): + node = DictClear() + my_dict = {"key1": "value1"} + assert node.clear(my_dict) == ({},) + + +def test_dict_copy(): + node = DictCopy() + my_dict = {"key1": "value1"} + assert node.copy(my_dict) == ({"key1": "value1"},) + + +def test_dict_from_keys(): + node = DictFromKeys() + keys = ["key1", "key2"] + assert node.from_keys(keys, value="value") == ({"key1": "value", "key2": "value"},) + + +def test_dict_pop(): + node = DictPop() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.pop(my_dict, "key1") == ({"key2": "value2"}, "value1") + + +def test_dict_pop_item(): + node = DictPopItem() + my_dict = {"key1": "value1"} + assert node.popitem(my_dict) == ({}, "key1", "value1", True) + assert node.popitem({}) == ({}, "", None, False) + + +def test_dict_set_default(): + node = DictSetDefault() + my_dict = {"key1": "value1"} + assert node.setdefault(my_dict, "key2", "default") == ({"key1": "value1", "key2": "default"}, "default") + + +def test_dict_update(): + node = DictUpdate() + my_dict = {"key1": "value1"} + update_dict = {"key2": "value2"} + assert node.update(my_dict, update_dict) == ({"key1": "value1", "key2": "value2"},) + + +def test_dict_length(): + node = DictLength() + my_dict = {"key1": "value1"} + assert node.length(my_dict) == (1,) + + +def test_dict_merge(): + node = DictMerge() + dict1 = {"key1": "value1"} + dict2 = {"key2": "value2"} + assert node.merge(dict1, dict2) == ({"key1": "value1", "key2": "value2"},) + + +def test_dict_get_keys_values(): + node = DictGetKeysValues() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.get_keys_values(my_dict) == (["key1", "key2"], ["value1", "value2"]) + + +def test_dict_remove(): + node = DictRemove() + my_dict = {"key1": "value1"} + assert node.remove(my_dict, "key1") == ({}, True) + + +def test_dict_filter_by_keys(): + node = DictFilterByKeys() + my_dict = {"key1": "value1", "key2": "value2"} + keys = ["key1"] + assert node.filter_by_keys(my_dict, keys) == ({"key1": "value1"},) + + +def test_dict_exclude_keys(): + node = DictExcludeKeys() + my_dict = {"key1": "value1", "key2": "value2"} + keys = ["key1"] + assert node.exclude_keys(my_dict, keys) == ({"key2": "value2"},) + + +def test_dict_get_multiple(): + node = DictGetMultiple() + my_dict = {"key1": "value1", "key2": "value2"} + keys = ["key1", "key3"] + assert node.get_multiple(my_dict, keys, default="default") == (["value1", "default"],) + + +def test_dict_invert(): + node = DictInvert() + my_dict = {"key1": "value1", "key2": "value2"} + assert node.invert(my_dict) == ({"value1": "key1", "value2": "key2"}, True) + + +def test_dict_create_from_lists(): + node = DictCreateFromLists() + keys = ["key1", "key2"] + values = ["value1", "value2"] + assert node.create_from_lists(keys, values) == ({"key1": "value1", "key2": "value2"},) + + +def test_dict_compare(): + node = DictCompare() + dict1 = {"key1": "value1"} + dict2 = {"key1": "value1"} + assert node.compare(dict1, dict2) == (True, [], [], []) + + +def test_any_to_dict(): + node = AnyToDict() + my_dict = {"key1": "value1"} + assert node.convert(my_dict) == ({"key1": "value1"},) diff --git a/tests/test_float_nodes.py b/tests/test_float_nodes.py new file mode 100644 index 0000000..1f548cb --- /dev/null +++ b/tests/test_float_nodes.py @@ -0,0 +1,85 @@ +import pytest +from src.basic_data_handling.float_nodes import ( + FloatAdd, + FloatSubtract, + FloatMultiply, + FloatDivide, + FloatPower, + FloatRound, + FloatIsInteger, + FloatAsIntegerRatio, + FloatHex, + FloatFromHex, +) + +def test_float_add(): + node = FloatAdd() + assert node.add(2.2, 2.2) == (4.4,) + assert node.add(-1.5, 1.5) == (0.0,) + assert node.add(0.0, 0.0) == (0.0,) + + +def test_float_subtract(): + node = FloatSubtract() + assert node.subtract(5.5, 2.2) == (3.3,) + assert node.subtract(-1.5, 1.5) == (-3.0,) + assert node.subtract(0.0, 0.0) == (0.0,) + + +def test_float_multiply(): + node = FloatMultiply() + assert node.multiply(2.0, 3.5) == (7.0,) + assert node.multiply(-2.0, 3.0) == (-6.0,) + assert node.multiply(0.0, 5.0) == (0.0,) + + +def test_float_divide(): + node = FloatDivide() + assert node.divide(7.0, 2.0) == (3.5,) + assert node.divide(-6.0, 3.0) == (-2.0,) + with pytest.raises(ValueError, match="Cannot divide by zero."): + node.divide(5.0, 0.0) + + +def test_float_power(): + node = FloatPower() + assert node.power(2.0, 3.0) == (8.0,) + assert node.power(5.0, 0.0) == (1.0,) + assert node.power(-2.0, 3.0) == (-8.0,) + + +def test_float_round(): + node = FloatRound() + assert node.round(3.14159, 2) == (3.14,) + assert node.round(2.71828, 3) == (2.718,) + assert node.round(-1.2345, 1) == (-1.2,) + assert node.round(0.0, 0) == (0.0,) + + +def test_float_is_integer(): + node = FloatIsInteger() + assert node.is_integer(3.0) == (True,) + assert node.is_integer(3.14) == (False,) + assert node.is_integer(-2.0) == (True,) + assert node.is_integer(0.0) == (True,) + + +def test_float_as_integer_ratio(): + node = FloatAsIntegerRatio() + assert node.as_integer_ratio(3.5) == (7, 2) + assert node.as_integer_ratio(2.0) == (2, 1) + assert node.as_integer_ratio(-1.25) == (-5, 4) + + +def test_float_hex(): + node = FloatHex() + assert node.to_hex(3.5) == ("0x1.c000000000000p+1",) + assert node.to_hex(0.0) == ("0x0.0p+0",) + assert node.to_hex(-2.5) == ("-0x1.4000000000000p+1",) + + +def test_float_from_hex(): + node = FloatFromHex() + assert node.from_hex("0x1.c000000000000p+1") == (3.5,) + assert node.from_hex("0x0.0p+0") == (0.0,) + assert node.from_hex("-0x1.4000000000000p+1") == (-2.5,) diff --git a/tests/test_int_nodes.py b/tests/test_int_nodes.py new file mode 100644 index 0000000..1b34b02 --- /dev/null +++ b/tests/test_int_nodes.py @@ -0,0 +1,86 @@ +import pytest +from src.basic_data_handling.int_nodes import ( + IntAdd, + IntSubtract, + IntMultiply, + IntDivide, + IntModulus, + IntPower, + IntBitLength, + IntToBytes, + IntFromBytes, + IntBitCount, +) + + +def test_int_add(): + node = IntAdd() + assert node.add(10, 5) == (15,) + assert node.add(-10, 5) == (-5,) + assert node.add(0, 0) == (0,) + + +def test_int_subtract(): + node = IntSubtract() + assert node.subtract(10, 5) == (5,) + assert node.subtract(5, 10) == (-5,) + assert node.subtract(0, 0) == (0,) + + +def test_int_multiply(): + node = IntMultiply() + assert node.multiply(10, 5) == (50,) + assert node.multiply(-1, 5) == (-5,) + assert node.multiply(0, 5) == (0,) + + +def test_int_divide(): + node = IntDivide() + assert node.divide(10, 2) == (5,) + assert node.divide(9, 4) == (2,) # Integer division + with pytest.raises(ValueError, match="Cannot divide by zero."): + node.divide(10, 0) + + +def test_int_modulus(): + node = IntModulus() + assert node.modulus(10, 3) == (1,) + assert node.modulus(9, 9) == (0,) + with pytest.raises(ValueError, match="Cannot perform modulus operation by zero."): + node.modulus(10, 0) + + +def test_int_power(): + node = IntPower() + assert node.power(2, 3) == (8,) + assert node.power(10, 0) == (1,) # Any number to the power 0 is 1 + assert node.power(-2, 3) == (-8,) + + +def test_int_bit_length(): + node = IntBitLength() + assert node.bit_length(0) == (0,) # Zero requires 0 bits + assert node.bit_length(1) == (1,) + assert node.bit_length(255) == (8,) # 255 requires 8 bits + assert node.bit_length(-255) == (8,) # Negative numbers have the same bit length + + +def test_int_to_bytes(): + node = IntToBytes() + assert node.to_bytes(255, 2, "big", "False") == (b"\x00\xff",) + assert node.to_bytes(255, 2, "little", "False") == (b"\xff\x00",) + assert node.to_bytes(-255, 2, "big", "True") == (b"\xff\x01",) + + +def test_int_from_bytes(): + node = IntFromBytes() + assert node.from_bytes(b"\x00\xff", "big", "False") == (255,) + assert node.from_bytes(b"\xff\x00", "little", "False") == (255,) + assert node.from_bytes(b"\xff\x01", "big", "True") == (-255,) + + +def test_int_bit_count(): + node = IntBitCount() + assert node.bit_count(0) == (0,) # Zero has no 1 bits + assert node.bit_count(255) == (8,) # 255 is 11111111 (8 ones) + assert node.bit_count(-255) == (8,) # Negative integer has the same bit count \ No newline at end of file diff --git a/tests/test_list_nodes.py b/tests/test_list_nodes.py new file mode 100644 index 0000000..0f97512 --- /dev/null +++ b/tests/test_list_nodes.py @@ -0,0 +1,139 @@ +import pytest +from src.basic_data_handling.list_nodes import ( + ListAppend, + ListExtend, + ListInsert, + ListRemove, + ListPop, + ListClear, + ListIndex, + ListCount, + ListSort, + ListReverse, + ListLength, + ListSlice, + ListGetItem, + ListSetItem, + ListContains, + ListMin, + ListMax +) + + +def test_list_append(): + node = ListAppend() + assert node.append([1, 2, 3], 4) == ([1, 2, 3, 4],) + assert node.append([], "item") == (["item"],) + assert node.append(["a", "b"], {"key": "value"}) == (["a", "b", {"key": "value"}],) + + +def test_list_extend(): + node = ListExtend() + assert node.extend([1, 2], [3, 4]) == ([1, 2, 3, 4],) + assert node.extend([], [1, 2, 3]) == ([1, 2, 3],) + assert node.extend([1, 2], []) == ([1, 2],) + + +def test_list_insert(): + node = ListInsert() + assert node.insert([1, 3, 4], 1, 2) == ([1, 2, 3, 4],) + assert node.insert([], 0, "first") == (["first"],) + assert node.insert([1, 2], 10, "out_of_bounds") == ([1, 2, "out_of_bounds"],) + + +def test_list_remove(): + node = ListRemove() + assert node.remove([1, 2, 3, 2], 2) == ([1, 3, 2], True) + assert node.remove([1, 2, 3], 4) == ([1, 2, 3], False) + assert node.remove([], "not_in_list") == ([], False) + + +def test_list_pop(): + node = ListPop() + assert node.pop([1, 2, 3], 1) == ([1, 3], 2) + assert node.pop([1, 2, 3]) == ([1, 2], 3) # Default: last item + assert node.pop([], 0) == ([], None) # Empty list pop + + +def test_list_clear(): + node = ListClear() + assert node.clear([1, 2, 3]) == ([],) + assert node.clear([]) == ([],) + + +def test_list_index(): + node = ListIndex() + assert node.index([1, 2, 3, 2], 2) == (1,) + assert node.index([1, 2, 3, 2], 2, 2) == (3,) + assert node.index([1, 2, 3, 2], 4) == (-1,) + + +def test_list_count(): + node = ListCount() + assert node.count([1, 2, 3, 2], 2) == (2,) + assert node.count([], 1) == (0,) + assert node.count(["a", "b", "a"], "a") == (2,) + + +def test_list_sort(): + node = ListSort() + assert node.sort([3, 1, 2]) == ([1, 2, 3],) + assert node.sort([3, 1, 2], "True") == ([3, 2, 1],) # Reverse + assert node.sort(["b", "a", "c"]) == (["a", "b", "c"],) + assert node.sort([1, "a"]) == ([1, "a"],) # Unsortable list + + +def test_list_reverse(): + node = ListReverse() + assert node.reverse([1, 2, 3]) == ([3, 2, 1],) + assert node.reverse([]) == ([],) + assert node.reverse(["a", "b", "c"]) == (["c", "b", "a"],) + + +def test_list_length(): + node = ListLength() + assert node.length([1, 2, 3]) == (3,) + assert node.length([]) == (0,) + assert node.length(["a", "b", "c", "d"]) == (4,) + + +def test_list_slice(): + node = ListSlice() + assert node.slice([1, 2, 3, 4], 1, 3) == ([2, 3],) + assert node.slice([1, 2, 3, 4], 0, -1) == ([1, 2, 3],) + assert node.slice([1, 2, 3, 4], 0, 4, 2) == ([1, 3],) + + +def test_list_get_item(): + node = ListGetItem() + assert node.get_item([1, 2, 3], 0) == (1,) + assert node.get_item([1, 2, 3], -1) == (3,) + assert node.get_item([], 0) == (None,) + + +def test_list_set_item(): + node = ListSetItem() + assert node.set_item([1, 2, 3], 1, 42) == ([1, 42, 3],) + with pytest.raises(IndexError): + node.set_item([1, 2, 3], 3, 42) == ([1, 2, 3],) # Out of range + + +def test_list_contains(): + node = ListContains() + assert node.contains([1, 2, 3], 2) == (True,) + assert node.contains([1, 2, 3], 4) == (False,) + assert node.contains([], "value") == (False,) + + +def test_list_min(): + node = ListMin() + assert node.find_min([1, 2, 3]) == (1,) + assert node.find_min(["b", "a", "c"]) == ("a",) + assert node.find_min([]) == (None,) + + +def test_list_max(): + node = ListMax() + assert node.find_max([1, 2, 3]) == (3,) + assert node.find_max(["b", "a", "c"]) == ("c",) + assert node.find_max([]) == (None,) diff --git a/tests/test_math_nodes.py b/tests/test_math_nodes.py new file mode 100644 index 0000000..5918e9f --- /dev/null +++ b/tests/test_math_nodes.py @@ -0,0 +1,148 @@ +import pytest +import math +from src.basic_data_handling.math_nodes import ( + MathSin, MathCos, MathTan, MathAsin, MathAcos, MathAtan, MathAtan2, + MathSqrt, MathExp, MathLog, MathLog10, MathDegrees, MathRadians, + MathFloor, MathCeil, MathAbs, MathPi, MathE, MathMin, MathMax, +) + + +def test_math_sin(): + node = MathSin() + assert node.calculate(0, "radians") == (0.0,) + assert node.calculate(math.pi / 2, "radians") == (1.0,) + assert node.calculate(30, "degrees") == (math.sin(math.radians(30)),) + + +def test_math_cos(): + node = MathCos() + assert node.calculate(0, "radians") == (1.0,) + assert node.calculate(math.pi, "radians") == (-1.0,) + assert node.calculate(60, "degrees") == (math.cos(math.radians(60)),) + + +def test_math_tan(): + node = MathTan() + assert node.calculate(0, "radians") == (0.0,) + assert node.calculate(45, "degrees") == (math.tan(math.radians(45)),) + with pytest.raises(ValueError): # Undefined tangent + node.calculate(90, "degrees") + + +def test_math_asin(): + node = MathAsin() + assert node.calculate(0, "radians") == (0.0,) + assert node.calculate(1, "radians") == (math.pi / 2,) + assert node.calculate(0.5, "degrees") == (math.degrees(math.asin(0.5)),) + + +def test_math_acos(): + node = MathAcos() + assert node.calculate(1, "radians") == (0.0,) + assert node.calculate(0, "radians") == (math.pi / 2,) + assert node.calculate(0.5, "degrees") == (math.degrees(math.acos(0.5)),) + + +def test_math_atan(): + node = MathAtan() + assert node.calculate(0, "radians") == (0.0,) + assert node.calculate(1, "degrees") == (45.0,) + assert node.calculate(-1, "radians") == (-math.pi / 4,) + + +def test_math_atan2(): + node = MathAtan2() + assert node.calculate(0, 1, "radians") == (0.0,) + assert node.calculate(1, 1, "degrees") == (45.0,) + assert node.calculate(-1, -1, "radians") == (-3 * math.pi / 4,) + + +def test_math_sqrt(): + node = MathSqrt() + assert node.calculate(4) == (2.0,) + assert node.calculate(0) == (0.0,) + with pytest.raises(ValueError): # Negative numbers + node.calculate(-1) + + +def test_math_exp(): + node = MathExp() + assert node.calculate(0) == (1.0,) + assert node.calculate(1) == (math.e,) + assert node.calculate(-1) == (1 / math.e,) + + +def test_math_log(): + node = MathLog() + assert node.calculate(1) == (0.0,) + assert node.calculate(math.e) == (1.0,) + assert node.calculate(8, base=2) == (3.0,) + with pytest.raises(ValueError): # Log of non-positive + node.calculate(-1) + + +def test_math_log10(): + node = MathLog10() + assert node.calculate(1) == (0.0,) + assert node.calculate(10) == (1.0,) + with pytest.raises(ValueError): # Log of non-positive + node.calculate(0) + + +def test_math_degrees(): + node = MathDegrees() + assert node.calculate(math.pi) == (180.0,) + assert node.calculate(math.pi / 2) == (90.0,) + assert node.calculate(0) == (0.0,) + + +def test_math_radians(): + node = MathRadians() + assert node.calculate(180) == (math.pi,) + assert node.calculate(90) == (math.pi / 2,) + assert node.calculate(0) == (0.0,) + + +def test_math_floor(): + node = MathFloor() + assert node.calculate(1.7) == (1,) + assert node.calculate(-1.7) == (-2,) + assert node.calculate(0.0) == (0,) + + +def test_math_ceil(): + node = MathCeil() + assert node.calculate(1.2) == (2,) + assert node.calculate(-1.2) == (-1,) + assert node.calculate(0.0) == (0,) + + +def test_math_abs(): + node = MathAbs() + assert node.calculate(-5) == (5,) + assert node.calculate(5) == (5,) + assert node.calculate(0) == (0,) + + +def test_math_pi(): + node = MathPi() + assert node.calculate() == (math.pi,) + + +def test_math_e(): + node = MathE() + assert node.calculate() == (math.e,) + + +def test_math_min(): + node = MathMin() + assert node.calculate(5, 10) == (5,) + assert node.calculate(-1, 0) == (-1,) + assert node.calculate(3.5, 2.5) == (2.5,) + + +def test_math_max(): + node = MathMax() + assert node.calculate(5, 10) == (10,) + assert node.calculate(-1, 0) == (0,) + assert node.calculate(3.5, 2.5) == (3.5,) diff --git a/tests/test_path_nodes.py b/tests/test_path_nodes.py new file mode 100644 index 0000000..62fc962 --- /dev/null +++ b/tests/test_path_nodes.py @@ -0,0 +1,181 @@ +import os +import pytest + +from src.basic_data_handling.path_nodes import ( + PathJoin, PathAbspath, PathExists, PathIsFile, PathIsDir, PathGetSize, + PathSplit, PathSplitExt, PathBasename, PathDirname, PathGetExtension, + PathNormalize, PathRelative, PathGlob, PathExpandVars, PathGetCwd, + PathListDir, PathIsAbsolute, PathCommonPrefix, +) + + +def test_path_join(): + node = PathJoin() + assert node.join_paths("folder", "file.txt") == (os.path.join("folder", "file.txt"),) + assert node.join_paths("folder", "") == (os.path.join("folder"),) + assert node.join_paths(".", "") == (".",) + + +def test_path_abspath(): + node = PathAbspath() + assert node.get_abspath(".") == (os.path.abspath("."),) + assert node.get_abspath("folder/file.txt") == (os.path.abspath("folder/file.txt"),) + + +def test_path_exists(tmp_path): + node = PathExists() + existing_file = tmp_path / "file.txt" + existing_file.write_text("content") + + assert node.check_exists(str(existing_file)) == (True,) + assert node.check_exists("nonexistent.file") == (False,) + + +def test_path_is_file(tmp_path): + node = PathIsFile() + file = tmp_path / "file.txt" + file.write_text("content") + directory = tmp_path / "directory" + directory.mkdir() + + assert node.check_is_file(str(file)) == (True,) + assert node.check_is_file(str(directory)) == (False,) + assert node.check_is_file("nonexistent.file") == (False,) + + +def test_path_is_dir(tmp_path): + node = PathIsDir() + directory = tmp_path / "directory" + directory.mkdir() + file = tmp_path / "file.txt" + file.write_text("content") + + assert node.check_is_dir(str(directory)) == (True,) + assert node.check_is_dir(str(file)) == (False,) + assert node.check_is_dir("nonexistent.dir") == (False,) + + +def test_path_get_size(tmp_path): + node = PathGetSize() + file = tmp_path / "file.txt" + file.write_text("content") + + assert node.get_size(str(file)) == (len("content"),) + + with pytest.raises(FileNotFoundError): + node.get_size("nonexistent.file") + + directory = tmp_path / "directory" + directory.mkdir() + + with pytest.raises(ValueError): + node.get_size(str(directory)) + + +def test_path_split(): + node = PathSplit() + path = os.path.join("folder", "file.txt") + + assert node.split_path(path) == (os.path.dirname(path), os.path.basename(path)) + assert node.split_path("file.txt") == ("", "file.txt") + + +def test_path_splitext(): + node = PathSplitExt() + assert node.split_ext("file.txt") == ("file", ".txt") + assert node.split_ext("file") == ("file", "") + + +def test_path_basename(): + node = PathBasename() + assert node.get_basename("folder/file.txt") == ("file.txt",) + assert node.get_basename("file.txt") == ("file.txt",) + + +def test_path_dirname(): + node = PathDirname() + assert node.get_dirname("folder/file.txt") == ("folder",) + assert node.get_dirname("file.txt") == ("",) + + +def test_path_get_extension(): + node = PathGetExtension() + assert node.get_extension("file.txt") == (".txt",) + assert node.get_extension("file") == ("",) + + +def test_path_normalize(): + node = PathNormalize() + assert node.normalize_path("folder/../file.txt") == (os.path.normpath("folder/../file.txt"),) + assert node.normalize_path("folder//file.txt") == (os.path.normpath("folder//file.txt"),) + + +def test_path_relative(tmp_path): + node = PathRelative() + start = tmp_path / "start" + start.mkdir() + target = tmp_path / "start/target" + target.mkdir() + + assert node.get_relative_path(str(target), str(start)) == (os.path.relpath(str(target), str(start)),) + assert node.get_relative_path(str(target)) == (os.path.relpath(str(target), os.getcwd()),) + + +def test_path_glob(tmp_path): + node = PathGlob() + (tmp_path / "file1.txt").write_text("content") + (tmp_path / "file2.txt").write_text("content") + (tmp_path / "file3.txt").write_text("content") + + result = node.glob_paths(str(tmp_path / "*.txt")) + expected = sorted([str(tmp_path / f"file{i}.txt") for i in range(1, 4)]) + + assert sorted(result[0]) == expected + + recursive_node = PathGlob() + assert recursive_node.glob_paths(str(tmp_path), recursive="True")[0] + + +def test_path_expand_vars(monkeypatch): + node = PathExpandVars() + monkeypatch.setenv("TEST_VAR", "test_value") + + assert node.expand_vars("$TEST_VAR/path") == ("test_value/path",) + assert node.expand_vars("${TEST_VAR}/path") == ("test_value/path",) + + +def test_path_get_cwd(): + node = PathGetCwd() + assert node.get_cwd() == (os.getcwd(),) + + +def test_path_list_dir(tmp_path): + node = PathListDir() + (tmp_path / "file1.txt").write_text("content") + (tmp_path / "dir1").mkdir() + + all_entries = node.list_directory(str(tmp_path)) + assert "file1.txt" in all_entries[0] + assert "dir1" in all_entries[0] + + only_files = node.list_directory(str(tmp_path), files_only="True") + assert "file1.txt" in only_files[0] + assert "dir1" not in only_files[0] + + only_dirs = node.list_directory(str(tmp_path), dirs_only="True") + assert "file1.txt" not in only_dirs[0] + assert "dir1" in only_dirs[0] + + +def test_path_is_absolute(): + node = PathIsAbsolute() + assert node.check_is_absolute("/") == (True,) + assert node.check_is_absolute("relative/path") == (False,) + + +def test_path_common_prefix(): + node = PathCommonPrefix() + path1 = "/path/to/file1.txt" + path2 = "/path/to/file2.txt" + + assert node.get_common_prefix(path1, path2) == (os.path.commonprefix([path1, path2]),) diff --git a/tests/test_regex_nodes.py b/tests/test_regex_nodes.py new file mode 100644 index 0000000..6f2335f --- /dev/null +++ b/tests/test_regex_nodes.py @@ -0,0 +1,58 @@ +#import pytest +from src.basic_data_handling.regex_nodes import ( + RegexSearchGroups, + RegexGroupDict, + RegexFindall, + RegexSplit, + RegexSub, + RegexTest +) + +def test_regex_search_groups(): + node = RegexSearchGroups() + assert node.search_groups(r"(foo)(bar)", "foobar") == (["foo", "bar"],) + assert node.search_groups(r"(test)(\d+)", "test123") == (["test", "123"],) + assert node.search_groups(r"(nothing)", "no match here") == ([],) # No match + assert node.search_groups(r"(\w+)", "word") == (["word"],) # Single group + + +def test_regex_group_dict(): + node = RegexGroupDict() + pattern = r"(?P\w+)-(?P\d+)" + string = "test-123" + assert node.groupdict(pattern, string) == ({"word": "test", "number": "123"},) + assert node.groupdict(pattern, "no-match") == ({},) # No match + assert node.groupdict(r"(?P\w+)", "Example") == ({"name": "Example"},) + + +def test_regex_findall(): + node = RegexFindall() + assert node.findall(r"\d+", "abc 123 def 456") == (["123", "456"],) + assert node.findall(r"\b[a-zA-Z]+\b", "Multiple words here") == (["Multiple", "words", "here"],) + assert node.findall(r"nonexistent", "no match") == ([],) # No match + assert node.findall(r"(a)(b)", "ab ab") == ([("a", "b"), ("a", "b")],) # Multiple groups + + +def test_regex_split(): + node = RegexSplit() + pattern = r",\s*" + string = "one, two, three" + assert node.split(pattern, string) == (["one", "two", "three"],) + assert node.split(r"\s+", "split several words") == (["split", "several", "words"],) # Whitespace split + assert node.split(r"z", "nozsplit") == (["no", "split"],) # Split by 'z' + + +def test_regex_sub(): + node = RegexSub() + assert node.sub(r"cat", "dog", "cat is here") == ("dog is here",) + assert node.sub(r"\d+", "NUMBER", "abc 123 def 456") == ("abc NUMBER def NUMBER",) + assert node.sub(r"\d+", "NUMBER", "abc 123 def 456", count=1) == ("abc NUMBER def 456",) # Limit replacements + assert node.sub(r"[aeiou]", "*", "hello") == ("h*ll*",) + + +def test_regex_test(): + node = RegexTest() + assert node.test(r"abc", "a quick abc") == (True,) + assert node.test(r"def", "a quick abc") == (False,) + assert node.test(r"\d+", "contains 456") == (True,) + assert node.test(r"^\s*$", "") == (True,) # Empty string matches whitespace pattern diff --git a/tests/test_set_nodes.py b/tests/test_set_nodes.py new file mode 100644 index 0000000..b80541f --- /dev/null +++ b/tests/test_set_nodes.py @@ -0,0 +1,124 @@ +#import pytest +from src.basic_data_handling.set_nodes import ( + SetAdd, + SetRemove, + SetDiscard, + SetPop, + SetClear, + SetUnion, + SetIntersection, + SetDifference, + SetSymmetricDifference, + SetIsSubset, + SetIsSuperset, + SetIsDisjoint, + SetContains, + SetLength, + ListToSet, + SetToList +) + +def test_set_add(): + node = SetAdd() + assert node.add({1, 2}, 3) == ({1, 2, 3},) + assert node.add({1, 2}, 1) == ({1, 2},) # Adding an existing item + + +def test_set_remove(): + node = SetRemove() + assert node.remove({1, 2, 3}, 2) == ({1, 3}, True) # Successful removal + assert node.remove({1, 2, 3}, 4) == ({1, 2, 3}, False) # Item not in set + + +def test_set_discard(): + node = SetDiscard() + assert node.discard({1, 2, 3}, 2) == ({1, 3},) # Successful removal + assert node.discard({1, 2, 3}, 4) == ({1, 2, 3},) # No error for missing item + + +def test_set_pop(): + node = SetPop() + input_set = {1, 2, 3} + result_set, removed_item = node.pop(input_set) + assert result_set != input_set # Arbitrary item removed + assert removed_item in input_set # Removed item was part of original set + + empty_set = set() + assert node.pop(empty_set) == (set(), None) # Handle empty set + + +def test_set_clear(): + node = SetClear() + assert node.clear({1, 2, 3}) == (set(),) + assert node.clear(set()) == (set(),) # Already empty set remains empty + + +def test_set_union(): + node = SetUnion() + assert node.union({1, 2}, {3, 4}) == ({1, 2, 3, 4},) + assert node.union({1}, {2}, {3}, {4}) == ({1, 2, 3, 4},) + assert node.union({1, 2}, set()) == ({1, 2},) # Union with empty set + + +def test_set_intersection(): + node = SetIntersection() + assert node.intersection({1, 2, 3}, {2, 3, 4}) == ({2, 3},) + assert node.intersection({1, 2, 3}, {4, 5}) == (set(),) # No common elements + assert node.intersection({1, 2, 3}, {2, 3}, {3, 4}) == ({3},) # Multiple sets + + +def test_set_difference(): + node = SetDifference() + assert node.difference({1, 2, 3}, {2, 3, 4}) == ({1},) + assert node.difference({1, 2, 3}, {4, 5}) == ({1, 2, 3},) # Nothing to remove + + +def test_set_symmetric_difference(): + node = SetSymmetricDifference() + assert node.symmetric_difference({1, 2, 3}, {3, 4, 5}) == ({1, 2, 4, 5},) + assert node.symmetric_difference({1, 2, 3}, {1, 2, 3}) == (set(),) # No unique elements + + +def test_set_is_subset(): + node = SetIsSubset() + assert node.is_subset({1, 2}, {1, 2, 3}) == (True,) + assert node.is_subset({1, 4}, {1, 2, 3}) == (False,) + assert node.is_subset(set(), {1, 2, 3}) == (True,) # Empty set is subset of all sets + + +def test_set_is_superset(): + node = SetIsSuperset() + assert node.is_superset({1, 2, 3}, {1, 2}) == (True,) + assert node.is_superset({1, 2}, {1, 2, 3}) == (False,) + assert node.is_superset(set(), set()) == (True,) # Empty set is a superset of itself + + +def test_set_is_disjoint(): + node = SetIsDisjoint() + assert node.is_disjoint({1, 2}, {3, 4}) == (True,) # No common elements + assert node.is_disjoint({1, 2}, {2, 3}) == (False,) # Common element + + +def test_set_contains(): + node = SetContains() + assert node.contains({1, 2, 3}, 2) == (True,) + assert node.contains({1, 2, 3}, 4) == (False,) + + +def test_set_length(): + node = SetLength() + assert node.length({1, 2, 3}) == (3,) + assert node.length(set()) == (0,) # Empty set + + +def test_list_to_set(): + node = ListToSet() + assert node.convert([1, 2, 3, 2]) == ({1, 2, 3},) + assert node.convert([]) == (set(),) # Empty list + + +def test_set_to_list(): + node = SetToList() + result = node.convert({1, 2, 3}) + assert isinstance(result, tuple) + assert sorted(result[0]) == [1, 2, 3] # Validate conversion to list