Skip to content

Commit a6b6826

Browse files
committed
Update documentation
1 parent 604430c commit a6b6826

File tree

4 files changed

+250
-140
lines changed

4 files changed

+250
-140
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ SharpAssert is a Pytest‑style assertions library for .NET
7373
- NEVER use Arrange/Act/Assert comments in tests. Instead, separate test sections with empty lines.
7474
- AVOID testing multiple independent behaviors in one test. Split complex tests into focused, single-behavior tests.
7575
- PREFER using message arguments in assertions to communicate what the assertion is testing instead of comments.
76+
- **CRITICAL**: Cross-platform compatibility - Use `CultureInfo.InvariantCulture` for DateTime formatting in error messages to ensure consistent output across macOS/Linux/Windows (e.g., `dt.ToString("M/d/yyyy", CultureInfo.InvariantCulture)`).
7677

7778
## TDD Cycle
7879

CONTRIBUTING.md

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ Thank you for your interest in contributing to SharpAssert! This guide will help
55
## 🚀 Quick Start
66

77
### Prerequisites
8-
- .NET 8.0 SDK or later
9-
- C# 12.0 compatible IDE (VS 2022 17.7+, Rider 2023.3+, VS Code)
8+
- .NET 9.0 SDK or later
9+
- C# 13.0 compatible IDE (VS 2022 17.12+, Rider 2024.3+, VS Code)
1010

1111
### Initial Setup
1212
```bash
@@ -28,11 +28,11 @@ SharpAssert/
2828
├── src/
2929
│ ├── SharpAssert.Runtime/ # Core assertion library with Assert methods
3030
│ ├── SharpAssert/ # MSBuild source rewriter + main package
31-
│ ├── SharpAssert.Tests/ # Unit tests for main library
32-
│ ├── SharpAssert.Rewriter.Tests/ # Unit tests for rewriter
31+
│ ├── SharpAssert.Tests/ # Unit tests for runtime and rewriter
3332
│ ├── SharpAssert.IntegrationTests/ # ★ Integration tests (MSBuild task)
3433
│ ├── SharpAssert.PackageTest/ # Package tests (local NuGet feed)
35-
│ └── SharpAssert.PowerAssertTest/ # PowerAssert forced mode tests
34+
│ ├── SharpAssert.PowerAssertTest/ # PowerAssert forced mode tests
35+
│ └── SharpAssert.Demo/ # Demo project showcasing features
3636
├── scripts/
3737
│ ├── dev-test.sh # Fast development testing
3838
│ ├── publish-local.sh # Publish to local feed
@@ -48,13 +48,14 @@ SharpAssert/
4848

4949
## 🧪 Testing Strategy
5050

51-
SharpAssert uses a comprehensive **four-layer testing approach** to ensure reliability and maintainability across all development phases:
51+
SharpAssert uses a comprehensive **four-layer testing approach** to ensure reliability and maintainability across all development phases.
52+
53+
> **For architectural details and implementation rationale, see [prd.md](prd.md) Section 9: Four-Layer Testing Strategy.**
5254
5355
### 1. **Unit Tests** - Component-Level Validation
54-
- **SharpAssert.Tests** - Tests core assertion logic without MSBuild integration
55-
- **SharpAssert.Rewriter.Tests** - Tests the source rewriter components in isolation
56+
- **SharpAssert.Tests** - Tests core assertion logic and rewriter components
5657
- **Purpose**: Fast feedback during development, tests individual methods and classes
57-
- **Run with**: `dotnet test src/SharpAssert.Tests/` or `dotnet test src/SharpAssert.Rewriter.Tests/`
58+
- **Run with**: `dotnet test src/SharpAssert.Tests/`
5859

5960
### 2. **Integration Tests** - MSBuild Task Validation (New!)
6061
- **SharpAssert.IntegrationTests** - Tests the rewriter as an actual MSBuild task during development
@@ -82,8 +83,8 @@ SharpAssert uses a comprehensive **four-layer testing approach** to ensure relia
8283
├─────────────────────────────────────────────────────────────┤
8384
│ SharpAssert.sln (Main Solution) │
8485
│ ├── SharpAssert.Tests (unit tests) │
85-
│ ├── SharpAssert.Rewriter.Tests (unit tests) │
8686
│ ├── SharpAssert.IntegrationTests (MSBuild task tests) ★ │
87+
│ ├── SharpAssert.Demo (feature showcase) │
8788
│ └── ... (main projects) │
8889
├─────────────────────────────────────────────────────────────┤
8990
│ SharpAssert.PackageTesting.sln (Package Isolation) │
@@ -171,8 +172,7 @@ dotnet pack SharpAssert/SharpAssert.csproj -p:VersionSuffix=local
171172
dotnet pack SharpAssert/SharpAssert.csproj -p:Version=1.2.3-beta
172173

173174
# Test the package
174-
cd SharpAssert.PackageTest
175-
./test-local-package.sh
175+
./test-local.sh
176176
```
177177

