Skip to content

test: cover client plot and media payloads, fix images() tile padding (PyTest - 9) - #1687

Open
Manik-Khajuria-5 wants to merge 47 commits into
fossasia:devfrom
Manik-Khajuria-5:Testing-9
Open

test: cover client plot and media payloads, fix images() tile padding (PyTest - 9)#1687
Manik-Khajuria-5 wants to merge 47 commits into
fossasia:devfrom
Manik-Khajuria-5:Testing-9

Conversation

@Manik-Khajuria-5

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

Copy link
Copy Markdown
Member

Description

Unit tests for the client methods that build the Plotly payload sent to the browser:

  • client_payloads_shapes.py (54) quiver, stem, pie, mesh, dual_axis_lines
  • client_payloads_graph.py (77) sunburst, sankey, graph, violin, parallel_coordinates
  • client_payloads_media.py (85) image, images, image_heatmap, image_select, audio, video, svg

Suite total: 1,428 tests (was 1,212).

Fixes : #1695

Motivation and Context

A wrong key or a broken scaling branch here raises nothing in Python the plot just renders wrong. The tests decode the payload (base64 images back into arrays) and assert the real contract with the frontend.

How Has This Been Tested?

  • 1,428 passed / 7 subtests, ~43s on Python 3.12; black clean; `p
  • Reverting the fix in a throwaway worktree fails exactly its 2 regression tests.
  • Live server: 18 panes across all covered methods, including `imagt raised before with no server-side errors.

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 funected)
  • Code refactor or cleanup (changes to existing code for improved readability or performance)

Checklist:

  • I adapted the version number under py/visdom/VERSION according to Semantic Versioning
  • 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

Add extensive unit and integration test coverage for client plotting/media payloads, server env/storage/sockets behavior, and CLI/build utilities, while hardening env persistence, readonly/write paths, authentication, and plot/update edge cases.

Bug Fixes:

  • Make JSONStore env and undo saves atomic via staged temp files and ensure interrupted writes or failed renames preserve prior environments and stacks.
  • Fix images() grid indexing so padding=0 works and tiles are centered correctly, and align plot update logic with missing-value handling for categorical axes.
  • Prevent compare_envs from crashing or corrupting panes when content is missing, empty, or carries unnamed traces, and skip non-comparable panes safely.
  • Ensure image/heatmap/plot history caps keep only the newest frames while maintaining valid selection indices, preventing unbounded in-memory growth.
  • Use constant-time comparisons for login credentials to avoid timing side channels and tighten error handling for bad frame indices and malformed socket messages.
  • Respect readonly mode on HTTP routes by rejecting writes with 403 while keeping reads functional, and tighten fork/delete semantics and window existence checks.
  • Adjust error page configuration so detailed tracebacks and request dumps are only shown under debug logging instead of always-on Tornado debug mode.

Enhancements:

  • Add comprehensive unit tests for client helpers, metric/curve computation, t-SNE backends, window/env comparison, and media payload encoding/decoding.
  • Add integration tests for HTTP window/env lifecycle, update routes, socket commands and lifecycle, polling parity, authentication, and readonly mode behavior.
  • Introduce LazyEnvData and deep-copy behavior tests to ensure lazy-loaded environments behave like dicts and can be safely forked and persisted.
  • Refine axis and layout option handling (including tickstep, tight_layout, stacked bars, and label normalization) and scrub None values consistently before sending to Plotly.
  • Centralize atomic file writes in JSONStore, unify undo and env paths, and document edge cases around hash-based env filenames and list_envs behavior.
  • Expose configurable max_plot_history alongside existing text/image/old-content caps and wire it through Application and handlers for plot history panes.
  • Clarify and expand testing guidance and structure in documentation, including unit vs integration layout, fixtures, HTTP test base class, and markers.

Build:

  • Update pytest configuration to include both py and py/tests on pythonpath, collect all *.py under py/tests/ as tests while excluding testutils via norecursedirs, and define unit/integration/slow/server markers.
  • Adjust packaging configuration to exclude tests packages from the distributed wheel while still discovering visdom under py/.
  • Document the testing setup and HTTP test strategy in .agents/context/testing.md for use by agents and contributors.

