Skip to content

Commit e850b06

Browse files
Modernize library: target net8.0/net10.0, modern C# idioms, xUnit v3
- Drop netstandard2.0, target net8.0 and net10.0 LTS only - Enable nullable reference types and implicit usings - Replace static Random instance with Random.Shared (thread-safe) - Replace RandomNumberGenerator.Create() + disposal with RandomNumberGenerator.Fill() - Use ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual/ThrowIfLessThan/ThrowIfGreaterThan guard APIs everywhere instead of manual throws - Add where T : struct, Enum constraint on GetEnum<T>() to eliminate runtime type check - Use Math.Clamp instead of separate Math.Min/Math.Max in GetBool - Rewrite UpperCaseFirstCharacter with stackalloc Span<char> for zero-allocation - Convert to file-scoped namespace, collection expressions, expression-bodied members - Use string interpolation instead of concatenation - Add nullable annotations to GetVersion parameters and EnumerableExtensions - Upgrade build packages: SourceLink 10.0.103, AsyncFixer 2.1.0, MinVer 7.0.0 - Use dynamic copyright year expression matching project convention - Upgrade test project to xUnit v3 with Microsoft Testing Platform - Add Foundatio.Xunit.v3 13.0.0-beta3, xunit.v3.mtp-v2 3.2.2 - Add TestingPlatformDotnetTestSupport and new-test-runner: true for CI - Restore test parallelization disabling via AssemblyInfo.cs - Add codeql-analysis.yml, copilot-setup-steps.yml, global.json, AGENTS.md - Update README with comprehensive usage examples (all snippets compile clean) Co-authored-by: Cursor <[email protected]>
1 parent 8385980 commit e850b06

File tree

11 files changed

+603
-359
lines changed

11 files changed

+603
-359
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ jobs:
66
uses: FoundatioFx/Foundatio/.github/workflows/build-workflow.yml@main
77
with:
88
org: exceptionless
9+
new-test-runner: true
910
secrets:
1011
NUGET_KEY: ${{ secrets.NUGET_KEY }}
1112
FEEDZ_KEY: ${{ secrets.FEEDZ_KEY }}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: "Code scanning - action"
2+
3+
on:
4+
schedule:
5+
- cron: '0 1 * * 2'
6+
7+
jobs:
8+
CodeQL-Build:
9+
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v6
15+
with:
16+
# We must fetch at least the immediate parents so that if this is
17+
# a pull request then we can checkout the head.
18+
fetch-depth: 2
19+
20+
# If this run was triggered by a pull request event, then checkout
21+
# the head of the pull request instead of the merge commit.
22+
- run: git checkout HEAD^2
23+
if: ${{ github.event_name == 'pull_request' }}
24+
25+
- name: Initialize CodeQL
26+
uses: github/codeql-action/init@v4
27+
with:
28+
languages: csharp
29+
30+
- name: Setup .NET Core
31+
uses: actions/setup-dotnet@v5
32+
with:
33+
dotnet-version: 10.0.x
34+
35+
- name: Build
36+
run: dotnet build Exceptionless.RandomData.slnx --configuration Release
37+
38+
- name: Perform CodeQL Analysis
39+
uses: github/codeql-action/analyze@v4
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: "Copilot Setup"
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
paths:
7+
- .github/workflows/copilot-setup-steps.yml
8+
pull_request:
9+
paths:
10+
- .github/workflows/copilot-setup-steps.yml
11+
12+
jobs:
13+
copilot-setup-steps:
14+
runs-on: ubuntu-latest
15+
16+
permissions:
17+
contents: read
18+
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v6
22+
23+
- name: Setup .NET Core
24+
uses: actions/setup-dotnet@v5
25+
with:
26+
dotnet-version: 10.0.x

