From 4a3854dac15d5de808be0800348057b36f5170dd Mon Sep 17 00:00:00 2001 From: John McCardle Date: Thu, 9 Apr 2026 21:45:12 -0400 Subject: [PATCH] Fix audit type count (44->46) and add regression test for Color __eq__, refs #307 Review of session 1b14b941 found two issues: - Exported type count was 44 in audit doc but array has 46 entries (EntityCollection3DIterType and one other were not counted) - No regression test existed for the Color.__eq__/__ne__ fix Co-Authored-By: Claude Opus 4.6 --- docs/api-audit-2026-04.md | 4 +- tests/regression/issue_307_color_eq_test.py | 86 +++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 tests/regression/issue_307_color_eq_test.py diff --git a/docs/api-audit-2026-04.md b/docs/api-audit-2026-04.md index 8ea9ee9..4cc08c0 100644 --- a/docs/api-audit-2026-04.md +++ b/docs/api-audit-2026-04.md @@ -32,7 +32,7 @@ ## Executive Summary -The McRogueFace Python API exposes **44 exported types**, **14 internal types**, **10 enums**, **13 module-level functions**, **7 module-level properties**, and **5 singleton instances** through the `mcrfpy` module. +The McRogueFace Python API exposes **46 exported types**, **14 internal types**, **10 enums**, **13 module-level functions**, **7 module-level properties**, and **5 singleton instances** through the `mcrfpy` module. Overall, the API is remarkably consistent. Properties and methods use snake_case throughout the type system. The major inconsistencies are concentrated in a few areas: @@ -930,7 +930,7 @@ Some types use raw string docstrings for methods instead of MCRF_METHOD macros. | Category | Count | |----------|-------| -| Exported types | 44 | +| Exported types | 46 | | Internal types | 14 | | Enums | 10 | | Module functions | 13 | diff --git a/tests/regression/issue_307_color_eq_test.py b/tests/regression/issue_307_color_eq_test.py new file mode 100644 index 0000000..9168c87 --- /dev/null +++ b/tests/regression/issue_307_color_eq_test.py @@ -0,0 +1,86 @@ +"""Regression test for issue #307: Color.__eq__/__ne__ for value comparison. + +Color had __hash__ but no __eq__/__ne__, meaning two Colors with identical RGBA +values compared by identity (id()) rather than by value. This violated the Python +convention that objects with __hash__ should also implement __eq__. +""" +import mcrfpy +import sys + +errors = [] + +def check(condition, msg): + if not condition: + errors.append(msg) + +# Basic equality +c1 = mcrfpy.Color(255, 0, 0) +c2 = mcrfpy.Color(255, 0, 0) +c3 = mcrfpy.Color(0, 255, 0) + +check(c1 == c2, "Same RGBA should be equal") +check(not (c1 != c2), "Same RGBA should not be not-equal") +check(c1 != c3, "Different RGBA should be not-equal") +check(not (c1 == c3), "Different RGBA should not be equal") + +# Alpha matters +c4 = mcrfpy.Color(255, 0, 0, 128) +check(c1 != c4, "Different alpha should be not-equal") + +# Self-equality +check(c1 == c1, "Color should equal itself") + +# Equality with tuples +check(c1 == (255, 0, 0), "Color should equal 3-tuple") +check(c1 == (255, 0, 0, 255), "Color should equal 4-tuple with matching alpha") +check(c1 != (255, 0, 0, 128), "Color should not equal tuple with different alpha") +check(c1 != (0, 0, 0), "Color should not equal different tuple") + +# Equality with lists +check(c1 == [255, 0, 0], "Color should equal list") +check(c1 == [255, 0, 0, 255], "Color should equal list with alpha") + +# Reflected comparisons (tuple on left) +check((255, 0, 0) == c1, "Reflected == should work") +check(not ((255, 0, 0) != c1), "Reflected != should work for equal") +check((0, 255, 0) != c1, "Reflected != should work for unequal") + +# Non-comparable types return NotImplemented (Python converts to False/True) +check(not (c1 == "red"), "Color vs string should not be equal") +check(c1 != "red", "Color vs string should be not-equal") +check(not (c1 == 42), "Color vs int should not be equal") +check(not (c1 == None), "Color vs None should not be equal") + +# Wrong-size tuples return NotImplemented +check(not (c1 == (255,)), "Color vs 1-tuple should not be equal") +check(not (c1 == (255, 0)), "Color vs 2-tuple should not be equal") +check(not (c1 == (255, 0, 0, 255, 0)), "Color vs 5-tuple should not be equal") + +# Ordering operators should raise TypeError +try: + c1 < c2 + errors.append("< should raise TypeError") +except TypeError: + pass + +try: + c1 > c2 + errors.append("> should raise TypeError") +except TypeError: + pass + +# Hash consistency: equal objects must have equal hashes +check(hash(c1) == hash(c2), "Equal colors must have equal hashes") + +# Explicit black +c_black = mcrfpy.Color(0, 0, 0) +check(c_black == (0, 0, 0, 255), "Color(0,0,0) should equal (0,0,0,255)") + +if errors: + for e in errors: + print(f"FAIL: {e}") + print(f"\n{len(errors)} error(s)") + sys.exit(1) +else: + print("PASS") + sys.exit(0)