Skip to content

test : Cover environment comparison, environment transfer and the server CLI with tests (PyTest - 7) - #1682

Open
Manik-Khajuria-5 wants to merge 41 commits into
fossasia:devfrom
Manik-Khajuria-5:Testing-7
Open

test : Cover environment comparison, environment transfer and the server CLI with tests (PyTest - 7)#1682
Manik-Khajuria-5 wants to merge 41 commits into
fossasia:devfrom
Manik-Khajuria-5:Testing-7

Conversation

@Manik-Khajuria-5

@Manik-Khajuria-5 Manik-Khajuria-5 commented Aug 3, 2026

Copy link
Copy Markdown
Member

Description

Adds three test modules and one robustness fix they uncovered.

  • py/tests/unit/compare_envs.py : server_utils.compare_envs: title-based merging, env numbering/legend, image panes.
  • py/tests/integration/env_transfer.py : /upload_env, /fork_env, /save, /env_state, /win_data input handling.
  • py/tests/unit/run_server_cli.py : run_server.main (port validation, base_url, flags, env credentials, SSL pairing) and build.download_scripts (URL table, subdirectory routing, version.built stamp).
  • compare_envs now skips a pane when the base window has no content or its plot data is empty.

139 tests; no production behaviour change beyond that guard.

Fixes : #1695

Integration note

This PR should be merged after #1679 , as it depends on the lower-layer changes introduced there.

Motivation and Context

These three areas had no coverage and are awkward to exercise by hand: main() ends in a blocking IO loop, download_scripts() reaches three CDNs, and compare_envs writes to a socket instead of returning

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Code refactor or cleanup (changes to existing code for improv)

Checklist:

  • I adapted the version number under py/visdom/VERSION accordhttps://semver.org/) — not needed, tests plus an internal guard
  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.

Summary by Sourcery

Expand and restructure the visdom test suite to cover environment storage durability, server/window/env lifecycle, socket and polling behaviour, auth/readonly enforcement, and CLI/build utilities, while hardening core server utilities and handler logic for robustness and readonly safety.

New Features:

  • Add comprehensive unit and integration test coverage for environment comparison, socket command handling, auth/readonly behaviour, plot updates, window/env lifecycle, polling transport, and CLI/build utilities.
  • Introduce shared test utilities, fixtures, and manual visual-check scripts to support reusable test setup and HTTP/socket driving.

Bug Fixes:

  • Make JSONStore environment and undo writes atomic via temporary files, ensuring durability on interrupted writes and failed renames.
  • Guard compare_envs against panes with missing or empty content and unnamed/empty plot data, and skip such panes during comparisons.
  • Enforce readonly mode across HTTP handlers, including /events, /update, /delete_env, /save, /fork_env, /win_data, /upload_env, and experiment logging, while preserving read paths.
  • Fix socket close handling so pane data is forwarded correctly, embeddings history pops safely, layout broadcasts work from any socket type, and polling wrappers initialise without errors.
  • Harden /update handling for plots, heatmaps, and histories (named trace validation, categorical axes, frame indices, empty data, plot/image/embedding history caps).
  • Use constant-time comparison for login credentials, ensure cookie secrets are loaded from the correct path, and tighten auth behaviour for web and socket handlers.
  • Prevent window index reuse after deletions and ensure new panes get unique, monotonic indices.
  • Avoid exposing internal tracebacks and request objects on error pages unless debug logging is enabled, using a dedicated show_error_details flag instead of Tornado's global debug mode.
  • Ensure build.download_scripts respects version stamps, handles partial failures, routes assets by type, and avoids installing test packages.

Enhancements:

  • Refine shared utilities (NaN sanitisation, missing-value detection, image slider index coercion) and add tests around them.
  • Add configurable caps for text, embedding undo, image history, and plot history growth, wiring them from Application defaults through handlers.
  • Tighten UpdateHandler behaviour for plot history, heatmap directions, and marker updates, and centralise readonly rejection helper logic.
  • Adjust pytest configuration and documentation to formalise unit/integration separation, testutils usage, and markers; ensure tests are discoverable but not packaged.
  • Improve handler/context wiring so Application exposes max_plot_history and readonly flags consistently to web and socket handlers.

Build:

  • Exclude tests packages from distribution in setup.py and extend build/download_scripts to manage CDN assets and MathJax bundles reliably.
  • Update pytest configuration (pyproject.toml) to include py/tests on PYTHONPATH, simplify file naming, exclude testutils from collection, and register new markers.

CI:

  • Document test running and structure in .agents/context/testing.md to guide contributors and tooling.

