Repository navigation
Expand file tree
/
Copy pathInstrumentationTests.cs
More file actions
137 lines (109 loc) · 4.73 KB
/
Copy pathInstrumentationTests.cs
File metadata and controls
137 lines (109 loc) · 4.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using System;
using System.Linq;
using System.Threading;
using PdfEditorApp.Services;
using Xunit;
namespace PdfEditorApp.Tests;
/// <summary>
/// Tests for the navigation/duration instrumentation added to make multi-second UI stalls
/// visible in the diagnostic log.
/// </summary>
[Collection("AppLogService")]
public class InstrumentationTests
{
/// <summary>Captures entries in memory so assertions don't depend on the shared singleton.</summary>
private sealed class RecordingLog : IAppLogService
{
public System.Collections.Generic.List<AppLogEntry> Entries { get; } = new();
public void Log(AppLogLevel level, string category, string message)
=> Entries.Add(new AppLogEntry { Level = level, Category = category, Message = message });
public System.Collections.Generic.IReadOnlyList<AppLogEntry> GetSnapshot() => Entries;
public void Clear() => Entries.Clear();
public int Capacity => int.MaxValue;
}
// ─── LogDuration level escalation ───────────────────────────────────────
[Fact]
public void LogDuration_EscalatesToWarningAtOrAboveTheThreshold()
{
var log = new RecordingLog();
log.LogDuration("Navigation", "fast", elapsedMs: 10, warnAboveMs: 250);
log.LogDuration("Navigation", "at threshold", elapsedMs: 250, warnAboveMs: 250);
log.LogDuration("Navigation", "slow", elapsedMs: 4200, warnAboveMs: 250);
Assert.Equal(AppLogLevel.Info, log.Entries[0].Level);
Assert.Equal(AppLogLevel.Warning, log.Entries[1].Level);
Assert.Equal(AppLogLevel.Warning, log.Entries[2].Level);
}
[Fact]
public void LogDuration_SetsTheLevelExplicitlyRatherThanByKeyword()
{
// AppLogService.ParseRaw classifies free-text by keyword; "took 4200ms" contains none
// of its warning keywords, so a slow navigation would otherwise be filed as Debug.
var (_, _, parsedLevel) = AppLogService.ParseRaw("[Navigation] Home -> Plugins took 4200ms.");
Assert.Equal(AppLogLevel.Debug, parsedLevel);
var log = new RecordingLog();
log.LogDuration("Navigation", "Home -> Plugins took 4200ms.", 4200);
Assert.Equal(AppLogLevel.Warning, log.Entries.Single().Level);
}
// ─── TimeOperation ──────────────────────────────────────────────────────
[Fact]
public void TimeOperation_LogsOnceOnDisposal()
{
var log = new RecordingLog();
using (log.TimeOperation("Navigation", "OpenPage"))
{
Assert.Empty(log.Entries);
}
var entry = Assert.Single(log.Entries);
Assert.Equal("Navigation", entry.Category);
Assert.Contains("OpenPage took", entry.Message, StringComparison.Ordinal);
Assert.Contains("ms", entry.Message, StringComparison.Ordinal);
}
[Fact]
public void TimeOperation_IsIdempotentAcrossRepeatedDisposal()
{
var log = new RecordingLog();
var timer = log.TimeOperation("Navigation", "OpenPage");
timer.Dispose();
timer.Dispose();
timer.Dispose();
Assert.Single(log.Entries);
}
[Fact]
public void TimeOperation_EscalatesWhenTheOperationExceedsTheThreshold()
{
var log = new RecordingLog();
using (log.TimeOperation("Navigation", "SlowPage", warnAboveMs: 20))
{
Thread.Sleep(60);
}
Assert.Equal(AppLogLevel.Warning, log.Entries.Single().Level);
}
[Fact]
public void TimeOperation_StaysInfoForAFastOperation()
{
var log = new RecordingLog();
using (log.TimeOperation("Navigation", "FastPage", warnAboveMs: 5000))
{
}
Assert.Equal(AppLogLevel.Info, log.Entries.Single().Level);
}
// ─── UI-thread watchdog ─────────────────────────────────────────────────
[Fact]
public void UiThreadWatchdog_IsInertWithoutAnApplication()
{
// No Avalonia Application in a unit-test host, so there is no dispatcher loop to probe.
// The watchdog must stay quiet rather than throw or report phantom stalls.
var log = new RecordingLog();
using var watchdog = new UiThreadWatchdog(log);
Thread.Sleep(120);
Assert.Empty(log.Entries);
Assert.Equal(0, watchdog.WorstStallMs);
}
[Fact]
public void UiThreadWatchdog_DisposesCleanlyAndIsIdempotent()
{
var watchdog = new UiThreadWatchdog(new RecordingLog());
watchdog.Dispose();
watchdog.Dispose();
}
}