From d6138f5d19c69415a71d28f4b3cffd2c3d45b674 Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:32:21 +0200 Subject: [PATCH 01/22] Started on test scrips to enchore that tehe code gives sensible outputs --- .../DataProsesing/test_DataConsolPrinter.py | 3 + UnitTest/DataProsesing/test_DataFormater.py | 3 + .../DataProsesing/test_DataHelperFunctions.py | 3 + UnitTest/DataProsesing/test_DataPrettifyer.py | 3 + UnitTest/DataProsesing/test_DataRW.py | 3 + UnitTest/DataProsesing/test_PID.py | 80 ++++++++++++++++ UnitTest/DataProsesing/test_filter.py | 3 + UnitTest/DataProsesing/test_map.py | 96 +++++++++++++++++++ UnitTest/Suporting/test_csvReader.py | 90 +++++++++++++++++ UnitTest/Suporting/test_csvWriter.py | 3 + UnitTest/Suporting/test_csvWriterLogger.py | 3 + UnitTest/Suporting/test_jsonReader.py | 3 + UnitTest/Suporting/test_jsonWriter.py | 3 + UnitTest/Suporting/test_yamlReader.py | 3 + UnitTest/Suporting/test_yamlWriter.py | 3 + UnitTest/Utility/test_ConfigManager.py | 3 + UnitTest/Utility/test_Debug.py | 3 + UnitTest/Utility/test_DeltaTime.py | 3 + UnitTest/Utility/test_Logger.py | 3 + UnitTest/Utility/test_UnitConverter.py | 3 + UnitTest/__init__.py | 26 +++++ setup.py | 1 + 22 files changed, 344 insertions(+) create mode 100644 UnitTest/DataProsesing/test_DataConsolPrinter.py create mode 100644 UnitTest/DataProsesing/test_DataFormater.py create mode 100644 UnitTest/DataProsesing/test_DataHelperFunctions.py create mode 100644 UnitTest/DataProsesing/test_DataPrettifyer.py create mode 100644 UnitTest/DataProsesing/test_DataRW.py create mode 100644 UnitTest/DataProsesing/test_PID.py create mode 100644 UnitTest/DataProsesing/test_filter.py create mode 100644 UnitTest/DataProsesing/test_map.py create mode 100644 UnitTest/Suporting/test_csvReader.py create mode 100644 UnitTest/Suporting/test_csvWriter.py create mode 100644 UnitTest/Suporting/test_csvWriterLogger.py create mode 100644 UnitTest/Suporting/test_jsonReader.py create mode 100644 UnitTest/Suporting/test_jsonWriter.py create mode 100644 UnitTest/Suporting/test_yamlReader.py create mode 100644 UnitTest/Suporting/test_yamlWriter.py create mode 100644 UnitTest/Utility/test_ConfigManager.py create mode 100644 UnitTest/Utility/test_Debug.py create mode 100644 UnitTest/Utility/test_DeltaTime.py create mode 100644 UnitTest/Utility/test_Logger.py create mode 100644 UnitTest/Utility/test_UnitConverter.py create mode 100644 UnitTest/__init__.py diff --git a/UnitTest/DataProsesing/test_DataConsolPrinter.py b/UnitTest/DataProsesing/test_DataConsolPrinter.py new file mode 100644 index 0000000..7372c6a --- /dev/null +++ b/UnitTest/DataProsesing/test_DataConsolPrinter.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +#TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataFormater.py b/UnitTest/DataProsesing/test_DataFormater.py new file mode 100644 index 0000000..7372c6a --- /dev/null +++ b/UnitTest/DataProsesing/test_DataFormater.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +#TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataHelperFunctions.py b/UnitTest/DataProsesing/test_DataHelperFunctions.py new file mode 100644 index 0000000..7372c6a --- /dev/null +++ b/UnitTest/DataProsesing/test_DataHelperFunctions.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +#TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataPrettifyer.py b/UnitTest/DataProsesing/test_DataPrettifyer.py new file mode 100644 index 0000000..7372c6a --- /dev/null +++ b/UnitTest/DataProsesing/test_DataPrettifyer.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +#TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataRW.py b/UnitTest/DataProsesing/test_DataRW.py new file mode 100644 index 0000000..7372c6a --- /dev/null +++ b/UnitTest/DataProsesing/test_DataRW.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +#TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_PID.py b/UnitTest/DataProsesing/test_PID.py new file mode 100644 index 0000000..f99cf69 --- /dev/null +++ b/UnitTest/DataProsesing/test_PID.py @@ -0,0 +1,80 @@ +import pytest +from GabesPythonToolBox.DataProsesing.PID import * + + + +# Helper functions + + +def almost_equal(a, b, tol=1e-6): + """Helper to compare floating point values.""" + return abs(a - b) < tol + + +def test_pid_proportional_only(): + pid = PID(P=2.0, I=0.0, D=0.0) + output, P_, I_, D_, error, dt = pid.Update(current_value=3, set_value=5, dt=1.0) + assert almost_equal(output, 4.0) + assert almost_equal(P_, 4.0) + assert almost_equal(I_, 0.0) + assert almost_equal(D_, 0.0) + + +def test_pid_integral_accumulation(): + pid = PID(P=0.0, I=1.0, D=0.0) + _, _, I1, _, _, _ = pid.Update(current_value=0, set_value=1, dt=1.0) + _, _, I2, _, _, _ = pid.Update(current_value=0, set_value=1, dt=1.0) + assert I2 > I1 # Integral should increase over time + + +def test_pid_derivative_response(): + pid = PID(P=0.0, I=0.0, D=1.0) + pid.Update(current_value=0, set_value=1, dt=1.0) # First call sets prev_error + output, _, _, D_, _, _ = pid.Update(current_value=0.5, set_value=1, dt=1.0) + # Derivative term should be negative (error decreasing) + assert D_ < 0 + + +def test_pid_integral_clamping(): + pid = PID(P=0.0, I=1.0, D=0.0, integral_limit=0.5) + for _ in range(10): + pid.Update(current_value=0, set_value=1, dt=1.0) + assert abs(pid.integral) <= 0.5 # Clamped integral + + +def test_pid_reset_integral(): + pid = PID(P=0.0, I=1.0, D=0.0) + pid.integral = 10 + pid.ResetIntegral() + assert pid.integral == 0 + + + +# PID parameter modification + + +def test_pid_change_parameters(): + pid = PID(P=1.0, I=1.0, D=1.0) + pid.Change(P_=2.0, I_=0.5, D_=0.1, integral_limit=5.0) + assert pid.P == 2.0 + assert pid.I == 0.5 + assert pid.D == 0.1 + assert pid.integral_limit == 5.0 + + +def test_pid_change_individual_terms(): + pid = PID() + pid.ChangeP(1.5) + pid.ChangeI(0.5) + pid.ChangeD(0.1) + assert pid.P == 1.5 + assert pid.I == 0.5 + assert pid.D == 0.1 + + +def test_pid_factory_function_creates_pid(): + pid = NewPID(P=1, I=2, D=3) + assert isinstance(pid, PID) + assert pid.P == 1 + assert pid.I == 2 + assert pid.D == 3 diff --git a/UnitTest/DataProsesing/test_filter.py b/UnitTest/DataProsesing/test_filter.py new file mode 100644 index 0000000..7372c6a --- /dev/null +++ b/UnitTest/DataProsesing/test_filter.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.DataProsesing import * +#TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_map.py b/UnitTest/DataProsesing/test_map.py new file mode 100644 index 0000000..c235083 --- /dev/null +++ b/UnitTest/DataProsesing/test_map.py @@ -0,0 +1,96 @@ +import pytest +import GabesPythonToolBox.DataProsesing.map as map + +def almost_equal(a, b, tol=1e-6): + return abs(a - b) < tol + + + +# Map tests + + +def test_map_basic_linear_mapping(): + # Simple case: map 5 from range 0–10 → 0–100 + result = map.Map(5, 0, 10, 0, 100) + assert almost_equal(result, 50) + + +def test_map_negative_to_positive_range(): + # Map -5 from -10–10 → 0–100 → should give 25 + result = map.Map(-5, -10, 10, 0, 100) + assert almost_equal(result, 25) + + +def test_map_reversed_input_range(): + # Reverse input range should still work correctly + result = map.Map(5, 10, 0, 0, 100) + assert almost_equal(result, 50) + + +def test_map_type_error(): + with pytest.raises(TypeError): + map.Map("a", 0, 10, 0, 100) + map.Map(0, "a", 10, 0, 100) + map.Map(10, 0, "a", 0, 100) + map.Map(0, 0, 10, "a", 100) + map.Map(100, 0, 10, 0, "a") + + + +# ClampMap tests + + +def test_clampmap_within_range(): + # Normal input: should act like Map + result = map.ClampMap(5, 0, 10, 0, 100) + assert almost_equal(result, 50) + + +def test_clampmap_above_max(): + # Input above maxInput should clamp to maxOutput + result = map.ClampMap(20, 0, 10, 0, 100) + assert almost_equal(result, 100) + + +def test_clampmap_below_min(): + # Input below minInput should clamp to minOutput + result = map.ClampMap(-10, 0, 10, 0, 100) + assert almost_equal(result, 0) + + +def test_clampmap_type_error(): + with pytest.raises(TypeError): + map.Map("a", 0, 10, 0, 100) + map.Map(0, "a", 10, 0, 100) + map.Map(10, 0, "a", 0, 100) + map.Map(0, 0, 10, "a", 100) + map.Map(100, 0, 10, 0, "a") + +# ClampMap tests + +def test_clamp_basic(): + assert map.Clamp(5, 0, 10) == 5 + assert map.Clamp(-1, 0, 10) == 0 + assert map.Clamp(15, 0, 10) == 10 + + +def test_clamp_min_only(): + assert map.Clamp(2, minOutput=5) == 5 + assert map.Clamp(10, minOutput=5) == 10 + + +def test_clamp_max_only(): + assert map.Clamp(12, maxOutput=10) == 10 + assert map.Clamp(3, maxOutput=10) == 3 + + +def test_clamp_no_limits(): + assert map.Clamp(7) == 7 + + +def test_clamp_type_error(): + with pytest.raises(TypeError): + map.Map("a", 0, 10) + map.Map(0, "a", 10) + map.Map(10, 0, "a") + \ No newline at end of file diff --git a/UnitTest/Suporting/test_csvReader.py b/UnitTest/Suporting/test_csvReader.py new file mode 100644 index 0000000..f8567ea --- /dev/null +++ b/UnitTest/Suporting/test_csvReader.py @@ -0,0 +1,90 @@ +import pytest +import tempfile +import os +from GabesPythonToolBox.Suporting.csvReader import * + + +# process_line tests + +def test_process_line_basic(): + result = process_line("1,2,3") + assert result == ["1", "2", "3"] + + +def test_process_line_empty_line(): + result = process_line(" ") + assert result == [] + + +def test_process_line_custom_separator(): + result = process_line("1;2;3", seperator_symbol=";") + assert result == ["1", "2", "3"] + + +def test_process_line_float_symbol_change(): + # Example where decimal uses ',' instead of '.' + result = process_line("1,23;4,56", seperator_symbol=";", float_symbol=",") + assert result == ["1.23", "4.56"] + + +def test_process_line_trims_spaces(): + result = process_line(" 1 , 2 , 3 ") + assert result == ["1", "2", "3"] + + + +# read_csv tests + + +def create_temp_csv(content: str): + """Helper to write a temporary CSV file and return its path.""" + tmp = tempfile.NamedTemporaryFile(delete=False, mode="w+", encoding="utf-8", suffix=".csv") + tmp.write(content) + tmp.flush() + tmp.close() + return tmp.name + + +def test_read_csv_all_rows(): + csv_content = "A,B,C\n1,2,3\n4,5,6\n" + path = create_temp_csv(csv_content) + + data = read_csv(path) + os.remove(path) + + assert len(data) == 2 + assert data[0] == {"A": "1", "B": "2", "C": "3"} + assert data[1] == {"A": "4", "B": "5", "C": "6"} + + +def test_read_csv_head(): + csv_content = "A,B\n1,2\n3,4\n5,6\n" + path = create_temp_csv(csv_content) + + data = read_csv(path, read_from="head", read_n_lines=1) + os.remove(path) + + assert len(data) == 1 + assert data[0] == {"A": "1", "B": "2"} + + +def test_read_csv_tail(): + csv_content = "A,B\n1,2\n3,4\n5,6\n" + path = create_temp_csv(csv_content) + + data = read_csv(path, read_from="tail", read_n_lines=1) + os.remove(path) + + assert len(data) == 1 + assert data[0] == {"A": "5", "B": "6"} + + +def test_read_csv_empty_file(): + csv_content = "" + path = create_temp_csv(csv_content) + + data = read_csv(path) + os.remove(path) + + assert data == [] # Expecting an empty list, not an exception + diff --git a/UnitTest/Suporting/test_csvWriter.py b/UnitTest/Suporting/test_csvWriter.py new file mode 100644 index 0000000..776ce67 --- /dev/null +++ b/UnitTest/Suporting/test_csvWriter.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Suporting import * +#TODO \ No newline at end of file diff --git a/UnitTest/Suporting/test_csvWriterLogger.py b/UnitTest/Suporting/test_csvWriterLogger.py new file mode 100644 index 0000000..776ce67 --- /dev/null +++ b/UnitTest/Suporting/test_csvWriterLogger.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Suporting import * +#TODO \ No newline at end of file diff --git a/UnitTest/Suporting/test_jsonReader.py b/UnitTest/Suporting/test_jsonReader.py new file mode 100644 index 0000000..776ce67 --- /dev/null +++ b/UnitTest/Suporting/test_jsonReader.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Suporting import * +#TODO \ No newline at end of file diff --git a/UnitTest/Suporting/test_jsonWriter.py b/UnitTest/Suporting/test_jsonWriter.py new file mode 100644 index 0000000..776ce67 --- /dev/null +++ b/UnitTest/Suporting/test_jsonWriter.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Suporting import * +#TODO \ No newline at end of file diff --git a/UnitTest/Suporting/test_yamlReader.py b/UnitTest/Suporting/test_yamlReader.py new file mode 100644 index 0000000..776ce67 --- /dev/null +++ b/UnitTest/Suporting/test_yamlReader.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Suporting import * +#TODO \ No newline at end of file diff --git a/UnitTest/Suporting/test_yamlWriter.py b/UnitTest/Suporting/test_yamlWriter.py new file mode 100644 index 0000000..776ce67 --- /dev/null +++ b/UnitTest/Suporting/test_yamlWriter.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Suporting import * +#TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_ConfigManager.py b/UnitTest/Utility/test_ConfigManager.py new file mode 100644 index 0000000..d002d71 --- /dev/null +++ b/UnitTest/Utility/test_ConfigManager.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Utility import * +#TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_Debug.py b/UnitTest/Utility/test_Debug.py new file mode 100644 index 0000000..d002d71 --- /dev/null +++ b/UnitTest/Utility/test_Debug.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Utility import * +#TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_DeltaTime.py b/UnitTest/Utility/test_DeltaTime.py new file mode 100644 index 0000000..d002d71 --- /dev/null +++ b/UnitTest/Utility/test_DeltaTime.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Utility import * +#TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_Logger.py b/UnitTest/Utility/test_Logger.py new file mode 100644 index 0000000..d002d71 --- /dev/null +++ b/UnitTest/Utility/test_Logger.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Utility import * +#TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_UnitConverter.py b/UnitTest/Utility/test_UnitConverter.py new file mode 100644 index 0000000..d002d71 --- /dev/null +++ b/UnitTest/Utility/test_UnitConverter.py @@ -0,0 +1,3 @@ +import pytest +from GabesPythonToolBox.Utility import * +#TODO \ No newline at end of file diff --git a/UnitTest/__init__.py b/UnitTest/__init__.py new file mode 100644 index 0000000..695c427 --- /dev/null +++ b/UnitTest/__init__.py @@ -0,0 +1,26 @@ +#Utility/ +#test_UnitConverter.py +#test_Logger.py +#test_DeltaTime.py +#test_Debug.py +#test_ConfigManager.py + +#Suporting/ +#test_yamlWriter.py +#test_yamlReader.py +#test_jsonWriter.py +#test_jsonReader.py +#test_csvWriterLogger.py +#test_csvWriter.py +#test_csvReader.py + + +#DataProsesing/ +#test_PID.py +#test_map.py +#test_filter.py +#test_DataRW.py +#test_DataPrettifyer.py +#test_DataHelperFunctions.py +#test_DataFormater.py +#test_DataConsolPrinter.py \ No newline at end of file diff --git a/setup.py b/setup.py index b96f798..53e18ad 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ install_requires=[ # List your dependencies here "matplotlib>=3.9.2,<3.10", "PyYAML>=6.0.2,<7.0", + "pytest>=8.4.2,<8.5", ], From 654c6eefbf908406284aaa69c2ad1f6a5adedb55 Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Wed, 8 Oct 2025 14:47:30 +0200 Subject: [PATCH 02/22] Started on test scrips to enchore that tehe code gives sensible outputs --- ChangeLog.md | 19 ++- README.md | 5 + Suporting/csvWriter.py | 15 +- Suporting/jsonWriter.py | 27 +++- Suporting/yamlWriter.py | 26 +++- UnitTest/DataProsesing/test_PID.py | 3 - UnitTest/DataProsesing/test_filter.py | 85 +++++++++- UnitTest/DataProsesing/test_map.py | 3 - UnitTest/Suporting/test_csvReader.py | 148 ++++++++---------- UnitTest/Suporting/test_csvWriter.py | 76 ++++++++- UnitTest/Suporting/test_csvWriterLogger.py | 3 - UnitTest/Suporting/test_jsonReader.py | 41 ++++- UnitTest/Suporting/test_jsonWriter.py | 53 ++++++- UnitTest/Suporting/test_yamlReader.py | 34 +++- UnitTest/Suporting/test_yamlWriter.py | 56 ++++++- UnitTest/Utility/test_ConfigManager.py | 173 ++++++++++++++++++++- Utility/ConfigManager.py | 1 - 17 files changed, 651 insertions(+), 117 deletions(-) delete mode 100644 UnitTest/Suporting/test_csvWriterLogger.py diff --git a/ChangeLog.md b/ChangeLog.md index 158a2c8..490967a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,23 @@ # Updates -# template +## 1.x.0 Test update + +### new things + +- Unit test + +### bug Fix + +- somthing + +### changes + +- yamlWriter now suports mode=a the right way +- Exsamlpe files now change name to exsample and not test + +### breaking changes + +- Exsample is no longer the main way of testing the code ## 1.7.1 PID Bug update diff --git a/README.md b/README.md index a70520e..f765871 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ using "import GabesPythonToolBox as GTB" for all or import "GabesPythonToolBox.Category.lib as (XX)" That’s it. You're good to go. +### Unit Test comand +''' +pytest UnitTest -v +''' + ## Current Features ### Utility Functions diff --git a/Suporting/csvWriter.py b/Suporting/csvWriter.py index 4136e9a..2fdcec0 100644 --- a/Suporting/csvWriter.py +++ b/Suporting/csvWriter.py @@ -1,8 +1,7 @@ -#from ..Utility.Debug import * -#import GabesPythonToolBox.Utility.Debug as Debug +from ..Utility.Debug import * import copy import csv -#from ..Utility.Debug import Debug + def write_csv( @@ -18,10 +17,10 @@ def write_csv( """ Write list-of-dictionaries data to a CSV file. """ - #Debug.log("write_csv", "Header", group="LIB") + Debug.log("write_csv", "Header", group="LIB") if not data or not isinstance(data, list) or not all(isinstance(d, dict) for d in data): - #Debug.log("Invalid data format for CSV writing", "Error", group="WarningError") + Debug.log("Invalid data format for CSV writing", "Error", group="WarningError") raise ValueError("Data must be a list of dictionaries.") # Make a deep copy to avoid modifying original @@ -67,10 +66,10 @@ def write_csv( } writer.writerow(formatted_row) - #Debug.log(f"CSV saved to {file_path}", "Info", group="LIB") + Debug.log(f"CSV saved to {file_path}", "Info", group="LIB") except Exception as e: - #Debug.log(f"Error writing CSV: {e}", "Error", group="WarningError") + Debug.log(f"Error writing CSV: {e}", "Error", group="WarningError") raise - #Debug.log("write_csv", "End", group="LIB") + Debug.log("write_csv", "End", group="LIB") diff --git a/Suporting/jsonWriter.py b/Suporting/jsonWriter.py index e34b084..0f19fbb 100644 --- a/Suporting/jsonWriter.py +++ b/Suporting/jsonWriter.py @@ -1,4 +1,27 @@ import json -def write_json(file_path,data,mode: str = 'w'): +import os + +def write_json(file_path, data, mode: str = 'w'): + """ + Write a dictionary to a JSON file. + If mode='a', merge with existing JSON content if it is a dict. + """ + if mode == 'a' and os.path.exists(file_path): + # Load existing data + with open(file_path, "r", encoding="utf-8") as f: + try: + existing_data = json.load(f) + except json.JSONDecodeError: + raise ValueError("Existing JSON content is invalid") + if not isinstance(existing_data, dict): + raise TypeError("Existing JSON content is not a dict; cannot merge") + + # Merge existing data with new data + existing_data.update(data) + data_to_write = existing_data + mode = 'w' # overwrite with merged content + else: + data_to_write = data + with open(file_path, mode=mode, encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) \ No newline at end of file + json.dump(data_to_write, f, indent=4, ensure_ascii=False) diff --git a/Suporting/yamlWriter.py b/Suporting/yamlWriter.py index 8a24c77..75803a8 100644 --- a/Suporting/yamlWriter.py +++ b/Suporting/yamlWriter.py @@ -1,4 +1,24 @@ import yaml -def write_yaml(file_path,data,mode: str = 'w'): - with open(file_path,mode=mode, encoding="utf-8") as f: - yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) \ No newline at end of file +import os + +def write_yaml(file_path, data, mode: str = 'w'): + """ + Write a dictionary to a YAML file. + If mode='a', merge with existing YAML content if it is a dict. + """ + if mode == 'a' and os.path.exists(file_path): + # Load existing data + with open(file_path, "r", encoding="utf-8") as f: + existing_data = yaml.safe_load(f) or {} + if not isinstance(existing_data, dict): + raise TypeError("Existing YAML content is not a dict; cannot merge") + + # Merge existing data with new data + existing_data.update(data) + data_to_write = existing_data + mode = 'w' # Overwrite file with merged content + else: + data_to_write = data + + with open(file_path, mode=mode, encoding="utf-8") as f: + yaml.safe_dump(data_to_write, f, sort_keys=False, allow_unicode=True) diff --git a/UnitTest/DataProsesing/test_PID.py b/UnitTest/DataProsesing/test_PID.py index f99cf69..518dd2d 100644 --- a/UnitTest/DataProsesing/test_PID.py +++ b/UnitTest/DataProsesing/test_PID.py @@ -1,11 +1,8 @@ import pytest from GabesPythonToolBox.DataProsesing.PID import * - - # Helper functions - def almost_equal(a, b, tol=1e-6): """Helper to compare floating point values.""" return abs(a - b) < tol diff --git a/UnitTest/DataProsesing/test_filter.py b/UnitTest/DataProsesing/test_filter.py index 7372c6a..9b8668e 100644 --- a/UnitTest/DataProsesing/test_filter.py +++ b/UnitTest/DataProsesing/test_filter.py @@ -1,3 +1,84 @@ import pytest -from GabesPythonToolBox.DataProsesing import * -#TODO \ No newline at end of file +from GabesPythonToolBox.DataProsesing.filter import Filter, newFilter + + +# Supporting funcs +def generate_sequence(n): + """Generate a simple increasing numeric sequence.""" + return [float(i + 1) for i in range(n)] + +def almost_equal(a, b, tol=1e-6): + return abs(a - b) < tol + +# Tests + +def test_average_filter_basic(): + f = Filter(num_points=3, filter_type="average") + inputs = generate_sequence(3) + outputs = [f(x) for x in inputs] + + # After all inputs, average should be (1 + 2 + 3) / 3 = 2 + assert pytest.approx(outputs[-1], 0.0001) == 2.0 + + +def test_average_filter_window_limit(): + f = Filter(num_points=3, filter_type="average") + # Add more than 3 points to ensure oldest values are dropped + val = generate_sequence(5) + for x in val: + out = f(x) + # Only last 3 points (3,4,5) should remain, avg = 4 + assert almost_equal(out,4) + + +def test_new_weighted_filter(): + f = Filter(num_points=3, filter_type="newWeighted") + for val in [10, 20, 30]: + out = f(val) + # Weighted average (1*10 + 2*20 + 3*30) / 6 = 23.33... + assert almost_equal(out,23.3333333333) + + +def test_old_weighted_filter(): + f = Filter(num_points=3, filter_type="oldWeighted") + for val in [10, 20, 30]: + out = f(val) + # Weighted average (3*10 + 2*20 + 1*30) / 6 = 16.67... + assert almost_equal(out,16.666666666) + + +def test_invalid_input_type_raises(): + f = Filter(num_points=3) + with pytest.raises(TypeError): + f("invalid") # not numeric + + +def test_unsupported_filter_type_raises(): + f = Filter(num_points=3, filter_type="unknownType") + with pytest.raises(ValueError): + f(5) + + +def test_empty_filter_returns_zero(): + f = Filter(num_points=3) + # Manually clear data_points to simulate empty state + f.data_points = [] + result = f(1) # adds one, should average correctly + assert isinstance(result, float) + + +def test_window_behavior_pop(): + f = Filter(num_points=3) + f(1) + f(2) + f(3) + f(4) # should pop oldest + assert len(f.data_points) == 3 + assert f.data_points == [2, 3, 4] + + +def test_newFilter_factory_creates_instance(): + f = newFilter(5, "average") + assert isinstance(f, Filter) + assert f.num_points == 5 + assert f.filter_type == "average" diff --git a/UnitTest/DataProsesing/test_map.py b/UnitTest/DataProsesing/test_map.py index c235083..04b83af 100644 --- a/UnitTest/DataProsesing/test_map.py +++ b/UnitTest/DataProsesing/test_map.py @@ -4,11 +4,8 @@ def almost_equal(a, b, tol=1e-6): return abs(a - b) < tol - - # Map tests - def test_map_basic_linear_mapping(): # Simple case: map 5 from range 0–10 → 0–100 result = map.Map(5, 0, 10, 0, 100) diff --git a/UnitTest/Suporting/test_csvReader.py b/UnitTest/Suporting/test_csvReader.py index f8567ea..081a0b6 100644 --- a/UnitTest/Suporting/test_csvReader.py +++ b/UnitTest/Suporting/test_csvReader.py @@ -1,90 +1,80 @@ import pytest -import tempfile +from GabesPythonToolBox.Suporting.csvReader import read_csv, process_line import os -from GabesPythonToolBox.Suporting.csvReader import * - - -# process_line tests +# Supporting func +def write_csv_for_test(file_path, lines): + """Helper function to write raw CSV lines for testing.""" + with open(file_path, "w", encoding="utf-8") as f: + for line in lines: + f.write(line + "\n") + +# Sample CSV content +csv_lines = [ + "name,age,score", + "Alice,30,95.5", + "Bob,25,88.2", + "Charlie,28,91.0", +] + +# Tests def test_process_line_basic(): - result = process_line("1,2,3") - assert result == ["1", "2", "3"] - + line = "Alice,30,95.5" + result = process_line(line) + assert result == ["Alice", "30", "95.5"] def test_process_line_empty_line(): result = process_line(" ") assert result == [] - -def test_process_line_custom_separator(): - result = process_line("1;2;3", seperator_symbol=";") - assert result == ["1", "2", "3"] - - -def test_process_line_float_symbol_change(): - # Example where decimal uses ',' instead of '.' - result = process_line("1,23;4,56", seperator_symbol=";", float_symbol=",") - assert result == ["1.23", "4.56"] - - -def test_process_line_trims_spaces(): - result = process_line(" 1 , 2 , 3 ") - assert result == ["1", "2", "3"] - - - -# read_csv tests - - -def create_temp_csv(content: str): - """Helper to write a temporary CSV file and return its path.""" - tmp = tempfile.NamedTemporaryFile(delete=False, mode="w+", encoding="utf-8", suffix=".csv") - tmp.write(content) - tmp.flush() - tmp.close() - return tmp.name - - -def test_read_csv_all_rows(): - csv_content = "A,B,C\n1,2,3\n4,5,6\n" - path = create_temp_csv(csv_content) - - data = read_csv(path) - os.remove(path) - +def test_process_line_custom_separators_and_floats(): + line = "Alice;30;95,5" + result = process_line(line, seperator_symbol=";", float_symbol=",") + assert result == ["Alice", "30", "95.5"] + +def test_read_csv_all_lines(tmp_path): + test_file = tmp_path / "test.csv" + write_csv_for_test(test_file, csv_lines) + + data = read_csv(test_file) + assert len(data) == 3 + assert data[0]["name"] == "Alice" + assert data[1]["score"] == "88.2" + +def test_read_csv_head_lines(tmp_path): + test_file = tmp_path / "test.csv" + write_csv_for_test(test_file, csv_lines) + + data = read_csv(test_file, read_from="head", read_n_lines=2) assert len(data) == 2 - assert data[0] == {"A": "1", "B": "2", "C": "3"} - assert data[1] == {"A": "4", "B": "5", "C": "6"} - - -def test_read_csv_head(): - csv_content = "A,B\n1,2\n3,4\n5,6\n" - path = create_temp_csv(csv_content) - - data = read_csv(path, read_from="head", read_n_lines=1) - os.remove(path) - - assert len(data) == 1 - assert data[0] == {"A": "1", "B": "2"} - - -def test_read_csv_tail(): - csv_content = "A,B\n1,2\n3,4\n5,6\n" - path = create_temp_csv(csv_content) - - data = read_csv(path, read_from="tail", read_n_lines=1) - os.remove(path) - - assert len(data) == 1 - assert data[0] == {"A": "5", "B": "6"} - - -def test_read_csv_empty_file(): - csv_content = "" - path = create_temp_csv(csv_content) - - data = read_csv(path) - os.remove(path) - - assert data == [] # Expecting an empty list, not an exception + assert data[0]["name"] == "Alice" +def test_read_csv_tail_lines(tmp_path): + test_file = tmp_path / "test.csv" + write_csv_for_test(test_file, csv_lines) + + data = read_csv(test_file, read_from="tail", read_n_lines=2) + assert len(data) == 2 + assert data[0]["name"] == "Bob" + assert data[1]["name"] == "Charlie" + +def test_read_csv_custom_separator_and_float(tmp_path): + test_file = tmp_path / "test.csv" + custom_lines = [ + "name;age;score", + "Alice;30;95,5", + "Bob;25;88,2" + ] + write_csv_for_test(test_file, custom_lines) + + data = read_csv(test_file, seperator_symbol=";", float_symbol=",") + assert data[0]["score"] == "95.5" + assert data[1]["score"] == "88.2" + +def test_read_csv_empty_file(tmp_path): + test_file = tmp_path / "empty.csv" + write_csv_for_test(test_file, []) + + # Should return empty list + data = read_csv(test_file) + assert data == [] diff --git a/UnitTest/Suporting/test_csvWriter.py b/UnitTest/Suporting/test_csvWriter.py index 776ce67..4ecc945 100644 --- a/UnitTest/Suporting/test_csvWriter.py +++ b/UnitTest/Suporting/test_csvWriter.py @@ -1,3 +1,75 @@ import pytest -from GabesPythonToolBox.Suporting import * -#TODO \ No newline at end of file +from GabesPythonToolBox.Suporting.csvWriter import write_csv +import csv +import os + +# Supporting func +def read_csv(file_path, delimiter=','): + """Helper to read CSV back into list-of-dicts for testing.""" + with open(file_path, newline="", encoding="utf-8") as f: + reader = csv.DictReader(f, delimiter=delimiter) + return list(reader) + +# Sample test data +sample_data = [ + {"name": "Alice", "age": 30, "score": 95.5}, + {"name": "Bob", "age": 25, "score": 88.2}, +] + +# Tests +def test_write_csv_creates_file(tmp_path): + test_file = tmp_path / "test.csv" + write_csv(test_file, sample_data) + assert test_file.exists() + +def test_write_csv_headers_and_body(tmp_path): + test_file = tmp_path / "test.csv" + write_csv(test_file, sample_data, data_mode="all") + loaded = read_csv(test_file) + # Check first row content + assert loaded[0]["name"] == "Alice" + assert loaded[1]["score"] == "88.2" # default float symbol + +def test_write_csv_float_symbol(tmp_path): + test_file = tmp_path / "test.csv" + write_csv(test_file, sample_data, float_symbol=",") + loaded = read_csv(test_file) + # Floats should have ',' instead of '.' + assert loaded[0]["score"] == "95,5" + assert loaded[1]["score"] == "88,2" + +def test_write_csv_data_modes(tmp_path): + test_file_all = tmp_path / "all.csv" + test_file_head = tmp_path / "head.csv" + test_file_body = tmp_path / "body.csv" + test_file_none = tmp_path / "none.csv" + + write_csv(test_file_all, sample_data, data_mode="all") + write_csv(test_file_head, sample_data, data_mode="head") + write_csv(test_file_body, sample_data, data_mode="body") + write_csv(test_file_none, sample_data, data_mode="none") + + # all: header + body + loaded_all = read_csv(test_file_all) + assert len(loaded_all) == 2 + # head: no body + with open(test_file_head, encoding="utf-8") as f: + content_head = f.read() + assert "Alice" not in content_head + # body: no header + with open(test_file_body, encoding="utf-8") as f: + content_body = f.read() + assert "name,age,score" not in content_body + # none: nothing written + with open(test_file_none, encoding="utf-8") as f: + content_none = f.read() + assert content_none.strip() == "" + +def test_write_csv_invalid_data(tmp_path): + test_file = tmp_path / "test.csv" + with pytest.raises(ValueError): + write_csv(test_file, {"not": "a list"}) + with pytest.raises(ValueError): + write_csv(test_file, []) + with pytest.raises(ValueError): + write_csv(test_file, [{"valid": 1}, "invalid row"]) diff --git a/UnitTest/Suporting/test_csvWriterLogger.py b/UnitTest/Suporting/test_csvWriterLogger.py deleted file mode 100644 index 776ce67..0000000 --- a/UnitTest/Suporting/test_csvWriterLogger.py +++ /dev/null @@ -1,3 +0,0 @@ -import pytest -from GabesPythonToolBox.Suporting import * -#TODO \ No newline at end of file diff --git a/UnitTest/Suporting/test_jsonReader.py b/UnitTest/Suporting/test_jsonReader.py index 776ce67..ad789bb 100644 --- a/UnitTest/Suporting/test_jsonReader.py +++ b/UnitTest/Suporting/test_jsonReader.py @@ -1,3 +1,40 @@ import pytest -from GabesPythonToolBox.Suporting import * -#TODO \ No newline at end of file +from GabesPythonToolBox.Suporting.jsonReader import read_json +import json + +# Supporting func to help the test +def write_json_for_test(file_path, data): + """Helper function to create JSON files for testing read_json.""" + with open(file_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4, ensure_ascii=False) + +# Tests +def test_read_json_basic(tmp_path): + test_file = tmp_path / "test.json" + data = {"key": "value"} + write_json_for_test(test_file, data) + + loaded = read_json(test_file) + assert loaded == data + +def test_read_json_with_unicode(tmp_path): + test_file = tmp_path / "test.json" + data = {"greeting": "こんにちは", "key": "üñîçødë"} + write_json_for_test(test_file, data) + + loaded = read_json(test_file, encoding="utf-8") + assert loaded == data + +def test_read_json_nonexistent_file_raises(tmp_path): + test_file = tmp_path / "missing.json" + + with pytest.raises(FileNotFoundError): + read_json(test_file) + +def test_read_json_invalid_json_raises(tmp_path): + test_file = tmp_path / "invalid.json" + with open(test_file, "w", encoding="utf-8") as f: + f.write("invalid json content") + + with pytest.raises(json.JSONDecodeError): + read_json(test_file) diff --git a/UnitTest/Suporting/test_jsonWriter.py b/UnitTest/Suporting/test_jsonWriter.py index 776ce67..8da7c56 100644 --- a/UnitTest/Suporting/test_jsonWriter.py +++ b/UnitTest/Suporting/test_jsonWriter.py @@ -1,3 +1,52 @@ import pytest -from GabesPythonToolBox.Suporting import * -#TODO \ No newline at end of file +from GabesPythonToolBox.Suporting.jsonWriter import write_json +import json + +# Supporting func +def read_json(file_path): + with open(file_path, "r", encoding="utf-8") as f: + return json.load(f) + +# Tests +def test_write_json_creates_file(tmp_path): + test_file = tmp_path / "test.json" + data = {"key": "value"} + write_json(test_file, data) + assert test_file.exists() + assert read_json(test_file) == data + +def test_write_json_overwrites_file(tmp_path): + test_file = tmp_path / "test.json" + write_json(test_file, {"old": "data"}) + write_json(test_file, {"new": "data"}) + assert read_json(test_file) == {"new": "data"} + +def test_write_json_merge_append(tmp_path): + test_file = tmp_path / "test.json" + write_json(test_file, {"a": 1, "b": 2}) + write_json(test_file, {"b": 3, "c": 4}, mode='a') # merge dictionaries + expected = {"a": 1, "b": 3, "c": 4} + assert read_json(test_file) == expected + +def test_write_json_append_non_dict_raises(tmp_path): + test_file = tmp_path / "test.json" + with open(test_file, "w", encoding="utf-8") as f: + f.write(json.dumps(["not", "a", "dict"])) + + with pytest.raises(TypeError): + write_json(test_file, {"new": "data"}, mode='a') + +def test_write_json_append_invalid_json_raises(tmp_path): + test_file = tmp_path / "test.json" + with open(test_file, "w", encoding="utf-8") as f: + f.write("invalid json") + + with pytest.raises(ValueError): + write_json(test_file, {"new": "data"}, mode='a') + +def test_write_json_unicode_support(tmp_path): + test_file = tmp_path / "test.json" + data = {"greeting": "こんにちは", "key": "üñîçødë"} + write_json(test_file, data) + loaded = read_json(test_file) + assert loaded == data diff --git a/UnitTest/Suporting/test_yamlReader.py b/UnitTest/Suporting/test_yamlReader.py index 776ce67..d450352 100644 --- a/UnitTest/Suporting/test_yamlReader.py +++ b/UnitTest/Suporting/test_yamlReader.py @@ -1,3 +1,33 @@ import pytest -from GabesPythonToolBox.Suporting import * -#TODO \ No newline at end of file +from GabesPythonToolBox.Suporting.yamlReader import read_ymal +import yaml +import os + +# Supporting func to help the test +def write_yaml_for_test(file_path, data): + """Helper to write YAML files for testing read function.""" + with open(file_path, "w", encoding="utf-8") as f: + yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) + +# Tests +def test_read_ymal_reads_correctly(tmp_path): + test_file = tmp_path / "test.yaml" + data = {"key": "value"} + write_yaml_for_test(test_file, data) + + loaded = read_ymal(test_file) + assert loaded == data + +def test_read_ymal_with_different_encoding(tmp_path): + test_file = tmp_path / "test.yaml" + data = {"key": "üñîçødë"} + write_yaml_for_test(test_file, data) + + loaded = read_ymal(test_file, encoding="utf-8") + assert loaded == data + +def test_read_ymal_nonexistent_file_raises(tmp_path): + test_file = tmp_path / "missing.yaml" + + with pytest.raises(FileNotFoundError): + read_ymal(test_file) diff --git a/UnitTest/Suporting/test_yamlWriter.py b/UnitTest/Suporting/test_yamlWriter.py index 776ce67..c01829e 100644 --- a/UnitTest/Suporting/test_yamlWriter.py +++ b/UnitTest/Suporting/test_yamlWriter.py @@ -1,3 +1,55 @@ import pytest -from GabesPythonToolBox.Suporting import * -#TODO \ No newline at end of file +from GabesPythonToolBox.Suporting.yamlWriter import write_yaml +import os +import yaml + +# Supporting func to help the test +def read_yaml(file_path): + """Helper function to read YAML for test verification.""" + with open(file_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + +# Tests +def test_write_yaml_creates_file(tmp_path): + test_file = tmp_path / "test.yaml" + data = {"key": "value"} + + write_yaml(test_file, data) + + assert test_file.exists() + assert read_yaml(test_file) == data + +def test_write_yaml_overwrites_file(tmp_path): + test_file = tmp_path / "test.yaml" + initial_data = {"old": "data"} + write_yaml(test_file, initial_data) + + new_data = {"new": "data"} + write_yaml(test_file, new_data) + + assert read_yaml(test_file) == new_data + +def test_write_yaml_merge_append(tmp_path): + test_file = tmp_path / "test.yaml" + + # Initial data + data1 = {"a": 1, "b": 2} + write_yaml(test_file, data1) + + # New data to append (merge) + data2 = {"b": 3, "c": 4} # 'b' should be overwritten + write_yaml(test_file, data2, mode='a') + + expected = {"a": 1, "b": 3, "c": 4} + assert read_yaml(test_file) == expected + +def test_write_yaml_append_non_dict_raises(tmp_path): + test_file = tmp_path / "test.yaml" + + # Write non-dict content + with open(test_file, "w", encoding="utf-8") as f: + f.write("Just a string") + + # Trying to append dict should raise TypeError + with pytest.raises(TypeError): + write_yaml(test_file, {"new": "data"}, mode='a') diff --git a/UnitTest/Utility/test_ConfigManager.py b/UnitTest/Utility/test_ConfigManager.py index d002d71..94e1375 100644 --- a/UnitTest/Utility/test_ConfigManager.py +++ b/UnitTest/Utility/test_ConfigManager.py @@ -1,3 +1,172 @@ import pytest -from GabesPythonToolBox.Utility import * -#TODO \ No newline at end of file +import os +import json +import yaml +import GabesPythonToolBox.Utility.ConfigManager as CM + +# Helper Functions + +def write_config(file_path, data): + """Write a JSON or YAML file for testing.""" + ext = os.path.splitext(file_path)[1].lower() + with open(file_path, "w", encoding="utf-8") as f: + if ext == ".json": + json.dump(data, f, indent=4) + elif ext in [".yaml", ".yml"]: + yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) + else: + raise ValueError("Unsupported test config type") + +def read_json(file_path): + with open(file_path, "r", encoding="utf-8") as f: + return json.load(f) + +def read_yaml(file_path): + with open(file_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + + + +# University-Inspired Sample Config + + +SAMPLE_CONFIG = { + "university": { + "name": "GTB University", + "department": "Robotics Engineering" + }, + "students": [ + {"id": 1, "name": "Alice", "project": "Autonomous Rover"}, + {"id": 2, "name": "Bob", "project": "Drone Swarm Control"} + ], + "projects": { + "active": [ + {"title": "Autonomous Rover", "progress": 65, "supervisor": "Dr. Grey"}, + {"title": "Drone Swarm Control", "progress": 40, "supervisor": "Dr. Lin"} + ], + "completed": [] + }, + "lab_settings": { + "open_hours": "08:00-18:00", + "max_students": 10, + "equipment": { + "3D_printers": 2, + "robots": ["Atlas", "Spot", "Rover"] + } + } +} + + + +#Core Functionality Tests + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_load_and_call(tmp_path, ext): + file = tmp_path / f"lab_config{ext}" + write_config(file, SAMPLE_CONFIG) + + cm = CM.ConfigManager(str(file)) + config = cm() + assert config["university"]["name"] == "GTB University" + assert config["students"][0]["name"] == "Alice" + assert isinstance(config, dict) + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_update_nested_and_indexed_keys(tmp_path, ext): + file = tmp_path / f"update_test{ext}" + write_config(file, SAMPLE_CONFIG) + cm = CM.ConfigManager(str(file)) + + # Update simple nested key + cm.update("university.department", "Artificial Intelligence") + assert cm.config["university"]["department"] == "Artificial Intelligence" + + # Update list item by index + cm.update("students[1].project", "AI Ethics Research") + assert cm.config["students"][1]["project"] == "AI Ethics Research" + + # Update deeply nested dictionary key + cm.update("lab_settings.equipment.3D_printers", 3) + assert cm.config["lab_settings"]["equipment"]["3D_printers"] == 3 + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_save_and_reload_same_format(tmp_path, ext): + file = tmp_path / f"save_test{ext}" + write_config(file, SAMPLE_CONFIG) + cm = CM.ConfigManager(str(file)) + + cm.update("projects.active[0].progress", 90) + new_file = tmp_path / f"saved{ext}" + cm.save(str(new_file)) + assert new_file.exists() + + reloaded = CM.ConfigManager(str(new_file))() + assert reloaded["projects"]["active"][0]["progress"] == 90 + + +@pytest.mark.parametrize("ext_from, ext_to", [ + (".json", ".yaml"), + (".yaml", ".json"), + (".yml", ".json"), +]) +def test_cross_format_save(tmp_path, ext_from, ext_to): + file = tmp_path / f"cross{ext_from}" + write_config(file, SAMPLE_CONFIG) + cm = CM.ConfigManager(str(file)) + + cm.update("university.name", "Techville Institute of Robotics") + out_file = tmp_path / f"converted{ext_to}" + cm.save(str(out_file)) + + assert out_file.exists() + loaded = read_json(out_file) if ext_to == ".json" else read_yaml(out_file) + assert loaded["university"]["name"] == "Techville Institute of Robotics" + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_reset_to_original(tmp_path, ext): + file = tmp_path / f"reset_test{ext}" + write_config(file, SAMPLE_CONFIG) + cm = CM.ConfigManager(str(file)) + + cm.update("students[0].name", "Charlie") + assert cm.config["students"][0]["name"] == "Charlie" + + cm.reset() + assert cm.config["students"][0]["name"] == "Alice" + + +@pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) +def test_start_config_manager_factory(tmp_path, ext): + file = tmp_path / f"factory_test{ext}" + write_config(file, SAMPLE_CONFIG) + + cm = CM.startConfigManager(str(file)) + assert isinstance(cm, CM.ConfigManager) + assert cm()["projects"]["active"][1]["title"] == "Drone Swarm Control" + + + +#Error Handling Tests + + +def test_missing_file_raises(tmp_path): + missing = tmp_path / "not_found.json" + with pytest.raises(FileNotFoundError): + CM.ConfigManager(str(missing)) + + +def test_invalid_extension_raises(tmp_path): + bad_file = tmp_path / "config.txt" + bad_file.write_text("not a valid format", encoding="utf-8") + with pytest.raises(ValueError): + CM.ConfigManager(str(bad_file)) + + +def test_invalid_json_content_raises(tmp_path): + bad_file = tmp_path / "broken.json" + bad_file.write_text("{ bad json data", encoding="utf-8") + with pytest.raises(Exception): + CM.ConfigManager(str(bad_file)) \ No newline at end of file diff --git a/Utility/ConfigManager.py b/Utility/ConfigManager.py index 4b372a7..76b14df 100644 --- a/Utility/ConfigManager.py +++ b/Utility/ConfigManager.py @@ -2,7 +2,6 @@ import yaml import os import copy - from ..Utility.Debug import * from ..DataProsesing.DataRW import * From 19225831c94e0c0fdb5b0c2205cb5bf12f263333 Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 14 Oct 2025 11:37:07 +0200 Subject: [PATCH 03/22] suprting scripts updated --- ChangeLog.md | 2 + .../DataProsesing/test_DataConsolPrinter.py | 1 + UnitTest/DataProsesing/test_DataFormater.py | 1 + .../DataProsesing/test_DataHelperFunctions.py | 1 + UnitTest/DataProsesing/test_DataPrettifyer.py | 1 + UnitTest/DataProsesing/test_DataRW.py | 1 + UnitTest/DataProsesing/test_PID.py | 7 +- UnitTest/DataProsesing/test_filter.py | 13 +-- UnitTest/DataProsesing/test_map.py | 4 +- UnitTest/Suporting/test_csvReader.py | 8 +- UnitTest/Suporting/test_csvWriter.py | 24 ++--- UnitTest/Suporting/test_jsonReader.py | 19 ++-- UnitTest/Suporting/test_jsonWriter.py | 31 +++--- UnitTest/Suporting/test_yamlReader.py | 18 ++-- UnitTest/Suporting/test_yamlWriter.py | 37 +++---- UnitTest/UnitTestComon/UntTestUtility.py | 100 ++++++++++++++++++ UnitTest/Utility/test_ConfigManager.py | 69 ++---------- UnitTest/Utility/test_Debug.py | 1 + UnitTest/Utility/test_DeltaTime.py | 6 +- UnitTest/Utility/test_Logger.py | 1 + UnitTest/Utility/test_UnitConverter.py | 1 + __init__.py | 8 ++ requirements.in | Bin 0 -> 274 bytes 23 files changed, 184 insertions(+), 170 deletions(-) create mode 100644 UnitTest/UnitTestComon/UntTestUtility.py create mode 100644 requirements.in diff --git a/ChangeLog.md b/ChangeLog.md index 490967a..4072286 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,7 @@ ### new things - Unit test +- UnitTestUtilities.py ### bug Fix @@ -14,6 +15,7 @@ - yamlWriter now suports mode=a the right way - Exsamlpe files now change name to exsample and not test +- all suporting script tests uptated to use the test util ### breaking changes diff --git a/UnitTest/DataProsesing/test_DataConsolPrinter.py b/UnitTest/DataProsesing/test_DataConsolPrinter.py index 7372c6a..6c11985 100644 --- a/UnitTest/DataProsesing/test_DataConsolPrinter.py +++ b/UnitTest/DataProsesing/test_DataConsolPrinter.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataFormater.py b/UnitTest/DataProsesing/test_DataFormater.py index 7372c6a..6c11985 100644 --- a/UnitTest/DataProsesing/test_DataFormater.py +++ b/UnitTest/DataProsesing/test_DataFormater.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataHelperFunctions.py b/UnitTest/DataProsesing/test_DataHelperFunctions.py index 7372c6a..6c11985 100644 --- a/UnitTest/DataProsesing/test_DataHelperFunctions.py +++ b/UnitTest/DataProsesing/test_DataHelperFunctions.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataPrettifyer.py b/UnitTest/DataProsesing/test_DataPrettifyer.py index 7372c6a..6c11985 100644 --- a/UnitTest/DataProsesing/test_DataPrettifyer.py +++ b/UnitTest/DataProsesing/test_DataPrettifyer.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_DataRW.py b/UnitTest/DataProsesing/test_DataRW.py index 7372c6a..6c11985 100644 --- a/UnitTest/DataProsesing/test_DataRW.py +++ b/UnitTest/DataProsesing/test_DataRW.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.DataProsesing import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/DataProsesing/test_PID.py b/UnitTest/DataProsesing/test_PID.py index 518dd2d..9f9a08c 100644 --- a/UnitTest/DataProsesing/test_PID.py +++ b/UnitTest/DataProsesing/test_PID.py @@ -1,11 +1,6 @@ import pytest from GabesPythonToolBox.DataProsesing.PID import * - -# Helper functions - -def almost_equal(a, b, tol=1e-6): - """Helper to compare floating point values.""" - return abs(a - b) < tol +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import almost_equal def test_pid_proportional_only(): diff --git a/UnitTest/DataProsesing/test_filter.py b/UnitTest/DataProsesing/test_filter.py index 9b8668e..2d1d515 100644 --- a/UnitTest/DataProsesing/test_filter.py +++ b/UnitTest/DataProsesing/test_filter.py @@ -1,20 +1,13 @@ import pytest from GabesPythonToolBox.DataProsesing.filter import Filter, newFilter +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import almost_equal,Generate_sequence -# Supporting funcs -def generate_sequence(n): - """Generate a simple increasing numeric sequence.""" - return [float(i + 1) for i in range(n)] - -def almost_equal(a, b, tol=1e-6): - return abs(a - b) < tol - # Tests def test_average_filter_basic(): f = Filter(num_points=3, filter_type="average") - inputs = generate_sequence(3) + inputs = Generate_sequence(3) outputs = [f(x) for x in inputs] # After all inputs, average should be (1 + 2 + 3) / 3 = 2 @@ -24,7 +17,7 @@ def test_average_filter_basic(): def test_average_filter_window_limit(): f = Filter(num_points=3, filter_type="average") # Add more than 3 points to ensure oldest values are dropped - val = generate_sequence(5) + val = Generate_sequence(5) for x in val: out = f(x) # Only last 3 points (3,4,5) should remain, avg = 4 diff --git a/UnitTest/DataProsesing/test_map.py b/UnitTest/DataProsesing/test_map.py index 04b83af..926af5a 100644 --- a/UnitTest/DataProsesing/test_map.py +++ b/UnitTest/DataProsesing/test_map.py @@ -1,8 +1,6 @@ import pytest import GabesPythonToolBox.DataProsesing.map as map - -def almost_equal(a, b, tol=1e-6): - return abs(a - b) < tol +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import almost_equal # Map tests diff --git a/UnitTest/Suporting/test_csvReader.py b/UnitTest/Suporting/test_csvReader.py index 081a0b6..319532d 100644 --- a/UnitTest/Suporting/test_csvReader.py +++ b/UnitTest/Suporting/test_csvReader.py @@ -1,13 +1,9 @@ import pytest from GabesPythonToolBox.Suporting.csvReader import read_csv, process_line -import os +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import write_csv_for_test # Supporting func -def write_csv_for_test(file_path, lines): - """Helper function to write raw CSV lines for testing.""" - with open(file_path, "w", encoding="utf-8") as f: - for line in lines: - f.write(line + "\n") + # Sample CSV content csv_lines = [ diff --git a/UnitTest/Suporting/test_csvWriter.py b/UnitTest/Suporting/test_csvWriter.py index 4ecc945..4034844 100644 --- a/UnitTest/Suporting/test_csvWriter.py +++ b/UnitTest/Suporting/test_csvWriter.py @@ -1,20 +1,6 @@ import pytest from GabesPythonToolBox.Suporting.csvWriter import write_csv -import csv -import os - -# Supporting func -def read_csv(file_path, delimiter=','): - """Helper to read CSV back into list-of-dicts for testing.""" - with open(file_path, newline="", encoding="utf-8") as f: - reader = csv.DictReader(f, delimiter=delimiter) - return list(reader) - -# Sample test data -sample_data = [ - {"name": "Alice", "age": 30, "score": 95.5}, - {"name": "Bob", "age": 25, "score": 88.2}, -] +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import read_csv_test,sample_data # Tests def test_write_csv_creates_file(tmp_path): @@ -25,7 +11,7 @@ def test_write_csv_creates_file(tmp_path): def test_write_csv_headers_and_body(tmp_path): test_file = tmp_path / "test.csv" write_csv(test_file, sample_data, data_mode="all") - loaded = read_csv(test_file) + loaded = read_csv_test(test_file) # Check first row content assert loaded[0]["name"] == "Alice" assert loaded[1]["score"] == "88.2" # default float symbol @@ -33,7 +19,7 @@ def test_write_csv_headers_and_body(tmp_path): def test_write_csv_float_symbol(tmp_path): test_file = tmp_path / "test.csv" write_csv(test_file, sample_data, float_symbol=",") - loaded = read_csv(test_file) + loaded = read_csv_test(test_file) # Floats should have ',' instead of '.' assert loaded[0]["score"] == "95,5" assert loaded[1]["score"] == "88,2" @@ -50,7 +36,7 @@ def test_write_csv_data_modes(tmp_path): write_csv(test_file_none, sample_data, data_mode="none") # all: header + body - loaded_all = read_csv(test_file_all) + loaded_all = read_csv_test(test_file_all) assert len(loaded_all) == 2 # head: no body with open(test_file_head, encoding="utf-8") as f: @@ -65,6 +51,8 @@ def test_write_csv_data_modes(tmp_path): content_none = f.read() assert content_none.strip() == "" +#TODO write UTF8 data + def test_write_csv_invalid_data(tmp_path): test_file = tmp_path / "test.csv" with pytest.raises(ValueError): diff --git a/UnitTest/Suporting/test_jsonReader.py b/UnitTest/Suporting/test_jsonReader.py index ad789bb..804505b 100644 --- a/UnitTest/Suporting/test_jsonReader.py +++ b/UnitTest/Suporting/test_jsonReader.py @@ -1,29 +1,22 @@ import pytest from GabesPythonToolBox.Suporting.jsonReader import read_json -import json +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import write_json_test,sample_data,Complex_data -# Supporting func to help the test -def write_json_for_test(file_path, data): - """Helper function to create JSON files for testing read_json.""" - with open(file_path, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4, ensure_ascii=False) +import json # Tests def test_read_json_basic(tmp_path): test_file = tmp_path / "test.json" - data = {"key": "value"} - write_json_for_test(test_file, data) - + write_json_test(test_file, sample_data) loaded = read_json(test_file) - assert loaded == data + assert loaded == sample_data def test_read_json_with_unicode(tmp_path): test_file = tmp_path / "test.json" - data = {"greeting": "こんにちは", "key": "üñîçødë"} - write_json_for_test(test_file, data) + write_json_test(test_file, Complex_data) loaded = read_json(test_file, encoding="utf-8") - assert loaded == data + assert loaded == Complex_data def test_read_json_nonexistent_file_raises(tmp_path): test_file = tmp_path / "missing.json" diff --git a/UnitTest/Suporting/test_jsonWriter.py b/UnitTest/Suporting/test_jsonWriter.py index 8da7c56..ea740e3 100644 --- a/UnitTest/Suporting/test_jsonWriter.py +++ b/UnitTest/Suporting/test_jsonWriter.py @@ -1,32 +1,31 @@ import pytest from GabesPythonToolBox.Suporting.jsonWriter import write_json import json - -# Supporting func -def read_json(file_path): - with open(file_path, "r", encoding="utf-8") as f: - return json.load(f) +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import read_json_test,sample_data,Complex_data,New_data # Tests def test_write_json_creates_file(tmp_path): test_file = tmp_path / "test.json" data = {"key": "value"} - write_json(test_file, data) + write_json(test_file, sample_data) assert test_file.exists() - assert read_json(test_file) == data + assert read_json_test(test_file) == sample_data def test_write_json_overwrites_file(tmp_path): test_file = tmp_path / "test.json" - write_json(test_file, {"old": "data"}) - write_json(test_file, {"new": "data"}) - assert read_json(test_file) == {"new": "data"} + write_json(test_file, sample_data) + write_json(test_file, New_data) + assert read_json_test(test_file) == New_data def test_write_json_merge_append(tmp_path): test_file = tmp_path / "test.json" - write_json(test_file, {"a": 1, "b": 2}) - write_json(test_file, {"b": 3, "c": 4}, mode='a') # merge dictionaries - expected = {"a": 1, "b": 3, "c": 4} - assert read_json(test_file) == expected + data0=sample_data[0] + data1=sample_data[1] + write_json(test_file,data0) + write_json(test_file, data1, mode='a') # merge dictionaries + expected = data0.copy() + expected.update(data1) + assert read_json_test(test_file) == expected def test_write_json_append_non_dict_raises(tmp_path): test_file = tmp_path / "test.json" @@ -46,7 +45,7 @@ def test_write_json_append_invalid_json_raises(tmp_path): def test_write_json_unicode_support(tmp_path): test_file = tmp_path / "test.json" - data = {"greeting": "こんにちは", "key": "üñîçødë"} + data = Complex_data write_json(test_file, data) - loaded = read_json(test_file) + loaded = read_json_test(test_file) assert loaded == data diff --git a/UnitTest/Suporting/test_yamlReader.py b/UnitTest/Suporting/test_yamlReader.py index d450352..9923dc2 100644 --- a/UnitTest/Suporting/test_yamlReader.py +++ b/UnitTest/Suporting/test_yamlReader.py @@ -1,30 +1,24 @@ import pytest from GabesPythonToolBox.Suporting.yamlReader import read_ymal -import yaml -import os +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import write_yaml_test,sample_data,Complex_data # Supporting func to help the test -def write_yaml_for_test(file_path, data): - """Helper to write YAML files for testing read function.""" - with open(file_path, "w", encoding="utf-8") as f: - yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) + # Tests def test_read_ymal_reads_correctly(tmp_path): test_file = tmp_path / "test.yaml" - data = {"key": "value"} - write_yaml_for_test(test_file, data) + write_yaml_test(test_file, sample_data) loaded = read_ymal(test_file) - assert loaded == data + assert loaded == sample_data def test_read_ymal_with_different_encoding(tmp_path): test_file = tmp_path / "test.yaml" - data = {"key": "üñîçødë"} - write_yaml_for_test(test_file, data) + write_yaml_test(test_file, Complex_data) loaded = read_ymal(test_file, encoding="utf-8") - assert loaded == data + assert loaded == Complex_data def test_read_ymal_nonexistent_file_raises(tmp_path): test_file = tmp_path / "missing.yaml" diff --git a/UnitTest/Suporting/test_yamlWriter.py b/UnitTest/Suporting/test_yamlWriter.py index c01829e..b277fbb 100644 --- a/UnitTest/Suporting/test_yamlWriter.py +++ b/UnitTest/Suporting/test_yamlWriter.py @@ -2,46 +2,37 @@ from GabesPythonToolBox.Suporting.yamlWriter import write_yaml import os import yaml - -# Supporting func to help the test -def read_yaml(file_path): - """Helper function to read YAML for test verification.""" - with open(file_path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import read_yaml_test,sample_data,Complex_data,New_data # Tests def test_write_yaml_creates_file(tmp_path): test_file = tmp_path / "test.yaml" - data = {"key": "value"} - - write_yaml(test_file, data) + write_yaml(test_file, sample_data) assert test_file.exists() - assert read_yaml(test_file) == data + assert read_yaml_test(test_file) == sample_data def test_write_yaml_overwrites_file(tmp_path): test_file = tmp_path / "test.yaml" - initial_data = {"old": "data"} - write_yaml(test_file, initial_data) + write_yaml(test_file, sample_data) + write_yaml(test_file, New_data) - new_data = {"new": "data"} - write_yaml(test_file, new_data) - - assert read_yaml(test_file) == new_data + assert read_yaml_test(test_file) == New_data def test_write_yaml_merge_append(tmp_path): test_file = tmp_path / "test.yaml" - # Initial data - data1 = {"a": 1, "b": 2} - write_yaml(test_file, data1) + data0=sample_data[0] + data1=sample_data[1] + write_yaml(test_file, data0) # New data to append (merge) - data2 = {"b": 3, "c": 4} # 'b' should be overwritten - write_yaml(test_file, data2, mode='a') + write_yaml(test_file, data1, mode='a') - expected = {"a": 1, "b": 3, "c": 4} - assert read_yaml(test_file) == expected + expected = data0.copy() + expected.update(data1) + assert read_yaml_test(test_file) == expected + def test_write_yaml_append_non_dict_raises(tmp_path): test_file = tmp_path / "test.yaml" diff --git a/UnitTest/UnitTestComon/UntTestUtility.py b/UnitTest/UnitTestComon/UntTestUtility.py new file mode 100644 index 0000000..f666cc4 --- /dev/null +++ b/UnitTest/UnitTestComon/UntTestUtility.py @@ -0,0 +1,100 @@ +import csv +import json +import yaml + +import os + +def Nothing(): + return + +def almost_equal(a, b, tol=1e-6): + return abs(a - b) < tol + +def Generate_sequence(n): + """Generate a simple increasing numeric sequence.""" + return [float(i + 1) for i in range(n)] + +def write_csv_for_test(file_path, lines): + """Helper function to write raw CSV lines for testing.""" + with open(file_path, "w", encoding="utf-8") as f: + for line in lines: + f.write(line + "\n") + +def read_csv_test(file_path, delimiter=','): + """Helper to read CSV back into list-of-dicts for testing.""" + with open(file_path, newline="", encoding="utf-8") as f: + reader = csv.DictReader(f, delimiter=delimiter) + return list(reader) + +def write_json_test(file_path, data): + """Helper function to create JSON files for testing read_json.""" + with open(file_path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=4, ensure_ascii=False) + +def read_json_test(file_path): + with open(file_path, "r", encoding="utf-8") as f: + return json.load(f) + +def write_yaml_test(file_path, data): + """Helper to write YAML files for testing read function.""" + with open(file_path, "w", encoding="utf-8") as f: + yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) + +def read_yaml_test(file_path): + """Helper function to read YAML for test verification.""" + with open(file_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + +def write_config_test(file_path, data): + """Write a JSON or YAML file for testing.""" + ext = os.path.splitext(file_path)[1].lower() + + with open(file_path, "w", encoding="utf-8") as f: + if ext == ".json": + write_json_test(file_path, data) + elif ext in [".yaml", ".yml"]: + write_yaml_test(file_path, data) + else: + raise ValueError("Unsupported test config type") + +# Sample test data +sample_data = [ + {"name": "Alice", "age": 30, "score": 95.5}, + {"name": "Bob", "age": 25, "score": 88.2}, +] + +# Complex test data +Complex_data = [ + {"greeting": "こんにちは", "key": "üñîçødë"}, +] + +New_data = [ + {"name": "Jacob", "age": 23, "score": 50.9}, + {"name": "Tory", "age": 21, "score": 99.8}, +] + +sample_config = { + "university": { + "name": "GTB University", + "department": "Robotics Engineering" + }, + "students": [ + {"id": 1, "name": "Alice", "project": "Autonomous Rover"}, + {"id": 2, "name": "Bob", "project": "Drone Swarm Control"} + ], + "projects": { + "active": [ + {"title": "Autonomous Rover", "progress": 65, "supervisor": "Dr. Grey"}, + {"title": "Drone Swarm Control", "progress": 40, "supervisor": "Dr. Lin"} + ], + "completed": [] + }, + "lab_settings": { + "open_hours": "08:00-18:00", + "max_students": 10, + "equipment": { + "3D_printers": 2, + "robots": ["Atlas", "Spot", "Rover"] + } + } +} \ No newline at end of file diff --git a/UnitTest/Utility/test_ConfigManager.py b/UnitTest/Utility/test_ConfigManager.py index 94e1375..39e1473 100644 --- a/UnitTest/Utility/test_ConfigManager.py +++ b/UnitTest/Utility/test_ConfigManager.py @@ -3,67 +3,14 @@ import json import yaml import GabesPythonToolBox.Utility.ConfigManager as CM - -# Helper Functions - -def write_config(file_path, data): - """Write a JSON or YAML file for testing.""" - ext = os.path.splitext(file_path)[1].lower() - with open(file_path, "w", encoding="utf-8") as f: - if ext == ".json": - json.dump(data, f, indent=4) - elif ext in [".yaml", ".yml"]: - yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) - else: - raise ValueError("Unsupported test config type") - -def read_json(file_path): - with open(file_path, "r", encoding="utf-8") as f: - return json.load(f) - -def read_yaml(file_path): - with open(file_path, "r", encoding="utf-8") as f: - return yaml.safe_load(f) - - - -# University-Inspired Sample Config - - -SAMPLE_CONFIG = { - "university": { - "name": "GTB University", - "department": "Robotics Engineering" - }, - "students": [ - {"id": 1, "name": "Alice", "project": "Autonomous Rover"}, - {"id": 2, "name": "Bob", "project": "Drone Swarm Control"} - ], - "projects": { - "active": [ - {"title": "Autonomous Rover", "progress": 65, "supervisor": "Dr. Grey"}, - {"title": "Drone Swarm Control", "progress": 40, "supervisor": "Dr. Lin"} - ], - "completed": [] - }, - "lab_settings": { - "open_hours": "08:00-18:00", - "max_students": 10, - "equipment": { - "3D_printers": 2, - "robots": ["Atlas", "Spot", "Rover"] - } - } -} - - +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import read_json_test,read_yaml_test,write_config_test,sample_config #Core Functionality Tests @pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) def test_load_and_call(tmp_path, ext): file = tmp_path / f"lab_config{ext}" - write_config(file, SAMPLE_CONFIG) + write_config_test(file, sample_config) cm = CM.ConfigManager(str(file)) config = cm() @@ -75,7 +22,7 @@ def test_load_and_call(tmp_path, ext): @pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) def test_update_nested_and_indexed_keys(tmp_path, ext): file = tmp_path / f"update_test{ext}" - write_config(file, SAMPLE_CONFIG) + write_config_test(file, sample_config) cm = CM.ConfigManager(str(file)) # Update simple nested key @@ -94,7 +41,7 @@ def test_update_nested_and_indexed_keys(tmp_path, ext): @pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) def test_save_and_reload_same_format(tmp_path, ext): file = tmp_path / f"save_test{ext}" - write_config(file, SAMPLE_CONFIG) + write_config_test(file, sample_config) cm = CM.ConfigManager(str(file)) cm.update("projects.active[0].progress", 90) @@ -113,7 +60,7 @@ def test_save_and_reload_same_format(tmp_path, ext): ]) def test_cross_format_save(tmp_path, ext_from, ext_to): file = tmp_path / f"cross{ext_from}" - write_config(file, SAMPLE_CONFIG) + write_config_test(file, sample_config) cm = CM.ConfigManager(str(file)) cm.update("university.name", "Techville Institute of Robotics") @@ -121,14 +68,14 @@ def test_cross_format_save(tmp_path, ext_from, ext_to): cm.save(str(out_file)) assert out_file.exists() - loaded = read_json(out_file) if ext_to == ".json" else read_yaml(out_file) + loaded = read_json_test(out_file) if ext_to == ".json" else read_yaml_test(out_file) assert loaded["university"]["name"] == "Techville Institute of Robotics" @pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) def test_reset_to_original(tmp_path, ext): file = tmp_path / f"reset_test{ext}" - write_config(file, SAMPLE_CONFIG) + write_config_test(file, sample_config) cm = CM.ConfigManager(str(file)) cm.update("students[0].name", "Charlie") @@ -141,7 +88,7 @@ def test_reset_to_original(tmp_path, ext): @pytest.mark.parametrize("ext", [".json", ".yaml", ".yml"]) def test_start_config_manager_factory(tmp_path, ext): file = tmp_path / f"factory_test{ext}" - write_config(file, SAMPLE_CONFIG) + write_config_test(file, sample_config) cm = CM.startConfigManager(str(file)) assert isinstance(cm, CM.ConfigManager) diff --git a/UnitTest/Utility/test_Debug.py b/UnitTest/Utility/test_Debug.py index d002d71..acc3d7e 100644 --- a/UnitTest/Utility/test_Debug.py +++ b/UnitTest/Utility/test_Debug.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.Utility import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_DeltaTime.py b/UnitTest/Utility/test_DeltaTime.py index d002d71..c8976eb 100644 --- a/UnitTest/Utility/test_DeltaTime.py +++ b/UnitTest/Utility/test_DeltaTime.py @@ -1,3 +1,5 @@ import pytest -from GabesPythonToolBox.Utility import * -#TODO \ No newline at end of file +from GabesPythonToolBox.Utility.DeltaTime import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing +#TODO + diff --git a/UnitTest/Utility/test_Logger.py b/UnitTest/Utility/test_Logger.py index d002d71..acc3d7e 100644 --- a/UnitTest/Utility/test_Logger.py +++ b/UnitTest/Utility/test_Logger.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.Utility import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/UnitTest/Utility/test_UnitConverter.py b/UnitTest/Utility/test_UnitConverter.py index d002d71..acc3d7e 100644 --- a/UnitTest/Utility/test_UnitConverter.py +++ b/UnitTest/Utility/test_UnitConverter.py @@ -1,3 +1,4 @@ import pytest from GabesPythonToolBox.Utility import * +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing #TODO \ No newline at end of file diff --git a/__init__.py b/__init__.py index 6086411..a17e32d 100644 --- a/__init__.py +++ b/__init__.py @@ -34,6 +34,11 @@ from .Utility.Logger import * from .Utility.UnitConverter import * +# ----------------------------- +# Utility +# ----------------------------- +from .UnitTest.UnitTestComon.UntTestUtility import* + # ----------------------------- # Public Exports # ----------------------------- @@ -63,4 +68,7 @@ "DeltaTime", "Logger", "UnitConverter", + + # UnitTest + "UntTestUtility" ] diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000000000000000000000000000000000000..d19922dadc7f8a015c52a5337a264a1d6062a718 GIT binary patch literal 274 zcmaKo$qK?i5JcZu@E-(^f^m!6NkkA&dJyj>#sy;(ji5iTK9{pJRSi8=T{H81l$kSS zB#IO$GiFJ|nH>o!7iQea*>WUPd9+f7@{JM$u~C^*QIT6G9eo1pJKu73Ta{SH!RbQv zUSyt67nS-a^ABqS=nP1 NvHG_o`n>-A11|`bE&Bif literal 0 HcmV?d00001 From dfda20e478cbd91dc968fecba2353294ce14d62d Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 14 Oct 2025 12:46:54 +0200 Subject: [PATCH 04/22] folder stucture --- README.md | 7 ++++++- folder_tree.txt | Bin 0 -> 12846 bytes 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 folder_tree.txt diff --git a/README.md b/README.md index f765871..5a0fd87 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,15 @@ using "import GabesPythonToolBox as GTB" for all or import "GabesPythonToolBox.Category.lib as (XX)" That’s it. You're good to go. -### Unit Test comand +### comands +#### unit test ''' pytest UnitTest -v ''' +#### get filestucture +''' +tree /F /A > folder_tree.txt +''' ## Current Features diff --git a/folder_tree.txt b/folder_tree.txt new file mode 100644 index 0000000000000000000000000000000000000000..fcdb2ab3baececb7cfb45f8e81b247973f672b4f GIT binary patch literal 12846 zcmc&)T~8ZF6rJZv{SUlXMkawY?He&6p#_4#G>TNo;=?#@4WZzWAmz8WJ!i+Kp4pw9 zon0?mA$uI}{l4ekxiii`fA5-4W@awU*xZ>lb7Z#7(7eT`nVFhYl)q*M<{4hwre}V_`og@* zqzuf@Wq~-I_k`mi<#y7IV4WNL(gwB^vnaEEa~m1D^Qotss9a7({lSwbFZDF+iKz1H z41Tk(Tw$G}X2!W1&C~1Oi~_sV6z{gv$YN3CS2z01KehS&K)5@{_qN-uqmL1W7PryKxn5eyy&SJcs`6`nm(#=Xf0f?Vk`UqLn^PJWmw8 z3p{-Qdc-qDeT)$)a&1|EEcj%L! zq3Z&?(}fMy*3v?DEAm{S9@DfQW1lOReFa&wN)PB|s6#5AN2uSOh!z(=!Wi+w{46Vw z-?)^*ht{fWUu5#;=u6CM&pbC9__MxWBuG9+M9)w+Hm<8mdyi~!=U7!*3RaujD(OWu zuP(C+@2hJFSH$XaZOt^7<5yL4Ilo|D;FQ-EmQ=^hEmEa7m+%35`~l{kwtRATk1;0J zd(`kXIstd*-K@KD4{`g7x)!_t41et7$%j5xz=*7b@NBIBm1WZ z@oA0mmsy{}`v~i=k;9nnp=a9F7)hcpM5BEy#g14E(&an@uIosOSuGD^5t3#3wLDv+ ztOhS(XA`-ni1Z~g#;9N7EWv%>A~DVHyqz(5P3&{yxvZBH+%vdOga@s+@aPUcJz^fx zY|mMOT}bod2saOXyf!fB^l|&ZX{V1U+8HS(UA1m0%X^xv3CR)`QqZm#r+CGw{Y%sf zNz>|DrwH2{6x)M_kjT#L(#bQeA^Ps(!u^M*dz;TKWVDahb9}R5w($27PhQrFNlfAk zNbf`XRxOEc^c3Y-?u8F)iOa2+D}P$cZ6?Myv#8XIf~!^b=v-?%#RIUxI?L}>vPo+> zSGdcFPiu@nzgLMzE4@&}-=Hs?!{R9{o&nWI=ny*X41pp}Gq3Re6}cS)QxkLro@jZ$ z=SD-adD7u&^d>v=@HEPfV^7b2LVoB5P`pRxSKQR}aPB+Al=UtspQqL3onBlX=Um>? z@pkbfs(B7?ZZkW_IQugaF+1J(711bf^5|nnWk0nw*f&1{f#Hgg1Xs@q`n2xps_riQ zGiM$RsutU;{fRl9b{`QV8th55j8Ahynnj{cvq^hrnru<0$##*gI&XTn;M>>yjAQfV zO5-H9sysg)ZA$YI(p<7o;d;J`-;&ps=R#In_DNn__PJd|yGCq+slIbn2XU)J`-$#; z((YO%iTfods?mSaRj` zx7LI!p<5eb_p@!Iz^}k={8aUJqfM3nl-k^`q5UUqPF9^RJ4A81sy?mfx&0kGy-R8$ z@h6D-{7v<1;%KU0{Ppy^*;78+?D?ULJ|!CCX!yT0r%d5(cegN}yNQ5w`&jHox9Y&J zqi%I~=eJwG>c08&1*iCHyyCyRkZX=#x9l0<6rVYt@^|jX#gyghQm@b&dbWBWiQ5#; zX`PACwR#yn*K?ObzG_|e8u#}J|Ch2l`&Y{Ekwx8gewi$C`<*F$w-d&S_pQ|)hhn-q zR?@N&#!TD2*Ue;i8uPzTcca-yP&Ybb=zB^FT;!%BRwrStOQb&4bRgAOTq;pf-49iDTANg|c(qdVzxqZ12><{9 literal 0 HcmV?d00001 From 86a132417109d695c7d72817f0805a94ba939300 Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:12:52 +0200 Subject: [PATCH 05/22] todo --- exsampleFiles/testPID.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exsampleFiles/testPID.py b/exsampleFiles/testPID.py index 459f7a1..133be22 100644 --- a/exsampleFiles/testPID.py +++ b/exsampleFiles/testPID.py @@ -1,6 +1,6 @@ from GabesPythonToolBox.Utility.Debug import Debug Debug.add_group('Showcase', True) -Debug.add_group('LIB', True) +Debug.add_group('LIB', False) import matplotlib.pyplot as plt import GabesPythonToolBox.DataProsesing.PID as GTB @@ -15,7 +15,7 @@ def __init__(self, initial_value=0, gain=1, resistance=0.1, delay=0.1, min_contr self.current_value = initial_value self.gain = gain self.resistance = resistance # Resistance to change - self.delay = delay # Time lag effect + self.delay = delay # Time lag effect #TODO shood use dt self.previous_value = initial_value self.min_control_signal = min_control_signal self.max_control_signal = max_control_signal @@ -64,7 +64,7 @@ def test(): setpointDiff = 5 required_hits = 10 - sim_time = 10.0 # total simulated seconds + sim_time = 3.0 # total simulated seconds time_delay = 0.01 # simulated timestep (Δt) time_steps = int(sim_time / time_delay) @@ -74,7 +74,7 @@ def test(): Debug.log(f"Simulation finished. ", "End", group="Showcase") graph(results) - val_graph(results) + #val_graph(results) From 931a19aa59b9b5b677f89863240224781f71c73b Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:13:10 +0200 Subject: [PATCH 06/22] corected todo --- exsampleFiles/testPID.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exsampleFiles/testPID.py b/exsampleFiles/testPID.py index 133be22..a1cb6f7 100644 --- a/exsampleFiles/testPID.py +++ b/exsampleFiles/testPID.py @@ -64,7 +64,7 @@ def test(): setpointDiff = 5 required_hits = 10 - sim_time = 3.0 # total simulated seconds + sim_time = 10.0 # total simulated seconds time_delay = 0.01 # simulated timestep (Δt) time_steps = int(sim_time / time_delay) From 059155d52ea032b2bbf53ac35de5057c1d54d210 Mon Sep 17 00:00:00 2001 From: NorgeSkiFollo <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 14 Oct 2025 13:33:18 +0200 Subject: [PATCH 07/22] Delta time test --- UnitTest/Utility/test_ConfigManager.py | 3 -- UnitTest/Utility/test_DeltaTime.py | 75 ++++++++++++++++++++++++-- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/UnitTest/Utility/test_ConfigManager.py b/UnitTest/Utility/test_ConfigManager.py index 39e1473..4fd5839 100644 --- a/UnitTest/Utility/test_ConfigManager.py +++ b/UnitTest/Utility/test_ConfigManager.py @@ -1,7 +1,4 @@ import pytest -import os -import json -import yaml import GabesPythonToolBox.Utility.ConfigManager as CM from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import read_json_test,read_yaml_test,write_config_test,sample_config diff --git a/UnitTest/Utility/test_DeltaTime.py b/UnitTest/Utility/test_DeltaTime.py index c8976eb..1d5268e 100644 --- a/UnitTest/Utility/test_DeltaTime.py +++ b/UnitTest/Utility/test_DeltaTime.py @@ -1,5 +1,74 @@ import pytest -from GabesPythonToolBox.Utility.DeltaTime import * -from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import Nothing -#TODO +from GabesPythonToolBox.Utility.DeltaTime import DeltaTime, DeltaTimer +from GabesPythonToolBox.UnitTest.UnitTestComon.UntTestUtility import almost_equal +import time +#DeltaTime tests + +def test_deltatime_basic(): + dt = DeltaTime() + time.sleep(0.01) # small sleep + delta = dt() + assert delta > 0 + # consecutive call should give a smaller delta if called immediately + delta2 = dt() + assert almost_equal(delta2, 0, tol=1e-2) # allow 0.01s tolerance + +#DeltaTimer tests + +def test_deltatimer_start_and_update(): + timer = DeltaTimer(0.05) # 50ms timer + # Initially not started + assert timer.Update() is False + assert not timer.Finished + # Start timer + timer.startTimer() + time.sleep(0.02) + finished = timer.Update() + assert finished is False + # timeLeft should decrease + assert timer.timeLeft < timer.duration + +def test_deltatimer_finish(): + timer = DeltaTimer(0.03) + timer.startTimer() + time.sleep(0.05) + finished = timer.Update() + assert finished is True + assert timer.Finished + assert timer.timeLeft <= 0 + +def test_deltatimer_pause_and_continue(): + timer = DeltaTimer(0.05) + timer.startTimer() + time.sleep(0.02) + timer.pauseTimer() + paused_time_left = timer.getTimeLeft() + time.sleep(0.02) + # Timer should not decrease while paused + assert almost_equal(paused_time_left, timer.getTimeLeft(), tol=1e-3) + timer.continueTimer() + time.sleep(0.02) + # After continuing, timer should decrease + assert timer.getTimeLeft() < paused_time_left + +def test_deltatimer_stop(): + timer = DeltaTimer(0.05) + timer.startTimer() + timer.stopTimer() + assert timer.Finished + assert timer.timeLeft == timer.timeLeft # no change after stop + + +def test_deltatimer_recurrent_start_finish(): + #Start timer, let it finish, start again, and finish again (simulate recurring events). + timer = DeltaTimer(00.03) + test_runs = 6 + i=0 + + while i < test_runs: + timer.startTimer() + time.sleep(0.05) + assert timer.Update() is True + assert timer.Finished + i+=1 From b6db8579108313c0ad5a2920daa91971ba7b73f4 Mon Sep 17 00:00:00 2001 From: VikingInOrbit <104018760+VikingInOrbit@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:08:12 +0100 Subject: [PATCH 08/22] base test --- .coverage | Bin 0 -> 122880 bytes .gitignore | 4 +++- .../StateMachine/test_BaseState.py | 7 +++++++ .../StateMachine/test_NoneState.py | 7 +++++++ .../StateMachine/test_StateLoader.py | 7 +++++++ .../StateMachine/test_StateManager.py | 7 +++++++ .../StateMachine/test_StateSwitcher.py | 7 +++++++ .../DataProsesing/test_DataConsolPrinter.py | 5 ++++- UnitTest/DataProsesing/test_DataFormater.py | 5 ++++- .../DataProsesing/test_DataHelperFunctions.py | 5 ++++- UnitTest/DataProsesing/test_DataPrettifyer.py | 5 ++++- UnitTest/DataProsesing/test_DataRW.py | 5 ++++- UnitTest/Utility/test_Debug.py | 5 ++++- UnitTest/Utility/test_Logger.py | 5 ++++- UnitTest/Utility/test_UnitConverter.py | 5 ++++- 15 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 .coverage create mode 100644 UnitTest/ControllSoftwere/StateMachine/test_BaseState.py create mode 100644 UnitTest/ControllSoftwere/StateMachine/test_NoneState.py create mode 100644 UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py create mode 100644 UnitTest/ControllSoftwere/StateMachine/test_StateManager.py create mode 100644 UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..a333a9f2f121283f73aa72f75338af0195d4607a GIT binary patch literal 122880 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 Date: Mon, 10 Nov 2025 15:44:47 +0100 Subject: [PATCH 09/22] changed location --- Tests/FallbackTest/.gitkeep | 0 .../UnitTest}/ControllSoftwere/StateMachine/test_BaseState.py | 0 .../UnitTest}/ControllSoftwere/StateMachine/test_NoneState.py | 0 .../UnitTest}/ControllSoftwere/StateMachine/test_StateLoader.py | 0 .../UnitTest}/ControllSoftwere/StateMachine/test_StateManager.py | 0 .../UnitTest}/ControllSoftwere/StateMachine/test_StateSwitcher.py | 0 .../UnitTest}/DataProsesing/test_DataConsolPrinter.py | 0 {UnitTest => Tests/UnitTest}/DataProsesing/test_DataFormater.py | 0 .../UnitTest}/DataProsesing/test_DataHelperFunctions.py | 0 {UnitTest => Tests/UnitTest}/DataProsesing/test_DataPrettifyer.py | 0 {UnitTest => Tests/UnitTest}/DataProsesing/test_DataRW.py | 0 {UnitTest => Tests/UnitTest}/DataProsesing/test_PID.py | 0 {UnitTest => Tests/UnitTest}/DataProsesing/test_filter.py | 0 {UnitTest => Tests/UnitTest}/DataProsesing/test_map.py | 0 {UnitTest => Tests/UnitTest}/Suporting/test_csvReader.py | 0 {UnitTest => Tests/UnitTest}/Suporting/test_csvWriter.py | 0 {UnitTest => Tests/UnitTest}/Suporting/test_jsonReader.py | 0 {UnitTest => Tests/UnitTest}/Suporting/test_jsonWriter.py | 0 {UnitTest => Tests/UnitTest}/Suporting/test_yamlReader.py | 0 {UnitTest => Tests/UnitTest}/Suporting/test_yamlWriter.py | 0 {UnitTest => Tests/UnitTest}/UnitTestComon/UntTestUtility.py | 0 {UnitTest => Tests/UnitTest}/Utility/test_ConfigManager.py | 0 {UnitTest => Tests/UnitTest}/Utility/test_Debug.py | 0 {UnitTest => Tests/UnitTest}/Utility/test_DeltaTime.py | 0 {UnitTest => Tests/UnitTest}/Utility/test_Logger.py | 0 {UnitTest => Tests/UnitTest}/Utility/test_UnitConverter.py | 0 {UnitTest => Tests/UnitTest}/__init__.py | 0 27 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 Tests/FallbackTest/.gitkeep rename {UnitTest => Tests/UnitTest}/ControllSoftwere/StateMachine/test_BaseState.py (100%) rename {UnitTest => Tests/UnitTest}/ControllSoftwere/StateMachine/test_NoneState.py (100%) rename {UnitTest => Tests/UnitTest}/ControllSoftwere/StateMachine/test_StateLoader.py (100%) rename {UnitTest => Tests/UnitTest}/ControllSoftwere/StateMachine/test_StateManager.py (100%) rename {UnitTest => Tests/UnitTest}/ControllSoftwere/StateMachine/test_StateSwitcher.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_DataConsolPrinter.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_DataFormater.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_DataHelperFunctions.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_DataPrettifyer.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_DataRW.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_PID.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_filter.py (100%) rename {UnitTest => Tests/UnitTest}/DataProsesing/test_map.py (100%) rename {UnitTest => Tests/UnitTest}/Suporting/test_csvReader.py (100%) rename {UnitTest => Tests/UnitTest}/Suporting/test_csvWriter.py (100%) rename {UnitTest => Tests/UnitTest}/Suporting/test_jsonReader.py (100%) rename {UnitTest => Tests/UnitTest}/Suporting/test_jsonWriter.py (100%) rename {UnitTest => Tests/UnitTest}/Suporting/test_yamlReader.py (100%) rename {UnitTest => Tests/UnitTest}/Suporting/test_yamlWriter.py (100%) rename {UnitTest => Tests/UnitTest}/UnitTestComon/UntTestUtility.py (100%) rename {UnitTest => Tests/UnitTest}/Utility/test_ConfigManager.py (100%) rename {UnitTest => Tests/UnitTest}/Utility/test_Debug.py (100%) rename {UnitTest => Tests/UnitTest}/Utility/test_DeltaTime.py (100%) rename {UnitTest => Tests/UnitTest}/Utility/test_Logger.py (100%) rename {UnitTest => Tests/UnitTest}/Utility/test_UnitConverter.py (100%) rename {UnitTest => Tests/UnitTest}/__init__.py (100%) diff --git a/Tests/FallbackTest/.gitkeep b/Tests/FallbackTest/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/UnitTest/ControllSoftwere/StateMachine/test_BaseState.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_BaseState.py similarity index 100% rename from UnitTest/ControllSoftwere/StateMachine/test_BaseState.py rename to Tests/UnitTest/ControllSoftwere/StateMachine/test_BaseState.py diff --git a/UnitTest/ControllSoftwere/StateMachine/test_NoneState.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_NoneState.py similarity index 100% rename from UnitTest/ControllSoftwere/StateMachine/test_NoneState.py rename to Tests/UnitTest/ControllSoftwere/StateMachine/test_NoneState.py diff --git a/UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py similarity index 100% rename from UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py rename to Tests/UnitTest/ControllSoftwere/StateMachine/test_StateLoader.py diff --git a/UnitTest/ControllSoftwere/StateMachine/test_StateManager.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateManager.py similarity index 100% rename from UnitTest/ControllSoftwere/StateMachine/test_StateManager.py rename to Tests/UnitTest/ControllSoftwere/StateMachine/test_StateManager.py diff --git a/UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py b/Tests/UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py similarity index 100% rename from UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py rename to Tests/UnitTest/ControllSoftwere/StateMachine/test_StateSwitcher.py diff --git a/UnitTest/DataProsesing/test_DataConsolPrinter.py b/Tests/UnitTest/DataProsesing/test_DataConsolPrinter.py similarity index 100% rename from UnitTest/DataProsesing/test_DataConsolPrinter.py rename to Tests/UnitTest/DataProsesing/test_DataConsolPrinter.py diff --git a/UnitTest/DataProsesing/test_DataFormater.py b/Tests/UnitTest/DataProsesing/test_DataFormater.py similarity index 100% rename from UnitTest/DataProsesing/test_DataFormater.py rename to Tests/UnitTest/DataProsesing/test_DataFormater.py diff --git a/UnitTest/DataProsesing/test_DataHelperFunctions.py b/Tests/UnitTest/DataProsesing/test_DataHelperFunctions.py similarity index 100% rename from UnitTest/DataProsesing/test_DataHelperFunctions.py rename to Tests/UnitTest/DataProsesing/test_DataHelperFunctions.py diff --git a/UnitTest/DataProsesing/test_DataPrettifyer.py b/Tests/UnitTest/DataProsesing/test_DataPrettifyer.py similarity index 100% rename from UnitTest/DataProsesing/test_DataPrettifyer.py rename to Tests/UnitTest/DataProsesing/test_DataPrettifyer.py diff --git a/UnitTest/DataProsesing/test_DataRW.py b/Tests/UnitTest/DataProsesing/test_DataRW.py similarity index 100% rename from UnitTest/DataProsesing/test_DataRW.py rename to Tests/UnitTest/DataProsesing/test_DataRW.py diff --git a/UnitTest/DataProsesing/test_PID.py b/Tests/UnitTest/DataProsesing/test_PID.py similarity index 100% rename from UnitTest/DataProsesing/test_PID.py rename to Tests/UnitTest/DataProsesing/test_PID.py diff --git a/UnitTest/DataProsesing/test_filter.py b/Tests/UnitTest/DataProsesing/test_filter.py similarity index 100% rename from UnitTest/DataProsesing/test_filter.py rename to Tests/UnitTest/DataProsesing/test_filter.py diff --git a/UnitTest/DataProsesing/test_map.py b/Tests/UnitTest/DataProsesing/test_map.py similarity index 100% rename from UnitTest/DataProsesing/test_map.py rename to Tests/UnitTest/DataProsesing/test_map.py diff --git a/UnitTest/Suporting/test_csvReader.py b/Tests/UnitTest/Suporting/test_csvReader.py similarity index 100% rename from UnitTest/Suporting/test_csvReader.py rename to Tests/UnitTest/Suporting/test_csvReader.py diff --git a/UnitTest/Suporting/test_csvWriter.py b/Tests/UnitTest/Suporting/test_csvWriter.py similarity index 100% rename from UnitTest/Suporting/test_csvWriter.py rename to Tests/UnitTest/Suporting/test_csvWriter.py diff --git a/UnitTest/Suporting/test_jsonReader.py b/Tests/UnitTest/Suporting/test_jsonReader.py similarity index 100% rename from UnitTest/Suporting/test_jsonReader.py rename to Tests/UnitTest/Suporting/test_jsonReader.py diff --git a/UnitTest/Suporting/test_jsonWriter.py b/Tests/UnitTest/Suporting/test_jsonWriter.py similarity index 100% rename from UnitTest/Suporting/test_jsonWriter.py rename to Tests/UnitTest/Suporting/test_jsonWriter.py diff --git a/UnitTest/Suporting/test_yamlReader.py b/Tests/UnitTest/Suporting/test_yamlReader.py similarity index 100% rename from UnitTest/Suporting/test_yamlReader.py rename to Tests/UnitTest/Suporting/test_yamlReader.py diff --git a/UnitTest/Suporting/test_yamlWriter.py b/Tests/UnitTest/Suporting/test_yamlWriter.py similarity index 100% rename from UnitTest/Suporting/test_yamlWriter.py rename to Tests/UnitTest/Suporting/test_yamlWriter.py diff --git a/UnitTest/UnitTestComon/UntTestUtility.py b/Tests/UnitTest/UnitTestComon/UntTestUtility.py similarity index 100% rename from UnitTest/UnitTestComon/UntTestUtility.py rename to Tests/UnitTest/UnitTestComon/UntTestUtility.py diff --git a/UnitTest/Utility/test_ConfigManager.py b/Tests/UnitTest/Utility/test_ConfigManager.py similarity index 100% rename from UnitTest/Utility/test_ConfigManager.py rename to Tests/UnitTest/Utility/test_ConfigManager.py diff --git a/UnitTest/Utility/test_Debug.py b/Tests/UnitTest/Utility/test_Debug.py similarity index 100% rename from UnitTest/Utility/test_Debug.py rename to Tests/UnitTest/Utility/test_Debug.py diff --git a/UnitTest/Utility/test_DeltaTime.py b/Tests/UnitTest/Utility/test_DeltaTime.py similarity index 100% rename from UnitTest/Utility/test_DeltaTime.py rename to Tests/UnitTest/Utility/test_DeltaTime.py diff --git a/UnitTest/Utility/test_Logger.py b/Tests/UnitTest/Utility/test_Logger.py similarity index 100% rename from UnitTest/Utility/test_Logger.py rename to Tests/UnitTest/Utility/test_Logger.py diff --git a/UnitTest/Utility/test_UnitConverter.py b/Tests/UnitTest/Utility/test_UnitConverter.py similarity index 100% rename from UnitTest/Utility/test_UnitConverter.py rename to Tests/UnitTest/Utility/test_UnitConverter.py diff --git a/UnitTest/__init__.py b/Tests/UnitTest/__init__.py similarity index 100% rename from UnitTest/__init__.py rename to Tests/UnitTest/__init__.py From 647e65d696f7dbecfd47000f48d323dd2a457a65 Mon Sep 17 00:00:00 2001 From: VikingInOrbit <104018760+VikingInOrbit@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:47:49 +0100 Subject: [PATCH 10/22] doc --- ChangeLog.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index 5e96498..3ce7cc9 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,14 @@ # Updates +## 1.x.1 location update + +### new things + +- Base Unit test +### breaking changes + +- file structure + ## 1.x.0 Test update ### new things From 550d5b6ce26591db905b3be1eb0d71dada8f68bd Mon Sep 17 00:00:00 2001 From: VikingInOrbit <104018760+VikingInOrbit@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:35:36 +0100 Subject: [PATCH 11/22] Location and 100 on suporting --- .coverage | Bin 122880 -> 122880 bytes .gitignore | 3 +- ChangeLog.md | 1 + Suporting/csvReader.py | 3 + Suporting/csvWriterLogger.py | 2 +- .../StateMachine/test_BaseState.py | 2 +- .../StateMachine/test_NoneState.py | 2 +- .../StateMachine/test_StateLoader.py | 2 +- .../StateMachine/test_StateManager.py | 2 +- .../StateMachine/test_StateSwitcher.py | 2 +- .../DataProsesing/test_DataConsolPrinter.py | 2 +- .../DataProsesing/test_DataFormater.py | 2 +- .../DataProsesing/test_DataHelperFunctions.py | 2 +- .../DataProsesing/test_DataPrettifyer.py | 2 +- Tests/UnitTest/DataProsesing/test_DataRW.py | 2 +- Tests/UnitTest/DataProsesing/test_PID.py | 2 +- Tests/UnitTest/DataProsesing/test_filter.py | 2 +- Tests/UnitTest/DataProsesing/test_map.py | 12 ++- Tests/UnitTest/Suporting/test_csvReader.py | 30 ++++++- Tests/UnitTest/Suporting/test_csvWriter.py | 12 ++- Tests/UnitTest/Suporting/test_jsonReader.py | 2 +- Tests/UnitTest/Suporting/test_jsonWriter.py | 2 +- Tests/UnitTest/Suporting/test_yamlReader.py | 2 +- Tests/UnitTest/Suporting/test_yamlWriter.py | 2 +- Tests/UnitTest/Utility/test_ConfigManager.py | 2 +- Tests/UnitTest/Utility/test_Debug.py | 2 +- Tests/UnitTest/Utility/test_DeltaTime.py | 2 +- Tests/UnitTest/Utility/test_Logger.py | 2 +- Tests/UnitTest/Utility/test_UnitConverter.py | 2 +- .../UnitTestComon/UntTestUtility.py | 2 +- Tests/UnitTestComon/test_UntTestUtility.py | 73 ++++++++++++++++++ Tests/{UnitTest => }/__init__.py | 0 __init__.py | 2 +- 33 files changed, 151 insertions(+), 31 deletions(-) rename Tests/{UnitTest => }/UnitTestComon/UntTestUtility.py (99%) create mode 100644 Tests/UnitTestComon/test_UntTestUtility.py rename Tests/{UnitTest => }/__init__.py (100%) diff --git a/.coverage b/.coverage index a333a9f2f121283f73aa72f75338af0195d4607a..96ec2d22e04de1cf0182e6e4e15ee981ee31babc 100644 GIT binary patch literal 122880 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 >jHZFkH`9coAqkU@HYc1{Ka$=y8FF) zFC;AQs#n!>>QQwMRsy(MU8K%Z19-mUNOhoEtCnDf$BC*_jaAhupoH9wv|y)vQ9dE> zlec1y$4lioGAn-|k3n9rUha!$Jo;rfPL+TE-~WG~fEjc`d6z4R;^$y7m3Jjk{2ZiM zJ~{7BGf1&~5`~vRish4YhRh(v^2u2bnL&!>lZcH6DV9%82U0AboHo-8QY@duoCSjv z%O?>K4^k|jM3H8YV)-Oe;X#V!lauF|L5k&*lP8(M4%$gP3qDA(d~(7QW{_g}B(mZ` zish47bz+cW`DAjO8KhV~iPFs=#q!Bs3`Vhhvin&xNU?kprJF&D<&!Ag3{otgL>tB+ z#qvokDLqKBe6pk03{otgj7H2L#qvo^F*``Hd@}Ni8KhV~8Nvn>%O_FA8KhV~i8|;Y z#q!D44l_uxe6qFK3{otgL`XeIv3#<*)eKTBpG00gNU?mfvC#}tET3$A!wga^pKN^1 z3{otgL^3@{v3wH6|3Ql7lXV!EV)*JBf3ILiyzTBh4U%^2xWW%^-#H z$v5iFAcgYD*RZz~$|qlGGlLY$C!dX*0)_HPlz9pi$|oP{Hw6milXuQF1q$W+@JwZa zLis*CQ(2%;z7OT60)_H@C_fdPP~LX+xvfGmlehb@B}MXmsIC?$lJ9kfqDa2i8HytL zUS}wZtXvOSoqxv-TGmsjCXLR?dY zLkLmKEF4UTT20}g65eMD-z7wWrf?u3rUWT$Aw(&rZ~);{?89b4%mY%`pAeT!VG|*$ zorR5rsCE`M5aQ}7tS7{jAcg%1`(8AKb%a=(y|9)LMVG=FLR>P1?+|t-Okp)4u9Lzl zLR32oD+y7GDXbtwzd&I*AugH1zJ%d%rm&2#{WViqN{H*Eu!Im(f)w^4MB%frHzC@U z3X2JGaTFF2Vr}-qLc+$dDJ&pt*kuay2~m|P%p*kMvoMzs^_RjNLR=Ar*@T$LyfBLp zh0nrFLQG^{m_azA(G;c=R_r!~X@r3aQx+#!%yPdP2yxZ-Z{p8(l=jDmSvP^EhyqzRv2wF1kaZI) zSC|4>H-S!w0$De)>~>Qi>n2d$Es%8+`z$pDvTg#85f{k12~7W9AnPVj-7S!H6PQn^ zK-Nvn$4fZYb-5Dw@-7s$>qO` za&H0_$dh{$D9Gl?y@?=RnB1Gd8clg}Zvr#6=gGZ^$}dcw+?zm|Hc#$NKofa#Z^CIy zCHE$LXiFvcCOkfqC-)|xi9ESC0Zrt|y(q?+Jh>OOIFl#$q7-NHOAoHT^ zX7XfSRNYLT%!{I%$&-0eb2E7|FG_AEPv-4%4l9|r%Q>uM-Y$4PPv-4{=ksLVu92-K zPv-3keq{1w-Y%!b*fFom)#bDp7uD{W0mv^1c2KPtjlDJKvZ8AFiWc@44J_t|#j`)pHc)-CN_?+cU$HMCW~j2Xm#{ zd+fjCzWW9HG5a3-X3V*FfjwxS{y$Jhe;0G@J!AdRy3@MBy4*U~%37ydM_C7AroFwb z8CLRJo}&MizEGd3&%mAbR=rU#*YhyTUY8z=PKJPXtIu$s{fc@<{ZZYiZcvx2b5&NI zs*b{4_8PUfnt>VizI_&jL3z47P9B0g>}7He*4m59W^~wl#5dw&@s`*wo)q_ErM+v! z#ptj9u{cp2E;ftRVv(38dPS(T&fXsP-`zXiFSs9b-{Zd7eU=(dB02njJokbh`Bo{@O%7QIB%G_lji33JVt%{YlFV<^TY~wuM$66o zN}J_kel~9H#ry<*!ZSbAufJiwbFTin#Q43hzhc%y`b%bkSrV90Hvgiu$9-xVvnUiD=~id>ra_6;y;)%;wQ`)@#E5ny>9(cX~f)6X)uzowsIZfn<1Gq<+sZOmg^ z^;67f9(azkPGo33vx zF>W68Z2~XDOMBBjq_5#Ea1H&s#JGphS2G`djQ&-b&(v2j zA9b|8lKIG^^e>r@7}8fTAGTFr&b;+7eOZ~$)0Z+Ix>aAoe8{2tV&;R7&=)ZuxJ6&c zyyZasi!xuNFJL}ki~c$D=7aV5%W=4GWSo_KVY8RuTN!$w@zW6 zI8UEk=01HAbKgXLB6Ih4eF8I_bUZVh^nGSH={ROM=~!ks=@{lnOdrjRF0iAR(ZqBl zb9dN(tUdV<+h=q_eubSJY^I?jx5q7G)bKUQ*oms>|m z?(Z7X5#9pdL}BKSDs_nY-BG%o`K319R$@Fws#}@yNsTS@JGzB=bd7Fi9yO$!n5#b2 zjm*I+J%$;dS_3mawR+~tQM!&f@VTx($VVPtAe?@#tJZxuCx5iBj2}t%)!x?mstfZ53^L3&5U}C`Cm^Gh9dv~ literal 122880 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 Date: Wed, 12 Nov 2025 12:35:47 +0100 Subject: [PATCH 12/22] doc --- ChangeLog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog.md b/ChangeLog.md index f732a25..8575567 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -5,6 +5,7 @@ ### new things - Base Unit test +- 100% suporting ### breaking changes From 451fd4eb1a7904d52f840b40e28bdd6790a1b8d1 Mon Sep 17 00:00:00 2001 From: VikingInOrbit <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:30:21 +0100 Subject: [PATCH 13/22] 80% start --- .coverage | Bin 122880 -> 122880 bytes ChangeLog.md | 32 +++++++++++++++++++++++ Tests/UnitTest/Utility/test_DeltaTime.py | 19 +++++++++----- 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/.coverage b/.coverage index 96ec2d22e04de1cf0182e6e4e15ee981ee31babc..9826bfbaca0bd8531f1664617168ecef89b4e55e 100644 GIT binary patch delta 29782 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 diff --git a/ChangeLog.md b/ChangeLog.md index 8575567..dc786dd 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,37 @@ # Updates + +## 1.x.2 get to 80% update + +### new things + +- Suproting 100% + +- Utility + - collor 100% + - deltatime 98% + +- Data Prosesing + - csv data pipeline 100% + - filter 96% + - map 91% + +- contol softwere + - State machine + - x 0% + +### bug Fix + +- somthing + +### changes + +- somthing + +### breaking changes + +- somthing + ## 1.x.1 location update ### new things diff --git a/Tests/UnitTest/Utility/test_DeltaTime.py b/Tests/UnitTest/Utility/test_DeltaTime.py index a0ead55..3fe3e47 100644 --- a/Tests/UnitTest/Utility/test_DeltaTime.py +++ b/Tests/UnitTest/Utility/test_DeltaTime.py @@ -1,5 +1,5 @@ import pytest -from GabesPythonToolBox.Utility.DeltaTime import DeltaTime, DeltaTimer +from GabesPythonToolBox.Utility.DeltaTime import DeltaTime, DeltaTimer,StartDeltaTimer from GabesPythonToolBox.Tests.UnitTestComon.UntTestUtility import almost_equal import time @@ -16,24 +16,29 @@ def test_deltatime_basic(): #DeltaTimer tests + def test_deltatimer_start_and_update(): - timer = DeltaTimer(0.05) # 50ms timer + timer = StartDeltaTimer(0.05) # 50ms timer # Initially not started - assert timer.Update() is False + assert timer() is False assert not timer.Finished # Start timer timer.startTimer() time.sleep(0.02) - finished = timer.Update() + finished = timer() assert finished is False # timeLeft should decrease assert timer.timeLeft < timer.duration + # chek finished + time.sleep(0.04) + finished = timer() + assert finished is True def test_deltatimer_finish(): timer = DeltaTimer(0.03) timer.startTimer() time.sleep(0.05) - finished = timer.Update() + finished = timer() assert finished is True assert timer.Finished assert timer.timeLeft <= 0 @@ -58,6 +63,8 @@ def test_deltatimer_stop(): timer.stopTimer() assert timer.Finished assert timer.timeLeft == timer.timeLeft # no change after stop + finished = timer() + assert finished is True def test_deltatimer_recurrent_start_finish(): @@ -69,6 +76,6 @@ def test_deltatimer_recurrent_start_finish(): while i < test_runs: timer.startTimer() time.sleep(0.05) - assert timer.Update() is True + assert timer() is True assert timer.Finished i+=1 From 24f14b563ee767bc2ff438ed1db2b5203a3e9679 Mon Sep 17 00:00:00 2001 From: VikingInOrbit <104018760+VikingInOrbit@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:32:33 +0100 Subject: [PATCH 14/22] doc --- setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 5bcc040..5723f34 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ from setuptools import setup, find_packages setup( - name='GabrielsPythonToolBox', # Name of your package - version='1.8.0', # fundemental change.finished feature.bug fix - packages=find_packages(), # Automatically finds all packages - install_requires=[ # List your dependencies here + name='GabrielsPythonToolBox', + version='1.8.0', # "fundemental change"."finished feature"."bug fix" + packages=find_packages(), + install_requires=[ "matplotlib>=3.10.6,<3.11", "PyYAML>=6.0.2,<7.0", "pytest>=8.4.2,<8.5", @@ -16,8 +16,8 @@ description="A collection of my useful Python tools", long_description=open('README.md').read(), long_description_content_type='text/markdown', - url="https://github.com/NorgeSkiFollo/GabrielsPythonToolBox.git", # Replace with your repo URL - classifiers=[ # Classifiers for your library + url="https://github.com/NorgeSkiFollo/GabrielsPythonToolBox.git", + classifiers=[ 'Programming Language :: Python :: 3', "License :: OSI Approved :: MIT License", 'Operating System :: OS Independent', From aa1a480f1ceea2a7582dc51b496f1b23e169e898 Mon Sep 17 00:00:00 2001 From: VikingInOrbit <104018760+VikingInOrbit@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:46:53 +0100 Subject: [PATCH 15/22] Unit converter --- .coverage | Bin 122880 -> 126976 bytes Tests/UnitTest/Utility/test_UnitConverter.py | 65 ++++++++++++++++++- Utility/UnitConverter.py | 5 +- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/.coverage b/.coverage index 9826bfbaca0bd8531f1664617168ecef89b4e55e..35207ce56d5d15b4d67673a9d5e5d329433daf48 100644 GIT binary patch delta 25922 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