Skip to content

fix: restore a green test suite and fix the bugs it exposed - #9

Merged
vnixx merged 2 commits into
mainfrom
fix/unit-tests
Oct 16, 2026
Merged

vnixx merged 2 commits into
mainfrom
fix/unit-tests

Conversation

@vnixx

@vnixx vnixx commented Oct 16, 2026

Copy link
Copy Markdown
Member

Summary

The unit test suite had drifted to 24 failing tests (38 assertions) on recent Xcode/iOS. Most failures were OS behavior changes or brittle tests, but four of them pointed at real library bugs, one of which crashes apps on iOS 17+. This PR fixes those bugs and makes the suite green and stable: 768 tests, 0 failures, over repeated full runs.

Library fixes

1. Crash: zero-size graphics contexts (iOS 17+)

Since iOS 17, UIGraphicsBeginImageContextWithOptions raises NSInternalInconsistencyException when width or height is 0 (older systems returned an empty context). Snapshotting a view before layout, or processing an empty image, crashed the app.

Every call site whose size can be zero now guards first and returns its existing failure value (nil for optionals, the original image or UIImage() otherwise):

  • UIImage: re(color:size:), withRoundedCorners, both rotated, orientationFixed, resize(toHeight:), resize(toWidth:)
  • UIView.snapshotImage, UIView.snapshot(afterScreenUpdates:), UIScrollView.snapshot
  • UITabBar (selection indicator and item images; the tab bar's frame is zero before layout)

2. Shared DateFormatter state leaked between calls

On the main thread Date.dateFormatter returns one shared instance, but iso8601String and re(iso8601String:) set its locale / timeZone and never restored them. Every later timeString, dateString, dayName, etc. used en_US_POSIX plus a fixed time zone. The getter now resets locale, timeZone and calendar (all null_resettable) before returning the instance.

3. String.re.date / dateTime shared that same instance

Their private static formatters were initialized from Date.dateFormatter, so on the main thread all three were the same object, and parsing depended on whoever last set dateFormat. Each now has its own formatter with en_US_POSIX, as recommended for fixed-format parsing (QA1480). This also fixes parsing for users on Buddhist/Japanese calendars or 12-hour time.

4. AES with an empty IV was nondeterministic

With the default empty iv, the empty Data's baseAddress is a non-nil stack pointer, and CCCrypt read 16 garbage bytes from it as the IV. Encrypt and decrypt could read different bytes, so "123" sometimes decrypted to "12". We now pass nil, so CommonCrypto uses an all-zero IV.

The docs claimed an empty IV meant ECB, but ECB mode was never enabled; they now describe the actual behavior. For input shorter than one block, an all-zero IV with CBC is identical to ECB. Known-answer vectors from openssl were added, because a round trip alone can't catch a garbage IV.

Test fixes

Category Cause Fix
Locale/region Assertions assumed en_US; the dev machine is zh_CN. For example, zh_CN week rules need 5 days in week 1, so 1970-01-01 is week 52. Pin test language/region to en/US in the shared schemes, pass en_US where the API takes a locale, and skip the remaining locale-dependent tests elsewhere
URL parsing Since iOS 17, URL(string:) percent-encodes "invalid url" instead of returning nil Use "http://exa mple.com", which is invalid under both parsers. Assert the lenient String.re.url encoding with an input that behaves the same before and after iOS 17
Image bytes ImageIO now writes extra EXIF metadata (+52 bytes) Compare against the system encoder output and decode the base64 to check size and color
Test bugs attributes(at: 0) on an empty string, the same VC pushed twice, count += 1 on a concurrent queue, a 0.1s debounce window Fixed each test
UIKit changes UIRefreshControl.beginRefreshing() is ignored outside a window. TextKit 2 only estimates contentSize for off-screen text Host the refresh control tests in a window. Compare wrapToContent with a measured height

A second commit applies Xcode 27's project format rewrites (pbxproj entry reordering only, plus a dropped BlueprintName attribute).

Verification

  • ReerKit-iOS: 768 tests, 0 failures across consecutive full runs
  • testAes and testForEachInParallel: 200 iterations each, all passed
  • Locale tests run with -testLanguage zh-Hans -testRegion CN show as skipped, not failed
  • Not run on macOS/tvOS/watchOS/visionOS; the changes there are guards and relaxed assertions

vnixx and others added 2 commits October 16, 2026 15:00
Library fixes:
- UIGraphicsBeginImageContextWithOptions throws NSInternalInconsistencyException
  for a zero width/height since iOS 17. Guard every call site whose size can be
  zero (UIImage color/rounded/rotated/orientationFixed/resize, UIView and
  UIScrollView snapshots, UITabBar) and return the existing failure value.
- Date.dateFormatter hands out one shared main-thread instance, but
  iso8601String/re(iso8601String:) changed its locale/timeZone permanently.
  Reset locale/timeZone/calendar (all null_resettable) before returning it.
- String.re.date/dateTime formatters were initialized from that same shared
  instance, so their dateFormat was overwritten by other callers. Give them
  their own en_US_POSIX formatters, as recommended for fixed-format parsing.
- AES with an empty iv passed the empty Data's non-nil stack baseAddress to
  CCCrypt, which read 16 garbage bytes as the IV, so results were
  nondeterministic. Pass nil so an all-zero IV is used, and fix the docs that
  claimed ECB.

Test fixes:
- Pin the test action language/region to en/US in the shared schemes, pass
  en_US explicitly where APIs accept a locale, and skip the remaining
  locale-dependent tests when run elsewhere.
- Use URLs that are invalid on both the pre- and post-iOS 17 URL parsers.
- Stop asserting exact JPEG/PNG bytes, which change with ImageIO metadata.
- Fix test bugs: attributes(at: 0) on an empty string, pushing the same view
  controller twice, a data race on a concurrent queue, a 0.1s debounce window.
- Host UIRefreshControl tests in a window, which beginRefreshing now requires.
- Compare wrapToContent against a measured height instead of TextKit 2's
  estimated contentSize.
- Add known-answer AES vectors for the empty-iv path.

Co-authored-by: Cursor <cursoragent@cursor.com>
- project.pbxproj: Xcode re-sorted the UserDefaults build file and file
  reference entries; no entries were added or removed.
- watchOS_Example scheme: Xcode dropped the BlueprintName attribute from the
  runnable's BuildableReference.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vnixx
vnixx merged commit dfe1ab9 into main Oct 16, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant