(self)
| 185 | |
| 186 | @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") |
| 187 | def test_copy_build_request(self) -> None: |
| 188 | options = FinalRequestOptions(method="get", url="/foo") |
| 189 | |
| 190 | def build_request(options: FinalRequestOptions) -> None: |
| 191 | client = self.client.copy() |
| 192 | client._build_request(options) |
| 193 | |
| 194 | # ensure that the machinery is warmed up before tracing starts. |
| 195 | build_request(options) |
| 196 | gc.collect() |
| 197 | |
| 198 | tracemalloc.start(1000) |
| 199 | |
| 200 | snapshot_before = tracemalloc.take_snapshot() |
| 201 | |
| 202 | ITERATIONS = 10 |
| 203 | for _ in range(ITERATIONS): |
| 204 | build_request(options) |
| 205 | |
| 206 | gc.collect() |
| 207 | snapshot_after = tracemalloc.take_snapshot() |
| 208 | |
| 209 | tracemalloc.stop() |
| 210 | |
| 211 | def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.StatisticDiff) -> None: |
| 212 | if diff.count == 0: |
| 213 | # Avoid false positives by considering only leaks (i.e. allocations that persist). |
| 214 | return |
| 215 | |
| 216 | if diff.count % ITERATIONS != 0: |
| 217 | # Avoid false positives by considering only leaks that appear per iteration. |
| 218 | return |
| 219 | |
| 220 | for frame in diff.traceback: |
| 221 | if any( |
| 222 | frame.filename.endswith(fragment) |
| 223 | for fragment in [ |
| 224 | # to_raw_response_wrapper leaks through the @functools.wraps() decorator. |
| 225 | # |
| 226 | # removing the decorator fixes the leak for reasons we don't understand. |
| 227 | "opencode_ai/_legacy_response.py", |
| 228 | "opencode_ai/_response.py", |
| 229 | # pydantic.BaseModel.model_dump || pydantic.BaseModel.dict leak memory for some reason. |
| 230 | "opencode_ai/_compat.py", |
| 231 | # Standard library leaks we don't care about. |
| 232 | "/logging/__init__.py", |
| 233 | ] |
| 234 | ): |
| 235 | return |
| 236 | |
| 237 | leaks.append(diff) |
| 238 | |
| 239 | leaks: list[tracemalloc.StatisticDiff] = [] |
| 240 | for diff in snapshot_after.compare_to(snapshot_before, "traceback"): |
| 241 | add_leak(leaks, diff) |
| 242 | if leaks: |
| 243 | for leak in leaks: |
| 244 | print("MEMORY LEAK:", leak) |
nothing calls this directly
no test coverage detected