SpicyRPN Release 2.0.0 end-to-end tests
First build the math libraries for each destination you will test, following BUILDING.md. Use the universal libraries for macOS, the arm64 Simulator libraries for an iPhone simulator on Apple silicon, and the device libraries for a connected iPhone. Configure signing for a physical device and keep it unlocked and available to Xcode automation.
For macOS, test both arm64 and x86_64. The default My Mac destination tests only the active architecture. An Apple-silicon Mac can also run the x86_64 destination with Rosetta installed; retain a native Intel run for release validation. An Intel Mac cannot execute arm64 tests.
Choose the test scope before starting. The full suite can take hours: it includes 767 ordered conversion pairs, 45 conversion edge cases, and ALG input-boundary tests with more than 10,000 digit presses and a 4,096-token expression. For a focused check, use the Test navigator to select one method, one feature class, or the non-UI SpicyRPNTests target.
Select the SpicyRPN scheme in Xcode and choose My Mac, an iPhone simulator, or a connected iPhone whose libraries you built. Press ⌘U for the full suite, or run your selected tests from the Test navigator. The shared scheme includes both existing unit tests and UI tests. Run separately for each destination. UI tests are serial so two runners cannot fight over the calculator window, keyboard focus, orientation, or audio output.
The UI target imports XCTest, not the calculator module. It launches the real app, presses real buttons, edits visible parameter sheets, and reads the accessibility value of the displayed number. That value includes all scrolling digits and the exponent exactly once. Separate interaction tests check the digit dragging, mode-specific scrollbars, pinned exponent, layout, calibration control, panels, and stack.
Running and diagnosing
- Numeric failures begin with
FAIL, identify the operation/scenario, and report the first differing normalized decimal position. Full expected and actual strings are attached to the failing activity in the Xcode report. - Parameterized cases have named XCTest activities, so a failure identifies its inputs even when many cases share one test method. A failure stops that method; Xcode continues with the remaining methods.
- Long numeric comparisons preserve every decimal digit. They do not convert the result to
Double, accept matching prefixes, or calculate the expected answer with SpicyRPN.
Reference values
The expected numeric results are literals committed with the tests. Their offline generators are maintenance tools; Xcode never runs Python to create an expected result. Exact arithmetic cases also use explicit integer/rational answers in the test scenario.
| Area | Independent reference calculation |
|---|---|
| Real scientific functions | Direct MPFR C API through Python ctypes, outside the Swift app/bridge, with agreement at 3072 and 4096 bits |
| π, e, ln 2, π × e to 600 places | MPFR reference, additionally checked using mpmath at 750 decimal working digits |
| Complex functions | mpmath 1.3.0, rounded references agreeing at 120 and 180 decimal digits |
| Finance | Python Decimal, closed formulas/direct discounted sums and independent bisection; 110/180-digit agreement |
| Statistics | Python Decimal at 100 digits; sample variance from centered deviations, rather than the app's accumulator formula |
| Unit conversions | Exact rational definitions of SI/imperial units; Decimal Chudnovsky π, with 120/180-digit agreement |
| Seeded random stream | Independent Python SplitMix64 implementation and decimal rounding |
| Dates, stack, flags, entry, storage | Explicit known calendar, integer, literal, and state expectations |
The 600-fractional-place tests select INFIX 650 before creating operands, then FIX 600. Other science and conversion sweeps use explicit SCI settings so screen width cannot change the rounding policy. Finance and complex cases normally check 12 fractional places; the financial iterative solver's tolerance does not promise 600-place solutions. For a mathematical zero returned by an iterative solver, a specifically named check uses the hardcoded zero reference and a hardcoded absolute error bound of 1e-28; FIX deliberately exposes a tiny nonzero residual in scientific notation. The 32°F → 0°C reference similarly allows at most 1e-65 absolute error at INFIX80 for finite-precision affine-offset cancellation. Its expected answer remains zero, rather than a recorded calculator residual. All other numeric comparisons remain exact after decimal-string normalization. TVM references use all four non-target registers and periodic interest. Solved period counts apply HP rounding: a fractional part below 0.005 rounds down, otherwise up. Entered fractional periods retain SpicyRPN’s fractional-exponent compound-growth extension; they are not rounded before solving a cash amount.
Before regenerating a changed answer, review it mathematically; a calculator test failure is never permission to copy the application's output into a reference. Install Python 3.11 or later, a separate MPFR shared library, and mpmath==1.3.0, then run these reference checks from the source root. The statistics generator prints its independently computed table for review; the other generators check the committed Swift literal blocks.
python3 Scripts/generate-scientific-ui-fixtures.py --check
python3 Scripts/generate-complex-ui-fixtures.py --check
python3 Scripts/generate-financial-ui-fixtures.py --check
python3 Scripts/generate-conversion-ui-fixtures.py --check
python3 Scripts/generate-statistics-ui-fixtures.py
Persistence and isolation
Each test gets a UUID in SPICYRPN_TEST_DATABASE, selecting a separate temporary disk-backed SQLite database. It does not use SPICYRPN_EPHEMERAL. Tests clear the banks with the actual MCLR/group sequence at setup and teardown. Seeded associative keys, ordinary registers, statistics and finance are recalled to verify clearing in the baseline and scope tests.
Relaunch tests terminate the app immediately after completed input and reopen the same UUID database. They test pending literals, both complex components, stack/Last values, memories, all financial inputs and compute readiness, cash flows, independent ALG expressions/ANS, shifts/prefixes, settings, dialog drafts, warnings, and scroll preferences. A separate launch does not generate the expected values.
Ordinary development launches continue using their existing application-support database. Disposable test databases are under the app's temporary directory. The test UUID changes for every method and is never shared between destinations.
Small application support changes
Stable accessibility identifiers identify flags, entry status/errors, dialog controls, cash-flow inspection and conversion pickers. The Dimension picker is explicitly a menu on both platforms. These expose existing controls rather than injecting calculator state.
A reproducible RAN# stream is available only in Debug, with all three of SPICYRPN_UI_TESTING=1, a valid SPICYRPN_TEST_DATABASE UUID and SPICYRPN_RANDOM_SEED. Normal and release launches retain system randomness. The seeded generator is test infrastructure, not a new calculator setting.
Limits of automation
Geometry and accessibility checks cannot judge photorealism, subtle engraved shading, or whether the loudspeaker sounds right. Display/control tests attach screenshots and exercise GOAT's actual playback path; visual finish and audible quality still need human review. RPN/ALG functions not assigned to any key (Bessel, programmable expressions, numerical calculus, SOLVE and optimization) are outside this UI suite. Existing lower-layer tests remain in the scheme.
Check the iPad layout in portrait and landscape, in RPN and ALG, with both real and complex displays. Review key spacing, shifted legends, group captions, and the enlarged display, including digit scrolling and the stack disclosure control. In real RPN, check the larger centered digits, absence of the small S1 preview and all scrollbars, and the bottom ruler's clearance from the stack disclosure. Check overflow with CAL shown and hidden, pending-command prompts, errors, and horizontal digit dragging in both main and full-stack views. Scroll a deep stack vertically and confirm each row retains its own horizontal position. In both ALG displays, check the larger digits, bottom expression line, calibration above it, and absence of scrollbars. Check that prompts and errors stay above the numbers, component scrolling remains independent, and CAL does not move the expression. Complex RPN retains its existing layout and scrollbars. Capture release screenshots from the final build after these checks.
For release validation, run the full suite on the supported Apple destinations and retain the Xcode test reports. Source-level checks and fixture regeneration do not replace native build, UI, rendering, and audio checks.
The app's PrivacyInfo.xcprivacy declares SQLite's access to file metadata in the app container under FileTimestamp reason C617.1, with no tracking or data collection. Verify that this resource is present in each native archive. SQLite's Apple implementation also calls fstatfs (and, with its Apple locking style, statfs) for filesystem type and locking flags. Apple lists these APIs in the DiskSpace category, but its approved reasons do not clearly describe this use. Validate the archive with App Store Connect and resolve this mapping with Apple if it is reported; do not declare a disk-capacity reason that the code does not implement. See Apple's required-reason API guidance and API categories and approved reasons.
See UI-Test-Coverage.md for the test-plan mapping.