Move 225 JetStream-related test files from NATS.Server.Tests into a dedicated NATS.Server.JetStream.Tests project. This includes root-level JetStream*.cs files, storage test files (FileStore, MemStore, StreamStoreContract), and the full JetStream/ subfolder tree (Api, Cluster, Consumers, MirrorSource, Snapshots, Storage, Streams). Updated all namespaces, added InternalsVisibleTo, registered in the solution file, and added the JETSTREAM_INTEGRATION_MATRIX define.
175 lines
4.7 KiB
C#
175 lines
4.7 KiB
C#
using NATS.Server.JetStream.Consumers;
|
|
|
|
namespace NATS.Server.JetStream.Tests.JetStream.Consumers;
|
|
|
|
/// <summary>
|
|
/// Tests for SampleTracker: sample frequency parsing and stochastic latency sampling.
|
|
/// Go reference: consumer.go sampleFrequency, shouldSample, parseSampleFrequency.
|
|
/// </summary>
|
|
public class SampleModeTests
|
|
{
|
|
// --- ParseSampleFrequency ---
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_one_percent()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("1%");
|
|
rate.ShouldBe(0.01, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_fifty_percent()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("50%");
|
|
rate.ShouldBe(0.5, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_hundred_percent()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("100%");
|
|
rate.ShouldBe(1.0, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_zero()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("0%");
|
|
rate.ShouldBe(0.0, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_no_percent_sign()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("25");
|
|
rate.ShouldBe(0.25, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_empty_string()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("");
|
|
rate.ShouldBe(0.0, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_null()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency(null);
|
|
rate.ShouldBe(0.0, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_invalid()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("abc");
|
|
rate.ShouldBe(0.0, 1e-9);
|
|
}
|
|
|
|
[Fact]
|
|
public void ParseSampleFrequency_over_100_clamped()
|
|
{
|
|
var rate = SampleTracker.ParseSampleFrequency("200%");
|
|
rate.ShouldBe(1.0, 1e-9);
|
|
}
|
|
|
|
// --- ShouldSample ---
|
|
|
|
[Fact]
|
|
public void ShouldSample_rate_100_always_samples()
|
|
{
|
|
var tracker = new SampleTracker(1.0);
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
tracker.ShouldSample().ShouldBeTrue();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ShouldSample_rate_0_never_samples()
|
|
{
|
|
var tracker = new SampleTracker(0.0);
|
|
for (var i = 0; i < 20; i++)
|
|
{
|
|
tracker.ShouldSample().ShouldBeFalse();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ShouldSample_increments_total_deliveries()
|
|
{
|
|
var tracker = new SampleTracker(0.5);
|
|
tracker.TotalDeliveries.ShouldBe(0L);
|
|
|
|
tracker.ShouldSample();
|
|
tracker.TotalDeliveries.ShouldBe(1L);
|
|
|
|
tracker.ShouldSample();
|
|
tracker.TotalDeliveries.ShouldBe(2L);
|
|
|
|
tracker.ShouldSample();
|
|
tracker.TotalDeliveries.ShouldBe(3L);
|
|
}
|
|
|
|
[Fact]
|
|
public void ShouldSample_stochastic_with_seeded_random()
|
|
{
|
|
// Use a seeded Random for deterministic results.
|
|
// With seed 42 and rate 0.5, we can predict exact outcomes.
|
|
var rng = new Random(42);
|
|
var tracker = new SampleTracker(0.5, rng);
|
|
|
|
// Pre-compute expected outcomes using the same seed.
|
|
var expectedRng = new Random(42);
|
|
var expected = new bool[10];
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
expected[i] = expectedRng.NextDouble() < 0.5;
|
|
}
|
|
|
|
var actual = new bool[10];
|
|
for (var i = 0; i < 10; i++)
|
|
{
|
|
actual[i] = tracker.ShouldSample();
|
|
}
|
|
|
|
actual.ShouldBe(expected);
|
|
tracker.TotalDeliveries.ShouldBe(10L);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordLatency_captures_all_fields()
|
|
{
|
|
var tracker = new SampleTracker(1.0);
|
|
var latency = TimeSpan.FromMilliseconds(42);
|
|
const ulong seq = 7UL;
|
|
const string subject = "orders.new";
|
|
|
|
var before = DateTime.UtcNow;
|
|
var sample = tracker.RecordLatency(latency, seq, subject);
|
|
var after = DateTime.UtcNow;
|
|
|
|
sample.Sequence.ShouldBe(seq);
|
|
sample.Subject.ShouldBe(subject);
|
|
sample.DeliveryLatency.ShouldBe(latency);
|
|
sample.SampledAtUtc.ShouldBeGreaterThanOrEqualTo(before);
|
|
sample.SampledAtUtc.ShouldBeLessThanOrEqualTo(after);
|
|
}
|
|
|
|
[Fact]
|
|
public void SampleCount_tracks_sampled_only()
|
|
{
|
|
// Rate 1.0: every delivery is sampled.
|
|
var allSampled = new SampleTracker(1.0);
|
|
for (var i = 0; i < 5; i++) allSampled.ShouldSample();
|
|
allSampled.SampleCount.ShouldBe(5L);
|
|
allSampled.TotalDeliveries.ShouldBe(5L);
|
|
|
|
// Rate 0.0: no delivery is sampled.
|
|
var noneSampled = new SampleTracker(0.0);
|
|
for (var i = 0; i < 5; i++) noneSampled.ShouldSample();
|
|
noneSampled.SampleCount.ShouldBe(0L);
|
|
noneSampled.TotalDeliveries.ShouldBe(5L);
|
|
}
|
|
}
|