Documentation:

  • Expand testing documentation to describe test layout, fixtures, HTTP testing patterns, and marker usage, and add README for manual visual checks.

Tests:

  • Restructure and significantly expand the test suite into unit and integration modules, covering data model durability, lazy env data, server utils, handlers, sockets, polling transport, auth/login, readonly mode, CLI and build scripts, tsne helpers, and various pane types and update pathways.
  • Introduce reusable test utilities for fake handlers, sockets, stores, HTTP base classes, and payload builders, plus manual visual-check scripts for UI verification.

Chores:

  • Rename and relocate existing tests to align with new layout (e.g., test_data_model.py to unit/data_model.py, test_storage_wiring.py to integration/storage_wiring.py, test_image_slider.py to unit/image_slider.py).

Adds conftest.py, a testutils package, and unit tests for window().
AsyncHTTPTestCase is a unittest.TestCase, and pytest will not inject
fixtures into those. Run the Application on a background event loop
instead and talk to it with requests, which is already a runtime
dependency, so HTTP tests can be plain functions.
Lands the window lifecycle file as plain pytest functions on the
visdom_server fixture, dropping the per-file setUp block and the
temp directory it never cleaned up.
Collapses the repeated plot-trace and opts assertions into parametrized
tables, so each case reports separately instead of hiding behind the
first failure.
The reload case now builds its second Application from the app_factory
fixture, which shares env_path with visdom_server, instead of hand-rolling
one against a leaked temp directory.
The update-into-a-missing-env case asserted 'status is 200 or 500',
which could not fail. It now pins the actual behaviour: 200 with
'win does not exist'.
register_window took the index from len(env), so closing any window but
the last produced a duplicate index and the pane order went undefined
after a reload. Use max(i) + 1, which is what the undo path already does.
Three validation checks used a bare assert, so an invalid request came
back as a 500 and, under python -O, was skipped entirely: forking an
unknown env then raised KeyError and a named trace update read past the
data it was given. Raise HTTPError(400) like the other checks do.
Places the two test files dev added into the new layout, and points the
new storage-wiring test at env_payload: it called the local _env helper
this branch had already replaced, which merged cleanly and then failed.
Brings in dev along with the layout placement and the env_payload fix
made on the PR-1 branch. D1 and D2 both survive the auto-merge of
server_utils.py and web_handlers.py.
Replaces the background event loop and requests session with the
AsyncHTTPTestCase base class from PR-1, which already runs the app
in-process on an ephemeral port. The four integration files become
TestCase subclasses; pytest still collects and marks them.
first/second read as two windows; they are the id returned by each call,
which is the same id both times. Also assert only one pane exists.
The guidance still said VisdomHTTPTestCase was scheduled for replacement
by a background-loop fixture. Record the opposite, and the consequences
for anyone writing an HTTP test: no fixtures, no parametrize, share via
a base class.
Both halves now run off VisdomHTTPTestCase instead of their own fixture block.
The three cap tables collapse onto parametrize; the file was untracked until now.
Guard the missing-point check behind a numeric test, and stop indexing data and
traces past their length when an update supplies fewer entries than the plot.
It was the one history pane that grew without bound.
Removing a heatmap names no trace and posts no data, so the shortcut for
opts-only updates returned before the delete branch and left the plot up.
A python client running with use_polling=True POSTs to /vis_socket_wrap
with no sid to get one minted. That path built a VisSocketWrapper and
initialized it without ever assigning .request, so open() raised
AttributeError reaching for request.remote_ip and the endpoint answered
500 -- polling mode could not connect at all. The subscriber route a few
lines below already assigns it; do the same here.
Polling had no tests at all, while AGENTS.md asks for every socket
feature to work over both transports. Covers minting a sid on each
route, the handshake, the three protocol failures, ten commands driven
end to end over HTTP, the readonly short-circuit, the idle reaper and
the pending-message deque.

Two behaviours are pinned rather than changed: a polling subscriber
receives a fourth, redundant layout_update because it registers before
initialize broadcasts, and the deque is unbounded, which is what makes
the reaper load-bearing.
The submitted username and derived key were checked with ==, which
short-circuits on the first differing character. That leaks, through
response timing, how much of a guess was correct, and the short-circuit
between the two checks leaked whether the username alone was right.
hmac.compare_digest on both halves removes the signal.
-readonly stopped socket commands and blocked uploads and experiment
logging, but the HTTP routes that create, update, close, fork, delete
and save were left open, so any client could still mutate a server
started readonly. Adds a check_readonly decorator next to check_auth and
puts it on those six endpoints, plus the one write hidden behind
/win_data, which also serves reads and so checks inline.

