Skip to content

test : Cover the client's metric and layout helpers, and apply a lone tick step (PyTest - 8) - #1683

Open
Manik-Khajuria-5 wants to merge 45 commits into
fossasia:devfrom
Manik-Khajuria-5:Testing-8
Open

test : Cover the client's metric and layout helpers, and apply a lone tick step (PyTest - 8)#1683
Manik-Khajuria-5 wants to merge 45 commits into
fossasia:devfrom
Manik-Khajuria-5:Testing-8

Conversation

@Manik-Khajuria-5

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

Copy link
Copy Markdown
Member

Description

Adds unit coverage for the private helpers at the top of py/visdom/__init__.py, and fixes one defect found while writing it.

  • py/tests/unit/client_metrics.py (113 tests) the curve/metric helpers behind roc_curve, pr_curve and confusion_matrix, plus a payload round trip over those three public methods.
  • py/tests/unit/client_helpers.py (153 tests) _axisformat, _opts2layout, _assert_opts, _normalize_labels, the marker/line validators and
    _decode_binary_arrays.
  • _axisformat now applies opts.xtickstep when it is the only axis option given.
  • _normalize_labels no longer emits a misleading numpy RuntimeWarning.

Fixes : #1695

Motivation and Context

These helpers build the Plotly layout and compute the numbers users read off ROC/PR curves, but nothing in py/tests/ touched them. A regression here shows up as a subtly wrong plot rather than a failure.

How Has This Been Tested?

  • Full suite green on Python 3.12: 1,212 passed, 41.5s (was 946 / 41.5s).
  • python -O green on both new files; black clean.
  • Both fixes confirmed load-bearing reverting either in a throwaway worktree
    fails exactly its own regression tests and nothing else.
  • Live server: scatter(opts=dict(xtickstep=0.5)) now stores
    xaxis: {"dtick": 0.5}; the axis was absent from the layout befo

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
  • 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

Strengthen visdom's reliability and safety by hardening environment persistence, update and comparison logic, readonly and auth enforcement, socket and polling behavior, and by significantly expanding unit and integration test coverage alongside test infrastructure and packaging configuration updates.

Bug Fixes:

  • Ensure JSONStore environment saves are atomic via temporary files and os.replace, preventing data loss on interrupted writes or failed renames, and keep hash-fallback filenames consistent with list/load/delete.
  • Fix LazyEnvData and JSONStore interactions so malformed or missing env files surface as ValueError with clear ids, and ensure long env ids use hash filenames while remaining discoverable.
  • Prevent double-pop and missing close events when closing panes over sockets by operating only on escaped environment IDs and existing pane data.
  • Guard embeddings pop operations against empty or absent history to avoid IndexError/KeyError escaping the socket loop.
  • Enforce max_plot_history caps for plot history panes and ensure selected indices remain valid after truncation, similar to existing text/image/undo caps.
  • Handle categorical axes and mixed data safely in plot updates by using a type-aware missing-value check instead of math.isnan directly, avoiding TypeError on strings.
  • Validate named trace updates so a named update carries exactly one data entry, returning HTTP 400 instead of assertions or silent misbehavior.
  • Skip unsafe or malformed compare_envs inputs (missing content, empty data, unnamed traces, incompatible pane types) and avoid mutating source environments while still producing meaningful comparison panes and legends.
  • Respect readonly mode for HTTP routes (save, fork, delete, events, win_data writes, uploads, experiment logging) via a dedicated decorator, while keeping read endpoints functional.
  • Ensure /win_data rejects reads of nonexistent windows with a clear 400 instead of assertion messages, and keep environment creation and deletion logic consistent.
  • Fix polling transport behavior so both subscribers and sources share the same on_message dispatch without raising ValueError from missing broadcast_layouts, and ensure polling wrappers have proper request context.
  • Make /update handling of heatmaps, plot traces, markers, and layouts robust to empty data lists, fully deleted traces, and named vs unnamed updates, while preserving existing traces when updates only adjust layout or opts.
  • Ensure compare_envs skips plots without usable legends or base content, avoids aliasing panes when comparing an env with itself, and keeps legends safe by escaping env names.
  • Prevent visdom's error pages from leaking stack traces and request details by default; only emit detailed debug info when logging level is DEBUG.
  • Avoid misleading numpy RuntimeWarnings in _normalize_labels by wrapping modular arithmetic in np.errstate, while still enforcing finite-label assertions.
  • Use constant-time hmac.compare_digest for username and password comparisons in IndexHandler to avoid timing side channels.
  • Fix wrapper-based subscriber creation so polling clients get a mock request before open(), preventing AttributeError when logging peer addresses.
  • Ensure register_window assigns unique, non-reused indices when panes are closed and reopened, aligning with undo behavior and avoiding index collisions in layouts.

Enhancements:

  • Add a check_readonly decorator and reject_readonly helper to centralize readonly enforcement across HTTP handlers, parallel to the existing socket-side short-circuit.
  • Extend UpdateHandler.update and server defaults to include a configurable max_plot_history cap and wire it through Application/BaseHandler, aligning behavior with image and embeddings history caps.
  • Refine compare_envs behavior to deep-copy base environments, handle hash-fallback env filenames, normalize legends, and support a show_all mode that namespaces panes per env while preserving merged comparisons.
  • Move broadcast_layouts implementation to the base socket handler so layout saves from source connections also notify subscribers, unifying behavior across transports.
  • Normalize plot comparison logic to skip windows lacking named traces or usable base content and to handle image_compare and plot windows consistently when merging multiple environments.
  • Refine write_error behavior to decouple Tornado's debug flag from error detail exposure, using a dedicated show_error_details setting driven by logging level instead.
  • Improve UpdateHandler.update_embeddings_packet to cap old_content length, preserve has_previous semantics, and integrate with the global max_old_content limit.
  • Document and formalize the test layout, markers, and usage of pytest markers in the testing context docs, emphasizing hermetic tests and HTTP vs unit style guidance.
  • Introduce VisdomHTTPTestCase and a suite of HTTP helpers for consistent in-process server testing, and expand testutils with fakes and payload builders to simplify future tests.

Build:

  • Update pytest configuration to treat all *.py under py/tests as tests (scoped by testpaths), add py/tests to pythonpath so testutils is importable, and exclude testutils from discovery via norecursedirs.
  • Adjust setup.py packaging to exclude tests packages from distributions by using find_packages(exclude=["tests", "tests.*"]), preventing test code from shipping to users.

Documentation:

  • Expand testing documentation to describe unit vs integration layout, shared fixtures, HTTP testing via VisdomHTTPTestCase, pytest markers, and the rationale for hermetic tests and manual visual scripts.
  • Add a README for manual visual checks and a visual_check.py script explaining how to run and evaluate UI sanity checks outside pytest.

Tests:

  • Add unit tests for client metric helpers (ROC/PR curves, confusion matrices) and direct coverage of the underlying numpy utilities.
  • Add unit tests for client layout/option helpers, marker/line validators, binary array decoding, and label normalization.
  • Add unit tests for server shared utilities, t-SNE backend selection, memory caps for text/image/plot histories, server utils helpers, and CLI/build behavior.
  • Add integration tests for window, environment, storage wiring, socket lifecycle and commands, HTTP update paths, auth/login and readonly enforcement, polling transport parity, and edge-case environment transfers.
  • Introduce shared test utilities (fake handlers, sockets, stores, HTTP base class, payload builders) and reorganize tests into unit and integration packages.
  • Add manual visual check script for end-to-end UI sanity checks outside pytest.

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

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds extensive unit and integration tests for client helpers, metrics, server handlers, sockets, storage wiring, and t-SNE, while fixing multiple robustness and security issues (atomic JSONStore writes, label normalization warnings, readonly enforcement, authentication timing, layout broadcast, plot/image history caps, and error-page behavior).

