From c73c238fdc435f5dd6d542162c0c37c7e770256f Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Tue, 23 Jun 2026 19:06:32 +0530 Subject: [PATCH 1/3] fix: prevent duplicate activities in array and self-referential relations --- networkdiagram/networkdiagram/networkdiagram.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/networkdiagram/networkdiagram/networkdiagram.py b/networkdiagram/networkdiagram/networkdiagram.py index a80199c..09c0247 100644 --- a/networkdiagram/networkdiagram/networkdiagram.py +++ b/networkdiagram/networkdiagram/networkdiagram.py @@ -202,14 +202,18 @@ def add_activities_relations(self, activities: List[str], durations: List[float] self._validate_activity_name(act) self._validate_duration(dur, act) self._validate_duplicate(act) + if act in all_valid_activities: + raise ValueError(f"Activity '{act}' already exists. Duplicate names are not allowed.") all_valid_activities.add(act) # Validate predecessors exist (check before adding) - for pred_list in predecessors: + for act, pred_list in zip(activities, predecessors): if pred_list == '-' or pred_list == '': continue preds = [p.strip() for p in pred_list.split(',') if p.strip()] for pred in preds: + if pred == act: + raise ValueError(f"Self-referential dependency detected: '{act}' cannot be a predecessor of itself.") # Check if predecessor exists in current nodes or will be added if pred not in self.nodes and pred not in all_valid_activities: raise ValueError( @@ -255,6 +259,8 @@ def add_relation(self, cur: str, predecessors: str) -> None: if predecessors and predecessors != '-': preds = [p.strip() for p in predecessors.split(',') if p.strip()] for p in preds: + if p == cur: + raise ValueError(f"Self-referential dependency detected: '{cur}' cannot be a predecessor of itself.") if p != 'O' and p not in self.nodes: raise ValueError( f"Predecessor '{p}' does not exist. " From 1e7db0dd9c4393c0e10445e4db419f3ad2d6606e Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Fri, 26 Jun 2026 19:27:19 +0530 Subject: [PATCH 2/3] fix: prevent duplicate activities in array and self-referential relations (#44) - Prevent full traceback on validation errors by wrapping main block in try-except - Add unit tests for duplicate activities and self-referential relations checks --- .../__pycache__/__init__.cpython-313.pyc | Bin 256 -> 259 bytes .../networkdiagram.cpython-313.pyc | Bin 19349 -> 39210 bytes .../networkdiagram/networkdiagram.py | 29 ++++++++------- tests/test_duplicate_and_self_referential.py | 35 ++++++++++++++++++ 4 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 tests/test_duplicate_and_self_referential.py diff --git a/networkdiagram/networkdiagram/__pycache__/__init__.cpython-313.pyc b/networkdiagram/networkdiagram/__pycache__/__init__.cpython-313.pyc index 108f3905f621a66231e73f37f9ff17ab26c61345..3028701e17524cbac6c7011da4c7653c97c2e9ea 100644 GIT binary patch delta 41 vcmZo*YG&g8%*)Hg00e67Ruj1!guI-sVnT~ki;82sJv~DV3=9k>&PoFS(mD%M delta 38 scmZo>YGC62%*)Hg00e)l^d@pQ@Vh%(#e^2878S=NWh7>2Pn??u0Lh~a9RL6T diff --git a/networkdiagram/networkdiagram/__pycache__/networkdiagram.cpython-313.pyc b/networkdiagram/networkdiagram/__pycache__/networkdiagram.cpython-313.pyc index d37536ce03dd8545da5d81d16e5ba9898aa16541..d9304bc5235b8ce0ae740932a016a06f26384604 100644 GIT binary patch literal 39210 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

0=@{PMcB gnU%6dwr*Ls7PFXmf0=A8`@mYUv4gXgu+{qi0Sm6d_y7O^ delta 4853 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 diff --git a/networkdiagram/networkdiagram/networkdiagram.py b/networkdiagram/networkdiagram/networkdiagram.py index 09c0247..452b1d0 100644 --- a/networkdiagram/networkdiagram/networkdiagram.py +++ b/networkdiagram/networkdiagram/networkdiagram.py @@ -783,16 +783,19 @@ def display_crash_schedule(self, crash_schedule): crash_costs = [1000, 700, 900, 500, 1100] crash_durations = [4, 2, 3, 2, 2] - cpm.add_activities_relations( - activities, durations, predecessors, - normal_costs=normal_costs, - crash_costs=crash_costs, - crash_durations=crash_durations - ) - - crash_schedule = cpm.crash_project(target_duration=13) - cpm.display_crash_schedule(crash_schedule) - # Display results - cpm.network_summary() - cpm.display_network() - cpm.generate_gantt_chart() + try: + cpm.add_activities_relations( + activities, durations, predecessors, + normal_costs=normal_costs, + crash_costs=crash_costs, + crash_durations=crash_durations + ) + + crash_schedule = cpm.crash_project(target_duration=13) + cpm.display_crash_schedule(crash_schedule) + # Display results + cpm.network_summary() + cpm.display_network() + cpm.generate_gantt_chart() + except ValueError as e: + print(f"Error: {e}") diff --git a/tests/test_duplicate_and_self_referential.py b/tests/test_duplicate_and_self_referential.py new file mode 100644 index 0000000..0a9b87e --- /dev/null +++ b/tests/test_duplicate_and_self_referential.py @@ -0,0 +1,35 @@ +import unittest +import sys +import os + +# Add parent directory to path so we can import networkdiagram +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'networkdiagram', 'networkdiagram'))) +from networkdiagram import CriticalPathMethod, Node + +class TestDuplicateAndSelfReferential(unittest.TestCase): + def setUp(self): + self.cpm = CriticalPathMethod() + self.cpm.add_activity('O', 0) + + def test_duplicate_activity_in_array(self): + activities = ['A', 'B', 'A'] + durations = [1, 2, 3] + predecessors = ['-', 'A', 'B'] + + with self.assertRaises(ValueError) as context: + self.cpm.add_activities_relations(activities, durations, predecessors) + + self.assertIn("Activity 'A' already exists. Duplicate names are not allowed.", str(context.exception)) + + def test_self_referential_dependency(self): + activities = ['A', 'B', 'C'] + durations = [1, 2, 3] + predecessors = ['-', 'B', 'B'] # 'B' is predecessor of 'B' + + with self.assertRaises(ValueError) as context: + self.cpm.add_activities_relations(activities, durations, predecessors) + + self.assertIn("Self-referential dependency detected: 'B' cannot be a predecessor of itself.", str(context.exception)) + +if __name__ == '__main__': + unittest.main() From eda7fc5cfdd08aa6f81f4cf70c229530d59b01a9 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Sat, 27 Jun 2026 04:10:02 +0530 Subject: [PATCH 3/3] fix: hide traceback for ValueError exceptions and add test cases --- .../networkdiagram.cpython-313.pyc | Bin 39210 -> 39627 bytes .../networkdiagram/networkdiagram.py | 10 ++++++++++ tests/test_duplicate_and_self_referential.py | 11 +++++++++++ 3 files changed, 21 insertions(+) diff --git a/networkdiagram/networkdiagram/__pycache__/networkdiagram.cpython-313.pyc b/networkdiagram/networkdiagram/__pycache__/networkdiagram.cpython-313.pyc index d9304bc5235b8ce0ae740932a016a06f26384604..359e382b27dc8b6b6a78c5f7b90bf034015ab552 100644 GIT binary patch delta 4485 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 j;QLCYu#!8Z|Ehz=O9G&?$yClXp diff --git a/networkdiagram/networkdiagram/networkdiagram.py b/networkdiagram/networkdiagram/networkdiagram.py index 452b1d0..5bae819 100644 --- a/networkdiagram/networkdiagram/networkdiagram.py +++ b/networkdiagram/networkdiagram/networkdiagram.py @@ -122,6 +122,8 @@ def _validate_duration(self, duration: float, activity_name: str = "") -> None: def _validate_duplicate(self, name: str) -> None: if name in self.nodes: + import sys + sys.tracebacklimit = 0 raise ValueError(f"Activity '{name}' already exists. Duplicate names are not allowed.") def _detect_cycles(self) -> None: @@ -132,6 +134,8 @@ def _detect_cycles(self) -> None: def dfs(node: str) -> None: if node in rec_stack: cycle_nodes = list(rec_stack) + [node] + import sys + sys.tracebacklimit = 0 raise ValueError(f"Circular dependency detected: {' -> '.join(cycle_nodes)}") if node in visited: return @@ -203,6 +207,8 @@ def add_activities_relations(self, activities: List[str], durations: List[float] self._validate_duration(dur, act) self._validate_duplicate(act) if act in all_valid_activities: + import sys + sys.tracebacklimit = 0 raise ValueError(f"Activity '{act}' already exists. Duplicate names are not allowed.") all_valid_activities.add(act) @@ -213,6 +219,8 @@ def add_activities_relations(self, activities: List[str], durations: List[float] preds = [p.strip() for p in pred_list.split(',') if p.strip()] for pred in preds: if pred == act: + import sys + sys.tracebacklimit = 0 raise ValueError(f"Self-referential dependency detected: '{act}' cannot be a predecessor of itself.") # Check if predecessor exists in current nodes or will be added if pred not in self.nodes and pred not in all_valid_activities: @@ -260,6 +268,8 @@ def add_relation(self, cur: str, predecessors: str) -> None: preds = [p.strip() for p in predecessors.split(',') if p.strip()] for p in preds: if p == cur: + import sys + sys.tracebacklimit = 0 raise ValueError(f"Self-referential dependency detected: '{cur}' cannot be a predecessor of itself.") if p != 'O' and p not in self.nodes: raise ValueError( diff --git a/tests/test_duplicate_and_self_referential.py b/tests/test_duplicate_and_self_referential.py index 0a9b87e..6010650 100644 --- a/tests/test_duplicate_and_self_referential.py +++ b/tests/test_duplicate_and_self_referential.py @@ -31,5 +31,16 @@ def test_self_referential_dependency(self): self.assertIn("Self-referential dependency detected: 'B' cannot be a predecessor of itself.", str(context.exception)) + def test_duplicate_activity_across_calls(self): + self.cpm.add_activity('A', 5) + activities = ['B', 'A'] + durations = [2, 3] + predecessors = ['-', '-'] + + with self.assertRaises(ValueError) as context: + self.cpm.add_activities_relations(activities, durations, predecessors) + + self.assertIn("Activity 'A' already exists. Duplicate names are not allowed.", str(context.exception)) + if __name__ == '__main__': unittest.main()