DeriveLifetime resolves a mock's runtime Lifetime from its on-disk metadata tag, with a legacy-format fallback for recordings captured before the tag convention was universally applied. Idempotency: safe to call more than once on the same mock. The several ingest paths (disk loader, syncMock.AddMoc
()
| 115 | // visible signal (surfaced at replay completion); per-call logging is |
| 116 | // deliberately avoided because DeriveLifetime runs on every mock load. |
| 117 | func (m *Mock) DeriveLifetime() { |
| 118 | // Idempotent short-circuit. LifetimeDerived is distinct from the |
| 119 | // Lifetime field itself because LifetimePerTest IS the zero value |
| 120 | // — without the bool, a PerTest classification is |
| 121 | // indistinguishable from "never derived", and the heavy switch + |
| 122 | // kind-fallback path would re-run on every ingest site (disk → |
| 123 | // StoreMocks → syncMock). The bool also guards |
| 124 | // legacyKindFallbackFires from double-counting when a mock passes |
| 125 | // through multiple ingest paths. |
| 126 | if m.TestModeInfo.LifetimeDerived { |
| 127 | return |
| 128 | } |
| 129 | defer func() { m.TestModeInfo.LifetimeDerived = true }() |
| 130 | |
| 131 | // Protocol-specific override that runs BEFORE the tag-based |
| 132 | // classification. A small allowlist of MySQL command-phase packet |
| 133 | // types have input-independent responses (COM_PING → OK, |
| 134 | // COM_STATISTICS → stats blob, COM_DEBUG → server-side no-op, |
| 135 | // COM_RESET_CONNECTION → OK) and are typically recorded at app |
| 136 | // startup (JDBC / HikariCP pool warm-up) BEFORE any test window |
| 137 | // begins. Without this override they'd be tagged "mocks" (per- |
| 138 | // test) by the recorder → strict-window pre-filter drops them → |
| 139 | // replay fails at connection init with "no matching mock". The |
| 140 | // promotion keeps the on-disk tag unchanged (backward compatible |
| 141 | // with older replayers) but steers the in-memory routing so the |
| 142 | // mock lands in the session pool here. |
| 143 | // |
| 144 | // Deliberately narrow: only the four commands whose response we |
| 145 | // know is input-independent. COM_QUERY / COM_INIT_DB / |
| 146 | // COM_CHANGE_USER / COM_SET_OPTION all depend on input and must |
| 147 | // stay per-test. |
| 148 | if m.Kind == MySQL && mysqlIsSessionReusableCommand(m) { |
| 149 | m.TestModeInfo.Lifetime = LifetimeSession |
| 150 | return |
| 151 | } |
| 152 | tag := "" |
| 153 | if m.Spec.Metadata != nil { |
| 154 | tag = m.Spec.Metadata["type"] |
| 155 | } |
| 156 | switch tag { |
| 157 | case "config": |
| 158 | m.TestModeInfo.Lifetime = LifetimeSession |
| 159 | return |
| 160 | case "connection": |
| 161 | // LifetimeConnection requires a non-empty connID so the |
| 162 | // per-connID pool lookup (GetConnectionMocks) has a stable |
| 163 | // key. A mock tagged "connection" without a connID is |
| 164 | // malformed — fall through to session semantics (still |
| 165 | // reusable, just not connection-scoped) rather than |
| 166 | // promoting it to per-test (which would be consumed on |
| 167 | // first match and break replay for the paired execute). |
| 168 | if m.Spec.Metadata["connID"] != "" { |
| 169 | m.TestModeInfo.Lifetime = LifetimeConnection |
| 170 | return |
| 171 | } |
| 172 | m.TestModeInfo.Lifetime = LifetimeSession |
| 173 | return |
| 174 | } |