AGENTS.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# Agent Guidelines for Exceptionless.RandomData
2+
3+
You are an expert .NET engineer working on Exceptionless.RandomData, a focused utility library for generating random data useful in unit tests and data seeding. The library provides methods for generating random integers, longs, doubles, decimals, booleans, strings, words, sentences, paragraphs, dates, enums, IP addresses, versions, and coordinates. It also includes an `EnumerableExtensions.Random<T>()` extension method to pick a random element from any collection.
4+
5+
**Craftsmanship Mindset**: Every line of code should be intentional, readable, and maintainable. Write code you'd be proud to have reviewed by senior engineers. Prefer simplicity over cleverness. When in doubt, favor explicitness and clarity.
6+
7+
## Repository Overview
8+
9+
Exceptionless.RandomData provides random data generation utilities for .NET applications:
10+
11+
- **Numeric** - `GetInt`, `GetLong`, `GetDouble`, `GetDecimal` with optional min/max ranges
12+
- **Boolean** - `GetBool` with configurable probability (0-100%)
13+
- **String** - `GetString`, `GetAlphaString`, `GetAlphaNumericString` with configurable length and allowed character sets
14+
- **Text** - `GetWord`, `GetWords`, `GetTitleWords`, `GetSentence`, `GetParagraphs` with lorem ipsum-style words and optional HTML output
15+
- **DateTime** - `GetDateTime`, `GetDateTimeOffset`, `GetTimeSpan` with optional start/end ranges
16+
- **Enum** - `GetEnum<T>()` to pick a random enum value (constrained to `struct, Enum`)
17+
- **Network** - `GetIp4Address` for random IPv4 addresses
18+
- **Version** - `GetVersion` for random version strings with optional min/max
19+
- **Coordinate** - `GetCoordinate` for random lat/lng pairs
20+
- **Collection** - `EnumerableExtensions.Random<T>()` to pick a random element from any `IEnumerable<T>`
21+
22+
Design principles: **simplicity**, **thread safety** (uses `Random.Shared`), **cryptographic quality strings** (uses `RandomNumberGenerator`), **modern .NET features** (targeting net8.0/net10.0).
23+
24+
## Quick Start
25+
26+
```bash
27+
# Build
28+
dotnet build Exceptionless.RandomData.slnx
29+
30+
# Test
31+
dotnet run --project test/Exceptionless.RandomData.Tests -f net8.0
32+
33+
# Format code
34+
dotnet format Exceptionless.RandomData.slnx
35+
```
36+
37+
## Project Structure
38+
39+
```text
40+
src
41+
└── Exceptionless.RandomData
42+
└── RandomData.cs # All random data generation + EnumerableExtensions
43+
test
44+
└── Exceptionless.RandomData.Tests
45+
├── RandomDataTests.cs # Unit tests
46+
└── Properties
47+
└── AssemblyInfo.cs # Disables test parallelization
48+
```
49+
50+
## Coding Standards
51+
52+
### Style & Formatting
53+
54+
- Follow `.editorconfig` rules (file-scoped namespaces, K&R braces)
55+
- Follow [Microsoft C# conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
56+
- Use `String.`/`Int32.`/`Char.` for static method access per `.editorconfig` `dotnet_style_predefined_type_for_member_access = false`
57+
- Run `dotnet format` to auto-format code
58+
- Match existing file style; minimize diffs
59+
- No code comments unless necessary—code should be self-explanatory
60+
61+
### Code Quality
62+
63+
- Write complete, runnable code—no placeholders, TODOs, or `// existing code...` comments
64+
- Use modern C# features available in **net8.0/net10.0**
65+
- **Nullable reference types** are enabled—annotate nullability correctly, don't suppress warnings without justification
66+
- **ImplicitUsings** are enabled—don't add `using System;`, `using System.Collections.Generic;`, etc.
67+
- Follow SOLID, DRY principles; remove unused code and parameters
68+
- Clear, descriptive naming; prefer explicit over clever
69+
70+
### Modern .NET Idioms
71+
72+
- **Guard APIs**: Use `ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual()`, `ArgumentOutOfRangeException.ThrowIfLessThan()`, `ArgumentOutOfRangeException.ThrowIfGreaterThan()`, `ArgumentNullException.ThrowIfNull()`, `ArgumentException.ThrowIfNullOrEmpty()` instead of manual checks
73+
- **`Random.Shared`**: Use `Random.Shared` instead of `new Random()` for thread-safe random number generation
74+
- **`RandomNumberGenerator.Fill()`**: Use the static method instead of `RandomNumberGenerator.Create()` + disposal
75+
- **Collection expressions**: Use `[...]` syntax for array initialization
76+
- **`Span<T>`**: Use `stackalloc` and span-based APIs to avoid allocations in hot paths
77+
- **Expression-bodied members**: Use for single-expression methods
78+
- **`Math.Clamp`**: Use instead of separate `Math.Min`/`Math.Max` calls
79+
- **Generic constraints**: Use `where T : struct, Enum` instead of runtime `typeof(T).IsEnum` checks
80+
- **Pattern matching**: Use `is null` / `is not null` instead of `== null` / `!= null`
81+
82+
### Exceptions
83+
84+
- Use `ArgumentOutOfRangeException.ThrowIf*` guard APIs at method entry
85+
- Use `ArgumentException` for invalid arguments that don't fit range checks
86+
- Include parameter names via `nameof()` where applicable
87+
- Fail fast: throw exceptions immediately for invalid arguments
88+
89+
## Making Changes
90+
91+
### Before Starting
92+
93+
1. **Gather context**: Read `RandomData.cs` and the test file to understand the full scope
94+
2. **Research patterns**: Find existing usages of the code you're modifying
95+
3. **Understand completely**: Know the problem, side effects, and edge cases before coding
96+
4. **Plan the approach**: Choose the simplest solution that satisfies all requirements
97+
98+
### While Coding
99+
100+
- **Minimize diffs**: Change only what's necessary, preserve formatting and structure
101+
- **Preserve behavior**: Don't break existing functionality or change semantics unintentionally
102+
- **Build incrementally**: Run `dotnet build` after each logical change to catch errors early
103+
- **Test continuously**: Run tests frequently to verify correctness
104+
- **Match style**: Follow the patterns in surrounding code exactly
105+
106+
### Validation
107+
108+
Before marking work complete, verify:
109+
110+
1. **Builds successfully**: `dotnet build Exceptionless.RandomData.slnx` exits with code 0
111+
2. **All tests pass**: `dotnet run --project test/Exceptionless.RandomData.Tests -f net8.0` shows no failures
112+
3. **No new warnings**: Check build output for new compiler warnings (warnings are treated as errors)
113+
4. **API compatibility**: Public API changes are intentional and backward-compatible when possible
114+
5. **Breaking changes flagged**: Clearly identify any breaking changes for review
115+
116+
## Testing
117+
118+
### Framework
119+
120+
- **xUnit v3** with **Microsoft Testing Platform** as the test runner
121+
- Test parallelization is disabled via `Properties/AssemblyInfo.cs`
122+
123+
### Running Tests
124+
125+
```bash
126+
# All tests, both TFMs
127+
dotnet run --project test/Exceptionless.RandomData.Tests -f net8.0
128+
dotnet run --project test/Exceptionless.RandomData.Tests -f net10.0
129+
```
130+
131+
### Note on Namespace Conflict
132+
133+
The test project uses `<RootNamespace>Exceptionless.Tests</RootNamespace>` to avoid a namespace conflict where the xUnit v3 MTP source generator creates a namespace `Exceptionless.RandomData.Tests` that shadows the `Exceptionless.RandomData` class. The test code uses `using Xunit;` and references `RandomData.*` methods directly since the `Exceptionless` namespace is accessible from within `Exceptionless.Tests`.
134+
135+
## Resources
136+
137+
- [README.md](README.md) - Overview and usage examples
138+
- [NuGet Package](https://www.nuget.org/packages/Exceptionless.RandomData/)

README.md

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,103 @@
55
[![Discord](https://img.shields.io/discord/715744504891703319)](https://discord.gg/6HxgFCx)
66
[![Donate](https://img.shields.io/badge/donorbox-donate-blue.svg)](https://donorbox.org/exceptionless?recurring=true)
77

8-
Utility class to easily generate random data. This makes generating good unit test data a breeze!
8+
A utility library for generating random data in .NET. Makes generating realistic test data a breeze. Targets **net8.0** and **net10.0**.
99

10-
## Getting Started (Development)
10+
## Getting Started
1111

12-
[This package](https://www.nuget.org/packages/Exceptionless.RandomData/) can be installed via the [NuGet package manager](https://docs.nuget.org/consume/Package-Manager-Dialog). If you need help, please contact us via in-app support or [open an issue](https://github.com/exceptionless/Exceptionless.RandomData/issues/new). Were always here to help if you have any questions!
12+
[This package](https://www.nuget.org/packages/Exceptionless.RandomData/) can be installed via the [NuGet package manager](https://docs.nuget.org/consume/Package-Manager-Dialog). If you need help, please contact us via in-app support or [open an issue](https://github.com/exceptionless/Exceptionless.RandomData/issues/new). We're always here to help if you have any questions!
1313

14-
1. You will need to have [Visual Studio Code](https://code.visualstudio.com/) installed.
15-
2. Open the root folder.
14+
```
15+
dotnet add package Exceptionless.RandomData
16+
```
17+
18+
## Usage
19+
20+
All methods are on the static `RandomData` class in the `Exceptionless` namespace.
21+
22+
### Numbers
23+
24+
```csharp
25+
using Exceptionless;
26+
27+
int value = RandomData.GetInt(1, 100);
28+
long big = RandomData.GetLong(0, 1_000_000);
29+
double d = RandomData.GetDouble(0.0, 1.0);
30+
decimal m = RandomData.GetDecimal(1, 500);
31+
```
32+
33+
### Booleans
34+
35+
```csharp
36+
using Exceptionless;
37+
38+
bool coin = RandomData.GetBool();
39+
bool likely = RandomData.GetBool(chance: 80); // 80% chance of true
40+
```
41+
42+
### Strings
43+
44+
```csharp
45+
using Exceptionless;
46+
47+
string random = RandomData.GetString(minLength: 5, maxLength: 20);
48+
string alpha = RandomData.GetAlphaString(10, 10);
49+
string alphaNum = RandomData.GetAlphaNumericString(8, 16);
50+
```
1651

17-
## Using RandomData
52+
### Words, Sentences, and Paragraphs
1853

19-
Below is a small sample of what you can do, so check it out!
54+
```csharp
55+
using Exceptionless;
56+
57+
string word = RandomData.GetWord();
58+
string title = RandomData.GetTitleWords(minWords: 3, maxWords: 6);
59+
string sentence = RandomData.GetSentence(minWords: 5, maxWords: 15);
60+
string text = RandomData.GetParagraphs(count: 2, minSentences: 3, maxSentences: 10);
61+
string html = RandomData.GetParagraphs(count: 2, html: true);
62+
```
63+
64+
### Dates and Times
65+
66+
```csharp
67+
using Exceptionless;
68+
69+
DateTime date = RandomData.GetDateTime();
70+
DateTime recent = RandomData.GetDateTime(start: DateTime.UtcNow.AddDays(-30), end: DateTime.UtcNow);
71+
DateTimeOffset dto = RandomData.GetDateTimeOffset();
72+
TimeSpan span = RandomData.GetTimeSpan(min: TimeSpan.FromMinutes(1), max: TimeSpan.FromHours(2));
73+
```
74+
75+
### Enums
2076

2177
```csharp
22-
private int[] _numbers = new[] { 1, 2, 3, 4, 5 };
78+
using Exceptionless;
2379

24-
private enum _days {
25-
Monday,
26-
Tuesday
27-
}
80+
DayOfWeek day = RandomData.GetEnum<DayOfWeek>();
81+
```
82+
83+
### Network and Versioning
84+
85+
```csharp
86+
using Exceptionless;
87+
88+
string ip = RandomData.GetIp4Address(); // e.g. "192.168.4.12"
89+
string coord = RandomData.GetCoordinate(); // e.g. "45.123,-90.456"
90+
string version = RandomData.GetVersion("1.0", "5.0");
91+
```
92+
93+
### Pick Random from Collection
94+
95+
The `Random<T>()` extension method picks a random element from any `IEnumerable<T>`:
96+
97+
```csharp
98+
using Exceptionless;
2899

29-
int value = RandomData.GetInt(1, 5);
30-
// or
31-
value = _numbers.Random();
100+
int[] numbers = [1, 2, 3, 4, 5];
101+
int picked = numbers.Random();
32102

33-
var day = RandomData.GetEnum<_days>();
103+
string[] names = ["Alice", "Bob", "Charlie"];
104+
string? name = names.Random();
34105
```
35106

36107
## Thanks to all the people who have contributed

build/common.props

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
<Project>
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
57
<Product>Exceptionless RandomData Generator</Product>
68
<Description>Exceptionless RandomData Generator</Description>
79
<PackageProjectUrl>https://github.com/exceptionless/Exceptionless.RandomData</PackageProjectUrl>
@@ -10,9 +12,9 @@
1012
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
1113
<MinVerTagPrefix>v</MinVerTagPrefix>
1214

13-
<Copyright>Copyright (c) 2025 Exceptionless. All rights reserved.</Copyright>
15+
<Copyright>Copyright © $([System.DateTime]::Now.ToString(yyyy)) Exceptionless. All rights reserved.</Copyright>
1416
<Authors>Exceptionless</Authors>
15-
<NoWarn>$(NoWarn);CS1591;NU1701</NoWarn>
17+
<NoWarn>$(NoWarn);CS1591</NoWarn>
1618
<WarningsAsErrors>true</WarningsAsErrors>
1719
<LangVersion>latest</LangVersion>
1820
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@@ -31,9 +33,9 @@
3133
</PropertyGroup>
3234

3335
<ItemGroup>
34-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
35-
<PackageReference Include="AsyncFixer" Version="1.6.0" PrivateAssets="All" />
36-
<PackageReference Include="MinVer" Version="5.0.0" PrivateAssets="All" />
36+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.103" PrivateAssets="All"/>
37+
<PackageReference Include="AsyncFixer" Version="2.1.0" PrivateAssets="All" />
38+
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="All" />
3739
</ItemGroup>
3840

3941
<ItemGroup>

global.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"test": {
3+
"runner": "Microsoft.Testing.Platform"
4+
}
5+
}

0 commit comments

Comments
 (0)