Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Concelier.Models;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Concelier.Models.Tests;
|
||||
|
||||
public sealed class ProvenanceDiagnosticsTests
|
||||
{
|
||||
[Fact]
|
||||
public void RecordMissing_AddsExpectedTagsAndDeduplicates()
|
||||
{
|
||||
ResetState();
|
||||
|
||||
var measurements = new List<(string Instrument, long Value, IReadOnlyDictionary<string, object?> Tags)>();
|
||||
using var listener = CreateListener(measurements);
|
||||
|
||||
var baseline = DateTimeOffset.UtcNow;
|
||||
ProvenanceDiagnostics.RecordMissing("source-A", "range:pkg", baseline, new[] { ProvenanceFieldMasks.VersionRanges });
|
||||
ProvenanceDiagnostics.RecordMissing("source-A", "range:pkg", baseline.AddMinutes(5), new[] { ProvenanceFieldMasks.VersionRanges });
|
||||
ProvenanceDiagnostics.RecordMissing("source-A", "reference:https://example", baseline.AddMinutes(10), new[] { ProvenanceFieldMasks.References });
|
||||
|
||||
listener.Dispose();
|
||||
|
||||
Assert.Equal(2, measurements.Count);
|
||||
|
||||
var first = measurements[0];
|
||||
Assert.Equal(1, first.Value);
|
||||
Assert.Equal("concelier.provenance.missing", first.Instrument);
|
||||
Assert.Equal("source-A", first.Tags["source"]);
|
||||
Assert.Equal("range:pkg", first.Tags["component"]);
|
||||
Assert.Equal("range", first.Tags["category"]);
|
||||
Assert.Equal("high", first.Tags["severity"]);
|
||||
Assert.Equal(ProvenanceFieldMasks.VersionRanges, first.Tags["fieldMask"]);
|
||||
|
||||
var second = measurements[1];
|
||||
Assert.Equal("concelier.provenance.missing", second.Instrument);
|
||||
Assert.Equal("reference", second.Tags["category"]);
|
||||
Assert.Equal("low", second.Tags["severity"]);
|
||||
Assert.Equal(ProvenanceFieldMasks.References, second.Tags["fieldMask"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportResumeWindow_ClearsTrackedEntries_WhenWindowBackfills()
|
||||
{
|
||||
ResetState();
|
||||
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
ProvenanceDiagnostics.RecordMissing("source-B", "package:lib", timestamp);
|
||||
|
||||
var (recorded, earliest, syncRoot) = GetInternalState();
|
||||
lock (syncRoot)
|
||||
{
|
||||
Assert.True(earliest.ContainsKey("source-B"));
|
||||
Assert.Contains(recorded, entry => entry.StartsWith("source-B|", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
ProvenanceDiagnostics.ReportResumeWindow("source-B", timestamp.AddMinutes(-5), NullLogger.Instance);
|
||||
|
||||
lock (syncRoot)
|
||||
{
|
||||
Assert.False(earliest.ContainsKey("source-B"));
|
||||
Assert.DoesNotContain(recorded, entry => entry.StartsWith("source-B|", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReportResumeWindow_RetainsEntries_WhenWindowTooRecent()
|
||||
{
|
||||
ResetState();
|
||||
|
||||
var timestamp = DateTimeOffset.UtcNow;
|
||||
ProvenanceDiagnostics.RecordMissing("source-C", "range:pkg", timestamp);
|
||||
|
||||
ProvenanceDiagnostics.ReportResumeWindow("source-C", timestamp.AddMinutes(1), NullLogger.Instance);
|
||||
|
||||
var (recorded, earliest, syncRoot) = GetInternalState();
|
||||
lock (syncRoot)
|
||||
{
|
||||
Assert.True(earliest.ContainsKey("source-C"));
|
||||
Assert.Contains(recorded, entry => entry.StartsWith("source-C|", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecordRangePrimitive_EmitsCoverageMetric()
|
||||
{
|
||||
var range = new AffectedVersionRange(
|
||||
rangeKind: "evr",
|
||||
introducedVersion: "1:1.1.1n-0+deb11u2",
|
||||
fixedVersion: null,
|
||||
lastAffectedVersion: null,
|
||||
rangeExpression: null,
|
||||
provenance: new AdvisoryProvenance("source-D", "range", "pkg", DateTimeOffset.UtcNow),
|
||||
primitives: new RangePrimitives(
|
||||
SemVer: null,
|
||||
Nevra: null,
|
||||
Evr: new EvrPrimitive(
|
||||
new EvrComponent(1, "1.1.1n", "0+deb11u2"),
|
||||
null,
|
||||
null),
|
||||
VendorExtensions: new Dictionary<string, string> { ["debian.release"] = "bullseye" }));
|
||||
|
||||
var measurements = new List<(string Instrument, long Value, IReadOnlyDictionary<string, object?> Tags)>();
|
||||
using var listener = CreateListener(measurements, "concelier.range.primitives");
|
||||
|
||||
ProvenanceDiagnostics.RecordRangePrimitive("source-D", range);
|
||||
|
||||
listener.Dispose();
|
||||
|
||||
var record = Assert.Single(measurements);
|
||||
Assert.Equal("concelier.range.primitives", record.Instrument);
|
||||
Assert.Equal(1, record.Value);
|
||||
Assert.Equal("source-D", record.Tags["source"]);
|
||||
Assert.Equal("evr", record.Tags["rangeKind"]);
|
||||
Assert.Equal("evr", record.Tags["primitiveKinds"]);
|
||||
Assert.Equal("true", record.Tags["hasVendorExtensions"]);
|
||||
}
|
||||
|
||||
private static MeterListener CreateListener(
|
||||
List<(string Instrument, long Value, IReadOnlyDictionary<string, object?> Tags)> measurements,
|
||||
params string[] instrumentNames)
|
||||
{
|
||||
var allowed = instrumentNames is { Length: > 0 } ? instrumentNames : new[] { "concelier.provenance.missing" };
|
||||
var allowedSet = new HashSet<string>(allowed, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var listener = new MeterListener
|
||||
{
|
||||
InstrumentPublished = (instrument, l) =>
|
||||
{
|
||||
if (instrument.Meter.Name == "StellaOps.Concelier.Models.Provenance" && allowedSet.Contains(instrument.Name))
|
||||
{
|
||||
l.EnableMeasurementEvents(instrument);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
listener.SetMeasurementEventCallback<long>((instrument, measurement, tags, state) =>
|
||||
{
|
||||
var dict = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var tag in tags)
|
||||
{
|
||||
dict[tag.Key] = tag.Value;
|
||||
}
|
||||
|
||||
measurements.Add((instrument.Name, measurement, dict));
|
||||
});
|
||||
|
||||
listener.Start();
|
||||
return listener;
|
||||
}
|
||||
|
||||
private static void ResetState()
|
||||
{
|
||||
var (_, _, syncRoot) = GetInternalState();
|
||||
lock (syncRoot)
|
||||
{
|
||||
var (recorded, earliest, _) = GetInternalState();
|
||||
recorded.Clear();
|
||||
earliest.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static (HashSet<string> Recorded, Dictionary<string, DateTimeOffset> Earliest, object SyncRoot) GetInternalState()
|
||||
{
|
||||
var type = typeof(ProvenanceDiagnostics);
|
||||
var recordedField = type.GetField("RecordedComponents", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("RecordedComponents not found");
|
||||
var earliestField = type.GetField("EarliestMissing", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("EarliestMissing not found");
|
||||
var syncField = type.GetField("SyncRoot", BindingFlags.NonPublic | BindingFlags.Static) ?? throw new InvalidOperationException("SyncRoot not found");
|
||||
|
||||
var recorded = (HashSet<string>)recordedField.GetValue(null)!;
|
||||
var earliest = (Dictionary<string, DateTimeOffset>)earliestField.GetValue(null)!;
|
||||
var sync = syncField.GetValue(null)!;
|
||||
return (recorded, earliest, sync);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user