How GPKit is tested

Five kinds of evidence, each with what it covers and what it doesn't.

1. SGP4 against the C++ it was ported from

GPKit's tests compile David Vallado's SGP4.cpp, unmodified, and run it beside GPKit's port on the same inputs.K-30K-40

2. Passes and positions against Skyfield

Skyfield 1.55, an independent Python library, computed the passes of five public element sets over 40° N, 75° W for two days each. Its answers are committed to GPKit's tests, and CI compares against them.K-42

largest difference (recorded run, 2026-10-07)
49 passes: 95 rises and sets 0.166 s
47 culminations 0.349 s
elevation, azimuth (329 times) 0.0044°, 0.0100°
range, range rate 84.0 m, 1.25 m/s
with UT1 applied as Skyfield does elevation within 0.000001°, range within 0.017 m

K-42 The gap closes when the Earth is turned by UT1 rather than UTC, which Skyfield applies and GPKit doesn't (GPKit takes UT1 to be UTC).K-34K-42 Skyfield's own search stops within half a second of an event.K-42

The two count culminations differently. Skyfield gives every peak of the elevation, and GPKit gives the highest of a pass. The comparison allows for that, pass for pass.K-42

3. SGP4 against python-sgp4, over real catalogs

Every element set of a gpconf corpus copy: 14,411 sets in CSV, JSON, TLE and XML files, each read by each library's own reader and propagated to 0, 90, 720, 1,440, 4,320 and 10,080 minutes from its epoch. Over 86,466 states the largest difference in position is 0.0024 mm, and no error code differs. Recorded 2026-10-07 with python-sgp4 2.27.K-43

This one isn't in CI: it needs a corpus copy that holds the provider files, which isn't in the repository.K-43

4. GPKit's own tests

68 tests in 14 suites, in Swift Testing. They cover:

K-44K-23K-29b

The README's examples are compiled and held to the words the README prints.K-44

CI builds and tests on macOS and Linux, builds for iOS, tvOS and visionOS, and checks that the core compiles with no Foundation.K-14K-04

What isn't here: no fuzzing, mutation testing or property-based tests were found in the repository.K-45

5. gpconf

gpconf is a free test kit that tells you whether your satellite software handles catalog numbers above 99,999 — built from real CelesTrak data, with every expected answer traced to its source.G-01

GPKit ships an adapter for gpconf's runner (Sources/gpconf-adapter). CI runs gpconf on every push, offline: the five cases that need no provider file. A hosted runner's address is shared, and the corpus asks that its fetch not run in a loop or in CI.K-82 In gpconf's own words, a run like that is "5 of 18 cases graded offline; not a full conformance result."G-21b

The result: gpconf 0.8.0

17 of 18 cases pass, each on every item exactly. None fails, none is skipped, and none passes only within a tolerance. The eighteenth, satcat-70000-cutoff, checks data and runs no parser, so it is not exercised.K-83

gpconf 0.8.0 from PyPI, corpus 0.8.0, run 5 November 2026 (00:36 UTC)
GPKit 1229da2, the code of 1.0.0, debug build
platform macOS 27.2 arm64, Apple Swift 6.4.2, Python 3.14.6
data the maintainer's provider files, fetched 21 September 2026: 128 files. The network was closed to the run
cases 18: pass 17, pass-tolerance 0, fail 0, skip 0, not-fetched 0, not-available 0, not-exercised 1
items pass 367, info 106, not-exercised 1
the gate, this month's launches TLE: 256 of 256 loaded; CSV: 256 of 256 loaded. None refused, misidentified or dropped
report SHA-256 f858f93f4fa0f23dbe04ecb5313e63bcc2303cc8653853ffded393b510250a62

K-83

