Skip to content

test: Vitest characterization harness + first jsfeat parity test (Phase 0) - #49

Merged
kalwalt merged 16 commits into
devfrom
feat/test-harness
Jul 7, 2026
Merged

test: Vitest characterization harness + first jsfeat parity test (Phase 0)#49
kalwalt merged 16 commits into
devfrom
feat/test-harness

Conversation

@kalwalt

@kalwalt kalwalt commented Jul 5, 2026

Copy link
Copy Markdown
Member

What & why

Phase 0 of the parity/refactor roadmap (#46): a complete characterization-test suite that pins jsfeatNext behavior against the original jsfeat as a golden oracle. This is the gate that makes the de-dup refactor (#47) and the package upgrades provably behavior-preserving.

Closes #39. Executes the function-level parity audit for #45 (findings posted on that issue as they were found).

The suite — 57 tests, 9 files

Module Coverage Result
math get_gaussian_kernel, qsort, median, deprecated perspective_4point_transform
matmath all 12 functions
transform all 3 functions ✅ (⚠️ signature divergence documented)
linalg lu_solve, cholesky_solve, svd_decompose/solve/invert, eigenVV
imgproc 14 functions incl. canny, warps, integral, equalize ✅ bit-for-bit
fast_corners, yape06, yape detect ✅ identical keypoints
orb.describe descriptors ✅ identical bytes
optical_flow_lk.track status + positions
motion_estimator ransac/lmeds + homography2d (seeded Math.random)
structs matrix_t, data-type helpers, resample f32

Bugs & divergences found (the suite pins each)

  1. 🐛 jsfeatNext bug → Bug: ransac/lmeds with affine2d kernel throws TypeError (missing motion_model.check_subset) #51: ransac/lmeds with affine2d kernel throws TypeError (base motion_model lost the default check_subset()); original jsfeat works.
  2. 🐛 Original-jsfeat bug, fixed in jsfeatNext: hough_transform throws ReferenceError (undeclared min_theta) in every distributed jsfeat build.
  3. ⚠️ jsfeat's transform module was never shipped in any jsfeat build/npm release — oracle restores it from src (vendored, commit 4c7b336).
  4. ⚠️ API divergences (Axis 2): static namespaces vs instance classes (incl. yape), raw-array vs matrix_t signatures in transform, top-level vs instance data-type helpers.

Infrastructure

Verification

npm test57 passed. CI (format + tests) and Build both green on ubuntu-24.04.

🤖 Generated with Claude Code

- Add vitest + original jsfeat (golden oracle) as devDependencies
- Add vitest.config.ts (node env) and test / test:watch scripts
- First parity test: math.get_gaussian_kernel matches original jsfeat within float tolerance across 4 cases (imports the real impl from src/, so it stays green through the planned de-dup refactor)

Part of Phase 0 (#39) — the test gate that makes the monolith de-duplication provably behavior-preserving.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kalwalt kalwalt added this to the Parity & Modernization milestone Jul 5, 2026
@kalwalt kalwalt self-assigned this Jul 6, 2026
@kalwalt kalwalt added enhancement New feature or request Typescript all about Typescript code design tests labels Jul 6, 2026
@kalwalt
kalwalt changed the base branch from main to dev July 6, 2026 11:04
kalwalt and others added 8 commits July 6, 2026 13:18
vitest 4 pulls in rolldown, which ships per-platform native binaries. The committed package-lock.json is generated on Windows, so on the Linux CI runner npm skipped the linux-x64 binding and the Test step crashed with 'Cannot find module @rolldown/binding-linux-x64-gnu'. Use 'npm install --no-package-lock' in CI so dependencies resolve for the runner's platform. Stopgap until the Vite migration (#42) reworks tooling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI installs with --no-package-lock so vitest 4's rolldown native binary resolves on Linux, but that also unpins prettier — a newer prettier reformatted 4 existing src files and failed format-check. Pin prettier to exactly 3.5.1 so the lock-free install keeps the formatter that matches the committed code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the fragile 'npm install --no-package-lock' (which unpinned every dependency and cascaded into prettier, then std-env ESM, breakage) with 'npm ci'. Regenerate package-lock.json in a node:20 Linux container so it correctly resolves the platform-specific native binaries that vitest 4 pulls in via rolldown. Verified end-to-end: 'npm ci && vitest run' in node:20 -> 4 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
npm 10.8.2 (bundled with Node 20) intermittently fails to install vitest 4's platform-specific rolldown native binary on the Linux CI runner (npm bug #4828), crashing 'npm ci' in the Test step. Node 24 ships npm 11, which resolves optional dependencies correctly. Verified in node:24 containers: 'npm ci' + vitest (4 passed) and 'npm run build-ts' both succeed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This branch bumps .nvmrc to Node 24 (npm 11) and adds Vitest characterization tests, so update the canonical AGENTS.md and the inlined Copilot instructions accordingly (Node version + 'npm test').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s oracle

Expands the Phase 0 characterization harness (#39) and starts filling the
function-level parity audit (#45):

- tests/parity/matmath.test.ts: all 12 matmath functions vs the oracle
- tests/parity/transform.test.ts: all 3 transform functions vs the oracle
- Replace the npm jsfeat dev-dependency with a vendored oracle
  (tests/vendor/, from inspirit/jsfeat@4c7b336): npm jsfeat@0.0.8 and even
  the distributed build/jsfeat.js NEVER included the transform module, so
  oracle.cjs evaluates src/jsfeat_transform.js against the bundle.

Parity findings captured (for the audit doc / #45):
- jsfeat's transform module is absent from every distributed jsfeat build;
  only math.perspective_4point_transform made it into the bundle.
- Signature divergence: jsfeat.transform.* take raw arrays, jsfeatNext's
  transform methods take matrix_t.

npm test: 20 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kalwalt and others added 5 commits July 6, 2026 22:50
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows checkouts were getting CRLF working trees, making local prettier
--check disagree with the Linux CI format-check (prettier 3 defaults
endOfLine: lf). Normalize everything to LF; renormalizes two example JS
files that were committed with CRLF.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45:
- math: qsort (full + sub-range), median, and the deprecated matrix_t-based
  perspective_4point_transform (parity with the jsfeat bundle's math version;
  jsfeatNext adds a deprecation console.warn - documented divergence)
- linalg: lu_solve, cholesky_solve, svd_decompose (U_T|V_T), svd_solve,
  svd_invert, eigenVV - all match the oracle on identical inputs

npm test: 30 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45. 14 imgproc functions vs the oracle on identical
synthetic images: grayscale (RGBA + BGRA codes), resample (u8 fast path),
box_blur_gray (scaled + NOSCALE), gaussian_blur, pyrdown, scharr/sobel
derivatives, compute_integral_image (sum+sqsum+tilted), equalize_histogram,
canny, warp_perspective, warp_affine - all bit-for-bit matches.

Parity finding: the ORIGINAL jsfeat hough_transform references min_theta /
max_theta without declaring them and throws ReferenceError under the
bundle's strict-mode IIFE - the distributed function is unusable. jsfeatNext
declares them (a fix, documented divergence); the test pins both behaviors.

npm test: 44 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues #39 / #45 on a deterministic corner-rich synthetic image:
- fast_corners: identical corners (x, y, score) at threshold 20
- yape06: identical keypoints at default thresholds
- yape: identical keypoints (API divergence documented: original is a
  static namespace, jsfeatNext requires instantiation)
- orb.describe: identical 32-byte descriptors for identical corners+angles
- optical_flow_lk.track: identical status + tracked positions across a
  (3,2) shift, tracking FAST corners through 2-level pyramids

npm test: 49 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kalwalt and others added 2 commits July 7, 2026 11:41
Continues #39 / #45. RANSAC and LMEDS made deterministic by stubbing
Math.random with an identical seeded sequence for both sides:
- ransac + homography2d: same success flag, same 3x3 model (5 decimals),
  same inlier mask; synthetic outliers rejected on both sides
- lmeds + homography2d: same model + mask
- ransac + affine2d: BUG FOUND - jsfeatNext throws TypeError because the
  motion_model base class lost the default check_subset() that original
  jsfeat provides; the oracle succeeds on identical data. Pinned as a
  documented divergence; fix tracked in #51.

npm test: 52 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the planned Phase 0 coverage (#39 / #45):
- matrix_t: construction shape/buffer, resize grow+shrink, copy_to
- get_data_type / get_channel / get_data_type_size across all popular
  formats (API divergence documented: top-level functions in jsfeat,
  instance methods in jsfeatNext)
- imgproc.resample f32 path

npm test: 57 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kalwalt
kalwalt merged commit d9edfcc into dev Jul 7, 2026
4 checks passed
kalwalt added a commit that referenced this pull request Jul 7, 2026
jsfeatNext's affine2d kernel was missing the error() and check_subset()
methods that original jsfeat defines on it, so motion_estimator.ransac /
lmeds with an affine2d kernel threw 'kernel.check_subset/error is not a
function'. Port both from jsfeat's affine2d (error = affine reprojection
residual; check_subset = true).

The parity test added in #49 is flipped from pinning the crash to a full
parity check: affine2d RANSAC now matches the oracle's 3x3 model and
inlier mask on identical seeded data, and rejects the synthetic outliers.

npm test: 57 passed; tsc --noEmit: clean.

Closes #51

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kalwalt
kalwalt deleted the feat/test-harness branch July 7, 2026 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code design enhancement New feature or request javascript tests Typescript all about Typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant