Skip to content

Commit adffbfc

Browse files
committed
Updated XML to not require the toggle but wont resolve XML until a filter is active
1 parent 1f9bfa9 commit adffbfc

34 files changed

Lines changed: 1238 additions & 333 deletions
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// // Copyright (c) Microsoft Corporation.
2+
// // Licensed under the MIT License.
3+
4+
using EventLogExpert.Eventing.EventResolvers;
5+
using EventLogExpert.Eventing.Helpers;
6+
using EventLogExpert.Eventing.Models;
7+
8+
namespace EventLogExpert.Eventing.Tests.EventResolvers;
9+
10+
public sealed class EventXmlResolverTests
11+
{
12+
[Fact]
13+
public async Task ClearAll_RemovesEveryEntry()
14+
{
15+
var resolver = new TrackingResolver(_ => "<xml/>");
16+
17+
var evt = CreateEvent(recordId: 1);
18+
await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
19+
Assert.Equal(1, resolver.ResolveCallCount);
20+
21+
resolver.ClearAll();
22+
23+
await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
24+
Assert.Equal(2, resolver.ResolveCallCount);
25+
}
26+
27+
[Fact]
28+
public async Task ClearLog_RemovesEntriesForThatLogOnly()
29+
{
30+
var resolver = new TrackingResolver(key => $"<xml log='{key.OwningLog}' id='{key.RecordId}'/>");
31+
32+
var evtA1 = CreateEvent(recordId: 1, owningLog: "A");
33+
var evtA2 = CreateEvent(recordId: 2, owningLog: "A");
34+
var evtB1 = CreateEvent(recordId: 1, owningLog: "B");
35+
36+
await resolver.GetXmlAsync(evtA1, TestContext.Current.CancellationToken);
37+
await resolver.GetXmlAsync(evtA2, TestContext.Current.CancellationToken);
38+
await resolver.GetXmlAsync(evtB1, TestContext.Current.CancellationToken);
39+
Assert.Equal(3, resolver.ResolveCallCount);
40+
41+
resolver.ClearLog("A");
42+
43+
// B is untouched.
44+
await resolver.GetXmlAsync(evtB1, TestContext.Current.CancellationToken);
45+
Assert.Equal(3, resolver.ResolveCallCount);
46+
47+
// A entries were evicted; both are re-resolved.
48+
await resolver.GetXmlAsync(evtA1, TestContext.Current.CancellationToken);
49+
await resolver.GetXmlAsync(evtA2, TestContext.Current.CancellationToken);
50+
Assert.Equal(5, resolver.ResolveCallCount);
51+
}
52+
53+
[Fact]
54+
public async Task GetXmlAsync_AtCapacity_EvictsLeastRecentlyUsed()
55+
{
56+
// Initial = max = 2, so eviction kicks in once we exceed 2 entries.
57+
var resolver = new BoundedTrackingResolver(initialCapacity: 2, maxCapacity: 2);
58+
59+
var evtA = CreateEvent(recordId: 1, owningLog: "A");
60+
var evtB = CreateEvent(recordId: 2, owningLog: "B");
61+
var evtC = CreateEvent(recordId: 3, owningLog: "C");
62+
63+
await resolver.GetXmlAsync(evtA, TestContext.Current.CancellationToken);
64+
await resolver.GetXmlAsync(evtB, TestContext.Current.CancellationToken);
65+
// Touch A so B becomes the least-recently-used entry.
66+
await resolver.GetXmlAsync(evtA, TestContext.Current.CancellationToken);
67+
await resolver.GetXmlAsync(evtC, TestContext.Current.CancellationToken);
68+
69+
// A and C should still be cached (B was evicted as LRU); A re-request must not re-resolve.
70+
await resolver.GetXmlAsync(evtA, TestContext.Current.CancellationToken);
71+
await resolver.GetXmlAsync(evtC, TestContext.Current.CancellationToken);
72+
Assert.Equal(3, resolver.ResolveCallCount);
73+
74+
// Re-requesting B triggers another resolve since it was evicted.
75+
await resolver.GetXmlAsync(evtB, TestContext.Current.CancellationToken);
76+
Assert.Equal(4, resolver.ResolveCallCount);
77+
}
78+
79+
[Fact]
80+
public async Task GetXmlAsync_ConcurrentRequestsForSameKey_ResolveOnce()
81+
{
82+
using var gate = new ManualResetEventSlim(initialState: false);
83+
var resolver = new TrackingResolver(_ =>
84+
{
85+
gate.Wait(TimeSpan.FromSeconds(5));
86+
87+
return "<xml/>";
88+
});
89+
90+
var evt = CreateEvent(recordId: 7);
91+
92+
var t1 = resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken).AsTask();
93+
var t2 = resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken).AsTask();
94+
var t3 = resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken).AsTask();
95+
96+
gate.Set();
97+
var results = await Task.WhenAll(t1, t2, t3);
98+
99+
Assert.All(results, r => Assert.Equal("<xml/>", r));
100+
Assert.Equal(1, resolver.ResolveCallCount);
101+
}
102+
103+
[Fact]
104+
public async Task GetXmlAsync_OnCacheMiss_ResolvesAndCachesResult()
105+
{
106+
var resolver = new TrackingResolver(key => $"<xml id='{key.RecordId}'/>");
107+
var evt = CreateEvent(recordId: 42);
108+
109+
var first = await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
110+
var second = await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
111+
112+
Assert.Equal("<xml id='42'/>", first);
113+
Assert.Equal("<xml id='42'/>", second);
114+
Assert.Equal(1, resolver.ResolveCallCount);
115+
}
116+
117+
[Fact]
118+
public async Task GetXmlAsync_WhenEventHasPreRenderedXml_ReturnsItWithoutResolving()
119+
{
120+
var resolver = new TrackingResolver(_ => "should-not-be-called");
121+
var evt = CreateEvent(recordId: 1) with { Xml = "<pre-rendered/>" };
122+
123+
var result = await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
124+
125+
Assert.Equal("<pre-rendered/>", result);
126+
Assert.Equal(0, resolver.ResolveCallCount);
127+
}
128+
129+
[Fact]
130+
public async Task GetXmlAsync_WhenOwningLogIsEmpty_ReturnsEmptyWithoutResolving()
131+
{
132+
var resolver = new TrackingResolver(_ => "x");
133+
var evt = CreateEvent(recordId: 1, owningLog: string.Empty);
134+
135+
var result = await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
136+
137+
Assert.Equal(string.Empty, result);
138+
Assert.Equal(0, resolver.ResolveCallCount);
139+
}
140+
141+
[Fact]
142+
public async Task GetXmlAsync_WhenRecordIdIsNull_ReturnsEmptyWithoutResolving()
143+
{
144+
var resolver = new TrackingResolver(_ => "x");
145+
var evt = CreateEvent(recordId: null);
146+
147+
var result = await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
148+
149+
Assert.Equal(string.Empty, result);
150+
Assert.Equal(0, resolver.ResolveCallCount);
151+
}
152+
153+
[Fact]
154+
public async Task GetXmlAsync_WhenResolveThrows_EvictsEntryAndAllowsRetry()
155+
{
156+
int callCount = 0;
157+
var resolver = new TrackingResolver(_ =>
158+
{
159+
callCount++;
160+
161+
return callCount == 1 ? throw new InvalidOperationException("boom") : "<xml/>";
162+
});
163+
164+
var evt = CreateEvent(recordId: 99);
165+
166+
await Assert.ThrowsAsync<InvalidOperationException>(
167+
async () => await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken));
168+
169+
var result = await resolver.GetXmlAsync(evt, TestContext.Current.CancellationToken);
170+
Assert.Equal("<xml/>", result);
171+
Assert.Equal(2, callCount);
172+
}
173+
174+
private static DisplayEventModel CreateEvent(long? recordId, string owningLog = "TestLog") =>
175+
new(owningLog, PathType.LogName)
176+
{
177+
RecordId = recordId,
178+
Xml = string.Empty
179+
};
180+
181+
private sealed class BoundedTrackingResolver(int initialCapacity, int maxCapacity)
182+
: EventXmlResolver(initialCapacity, maxCapacity)
183+
{
184+
private int _resolveCallCount;
185+
186+
public int ResolveCallCount => Volatile.Read(ref _resolveCallCount);
187+
188+
protected override string ResolveXml(string owningLog, long recordId, PathType pathType)
189+
{
190+
Interlocked.Increment(ref _resolveCallCount);
191+
192+
return $"<xml log='{owningLog}' id='{recordId}'/>";
193+
}
194+
}
195+
196+
private class TrackingResolver(Func<ResolveKey, string> resolve) : EventXmlResolver
197+
{
198+
private int _resolveCallCount;
199+
200+
public int ResolveCallCount => Volatile.Read(ref _resolveCallCount);
201+
202+
protected override string ResolveXml(string owningLog, long recordId, PathType pathType)
203+
{
204+
Interlocked.Increment(ref _resolveCallCount);
205+
206+
return resolve(new ResolveKey(owningLog, recordId, pathType));
207+
}
208+
}
209+
210+
private sealed record ResolveKey(string OwningLog, long RecordId, PathType PathType);
211+
}

0 commit comments

Comments
 (0)