Testing Guide #
This document describes the test infrastructure, verification methodology, and test results for LibEphemeris.
Table of Contents #
- Test Results Overview
- Expected Failures (xfail)
- Running All Tests (Without Skips)
- Full Test Command
- Categories of Skipped Tests
- Considerations
- Quick Reference
- Testing with SPK Kernels (Downstream Projects)
Test Results Overview #
When running pytest, the output may show results like:
6336 passed
4 xpassed
3 xfailed
57 skipped
What do these mean? #
| Status | Meaning |
|---|---|
| passed | Test completed successfully |
| xpassed | Test was expected to fail, but passed (unexpected success) |
| xfailed | Test was expected to fail and did fail (expected behavior) |
| skipped | Test was intentionally skipped due to missing dependencies |
Note:
xfailedtests are NOT failures! They document known limitations or future goals.
Expected Failures (xfail) #
These tests are marked as “expected to fail” for documented reasons:
| Test | Reason |
|---|---|
test_deltat_future_years_match_swe |
Delta T for future dates (>2030) is a prediction - different algorithms give divergent results |
test_interpolated_apogee_target_precision |
Target precision <0.1° requires implementing analytical Moshier method |
test_interpolated_perigee_target_precision |
Target precision <0.1° requires implementing analytical Moshier method |
| Benchmark tests | Performance comparisons between pure Python and C (pyswisseph) - Python is inherently slower |
Running All Tests (Without Skips) #
To run the complete test suite without skips, the following conditions must be satisfied.
1. Install pyswisseph #
Most skipped tests require the C-based Swiss Ephemeris library for comparison:
pip install pyswisseph
2. Install Optional Dependencies #
pip install requests # Required for orbital elements update tests
3. Enable Network Tests #
Some tests require network access to download data from JPL Horizons. Enable them with environment variables:
# Enable SPK auto-download tests
export LIBEPHEMERIS_TEST_SPK_AUTO_DOWNLOAD=1
# Enable SPK download tests
export LIBEPHEMERIS_TEST_SPK_DOWNLOAD=1
4. Ephemeris Data Files #
Some tests require ephemeris data files in swisseph/ephe/:
fictitious_orbits.csv- bundled orbital elements dataset (included in package)- Asteroid/TNO data files
Full Test Command #
Run all tests with network tests enabled:
LIBEPHEMERIS_TEST_SPK_AUTO_DOWNLOAD=1 \
LIBEPHEMERIS_TEST_SPK_DOWNLOAD=1 \
pytest
Or set the variables permanently in the shell:
export LIBEPHEMERIS_TEST_SPK_AUTO_DOWNLOAD=1
export LIBEPHEMERIS_TEST_SPK_DOWNLOAD=1
pytest
Categories of Skipped Tests #
pyswisseph-dependent (majority of skips) #
Tests that compare LibEphemeris results against pyswisseph:
test_lunar/test_true_lilith_latitude.pytest_lunar/test_true_lilith_precision.pytest_minor_bodies/test_tno_validation.pytest_sol_eclipse_where_how.py- And many more…
Network-dependent #
Tests that download data from external sources:
| Test File | Environment Variable |
|---|---|
test_spk_auto.py |
LIBEPHEMERIS_TEST_SPK_AUTO_DOWNLOAD=1 |
test_spk.py |
LIBEPHEMERIS_TEST_SPK_DOWNLOAD=1 |
test_asteroid_by_number.py |
Always skipped (requires JPL SBDB) |
Data-dependent (runtime skips) #
These tests skip dynamically based on available data:
- Eclipse tests: skip if no eclipse found in search period
- Star tests: skip if star not in catalog
- TNO tests: skip if SwissEph data not available
- Historical tests: skip if date outside ephemeris range
Considerations #
- Network tests are slow - they download data from JPL Horizons
- Some skips are unavoidable - dynamic skips depend on runtime data availability
- pyswisseph versions vary - some versions don’t include eclipse functions
- Benchmark xfails are expected - pure Python will always be slower than C
Quick Reference #
| Goal | Command |
|---|---|
| Run all tests | pytest |
| Run with verbose output | pytest -v |
| Run with network tests | LIBEPHEMERIS_TEST_SPK_AUTO_DOWNLOAD=1 LIBEPHEMERIS_TEST_SPK_DOWNLOAD=1 pytest |
| Run specific test file | pytest tests/test_file.py |
| Run tests matching pattern | pytest -k "pattern" |
| Show skip reasons | pytest -rs |
| Show xfail reasons | pytest -rx |
Testing with SPK Kernels (Downstream Projects) #
Projects that depend on LibEphemeris (e.g., kerykeion) often need high-precision asteroid calculations using SPK kernels. This section provides ready-to-use pytest fixtures for configuring SPK auto-download in a test suite.
The Problem #
By default, LibEphemeris uses Keplerian orbital elements for minor bodies like Chiron, Ceres, Vesta, etc. This provides ~10-30 arcsecond accuracy. For sub-arcsecond precision, SPK kernels must be downloaded from JPL Horizons.
Without proper test configuration:
- First test run downloads SPK files (slow, network-dependent)
- Each test might trigger redundant download checks
- CI environments may fail if network is unavailable
- Test isolation becomes difficult
Recommended Fixture Pattern #
Add this fixture to the project’s conftest.py:
import pytest
import libephemeris as eph
from libephemeris.constants import SE_CHIRON, SE_CERES, SE_PALLAS, SE_JUNO, SE_VESTA
@pytest.fixture(scope="session", autouse=True)
def setup_spk_for_tests():
"""
Session-scoped fixture to pre-download SPK kernels for major asteroids.
This ensures all SPK files are downloaded once at the start of the test
session, rather than on-demand during individual tests. This improves
test speed and reliability.
"""
# Enable automatic SPK download
eph.set_auto_spk_download(True)
# Pre-download SPK kernels for commonly used major asteroids
# These are the bodies with automatic SPK download support
major_asteroids = [
SE_CHIRON, # Centaur, commonly used in astrology
SE_CERES, # Dwarf planet
SE_PALLAS, # Main belt asteroid
SE_JUNO, # Main belt asteroid
SE_VESTA, # Main belt asteroid
]
for body in major_asteroids:
# ensure_major_asteroid_spk returns True if SPK is available
# (either already cached or successfully downloaded)
eph.ensure_major_asteroid_spk(body)
yield
# Optional: cleanup after all tests
# eph.set_auto_spk_download(False)
Minimal Fixture (Chiron Only) #
For projects that only need Chiron (the most commonly used minor body):
import pytest
import libephemeris as eph
from libephemeris.constants import SE_CHIRON
@pytest.fixture(scope="session", autouse=True)
def setup_chiron_spk():
"""Pre-download Chiron SPK for high-precision calculations."""
eph.set_auto_spk_download(True)
eph.ensure_major_asteroid_spk(SE_CHIRON)
yield
Environment Variables #
LibEphemeris respects these environment variables for testing:
| Variable | Values | Description |
|---|---|---|
LIBEPHEMERIS_AUTO_SPK |
1, true, yes |
Enable auto SPK download globally |
LIBEPHEMERIS_LOG_LEVEL |
DEBUG, INFO, WARNING, ERROR |
Control logging verbosity |
Source Tracing (DEBUG logs) #
At DEBUG level, libephemeris logs which calculation backend was used for
every body at every dispatch point. The log format is:
body=<id> jd=<julian_day> source=<BACKEND>
Example output:
[libephemeris] DEBUG: body=0 jd=2448045.9167 source=LEB
[libephemeris] DEBUG: body=15 jd=2448045.9167 source=SPK
[libephemeris] DEBUG: body=146199 jd=2448045.9167 source=ASSIST (n-body)
Possible source values:
| Value | Description |
|---|---|
LEB |
Precomputed .leb binary ephemeris (Chebyshev polynomial fast-path) |
LEB->fallback |
LEB lookup failed, retrying with Skyfield (the next log line shows the actual source) |
Skyfield |
NASA JPL DE440/DE441 via Skyfield |
Horizons |
NASA JPL Horizons online API |
SPK |
Direct SPK kernel evaluation (minor bodies) |
SPK (auto-downloaded) |
SPK kernel auto-downloaded for this body |
ASSIST (n-body) |
N-body integration via ASSIST |
Keplerian (fallback) |
Analytical Keplerian orbit (last resort, logged at WARNING) |
These log statements are emitted in planets.py (swe_calc_ut(),
swe_calc(), _calc_body()) and context.py (calc_ut(), calc()).
To enable source tracing during tests:
LIBEPHEMERIS_LOG_LEVEL=DEBUG pytest -s tests/
Or programmatically:
import logging
logging.getLogger("libephemeris").setLevel(logging.DEBUG)
The Astrologer API uses this mechanism via an opt-in X-Debug-Ephemeris: true
HTTP header that temporarily lowers the logger to DEBUG, captures source=
entries, and injects them into the JSON response.
Programmatic Tracing (ContextVar-based) #
For structured, machine-readable source tracing in application code or tests,
use start_tracing() / get_trace_results() instead of DEBUG logging:
import libephemeris as swe
from libephemeris.constants import SE_SUN, SEFLG_SPEED
token = swe.start_tracing()
swe.calc_ut(2451545.0, SE_SUN, SEFLG_SPEED)
traces = swe.get_trace_results() # {0: "LEB"}
token.var.reset(token)
This returns a {body_id: source_name} dict and is thread-safe via
ContextVar. Overhead is ~50 ns when inactive. See the
Tracing Guide for full details, thread safety,
and nested tracing.
Custom SPK Cache Directory #
For custom storage locations:
import pytest
import libephemeris as eph
from pathlib import Path
@pytest.fixture(scope="session", autouse=True)
def setup_spk_with_custom_cache(tmp_path_factory):
"""Setup SPK with custom cache directory."""
# Use a persistent cache directory for CI
cache_dir = Path.home() / ".cache" / "libephemeris-spk"
cache_dir.mkdir(parents=True, exist_ok=True)
eph.set_spk_cache_dir(str(cache_dir))
eph.set_auto_spk_download(True)
# Pre-download all major asteroids
from libephemeris.constants import SE_CHIRON, SE_CERES, SE_PALLAS, SE_JUNO, SE_VESTA
for body in [SE_CHIRON, SE_CERES, SE_PALLAS, SE_JUNO, SE_VESTA]:
eph.ensure_major_asteroid_spk(body)
yield
Disabling Strict Precision Mode #
LibEphemeris has a “strict precision” mode that raises errors when SPK kernels are not available for major asteroids. For tests that don’t require SPK precision:
import pytest
import libephemeris as eph
@pytest.fixture(autouse=True)
def disable_strict_precision():
"""Allow Keplerian fallback for minor body calculations."""
original = eph.get_strict_precision()
eph.set_strict_precision(False)
yield
eph.set_strict_precision(original if original else None)
Complete Example for Downstream Projects #
Here’s a complete conftest.py for a project using libephemeris:
"""
pytest configuration for a project using libephemeris.
"""
import pytest
import libephemeris as eph
from libephemeris.constants import (
SE_CHIRON, SE_CERES, SE_PALLAS, SE_JUNO, SE_VESTA,
SE_SIDM_FAGAN_BRADLEY,
)
@pytest.fixture(scope="session", autouse=True)
def setup_libephemeris():
"""
Session-scoped setup for libephemeris.
Downloads SPK kernels once at session start for consistent,
high-precision asteroid calculations across all tests.
"""
# Enable SPK auto-download
eph.set_auto_spk_download(True)
# Disable strict mode to allow Keplerian fallback if download fails
eph.set_strict_precision(False)
# Pre-download SPK for major asteroids
for body in [SE_CHIRON, SE_CERES, SE_PALLAS, SE_JUNO, SE_VESTA]:
eph.ensure_major_asteroid_spk(body)
yield
@pytest.fixture(autouse=True)
def reset_sidereal_mode():
"""Reset sidereal mode between tests."""
eph.swe_set_sid_mode(SE_SIDM_FAGAN_BRADLEY)
yield
eph.swe_set_sid_mode(SE_SIDM_FAGAN_BRADLEY)
Checking SPK Availability in Tests #
To verify SPK kernels are properly configured:
def test_spk_available_for_chiron():
"""Verify SPK is available for Chiron."""
from libephemeris import is_spk_available_for_body
from libephemeris.constants import SE_CHIRON
assert is_spk_available_for_body(SE_CHIRON), (
"Chiron SPK not available - ensure setup_spk fixture ran"
)
Supported Major Asteroids #
The following bodies have automatic SPK download support:
| Constant | Body | Asteroid # | Description |
|---|---|---|---|
SE_CERES |
Ceres | 1 | Dwarf planet in asteroid belt |
SE_PALLAS |
Pallas | 2 | Second-largest asteroid |
SE_JUNO |
Juno | 3 | Main belt asteroid |
SE_VESTA |
Vesta | 4 | Second-most-massive asteroid |
SE_CHIRON |
Chiron | 2060 | Centaur, important in astrology |
Other bodies (TNOs like Eris, Sedna, etc.) require manual SPK download using download_and_register_spk().
Troubleshooting #
SPK download fails in CI:
- Ensure
astroqueryis installed:pip install astroquery - Check network access to
ssd.jpl.nasa.gov - Use
LIBEPHEMERIS_LOG_LEVEL=DEBUGto see download details
Tests are slow on first run:
- This is expected - SPK files are ~1-5 MB each
- Use CI caching for the
~/.libephemeris/spk/directory - Consider using
pytest-xdistfor parallel test execution
Different results between runs:
- Ensure
set_strict_precision(False)when accepting Keplerian fallback - Check that SPK coverage includes the test dates (default: 10 years around current date)