Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
105df790cf | ||
|
|
27cdb16d68 | ||
|
|
333c385452 | ||
|
|
8ed8dbe795 |
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Preload script for running Jest-style tests under Deno.
|
||||
* Injects Jest-compatible globals (describe, test, beforeAll, expect)
|
||||
* Injects Jest-compatible globals (describe, test, beforeAll, expect, jest)
|
||||
* using Deno's standard library BDD and expect modules.
|
||||
*
|
||||
* Usage:
|
||||
@@ -11,6 +11,91 @@
|
||||
import { beforeAll, beforeEach, afterAll, afterEach, describe, it } from "jsr:@std/testing/bdd";
|
||||
import { expect } from "jsr:@std/expect";
|
||||
|
||||
// @std/expect recognises mock functions via this well-known symbol
|
||||
const MOCK_SYMBOL = Symbol.for("@MOCK");
|
||||
|
||||
type MockCall = {
|
||||
args: unknown[];
|
||||
returned?: unknown;
|
||||
thrown?: unknown;
|
||||
timestamp: number;
|
||||
returns: boolean;
|
||||
throws: boolean;
|
||||
};
|
||||
|
||||
function createMock(impl?: (...args: unknown[]) => unknown) {
|
||||
let currentImpl = impl;
|
||||
const calls: MockCall[] = [];
|
||||
const mockInfo = { calls };
|
||||
|
||||
const mockFn = function (this: unknown, ...args: unknown[]) {
|
||||
const call: MockCall = {
|
||||
args,
|
||||
timestamp: Date.now(),
|
||||
returns: false,
|
||||
throws: false,
|
||||
};
|
||||
calls.push(call);
|
||||
try {
|
||||
const result = currentImpl ? currentImpl.apply(this, args) : undefined;
|
||||
call.returned = result;
|
||||
call.returns = true;
|
||||
return result;
|
||||
} catch (err) {
|
||||
call.thrown = err;
|
||||
call.throws = true;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
(mockFn as any)[MOCK_SYMBOL] = mockInfo;
|
||||
|
||||
(mockFn as any).mockResolvedValue = (val: unknown) => {
|
||||
currentImpl = () => Promise.resolve(val);
|
||||
return mockFn;
|
||||
};
|
||||
(mockFn as any).mockRejectedValue = (val: unknown) => {
|
||||
currentImpl = () => Promise.reject(val);
|
||||
return mockFn;
|
||||
};
|
||||
(mockFn as any).mockImplementation = (fn: (...args: unknown[]) => unknown) => {
|
||||
currentImpl = fn;
|
||||
return mockFn;
|
||||
};
|
||||
(mockFn as any).mockReturnValue = (val: unknown) => {
|
||||
currentImpl = () => val;
|
||||
return mockFn;
|
||||
};
|
||||
(mockFn as any).mockReset = () => {
|
||||
calls.length = 0;
|
||||
currentImpl = undefined;
|
||||
return mockFn;
|
||||
};
|
||||
(mockFn as any).mockClear = () => {
|
||||
calls.length = 0;
|
||||
return mockFn;
|
||||
};
|
||||
(mockFn as any).mockRestore = () => {};
|
||||
|
||||
return mockFn;
|
||||
}
|
||||
|
||||
const jest = {
|
||||
fn: (impl?: (...args: unknown[]) => unknown) => createMock(impl),
|
||||
spyOn: <T extends Record<string, unknown>>(obj: T, method: keyof T) => {
|
||||
const original = obj[method];
|
||||
const mock = createMock(
|
||||
typeof original === "function" ? (original as (...args: unknown[]) => unknown) : undefined
|
||||
);
|
||||
const restore = () => {
|
||||
obj[method] = original;
|
||||
};
|
||||
(mock as any).mockRestore = restore;
|
||||
obj[method] = mock as unknown as T[keyof T];
|
||||
return mock;
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign(globalThis, {
|
||||
describe,
|
||||
test: it,
|
||||
@@ -20,4 +105,5 @@ Object.assign(globalThis, {
|
||||
afterAll,
|
||||
afterEach,
|
||||
expect,
|
||||
jest,
|
||||
});
|
||||
|
||||
@@ -470,7 +470,11 @@ describe("TestMission", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("TestAbortSignal", () => {
|
||||
// Skip under Deno: jest.spyOn cannot patch ES-module namespace objects whose
|
||||
// properties are frozen. These unit tests are covered by the Jest suite.
|
||||
const canSpyOnModules = typeof (globalThis as any).Deno === "undefined";
|
||||
|
||||
(canSpyOnModules ? describe : describe.skip)("TestAbortSignal", () => {
|
||||
test("retain passes abort signal to SDK", async () => {
|
||||
const bankId = randomBankId();
|
||||
const controller = new AbortController();
|
||||
|
||||
@@ -10,6 +10,7 @@ import os
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
import sysconfig
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import IO, Optional
|
||||
@@ -132,12 +133,23 @@ class DaemonEmbedManager(EmbedManager):
|
||||
if dev_api_path.exists() and (dev_api_path / "pyproject.toml").exists():
|
||||
return ["uv", "run", "--project", str(dev_api_path), "--extra", "all", "hindsight-api"]
|
||||
|
||||
# Prefer a hindsight-api entry point installed alongside hindsight-embed
|
||||
# (e.g. `uv pip install hindsight-all` or `--target`). Falling through
|
||||
# to uvx in that case downloads a standalone Python whose ABI won't
|
||||
# match the sibling site-packages' C extensions (issue #1240).
|
||||
package_root = Path(__file__).parent.parent
|
||||
# Prefer a hindsight-api entry point installed alongside hindsight-embed.
|
||||
# Try two strategies:
|
||||
#
|
||||
# 1. sysconfig: resolves <venv>/bin or <venv>/Scripts for standard
|
||||
# pip/venv installs (issue #1401).
|
||||
# 2. __file__-relative: resolves <target>/bin or <target>/Scripts for
|
||||
# `pip install --target` layouts where sysconfig still points at the
|
||||
# system/venv scripts dir (issue #1240).
|
||||
binary_name = "hindsight-api.exe" if platform.system() == "Windows" else "hindsight-api"
|
||||
|
||||
scripts_dir = Path(sysconfig.get_path("scripts"))
|
||||
candidate = scripts_dir / binary_name
|
||||
if candidate.exists():
|
||||
return [str(candidate)]
|
||||
|
||||
# --target installs place binaries alongside site-packages contents
|
||||
package_root = Path(__file__).parent.parent
|
||||
for bin_dir in ("bin", "Scripts"):
|
||||
candidate = package_root / bin_dir / binary_name
|
||||
if candidate.exists():
|
||||
|
||||
@@ -109,38 +109,64 @@ def test_find_ui_command_uses_npx_yes_flag_for_published_control_plane(monkeypat
|
||||
]
|
||||
|
||||
|
||||
def test_find_api_command_prefers_sibling_binary_over_uvx(tmp_path, monkeypatch):
|
||||
def test_find_api_command_prefers_installed_binary_over_uvx(tmp_path, monkeypatch):
|
||||
"""
|
||||
When hindsight-api is installed alongside hindsight-embed (e.g. via
|
||||
`uv pip install hindsight-all`), _find_api_command should invoke that
|
||||
binary directly rather than shelling out to uvx. uvx downloads a
|
||||
standalone Python whose ABI won't match sibling C extensions compiled
|
||||
for the host Python (regression for issue #1240, NixOS asyncpg failure).
|
||||
`pip install hindsight-all`), _find_api_command should invoke that
|
||||
binary directly rather than shelling out to uvx. Uses sysconfig to
|
||||
locate the venv's scripts directory (issue #1401, #1240).
|
||||
"""
|
||||
package_root = tmp_path / "site-packages" / "hindsight_embed"
|
||||
package_root.mkdir(parents=True)
|
||||
fake_module = package_root / "daemon_embed_manager.py"
|
||||
scripts_dir = tmp_path / "bin"
|
||||
scripts_dir.mkdir()
|
||||
api_binary = scripts_dir / "hindsight-api"
|
||||
api_binary.touch()
|
||||
|
||||
manager = DaemonEmbedManager()
|
||||
# Point __file__ away from monorepo so dev-mode check doesn't trigger
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.__file__", str(tmp_path / "hindsight_embed" / "daemon_embed_manager.py"))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.sysconfig.get_path", lambda key: str(scripts_dir))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.platform.system", lambda: "Linux")
|
||||
|
||||
assert manager._find_api_command() == [str(api_binary)]
|
||||
|
||||
|
||||
def test_find_api_command_target_install_uses_file_relative_fallback(tmp_path, monkeypatch):
|
||||
"""
|
||||
When installed with `pip install --target`, sysconfig still points at the
|
||||
system/venv scripts dir (no binary there). The __file__-relative fallback
|
||||
should find the sibling binary in <target>/bin/ (issue #1240).
|
||||
"""
|
||||
# sysconfig points to an empty venv scripts dir (no binary)
|
||||
venv_scripts = tmp_path / "venv_bin"
|
||||
venv_scripts.mkdir()
|
||||
|
||||
# --target layout: binary sits next to site-packages contents
|
||||
target_dir = tmp_path / "target"
|
||||
pkg_dir = target_dir / "hindsight_embed"
|
||||
pkg_dir.mkdir(parents=True)
|
||||
fake_module = pkg_dir / "daemon_embed_manager.py"
|
||||
fake_module.write_text("")
|
||||
sibling_bin = tmp_path / "site-packages" / "bin" / "hindsight-api"
|
||||
sibling_bin.parent.mkdir(parents=True)
|
||||
sibling_bin = target_dir / "bin" / "hindsight-api"
|
||||
sibling_bin.parent.mkdir()
|
||||
sibling_bin.touch()
|
||||
|
||||
manager = DaemonEmbedManager()
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.__file__", str(fake_module))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.sysconfig.get_path", lambda key: str(venv_scripts))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.platform.system", lambda: "Linux")
|
||||
|
||||
assert manager._find_api_command() == [str(sibling_bin)]
|
||||
|
||||
|
||||
def test_find_api_command_falls_back_to_uvx_when_no_sibling_binary(tmp_path, monkeypatch):
|
||||
"""Without a sibling binary or dev checkout, fall back to uvx."""
|
||||
package_root = tmp_path / "site-packages" / "hindsight_embed"
|
||||
package_root.mkdir(parents=True)
|
||||
fake_module = package_root / "daemon_embed_manager.py"
|
||||
fake_module.write_text("")
|
||||
def test_find_api_command_falls_back_to_uvx_when_no_binary(tmp_path, monkeypatch):
|
||||
"""Without an installed binary or dev checkout, fall back to uvx."""
|
||||
scripts_dir = tmp_path / "bin"
|
||||
scripts_dir.mkdir()
|
||||
# No hindsight-api binary in scripts_dir
|
||||
|
||||
manager = DaemonEmbedManager()
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.__file__", str(fake_module))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.__file__", str(tmp_path / "hindsight_embed" / "daemon_embed_manager.py"))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.sysconfig.get_path", lambda key: str(scripts_dir))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.platform.system", lambda: "Linux")
|
||||
monkeypatch.setenv("HINDSIGHT_EMBED_API_VERSION", "1.2.3")
|
||||
|
||||
@@ -148,17 +174,16 @@ def test_find_api_command_falls_back_to_uvx_when_no_sibling_binary(tmp_path, mon
|
||||
|
||||
|
||||
def test_find_api_command_windows_uses_exe_suffix(tmp_path, monkeypatch):
|
||||
"""On Windows, the sibling binary has a .exe suffix."""
|
||||
package_root = tmp_path / "site-packages" / "hindsight_embed"
|
||||
package_root.mkdir(parents=True)
|
||||
fake_module = package_root / "daemon_embed_manager.py"
|
||||
fake_module.write_text("")
|
||||
sibling_bin = tmp_path / "site-packages" / "Scripts" / "hindsight-api.exe"
|
||||
sibling_bin.parent.mkdir(parents=True)
|
||||
sibling_bin.touch()
|
||||
"""On Windows, the installed binary has a .exe suffix."""
|
||||
scripts_dir = tmp_path / "Scripts"
|
||||
scripts_dir.mkdir()
|
||||
api_binary = scripts_dir / "hindsight-api.exe"
|
||||
api_binary.touch()
|
||||
|
||||
manager = DaemonEmbedManager()
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.__file__", str(fake_module))
|
||||
# Point __file__ away from monorepo so dev-mode check doesn't trigger
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.__file__", str(tmp_path / "hindsight_embed" / "daemon_embed_manager.py"))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.sysconfig.get_path", lambda key: str(scripts_dir))
|
||||
monkeypatch.setattr("hindsight_embed.daemon_embed_manager.platform.system", lambda: "Windows")
|
||||
|
||||
assert manager._find_api_command() == [str(sibling_bin)]
|
||||
assert manager._find_api_command() == [str(api_binary)]
|
||||
|
||||
Reference in New Issue
Block a user