Add tests for SBOM generation determinism across multiple formats
- Created `StellaOps.TestKit.Tests` project for unit tests related to determinism. - Implemented `DeterminismManifestTests` to validate deterministic output for canonical bytes and strings, file read/write operations, and error handling for invalid schema versions. - Added `SbomDeterminismTests` to ensure identical inputs produce consistent SBOMs across SPDX 3.0.1 and CycloneDX 1.6/1.7 formats, including parallel execution tests. - Updated project references in `StellaOps.Integration.Determinism` to include the new determinism testing library.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
namespace StellaOps.TestKit.Deterministic;
|
||||
|
||||
/// <summary>
|
||||
/// Provides deterministic random number generation for testing.
|
||||
/// Uses a fixed seed to ensure reproducible random sequences.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Usage:
|
||||
/// <code>
|
||||
/// var random = new DeterministicRandom(seed: 42);
|
||||
/// var value1 = random.Next(); // Same value every time with seed 42
|
||||
/// var value2 = random.NextDouble(); // Deterministic sequence
|
||||
///
|
||||
/// // For property-based testing with FsCheck
|
||||
/// var gen = DeterministicRandom.CreateGen(seed: 42);
|
||||
/// </code>
|
||||
/// </remarks>
|
||||
public sealed class DeterministicRandom
|
||||
{
|
||||
private readonly System.Random _random;
|
||||
private readonly int _seed;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new deterministic random number generator with the specified seed.
|
||||
/// </summary>
|
||||
/// <param name="seed">The seed value. Same seed always produces same sequence.</param>
|
||||
public DeterministicRandom(int seed)
|
||||
{
|
||||
_seed = seed;
|
||||
_random = new System.Random(seed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the seed used for this random number generator.
|
||||
/// </summary>
|
||||
public int Seed => _seed;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative random integer.
|
||||
/// </summary>
|
||||
public int Next() => _random.Next();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a non-negative random integer less than the specified maximum.
|
||||
/// </summary>
|
||||
public int Next(int maxValue) => _random.Next(maxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random integer within the specified range.
|
||||
/// </summary>
|
||||
public int Next(int minValue, int maxValue) => _random.Next(minValue, maxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random floating-point number between 0.0 and 1.0.
|
||||
/// </summary>
|
||||
public double NextDouble() => _random.NextDouble();
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of the specified array with random bytes.
|
||||
/// </summary>
|
||||
public void NextBytes(byte[] buffer) => _random.NextBytes(buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Fills the elements of the specified span with random bytes.
|
||||
/// </summary>
|
||||
public void NextBytes(Span<byte> buffer) => _random.NextBytes(buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new deterministic Random instance with the specified seed.
|
||||
/// Useful for integration with code that expects System.Random.
|
||||
/// </summary>
|
||||
public static System.Random CreateRandom(int seed) => new(seed);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a deterministic GUID based on the seed.
|
||||
/// </summary>
|
||||
public Guid NextGuid()
|
||||
{
|
||||
var bytes = new byte[16];
|
||||
_random.NextBytes(bytes);
|
||||
return new Guid(bytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a deterministic string of the specified length using alphanumeric characters.
|
||||
/// </summary>
|
||||
public string NextString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var result = new char[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result[i] = chars[_random.Next(chars.Length)];
|
||||
}
|
||||
return new string(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects a random element from the specified array.
|
||||
/// </summary>
|
||||
public T NextElement<T>(T[] array)
|
||||
{
|
||||
if (array == null || array.Length == 0)
|
||||
{
|
||||
throw new ArgumentException("Array cannot be null or empty", nameof(array));
|
||||
}
|
||||
return array[_random.Next(array.Length)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuffles an array in-place using the Fisher-Yates algorithm (deterministic).
|
||||
/// </summary>
|
||||
public void Shuffle<T>(T[] array)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
for (int i = array.Length - 1; i > 0; i--)
|
||||
{
|
||||
int j = _random.Next(i + 1);
|
||||
(array[i], array[j]) = (array[j], array[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user