readonly moves onto _WEB_APP_ATTRIBUTES, which is where every other
per-request attribute already comes from; the two handlers that copied
it by hand no longer need an initialize override.
Neither gate had any tests. Covers the login page and its form, the four
ways a credential can be wrong, 401 on every authenticated route with
the state left untouched, the same routes succeeding with the issued
cookie, a forged cookie, /health staying public by design, and both
socket kinds closing instead of registering when unauthenticated.

The readonly half asserts 403 and no state change for each of the nine
mutating routes, that reads and the env page still work, and that an
ordinary server still answers 200 on all of them -- otherwise a
decorator that refused everything would look like a pass.
write_error rendered the exception, its traceback and request.__dict__
whenever tornado's debug flag was set, and app.py set that flag
unconditionally -- so every deployment served its own source paths and
request internals on any 500, and error.html's production branch was
unreachable. The flag also turned on autoreload for every server as a
side effect.

Replaces it with a show_error_details setting driven by the root logging
level, so -logging_level DEBUG brings the detail back and nothing else
changes with it.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sorry @Manik-Khajuria-5, your pull request is larger than the review limit of 150000 diff characters

@sourcery-ai

sourcery-ai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds extensive unit and integration coverage around environment persistence, socket/HTTP command handling, and the server CLI, plus a small robustness fix in compare_envs and atomic JSONStore writes.

Sequence diagram for readonly HTTP write handling

sequenceDiagram
    actor Client
    participant UpdateHandler as UpdateHandler.post
    participant check_auth
    participant check_readonly
    participant reject_readonly
    participant JSONStore

    Client->>UpdateHandler: POST /update
    UpdateHandler->>check_auth: _check_auth(handler)
    check_auth-->>UpdateHandler: authenticated

    UpdateHandler->>check_readonly: _check_readonly(handler)
    alt handler.readonly is True
        check_readonly->>reject_readonly: reject_readonly(handler)
        reject_readonly-->>Client: 403 readonly response
    else handler.readonly is False
        check_readonly-->>UpdateHandler: call wrapped post
        UpdateHandler->>JSONStore: save_env(eid, state[eid])
        JSONStore-->>UpdateHandler: success
        UpdateHandler-->>Client: 200 OK
    end
Loading

File-Level Changes

Change Details Files
Strengthen JSONStore env/undo durability and LazyEnvData semantics, and extend data model unit tests.
  • Refactor tests from test_data_model.py into py/tests/unit/data_model.py and convert to pytest-style with pytestmark=unit.
  • Introduce JSONStore._atomic_write and use it for env and undo saves to ensure atomic writes via .tmp files and os.replace.
  • Add tests for interrupted env/undo saves, stranded .tmp files, long-name hash fallbacks, and name collision edge cases.
  • Add extensive LazyEnvData tests covering lazy loading, mapping interface, malformed/missing env handling, experiment metadata preservation, deepcopy behavior, and fork persistence.
py/tests/unit/data_model.py
py/visdom/data_model/json_store.py
Tighten HTTP handler behavior for updates, env transfer, auth, and readonly, and add tests for edge cases and docs for testing layout.
  • Add check_readonly decorator and reject_readonly helper; apply to mutating web handlers and adjust /win_data to allow reads but guard writes.
  • Refine UpdateHandler.update to support max_plot_history, stricter trace-name handling, heatmap deletion semantics, categorical axes via _is_missing_value, and safer plot_history frame index validation.
  • Improve /fork_env error handling and win_data error messages, plus add constant-time auth comparison using hmac.compare_digest and simplify UploadEnvHandler/ExperimentLogHandler initialize.
  • Document test layout, fixtures, HTTP testing strategy, and markers in .agents/context/testing.md.
  • Add numerous integration tests for auth, readonly behavior, env transfer, window lifecycle, edge cases, polling parity, socket lifecycle, update_plots/text/media, environment lifecycle, build verification, and socket commands.
py/visdom/server/handlers/web_handlers.py
.agents/context/testing.md
py/visdom/utils/server_utils.py
py/visdom/utils/shared_utils.py
py/visdom/server/app.py
py/visdom/server/handlers/base_handlers.py
py/tests/integration/auth.py
py/tests/integration/env_transfer.py
py/tests/integration/window_lifecycle.py
py/tests/integration/edge_cases.py
py/tests/integration/polling_parity.py
py/tests/integration/socket_lifecycle.py
py/tests/integration/socket_commands.py
py/tests/integration/update_plots.py
py/tests/integration/update_text_media.py
py/tests/integration/environment_lifecycle.py
py/tests/integration/build_verification.py
Harden socket_handlers behavior and add tests around message processing, layout broadcasting, and embeddings drilldown.
  • Move broadcast_layouts implementation to AnySocketHandlerOrWrapper and ensure polling wrapper open() sets request before initialize().
  • Fix close command to only pop pane data once and always forward close events; adjust plot_history frame validation and embeddings pop behavior to avoid crashes on bad input or empty history.
  • Ensure socket save_layouts works from both sources and subscribers and respects readonly short-circuiting.
  • Add integration tests driving AnySocketHandlerOrWrapper.on_message via real socket doubles for all commands, including save_all, save_layouts, layout_item_update, update_plot_layout, update_comment, forward_to_vis, pop_embeddings_pane, echo, and unknown commands.
py/visdom/server/handlers/socket_handlers.py
py/tests/integration/socket_commands.py
Improve compare_envs robustness and add targeted unit tests for env comparison behavior.
  • Teach compare_envs to skip base panes without content or with empty/unnamed plot data, and to compute base plot data defensively.
  • Add MAX_PLOT_HISTORY default and pass it through UpdateHandler.update for plot_history cap alignment.
  • Introduce unit test module for compare_envs to validate env numbering, legend generation, title/type matching, image strip behavior, show_all labeling, and ordering of emitted panes.
py/visdom/utils/server_utils.py
py/visdom/server/defaults.py
py/tests/unit/compare_envs.py
Add memory-growth caps and shared_utils tests, and wire caps through handlers and defaults.
  • Add DEFAULT_MAX_PLOT_HISTORY and propagate max_plot_history through Application, BaseHandler, and UpdateHandler.update/update_packet.
  • Implement plot_history cap logic mirroring image_history and ensure selectors remain valid after truncation.
  • Extend shared_utils with _is_missing_value and _coerce_image_slider_index and add unit tests for _sanitize_nans, NanSafeEncoder, warn_once, path/id helpers, and value classification.
  • Add unit tests verifying memory caps for text, embeddings old_content, image_history, and plot_history panes, plus handler wiring of caps.
py/visdom/server/defaults.py
py/visdom/server/app.py
py/visdom/server/handlers/base_handlers.py
py/visdom/server/handlers/web_handlers.py
py/visdom/utils/shared_utils.py
py/tests/unit/memory_caps.py
py/tests/unit/shared_utils.py
Add unit tests for server_utils helpers, window construction/update, t-SNE backend selection, and refactor test utilities.
  • Add unit tests for escape_eid, extract_eid, hash_password, stringify, recursive_order, window() and update_window() behaviour.
  • Add t-SNE unit tests covering _get_perplexity, _normalize_tsne, and backend selection between openTSNE and bhtsne via reloaded_visdom fixture.
  • Introduce shared test utilities (FakeHandler, FakeSocket, SpyStore, VisdomHTTPTestCase, socket_double, payload builders) under py/tests/testutils and configure pytest to import them via pythonpath.
  • Update conftest.py with common fixtures (env_path, store, spy_store, app, app_factory, handler, app_handler, fake_socket, offline_client, capture_send, reset_warn_once).
py/tests/unit/server_utils.py
py/tests/unit/window_builder.py
py/tests/unit/tsne.py
py/tests/testutils/__init__.py
py/tests/testutils/http.py
py/tests/testutils/sockets.py
py/tests/testutils/payloads.py
py/tests/testutils/fakes.py
py/tests/conftest.py
Test and document manual visual checks and adjust packaging/pytest config.
  • Add example/manual/visual_check.py and README describing manual visual checks separate from pytest.
  • Update pyproject.toml to treat all *.py under py/tests as test modules, add pythonpath=py/tests, exclude testutils from collection, and register new markers.
  • Adjust setup.py find_packages to exclude tests so shipping package omits test modules.
  • Update .agents/context/testing.md with detailed guidance on test layout, styles, fixtures, HTTP tests, and markers.
example/manual/visual_check.py
example/manual/README.md
pyproject.toml
setup.py
.agents/context/testing.md

Possibly linked issues

  • #(unknown): PR adjusts UpdateHandler.update signature and tests to include max_plot_history, resolving the signature mismatch failures.
  • #: PR introduces pytest tests, fixtures, and testing docs, fulfilling the request for a structured testing setup.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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.

Add a Python test suite (pytest) for the server and client

1 participant