From 57cfca5fe9a0646e8fd7205c7263c588aa8e9a34 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 28 Aug 2026 21:06:05 +0200 Subject: [PATCH] Testlauf, dem man glauben kann: Zeitzonen-Tests und Schluss mit Nebenlaeufigkeit Zwei Tests zur Zeitzonen-Umstellung aus dem letzten Commit: der Zeitstempel eines Logeintrags traegt den Versatz der ANZEIGE-Zone (nicht den des Systems), und die abgelegte JSONL-Zeile traegt ihn mit. Sie liegen bewusst in AppTimeZoneTests - dort steht der uebrige Zeitzonen-Zustand, und innerhalb einer Klasse laufen Tests nacheinander. Das genuegte aber nicht. Von sechs Laeufen fiel einer um und liess sich danach nicht wieder einfangen. Die Ursache ist kein einzelner Test, sondern die Bauart: Mehrere Bausteine halten prozessweiten Zustand, den Tests bewusst umstellen - AppTimeZone, SecretProtection, die Diagnosezaehler von AccountIdSetJson. xUnit fuehrt Testklassen standardmaessig nebenlaeufig aus, also sieht eine Klasse mitten im eigenen Lauf, was eine andere gerade umgestellt hat. Neu ist das nicht, aber die Zeitzonen-Umstellung hat es verschaerft: Seit die Logzeitstempel der Anzeige-Zeitzone folgen, haengt jedes LogMessageEventArgs an AppTimeZone - waehrend AppTimeZoneTests dieselbe Einstellung nach Auckland und zurueck dreht. Deshalb laufen die Testklassen jetzt nacheinander, und die Zaehlerpruefungen arbeiten mit der Differenz statt mit absoluten Werten. Kosten: 4-5 s statt 3 s Laufzeit. Ein Testlauf, dem man glauben kann, ist das allemal wert - danach sechs Laeufe in Folge gruen. Co-Authored-By: Claude Opus 5 --- tests/PolyTrader.Tests/AppTimeZoneTests.cs | 40 +++++++++++++++++++- tests/PolyTrader.Tests/AssemblyInfo.cs | 17 +++++++++ tests/PolyTrader.Tests/LinuxFindingsTests.cs | 16 +++++--- 3 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 tests/PolyTrader.Tests/AssemblyInfo.cs diff --git a/tests/PolyTrader.Tests/AppTimeZoneTests.cs b/tests/PolyTrader.Tests/AppTimeZoneTests.cs index f91e40a..ddad4af 100644 --- a/tests/PolyTrader.Tests/AppTimeZoneTests.cs +++ b/tests/PolyTrader.Tests/AppTimeZoneTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using PolyTraderSharp.Services; using Xunit; @@ -66,5 +66,43 @@ namespace PolyTrader.Tests var local = new DateTime(2026, 1, 15, 12, 0, 0, DateTimeKind.Local); Assert.Equal(12, AppTimeZone.ToDisplay(local).Hour); } + + // ── §8 des Linux-Befundberichts: Logs stempelten UTC, die Oberfläche zeigte Ortszeit ── + + [Fact] + public void Now_carries_the_offset_of_the_display_zone() + { + AppTimeZone.Configure("Pacific/Auckland"); + try + { + var now = AppTimeZone.Now; + var expected = AppTimeZone.Current.GetUtcOffset(DateTimeOffset.UtcNow); + + // Der Versatz gehört zur ANZEIGE-Zone. Ein umgerechneter DateTime trüge ihn nicht + // mit sich – beim Formatieren mit "zzz" käme dann der Versatz des Systems heraus. + Assert.Equal(expected, now.Offset); + Assert.Equal(DateTime.UtcNow.Add(expected).Date, AppTimeZone.Today); + } + finally { AppTimeZone.Configure(null); } + } + + [Fact] + public void Log_entries_are_stamped_in_the_display_zone() + { + AppTimeZone.Configure("Europe/Berlin"); + try + { + var e = new LogMessageEventArgs("egal", LogLevel.Info); + Assert.Equal(AppTimeZone.Current.GetUtcOffset(e.Timestamp.UtcDateTime), e.Timestamp.Offset); + + // Und die abgelegte JSONL-Zeile trägt denselben Versatz mit. + var parsed = DateTimeOffset.Parse( + LogJson.ParseLine(LogJson.Format(e))!.Time, + System.Globalization.CultureInfo.InvariantCulture); + Assert.Equal(e.Timestamp.Offset, parsed.Offset); + Assert.Equal(e.Timestamp.ToUnixTimeSeconds(), parsed.ToUnixTimeSeconds()); + } + finally { AppTimeZone.Configure(null); } + } } } diff --git a/tests/PolyTrader.Tests/AssemblyInfo.cs b/tests/PolyTrader.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..987afcd --- /dev/null +++ b/tests/PolyTrader.Tests/AssemblyInfo.cs @@ -0,0 +1,17 @@ +using Xunit; + +// Testklassen laufen NACHEINANDER, nicht parallel. +// +// Warum: Mehrere Bausteine der Anwendung halten prozessweiten Zustand, und die Tests stellen ihn +// bewusst um — AppTimeZone (Anzeige-Zeitzone), SecretProtection (Master-Key) und die +// Diagnosezähler von AccountIdSetJson. xUnit führt Testklassen standardmäßig nebenläufig aus; +// dann sieht eine Klasse die Umstellung einer anderen mitten im eigenen Lauf. +// +// Das ist nicht theoretisch: Seit die Logzeitstempel der Anzeige-Zeitzone folgen (§8 des +// Linux-Befundberichts vom 26.08.2026), hängt jedes LogMessageEventArgs an AppTimeZone — während +// AppTimeZoneTests dieselbe Einstellung nach Auckland und zurück dreht. Am 28.08.2026 fiel dabei +// ein Lauf von sechs um, ohne sich danach wieder einfangen zu lassen. +// +// Der Preis ist gering: Der gesamte Satz läuft in rund drei Sekunden. Ein Testlauf, dem man +// glauben kann, ist das allemal wert. +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/tests/PolyTrader.Tests/LinuxFindingsTests.cs b/tests/PolyTrader.Tests/LinuxFindingsTests.cs index 91e0254..0d8a3a7 100644 --- a/tests/PolyTrader.Tests/LinuxFindingsTests.cs +++ b/tests/PolyTrader.Tests/LinuxFindingsTests.cs @@ -57,36 +57,40 @@ namespace PolyTrader.Tests Assert.Empty(AccountIdSetJson.Deserialize("[]")); } + // Der Zaehler ist prozessweit. Deshalb wird die DIFFERENZ geprueft und nicht der + // absolute Wert - so bleibt der Test auch dann richtig, wenn vorher schon jemand + // einen unlesbaren Wert durchgereicht hat. + [Fact] public void Plain_number_list_is_still_understood() { - AccountIdSetJson.ResetDiagnostics(); + int before = AccountIdSetJson.MalformedCount; Assert.Equal(new System.Collections.Generic.HashSet { 1, 2 }, AccountIdSetJson.Deserialize("1,2")); - Assert.Equal(0, AccountIdSetJson.MalformedCount); + Assert.Equal(before, AccountIdSetJson.MalformedCount); } [Fact] public void Unreadable_value_yields_an_empty_set_and_is_counted() { - AccountIdSetJson.ResetDiagnostics(); + int before = AccountIdSetJson.MalformedCount; // Der Fall aus dem Test: kaputter Wert statt JSON-Array. Frueher warf die // EF-Wertkonvertierung hier und es wurde KEIN einziger Trader geladen. Assert.Empty(AccountIdSetJson.Deserialize("{kaputt")); - Assert.Equal(1, AccountIdSetJson.MalformedCount); + Assert.Equal(before + 1, AccountIdSetJson.MalformedCount); Assert.Equal("{kaputt", AccountIdSetJson.LastMalformedValue); } [Fact] public void Text_that_is_not_a_number_list_is_not_silently_accepted() { - AccountIdSetJson.ResetDiagnostics(); + int before = AccountIdSetJson.MalformedCount; Assert.Empty(AccountIdSetJson.Deserialize("1,zwei")); - Assert.Equal(1, AccountIdSetJson.MalformedCount); + Assert.Equal(before + 1, AccountIdSetJson.MalformedCount); } } }