diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e69de29..500d8bf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -0,0 +1,27 @@ +name: Unit Tests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + - name: Run tests with pytest + run: | + pytest tests/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8a4a849 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +__pycache__/ +*.py[cod] +*$py.class +.coverage +coverage.xml +.pytest_cache/ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..623a184 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "node-diff" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "pytest>=8.3.4", +] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..824415a --- /dev/null +++ b/pytest.ini @@ -0,0 +1,4 @@ +[pytest] +pythonpath = . +testpaths = tests +python_files = test_*.py diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1 @@ +pytest diff --git a/src/validate_nodes.py b/src/validate_nodes.py index 521ea10..7f8925f 100644 --- a/src/validate_nodes.py +++ b/src/validate_nodes.py @@ -8,7 +8,6 @@ class BreakingChangeType(Enum): RETURN_TYPES_CHANGED = "Return types changed" - RETURN_TYPES_REORDERED = "Return types reordered" INPUT_REMOVED = "Required input removed" INPUT_TYPE_CHANGED = "Input type changed" NODE_REMOVED = "Node removed" @@ -91,36 +90,17 @@ def compare_return_types(node_name: str, base_class: Type, pr_class: Type) -> li base_types = getattr(base_class, "RETURN_TYPES", tuple()) pr_types = getattr(pr_class, "RETURN_TYPES", tuple()) - if len(base_types) != len(pr_types): - changes.append(BreakingChange( - node_name=node_name, - change_type=BreakingChangeType.RETURN_TYPES_CHANGED, - details=f"Number of return types changed from {len(base_types)} to {len(pr_types)}", - base_value=base_types, - pr_value=pr_types - )) - return changes - - # Check for type changes and reordering - base_types_set = set(base_types) - pr_types_set = set(pr_types) - - if base_types_set != pr_types_set: - changes.append(BreakingChange( - node_name=node_name, - change_type=BreakingChangeType.RETURN_TYPES_CHANGED, - details="Return types changed", - base_value=base_types, - pr_value=pr_types - )) - elif base_types != pr_types: - changes.append(BreakingChange( - node_name=node_name, - change_type=BreakingChangeType.RETURN_TYPES_REORDERED, - details="Return types were reordered", - base_value=base_types, - pr_value=pr_types - )) + # Check if all base return types are preserved in PR + for i, base_type in enumerate(base_types): + if i >= len(pr_types) or pr_types[i] != base_type: + changes.append(BreakingChange( + node_name=node_name, + change_type=BreakingChangeType.RETURN_TYPES_CHANGED, + details="Return types changed or removed. This is a breaking change.", + base_value=base_types, + pr_value=pr_types + )) + return changes return changes diff --git a/tests/test_validate_nodes.py b/tests/test_validate_nodes.py new file mode 100644 index 0000000..8992021 --- /dev/null +++ b/tests/test_validate_nodes.py @@ -0,0 +1,60 @@ +import pytest +from src.validate_nodes import compare_return_types, BreakingChangeType + +class BaseNode: + RETURN_TYPES = ("STRING", "INT", "FLOAT") + +class PRNode: + RETURN_TYPES = ("STRING", "INT", "FLOAT") + +class ReorderedNode: + RETURN_TYPES = ("INT", "STRING", "FLOAT") + +class IncompatibleNode: + RETURN_TYPES = ("STRING", "BOOL", "FLOAT") + +class AddTypeNode: + RETURN_TYPES = ("STRING", "INT", "FLOAT", "BOOL") + +class RemoveTypeNode: + RETURN_TYPES = ("STRING", "INT") + +class NoReturnTypesNode: + pass + +def test_compare_return_types_no_changes(): + changes = compare_return_types("TestNode", BaseNode, PRNode) + assert len(changes) == 0 + + +def test_compare_return_types_add_type(): + changes = compare_return_types("TestNode", BaseNode, AddTypeNode) + assert len(changes) == 0 + +def test_compare_return_types_remove_type(): + changes = compare_return_types("TestNode", BaseNode, RemoveTypeNode) + assert len(changes) == 1 + assert changes[0].change_type == BreakingChangeType.RETURN_TYPES_CHANGED + assert changes[0].base_value == ("STRING", "INT", "FLOAT") + assert changes[0].pr_value == ("STRING", "INT") + +def test_compare_return_types_reordered(): + changes = compare_return_types("TestNode", BaseNode, ReorderedNode) + assert len(changes) == 1 + assert changes[0].change_type == BreakingChangeType.RETURN_TYPES_CHANGED + assert changes[0].base_value == ("STRING", "INT", "FLOAT") + assert changes[0].pr_value == ("INT", "STRING", "FLOAT") + +def test_compare_return_types_incompatible(): + changes = compare_return_types("TestNode", BaseNode, IncompatibleNode) + assert len(changes) == 1 + assert changes[0].change_type == BreakingChangeType.RETURN_TYPES_CHANGED + assert changes[0].base_value == ("STRING", "INT", "FLOAT") + assert changes[0].pr_value == ("STRING", "BOOL", "FLOAT") + +def test_compare_return_types_missing(): + changes = compare_return_types("TestNode", BaseNode, NoReturnTypesNode) + assert len(changes) == 1 + assert changes[0].change_type == BreakingChangeType.RETURN_TYPES_CHANGED + assert changes[0].base_value == ("STRING", "INT", "FLOAT") + assert changes[0].pr_value == tuple() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1fb390e --- /dev/null +++ b/uv.lock @@ -0,0 +1,64 @@ +version = 1 +requires-python = ">=3.12" + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "node-diff" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "pytest" }, +] + +[package.metadata] +requires-dist = [{ name = "pytest", specifier = ">=8.3.4" }] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +]