跳到正文
cccskills
免费在 GitHub 公开

testing

Write and run MeshWeaver tests, and triage a red CI run. Use when adding a test, running one locally, or reading a CI failure. The house standards are non-negotiable: no mocking of IMessageHub / IMeshService / core interfaces (use the real mesh test bases), never Task.Delay to wait for propagation (wait on the condition), never assert an exact change-event count on a change feed, and never re-run a test to see whether it was a flake. Also covers the xUnit v3 configuration, why methodTimeout does not bound fixture or teardown wedges, and how to read a shard artifact (trx TextMessages, the trace log that survives a killed host, the CI exit markers).

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver星标 12(GitHub 12 + cccskills 0)

实测评分 未实测

查看安装方法

包含的文件(1)

  • SKILL.md12.3 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

/testing — real meshes, real conditions, one run

Canonical references: WritingTests.md · TestStateIsolation.md · CqrsAndContentAccess.md · SatelliteEntityPatterns.md.

The standards

No mocking. Use the real mesh test bases — never mock IMessageHub, IMeshService, or core interfaces (#1810).

Always run_in_background: true for test runs (they take minutes).

Never --verbosity minimal when tests may fail — it hides stack traces.

Never Task.Delay to wait for propagation. A fixed sleep races CI load: too short → flakes, too long → wastes minutes across the suite. Wait on the actual condition via stream.Where(...).FirstAsync().Timeout(...). When the source is request/response (not an observable), wrap the re-query in Observable.Interval(50.Milliseconds()).StartWith(0L).SelectMany(...).Where(predicate).FirstAsync().Timeout(...). Hand-rolled while + Task.Delay(50) poll loops are forbidden. Sanctioned Task.Delay uses: forcing distinct timestamps for sort assertions, and "wait to confirm nothing happened" negative tests where there's no positive signal to filter for. See WritingTests.md → "Polling loops around QueryAsync". A negative that HAS a later positive fact to end on (a sentinel through the same pipeline, the owner's answer to a follow-up request, a response that follows the work) uses .Should().Within(t).NotEmitBefore(settled) instead of NotEmit(window) — a passing NotEmit spends its whole window, 36 s on CI at TestTimeouts.Quick (#6817). WritingTests.md → "A negative assertion that spends a fixed window when a settled signal exists".

Never assert "exactly N change events" on a stream backed by pg_notify or any change feed that can race the initial-snapshot path. Filter on the emission shape (e.g. .Where(c => c.ChangeType == QueryChangeType.Initial)), not the count.

🚨 A reachability / GC assertion joins DisposalCompleted BEFORE it collects. Dispose() only STARTS a hub's teardown — it posts ShutdownRequest(Quiescing) and returns — so on return the hub is still rooted by its own in-flight shutdown. WeakReference + Dispose() + GC.Collect() therefore measures the teardown's speed, not the reference graph, and it fails GREEN: #3321's version passed inside a 485-test project and failed alone on the same binary. Take the weak reference and drop the strong one inside a [MethodImpl(NoInlining)] helper, return the completion observable rather than the object, await it, then collect. Suite-green / alone-red with nothing rebuilt is its own signal — and the PASSING run is the control: it names the ambient wait the test was silently relying on. See WritingTests.md → "A reachability assertion that collects before teardown has finished".

🚨 NEVER re-run a test (single or suite) unless code under test has changed. Re-running to "see if it was a flake" hides the bug — flakes are real races. Either fix the race or pin the failure with a smaller repro; do not retry. The only exceptions: (a) the test harness itself crashed (MSBuild MSB4166, infrastructure error — a re-run is the same input), (b) the previous run was killed by the user before completion.

🚨 .ToTask() is FORBIDDEN in tests too (maintainer, 2026-08-30 — the earlier test exemption is retracted: a Task completed inside an Rx pipeline resumes inline on the signalling thread, still inside the trampoline, so the bridge changes what the test measures). 🚨 Awaiting the observable DIRECTLY is not the alternative — Rx's awaiter is an AsyncSubject<T> that completes its continuation from inside OnCompleted and resumes inline just the same (InlineResumptionMechanismTest); this line used to say it was, and DirectObservableAwaitSites.allow is the 228-site inventory that advice produced. Assert on the stream through MeshWeaver.Reactive.Assertions, or wait through .Await(ct) / .ObserveCompletion(reportLateFault, ct) — see /async.

No static collections, in test/ as much as src/ — a Clear() added "for test isolation" is the proof of the bug, not the fix.

Configuration and its blind spot

xUnit v3 config (test/xunit.runner.json): parallelizeAssembly: false, parallelizeTestCollections: false, maxParallelThreads: 1, methodTimeout: 30000 (30 s).

🚨 methodTimeout only bounds time spent INSIDE a test method. A wedge in fixture construction, class init, or teardown is outside it and runs unbounded — on 2026-08-04 an orphaned local run sat at a pegged core for 25+ minutes and, together with a leaking e2e container, drove the colima VM to 128 MB free / load average 195, OOM-killing unrelated containers.

So always give a local test run its own wall-clock cap — but not with timeout: neither timeout nor gtimeout exists on this macOS host, so timeout 20m dotnet test … runs nothing and reports command not found. Background the run and hold the deadline yourself against date -u — the five-step shape is in /worktree → "A verification step that cannot fail".

CI has the equivalent at two levels — its runners are Linux, where timeout does exist: timeout 8m per project inside the shard loop, and timeout-minutes: 20 on the shard job itself for a wedge BETWEEN projects (which the per-project cap cannot see). If a run hits either, it is stuck — do not raise the bound, find what is not completing (AGENTS.md → "No band-aids").

Running tests

dotnet build test/MeshWeaver.Hosting.Monolith.Test/MeshWeaver.Hosting.Monolith.Test.csproj
dotnet test test/MeshWeaver.Hosting.Monolith.Test --no-build
dotnet test test/MeshWeaver.Graph.Test --filter "FullyQualifiedName~AccessAssignment" --no-build

Build first, every time: dotnet test --no-restore/--no-build against a project this worktree has never built exits 0 with no output and no .trx — an unmissable-looking pass that ran nothing. One project per dotnet build invocation (several args is an MSB1008 no-op).

Workflow: run once in background → read failures → fix → run once more.

In-mesh suites: a node's Tests area — run it ONCE, as an activity

🚨 Rendering a Tests area RUNS the suite. Every get @<node>/area/Tests (or render_area) opens a fresh subscription, so polling the area re-runs every live case, files their side effects again, and can still show a verdict an earlier run cached. The area streams now (every case pending → the running case with its elapsed time and output → the verdict), and every case is bounded, so a hung case fails as timed out: no verdict within Ns instead of leaving the page on Rendering …. From a terminal or an agent: memex tests @<node> (exit 0 passed, 1 failed, 4 no verdict in time), or the MCP run_tests tool — both drive MeshOperations.RunTests, ONE subscription written to a TestRun activity you poll with get, which starts nothing. Only a verdict frame counts: a progress frame (tests-running) shows ✔/✖, never ✅/❌ or N/M passed. Full reference: WritingTests.md, "A Tests area streams its progress".

Test triage — when CI fails

DO NOT run entire test projects. Iterate one test at a time:

  1. Read failed test names from CI logs (gh run view <id> --log).
  2. dotnet build <project>.csproj — in a fresh worktree, skipping this makes step 3 a silent no-op (exit 0, no output, no .trx).
  3. dotnet test <project> --filter "FullyQualifiedName~<TestName>" --no-build.
  4. No skipping — CI-only failures catch real timing/state bugs.

🚨 Read the run's evidence before reasoning about it. The shard artifact carries three things and they answer different questions:

  • the per-project .trx — output lives in <Output><TextMessages>, not <StdOut>; the native xUnit v3 writer does not use StdOut, so finding it empty says nothing;
  • collected-logs/_meshweaver-test-trace.log — the ONLY log that survives a host killed at the wall-clock cap, carrying TEST_START/TEST_END window markers and [FAULT] records with stacks, joinable by pid= and timestamp;
  • the classified [CI] <name> exit=<n> markers.

A host that CRASHED is written into the trx as a <project>.HOST_CRASHED failure, so no summary can report a pass over a dead process. Full map: WritingTests.md → "Reading a CI Failure".

A hub-handler test that hangs, or a message that disappears, is a message-flow problem, not a timeout problem — see /debug and DebuggingMessageFlow.md first. Never rerun a hung test "to see".

Test bases and fixtures

  • MonolithMeshTestBase (recommended) — full integration with persistence, messaging, DI; use AwaitResponseAsync(request, ...) for request/response in tests.
  • HubTestBase — message routing / layout tests; assert on the stream, e.g. await hub.Observe<TResponse>(request).Should().Within(TestTimeouts.Convergence).Emit("the response arrives"), or wait through the one bridge: await hub.Observe<TResponse>(request).FirstAsync().Timeout(TestTimeouts.Convergence).Await(ct). Never .ToTask(), and never a bare await hub.Observe<TResponse>(request).FirstAsync() — Rx's own awaiter resumes inline on the signalling thread exactly as .ToTask() does.
  • MeshWeaver.Hosting.Orleans.TestBase — the core Orleans cluster machinery (test cluster, disposal drain, shutdown-race suppression). OrleansMeshTestBase is the ONE base a suite derives from; which cluster it gets is Bootstrap => MeshBootstrap.Orleans(…) plus SiloConfiguratorType. The AI-flavoured Orleans rig (the derived fixture and the swappable chat-client factory) ships with the AI engine in MeshWeaver.Plugins, not here (#2276).

Use hub.Observe(...), not RegisterCallback/AwaitResponse — those overloads are [Obsolete] and deadlock. Tests use MonolithMeshTestBase.AwaitResponseAsync(...).

DevLogin and access control

MonolithMeshTestBase auto-logs in rbuergi@systemorph.com as Admin. Available helpers: TestUsers.Admin, TestUsers.SampleUsers(), builder.AddSampleUsers().

For per-user access-control tests, use accessService.SetCircuitContext(new AccessContext { ObjectId = "...", Name = "..." }) before creating test data; set null after.

Node types available in a test mesh

From AddGraph(): Markdown, Code, Group, User, VUser, Role, Notification, Approval, AccessAssignment, GroupMembership, PartitionAccessPolicy, ActivityLog, UserActivity, Comment, Redirect.

Agent, Skill, Thread and ThreadMessage are declared by the AI engine module, which lives in MeshWeaver.Plugins (#2276) — a core-only test mesh does not have them.

Custom types: builder.AddMeshNodes(new MeshNode("MyType") { Name = "My Type" }) in ConfigureMesh.

Checklist

  • Real test base, no mock of a core interface.
  • Every wait is on a condition (Where(...).FirstAsync().Timeout(...)), never a sleep.
  • No exact change-event count asserted on a change feed.
  • The project was built before --no-build, and the run produced a fresh .trx.
  • Any reachability / GC assertion awaits the owner's DisposalCompleted before collecting.
  • A failure was investigated, not re-run.

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

async

免费

Forwarding AccessContext through async/reactive boundaries, and the no-async rule on the actor-model mesh. Use when writing or reviewing any hub-reachable / Blazor-view / agent-round / IIoPool code that creates, reads, or updates a mesh node, OR when a write "silently does nothing" / "Access denied" appears after a .Subscribe / IIoPool / Observable.Create hop. The async/await/ToTask/.Wait/.Result/.GetAwaiter/FirstAsync family and lost AccessContext are the two ways async boundaries cause wedges and storms. Grounded in AsynchronousCalls.md, SyncedMeshNodeQueries.md, AccessContextPropagation.md.

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver122026年10月29日 更新

ci

免费

What CI proves, what it cannot prove, and how to write a gate that cannot pass on no evidence. Use when authoring or changing a GitHub Actions workflow, adding a required check, wiring a satellite repo CI, or reasoning about whether a green run actually covers your change. Covers the absolute no-skip-trapdoor rule (a gate must never carry continue-on-error on its input step nor an if that asks whether a secret is set — GitHub paints a skipped job the same colour as a passed one), the reusable workflow_call jobs satellite repos must call instead of hand-rolling, why runs on main are deliberately never cancelled, and the large body of in-mesh C# that no dotnet build or test ever type-checks.

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver122026年10月29日 更新

debug

免费

Debug a deadlock / hang / timeout in the mesh — a test that times out, a hub handler whose response never arrives, a GetRemoteStream / GetMeshNodeStream / GetQuery that never emits, an agent round or per-instance-hub activation that parks forever. Use whenever something times out and you need to find WHERE the message flow breaks down. The timeout is the symptom, never the bug — raising it just hangs longer. Grounded in DebuggingMessageFlow.md.

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver122026年10月29日 更新

Remove / reset a user (and their partition) on a running MeshWeaver portal (AKS). Use when you need to kick out a user — e.g. a throwaway test account — so they re-onboard from scratch. The normal delete API REFUSES a user's home ("user partition root … cannot be deleted"), so deletion is a Postgres-schema drop, not an MCP call. Covers where a user's data actually lives (per-user PG schemas named by BOTH id AND email, e.g. `rbuergi` + `rbuergi@systemorph.com`), how to reach PG (the connection string is inline in the portal's `ConnectionStrings__memex` env — NOT the `POSTGRES_PASSWORD` secret), the substring-match footgun (`buergi` also matches admin `rbuergi`), the in-memory resurrection gotcha (a recycle re-saves the node — a portal restart is what actually clears it), and the half-onboarded case where the user has NO schema at all (in-memory only → restart is the whole fix).

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver122026年10月29日 更新

deploy

免费

Deploy MeshWeaver — the two routes (shared AKS cluster, and Azure Container Apps via the Aspire test/prod modes) and the traps that make a deploy look done when it is not. Use when rolling a code change onto a portal, standing up or patching an environment, or verifying that what is running is what you built. FIRST RULE (maintainer, 2026-09-08): on AKS an operation is a Hosting/InstanceAction on the control instance (Roll, Restart, Suspend, Audit, Reconcile, …) executed by the in-cluster operator — az aks command invoke / kubectl are break-glass, never the procedure. Also covers the bootstrap build-image / set-image / restart sequence the operator runs for you, why an env deploy.sh is first-time setup and not a code-update path, why the database migration is a run-once Job that a helm upgrade runs — never a Deployment to roll — and the DbVersionGate that makes a portal refuse to serve ahead of its schema.

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver122026年10月29日 更新

gui

免费

Build or review ANY MeshWeaver UI — layout areas, editable views, forms, tables, content editing, side-panel composers — by wiring the framework's EXISTING data-binding pieces, NEVER hand-rolling. The one rule is ALWAYS DATA-BIND, especially editable views: the backend layout area declares WHAT to render (a UiControl tree + node PATHS), and every value read / write-back happens on the GUI side through the per-node IMeshNodeStreamCache. Hand-woven binding — replicating a node into a /data/{id} copy + a save subscription, emitting HTML strings, hand-built selects/checkboxes, .Take(1) on a live stream, async/await/QueryAsync in a layout area — is the bug class behind "awaiting first data" / "renders empty" / a stuck editable view. Use when writing or reviewing a layout area, control, editor, form, table, or any data-bound view. Grounded in src/MeshWeaver.Layout/DataBinding/, Doc/GUI/DataBinding.md + Editor.md, the Edit extension (EditorExtensions.cs), and the node-bound editor controls.

中文简介正在准备中。当前显示原文说明。

Systemorph/MeshWeaver122026年10月29日 更新

查看 Systemorph 的全部技能

报告此技能的问题