Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
479 lines
16 KiB
C#
479 lines
16 KiB
C#
using System.Threading.Tasks;
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.Testing;
|
|
using Xunit;
|
|
using Verifier = Microsoft.CodeAnalysis.CSharp.Testing.XUnit.AnalyzerVerifier<StellaOps.Telemetry.Analyzers.MetricLabelAnalyzer>;
|
|
|
|
namespace StellaOps.Telemetry.Analyzers.Tests;
|
|
|
|
public sealed class MetricLabelAnalyzerTests
|
|
{
|
|
[Fact]
|
|
public async Task ValidLabelKey_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status_code", "200"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task InvalidLabelKey_UpperCase_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag({|#0:"StatusCode"|}, "200"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.InvalidLabelKeyDiagnosticId)
|
|
.WithLocation(0)
|
|
.WithArguments("StatusCode");
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HighCardinalityLabelKey_UserId_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag({|#0:"user_id"|}, "123"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
|
|
.WithLocation(0)
|
|
.WithArguments("user_id");
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HighCardinalityLabelKey_RequestId_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void IncrementRequests(params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.IncrementRequests(GoldenSignalMetrics.Tag({|#0:"request_id"|}, "abc-123"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
|
|
.WithLocation(0)
|
|
.WithArguments("request_id");
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task HighCardinalityLabelKey_Email_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void IncrementErrors(params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.IncrementErrors(GoldenSignalMetrics.Tag({|#0:"user_email"|}, "test@example.com"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
|
|
.WithLocation(0)
|
|
.WithArguments("user_email");
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DynamicLabelValue_Variable_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod(string dynamicValue)
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("operation", {|#0:dynamicValue|}));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.DynamicLabelDiagnosticId)
|
|
.WithLocation(0);
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DynamicLabelValue_InterpolatedString_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod(int code)
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", {|#0:$"code_{code}"|}));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.DynamicLabelDiagnosticId)
|
|
.WithLocation(0);
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StaticLabelValue_Constant_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
private const string StatusOk = "ok";
|
|
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", StatusOk));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EnumLabelValue_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public enum Status { Ok, Error }
|
|
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", Status.Ok));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EnumToStringLabelValue_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public enum Status { Ok, Error }
|
|
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", Status.Ok.ToString()));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task TupleSyntax_ValidLabel_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Diagnostics.Metrics;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class TestClass
|
|
{
|
|
public void TestMethod(Counter<int> counter)
|
|
{
|
|
counter.Add(1, ("status_code", "200"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task KeyValuePairCreation_HighCardinalityKey_ReportsDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Metrics;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class TestClass
|
|
{
|
|
public void TestMethod(Counter<int> counter)
|
|
{
|
|
counter.Add(1, new KeyValuePair<string, object?>({|#0:"session_id"|}, "abc"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected = Verifier.Diagnostic(MetricLabelAnalyzer.HighCardinalityDiagnosticId)
|
|
.WithLocation(0)
|
|
.WithArguments("session_id");
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NonMetricMethod_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class RegularClass
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void SomeMethod(params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var obj = new RegularClass();
|
|
obj.SomeMethod(RegularClass.Tag("user_id", "123"));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MultipleIssues_ReportsAllDiagnostics()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod(string dynamicValue)
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0,
|
|
GoldenSignalMetrics.Tag({|#0:"UserId"|}, "static"),
|
|
GoldenSignalMetrics.Tag("operation", {|#1:dynamicValue|}));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
var expected1 = Verifier.Diagnostic(MetricLabelAnalyzer.InvalidLabelKeyDiagnosticId)
|
|
.WithLocation(0)
|
|
.WithArguments("UserId");
|
|
|
|
var expected2 = Verifier.Diagnostic(MetricLabelAnalyzer.DynamicLabelDiagnosticId)
|
|
.WithLocation(1);
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test, expected1, expected2);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StaticReadonlyField_LabelValue_NoDiagnostic()
|
|
{
|
|
var test = """
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace TestNamespace
|
|
{
|
|
public class GoldenSignalMetrics
|
|
{
|
|
public static KeyValuePair<string, object?> Tag(string key, object? value) => new(key, value);
|
|
public void RecordLatency(double value, params KeyValuePair<string, object?>[] tags) { }
|
|
}
|
|
|
|
public static class Labels
|
|
{
|
|
public static readonly string StatusOk = "ok";
|
|
}
|
|
|
|
public class TestClass
|
|
{
|
|
public void TestMethod()
|
|
{
|
|
var metrics = new GoldenSignalMetrics();
|
|
metrics.RecordLatency(100.0, GoldenSignalMetrics.Tag("status", Labels.StatusOk));
|
|
}
|
|
}
|
|
}
|
|
""";
|
|
|
|
await Verifier.VerifyAnalyzerAsync(test);
|
|
}
|
|
}
|