178178
## 🔧 Development Tips
@@ -202,9 +202,9 @@ dotnet test src/SharpAssert.PackageTest/ # Test with local packages
202202
The package testing solution includes **two specialized test projects**:
203203

204204
#### SharpAssert.PackageTest
205-
- Tests **basic functionality** via NuGet packages
205+
- Tests **basic functionality** via NuGet packages
206206
- Validates core assertions work through complete packaging pipeline
207-
- Uses normal SharpAssert configuration (with PowerAssert fallback)
207+
- Uses normal SharpAssert configuration
208208

209209
#### SharpAssert.PowerAssertTest
210210
- Tests **PowerAssert forced mode** via NuGet packages
@@ -279,13 +279,37 @@ Working on the MSBuild rewriter now has **two validation approaches**:
279279
- 🎯 **MSBuild task testing** - validates actual MSBuild integration
280280
- 🔧 **Easy debugging** - breakpoints work seamlessly
281281

282+
### Demo Project
283+
284+
The **SharpAssert.Demo** project provides a living documentation system for exploring and showcasing features:
285+
286+
```bash
287+
# Run all demos in console
288+
cd src/SharpAssert.Demo
289+
dotnet run
290+
291+
# Generate markdown documentation
292+
dotnet run -- --format markdown
293+
294+
# View specific feature category
295+
dotnet run -- --category "STRING COMPARISONS"
296+
```
297+
298+
**Purpose**:
299+
- ✅ Interactive feature exploration
300+
- ✅ Generate documentation examples
301+
- ✅ Verify rewriter behavior on real-world expressions
302+
- ✅ Test edge cases manually
303+
304+
See [SharpAssert.Demo/README.md](src/SharpAssert.Demo/README.md) for full documentation.
305+
282306
### Adding New Features
283307

284308
1. **Write failing test first** (TDD)
285309
2. **Implement minimal code** to pass the test
286310
3. **Refactor** if needed
287311
4. **Update integration tests** if behavior changes
288-
5. **Test the package** with `test-local-package.sh`
312+
5. **Test the package** with `./test-local.sh`
289313
6. **Update documentation** if adding public API
290314

291315
## 🐛 Debugging Issues

README.md

Lines changed: 175 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ Assert(items.Contains(target));
2828

2929
- **🔍 Detailed Expression Analysis** - See exactly why your assertions failed
3030
- **📦 Simple Setup** - Just add NuGet package, no MSBuild configuration needed
31-
- **🔄 PowerAssert Integration** - Complete support for PowerAssert (switch option)
31+
- **🎯 Exception Testing** - `Throws<T>` and `ThrowsAsync<T>` with detailed exception diagnostics
32+
- **🔤 String Diffs** - Character-level inline diffs for strings (powered by DiffPlex)
33+
- **📊 Collection Comparison** - First mismatch, missing/extra elements detection
34+
- **🔎 Object Deep Diff** - Property-level differences for objects/records (powered by Compare-Net-Objects)
35+
- **🔗 LINQ Operations** - Enhanced diagnostics for Contains/Any/All operations
36+
- **⚡ Async/Await Support** - Full support for async assertions with value diagnostics
37+
- **💫 Dynamic Types** - Dynamic expression support with DLR semantics
38+
- **🔄 PowerAssert Integration** - Optional PowerAssert mode
3239

3340
## Quick Start
3441

@@ -63,35 +70,111 @@ public void Should_be_equal()
6370
Assert(user.IsActive, $"User {user.Name} should be active for this operation");
6471
```
6572

66-
### Asserting exceptions
73+
## Features in Detail
74+
75+
### Exception Testing
76+
77+
Test expected exceptions with `Throws<T>` and `ThrowsAsync<T>`:
6778

6879
```csharp
69-
using static SharpAssert.Sharp;
80+
// Positive assertion - expects exception
81+
Assert(Throws<ArgumentException>(() => throw new ArgumentException("invalid")));
7082