Tests:

  • Add large suites of unit tests for client image/audio/video/svg payloads, layout/marker helpers, metrics/curves, shapes (quiver/stem/pie/mesh/dual-axis), shared utils, t-SNE, compare_envs, window builder, memory caps, and CLI/build logic.
  • Add integration tests covering storage wiring through JSONStore, environment lifecycle (create/fork/save/delete/reload), window CRUD, update routes for plots/text/media, socket commands and lifecycle, polling transport parity, authentication/login/readonly behavior, and static asset serving.
  • Introduce reusable test utilities (fake handlers/sockets/stores, HTTP test base, payload builders, socket doubles) and restructure tests into unit and integration packages with pytest markers and configuration.

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 5, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds extensive unit and integration coverage for client Plotly/media payloads, server env/storage durability, socket and HTTP behavior, and refines multiple server utilities (atomic JSONStore writes, readonly enforcement, safer updates, auth hardening, plot/image history caps, and images() padding), while restructuring the test suite layout and pytest configuration.

Sequence diagram for readonly HTTP write rejection

sequenceDiagram
    actor Client
    participant PostHandler
    participant check_auth
    participant check_readonly
    participant reject_readonly

    Client->>PostHandler: post()
    activate PostHandler
    PostHandler->>check_auth: _check_auth(handler)
    check_auth-->>PostHandler: auth ok
    PostHandler->>check_readonly: _check_readonly(handler)
    alt handler.readonly
        check_readonly->>reject_readonly: reject_readonly(handler)
        reject_readonly-->>Client: 403 {success: False, error: ...}
    else not readonly
        check_readonly-->>PostHandler: proceed
        PostHandler-->>Client: 200 response
    end
    deactivate PostHandler
Loading

File-Level Changes

Change Details Files
Add unit tests for client media, graph, metrics, helper, and t-SNE payloads plus server compare_envs, LazyEnvData, memory caps, shared utils, window builder, and run_server CLI/build logic.
  • Introduce unit tests covering client image/audio/video/svg and image slider behavior, including decoding Plotly payloads to assert pixel-level contracts and pinned quirks.
  • Add unit tests for hierarchy/graph/distribution plots (sunburst, sankey, graph, violin, parallel_coordinates) to validate trace structure and opts-to-Plotly mapping.
  • Add metric and curve helper tests (ROC/PR/confusion matrix) including public roc_curve/pr_curve/confusion_matrix behavior and error handling.
  • Add tests for client helper functions (_title2str,_axisformat,_normalize_labels,_decode_binary_arrays) and for t-SNE backend selection and normalization.
  • Add unit tests for compare_envs, LazyEnvData durability/deepcopy behavior, memory caps for text/image/plot history, shared_utils helpers, window construction/update, and run_server/build download scripts.
py/tests/unit/client_payloads_media.py
py/tests/unit/client_payloads_graph.py
py/tests/unit/client_helpers.py
py/tests/unit/client_metrics.py
py/tests/unit/compare_envs.py
py/tests/unit/memory_caps.py
py/tests/unit/shared_utils.py
py/tests/unit/window_builder.py
py/tests/unit/run_server_cli.py
py/tests/unit/tsne.py
Add integration tests for sockets, polling transport, socket command handling, HTTP update paths, environment and window lifecycle, auth/readonly, storage wiring, and build verification.
  • Add socket_lifecycle, socket_commands, and polling_parity tests to drive AnySocketHandlerOrWrapper/VisSocketWrapper over a real Application and polling wrappers, asserting open/register/close semantics, message routing, and error handling.
  • Add integration tests for /update text/media/heatmap/plot updates, including edge-case handling for bad frames, empty data lists, and categorical axes.
  • Add environment/window lifecycle tests covering implicit env creation, fork/save/delete/reload over HTTP, and window CRUD behavior including index stability and churn.
  • Add auth integration tests for login-enabled servers, cookie handling, constant-time credential checks, readonly HTTP behavior, and health route public access.
  • Add tests for storage wiring ensuring JSONStore-based backends are used for load/save/delete/undo and read helpers, plus read-helper behavior through fake sockets.
  • Add build_verification tests to assert presence and serving of static assets including main.js bundle, CSS, HTML, and optionally downloaded CDN assets.
  • Add edge_cases and env_transfer tests to exercise error pages, malformed requests, env id escaping, upload_env/fork_env/save/env_state/win_data protocols.
  • Introduce HTTP test base class (VisdomHTTPTestCase) and socket doubles in testutils to share HTTP helpers and real handler wrappers across integration tests.