Sequence diagram for readonly enforcement on HTTP handlers

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

    Client->>UpdateHandler: post(request)
    UpdateHandler->>check_readonly: check_readonly(handler)
    alt handler.readonly is True
        check_readonly->>reject_readonly: reject_readonly(handler)
        reject_readonly->>Client: 403 {success: False, error: "The server is running in readonly mode"}
    else handler.readonly is False
        check_readonly->>UpdateHandler: call original post(handler)
        UpdateHandler-->>Client: 200 OK (state changed)
    end
Loading

Sequence diagram for capped plot_history updates

sequenceDiagram
    actor Client
    participant UpdateHandler as UpdateHandler
    participant UpdatePacket as UpdateHandler.update_packet
    participant Update as UpdateHandler.update

    Client->>UpdateHandler: post(update packet)
    UpdateHandler->>UpdatePacket: update_packet(p, args, max_text_lines, max_old_content, max_image_history, max_plot_history)
    UpdatePacket->>Update: update(p, args, max_text_lines, max_old_content, max_image_history, max_plot_history)
    Update->>Update: [p.type == "plot"]
    Update->>Update: [args.data[0].type == "plot_history"]
    Update->>Update: p.content.append(args.data[0].content)
    alt len(p.content) > max_plot_history
        Update->>Update: p.content = p.content[-max_plot_history:]
    end
    Update-->>UpdatePacket: updated packet p
    UpdatePacket-->>UpdateHandler: updated packet p
    UpdateHandler-->>Client: 200 OK (plot history updated)
Loading

File-Level Changes

Change Details Files
Add comprehensive unit test coverage for client-side layout, metric, and helper functions, and confirm public metrics APIs via payload round trips.
  • Introduce unit tests for metric helpers behind roc_curve, pr_curve, and confusion_matrix, including both raw-data and precomputed-point modes and confusion-matrix normalization variants.
  • Add unit tests for client helpers such as _axisformat/_axisformat3d, _opts2layout, _assert_opts, _normalize_labels, marker/line validators, and _decode_binary_arrays, covering edge cases like lone tickstep and non-finite labels.
  • Add tsne-focused unit tests that exercise backend selection (openTSNE vs. bhtsne), perplexity heuristics, and embedding normalization.
py/tests/unit/client_metrics.py
py/tests/unit/client_helpers.py
py/tests/unit/tsne.py
Strengthen JSONStore durability and naming behavior for env and undo files, including atomic writes and hash-fallback semantics.
  • Refactor JSONStore writes into a shared atomic_write helper that stages writes to a .tmp file and uses os.replace for both env and undo stacks, preventing truncated files on interruption.
  • Add tests that simulate mid-write failures and rename failures to ensure previous env/undo data remains intact and that stranded .tmp files do not appear as envs.
  • Document and test hash_fallback behaviors, including environments whose IDs collide with hash* filenames and long-name fallback files reporting real IDs.
py/visdom/data_model/json_store.py
py/tests/unit/data_model.py
Improve server-side update and comparison logic for plots, images, embeddings, and environment comparison, adding memory caps for histories.
  • Extend UpdateHandler.update/update_packet and related HTTP tests to support max_plot_history, cap text lines, image history, embedding old_content, and plot history, always keeping newest entries and preserving explicit user selections.
  • Fix plot update behavior: allow injecting traces into empty plots, forbid multi-entry named updates, treat all-missing numeric updates as no-ops via _is_missing_value, and avoid TypeError on categorical axes.
  • Harden compare_envs to skip malformed panes (missing content, empty data, unnamed traces), avoid mutating source envs, handle image_compare strips correctly, and support a show_all mode that duplicates non-comparable panes with env-prefixed titles and ids.
py/visdom/server/handlers/web_handlers.py
py/visdom/server/defaults.py
py/tests/unit/image_slider.py
py/tests/unit/memory_caps.py
py/tests/unit/compare_envs.py
py/tests/integration/update_plots.py
Tighten socket handlers, polling transport, and undo/close semantics to avoid crashes and ensure consistent behavior between WebSocket and polling clients.
  • Move broadcast_layouts into AnySocketHandlerOrWrapper, ensuring save_layouts works from both subscriber and source sockets, and make polling wrappers initialize with a mock request to avoid AttributeError.
  • Fix close event handling to pop pane data only once under the escaped env id, ensuring correct pane_data is pushed to sources and undo stacks; add guards around pop_embeddings_pane to avoid IndexError/KeyError when histories are empty.
  • Strengthen plot_history frame selection by validating frame indices (type and range) to avoid TypeError, and centralize layout history updates with max_plot_history enforcement.
py/visdom/server/handlers/socket_handlers.py
py/tests/integration/socket_commands.py
py/tests/integration/polling_parity.py
py/tests/integration/socket_lifecycle.py
Enforce readonly mode consistently across HTTP and socket routes, while refining auth and error handling behavior.
  • Introduce check_readonly and reject_readonly in server_utils, apply them to mutating HTTP handlers (Post, Update, Close, Save, ForkEnv, UploadEnv, ExperimentLog, WinData), and ensure readonly servers answer 403 with a structured error body.
  • Adjust win_data to still allow writes behind a mixed read/write endpoint by explicitly calling reject_readonly when necessary, and ensure window-nonexistent errors are reported as HTTP 400 instead of assertions.
  • Replace tornado debug flag with a show_error_details flag driven by logging level, so production error pages omit tracebacks and request data while debug logs still capture them.
py/visdom/utils/server_utils.py
py/visdom/server/handlers/web_handlers.py
py/visdom/server/app.py
py/tests/integration/auth.py
py/tests/integration/edge_cases.py
Improve authentication security and login handling, including constant-time comparisons and cookie management.
  • Update IndexHandler.post to use hmac.compare_digest for both username and password comparisons to avoid timing side channels, and ensure both halves are always compared.
  • Ensure login flow reads cookie secret from DEFAULT_ENV_PATH safely in tests and that wrong credentials, empty passwords, and forged cookies are handled without setting cookies.
  • Add integration tests to cover login page rendering, credential handling, cookie issuance, and authenticated vs. unauthenticated access to protected routes.
py/visdom/server/handlers/web_handlers.py
py/tests/integration/auth.py
Reorganize tests into unit/integration, add test utilities, and refine pytest configuration and packaging behavior.
  • Move existing tests into py/tests/unit and py/tests/integration, introduce testutils package (FakeHandler, FakeSocket, SpyStore, VisdomHTTPTestCase, payload builders, socket doubles), and update references accordingly.
  • Adjust pytest configuration to treat all *.py under py/tests as tests, add pythonpath entries for py and py/tests, and exclude testutils/ from collection via norecursedirs.
  • Update setup.py to exclude tests packages from distribution and extend testing documentation with guidance on unit vs integration, HTTP tests, fixtures, and markers.
py/tests/unit/window_builder.py
py/tests/integration/storage_wiring.py
py/tests/integration/window_lifecycle.py
py/tests/integration/environment_lifecycle.py
py/tests/testutils/__init__.py
py/tests/testutils/http.py
py/tests/testutils/fakes.py
py/tests/testutils/sockets.py
py/tests/testutils/payloads.py
.agents/context/testing.md
pyproject.toml
setup.py
Refine shared utilities for NaN sanitization, missing-value detection, slider index coercion, and warning deduplication, with targeted unit tests.
  • Add _is_missing_value helper in shared_utils to unify missing-value detection (None/NaN/Inf) while avoiding TypeError on non-numeric values, and use it in plot update logic.
  • Strengthen _sanitize_nans and NanSafeEncoder behavior with tests covering nested structures, tuple-to-list conversion, and ndarray pass-through; add _coerce_image_slider_index and warn_once tests with fixture-based reset.
  • Ensure compare_envs and related utilities gracefully handle malformed state (missing content, empty data, unnamed traces) without raising, and that env numbering/legend labeling logic is fully covered.
py/visdom/utils/shared_utils.py
py/tests/unit/shared_utils.py
py/tests/unit/server_utils.py
py/tests/unit/compare_envs.py

Possibly linked issues

  • #0: PR updates UpdateHandler.update usage and tests (e.g., image_slider) to include new parameters, fixing signature mismatch errors.

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