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
@@ -6,11 +6,11 @@ using Shouldly;
|
||||
namespace ClawdDotNet.Core.Tests.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Alle Konfigurations- und Zustandsdateien wurden mit File.WriteAllText geschrieben.
|
||||
/// Das kürzt die Zieldatei zuerst auf null und füllt sie dann — bricht der Vorgang
|
||||
/// dazwischen ab, ist der alte Inhalt weg und der neue unvollständig.
|
||||
/// All configuration and state files were written with File.WriteAllText.
|
||||
/// That first truncates the target file to zero and then fills it — if the operation
|
||||
/// aborts in between, the old content is gone and the new one incomplete.
|
||||
///
|
||||
/// Das ist im Betrieb bereits eingetreten: In einer Instanz lag eine
|
||||
/// That has already happened in operation: an instance contained a
|
||||
/// TokenUsage.json.corrupt_…
|
||||
/// </summary>
|
||||
public sealed class AtomicFileTests : IDisposable
|
||||
@@ -26,13 +26,13 @@ public sealed class AtomicFileTests : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(_directory, recursive: true); }
|
||||
catch { /* Aufräumen ist Nebensache */ }
|
||||
catch { /* Cleaning up is secondary */ }
|
||||
}
|
||||
|
||||
private string Path_(string name) => Path.Combine(_directory, name);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Grundverhalten
|
||||
// Basic behavior
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -102,13 +102,13 @@ public sealed class AtomicFileTests : IDisposable
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Die eigentliche Zusage: nie ein halber Inhalt
|
||||
// The actual promise: never half a content
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Während geschrieben wird, wird parallel gelesen. Beim alten Verfahren erwischt
|
||||
/// man dabei zwangsläufig abgeschnittene Zwischenstände — hier darf jeder Lesevorgang
|
||||
/// nur ein vollständiges Dokument sehen.
|
||||
/// While writing, reading happens in parallel. With the old procedure you inevitably
|
||||
/// catch truncated intermediate states — here every read may
|
||||
/// only see a complete document.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Waehrend_des_Schreibens_wird_nie_ein_halber_Inhalt_sichtbar()
|
||||
@@ -122,15 +122,15 @@ public sealed class AtomicFileTests : IDisposable
|
||||
var fehler = 0;
|
||||
var gelesen = 0;
|
||||
|
||||
// Harte Zeitgrenze, damit ein Fehler im Schreiber den Test nicht hängen lässt.
|
||||
// Hard time limit, so an error in the writer does not leave the test hanging.
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
|
||||
var schreiber = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// #722: Geschrieben wird, bis der Leser genug gesehen hat — nicht eine feste
|
||||
// Zahl von Malen. Unter Parallellast kam der Leser sonst nur auf fuenf Blicke.
|
||||
// #722: Writing continues until the reader has seen enough — not a fixed
|
||||
// number of times. Under parallel load the reader otherwise only got five looks.
|
||||
for (var i = 0; (i < 40 || Volatile.Read(ref gelesen) < 20) && !cts.IsCancellationRequested; i++)
|
||||
{
|
||||
AtomicFile.WriteAllText(path, i % 2 == 0 ? gross : klein);
|
||||
@@ -149,12 +149,12 @@ public sealed class AtomicFileTests : IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
// AtomicFile.ReadAllText, damit der Leser den Schreiber nicht
|
||||
// blockiert — genau dafür gibt es den Helfer.
|
||||
// AtomicFile.ReadAllText, so the reader does not block the
|
||||
// writer — that is exactly what the helper exists for.
|
||||
var inhalt = AtomicFile.ReadAllText(path);
|
||||
Interlocked.Increment(ref gelesen);
|
||||
|
||||
// Muss immer ein vollständiges JSON-Array sein.
|
||||
// Must always be a complete JSON array.
|
||||
JsonSerializer.Deserialize<string[]>(inhalt);
|
||||
}
|
||||
catch (JsonException)
|
||||
@@ -163,7 +163,7 @@ public sealed class AtomicFileTests : IDisposable
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// Kurzzeitig nicht zu öffnen ist kein Datenfehler.
|
||||
// Briefly not being able to open it is not a data error.
|
||||
}
|
||||
await Task.Delay(5);
|
||||
}
|
||||
@@ -176,12 +176,12 @@ public sealed class AtomicFileTests : IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Der nicht offensichtliche Teil: Ein Leser, der die Datei geöffnet hält, würde
|
||||
/// das Ersetzen blockieren — es sei denn, er gibt das Löschen frei. Genau das tut
|
||||
/// <see cref="AtomicFile.ReadAllText"/>.
|
||||
/// The non-obvious part: a reader holding the file open would
|
||||
/// block the replacement — unless it shares delete. That is exactly what
|
||||
/// <see cref="AtomicFile.ReadAllText"/> does.
|
||||
///
|
||||
/// Gemessen auf Windows: File.Move scheitert in jedem Fall an einem offenen Leser,
|
||||
/// File.Replace kommt mit einem freigebenden Leser zurecht. Deshalb Replace.
|
||||
/// Measured on Windows: File.Move fails in every case with an open reader,
|
||||
/// File.Replace copes with a sharing reader. Hence Replace.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ein_offener_Leser_blockiert_das_Schreiben_nicht()
|
||||
@@ -205,7 +205,7 @@ public sealed class AtomicFileTests : IDisposable
|
||||
Parallel.For(0, 40, i =>
|
||||
AtomicFile.WriteAllText(path, JsonSerializer.Serialize(new { lauf = i })));
|
||||
|
||||
// Welcher Lauf gewinnt, ist offen — die Datei muss aber lesbar sein.
|
||||
// Which run wins is open — but the file must be readable.
|
||||
Should.NotThrow(() => JsonDocument.Parse(File.ReadAllText(path)));
|
||||
Directory.GetFiles(_directory).ShouldHaveSingleItem();
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ using Shouldly;
|
||||
namespace ClawdDotNet.Core.Tests.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// #575: Auf der Test-VM (umask 0002) ging InstanceSettings.json beim ersten Speichern von
|
||||
/// 0600 auf 0664, Protokolle entstanden mit 0664. Unter Windows nicht aussagekraeftig.
|
||||
/// #575: On the test VM (umask 0002) InstanceSettings.json went from
|
||||
/// 0600 to 0664 on the first save, logs were created with 0664. Not meaningful under Windows.
|
||||
/// </summary>
|
||||
public sealed class OwnerOnlyFileTests : IDisposable
|
||||
{
|
||||
@@ -16,7 +16,7 @@ public sealed class OwnerOnlyFileTests : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(_dir, recursive: true); } catch { /* Aufraeumen ist best effort */ }
|
||||
try { Directory.Delete(_dir, recursive: true); } catch { /* Cleaning up is best effort */ }
|
||||
}
|
||||
|
||||
private const UnixFileMode NurBesitzer = UnixFileMode.UserRead | UnixFileMode.UserWrite;
|
||||
|
||||
@@ -5,14 +5,14 @@ using Shouldly;
|
||||
namespace ClawdDotNet.Core.Tests.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// Die Einschließungsprüfung verglich bis zur Linux-Portierung mit
|
||||
/// <c>OrdinalIgnoreCase</c> — die Windows-Annahme, dass Groß- und Kleinschreibung
|
||||
/// zusammenfallen. Unter Linux sind <c>Workspace</c> und <c>workspace</c> zwei
|
||||
/// Verzeichnisse, und eine symbolische Verknüpfung kann aus dem Wurzelverzeichnis
|
||||
/// hinauszeigen, ohne dass die reine Pfadrechnung das bemerkt.
|
||||
/// Until the Linux port the containment check compared with
|
||||
/// <c>OrdinalIgnoreCase</c> — the Windows assumption that upper and lower case
|
||||
/// fold together. Under Linux <c>Workspace</c> and <c>workspace</c> are two
|
||||
/// directories, and a symbolic link can point out of the root directory
|
||||
/// without the plain path calculation noticing.
|
||||
///
|
||||
/// Das Dateisystem wird hier bewusst nicht abstrahiert: Genau die Semantik, um die es
|
||||
/// geht, würde eine Abstraktion verstecken.
|
||||
/// The file system is deliberately not abstracted here: exactly the semantics this is
|
||||
/// about would be hidden by an abstraction.
|
||||
/// </summary>
|
||||
public sealed class PathBoundaryTests : IDisposable
|
||||
{
|
||||
@@ -29,11 +29,11 @@ public sealed class PathBoundaryTests : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
try { Directory.Delete(_base, recursive: true); }
|
||||
catch { /* Aufräumen ist Nebensache */ }
|
||||
catch { /* Cleaning up is secondary */ }
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Verzeichnisgrenzen (auf beiden Plattformen gleich)
|
||||
// Directory boundaries (the same on both platforms)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[Fact]
|
||||
@@ -63,13 +63,13 @@ public sealed class PathBoundaryTests : IDisposable
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Groß-/Kleinschreibung — hier gehen die Plattformen auseinander
|
||||
// Upper/lower case — here the platforms diverge
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[LinuxFact]
|
||||
public void Unter_Linux_ist_ein_Verzeichnis_mit_anderer_Schreibweise_ein_anderes()
|
||||
{
|
||||
// "workspace" neben "Workspace": zwei echte, getrennte Verzeichnisse.
|
||||
// "workspace" next to "Workspace": two real, separate directories.
|
||||
var lower = Path.Combine(_base, "workspace");
|
||||
Directory.CreateDirectory(lower);
|
||||
|
||||
@@ -92,20 +92,20 @@ public sealed class PathBoundaryTests : IDisposable
|
||||
|
||||
var gleich = PathBoundary.CanonicalKey(upper) == PathBoundary.CanonicalKey(lower);
|
||||
|
||||
// Unter Windows dieselbe Datei — dieselbe Sperre. Unter Linux zwei Dateien,
|
||||
// die sich keine teilen dürfen.
|
||||
// Under Windows the same file — the same lock. Under Linux two files
|
||||
// that must not share one.
|
||||
gleich.ShouldBe(OperatingSystem.IsWindows() || OperatingSystem.IsMacOS());
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// Symbolische Verknüpfungen
|
||||
// Symbolic links
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
[LinuxFact]
|
||||
public void Eine_Verknuepfung_aus_dem_Wurzelverzeichnis_heraus_wird_erkannt()
|
||||
{
|
||||
// Der Fall, den die reine Pfadrechnung nicht sieht: "Workspace/raus" zeigt
|
||||
// nach draußen, "Workspace/raus/beute.txt" liegt lexikalisch aber innerhalb.
|
||||
// The case the plain path calculation does not see: "Workspace/raus" points
|
||||
// outside, but "Workspace/raus/beute.txt" lies lexically inside.
|
||||
var draussen = Path.Combine(_base, "Draussen");
|
||||
Directory.CreateDirectory(draussen);
|
||||
Directory.CreateSymbolicLink(Path.Combine(_root, "raus"), draussen);
|
||||
@@ -117,7 +117,7 @@ public sealed class PathBoundaryTests : IDisposable
|
||||
[LinuxFact]
|
||||
public void Eine_Verknuepfung_innerhalb_des_Wurzelverzeichnisses_bleibt_erlaubt()
|
||||
{
|
||||
// Gegenprobe: Wer innerhalb bleibt, wird nicht ausgesperrt.
|
||||
// Counter-check: whoever stays inside is not locked out.
|
||||
var ziel = Path.Combine(_root, "echte-daten");
|
||||
Directory.CreateDirectory(ziel);
|
||||
Directory.CreateSymbolicLink(Path.Combine(_root, "abkuerzung"), ziel);
|
||||
@@ -129,9 +129,9 @@ public sealed class PathBoundaryTests : IDisposable
|
||||
[LinuxFact]
|
||||
public void Ein_Wurzelverzeichnis_hinter_einer_Verknuepfung_sperrt_sich_nicht_selbst_aus()
|
||||
{
|
||||
// Liegt der Workspace selbst hinter einer Verknüpfung (üblich bei /var → /private/var
|
||||
// oder gemounteten Datenverzeichnissen), muss die Auflösung auf beiden Seiten
|
||||
// greifen — sonst passt nach dem Auflösen nichts mehr zusammen.
|
||||
// If the workspace itself lies behind a link (common with /var → /private/var
|
||||
// or mounted data directories), the resolution must apply on both sides
|
||||
// — otherwise nothing matches any more after resolving.
|
||||
var echt = Path.Combine(_base, "EchterOrt");
|
||||
Directory.CreateDirectory(echt);
|
||||
var ueber = Path.Combine(_base, "Verknuepft");
|
||||
@@ -144,8 +144,8 @@ public sealed class PathBoundaryTests : IDisposable
|
||||
[Fact]
|
||||
public void Nicht_existierende_Pfade_bleiben_pruefbar()
|
||||
{
|
||||
// Beim Schreiben gibt es die Datei noch nicht. Was nicht existiert, kann keine
|
||||
// Verknüpfung sein — die Auflösung darf daran nicht scheitern.
|
||||
// When writing, the file does not exist yet. What does not exist cannot be a
|
||||
// link — the resolution must not fail on that.
|
||||
PathBoundary.IsInside(Path.Combine(_root, "gibt", "es", "noch", "nicht.txt"), _root)
|
||||
.ShouldBeTrue();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user