Every case | case | verdict | items | |---|---|---| | `epoch-year-19xx` | pass | pass 34, info 9 | | `baseline-iss-five-formats` | pass | pass 27, info 9 | | `six-digit-omm-saramago` | pass | pass 28, info 10 | | `tle-omits-six-digit-objects` | pass | pass 6, info 3 | | `analyst-objects` | pass | pass 66, info 14 | | `nine-digit-supgp-launch-nominals` | pass | pass 26, info 6 | | `supgp-celestrak-classification-c` | pass | pass 24, info 6 | | `bstar-and-derivative-forms` | pass | pass 24, info 8 | | `satcat-70000-cutoff` | not-exercised | not-exercised 1, info 6 | | `csv-json-omitted-mandatory-fields` | pass | pass 26 | | `mean-motion-derivative-convention` | pass | pass 6 | | `tle-vs-omm-precision-loss` | pass | pass 6 | | `omm-xml-schema` | pass | pass 16, info 8 | | `alpha5-encoding-vectors` | pass | pass 5 | | `alpha5-tle-derived` | pass | pass 18, info 12 | | `kvn-syntax-variants` | pass | pass 29, info 6 | | `tle-writer-alpha5` | pass | pass 5, info 2 | | `corrupt-input` | pass | pass 21, info 7 | K-83

Run it yourself

The commands, from GPKit's own record of the runK-83:

python3 -m venv venv && ./venv/bin/python -m pip install gpconf==0.8.0
swift build
./venv/bin/gpconf run --data DATA --no-fetch-hint -v \
  --cmd "'$PWD/.build/debug/gpkit-gpconf' parse {fmt}" \
  --vectors-cmd "'$PWD/.build/debug/gpkit-gpconf' vectors" \
  --write-cmd "'$PWD/.build/debug/gpkit-gpconf' write" \
  --json report.json
python3 tools/check_gpconf.py report.json --gate

DATA is a folder holding fixtures/<case>/raw/, as gpconf fetch writes it. A later fetch reads other records, so the counts of items can differ; the verdicts should not.K-83 gpconf's own published counts were measured with launch-window files that a new fetch can't obtain.G-26 The provider files' fingerprint is in GPKit's record, with statuses and counts only: an item's detail can quote provider values, so the full report isn't kept.K-83

Reading a gpconf result

status means
pass every check passed exactly
pass-tolerance passed only because of a tolerance (1e-12 relative for a binary float, 2 µs for an epoch), reported apart from an exact pass
fail a check failed
skip the adapter said it can't read that format. That's the adapter's claim, reported and never verified
not-exercised a data check that runs no parser, or data that didn't occur
not-fetched a provider file that isn't on disk
not-available a launch-window capture that no fetch requests

G-21G-22G-23G-24 gpconf's own rule applies to every count: "a count is a result against that version on that date, not a verdict on the project."G-25

