Skip to content

test: mirror data/ingestion tests + fix layout parity (#55)#58

Open
tschm wants to merge 5 commits into
alihaskar:masterfrom
tschm:fix-test-layout-55
Open

test: mirror data/ingestion tests + fix layout parity (#55)#58
tschm wants to merge 5 commits into
alihaskar:masterfrom
tschm:fix-test-layout-55

Conversation

@tschm

@tschm tschm commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Fixes the check_test_layout.py parity failure for the data package.

Root cause

The migration to the mirrored tests/<pkg>/** layout created mirrors for api and core but never created the data mirror — the flat tests/test_data_ingestion.py and tests/test_data_slicing.py were deleted without replacement, so data/ingestion.py had no test file at all.

Two problems compounded it:

  1. Missing mirror. Restored both deleted suites, merged into a single tests/pycharting/data/test_ingestion.py. Organized to satisfy the checker's class-parity rule (each Test<Class> must map to a source class):

    • TestDataValidationError — every case that raises DataValidationError
    • TestDataManager — construction, properties, repr, and get_chunk slicing (folded in, since get_chunk is a DataManager method)
    • module-level functions — validate_input behaviour (it's a free function, so unconstrained)
  2. .gitignore swallowed the dir. An unanchored data/ rule (meant for local sample data) matched tests/pycharting/data/ — which is why the mirror could never be committed. Anchored it to /data/.

Verification

  • check_test_layout.py → exits 0 ("tests mirror sources 1:1")
  • Full suite: 146 passed, coverage 100% (data/ingestion.py 181/181)
  • No module is tested by two files (the flat duplicate is gone).

Closes #55

Also in this PR

Merged #57 and addressed three further quality issues (see the PR comment for the full summary):

Closes #45
Closes #47
Closes #49

🤖 Generated with Claude Code

tschm and others added 3 commits July 20, 2026 11:33
…haskar#56, alihaskar#50)

- mkdocs.yml: replace placeholder site_description with the real
  description from pyproject.toml (alihaskar#56)
- interface.py: split the broad except in plot() so DataValidationError
  is reported with stage="validation" (fixable input) separately from
  server/runtime failures reported with stage="server" (alihaskar#50)
- test_interface.py: assert the validation stage is reported

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Splitting the broad except in plot() left the generic (server/runtime)
except branch unexercised once validation errors took their own branch,
dropping interface.py coverage below 100%. Add a test that forces a
ChartServer startup failure and asserts stage="server".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration to the mirrored tests/<pkg>/** layout never created the
data package mirror, so check_test_layout.py failed with a missing
tests/pycharting/data/test_ingestion.py. Two problems compounded it:

- The flat tests/test_data_ingestion.py and tests/test_data_slicing.py
  were deleted in the migration without a mirrored replacement. Restore
  them, merged into tests/pycharting/data/test_ingestion.py with the
  class layout the checker enforces: TestDataValidationError and
  TestDataManager (get_chunk slicing folded in), plus module-level
  functions for validate_input behaviour.
- .gitignore had an unanchored `data/` rule (for local sample data)
  that silently ignored tests/pycharting/data/. Anchor it to `/data/`.

check_test_layout.py now exits 0 and data/ingestion.py stays at 100%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 20, 2026 08:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores test-layout parity for pycharting.data by reintroducing a mirrored ingestion test suite under tests/pycharting/data/ and fixing a .gitignore rule that previously prevented that directory from being committed.

Changes:

  • Add tests/pycharting/data/test_ingestion.py to mirror and cover src/pycharting/data/ingestion.py (validation + DataManager.get_chunk).
  • Add tests/pycharting/data/__init__.py to complete the mirrored test package structure.
  • Anchor the .gitignore data/ rule to /data/ so it no longer matches tests/pycharting/data/.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
tests/pycharting/data/test_ingestion.py Adds the missing mirrored ingestion/validation/slicing test suite for data/ingestion.py.
tests/pycharting/data/init.py Creates the mirrored tests.pycharting.data test package.
.gitignore Anchors data/ ignore rule to the repo root to avoid ignoring the mirrored tests directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


# Verify timestamps are in the correct range (milliseconds since epoch)
# For 2024-01-01, timestamps should be around 1704067200000 (ms)
expected_first_ts = int(pd.Timestamp("2024-01-01").timestamp() * 1000)
Comment on lines +722 to +741
def test_get_chunk_performance_large_dataset(self):
"""Test performance with large dataset (100k points)."""
n = 100000
index = np.arange(n)
open_data = np.random.uniform(100, 200, n)
high = open_data + np.random.uniform(0, 10, n)
low = open_data - np.random.uniform(0, 10, n)
close = np.random.uniform(low, high)

dm = DataManager(index, open_data, high, low, close)

# Measure slicing performance
start_time = time.time()
chunk = dm.get_chunk(10000, 20000) # 10k points
elapsed_ms = (time.time() - start_time) * 1000

# Should be well under 100ms for 10k points
assert elapsed_ms < 100, f"Slicing took {elapsed_ms:.2f}ms, expected <100ms"
assert len(chunk["index"]) == 10000

Comment on lines +742 to +761
def test_get_chunk_performance_small_slice_large_dataset(self):
"""Test performance of small slice from large dataset."""
n = 100000
index = np.arange(n)
open_data = np.random.uniform(100, 200, n)
high = open_data + np.random.uniform(0, 10, n)
low = open_data - np.random.uniform(0, 10, n)
close = np.random.uniform(low, high)

dm = DataManager(index, open_data, high, low, close)

# Small slice should be extremely fast
start_time = time.time()
chunk = dm.get_chunk(50000, 50100) # Just 100 points
elapsed_ms = (time.time() - start_time) * 1000

# Should be very fast (< 10ms)
assert elapsed_ms < 10, f"Small slice took {elapsed_ms:.2f}ms, expected <10ms"
assert len(chunk["index"]) == 100

tschm and others added 2 commits July 20, 2026 12:14
…alihaskar#45, alihaskar#47, alihaskar#49)

alihaskar#45 — Test the pragma-excluded websocket/auto-shutdown paths
Removed the 5 `# pragma: no cover` markers on the concurrency-critical paths
(websocket heartbeat, _monitor_connection stale/disconnect branches, _run_server
exception handler, and plot()'s blocking loop) and covered them with tests that
drive the handlers directly — no real sockets or timing races. Coverage 100%.

alihaskar#47 — Eliminate test-suite flakiness (xdist worker crash, sleep-based readiness)
Added tests/pycharting/conftest.py with:
- a `wait_until` polling helper replacing fixed `time.sleep` readiness waits, and
- a `FakeUvicornServer` / `fake_uvicorn` fixture so behaviour tests exercise the
  full ChartServer lifecycle without binding real ports.
Only the two genuine integration tests still start a real server (now waited on
via health polling). Suite went from ~88s to ~9s and passes 10/10 consecutive
runs under `-n auto` with no worker crashes.

alihaskar#49 — Type the session registry and route responses
- `_data_managers` is now `dict[str, DataManager]`.
- Route responses use concrete Pydantic models (InitDataResponse, SessionInfo,
  SessionListResponse, DeleteSessionResponse, StatusResponse).
- `plot()` returns a `PlotResult` TypedDict and `get_server_status()` a
  `ServerStatus` TypedDict instead of `dict[str, Any]`.
This surfaced (and fixed) an unchecked-None in `_repr_html_`. `ty` stays clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tschm

tschm commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

Updated: merged #57 + addressed #45, #47, #49

This branch now contains the original data/ingestion test-layout fix (#55), the merge of #57, and fixes for three further quality issues.

Merged #57 (no conflicts) — brings in #56 (mkdocs description), #50 (staged plot() error handling), #54.

#49 — Type the API boundary

  • _data_managersdict[str, DataManager]
  • Route responses use concrete Pydantic models (InitDataResponse, SessionInfo, SessionListResponse, DeleteSessionResponse, StatusResponse)
  • plot()PlotResult TypedDict; get_server_status()ServerStatus TypedDict (was dict[str, Any])
  • Surfaced and fixed an unchecked-None in _repr_html_. ty stays clean.

#45 — Cover the pragma-excluded websocket/auto-shutdown paths

  • Removed all 5 # pragma: no cover markers on the concurrency-critical paths: websocket heartbeat, _monitor_connection (stale + disconnect branches), _run_server exception handler, and plot()'s blocking loop
  • Covered them by driving the handlers directly (scripted fake websocket, direct _monitor_connection calls with patched sleep, fake shutdown events) — no real sockets, no timing races

#47 — Eliminate test-suite flakiness

  • New tests/pycharting/conftest.py: a wait_until polling helper (replaces fixed time.sleep readiness) and a FakeUvicornServer / fake_uvicorn fixture so behaviour tests exercise the full ChartServer lifecycle without binding real ports
  • Only the two genuine integration tests still start a real server (now health-polled)

Verification

  • make test: 154 passed, coverage 100% (≥90 gate met)
  • 10/10 consecutive -n auto runs, all exit 0, no worker crashes
  • Suite runtime ~88s → ~9s
  • ty clean · ruff check + format clean · pre-commit hooks pass

Closes #45
Closes #47
Closes #49

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants