StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | //! Memory-behavior repro for APP-4154 batch 1.C (strato_ui-platform-nsstring). |
| 2 | //! |
| 3 | //! Exercises the `NSString::alloc(nil).init_str(...)` → `make_nsstring(...)` conversions |
| 4 | //! applied to `pasteboard_type_for_image_mime_type` and related clipboard |
| 5 | //! helpers. The helper is shared by every retained-NSString site in this file |
| 6 | //! (6 in `read_image_data_from_pasteboard`, 2 in `Clipboard::write`, and this |
| 7 | //! one in `pasteboard_type_for_image_mime_type`), so it is representative for |
| 8 | //! the whole file. |
| 9 | //! |
| 10 | //! On master the raw `NSString::alloc(nil).init_str(...)` returns a string with |
| 11 | //! a +1 retain count that no pool drain can clean up, so even inside an outer |
| 12 | //! `NSAutoreleasePool` the string survives past `pool.drain()` and memory grows |
| 13 | //! linearly with iteration count. |
| 14 | //! |
| 15 | //! On the PR branch `make_nsstring` autoreleases, so every string returned here |
| 16 | //! is released when the surrounding pool drains and peak RSS stays flat across |
| 17 | //! outer iterations. |
| 18 | //! |
| 19 | //! Run as: |
| 20 | //! cargo test --release -p strato_ui \ |
| 21 | //! pasteboard_type_for_image_mime_type_memory_behavior -- --nocapture --ignored |
| 22 | //! and measure peak RSS with `/usr/bin/time -l`. |
| 23 | use cocoa::base::nil; |
| 24 | use cocoa::foundation::NSAutoreleasePool; |
| 25 | |
| 26 | use super::pasteboard_type_for_image_mime_type; |
| 27 | |
| 28 | /// Number of outer pool cycles. Each cycle creates an `NSAutoreleasePool`, |
| 29 | /// runs the inner loop, then drains. On master the retained NSStrings survive |
| 30 | /// the drain, so memory usage grows proportionally to OUTER * INNER. |
| 31 | const OUTER: usize = 60; |
| 32 | |
| 33 | /// Number of inner iterations per pool cycle. Must be large enough to produce |
| 34 | /// a measurable RSS delta but small enough to fit easily in memory on the |
| 35 | /// branch side (where strings are reclaimed per cycle). |
| 36 | const INNER: usize = 20_000; |
| 37 | |
| 38 | const MIME_TYPES: &[&str] = &[ |
| 39 | "image/png", |
| 40 | "image/jpeg", |
| 41 | "image/gif", |
| 42 | "image/webp", |
| 43 | "image/svg+xml", |
| 44 | ]; |
| 45 | |
| 46 | #[test] |
| 47 | #[ignore = "memory repro; run with --ignored --nocapture in release mode"] |
| 48 | fn pasteboard_type_for_image_mime_type_memory_behavior() { |
| 49 | unsafe { |
| 50 | for _ in 0..OUTER { |
| 51 | let pool = NSAutoreleasePool::new(nil); |
| 52 | for _ in 0..INNER { |
| 53 | for mime in MIME_TYPES { |
| 54 | let ns = pasteboard_type_for_image_mime_type(mime); |
| 55 | assert!(ns.is_some(), "mime {mime} should be mapped"); |
| 56 | } |
| 57 | } |
| 58 | pool.drain(); |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 |