Stufe 4c (#764): Kommentare in C#, WebUI-Skripten, CSS und HTML auf Englisch
Rund 2.600 Kommentarblöcke in 310 C#-Dateien übersetzt, Begründungen vollständig erhalten: blockweise gleiche Anzahl wie vorher, kein Block unter 60 % seiner Länge, der Code außerhalb der Kommentare per Tokenizer byte-identisch geprüft. Zitate deutscher Oberflächentexte und Testdaten bleiben deutsch. WebUI: Kommentare in JS/CSS/HTML übersetzt, JSDoc-@param an die in 4b umbenannten Parameter angeglichen. Dabei behoben: - 4b hatte "tag" (HTML-Tag) als deutsches "Tag" gelesen und zu "dayValue" gemacht (makeElement, svg, Tagesübersicht) - zurück auf tag/day. - Aufgaben "InProgress" bekamen die Plakettenart "laufend", für die es nie eine CSS-Klasse gab - jetzt "success" wie laufende Instanzen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
da4ceac7d5
commit
837a634a22
@@ -4,14 +4,14 @@ using ClawdDotNet.Core.Api.Models;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Prüft, ob eine Nachrichtenfolge von der Chat-Completions-API akzeptiert würde.
|
||||
/// Checks whether a message sequence would be accepted by the chat completions API.
|
||||
///
|
||||
/// Die Regeln entsprechen dem, was OpenRouter/Anthropic/OpenAI verlangen:
|
||||
/// Eine tool-Antwort ist nur gültig, wenn ihr eine assistant-Nachricht mit einem
|
||||
/// passenden tool_call vorausgeht — und jeder tool_call braucht seine Antwort.
|
||||
/// The rules correspond to what OpenRouter/Anthropic/OpenAI demand:
|
||||
/// a tool answer is only valid if it is preceded by an assistant message with a
|
||||
/// matching tool_call — and every tool_call needs its answer.
|
||||
///
|
||||
/// Wird eine Regel verletzt, antwortet die API mit HTTP 400 und der laufende
|
||||
/// Agent bricht ab. Genau das ist Bug B1 aus der Bestandsaufnahme.
|
||||
/// If a rule is violated, the API answers with HTTP 400 and the running
|
||||
/// agent aborts. That is exactly bug B1 from the inventory.
|
||||
/// </summary>
|
||||
internal static class ContextInvariants
|
||||
{
|
||||
@@ -37,22 +37,22 @@ internal static class ContextInvariants
|
||||
|
||||
private static IEnumerable<string> Validate(IReadOnlyList<ChatMessage> messages)
|
||||
{
|
||||
// ── Regel 1: höchstens eine system-Nachricht, und zwar ganz vorne ──
|
||||
// ── Rule 1: at most one system message, and at the very front ──
|
||||
for (var i = 1; i < messages.Count; i++)
|
||||
{
|
||||
if (messages[i].Role == "system")
|
||||
yield return $"[{i}] system-Nachricht steht nicht an Position 0";
|
||||
}
|
||||
|
||||
// ── Regel 2: jede tool-Nachricht braucht eine ToolCallId ──
|
||||
// ── Rule 2: every tool message needs a ToolCallId ──
|
||||
for (var i = 0; i < messages.Count; i++)
|
||||
{
|
||||
if (messages[i].Role == "tool" && string.IsNullOrEmpty(messages[i].ToolCallId))
|
||||
yield return $"[{i}] tool-Nachricht ohne tool_call_id";
|
||||
}
|
||||
|
||||
// ── Regel 3: jede tool-Nachricht gehört zum unmittelbar vorausgehenden
|
||||
// assistant-Block mit passender tool_call-Id ──
|
||||
// ── Rule 3: every tool message belongs to the immediately preceding
|
||||
// assistant block with a matching tool_call ID ──
|
||||
var openCalls = new HashSet<string>();
|
||||
var answered = new HashSet<string>();
|
||||
|
||||
@@ -62,7 +62,7 @@ internal static class ContextInvariants
|
||||
|
||||
if (msg.Role == "assistant" && msg.ToolCalls is { Count: > 0 })
|
||||
{
|
||||
// Vorheriger Block muss vollständig beantwortet sein
|
||||
// The previous block must be completely answered
|
||||
var unanswered = openCalls.Except(answered).ToList();
|
||||
if (unanswered.Count > 0)
|
||||
yield return $"[{i}] neuer assistant-Block, aber tool_call(s) noch unbeantwortet: {string.Join(", ", unanswered)}";
|
||||
@@ -87,7 +87,7 @@ internal static class ContextInvariants
|
||||
}
|
||||
else
|
||||
{
|
||||
// user/system/assistant-ohne-tool_calls beenden den Block
|
||||
// user/system/assistant-without-tool_calls end the block
|
||||
var unanswered = openCalls.Except(answered).ToList();
|
||||
if (unanswered.Count > 0)
|
||||
yield return $"[{i}] {msg.Role}-Nachricht, aber tool_call(s) unbeantwortet: {string.Join(", ", unanswered)}";
|
||||
@@ -97,13 +97,13 @@ internal static class ContextInvariants
|
||||
}
|
||||
}
|
||||
|
||||
// ── Regel 4: am Ende darf kein tool_call offen sein ──
|
||||
// ── Rule 4: no tool_call may be open at the end ──
|
||||
var stillOpen = openCalls.Except(answered).ToList();
|
||||
if (stillOpen.Count > 0)
|
||||
yield return $"[Ende] unbeantwortete tool_call(s): {string.Join(", ", stillOpen)}";
|
||||
}
|
||||
|
||||
/// <summary>Kompakte, lesbare Darstellung für Fehlermeldungen.</summary>
|
||||
/// <summary>Compact, readable representation for error messages.</summary>
|
||||
public static string Describe(IReadOnlyList<ChatMessage> messages)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
@@ -3,7 +3,7 @@ using ClawdDotNet.Core.Api.Models;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Baut gültige Nachrichtenfolgen für Tests — lesbar und garantiert API-konform.
|
||||
/// Builds valid message sequences for tests — readable and guaranteed API-compliant.
|
||||
/// </summary>
|
||||
internal sealed class Conversation
|
||||
{
|
||||
@@ -31,8 +31,8 @@ internal sealed class Conversation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fügt einen vollständigen Tool-Zyklus hinzu: assistant mit N tool_calls,
|
||||
/// gefolgt von genau N passenden tool-Antworten.
|
||||
/// Adds a complete tool cycle: assistant with N tool_calls,
|
||||
/// followed by exactly N matching tool answers.
|
||||
/// </summary>
|
||||
public Conversation ToolCycle(int count = 1, int resultLength = 50, string toolName = "TestTool")
|
||||
{
|
||||
@@ -54,7 +54,7 @@ internal sealed class Conversation
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Wiederholt ein Muster aus User-Nachricht und Tool-Zyklus.</summary>
|
||||
/// <summary>Repeats a pattern of user message and tool cycle.</summary>
|
||||
public Conversation Repeat(int times, int toolCallsPerCycle = 1, int resultLength = 50)
|
||||
{
|
||||
for (var i = 0; i < times; i++)
|
||||
|
||||
@@ -5,17 +5,17 @@ using ClawdDotNet.Core.Tools;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Ein Tool, das einen anderen Agenten beauftragt — der Nachbau dessen, was
|
||||
/// <c>AgentComm.send_message</c> und <c>AgentSpawn.spawn_agent</c> tun.
|
||||
/// A tool that commissions another agent — the replica of what
|
||||
/// <c>AgentComm.send_message</c> and <c>AgentSpawn.spawn_agent</c> do.
|
||||
///
|
||||
/// <para>Der Nachbau ist nötig, weil beide Werkzeuge in eigenen Projekten liegen, die
|
||||
/// <c>ClawdDotNet.Core.Tests</c> nicht referenziert. Entscheidend ist ohnehin nur die
|
||||
/// eine Zeile, in der sie sich hier gleichen: Die Delegationskette des eigenen Laufs
|
||||
/// wird weitergereicht. Ein Tool, das sie unterschlägt, macht aus einem Zyklus eine
|
||||
/// neue Beauftragung — genau der Fall, den B8 beschreibt.</para>
|
||||
/// <para>The replica is necessary because both tools lie in projects of their own that
|
||||
/// <c>ClawdDotNet.Core.Tests</c> does not reference. All that matters anyway is the
|
||||
/// one line in which they are the same here: the delegation chain of the own run
|
||||
/// is passed on. A tool that suppresses it turns a cycle into a
|
||||
/// new commission — exactly the case B8 describes.</para>
|
||||
///
|
||||
/// <para>Wen ein Agent beauftragt, steht in <paramref name="routes"/>: agentId → Ziel.
|
||||
/// Wer dort nicht vorkommt, delegiert nicht und antwortet schlicht.</para>
|
||||
/// <para>Whom an agent commissions is in <paramref name="routes"/>: agentId → target.
|
||||
/// Whoever does not appear there does not delegate and simply answers.</para>
|
||||
/// </summary>
|
||||
internal sealed class DelegatingTool : IAgentTool
|
||||
{
|
||||
@@ -30,13 +30,13 @@ internal sealed class DelegatingTool : IAgentTool
|
||||
{ "type": "object", "properties": { "message": { "type": "string" } } }
|
||||
""").RootElement.Clone();
|
||||
|
||||
/// <summary>Was zurückkam — Aufrufer, Ziel und das Ergebnis der Weitergabe.</summary>
|
||||
/// <summary>What came back — caller, target and the result of the hand-off.</summary>
|
||||
public ConcurrentBag<(string From, string To, bool Delivered, string? Error)> Handoffs { get; } = new();
|
||||
|
||||
/// <param name="delayBeforeHandoff">
|
||||
/// Wartezeit, bevor weitergegeben wird. Der Fake-Client antwortet synchron; ohne
|
||||
/// diese Pause läuft ein Chat vollständig durch, bevor der nächste überhaupt
|
||||
/// beginnt, und zwei Ketten können sich nicht überschneiden.
|
||||
/// Waiting time before handing off. The fake client answers synchronously; without
|
||||
/// this pause a chat runs through completely before the next one even
|
||||
/// begins, and two chains cannot overlap.
|
||||
/// </param>
|
||||
public DelegatingTool(
|
||||
IReadOnlyDictionary<string, string> routes,
|
||||
@@ -74,8 +74,8 @@ internal sealed class DelegatingTool : IAgentTool
|
||||
|
||||
Handoffs.Add((context.AgentId, target, result.Delivered, result.Error));
|
||||
|
||||
// Wie das echte Tool: Eine Ablehnung ist kein Wurf, sondern ein Ergebnis, das
|
||||
// der Agent lesen und auf das er anders reagieren kann.
|
||||
// Like the real tool: a rejection is not a throw, but a result that
|
||||
// the agent can read and react to differently.
|
||||
return result.Delivered
|
||||
? ToolResult.Ok(result.Response ?? "")
|
||||
: ToolResult.Ok(JsonSerializer.Serialize(new { delivered = false, error = result.Error }));
|
||||
|
||||
@@ -9,8 +9,8 @@ using ClawdDotNet.Core.Tools;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Baut eine einsatzbereite AgentEngine mit Fake-Client, Fake-Tools und
|
||||
/// In-Memory-State zusammen.
|
||||
/// Assembles a ready-to-use AgentEngine with fake client, fake tools and
|
||||
/// in-memory state.
|
||||
/// </summary>
|
||||
internal sealed class EngineFixture
|
||||
{
|
||||
@@ -21,7 +21,7 @@ internal sealed class EngineFixture
|
||||
|
||||
private readonly List<AgentConfig> _agents = new();
|
||||
|
||||
/// <summary>Die registrierten Agenten — für Tests, die einen Dispatcher selbst bauen.</summary>
|
||||
/// <summary>The registered agents — for tests that build a dispatcher themselves.</summary>
|
||||
public IReadOnlyList<AgentConfig> Agents => _agents;
|
||||
|
||||
public EngineFixture(
|
||||
@@ -44,7 +44,7 @@ internal sealed class EngineFixture
|
||||
TranscriptStore = transcripts
|
||||
};
|
||||
|
||||
// Kein Agent-Verzeichnis → keine Persistenz auf die Platte während der Tests.
|
||||
// No agent directory → no persistence to disk during the tests.
|
||||
Engine.SetAgentConfigProvider(() => _agents, "test-instance", _ => null);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ internal sealed class EngineFixture
|
||||
MaxSteps = 50,
|
||||
MaxCumulativeTokens = 10_000_000,
|
||||
TimeoutSeconds = 60,
|
||||
MaxContextTokens = 10_000_000 // Compaction in Engine-Tests ausschalten
|
||||
MaxContextTokens = 10_000_000 // switch off compaction in engine tests
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@ using ClawdDotNet.Core.Api.Models;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Ersetzt den OpenRouterClient in Tests. Liefert eine vorprogrammierte Antwortfolge
|
||||
/// und schreibt jeden empfangenen Request mit.
|
||||
/// Replaces the OpenRouterClient in tests. Delivers a preprogrammed sequence of answers
|
||||
/// and records every request received.
|
||||
///
|
||||
/// Wichtig: Requests werden tief kopiert. Die Engine reicht dieselbe List<ChatMessage>
|
||||
/// weiter und verändert sie danach — ohne Kopie würden Tests den Endzustand prüfen
|
||||
/// statt dessen, was tatsächlich gesendet wurde.
|
||||
/// Important: requests are deep-copied. The engine passes on the same List<ChatMessage>
|
||||
/// and changes it afterwards — without a copy tests would check the final state
|
||||
/// instead of what was actually sent.
|
||||
/// </summary>
|
||||
internal sealed class FakeChatClient : IChatCompletionClient
|
||||
{
|
||||
@@ -18,12 +18,12 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
private Func<ChatRequest, ChatResponse>? _fallback;
|
||||
private readonly Lock _gate = new();
|
||||
|
||||
/// <summary>Alle empfangenen Requests, als tiefe Kopien.</summary>
|
||||
/// <summary>All requests received, as deep copies.</summary>
|
||||
public List<ChatRequest> ReceivedRequests { get; } = new();
|
||||
|
||||
public int CallCount => ReceivedRequests.Count;
|
||||
|
||||
// ─── Programmierung der Antworten ───
|
||||
// ─── Programming the answers ───
|
||||
|
||||
public FakeChatClient RespondsWithText(string text, Usage? usage = null)
|
||||
{
|
||||
@@ -42,7 +42,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
|
||||
private static ChatResponse ToolCallResponse((string Tool, string Args, string? Id)[] calls)
|
||||
{
|
||||
// Ids müssen pro Antwort eindeutig sein, sonst kollidieren parallele Läufe.
|
||||
// IDs must be unique per answer, otherwise parallel runs collide.
|
||||
var toolCalls = calls.Select(c => new ToolCall
|
||||
{
|
||||
Id = c.Id ?? "call_" + Guid.NewGuid().ToString("N")[..12],
|
||||
@@ -58,7 +58,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Antwortet mit einer bestimmten Prompt-Token-Zahl — für Compaction-Schwellen.</summary>
|
||||
/// <summary>Answers with a particular prompt token count — for compaction thresholds.</summary>
|
||||
public FakeChatClient RespondsWithTokens(int promptTokens, string text = "fertig")
|
||||
{
|
||||
_responses.Enqueue(_ => TextResponse(text, new Usage
|
||||
@@ -77,7 +77,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Antwortet mit einem an der Ausgabegrenze abgeschnittenen Text (#548).
|
||||
/// Answers with a text cut off at the output limit (#548).
|
||||
/// </summary>
|
||||
public FakeChatClient RespondsWithTruncatedText(string text, string finishReason = "length")
|
||||
{
|
||||
@@ -99,8 +99,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Antwortet mit Werkzeugaufrufen UND Begleittext bzw. Denkverlauf — der Fall, den
|
||||
/// #547 und #549 betreffen.
|
||||
/// Answers with tool calls AND accompanying text or reasoning — the case
|
||||
/// #547 and #549 concern.
|
||||
/// </summary>
|
||||
public FakeChatClient RespondsWithToolCallAndContent(
|
||||
string toolName,
|
||||
@@ -122,7 +122,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Antwort für alle Aufrufe, die über die programmierte Folge hinausgehen.</summary>
|
||||
/// <summary>Answer for all calls that go beyond the programmed sequence.</summary>
|
||||
public FakeChatClient AlwaysRespondsWithText(string text)
|
||||
{
|
||||
_fallback = _ => TextResponse(text);
|
||||
@@ -136,9 +136,9 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Antwortet passend zum jeweiligen Gesprächsstand: nach einer Tool-Antwort
|
||||
/// kommt Text, sonst ein Tool-Aufruf. Damit verhält sich der Fake auch dann
|
||||
/// sinnvoll, wenn mehrere Konversationen ineinander verschränkt laufen.
|
||||
/// Answers matching the respective state of the conversation: after a tool answer
|
||||
/// comes text, otherwise a tool call. That way the fake behaves sensibly
|
||||
/// even when several conversations run interleaved.
|
||||
/// </summary>
|
||||
public FakeChatClient RespondsContextually(string toolName = "TestTool")
|
||||
{
|
||||
@@ -154,8 +154,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Erst kopieren, dann auswerten: Die Engine mutiert die Original-Liste
|
||||
// weiter, und bei Nebenläufigkeitstests soll der Fake daran nicht scheitern.
|
||||
// Copy first, then evaluate: the engine keeps mutating the original list,
|
||||
// and in concurrency tests the fake should not fail because of that.
|
||||
var snapshot = DeepClone(request);
|
||||
lock (_gate)
|
||||
ReceivedRequests.Add(snapshot);
|
||||
@@ -172,7 +172,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
return Task.FromResult(responder(snapshot));
|
||||
}
|
||||
|
||||
// ─── Helfer ───
|
||||
// ─── Helpers ───
|
||||
|
||||
private static ChatResponse TextResponse(string text, Usage? usage = null) => new()
|
||||
{
|
||||
@@ -189,8 +189,8 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
MaxTokens = request.MaxTokens,
|
||||
ToolChoice = request.ToolChoice,
|
||||
Tools = request.Tools?.ToList(),
|
||||
// Die Gateway-Felder gehören mitkopiert, sonst prüft ein Test die Härtung
|
||||
// am Wire-Format vorbei — dieselbe Falle wie bei CacheBreakpoint.
|
||||
// The gateway fields must be copied too, otherwise a test checks the hardening
|
||||
// past the wire format — the same trap as with CacheBreakpoint.
|
||||
Provider = request.Provider,
|
||||
Usage = request.Usage,
|
||||
Reasoning = request.Reasoning,
|
||||
@@ -202,7 +202,7 @@ internal sealed class FakeChatClient : IChatCompletionClient
|
||||
Role = m.Role,
|
||||
Content = m.Content,
|
||||
ToolCallId = m.ToolCallId,
|
||||
// Muss mitkopiert werden, sonst prüfen Caching-Tests am Wire-Format vorbei.
|
||||
// Must be copied too, otherwise caching tests check past the wire format.
|
||||
CacheBreakpoint = m.CacheBreakpoint,
|
||||
Reasoning = m.Reasoning,
|
||||
ReasoningDetails = m.ReasoningDetails,
|
||||
|
||||
@@ -3,7 +3,7 @@ using System.Net;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Liefert vorprogrammierte HTTP-Antworten, ohne dass ein Netzwerkzugriff stattfindet.
|
||||
/// Delivers preprogrammed HTTP responses without any network access taking place.
|
||||
/// </summary>
|
||||
internal sealed class FakeHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Ein steuerbarer <see cref="TimeProvider"/> für die Zeit-abhängigen Tests (R2 der
|
||||
/// Teststrategie). Bewusst schlank: die Tests treiben den Scanner über
|
||||
/// <c>ScanOnceAsync</c> direkt, sie brauchen nur eine feste, verstellbare Uhr.
|
||||
/// A controllable <see cref="TimeProvider"/> for the time-dependent tests (R2 of the
|
||||
/// test strategy). Deliberately lean: the tests drive the scanner directly via
|
||||
/// <c>ScanOnceAsync</c>, they only need a fixed, adjustable clock.
|
||||
/// </summary>
|
||||
internal sealed class FakeTimeProvider : TimeProvider
|
||||
{
|
||||
|
||||
@@ -5,8 +5,8 @@ using ClawdDotNet.Core.Tools;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Konfigurierbares Tool für Engine-Tests: liefert feste Ergebnisse, kann verzögern
|
||||
/// (um Nebenläufigkeit zu provozieren) oder werfen.
|
||||
/// Configurable tool for engine tests: delivers fixed results, can delay
|
||||
/// (to provoke concurrency) or throw.
|
||||
/// </summary>
|
||||
internal sealed class FakeTool : IAgentTool
|
||||
{
|
||||
@@ -19,18 +19,18 @@ internal sealed class FakeTool : IAgentTool
|
||||
{ "type": "object", "properties": { "action": { "type": "string" } } }
|
||||
""").RootElement.Clone();
|
||||
|
||||
/// <summary>Jeder Aufruf wird mitgeschrieben — inklusive der aufrufenden Agent-Id.</summary>
|
||||
/// <summary>Every call is recorded — including the calling agent ID.</summary>
|
||||
public ConcurrentBag<string> Invocations { get; } = new();
|
||||
|
||||
/// <summary>Ob das Werkzeug sich für gleichzeitige Ausführung freigibt (#551).</summary>
|
||||
/// <summary>Whether the tool declares itself fit for simultaneous execution (#551).</summary>
|
||||
public bool IsParallelSafe { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Höchste Zahl gleichzeitig laufender Aufrufe, die dieses Werkzeug gesehen hat.
|
||||
/// Highest number of simultaneously running calls this tool has seen.
|
||||
///
|
||||
/// Das ist der einzige Nachweis, der zählt: Eine kürzere Gesamtlaufzeit könnte auch
|
||||
/// von einer schnellen Maschine kommen, aber ein Höchststand von 2 kann nur
|
||||
/// entstehen, wenn zwei Aufrufe einander tatsächlich überlappt haben.
|
||||
/// That is the only proof that counts: a shorter total runtime could also
|
||||
/// come from a fast machine, but a peak of 2 can only
|
||||
/// arise if two calls actually overlapped each other.
|
||||
/// </summary>
|
||||
public int MaxConcurrent => _maxConcurrent;
|
||||
|
||||
@@ -44,8 +44,8 @@ internal sealed class FakeTool : IAgentTool
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ein langsames Werkzeug, das sich für Nebenläufigkeit freigibt und mitzählt,
|
||||
/// wie viele Aufrufe sich überlappt haben.
|
||||
/// A slow tool that declares itself fit for concurrency and counts
|
||||
/// how many calls overlapped.
|
||||
/// </summary>
|
||||
public static FakeTool SlowParallelSafe(TimeSpan delay, string result = "ok", string name = "ParallelTool")
|
||||
=> new(name, async (_, _, ct) =>
|
||||
@@ -55,7 +55,7 @@ internal sealed class FakeTool : IAgentTool
|
||||
})
|
||||
{ IsParallelSafe = true };
|
||||
|
||||
/// <summary>Dasselbe, aber ohne Freigabe — muss seriell bleiben.</summary>
|
||||
/// <summary>The same, but without declaring itself fit — must stay serial.</summary>
|
||||
public static FakeTool SlowSerial(TimeSpan delay, string result = "ok", string name = "SerialTool")
|
||||
=> new(name, async (_, _, ct) =>
|
||||
{
|
||||
@@ -66,7 +66,7 @@ internal sealed class FakeTool : IAgentTool
|
||||
public static FakeTool Returning(string result, string name = "TestTool")
|
||||
=> new(name, (_, _, _) => Task.FromResult(ToolResult.Ok(result)));
|
||||
|
||||
/// <summary>Verzögert die Ausführung — vergrößert das Zeitfenster für Races.</summary>
|
||||
/// <summary>Delays execution — enlarges the time window for races.</summary>
|
||||
public static FakeTool Slow(TimeSpan delay, string result = "ok", string name = "TestTool")
|
||||
=> new(name, async (_, _, ct) =>
|
||||
{
|
||||
@@ -74,7 +74,7 @@ internal sealed class FakeTool : IAgentTool
|
||||
return ToolResult.Ok(result);
|
||||
});
|
||||
|
||||
/// <summary>Scheitert jedes Mal mit derselben Meldung - fuer die Wiederholungswache (#581).</summary>
|
||||
/// <summary>Fails every time with the same message - for the repetition guard (#581).</summary>
|
||||
public static FakeTool Failing(string error, string name = "TestTool")
|
||||
=> new(name, (_, _, _) => Task.FromResult(ToolResult.Fail(error)));
|
||||
|
||||
@@ -95,8 +95,8 @@ internal sealed class FakeTool : IAgentTool
|
||||
|
||||
var now = Interlocked.Increment(ref _current);
|
||||
|
||||
// Höchststand fortschreiben, ohne ihn je zu senken: Vergleichen und Tauschen,
|
||||
// bis der gespeicherte Wert nicht mehr kleiner ist.
|
||||
// Advance the peak without ever lowering it: compare and swap
|
||||
// until the stored value is no longer smaller.
|
||||
int seen;
|
||||
while (now > (seen = Volatile.Read(ref _maxConcurrent)))
|
||||
Interlocked.CompareExchange(ref _maxConcurrent, now, seen);
|
||||
|
||||
@@ -4,8 +4,8 @@ using ClawdDotNet.Core.Audit;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// In-Memory-Attrappe des Audit-Logs für Engine-Tests — hält Einträge und Belege in
|
||||
/// Listen, damit ein Test prüfen kann, was die Engine stempelt, ohne eine Datei.
|
||||
/// In-memory fake of the audit log for engine tests — keeps entries and receipts in
|
||||
/// lists, so a test can check what the engine stamps, without a file.
|
||||
/// </summary>
|
||||
internal sealed class InMemoryAuditRepository : IAuditRepository
|
||||
{
|
||||
@@ -46,7 +46,7 @@ internal sealed class InMemoryAuditRepository : IAuditRepository
|
||||
|
||||
public Task<int> CountAsync(CancellationToken ct) => Task.FromResult(Entries.Count);
|
||||
|
||||
/// <summary>Spiegelt die SQL-Regel: Started ohne späteren Abschluss derselben CallId.</summary>
|
||||
/// <summary>Mirrors the SQL rule: Started without a later completion of the same CallId.</summary>
|
||||
public Task<IReadOnlyList<AuditEntry>> ListOpenCallsAsync(CancellationToken ct)
|
||||
{
|
||||
var alle = Entries.ToList();
|
||||
|
||||
@@ -3,7 +3,7 @@ using ClawdDotNet.Core.Staging;
|
||||
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>In-Memory-Attrappe der Staging-Warteschlange für Engine-Tests.</summary>
|
||||
/// <summary>In-memory fake of the staging queue for engine tests.</summary>
|
||||
internal sealed class InMemoryStagingRepository : IStagingRepository
|
||||
{
|
||||
private readonly ConcurrentDictionary<long, StagedCall> _calls = new();
|
||||
@@ -89,7 +89,7 @@ internal sealed class InMemoryStagingRepository : IStagingRepository
|
||||
=> Task.FromResult<IReadOnlyList<StagedCall>>(
|
||||
_calls.Values.Where(c => c.Approver is not null && c.CreatedAt >= since).OrderBy(c => c.Id).ToList());
|
||||
|
||||
/// <summary>Verschiebt den Anlagezeitpunkt eines Vorschlags in die Vergangenheit - fuer Verfallstests.</summary>
|
||||
/// <summary>Moves the creation time of a proposal into the past - for expiry tests.</summary>
|
||||
public void Altern(long id, TimeSpan um)
|
||||
{
|
||||
if (_calls.TryGetValue(id, out var call))
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Ein Test, der nur unter Linux etwas aussagt.
|
||||
/// A test that only says something under Linux.
|
||||
///
|
||||
/// Nötig geworden mit der Linux-Portierung: Groß-/Kleinschreibung und symbolische
|
||||
/// Verknüpfungen verhalten sich je Dateisystem unterschiedlich. Solche Fälle unter
|
||||
/// Windows mitlaufen zu lassen, würde entweder fehlschlagen oder — schlimmer — grün
|
||||
/// sein, ohne das Gemeinte geprüft zu haben.
|
||||
/// Became necessary with the Linux port: upper/lower case and symbolic
|
||||
/// links behave differently per file system. Letting such cases run along under
|
||||
/// Windows would either fail or — worse — be green
|
||||
/// without having checked what was meant.
|
||||
///
|
||||
/// Bewusst ein Übersprung mit Begründung statt eines stillen <c>return</c>: Im
|
||||
/// Testbericht ist dann sichtbar, dass hier etwas <em>nicht</em> geprüft wurde.
|
||||
/// Deliberately a skip with a reason instead of a silent <c>return</c>: in the
|
||||
/// test report it is then visible that something was <em>not</em> checked here.
|
||||
/// </summary>
|
||||
public sealed class LinuxFactAttribute : FactAttribute
|
||||
{
|
||||
@@ -20,7 +20,7 @@ public sealed class LinuxFactAttribute : FactAttribute
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ein Test, der nur unter Windows etwas aussagt. Siehe <see cref="LinuxFactAttribute"/>.</summary>
|
||||
/// <summary>A test that only says something under Windows. See <see cref="LinuxFactAttribute"/>.</summary>
|
||||
public sealed class WindowsFactAttribute : FactAttribute
|
||||
{
|
||||
public WindowsFactAttribute()
|
||||
|
||||
@@ -4,10 +4,10 @@ using Xunit.Sdk;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Führt einen Theory-Test mehrfach aus.
|
||||
/// Runs a theory test several times.
|
||||
///
|
||||
/// Race Conditions treten sporadisch auf — ein einmaliger Durchlauf sagt wenig.
|
||||
/// Der Testparameter ist der Durchlauf-Index und wird üblicherweise ignoriert.
|
||||
/// Race conditions occur sporadically — a single run says little.
|
||||
/// The test parameter is the run index and is usually ignored.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public sealed class RepeatAttribute(int count) : DataAttribute
|
||||
|
||||
@@ -4,20 +4,20 @@ using ClawdDotNet.Core.Security;
|
||||
namespace ClawdDotNet.Core.Tests.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Hält den Schlüssel von <see cref="SecretKeyStore"/> aus den Tests heraus.
|
||||
/// Keeps the key of <see cref="SecretKeyStore"/> out of the tests.
|
||||
///
|
||||
/// Zwei Dinge müssen zusammenkommen:
|
||||
/// Two things must come together:
|
||||
///
|
||||
/// 1. <see cref="Init"/> verlegt den Schlüssel <b>einmal beim Laden</b> der
|
||||
/// Testassembly in ein Wegwerfverzeichnis. Ohne das würden die Tests den echten
|
||||
/// Benutzerschlüssel anlegen oder lesen — auf dem Entwicklungsrechner wie auf dem
|
||||
/// Bauserver.
|
||||
/// 1. <see cref="Init"/> relocates the key <b>once when loading</b> the
|
||||
/// test assembly into a throwaway directory. Without that the tests would create or read
|
||||
/// the real user key — on the development machine as on the
|
||||
/// build server.
|
||||
///
|
||||
/// 2. Wer den Schlüssel <em>während</em> eines Tests wechselt (etwa um zu prüfen, dass
|
||||
/// ein fremder Schlüssel nicht liest), muss in dieser Sammlung laufen. Der Speicher
|
||||
/// ist statisch; ohne abgeschaltete Parallelität zöge ein solcher Wechsel einer
|
||||
/// gleichzeitig laufenden Klasse den Schlüssel unter den Füßen weg. Genau das ist
|
||||
/// zwischen den Geheimnis- und den Sicherungstests passiert.
|
||||
/// 2. Whoever changes the key <em>during</em> a test (for example to check that
|
||||
/// a foreign key does not read) must run in this collection. The store
|
||||
/// is static; without parallelism switched off, such a change would pull the key
|
||||
/// out from under a class running at the same time. Exactly that
|
||||
/// happened between the secret and the backup tests.
|
||||
/// </summary>
|
||||
[CollectionDefinition(Name, DisableParallelization = true)]
|
||||
public sealed class SecretKeyCollection
|
||||
|
||||
Reference in New Issue
Block a user