java-topology/defects/go-libp2p-0001/test/go_libp2p_0001_test.py

128 lines
3.7 KiB
Python

#!/usr/bin/env python3
"""
Unit test for go-libp2p-0001: identify protocol diff() uses
slices.Contains inside loop = O(N^2) protocol comparison.
Fix: use map[protocol.ID]struct{} for O(1) lookups = O(N) total.
File: p2p/protocol/identify/id.go
Function: diff(a, b []protocol.ID) (added, removed []protocol.ID)
"""
import time
import unittest
# --- DEFECTIVE VERSION: O(N^2) ---
def diff_defective(a, b):
"""Original: slices.Contains(a, x) inside for-range b loop."""
added = []
removed = []
for x in b:
if x not in a: # linear scan = O(N) per element
added.append(x)
for x in a:
if x not in b: # linear scan = O(N) per element
removed.append(x)
return added, removed
# --- PATCHED VERSION: O(N) ---
def diff_patched(a, b):
"""Patched: build sets for O(1) lookups."""
a_set = set(a)
b_set = set(b)
added = [x for x in b if x not in a_set]
removed = [x for x in a if x not in b_set]
return added, removed
class TestDiffCorrectness(unittest.TestCase):
"""Verify patched version matches defective version output."""
def _check(self, a, b, want_add, want_rem):
add_d, rem_d = diff_defective(a, b)
add_p, rem_p = diff_patched(a, b)
self.assertEqual(add_d, add_p, "added mismatch between versions")
self.assertEqual(rem_d, rem_p, "removed mismatch between versions")
self.assertEqual(add_p, want_add)
self.assertEqual(rem_p, want_rem)
def test_no_change(self):
self._check(
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
[], []
)
def test_one_added(self):
self._check(
["/ipfs/id/1.0.0"],
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
["/ipfs/ping/1.0.0"], []
)
def test_one_removed(self):
self._check(
["/ipfs/id/1.0.0", "/ipfs/ping/1.0.0"],
["/ipfs/id/1.0.0"],
[], ["/ipfs/ping/1.0.0"]
)
def test_both_added_and_removed(self):
self._check(
["/proto/a", "/proto/b"],
["/proto/b", "/proto/c"],
["/proto/c"], ["/proto/a"]
)
def test_empty_to_many(self):
self._check(
[],
["/p/1", "/p/2", "/p/3"],
["/p/1", "/p/2", "/p/3"], []
)
def test_many_to_empty(self):
self._check(
["/p/1", "/p/2", "/p/3"],
[],
[], ["/p/1", "/p/2", "/p/3"]
)
class TestDiffPerformance(unittest.TestCase):
"""Benchmark: patched should be significantly faster at scale."""
def test_performance_scaling(self):
sizes = [10, 100, 500, 1000]
for n in sizes:
# Build two protocol lists with ~50% overlap
a = [f"/proto/{i}" for i in range(n)]
b = [f"/proto/{i}" for i in range(n // 2, n + n // 2)]
iters = 200
# Benchmark defective (list-based O(N^2))
start = time.perf_counter()
for _ in range(iters):
diff_defective(a, b)
defective_time = time.perf_counter() - start
# Benchmark patched (set-based O(N))
start = time.perf_counter()
for _ in range(iters):
diff_patched(a, b)
patched_time = time.perf_counter() - start
ratio = defective_time / patched_time if patched_time > 0 else float('inf')
print(f" N={n:4d} defective={defective_time:.4f}s patched={patched_time:.4f}s ratio={ratio:.1f}x")
if n >= 100:
self.assertGreater(ratio, 2.0,
f"N={n}: expected patched to be at least 2x faster, got {ratio:.1f}x")
if __name__ == "__main__":
unittest.main()