From 751298f487b4afe0bc73cc69bd4833a6e3a6d7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Thu, 8 Jan 2026 17:05:59 +0100 Subject: [PATCH 1/6] Add unit tests --- .github/workflows/pipeline.yml | 43 ++++++++++++++++++++++++++++++++++ tests/test_shared.py | 10 ++++++++ 2 files changed, 53 insertions(+) create mode 100644 tests/test_shared.py diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 8c07cf8..3f1caa2 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -159,3 +159,46 @@ jobs: jupyter-book build . --path-output public mv public/_build/html public_html touch public_html/.nojekyll + + unittest_matrix: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + include: + - operating-system: macos-latest + python-version: '3.14' + + - operating-system: ubuntu-latest + python-version: '3.14' + + - operating-system: windows-latest + python-version: '3.14' + + - operating-system: ubuntu-latest + python-version: '3.13' + + - operating-system: ubuntu-latest + python-version: '3.12' + + - operating-system: ubuntu-latest + python-version: '3.11' + + steps: + - uses: actions/checkout@v4 + - name: Setup environment + shell: bash -l {0} + run: | + echo -e "channels:\n - conda-forge\n" > .condarc + - name: Setup Mambaforge + uses: conda-incubator/setup-miniconda@v3 + with: + python-version: ${{ matrix.python-version }} + miniforge-version: latest + condarc-file: .condarc + environment-file: binder/environment.yml + - name: Test + shell: bash -l {0} + timeout-minutes: 30 + run: | + pip install . --no-deps --no-build-isolation + python -m unittest discover tests diff --git a/tests/test_shared.py b/tests/test_shared.py new file mode 100644 index 0000000..696bcf3 --- /dev/null +++ b/tests/test_shared.py @@ -0,0 +1,10 @@ +import unittest +from python_workflow_definition.shared import get_dict, get_list + + +class TestShared(unittest.TestCase): + def test_get_dict(self): + self.assertEqual({"a": 1, "b": 2, "c": 3}, get_dict(a=1, b=2, c=3)) + + def test_get_list(self): + self.assertEqual([1, 2, 3], get_list(a=1, b=2, c=3)) From d18822eef6f0c8c7d5d842e40bbfeb749f5e5f8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Thu, 8 Jan 2026 17:09:50 +0100 Subject: [PATCH 2/6] remove python version --- .github/workflows/pipeline.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 3f1caa2..86a9101 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -189,6 +189,7 @@ jobs: shell: bash -l {0} run: | echo -e "channels:\n - conda-forge\n" > .condarc + sed '/- python/d' binder/environment.yml - name: Setup Mambaforge uses: conda-incubator/setup-miniconda@v3 with: From 8f8e3056494d69a4de0acc19cea66bc8c53caef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Thu, 8 Jan 2026 17:12:13 +0100 Subject: [PATCH 3/6] more fixes --- .github/workflows/pipeline.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 86a9101..7b03854 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -189,14 +189,14 @@ jobs: shell: bash -l {0} run: | echo -e "channels:\n - conda-forge\n" > .condarc - sed '/- python/d' binder/environment.yml + sed '/- python/d' binder/environment.yml > environment.yml - name: Setup Mambaforge uses: conda-incubator/setup-miniconda@v3 with: python-version: ${{ matrix.python-version }} miniforge-version: latest condarc-file: .condarc - environment-file: binder/environment.yml + environment-file: environment.yml - name: Test shell: bash -l {0} timeout-minutes: 30 From b7a7ddb5ee1b74b5d110f605bd4d19662fae5011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jan=C3=9Fen?= Date: Thu, 8 Jan 2026 17:14:43 +0100 Subject: [PATCH 4/6] Restrict python version --- .github/workflows/pipeline.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 7b03854..75ab7f9 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -166,18 +166,12 @@ jobs: matrix: include: - operating-system: macos-latest - python-version: '3.14' + python-version: '3.12' - operating-system: ubuntu-latest - python-version: '3.14' + python-version: '3.12' - operating-system: windows-latest - python-version: '3.14' - - - operating-system: ubuntu-latest - python-version: '3.13' - - - operating-system: ubuntu-latest python-version: '3.12' - operating-system: ubuntu-latest From bf57dfcecf2c0e0a8bfd1ca4c98f06760c589854 Mon Sep 17 00:00:00 2001 From: Jan Janssen Date: Thu, 8 Jan 2026 17:19:19 +0100 Subject: [PATCH 5/6] Update pipeline.yml --- .github/workflows/pipeline.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 75ab7f9..cab65cd 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -171,9 +171,6 @@ jobs: - operating-system: ubuntu-latest python-version: '3.12' - - operating-system: windows-latest - python-version: '3.12' - - operating-system: ubuntu-latest python-version: '3.11' From e3b3c37987807503d9feb26c3d78885074351e44 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 17:56:09 +0100 Subject: [PATCH 6/6] feat: Add unit tests for shared.py to achieve 100% coverage (#151) Adds a comprehensive test suite for the `python_workflow_definition/shared.py` module, increasing test coverage to 100%. The new tests cover all functions in the module, including: - `get_kwargs` - `get_source_handles` - `convert_nodes_list_to_dict` - `update_node_names` - `set_result_node` - `remove_result` In addition to happy path scenarios, the tests also cover edge cases such as empty list inputs and error conditions, ensuring the robustness of the module. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- tests/test_shared.py | 148 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/tests/test_shared.py b/tests/test_shared.py index 696bcf3..db908e4 100644 --- a/tests/test_shared.py +++ b/tests/test_shared.py @@ -1,5 +1,20 @@ import unittest -from python_workflow_definition.shared import get_dict, get_list +from python_workflow_definition.shared import ( + get_dict, + get_list, + get_kwargs, + get_source_handles, + convert_nodes_list_to_dict, + update_node_names, + set_result_node, + remove_result, + EDGES_LABEL, + NODES_LABEL, + SOURCE_LABEL, + SOURCE_PORT_LABEL, + TARGET_LABEL, + TARGET_PORT_LABEL, +) class TestShared(unittest.TestCase): @@ -8,3 +23,134 @@ def test_get_dict(self): def test_get_list(self): self.assertEqual([1, 2, 3], get_list(a=1, b=2, c=3)) + + def test_get_kwargs(self): + lst = [ + { + TARGET_PORT_LABEL: "a", + SOURCE_LABEL: "s1", + SOURCE_PORT_LABEL: "sp1", + }, + { + TARGET_PORT_LABEL: "b", + SOURCE_LABEL: "s2", + SOURCE_PORT_LABEL: "sp2", + }, + ] + self.assertEqual( + { + "a": {SOURCE_LABEL: "s1", SOURCE_PORT_LABEL: "sp1"}, + "b": {SOURCE_LABEL: "s2", SOURCE_PORT_LABEL: "sp2"}, + }, + get_kwargs(lst), + ) + + def test_get_kwargs_empty(self): + self.assertEqual({}, get_kwargs([])) + + def test_get_source_handles(self): + edges_lst = [ + {SOURCE_LABEL: "s1", SOURCE_PORT_LABEL: "sp1"}, + {SOURCE_LABEL: "s1", SOURCE_PORT_LABEL: "sp2"}, + {SOURCE_LABEL: "s2", SOURCE_PORT_LABEL: "sp3"}, + ] + self.assertEqual( + {"s1": ["sp1", "sp2"], "s2": ["sp3"]}, get_source_handles(edges_lst) + ) + + def test_get_source_handles_no_port(self): + edges_lst = [ + {SOURCE_LABEL: "s1", SOURCE_PORT_LABEL: None}, + {SOURCE_LABEL: "s1", SOURCE_PORT_LABEL: None}, + ] + self.assertEqual({"s1": [0, 1]}, get_source_handles(edges_lst)) + + def test_get_source_handles_empty(self): + self.assertEqual({}, get_source_handles([])) + + def test_convert_nodes_list_to_dict(self): + nodes_list = [ + {"id": 1, "name": "n1"}, + {"id": 2, "value": "v2"}, + {"id": 0, "name": "n0"}, + ] + self.assertEqual( + {"0": "n0", "1": "n1", "2": "v2"}, + convert_nodes_list_to_dict(nodes_list), + ) + + def test_convert_nodes_list_to_dict_empty(self): + self.assertEqual({}, convert_nodes_list_to_dict([])) + + def test_update_node_names(self): + workflow_dict = { + NODES_LABEL: [ + {"id": 0, "type": "input", "name": ""}, + {"id": 1, "type": "input", "name": ""}, + {"id": 2, "type": "input", "name": ""}, + {"id": 3, "type": "other", "name": ""}, + ], + EDGES_LABEL: [ + {SOURCE_LABEL: 0, TARGET_PORT_LABEL: "a"}, + {SOURCE_LABEL: 1, TARGET_PORT_LABEL: "b"}, + {SOURCE_LABEL: 2, TARGET_PORT_LABEL: "a"}, + ], + } + update_node_names(workflow_dict) + self.assertEqual("a_0", workflow_dict[NODES_LABEL][0]["name"]) + self.assertEqual("b", workflow_dict[NODES_LABEL][1]["name"]) + self.assertEqual("a_1", workflow_dict[NODES_LABEL][2]["name"]) + + def test_set_result_node(self): + workflow_dict = { + NODES_LABEL: [{"id": 0, "type": "input"}], + EDGES_LABEL: [], + } + set_result_node(workflow_dict) + self.assertEqual(2, len(workflow_dict[NODES_LABEL])) + self.assertEqual("output", workflow_dict[NODES_LABEL][1]["type"]) + self.assertEqual(1, len(workflow_dict[EDGES_LABEL])) + + def test_set_result_node_already_exists(self): + workflow_dict = { + NODES_LABEL: [ + {"id": 0, "type": "input"}, + {"id": 1, "type": "output"}, + ], + EDGES_LABEL: [{SOURCE_LABEL: 0, TARGET_LABEL: 1}], + } + set_result_node(workflow_dict) + self.assertEqual(3, len(workflow_dict[NODES_LABEL])) + self.assertEqual("output", workflow_dict[NODES_LABEL][2]["type"]) + self.assertEqual(2, len(workflow_dict[EDGES_LABEL])) + + def test_set_result_node_no_end_node(self): + workflow_dict = { + NODES_LABEL: [{"id": 0}, {"id": 1}], + EDGES_LABEL: [ + {SOURCE_LABEL: 0, TARGET_LABEL: 1}, + {SOURCE_LABEL: 1, TARGET_LABEL: 0}, + ], + } + with self.assertRaises(IndexError): + set_result_node(workflow_dict) + + def test_remove_result(self): + workflow_dict = { + NODES_LABEL: [ + {"id": 0, "type": "input"}, + {"id": 1, "type": "output"}, + ], + EDGES_LABEL: [{SOURCE_LABEL: 0, TARGET_LABEL: 1}], + } + new_workflow = remove_result(workflow_dict) + self.assertEqual(1, len(new_workflow[NODES_LABEL])) + self.assertEqual(0, len(new_workflow[EDGES_LABEL])) + + def test_remove_result_no_output(self): + workflow_dict = { + NODES_LABEL: [{"id": 0, "type": "input"}], + EDGES_LABEL: [], + } + with self.assertRaises(IndexError): + remove_result(workflow_dict)