From 16e0a3d0902633478f16bc38bf88e582c5b45941 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 7 Aug 2026 20:56:25 +0530 Subject: [PATCH 1/2] fix: return after peer color cache hit Color(peerSeed:isDark:) checked the cache but never returned on a hit, so every call recomputed the hue/saturation/brightness math and overwrote the cache entry with an identical value. The cache never actually short-circuited anything. --- bitchat/Utils/Color+Peer.swift | 12 ++++++- bitchatTests/Utils/ColorPeerTests.swift | 45 +++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 bitchatTests/Utils/ColorPeerTests.swift diff --git a/bitchat/Utils/Color+Peer.swift b/bitchat/Utils/Color+Peer.swift index e2ab3a15..2a5a0d0a 100644 --- a/bitchat/Utils/Color+Peer.swift +++ b/bitchat/Utils/Color+Peer.swift @@ -10,12 +10,22 @@ import SwiftUI extension Color { private static var peerColorCache: [String: Color] = [:] - + + #if DEBUG + /// Counts cache-miss computations; exposed only so regression tests can + /// verify a repeated seed hits the cache instead of recomputing. + static var _peerColorComputeCountForTesting = 0 + #endif + init(peerSeed: String, isDark: Bool) { let cacheKey = peerSeed + (isDark ? "|dark" : "|light") if let cached = Self.peerColorCache[cacheKey] { self = cached + return } + #if DEBUG + Self._peerColorComputeCountForTesting += 1 + #endif let h = peerSeed.djb2() var hue = Double(h % 1000) / 1000.0 let orange = 30.0 / 360.0 diff --git a/bitchatTests/Utils/ColorPeerTests.swift b/bitchatTests/Utils/ColorPeerTests.swift new file mode 100644 index 00000000..023459ee --- /dev/null +++ b/bitchatTests/Utils/ColorPeerTests.swift @@ -0,0 +1,45 @@ +// +// ColorPeerTests.swift +// bitchatTests +// +// Tests for Color(peerSeed:isDark:) caching +// + +import Testing +import SwiftUI +@testable import bitchat + +struct ColorPeerTests { + + @Test func repeatedSeedHitsCacheInsteadOfRecomputing() { + let seed = "cache-hit-\(UUID().uuidString)" + + let before = Color._peerColorComputeCountForTesting + let first = Color(peerSeed: seed, isDark: true) + let afterFirst = Color._peerColorComputeCountForTesting + let second = Color(peerSeed: seed, isDark: true) + let afterSecond = Color._peerColorComputeCountForTesting + + #expect(afterFirst == before + 1) + #expect(afterSecond == afterFirst) + #expect(first == second) + } + + @Test func differentAppearanceForSameSeedIsNotCachedTogether() { + let seed = "appearance-\(UUID().uuidString)" + + let before = Color._peerColorComputeCountForTesting + _ = Color(peerSeed: seed, isDark: true) + _ = Color(peerSeed: seed, isDark: false) + let after = Color._peerColorComputeCountForTesting + + #expect(after == before + 2) + } + + @Test func sameSeedProducesSameColor() { + let seed = "deterministic-\(UUID().uuidString)" + let a = Color(peerSeed: seed, isDark: false) + let b = Color(peerSeed: seed, isDark: false) + #expect(a == b) + } +} From 83acfc4510d04719b518131de2b25a8602b1a99f Mon Sep 17 00:00:00 2001 From: Taksh Date: Mon, 17 Aug 2026 10:49:46 +0530 Subject: [PATCH 2/2] test: serialize the peer colour cache suite The compute-count assertions read a process-global counter, and swift testing runs a suite's tests in parallel by default, so one test computing a colour between another's two reads would fail it for a reason that has nothing to do with the cache. The seeds are unique per test; the counter is not. No other test touches _peerColorComputeCountForTesting, so serializing this suite is enough. --- bitchatTests/Utils/ColorPeerTests.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bitchatTests/Utils/ColorPeerTests.swift b/bitchatTests/Utils/ColorPeerTests.swift index 023459ee..12de5185 100644 --- a/bitchatTests/Utils/ColorPeerTests.swift +++ b/bitchatTests/Utils/ColorPeerTests.swift @@ -9,6 +9,10 @@ import Testing import SwiftUI @testable import bitchat +/// Serialized: the compute-count assertions read a process-global counter, +/// so a sibling test computing a colour between two reads would fail them for +/// the wrong reason. The seeds are unique per test, but the counter is not. +@Suite(.serialized) struct ColorPeerTests { @Test func repeatedSeedHitsCacheInsteadOfRecomputing() {