Sources

  1. K-30 SGP4 is a port of Vallado's SGP4.cpp (CelesTrak/fundamentals-of-astrodynamics 49df0479c950), with names and order of operations kept. Source: GPKit: NOTICE:9-17; SGP4Core.swift:1-15.
  2. K-40 SGP4 vs the unmodified C++: 33 cases (24 deep space), 2,349 steps, equal error codes, agreeing within 1 micrometre (asserted). Source: GPKit: SGP4Tests.swift:74-75,102-126.
  3. K-40b The same comparison with WGS-72-old, WGS-72 and WGS-84, each in modes i and a: equal error codes, within 1 micrometre. Source: GPKit: SGP4Tests.swift:188-209.
  4. K-41b The oracle compiles SGP4.cpp with #pragma STDC FP_CONTRACT OFF: Clang fuses multiply-adds by default on arm64, and Swift never does. Source: GPKit: Tests/SGP4Oracle/oracle.cpp:1-9.
  5. K-37 The TLE epoch is kept exactly; the C++ reader's round trip moves it by up to 20 µs. Source: GPKit: Propagator.swift:91-93; README.md:237-243.
  6. K-43b From the lines: at the C++ reader's epoch within 1 µm; at GPKit's own epoch, case 23333 under 5 mm and every other under 1 mm (asserted); the two epochs within 25 µs. Source: GPKit: SGP4Tests.swift:128-167.
  7. K-42 Skyfield 1.55: five public element sets over 40° N, 75° W, two days each; 49 passes; 95 rises and sets within 0.166 s; 47 culminations within 0.349 s; look-angle bounds. Source: GPKit: conformance/skyfield-2026-10-07.txt:1-19; test PassTests.swift:168-248.
  8. K-34 Positions are TEME; UT1 is taken as UTC (within 0.9 s); polar motion is left out. Source: GPKit: Observer.swift:102-104; README.md:218-219.
  9. K-43 python-sgp4 2.27: 14,411 element sets, 86,466 states (0, 90, 720, 1,440, 4,320 and 10,080 min), largest difference 0.0024 mm, no differing error codes. Not run in CI. Source: GPKit: conformance/python-sgp4-crosscheck-2026-10-07.txt:5-10; CrossCheckTests.swift:5-14.
  10. K-44 68 tests in 14 suites; the README examples are compiled by tests. Source: GPKit: grep of @Test and @Suite; ReadmeTests.swift:5-6.
  11. K-23 Alpha-5 as Space-Track defines it: A–Z without I and O = 10–33, ceiling 339999. Test vectors. Source: GPKit: Alpha5.swift:1-46; ValueTests.swift:85-101.
  12. K-29b What was read is written back byte for byte (test). Source: GPKit: TLETests.swift:155-160.
  13. K-14 No Foundation outside Date.swift, enforced by a script in CI. Source: GPKit: Package.swift:14-15; tools/check-no-foundation.sh; ci.yml:24-31.
  14. K-04 Platforms: macOS 13, iOS 16, tvOS 16, visionOS 1. Linux is built in CI. Source: GPKit: Package.swift:9; .github/workflows/ci.yml:16.
  15. K-45 No fuzz, mutation or property tests. Source: GPKit: a grep that found none.
  16. G-01 The written sentence (quoted exactly). Source: gpconf: docs/BRAND.md:40.
  17. K-82 CI runs gpconf's 5 offline cases; 13 need provider files that gpconf fetches and does not ship. Source: GPKit: README.md:153-155; ci.yml:33-56; tools/check_gpconf.py.
  18. G-21b gpconf's run line for an offline run: "5 of 18 cases graded offline; not a full conformance result.". Source: gpconf: docs/ADAPTERS.md:688-690.
  19. K-83 gpconf 0.8.0 (PyPI), corpus 0.8.0, run 2026-11-05 00:36:31Z: GPKit 1229da2 (1.0.0's code), debug, macOS 27.2 arm64, Swift 6.4.2, Python 3.14.6; the maintainer's provider files of 2026-09-21 (128), network closed; cases 18, pass 17, pass-tolerance 0, fail 0, skip 0, not-fetched 0, not-available 0, not-exercised 1 (satcat-70000-cutoff); items pass 367, info 106, not-exercised 1; gate 256/256 TLE and CSV; report SHA-256 f858f93f…0a62; the commands; per-case verdicts and item counts. Source: GPKit: conformance/gpconf-0.8.0-2026-11-05.txt on main at 6d6ce58 (PR #1, merged 2026-11-05T00:42:49Z; first read at 216459a).
  20. G-26 Published counts were measured with launch-window files that a new user's fetch can't obtain. Source: gpconf: README.md:380-382.
  21. G-21 How a case's status is chosen (first of fail, pass-tolerance, pass, not-exercised, not-fetched, not-available, skip). Source: gpconf: docs/ADAPTERS.md:677-680.
  22. G-22 pass-tolerance is reported apart from an exact pass; tolerances are 1e-12 relative and 2 µs. Source: gpconf: README.md:348-355.
  23. G-23 A skip is the adapter's claim, reported and never verified. Source: gpconf: docs/ADAPTERS.md:66-69.
  24. G-24 not-fetched and not-available definitions. Source: gpconf: README.md:359-371.
  25. G-25 "a count is a result against that version on that date, not a verdict on the project". Source: gpconf: README.md:242.