mirror of
https://github.com/logos-co/logos-logoscore-py.git
synced 2026-08-27 11:11:09 +00:00
* build: relock onto test-modules and logoscore-cli master test-modules ff5dbbd brings the ext qtproxy fixture this branch consumes, and the conformance registries with the arity entries retired. logoscore-cli 1ec3683 carries ed19258 (#99) and b3f1a403 (#101), which is what retires the pre-99 cells: the daemon reads the error channel instead of inferring failure from a null RESULT, and asks the module for getPluginMethods on a null return so an unknown name becomes METHOD_NOT_FOUND. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * conformance(ext): run the ext table through the Qt consumer, twice The ext gate ran one consumer point and computed no consumer differential. It now runs the same three-consumer shape the full_api gate has — py, extqtproxy-sync, extqtproxy-async — against test_fullapi_ext_qtproxy, the Qt-typed consumer of full_api_ext (a SEPARATE module from test_fullapi_qtproxy, because a Qt consumer wrapper is generated per contract). THE PROBE IS THE ONLY DRIVER CHANGE, and it is a real one. A consumer point with a call mode proves the selected wrapper table actually ran by making one known-good call and reading `lastCallStatus()` back — `useCallMode("async")` returning true only says the flag is set. That probe was hard-coded to `echoInt(1)`, which is a full_api method. full_api_ext has none like it: no method takes a bare scalar, and probing its one zero-parameter method with a spare `1` would work only because of a REGISTERED defect (known-ext.json B-arity-overflow drops extra arguments) and would break the day that is fixed. So `--proxy-consumer` grew a fifth field: `METHOD[:JSON_ARGS]`, defaulting to `echoInt:[1]` so no existing invocation moves. Arguments go through the table's own `materialize`, so bytes are spelled `{"_bytes": ...}` in the probe exactly as they are in a case. A malformed spec is REFUSED rather than defaulted — falling back to `echoInt` would silently stop proving the call mode and the run would stay green. tests/unit/test_matrix_proxy_consumer.py pins that, the default, and the four-field spelling. Measured on framework.lan with logos-test-modules at its matching branch: 264 cells, 217 pass / 47 xfail / 0 fail / 0 xpass / 0 uncovered. * conformance: an error expectation may name a set of acceptable codes `matches` tested one code as a substring of the reported error. A cell whose answer is decided by which of two deadlines fires first cannot be spelled that way: asserting either token asserts the timing rather than the claim. A LIST means any-of. Deliberately not a general escape hatch -- widening a single-code expectation weakens it, so a case that does this owes an explanation in `why` of what the codes have in common. The one case that uses it (failure/A/module-not-loaded) gives it: both are reported on the error channel and name a transport reason, and a VALUE or a provider-side code still fails. The single-string form is untouched, and the new test pins both -- including that a successful call is still a failure for an error expectation, which is the property the widening must not cost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * conformance: retry the SUBSCRIBE half of the event race, not just the fire half `capture_event` already knew the watcher and the fire race each other, and re-fired on a cadence to close it. Subscribing races too, and that half was attempted exactly once: `on_event` asked too early is REFUSED, the daemon answers WATCH_FAILED, and nothing tried again -- so an event cell reported a transport error instead of a value. MEASURED, across three otherwise identical runs of the same table at the same revision: event/tstr on test_fullapi_rust failed, passed, then failed. In both failing runs BOTH proxy consumers received "hello" from the same provider, which is what rules out the module: it was emitting, and the `py` subscription was losing the race. It is not a regression from the SDK bump either -- the bump is what made module startup slow enough to lose more often. Retried against the same deadline as the fire loop rather than a fixed count, so a slow start costs time instead of a false negative. A subscription that never succeeds still raises and still fails the cell, so this closes a race without hiding a refusal. Not registered as an xfail: the cell PASSES most of the time, and a registered cell that passes is an xpass, which reddens the gate. A flake has to be fixed or left visible; it cannot be parked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * conformance: an any-of expectation declares the DIFFERENTIAL too, not just the cell Widening failure/A/module-not-loaded to accept either transport code fixed the CELL and left three differential rows failing: qtproxy-sync answered RPC_FAILED while qtproxy-async answered object_unavailable, and the differential compares actual values independently of `expect`. I had assumed the delta rows follow the cell's status; they do not, and the run said so. That independence is deliberate and worth keeping -- it is what caught the `void` divergence nobody predicted -- so this does not become "both sides matched the expectation, therefore they agree". The narrow rule instead: an `expect` naming a LIST of codes has already said more than one answer satisfies the claim, which is the same kind of statement `expect_by_provider` makes for the provider axis. It gets the same treatment -- status `declared`, printed so the divergence stays visible, not counted as a failure. It applies only when the expectation names several codes AND BOTH sides satisfy it; a coordinate answering something outside the set, or a value instead of an error, still fails. One implementation, at module scope, called by the differential and exercised directly by the test -- the first draft had the rule written twice, once in a closure and once for the test to reach, which is a pair that drifts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test: move the driver tests to the file that loads the driver CI red on both arches, 2 failed / 140 passed: AttributeError: module 'matrix_report' has no attribute 'matches' AttributeError: module 'matrix_report' has no attribute 'Result' I put two tests for run_matrix.py's `matches` and `declared_multi_divergence` into tests/unit/test_matrix_report.py, whose `R` is the matrix_report module. The driver is a different file and is loaded by importlib, in tests/unit/test_matrix_proxy_consumer.py. The tests move there, unchanged in substance. Why this reached CI at all: I could not run pytest locally and shipped the tests unexecuted. That was wrong twice over — `python3 -m pytest` failed only because the homebrew interpreter has no pytest, and pyenv had one the whole time. "The tool is missing" was a conclusion I drew from one command instead of looking. Now run: 138 passed, 4 skipped locally (the 4 skips are environmental — the shared conformance table is not resolvable outside the nix build; CI resolves it and runs them). Total matches CI's 142. Both conformance matrices were already green in CI on this branch; this touches nothing they exercise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
491 lines
28 KiB
Nix
491 lines
28 KiB
Nix
{
|
|
description = "Python wrappers for the logoscore and logosctl CLIs — launch daemons, load modules, call methods, subscribe to events";
|
|
|
|
inputs = {
|
|
logos-nix.url = "github:logos-co/logos-nix";
|
|
nixpkgs.follows = "logos-nix/nixpkgs";
|
|
logos-logoscore-cli.url = "github:logos-co/logos-logoscore-cli";
|
|
logos-test-modules.url = "github:logos-co/logos-test-modules";
|
|
};
|
|
|
|
outputs = { self, nixpkgs, logos-logoscore-cli, logos-test-modules, ... }:
|
|
let
|
|
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
|
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
|
|
inherit system;
|
|
pkgs = import nixpkgs { inherit system; };
|
|
});
|
|
|
|
in
|
|
{
|
|
# ── Packages ──────────────────────────────────────────────────────────
|
|
# `nix build` produces a Python wheel. The `logoscore` CLI is propagated
|
|
# so anyone using this package also has the binary on PATH.
|
|
#
|
|
# `dockerBundle` / `dockerBundlePortable` (Linux only) prepare an
|
|
# `out/bundle` directory consumed by `tests/docker_smoke/Dockerfile`
|
|
# — the smoke image's stage-1 nix-build copies it into the
|
|
# ubuntu-based runtime stage. The actual docker image is built
|
|
# via `tests/docker_smoke/build_smoke_image.sh`, not directly
|
|
# from these flake outputs.
|
|
packages = forAllSystems ({ pkgs, system }:
|
|
let
|
|
logoscoreBin = logos-logoscore-cli.packages.${system}.default;
|
|
pythonPkg = pkgs.python3Packages.buildPythonPackage {
|
|
pname = "logoscore";
|
|
version = "0.1.0";
|
|
format = "pyproject";
|
|
src = ./.;
|
|
nativeBuildInputs = [ pkgs.python3Packages.hatchling ];
|
|
# Only `logoscore` is propagated, even though the wheel now also
|
|
# ships the `logosctl` client. Propagating `ctl` would put both
|
|
# binaries on the critical path of `nix build` — the one output
|
|
# every consumer of this flake pulls. Anyone who wants logosctl
|
|
# takes it from `logos-logoscore-cli.packages.*.ctl` explicitly
|
|
# (the dev shell and the logosctl checks below do exactly that).
|
|
propagatedBuildInputs = [ logoscoreBin ];
|
|
doCheck = false;
|
|
pythonImportsCheck = [ "logoscore" "logosctl" ];
|
|
};
|
|
|
|
# ── Docker bundles ─────────────────────────────────────────────
|
|
# The bundle is **just the logoscore CLI** plus whatever modules
|
|
# it ships with (currently capability_module, package_manager_module).
|
|
# No test modules — those get bind-mounted at runtime via
|
|
# `-v $modules_dir:/user-modules` and `-m /user-modules`. That
|
|
# makes the image reusable for anyone who wants to test their
|
|
# own module: pull the image, `docker run -v ./my-modules:/user-modules
|
|
# logoscore:smoke-dev daemon -m /user-modules …`.
|
|
#
|
|
# Two flavors:
|
|
#
|
|
# * `dockerBundle` (dev) — uses `.#cli` (the default logoscore
|
|
# package, which links against Qt/Boost/OpenSSL from the nix
|
|
# store via rpath). Smaller bundle (~60 MB payload) but the
|
|
# runtime image MUST ship the nix store so those rpaths
|
|
# resolve, and the CLI's built-in modules are found via
|
|
# LOGOS_BUNDLED_MODULES_DIR (set by `wrapQtAppsNoGuiHook`
|
|
# when the CLI was built). This is the flavor the
|
|
# `logoscore-py` dev shell matches.
|
|
#
|
|
# * `dockerBundlePortable` — uses `.#cli-bundle-dir` (a
|
|
# self-contained `bin/ + lib/ + modules/` tree with every
|
|
# Qt dep + the CLI's built-in modules copied in). Larger
|
|
# (~400 MB) but runs standalone — no nix store needed. The
|
|
# CLI's built-in modules live at `/opt/logoscore/modules`
|
|
# and are discovered by explicitly passing `-m
|
|
# /opt/logoscore/modules` (no wrapper env var here).
|
|
#
|
|
# The Dockerfile picks one via `--build-arg FLAVOR=dev|portable`.
|
|
# The pytest suite parametrises over both flavors so regressions
|
|
# in either path surface in the smoke matrix.
|
|
|
|
logoscorePortable = logos-logoscore-cli.packages.${system}.cli-bundle-dir;
|
|
|
|
dockerBundle = pkgs.runCommand "logoscore-bundle-dev" { } ''
|
|
# Dev flavor: just the binary. rpath points into /nix/store
|
|
# (copied wholesale in Dockerfile stage 2), and the
|
|
# wrapped binary carries `LOGOS_BUNDLED_MODULES_DIR` baked
|
|
# in — pointing at the CLI's own modules dir in the store —
|
|
# so capability_module etc. resolve without extra `-m` flags.
|
|
mkdir -p $out/bin
|
|
cp ${logoscoreBin}/bin/logoscore $out/bin/
|
|
'';
|
|
|
|
dockerBundlePortable = pkgs.runCommand "logoscore-bundle-portable" { } ''
|
|
# Portable flavor: cli-bundle-dir is already a self-contained
|
|
# bin/ + lib/ + modules/ tree — the CLI's own built-in
|
|
# modules live under its modules/ subdir. Copy it as-is.
|
|
mkdir -p $out
|
|
cp -r ${logoscorePortable}/* $out/
|
|
chmod -R u+w $out
|
|
'';
|
|
in {
|
|
default = pythonPkg;
|
|
logoscore-py = pythonPkg;
|
|
dockerBundle = dockerBundle;
|
|
dockerBundlePortable = dockerBundlePortable;
|
|
}
|
|
);
|
|
|
|
# ── Dev shell ─────────────────────────────────────────────────────────
|
|
# `nix develop` drops you into a shell with python + pytest + logoscore
|
|
# + a pre-built test_fullapi_cpp install tree, so `pytest` just works
|
|
# without any extra environment setup. The nix `integration` check
|
|
# sets the same two env vars, so the dev shell matches CI behaviour.
|
|
devShells = forAllSystems ({ pkgs, system }:
|
|
let
|
|
logoscoreBin = logos-logoscore-cli.packages.${system}.default;
|
|
# Both binaries are on PATH here so a plain `pytest` covers both
|
|
# suites — tests/logosctl skips silently when LOGOSCTL_BIN is unset,
|
|
# which would make the new suite look green while never running.
|
|
logosctlBin = logos-logoscore-cli.packages.${system}.ctl;
|
|
# `test_fullapi_cpp` (universal C++) is the single test module the
|
|
# suite loads — its methods + typed events span the whole
|
|
# parameter/return/event surface. `.install` lays out
|
|
# modules/<name>/… ready for the daemon's `-m` flag.
|
|
testModulesInstall = logos-test-modules.modules.${system}.test_fullapi_cpp.install;
|
|
testModulesInstallPortable = logos-test-modules.modules.${system}.test_fullapi_cpp.install-portable;
|
|
in {
|
|
default = pkgs.mkShell {
|
|
packages = [
|
|
(pkgs.python3.withPackages (ps: [ ps.pytest ]))
|
|
logoscoreBin
|
|
logosctlBin
|
|
];
|
|
|
|
# Integration tests skip when these are unset (by design, so
|
|
# `pytest` on a plain Python env doesn't try to spawn daemons).
|
|
# Exporting them here means the dev shell exercises the full
|
|
# suite out of the box.
|
|
#
|
|
# Two module-dir vars because the docker smoke flavors differ:
|
|
# the `dev` image has /nix/store so `.install` modules (which
|
|
# rpath into the store) work; the `portable` image is standalone
|
|
# so we need `.install-portable` (self-contained). The docker
|
|
# smoke fixture picks the right one per flavor.
|
|
LOGOSCORE_BIN = "${logoscoreBin}/bin/logoscore";
|
|
LOGOSCORE_TEST_MODULES_DIR = "${testModulesInstall}/modules";
|
|
LOGOSCORE_TEST_MODULES_DIR_PORTABLE = "${testModulesInstallPortable}/modules";
|
|
|
|
# The logosctl suite reads its own pair of variables (its conftest
|
|
# rebinds `test_modules_dir` to LOGOSCTL_TEST_MODULES_DIR) so a
|
|
# machine can point the two suites at different builds. Here they
|
|
# are the same modules — the module ABI is shared, only the CLI differs.
|
|
LOGOSCTL_BIN = "${logosctlBin}/bin/logosctl";
|
|
LOGOSCTL_TEST_MODULES_DIR = "${testModulesInstall}/modules";
|
|
|
|
shellHook = ''
|
|
echo "logos-logoscore-py dev shell"
|
|
echo " python: $(python --version)"
|
|
echo " logoscore: $(logoscore --version 2>/dev/null || echo 'not on PATH')"
|
|
echo " logosctl: $(logosctl --version 2>/dev/null || echo 'not on PATH')"
|
|
echo " LOGOSCORE_BIN: $LOGOSCORE_BIN"
|
|
echo " LOGOSCORE_TEST_MODULES_DIR (dev): $LOGOSCORE_TEST_MODULES_DIR"
|
|
echo " LOGOSCORE_TEST_MODULES_DIR_PORTABLE: $LOGOSCORE_TEST_MODULES_DIR_PORTABLE"
|
|
echo " LOGOSCTL_BIN: $LOGOSCTL_BIN"
|
|
echo " LOGOSCTL_TEST_MODULES_DIR: $LOGOSCTL_TEST_MODULES_DIR"
|
|
export PYTHONPATH="$PWD/src:$PYTHONPATH"
|
|
'';
|
|
};
|
|
});
|
|
|
|
# ── Checks ────────────────────────────────────────────────────────────
|
|
# `nix flake check` runs the unit tests (no daemon required) and the
|
|
# integration test suite against a real CLI + test modules.
|
|
#
|
|
# Both suites exist twice, once per client: `unit` / `integration-*`
|
|
# drive `logoscore`, `unit-logosctl` / `integration-logosctl-*` drive
|
|
# `logosctl`. Separate derivations throughout, never one derivation
|
|
# looping over both — see the `unit-logosctl` comment.
|
|
#
|
|
# The integration suite is replicated across three transports so a
|
|
# regression in tcp framing or tcp_ssl handshaking surfaces at the
|
|
# same layer the test names already cover. Three separate flake
|
|
# outputs (rather than one derivation that loops) so:
|
|
# - CI can fan them out across runners in parallel,
|
|
# - a tcp_ssl failure doesn't block the local/tcp signal,
|
|
# - the build log of any single transport stays focused.
|
|
checks = forAllSystems ({ pkgs, system }:
|
|
let
|
|
python = pkgs.python3.withPackages (ps: [ ps.pytest ]);
|
|
logoscoreBin = logos-logoscore-cli.packages.${system}.default;
|
|
# Same repo, sibling output: `ctl` ships alongside `default`/
|
|
# `cli` since logos-logoscore-cli#76. Referenced only from the
|
|
# `*-logosctl` checks below so a logosctl hiccup cannot redden a
|
|
# logoscore check's evaluation path either.
|
|
logosctlBin = logos-logoscore-cli.packages.${system}.ctl;
|
|
# `.install` lays out modules/<name>/<name>_plugin.{so,dylib} +
|
|
# manifest.json — the layout logoscore's `-m` flag expects.
|
|
# `test_fullapi_cpp` (universal C++) is the single test module the
|
|
# integration suite loads; its methods + typed events span the
|
|
# whole parameter/return/event surface.
|
|
testModulesInstall = logos-test-modules.modules.${system}.test_fullapi_cpp.install;
|
|
# The conformance matrix replays every case against BOTH providers —
|
|
# a divergence between them is a finding in its own right, and one
|
|
# provider must never be able to satisfy an assertion for the other.
|
|
testModulesRustInstall = logos-test-modules.modules.${system}.test_fullapi_rust.install;
|
|
# The ext contract (records, bytes at depth, typed maps, nested
|
|
# composites). The C++ cdylib backend could not express these types
|
|
# when the table was split out, so it ran single-provider and without a
|
|
# differential; logos-cpp-sdk#125 lifted that and both providers are
|
|
# wired below, so this table carries a provider differential like
|
|
# full_api. Its consumer axis is `testModulesExtQtProxyInstall` below.
|
|
testModulesExtInstall = logos-test-modules.modules.${system}.test_fullapi_ext_rust.install;
|
|
testModulesExtCppInstall = logos-test-modules.modules.${system}.test_fullapi_ext_cpp.install;
|
|
# The QT-TYPED consumer. `type: core` with no `interface` key selects
|
|
# apiStyle=qt, so this module's generated wrappers are the Qt ones —
|
|
# the surface the two existing proxies cannot reach (universal forces
|
|
# apiStyle=lp, cdylib forces the Rust client). It forwards the whole
|
|
# contract, so the case table replays through it unchanged, twice:
|
|
# once per generated wrapper table (sync / async).
|
|
testModulesQtProxyInstall =
|
|
logos-test-modules.modules.${system}.test_fullapi_qtproxy.install;
|
|
# The ext table's Qt-typed consumer, and the reason it is a SECOND
|
|
# module rather than a second binding of the first: a Qt consumer
|
|
# wrapper is generated per CONTRACT, and full_api_ext is a different
|
|
# contract. It is also where the widened Qt mapping actually lives —
|
|
# records, QList<Blob>, QMap<QString, QList<QByteArray>>,
|
|
# QList<QList<qlonglong>>, std::optional<QString> — none of which
|
|
# full_api can spell, so none of which qtproxy-sync/async execute.
|
|
testModulesExtQtProxyInstall =
|
|
logos-test-modules.modules.${system}.test_fullapi_ext_qtproxy.install;
|
|
|
|
# Helper: run the integration suite once with the given
|
|
# `--transport` value. Same env wiring as the unit check
|
|
# plus openssl (needed by the `self_signed_cert` fixture
|
|
# for `tcp_ssl`; harmless for `local` / `tcp`).
|
|
mkIntegration = transport: pkgs.runCommand
|
|
"logoscore-py-integration-tests-${transport}" {
|
|
nativeBuildInputs = [ python logoscoreBin pkgs.openssl ]
|
|
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
|
|
} ''
|
|
cp -r ${./.}/. .
|
|
chmod -R +w .
|
|
export QT_QPA_PLATFORM=offscreen
|
|
export QT_FORCE_STDERR_LOGGING=1
|
|
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
|
|
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
|
|
''}
|
|
export PYTHONPATH=$PWD/src
|
|
export LOGOSCORE_BIN=${logoscoreBin}/bin/logoscore
|
|
export LOGOSCORE_TEST_MODULES_DIR=${testModulesInstall}/modules
|
|
# Run from a writable HOME so any stray ~/.logoscore writes are isolated.
|
|
export HOME=$PWD/home
|
|
mkdir -p $HOME
|
|
${python}/bin/pytest tests/integration -v --transport=${transport}
|
|
touch $out
|
|
'';
|
|
|
|
# Helper: the same thing for the logosctl suite. A sibling rather
|
|
# than a `binary:`/`suite:` parameter on `mkIntegration`, for the
|
|
# reason the two test trees are duplicated in the first place — the
|
|
# two CLIs configure a daemon through different mechanisms, and
|
|
# retiring logoscore should be a delete, not an untangle. The two
|
|
# helpers drifting apart is expected, not a smell.
|
|
mkIntegrationLogosctl = transport: pkgs.runCommand
|
|
"logosctl-py-integration-tests-${transport}" {
|
|
nativeBuildInputs = [ python logosctlBin pkgs.openssl ]
|
|
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
|
|
} ''
|
|
cp -r ${./.}/. .
|
|
chmod -R +w .
|
|
export QT_QPA_PLATFORM=offscreen
|
|
export QT_FORCE_STDERR_LOGGING=1
|
|
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
|
|
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
|
|
''}
|
|
export PYTHONPATH=$PWD/src
|
|
export LOGOSCTL_BIN=${logosctlBin}/bin/logosctl
|
|
export LOGOSCTL_TEST_MODULES_DIR=${testModulesInstall}/modules
|
|
# tests/logosctl/conftest.py SKIPS when either of those is unset,
|
|
# so a rename here turns the whole suite green-by-omission.
|
|
#
|
|
# Writable HOME: logosctl defaults to ~/.logosctl and refuses to
|
|
# start a second daemon in a config dir that already holds a live
|
|
# one. Every test drives an isolated --config-dir, but a stray
|
|
# default-session write must still land somewhere sandbox-local.
|
|
export HOME=$PWD/home
|
|
mkdir -p $HOME
|
|
${python}/bin/pytest tests/logosctl/integration -v --transport=${transport}
|
|
touch $out
|
|
'';
|
|
in
|
|
# `rec` so `conformance-matrix-merged` can name the two runs it is built
|
|
# from. It depends on them; it does not re-measure anything.
|
|
rec {
|
|
unit = pkgs.runCommand "logoscore-py-unit-tests" {
|
|
nativeBuildInputs = [ python ];
|
|
} ''
|
|
cp -r ${./.}/. .
|
|
chmod -R +w .
|
|
export PYTHONPATH=$PWD/src
|
|
# The inline-vs-shared table guard resolves the shared table by
|
|
# sibling checkout, which does not exist in the sandbox — so without
|
|
# this it SKIPPED here and ran only on a developer's workspace. A
|
|
# drift guard that is green because it never ran is worse than no
|
|
# guard: pointed at the table it found two boundaries pinned inline
|
|
# and absent from cases.json.
|
|
export LOGOS_CONFORMANCE_DIR=${logos-test-modules}/conformance
|
|
${python}/bin/pytest tests/unit -v
|
|
touch $out
|
|
'';
|
|
|
|
# ── logosctl ────────────────────────────────────────────────────
|
|
# A parallel suite for the parallel client, in its own derivations
|
|
# so nix builds it concurrently with the logoscore ones and a red
|
|
# logosctl cannot mask a logoscore regression. Dropping logosctl
|
|
# later is deleting these four attributes, `mkIntegrationLogosctl`,
|
|
# and `logosctlBin`.
|
|
#
|
|
# Deliberately NOT duplicated: the conformance matrix. It measures
|
|
# the LIDL type contract, which lives in the runtime both binaries
|
|
# embed — replaying the whole matrix through a second CLI would
|
|
# double the longest job in the flake for no signal the first run
|
|
# does not already carry.
|
|
unit-logosctl = pkgs.runCommand "logosctl-py-unit-tests" {
|
|
nativeBuildInputs = [ python ];
|
|
} ''
|
|
cp -r ${./.}/. .
|
|
chmod -R +w .
|
|
export PYTHONPATH=$PWD/src
|
|
# No LOGOSCTL_BIN: these tests drive a fake binary and assert on
|
|
# argv, env and the generated config documents. Nothing here
|
|
# spawns a daemon, which is why they run without the CLI closure.
|
|
${python}/bin/pytest tests/logosctl/unit -v
|
|
touch $out
|
|
'';
|
|
|
|
# The LIDL conformance matrix: every (type x position) in the
|
|
# `full_api` contract, replayed against BOTH providers and every
|
|
# CONSUMER surface, reported as per-cell coordinates. The case table
|
|
# and the xfail registry live in logos-test-modules/conformance/ (with
|
|
# the providers they describe); this repo owns the driver because it
|
|
# owns the client it uses.
|
|
#
|
|
# Three consumers, one run — they have to share a process for the
|
|
# consumer differential to exist at all:
|
|
# py this package's client, talking to the provider
|
|
# qtproxy-sync Qt-typed wrappers, sync table (_result.toT())
|
|
# qtproxy-async Qt-typed wrappers, async table (qvariant_cast<T>)
|
|
#
|
|
# Fails on: a red cell, an `xpass` (a registered known-broken cell
|
|
# that started passing — the registry has to be updated), a `skip`
|
|
# entry whose cell turns out to work, or a (type, position) the
|
|
# contract declares and no case covers.
|
|
#
|
|
# Three artifacts land in $out, and none of them is the verdict — the
|
|
# exit status is: matrix.txt (the terminal report, with the TYPE x
|
|
# POSITION grid and the differential), matrix.jsonl (one object per
|
|
# cell, unchanged), and matrix.html — a self-contained page with no
|
|
# external requests, publishable to Pages the way the doctest
|
|
# harness's report already is.
|
|
conformance-matrix = pkgs.runCommand "logoscore-py-conformance-matrix" {
|
|
nativeBuildInputs = [ python logoscoreBin pkgs.openssl ]
|
|
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
|
|
} ''
|
|
cp -r ${./.}/. .
|
|
chmod -R +w .
|
|
export QT_QPA_PLATFORM=offscreen
|
|
export QT_FORCE_STDERR_LOGGING=1
|
|
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
|
|
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
|
|
''}
|
|
export PYTHONPATH=$PWD/src
|
|
export HOME=$PWD/home
|
|
mkdir -p $HOME $out
|
|
${python}/bin/python conformance/run_matrix.py \
|
|
--logoscore ${logoscoreBin}/bin/logoscore \
|
|
--cases ${logos-test-modules}/conformance/cases.json \
|
|
--known ${logos-test-modules}/conformance/known.json \
|
|
--contract ${logos-test-modules}/test-fullapi-proxy-module-rust/full_api.lidl \
|
|
--cpp-modules ${testModulesInstall}/modules \
|
|
--rust-modules ${testModulesRustInstall}/modules \
|
|
--proxy-consumer qtproxy-sync=test_fullapi_qtproxy=${testModulesQtProxyInstall}/modules=sync \
|
|
--proxy-consumer qtproxy-async=test_fullapi_qtproxy=${testModulesQtProxyInstall}/modules=async \
|
|
--jsonl $out/matrix.jsonl \
|
|
--report $out/matrix.html \
|
|
--md $out/known-broken.md \
|
|
--no-color \
|
|
2>&1 | tee $out/matrix.txt
|
|
'';
|
|
|
|
# The ext contract, with the SAME three-consumer shape the full_api
|
|
# gate has:
|
|
# py this package's client, talking to the provider
|
|
# extqtproxy-sync Qt-typed wrappers, sync table
|
|
# extqtproxy-async Qt-typed wrappers, async table
|
|
#
|
|
# The proxy is a DIFFERENT module from the full_api one because a Qt
|
|
# consumer wrapper is generated per contract. The probe method is
|
|
# given explicitly (fifth field of --proxy-consumer): the driver's
|
|
# default is `echoInt`, which full_api_ext does not have.
|
|
conformance-matrix-ext = pkgs.runCommand "logoscore-py-conformance-matrix-ext" {
|
|
nativeBuildInputs = [ python logoscoreBin pkgs.openssl ]
|
|
++ pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.qt6.qtbase ];
|
|
} ''
|
|
cp -r ${./.}/. .
|
|
chmod -R +w .
|
|
export QT_QPA_PLATFORM=offscreen
|
|
export QT_FORCE_STDERR_LOGGING=1
|
|
${pkgs.lib.optionalString pkgs.stdenv.isLinux ''
|
|
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/${pkgs.qt6.qtbase.qtPluginPrefix}"
|
|
''}
|
|
export PYTHONPATH=$PWD/src
|
|
export HOME=$PWD/home
|
|
mkdir -p $HOME $out
|
|
${python}/bin/python conformance/run_matrix.py \
|
|
--logoscore ${logoscoreBin}/bin/logoscore \
|
|
--cases ${logos-test-modules}/conformance/ext-cases.json \
|
|
--known ${logos-test-modules}/conformance/known-ext.json \
|
|
--contract ${logos-test-modules}/test-fullapi-ext-module-rust/rust-lib/test_fullapi_ext_rust.lidl \
|
|
--modules test_fullapi_ext_rust=${testModulesExtInstall}/modules \
|
|
--modules test_fullapi_ext_cpp=${testModulesExtCppInstall}/modules \
|
|
--proxy-consumer 'extqtproxy-sync=test_fullapi_ext_qtproxy=${testModulesExtQtProxyInstall}/modules=sync=echoStringMap:[{"k":"v"}]' \
|
|
--proxy-consumer 'extqtproxy-async=test_fullapi_ext_qtproxy=${testModulesExtQtProxyInstall}/modules=async=echoStringMap:[{"k":"v"}]' \
|
|
--jsonl $out/matrix-ext.jsonl \
|
|
--report $out/matrix-ext.html \
|
|
--no-color \
|
|
2>&1 | tee $out/matrix-ext.txt
|
|
'';
|
|
|
|
# ── the merged report ───────────────────────────────────────────
|
|
# ONE page, BOTH contracts, and nothing computed across them. A
|
|
# view-layer merge: it re-runs no measurement, it reads what the two
|
|
# runs above already wrote (each page carries its payload in
|
|
# `<script id="report-data">`) and lays them out as two labelled
|
|
# contracts on one page.
|
|
#
|
|
# A third DERIVATION rather than a third run, and the two gates stay
|
|
# separate on purpose: they measure different contracts against
|
|
# different providers, and one going red must not suppress the
|
|
# other's report. The consequence is that this derivation cannot
|
|
# build while either gate is red — which is right for a `check`, and
|
|
# exactly why CI does NOT get its landing page from here: CI merges
|
|
# the uploaded ARTIFACTS with the same script (see
|
|
# .github/workflows/conformance.yml), so the merged page still exists
|
|
# on the run where it is most worth reading.
|
|
#
|
|
# $out is laid out as the published site, so `nix build` produces
|
|
# something serveable as-is: index.html is the merged page, and
|
|
# full/ + ext/ are the standalone reports it links to.
|
|
conformance-matrix-merged =
|
|
pkgs.runCommand "logoscore-py-conformance-matrix-merged" {
|
|
nativeBuildInputs = [ python ];
|
|
} ''
|
|
mkdir -p $out/full $out/ext
|
|
cp ${conformance-matrix}/matrix.html $out/full/index.html
|
|
cp ${conformance-matrix}/matrix.jsonl $out/full/matrix.jsonl
|
|
cp ${conformance-matrix}/matrix.txt $out/full/matrix.txt
|
|
cp ${conformance-matrix}/known-broken.md $out/full/known-broken.md
|
|
cp ${conformance-matrix-ext}/matrix-ext.html $out/ext/index.html
|
|
cp ${conformance-matrix-ext}/matrix-ext.jsonl $out/ext/matrix.jsonl
|
|
cp ${conformance-matrix-ext}/matrix-ext.txt $out/ext/matrix.txt
|
|
chmod -R u+w $out
|
|
${python}/bin/python ${./conformance/matrix_report.py} \
|
|
--merge $out/full/index.html $out/ext/index.html \
|
|
--href ./full/ ./ext/ \
|
|
-o $out/index.html
|
|
'';
|
|
|
|
# One check per transport. CI's matrix fans them out; a local
|
|
# `nix flake check` runs all three sequentially.
|
|
integration-local = mkIntegration "local";
|
|
integration-tcp = mkIntegration "tcp";
|
|
integration-tcp_ssl = mkIntegration "tcp_ssl";
|
|
|
|
# Same three transports, driven through logosctl. Split the same way
|
|
# and for the same reasons.
|
|
integration-logosctl-local = mkIntegrationLogosctl "local";
|
|
integration-logosctl-tcp = mkIntegrationLogosctl "tcp";
|
|
integration-logosctl-tcp_ssl = mkIntegrationLogosctl "tcp_ssl";
|
|
|
|
# Back-compat alias — equivalent to `integration-local`. Kept
|
|
# so anyone with `nix build .#checks.<system>.integration` in
|
|
# muscle memory still gets a green path.
|
|
integration = mkIntegration "local";
|
|
}
|
|
);
|
|
};
|
|
}
|