py/tests/integration/socket_lifecycle.py
py/tests/integration/socket_commands.py
py/tests/integration/polling_parity.py
py/tests/integration/update_plots.py
py/tests/integration/update_text_media.py
py/tests/integration/environment_lifecycle.py
py/tests/integration/window_lifecycle.py
py/tests/integration/auth.py
py/tests/integration/storage_wiring.py
py/tests/integration/build_verification.py
py/tests/integration/edge_cases.py
py/tests/integration/env_transfer.py
py/tests/integration/window_types.py
py/tests/testutils/http.py
py/tests/testutils/sockets.py
py/tests/testutils/fakes.py
py/tests/testutils/payloads.py
py/tests/testutils/__init__.py
Strengthen JSONStore durability and naming behavior, including atomic writes for env and undo files and documented hash-fallback edge cases.
  • Introduce JSONStore._atomic_write to write via .tmp then os.replace, and use it from serialize_env and save_undo to prevent truncated env/undo files on interrupted writes.
  • Adjust save_undo to use atomic_write for both plain and hashed undo paths, keeping behavior consistent with env writes.
  • Add tests simulating mid-write failures via monkeypatched open, asserting that interrupted saves keep previous env/undo state and leave usable staging files, and that failed rename keeps previous env on disk.
  • Document and test the hash_fallback behavior: stranded .json.tmp never counted as envs, envs named like hash<64hex> disappear from list_envs, and long-name fallback files report real ids.
  • Ensure long eid fallback path stages and renames tmp files and preserves env content by id.
py/visdom/data_model/json_store.py
py/tests/unit/data_model.py
Tighten server-side update logic for plots and embeddings, including plot history caps, safer heatmap updates, and improved error handling for bad update payloads.
  • Extend UpdateHandler.update_packet/update to accept max_plot_history and truncate plot_history content to last N frames, and wire max_plot_history through Application defaults and handlers.
  • Refine plot update logic: respect delete flag and named trace deletion, inject new traces for empty plots, iterate idxs/new_data with zip, and use shared _is_missing_value to skip all-missing-x updates without TypeError on categorical axes.
  • Improve heatmap updateDir behavior by better handling appendRow/prependRow/appendColumn/prependColumn/replace, including label and shape checks and preventing KeyError when base panes lack content/data.
  • Guard update_plot_layout in socket_handlers: validate frame index type/range and handle missing old_content when popping embeddings pane, logging warnings instead of raising IndexError/KeyError.
  • Adjust image slider tests and helper throttling to match new max_plot_history and ensure image_update_selected and update_image_slider route through /update with env passthrough.
py/visdom/server/handlers/web_handlers.py
py/visdom/server/handlers/socket_handlers.py
py/tests/unit/image_slider.py
py/visdom/server/defaults.py
py/visdom/server/app.py
Enforce readonly mode consistently across HTTP and socket handlers, and centralize readonly rejection logic.
  • Add reject_readonly and check_readonly helpers in server_utils to send 403 with JSON error for write attempts against readonly servers, stacking under check_auth.
  • Decorate PostHandler, UpdateHandler, DeleteEnvHandler, SaveHandler, ForkEnvHandler, SaveLayoutsHandler, and ExperimentLogHandler POSTs with check_readonly, and special-case /win_data writes to call reject_readonly when handler.readonly.
  • Ensure AnySocketHandlerOrWrapper already short-circuits messages under readonly and add tests asserting commands are dropped but sockets stay alive.
  • Update UploadEnvHandler and ExperimentLogHandler to rely on BaseHandler.initialize for readonly, removing redundant initialize overrides.
  • Add integration tests verifying readonly servers answer 403 for writes but still serve reads, and that non-readonly servers still accept writes.
py/visdom/utils/server_utils.py
py/visdom/server/handlers/web_handlers.py
py/tests/integration/auth.py
Harden authentication and error reporting by using constant-time credential comparison and decoupling debug logging from error page detail.
  • Change IndexHandler.post to use hmac.compare_digest on both username and password (derived hash) to avoid timing leaks and ensure both halves are always checked.
  • Adjust Application to set Tornado setting show_error_details based on root logger DEBUG level instead of always enabling debug; modify BaseHandler.write_error to honour show_error_details when deciding whether to render exception, traceback, and request details.
  • Add integration tests verifying login page behavior, cookie issuance, rejection paths (wrong username/password), constant-time comparison via patched compare_digest call counts, and that production error pages omit tracebacks/requests while DEBUG-level servers include them.
