#!/usr/bin/env python3 # UNDF: UNDF-2026-000001277 (selenium-0001), # UNDF-2026-000001288 (selenium-0002) # # CWE-407: Algorithmic Complexity # # Defects: # selenium-0001: SessionCapabilitiesMutator.mergeChromiumOptions / # mergeFirefoxOptions dedup args+extensions via # List.contains inside forEach -> O(N*M) per session. # selenium-0002: ChromiumOptions.mergeInPlace and mergeInOptionsFromCaps # apply the same list.contains dedup pattern across four # merge loops, giving O(N*M) per merge. # # Fixes: # selenium-0001: Pre-build a LinkedHashSet from stereotype args/extensions # once, then iterate incoming with set.add() semantics. # Order preserved, cost drops to O(N+M). # selenium-0002: Helpers addArgumentsUnique / addEncodedExtensionsUnique # consolidate the four merge loops behind a HashSet-backed # dedup. Cost drops to O(N+M) per call. # # Complexity gates (from bench/results.txt on this machine): # selenium-0001: N=M=1000 defective=24.7ms, fixed=0.13ms. Fixed must # complete in <5ms. k-scaling: time(5x) / time(1x) < 17.5x. # selenium-0002: N=M=1000 defective=121.9ms, fixed=0.48ms. Fixed must # complete in <5ms. k-scaling: time(5x) / time(1x) < 17.5x. import os import sys import unittest HERE = os.path.dirname(os.path.abspath(__file__)) BENCH = os.path.join(os.path.dirname(HERE), "bench") sys.path.insert(0, BENCH) import importlib.util def _load(fname): path = os.path.join(BENCH, fname) spec = importlib.util.spec_from_file_location(fname, path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) return mod _mod_0001 = _load("bench-selenium-0001.py") _mod_0002 = _load("bench-selenium-0002.py") # --------------------------------------------------------------------------- # Correctness: fix must produce the same merged list as the defective version. # --------------------------------------------------------------------------- def _merge_defective(stereotype, incoming): result = list(stereotype) for arg in incoming: if arg not in result: result.append(arg) return result def _merge_fixed(stereotype, incoming): result = list(stereotype) seen = set(result) for arg in incoming: if arg not in seen: seen.add(arg) result.append(arg) return result class TestSelenium0001Correctness(unittest.TestCase): def test_merge_empty_stereotype(self): self.assertEqual( _merge_fixed([], ["--a", "--b", "--a"]), _merge_defective([], ["--a", "--b", "--a"]), ) def test_merge_overlapping(self): stereo = ["--x", "--y"] inc = ["--y", "--z", "--x", "--w"] self.assertEqual(_merge_fixed(stereo, inc), _merge_defective(stereo, inc)) def test_merge_order_preserved(self): stereo = ["--a", "--b", "--c"] inc = ["--d", "--e"] fixed = _merge_fixed(stereo, inc) self.assertEqual(fixed, ["--a", "--b", "--c", "--d", "--e"]) class TestSelenium0001ComplexityGate(unittest.TestCase): def test_fixed_wallclock_N1000(self): t_s = _mod_0001.bench_fixed(1000, 1000) self.assertLess(t_s * 1000, 5.0, f"fixed took {t_s*1000:.3f}ms at N=M=1000, expected <5ms") def test_fixed_scaling_linear(self): t_100 = min(_mod_0001.bench_fixed(100, 100) for _ in range(3)) t_500 = min(_mod_0001.bench_fixed(500, 500) for _ in range(3)) # Ensure fixed scales ~linearly: 5x input should cost <17.5x (not 25x) ratio = t_500 / t_100 if t_100 > 0 else float("inf") self.assertLess(ratio, 17.5, f"fixed N=500/N=100 ratio {ratio:.2f}x, expected <17.5x (O(N))") class TestSelenium0002ComplexityGate(unittest.TestCase): def test_fixed_wallclock_N1000(self): t_s = _mod_0002.bench_fixed(1000, 1000) self.assertLess(t_s * 1000, 5.0, f"fixed took {t_s*1000:.3f}ms at N=M=1000, expected <5ms") def test_fixed_scaling_linear(self): t_100 = min(_mod_0002.bench_fixed(100, 100) for _ in range(3)) t_500 = min(_mod_0002.bench_fixed(500, 500) for _ in range(3)) ratio = t_500 / t_100 if t_100 > 0 else float("inf") self.assertLess(ratio, 17.5, f"fixed N=500/N=100 ratio {ratio:.2f}x, expected <17.5x (O(N))") if __name__ == "__main__": unittest.main(verbosity=2)