71-
[Test]
72-
public async Task Throws_catch_exceptions_in_exception_result()
73-
{
74-
// Thows returns ExceptionResult which allows using them as condition in Assert
75-
Assert(Throws<ArgumentException>(()=> new ArgumentException("foo")));
76-
Assert(Throws<ArgumentException>(()=> new ArgumentNullException("bar"))); // will throw unexpected exception
77-
Assert(!Throws<ArgumentException>(()=> {})); // negative assertion via C# not syntax
83+
// Negative assertion - expects no exception
84+
Assert(!Throws<ArgumentException>(() => { /* no exception */ }));
7885

79-
var ex = Throws<ArgumentException>(()=> new ArgumentException("baz")); // always returns ExceptionResult
80-
Assert(ex.Exception.ArgumentName == "baz"); // get thrown exception and assert on any custom property
86+
// Access exception properties
87+
var ex = Throws<ArgumentNullException>(() => throw new ArgumentNullException("param"));
88+
Assert(ex.Message.Contains("param"));
8189

82-
Assert(Throws<ArgumentException>(()=>
83-
new ArgumentException("baz")).Data == "baz"); // shortcut form to assert on exception Data property
90+
// Async version
91+
Assert(await ThrowsAsync<InvalidOperationException>(() =>
92+
Task.Run(() => throw new InvalidOperationException())));
93+
```
8494

85-
Assert(Throws<ArgumentException>(()=>
86-
new ArgumentException("bar")).Message.Contains("bar")); // shortcut form to assert on exception Message
87-
88-
// async version
89-
Assert(await ThrowsAsync<ArgumentException>(()=>
90-
Task.Run(() => throw ArgumentException("async"))));
95+
### String Comparisons
9196

92-
var ex = ThrowsAsync<ArgumentException>(()=> Task.Run(() => throw ArgumentException("async"))); // always returns ExceptionResult
93-
Assert(ex.Message.Contains("async")); // assert on message using shortcut on ExceptionResult
94-
}
97+
Character-level diffs powered by DiffPlex:
98+
99+
```csharp
100+
var actual = "hello";
101+
var expected = "hallo";
102+
103+
Assert(actual == expected);
104+
// Assertion failed: actual == expected
105+
// String diff (inline):
106+
// h[-e][+a]llo
107+
```
108+
109+
Multiline string diffs:
110+
111+
```csharp
112+
var actual = "line1\nline2\nline3";
113+
var expected = "line1\nMODIFIED\nline3";
114+
115+
Assert(actual == expected);
116+
// Assertion failed: actual == expected
117+
// String diff:
118+
// line1
119+
// - line2
120+
// + MODIFIED
121+
// line3
122+
```
123+
124+
### Collection Comparisons
125+
126+
First mismatch and missing/extra elements:
127+
128+
```csharp
129+
var actual = new[] { 1, 2, 3, 5 };
130+
var expected = new[] { 1, 2, 4, 5 };
131+
132+
Assert(actual.SequenceEqual(expected));
133+
// Assertion failed: actual.SequenceEqual(expected)
134+
// Collections differ at index 2:
135+
// Expected: 4
136+
// Actual: 3
137+
```
138+
139+
### Object Deep Comparison
140+
141+
Property-level diffs powered by Compare-Net-Objects:
142+
143+
```csharp
144+
var actual = new User { Name = "John", Age = 30, City = "NYC" };
145+
var expected = new User { Name = "John", Age = 25, City = "LA" };
146+
147+
Assert(actual == expected);
148+
// Assertion failed: actual == expected
149+
// Object differences:
150+
// Age: 30 → 25
151+
// City: "NYC" → "LA"
152+
```
153+
154+
### LINQ Operations
155+
156+
Enhanced diagnostics for Contains, Any, All:
157+
158+
```csharp
159+
var users = new[] { "Alice", "Bob", "Charlie" };
160+
161+
Assert(users.Contains("David"));
162+
// Assertion failed: users.Contains("David")
163+
// Collection: ["Alice", "Bob", "Charlie"]
164+
// Looking for: "David"
165+
// Result: false
166+
```
167+
168+
### Async/Await Support
169+
170+
Full support for async expressions:
171+
172+
```csharp
173+
Assert(await client.GetAsync() == await server.GetAsync());
174+
// Assertion failed: await client.GetAsync() == await server.GetAsync()
175+
// Left: { Id: 1, Name: "Client" }
176+
// Right: { Id: 2, Name: "Server" }
177+
// Result: false
95178
```
96179

97180
## How It Works
@@ -124,39 +207,99 @@ Assert(order.Items.Length > 0 && order.Total == expectedTotal);
124207
SharpAssert is built on modern .NET technologies:
125208

126209
- **MSBuild Source Rewriting** - Compile-time code transformation
127-
- **Roslyn Syntax Analysis** - Advanced C# code parsing and generation
128-
- **Expression Trees** - Runtime expression analysis
210+
- **Roslyn Syntax Analysis** - Advanced C# code parsing and generation
211+
- **Expression Trees** - Runtime expression analysis for rich diagnostics
212+
- **DiffPlex** - String and sequence diffs
213+
- **CompareNETObjects** - Deep object comparison
214+
- **PowerAssert** - Optional alternative assertion engine
129215
- **CallerArgumentExpression** - Fallback for edge cases
130216

131-
### PowerAssert Integration
217+
### Requirements
218+
- .NET 9.0 or later
219+
- Test frameworks: xUnit, NUnit, or MSTest
220+
221+
### Packages
222+
SharpAssert consists of two NuGet packages:
223+
224+
- **SharpAssert** - Main package with MSBuild rewriter (install this one)
225+
- **SharpAssert.Runtime** - Core assertion library (automatically included as dependency)
226+
227+
When you install `SharpAssert`, you get everything you need. The runtime package is a transitive dependency and requires no separate installation.
228+
229+
## Configuration
230+
231+
### PowerAssert Mode
232+
233+
SharpAssert includes optional PowerAssert integration. You can force PowerAssert for all assertions via MSBuild property:
234+
235+
```xml
236+
<PropertyGroup>
237+
<!-- Force PowerAssert for ALL assertions (optional) -->
238+
<SharpAssertUsePowerAssert>true</SharpAssertUsePowerAssert>
239+
</PropertyGroup>
240+
```
132241

133-
SharpAssert includes PowerAssert integration and also uses it as a fallback mechanism for not yet implemented features.
242+
### Diagnostic Logging
134243

135-
To force PowerAssert for all assertions:
244+
Enable detailed rewriter diagnostics:
136245

137246
```xml
138247
<PropertyGroup>
139-
<UsePowerAssert>true</UsePowerAssert>
248+
<!-- Enable diagnostic logging for troubleshooting rewriter issues -->
249+
<SharpAssertEmitRewriteInfo>true</SharpAssertEmitRewriteInfo>
140250
</PropertyGroup>
141251
```
142252

143-
## Known issues
144-
-
145-
- Collection initializers could not be used in expression trees. Compiler limitation. Use `new[]{1,2,3}` instead of `[1, 2, 3]`
253+
## Performance
254+
255+
SharpAssert is designed for minimal overhead:
256+
257+
- **Passing tests**: Near-zero overhead - only the assertion check itself
258+
- **Failing tests**: Rich diagnostics are computed only when assertions fail
259+
- **Expression evaluation**: Each sub-expression evaluated exactly once (cached)
260+
- **Build time**: Negligible impact - rewriter processes only test files
261+
262+
The rich diagnostic tools (object diffing, collection comparison) are **only invoked on failure**.
263+
This means your passing tests run at full speed, and the diagnostic cost is only paid when you need to understand a failure.
264+
265+
## Known Issues
266+
267+
- Collection initializers cannot be used in expression trees (C# compiler limitation)
268+
- Use `new[]{1,2,3}` instead of `[1, 2, 3]`
269+
270+
## Live Examples
271+
272+
Want to see all features in action? Check out the **SharpAssert.Demo** project:
146273

274+
```bash
275+
cd src/SharpAssert.Demo
276+
dotnet run
277+
```
278+
279+
See [SharpAssert.Demo/README.md](src/SharpAssert.Demo/README.md) for interactive feature exploration and generated markdown documentation.
147280

148281
## Troubleshooting
149282

150283
### Rewriting not working
151284
1. Verify `SharpAssert` package is installed (SharpAssert.Runtime comes automatically)
152285
2. Ensure `using static SharpAssert.Sharp;` import
286+
3. Clean and rebuild: `dotnet clean && dotnet build`
153287

154288
### No detailed error messages
155289
1. Check build output contains: "SharpAssert: Rewriting X source files"
156290
2. Verify rewritten files exist in `obj/Debug/net9.0/SharpRewritten/`
157291
3. Ensure `SharpInternal.Assert` calls are being made (check generated code)
158292
4. Look for #line directives in generated files
159293

294+
### Enable diagnostic logging
295+
For troubleshooting rewriter issues:
296+
```xml
297+
<PropertyGroup>
298+
<SharpAssertEmitRewriteInfo>true</SharpAssertEmitRewriteInfo>
299+
</PropertyGroup>
300+
```
301+
Then rebuild with verbose output: `dotnet build -v detailed`
302+
160303
## Contributing
161304

162305
We welcome contributions! Please see our comprehensive [Contributing Guide](CONTRIBUTING.md) for:

0 commit comments

Comments
 (0)