| File: Model\MetricPinStoreTests.cs | Web Access |
| Project: src\tests\Aspire.Dashboard.Tests\Aspire.Dashboard.Tests.csproj (Aspire.Dashboard.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Dashboard.Configuration; using Aspire.Dashboard.Model; using Aspire.Dashboard.Otlp.Model; using Microsoft.Extensions.Logging.Abstractions; using Xunit; namespace Aspire.Dashboard.Tests.Model; public sealed class MetricPinStoreTests(ITestOutputHelper testOutputHelper) : IDisposable { private static readonly OtlpInstrumentKey s_duration = new("Microsoft.AspNetCore.Hosting", "http.server.request.duration"); private static readonly OtlpInstrumentKey s_heap = new("System.Runtime", "dotnet.gc.heap.total_allocated"); private static readonly OtlpInstrumentKey s_cpu = new("System.Runtime", "dotnet.process.cpu.time"); private readonly TemporaryWorkspace _workspace = TemporaryWorkspace.Create(testOutputHelper); [Fact] public void SetPinned_PersistsInPinnedOrderAcrossInstances() { var databasePath = Path.Combine(_workspace.Path, DashboardSettingsDatabase.DatabaseFileName); var store = new MetricPinStore(databasePath, NullLogger.Instance); Assert.True(store.SetPinned(s_heap, pinned: true)); Assert.True(store.SetPinned(s_duration, pinned: true)); Assert.True(store.SetPinned(s_cpu, pinned: true)); Assert.True(store.SetPinned(s_duration, pinned: false)); // A new instance reads the saved pins, as after restarting the dashboard. store = new MetricPinStore(databasePath, NullLogger.Instance); Assert.Equal([s_heap, s_cpu], store.GetPins()); Assert.True(store.IsPinned(s_heap)); Assert.False(store.IsPinned(s_duration)); // Re-pinning moves the instrument to the end because pins are listed in the order they were pinned. Assert.True(store.SetPinned(s_duration, pinned: true)); store = new MetricPinStore(databasePath, NullLogger.Instance); Assert.Equal([s_heap, s_cpu, s_duration], store.GetPins()); } [Fact] public void SetPinned_RaisesChangedOnlyWhenPinsChange() { var store = new MetricPinStore(databasePath: null, NullLogger.Instance); var changedCount = 0; store.Changed += () => changedCount++; store.SetPinned(s_heap, pinned: true); store.SetPinned(s_heap, pinned: true); store.SetPinned(s_cpu, pinned: false); store.SetPinned(s_heap, pinned: false); Assert.Equal(2, changedCount); Assert.Empty(store.GetPins()); } [Fact] public void SetPinned_UnreadableDatabase_KeepsPinsInMemory() { // A directory where the database file should be makes opening it fail. var databasePath = Path.Combine(_workspace.Path, DashboardSettingsDatabase.DatabaseFileName); Directory.CreateDirectory(databasePath); var store = new MetricPinStore(databasePath, NullLogger.Instance); // Like the other settings stores, pins keep working for the rest of the process without touching the file. Assert.True(store.SetPinned(s_heap, pinned: true)); Assert.Equal([s_heap], store.GetPins()); Assert.True(Directory.Exists(databasePath)); Assert.Empty(Directory.GetFileSystemEntries(databasePath)); } [Fact] public void GetPinnedInstruments_ReturnsReportedInstrumentsInPinnedOrder() { var store = new MetricPinStore(databasePath: null, NullLogger.Instance); store.SetPinned(s_cpu, pinned: true); store.SetPinned(s_duration, pinned: true); store.SetPinned(s_heap, pinned: true); // This resource doesn't report the HTTP duration instrument, so that pin is skipped. var heap = CreateInstrument(s_heap); var cpu = CreateInstrument(s_cpu); var unpinned = CreateInstrument(new OtlpInstrumentKey("System.Runtime", "dotnet.exceptions")); Assert.Equal([cpu, heap], store.GetPinnedInstruments([heap, unpinned, cpu])); } private static OtlpInstrumentSummary CreateInstrument(OtlpInstrumentKey key) { var context = new OtlpContext { Options = new TelemetryLimitOptions(), Logger = NullLogger.Instance }; var resource = new OtlpResource("resource", instanceId: null, uninstrumentedPeer: false, context); return new OtlpInstrumentSummary { Name = key.InstrumentName, Description = string.Empty, Unit = string.Empty, Type = OtlpInstrumentType.Sum, AggregationTemporality = OtlpAggregationTemporality.Cumulative, IsMonotonic = true, Parent = new OtlpScope(key.MeterName, version: string.Empty, attributes: []), ResourceView = new OtlpResourceView(resource, Array.Empty<KeyValuePair<string, string>>()) }; } public void Dispose() => _workspace.Dispose(); }