Testanleitung: eigenes X-Display je Projekt fuer Parallelbetrieb
Damit mehrere Agenten gleichzeitig Oberflaechen testen koennen, bekommt jedes Projekt eine feste Displaynummer (Vorgabe Richard): :99 PolyTrader, :98 Predictalytics, :97 ClawdDotNet, :96 IBKRTrader, :95 und absteigend fuer kuenftige Projekte. Zwei Prozesse auf demselben Display sehen die Fenster des jeweils anderen - Klicks landen im falschen Programm, Aufnahmen zeigen fremde Anwendungen. Beim Durcharbeiten fielen zwei weitere Kollisionspunkte auf, die zum Parallelbetrieb dazugehoeren und mit aufgenommen wurden: - Screenshot- und Logdateien in /tmp brauchen ein Projektpraefix. /tmp/v_1.png ueberschreiben sich parallel laufende Projekte gegenseitig. - "xvfb-run -a" darf NICHT mehr verwendet werden: Das -a sucht sich selbst ein freies Display und beginnt dabei bei :99 - ausgerechnet PolyTraders Nummer. Stattdessen "xvfb-run -n <nummer>". Die Warnung ist nicht theoretisch: Auf der VM lag ein verwaister X98-Socket, entstanden durch genau so einen "xvfb-run -a"-Aufruf beim smoke-ui-Test, als :99 belegt war. Er blockierte Predictalytics' reservierte Nummer, ohne dass ein Prozess lief. Entfernt, und die Anleitung erklaert jetzt, wie man verwaiste Sockets erkennt (ls /tmp/.X11-unix/ gegen pgrep -a Xvfb lesen) und aufraeumt. Weiter ergaenzt: - pkill nie ohne Displaynummer - "pkill Xvfb" erschlaegt die Sitzungen aller anderen Projekte. - DISPLAY in jedem Skript setzen, sonst landet man auf :0, dem echten Desktop. - Die Skriptvorlage hat jetzt einen Kopf mit PROJEKT/DISP/FENSTER; das ist alles, was sich je Projekt unterscheidet. Praktisch gegengetestet. - Aufraeumabschnitt und Belegungsliste um Displays und Verzeichnisse erweitert. Nebenbei aufgefallen: Das PolyTrader-Publish lag unter ~/pt-test statt ~/polytrader-test und verletzte damit die eigene Verzeichniskonvention. Umbenannt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -32,6 +33,9 @@ namespace PolyTrader.App.Avalonia.ViewModels
|
||||
/// <summary>Falsch bei Eigenschaften ohne Setter (reine Statusanzeigen).</summary>
|
||||
public bool IsEditable => Property.CanWrite;
|
||||
|
||||
/// <summary>Typ der dahinterliegenden Eigenschaft – die Konstruktionsprüfung schaut darauf.</summary>
|
||||
public Type ValueType => Property.PropertyType;
|
||||
|
||||
protected void Raise([CallerMemberName] string? name = null) =>
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
||||
|
||||
@@ -50,6 +54,79 @@ namespace PolyTrader.App.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freitext über mehrere Zeilen. Gleiche Bindung wie <see cref="TextSettingsField"/>, nur mit
|
||||
/// eigener Darstellung: Bis zum 26.08.2026 standen auch „Beschreibung" und „Begründung"
|
||||
/// (je bis 1000 Zeichen) in einzeiligen Feldern, in denen nur ein Bruchteil sichtbar war
|
||||
/// (Befund B-14 des Linux-Sichttests). Ausgezeichnet wird das am Modell mit
|
||||
/// <c>[DataType(DataType.MultilineText)]</c>.
|
||||
/// </summary>
|
||||
public sealed class MultilineTextSettingsField : SettingsField
|
||||
{
|
||||
public MultilineTextSettingsField(object t, PropertyInfo p, string l, string? d) : base(t, p, l, d) { }
|
||||
|
||||
public string Value
|
||||
{
|
||||
get => Property.GetValue(Target) as string ?? string.Empty;
|
||||
set { if (IsEditable) { Property.SetValue(Target, value ?? string.Empty); Raise(); } }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dezimalzahl (decimal/double/float) – wie <see cref="IntSettingsField"/> bewusst als Text:
|
||||
/// eine ungültige Eingabe lässt den bisherigen Wert stehen, statt still eine 0 zu schreiben.
|
||||
///
|
||||
/// <para>Vor dem 27.08.2026 fehlte dieser Feldtyp: alle Dezimal-Einstellungen fielen auf
|
||||
/// <see cref="ReadOnlySettingsField"/> zurück und waren damit in der Oberfläche gar nicht mehr
|
||||
/// änderbar – betroffen war die komplette Risikokonfiguration (Positionsgrößen, Preisgrenzen,
|
||||
/// Kill-Switch-Schwellen), die unter WinForms im PropertyGrid noch editierbar war.</para>
|
||||
///
|
||||
/// <para>Beim Lesen wird die Anzeigekultur verwendet (de-DE: „5,0"), beim Schreiben werden
|
||||
/// Komma und Punkt akzeptiert – sonst scheitert ein aus der Dokumentation kopierter Wert.</para>
|
||||
/// </summary>
|
||||
public sealed class DecimalSettingsField : SettingsField
|
||||
{
|
||||
public DecimalSettingsField(object t, PropertyInfo p, string l, string? d) : base(t, p, l, d) { }
|
||||
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
{
|
||||
object? raw = Property.GetValue(Target);
|
||||
return raw switch
|
||||
{
|
||||
decimal m => m.ToString("0.############", CultureInfo.CurrentCulture),
|
||||
double db => db.ToString("0.############", CultureInfo.CurrentCulture),
|
||||
float f => f.ToString("0.############", CultureInfo.CurrentCulture),
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!IsEditable) return;
|
||||
if (TryParse(value, out decimal parsed))
|
||||
{
|
||||
Type t = Nullable.GetUnderlyingType(Property.PropertyType) ?? Property.PropertyType;
|
||||
if (t == typeof(decimal)) Property.SetValue(Target, parsed);
|
||||
else if (t == typeof(double)) Property.SetValue(Target, (double)parsed);
|
||||
else if (t == typeof(float)) Property.SetValue(Target, (float)parsed);
|
||||
}
|
||||
Raise();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Nimmt „5,0" und „5.0" an – unabhängig von der eingestellten Kultur.</summary>
|
||||
internal static bool TryParse(string? text, out decimal value)
|
||||
{
|
||||
value = 0m;
|
||||
if (string.IsNullOrWhiteSpace(text)) return false;
|
||||
const NumberStyles styles = NumberStyles.Float;
|
||||
return decimal.TryParse(text, styles, CultureInfo.CurrentCulture, out value)
|
||||
|| decimal.TryParse(text, styles, CultureInfo.InvariantCulture, out value)
|
||||
|| decimal.TryParse(text.Replace(',', '.'), styles, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ja/Nein (bool) – als Kontrollkästchen.</summary>
|
||||
public sealed class BoolSettingsField : SettingsField
|
||||
{
|
||||
@@ -178,7 +255,15 @@ namespace PolyTrader.App.Avalonia.ViewModels
|
||||
if (t.IsEnum) return new ChoiceSettingsField(target, p, label, description);
|
||||
if (t == typeof(bool)) return new BoolSettingsField(target, p, label, description);
|
||||
if (t == typeof(int) || t == typeof(long)) return new IntSettingsField(target, p, label, description);
|
||||
if (t == typeof(string)) return new TextSettingsField(target, p, label, description);
|
||||
if (t == typeof(decimal) || t == typeof(double) || t == typeof(float))
|
||||
return new DecimalSettingsField(target, p, label, description);
|
||||
if (t == typeof(string))
|
||||
{
|
||||
bool multiline = p.GetCustomAttribute<DataTypeAttribute>()?.DataType == DataType.MultilineText;
|
||||
return multiline
|
||||
? new MultilineTextSettingsField(target, p, label, description)
|
||||
: new TextSettingsField(target, p, label, description);
|
||||
}
|
||||
|
||||
// Listen u.a. werden angezeigt, aber nicht hier bearbeitet (z.B. DisabledModules –
|
||||
// die pflegt der Modul-Tab des Dashboards).
|
||||
|
||||
Reference in New Issue
Block a user