py/visdom/server/handlers/web_handlers.py
py/visdom/server/app.py
py/visdom/server/handlers/base_handlers.py
py/tests/integration/auth.py
py/tests/integration/edge_cases.py
Improve server socket behavior and layout broadcasting, especially for polling clients and delete/undo/embeddings commands.
  • Move broadcast_layouts implementation from subscriber subclass into AnySocketHandlerOrWrapper so save_layouts works for both source and subscriber sockets, always broadcasting layouts to subs.
  • Fix close command handling to pop pane data only once under escaped eid and avoid losing pane_data or missing close events when raw eid differs.
  • Add stricter validation for plot_history frame indices and handle bool separately to prevent TypeError; guard pop_embeddings_pane against empty/absent old_content and log warnings instead of raising, and keep pane usable afterwards.
  • Ensure VisSocketWrapper mock for polling open() gets a request attribute to satisfy log code, avoiding AttributeError for polling clients requesting sid.
  • Enhance polling wrapper monitor (socket_wrap_monitor_thread) behavior and message queue draining, and add detailed integration tests for polling parity, queue ordering, idle reaper, and echo/source-sub split.
  • Add new FakeSocket/FakeHandler and socket_double helpers to instantiate real handler classes without Tornado WebSocket stack, used extensively in integration tests.
py/visdom/server/handlers/socket_handlers.py
py/tests/integration/socket_commands.py
py/tests/integration/socket_lifecycle.py
py/tests/integration/polling_parity.py
py/tests/testutils/fakes.py
py/tests/testutils/sockets.py
Refine compare_envs behavior for plots and images, ensuring robustness against malformed panes and documenting hash-fallback edge cases.
  • Modify compare_envs to skip destinations whose content lacks 'data' or has empty/unnamed data when merging plot windows, preventing KeyError/IndexError when envs contain malformed panes or uploads.
  • Ensure image comparisons only seed strips from the first env’s image panes and only mark has_compare true when at least two envs contribute, while preserving layout and reload settings.
  • Skip windows without content when merging, handle missing 'content' key gracefully, and ensure sorting of panes by index with untitled/unknown panes placed last.
  • Augment and restructure tests (now unit compare_envs.py and integration env_transfer/window_types) to assert merge behavior, legend pane contents, env numbering rules, and show_all mode labelling.
py/visdom/utils/server_utils.py
py/tests/unit/compare_envs.py
py/tests/integration/env_transfer.py
py/tests/integration/window_types.py
Fix images() tiling padding and align grid cells consistently for float/uint8 inputs.
  • Adjust images() grid offsets in visdom.images to start each tile at padding instead of 1+padding, preventing out-of-bounds indexing when padding=0 and centering tiles symmetrically within their cells.
  • Update comments and calculations to clarify each cell consists of image plus padding on all sides, and that previous offsets dropped last row/column of cell grid.
  • Add unit tests that tile images with and without padding, assert shape, centering, and pinned behavior where padding colour differs between float and uint8 inputs.
py/visdom/__init__.py
py/tests/unit/client_payloads_media.py
Tighten packaging and pytest configuration, formalizing test layout and markers while excluding tests from the installed distribution.
  • Change setup.py find_packages(where='py') call to exclude test packages ('tests', 'tests.*'), ensuring visdom wheels do not ship py/tests as a top-level tests package.
  • Update pyproject.toml pytest config to include py/tests on pythonpath (and py) so testutils is importable, to treat all *.py under py/tests as tests, and to exclude testutils and other directories via norecursedirs.
  • Document test layout, styles (unit vs integration vs HTTP tests), fixtures, and markers in .agents/context/testing.md to guide contributors.
  • Add registered markers 'unit','integration','slow','server' and adjust marker docs to clarify server = externally launched visdom server.
setup.py
pyproject.toml
.agents/context/testing.md

Possibly linked issues

  • #0: PR changes UpdateHandler.update to accept max_plot_history and updates tests (e.g., image_slider) to match, resolving the signature mismatch issue.

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