Skip to content
Merged

V2 #105

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
2475936
Refactor assertion operations to utilize result objects for improved …
gedaiu Dec 1, 2025
6f129e6
Refactor error message assertions in unit tests for string operations
gedaiu Dec 2, 2025
ffff43c
feat: Enhance serialization utilities and add source code analysis
gedaiu Dec 2, 2025
142c17b
Refactor and add new tests for string and type operations
gedaiu Dec 2, 2025
531ebbd
Remove obsolete test files and directories
gedaiu Dec 2, 2025
a360889
feat: Enhance lifecycle management and failure handling in assertions
gedaiu Dec 2, 2025
7b566d3
Enhance error reporting in unit tests and improve lifecycle handling
gedaiu Dec 2, 2025
71425ec
feat: Add recordEvaluation function for capturing assertion results
gedaiu Dec 2, 2025
e503714
feat: Add custom failure handler support and enhance evaluation resul…
gedaiu Dec 2, 2025
ef68c94
feat: Improve documentation for comparison and assertion functions
gedaiu Dec 2, 2025
a2a183a
feat: Add contributing guidelines with improvement suggestions
gedaiu Dec 2, 2025
71f2f4e
Refactor string assertions and improve exception handling
gedaiu Dec 3, 2025
b90e567
Refactor string assertions and enhance error reporting.
gedaiu Dec 4, 2025
d00ef8f
feat: Add operation snapshots and enhance negation handling in assert…
gedaiu Dec 6, 2025
2368469
refactor: Simplify cleanMixinPath function by removing line number pa…
gedaiu Dec 6, 2025
77d63b3
feat: Enhance memory tracking in evaluations and add new snapshot cas…
gedaiu Dec 6, 2025
18c8c84
feat: Add memory allocation tracking for callables and enhance relate…
gedaiu Dec 7, 2025
e94626f
feat: Implement non-GC memory allocation tracking and enhance related…
gedaiu Dec 7, 2025
a6eb25b
feat: Add introduction and philosophy documentation for fluent-asserts
gedaiu Dec 7, 2025
6ee6f55
fix: Rename mallinfo struct to mallinfo_t for clarity in Linux memory…
gedaiu Dec 7, 2025
a250146
fix: Cast value to void in evaluate function to suppress unused resul…
gedaiu Dec 7, 2025
4b2dc4b
feat: Enhance non-GC memory allocation detection with a 4KB threshold
gedaiu Dec 7, 2025
dcf34bf
fix: Update assertion to verify message format for non-GC memory allo…
gedaiu Dec 7, 2025
83ef79d
fix: Improve documentation for GC and non-GC memory allocation assert…
gedaiu Dec 7, 2025
4c9f4d4
fix: Update documentation for GC and non-GC memory assertions to clar…
gedaiu Dec 7, 2025
de6dd49
fix: Update titles in documentation for consistency with fluent asser…
gedaiu Dec 8, 2025
7804cd4
fix: Add @nogc attribute to various functions for improved memory man…
gedaiu Dec 8, 2025
92f5210
fix: Add @trusted and @safe attributes to serializer functions for im…
gedaiu Dec 9, 2025
3ccced1
fix: Add @safe and @nogc attributes to inhibit and getSerialized meth…
gedaiu Dec 9, 2025
9c75bd4
fix: Refactor Expect struct to use direct Evaluation instance for imp…
gedaiu Dec 9, 2025
98797fe
fix: Refactor Evaluation and AssertResult structures for improved ope…
gedaiu Dec 9, 2025
8e9470c
fix: Enhance safety and memory management by adding @safe, @trusted, …
gedaiu Dec 9, 2025
589d2bc
fix: Simplify equality check in ObjectEquable class by consolidating …
gedaiu Dec 9, 2025
04ba0ea
Refactor assertion result handling and improve string management.
gedaiu Dec 10, 2025
54cc2ab
fix: Enhance memory management by adding @nogc attributes and refacto…
gedaiu Dec 10, 2025
0d38d26
fix: Add @nogc attributes to improve memory management in arrayEqual …
gedaiu Dec 11, 2025
026749e
fix: Add @nogc attributes and refactor null checks in beNull and inst…
gedaiu Dec 11, 2025
4982ae5
fix: Add @nogc attributes and refactor string checks in endWith and s…
gedaiu Dec 11, 2025
7ce82f8
fix: Refactor contain function for improved negation handling and mat…
gedaiu Dec 11, 2025
edd536b
fix: Add @nogc attributes to countMatches and appendValueList functio…
gedaiu Dec 11, 2025
2c9ccd4
fix: Add @nogc attributes to comparison functions for improved memory…
gedaiu Dec 11, 2025
2c923b0
fix: Refactor containsSubstring function for improved substring match…
gedaiu Dec 11, 2025
9eeb012
fix: Replace containsSubstring function with canFind for improved sub…
gedaiu Dec 11, 2025
c74139d
fix: Use idup for string handling in non-GC memory allocation tests
gedaiu Dec 12, 2025
3e668f2
fix: Update expected and actual result formatting for better clarity …
gedaiu Dec 12, 2025
76977f9
fix: Update assertion to check for memory allocation ending with 'MB'…
gedaiu Dec 12, 2025
ebdc769
fix: Integrate toNumeric for improved value parsing in lessThan asser…
gedaiu Dec 12, 2025
41e0b15
fix: Refactor comparison functions to use toNumeric for improved nume…
gedaiu Dec 12, 2025
d8e5bdf
fix: Update delta value in approximately unit tests for improved prec…
gedaiu Dec 12, 2025
cd387b9
feat: Implement FixedArray and HeapData structures for efficient memo…
gedaiu Dec 13, 2025
f861c73
fix: Refactor parsing and string handling for improved memory managem…
gedaiu Dec 13, 2025
3f6e9c3
feat: Add @nogc support for evaluator operations and string containme…
gedaiu Dec 13, 2025
7c43190
feat: Add @nogc attribute to lessThanGeneric function for improved me…
gedaiu Dec 13, 2025
1e4a6f4
feat: Enhance Evaluation and HeapData with new features for improved …
gedaiu Dec 13, 2025
4a7255b
Refactor comparison operations to use array syntax for string values
gedaiu Dec 15, 2025
7263782
feat: Enhance HeapData with small buffer optimization and concatenati…
gedaiu Dec 16, 2025
f4aff0d
feat: Refactor ValueEvaluation and Evaluation structs for improved me…
gedaiu Dec 16, 2025
d3a318d
feat: Disable postblit to enforce copy constructor usage for improved…
gedaiu Dec 16, 2025
6308590
feat: Remove @nogc from between function to allow garbage collection
gedaiu Dec 16, 2025
74f7cf5
feat: Enhance message handling with HeapString for improved memory ma…
gedaiu Dec 16, 2025
acdd1ae
Add HeapData structure for heap-allocated dynamic arrays
gedaiu Dec 16, 2025
1ed5daf
feat: Enhance memory management and copy semantics in evaluation and …
gedaiu Dec 16, 2025
55b3860
feat: Refactor type name handling for improved memory management and …
gedaiu Dec 17, 2025
2ab03c3
Refactor fluent-asserts to use HeapEquableValue for memory management…
gedaiu Dec 19, 2025
9402d08
feat(evaluation): Introduce Evaluation struct and related evaluation …
gedaiu Dec 20, 2025
bea6b39
Refactor comparison operations for improved clarity and consistency
gedaiu Dec 21, 2025
01871c2
feat(evaluation): Enhance evaluation logic with duration and SysTime …
gedaiu Dec 21, 2025
f1edfd9
refactor: Update import paths for memory management and evaluation mo…
gedaiu Dec 21, 2025
5243d43
refactor: Remove unused imports and streamline evaluation error handling
gedaiu Dec 21, 2025
9864f42
refactor: Remove deprecated operations from lifecycle management
gedaiu Dec 22, 2025
e150887
feat: Implement source code context extraction for fluent-asserts
gedaiu Dec 22, 2025
dfa2b3c
Refactor numeric conversion: Move to conversion module
gedaiu Dec 22, 2025
afbe11f
Add array containment assertions and related utilities
gedaiu Dec 23, 2025
08782f5
feat: Implement Myers diff algorithm and related utilities
gedaiu Dec 24, 2025
9c0b683
feat(snapshot): enhance snapshot generation and normalization
gedaiu Dec 24, 2025
75fad53
feat(snapshot): enhance snapshot normalization and add comprehensive …
gedaiu Dec 24, 2025
d5d5a1b
feat(memory): add memory allocation assertions and documentation
gedaiu Dec 24, 2025
0dc6a86
feat: Introduce centralized configuration for fluent-asserts
gedaiu Dec 25, 2025
b5a39b4
feat: Refactor assertion messages for clarity and performance improve…
gedaiu Dec 25, 2025
0d51961
feat: Enhance EvaluationResult and HeapEquableValue for improved type…
gedaiu Dec 25, 2025
86550f2
feat: Enhance numeric comparison in HeapEquableValue and add unit tes…
gedaiu Dec 25, 2025
b058ecf
feat: Implement opEquals for Thing class and add unit tests for equal…
gedaiu Dec 25, 2025
e30dfd2
feat: Add unit tests for object and nested array equality and contain…
gedaiu Dec 25, 2025
fe134c0
feat: Add release build configuration details to documentation and im…
gedaiu Dec 25, 2025
931c7e4
feat: Add unittest for lessThan with std.checkedint.Checked
gedaiu Dec 25, 2025
ece698f
feat: Enhance parseDouble to support scientific notation and improve …
gedaiu Dec 25, 2025
3f92f66
feat: Implement ensureLifecycle to initialize Lifecycle singleton and…
gedaiu Dec 25, 2025
c4dd76e
feat: Add unittests to ensure opEquals is honored when asserting equa…
gedaiu Dec 25, 2025
aff8a63
feat: Add unittests for Object[] and nested arrays to ensure proper e…
gedaiu Dec 25, 2025
3cd68d8
feat: Update findOpenParen to handle extra whitespace in Assert calls…
gedaiu Dec 25, 2025
392f7b6
feat: Add unittests for Assert.greaterOrEqualTo and Assert.lessOrEqua…
gedaiu Dec 25, 2025
34bf034
feat: Implement assertion statistics tracking for monitoring test beh…
gedaiu Dec 25, 2025
d191153
feat: Add unittests for handling std.container.array ranges with @sys…
gedaiu Dec 25, 2025
ca8f0aa
feat: Add unittests for handling std.range.interfaces.InputRange in s…
gedaiu Dec 25, 2025
3718c36
feat: Add unittests for handling range of ranges in equal and contain…
gedaiu Dec 25, 2025
130bf37
feat: Add unittest for throwException to verify correct source locati…
gedaiu Dec 25, 2025
1886b65
feat: Add context data support and formatted messages for assertions
gedaiu Dec 25, 2025
ed37ff1
feat: Enhance context handling in assertions with overflow management
gedaiu Dec 25, 2025
f4edf2b
feat: Upgrade fluent-asserts to v2.0 with major architectural changes
gedaiu Dec 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Deploy Documentation

on:
push:
branches: [master]
paths:
- 'docs/**'
- 'source/**'
- '.github/workflows/docs.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # Needed for git tags

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
cache-dependency-path: docs/package-lock.json

- name: Install dependencies
working-directory: docs
run: npm ci

- name: Update version info
working-directory: docs
run: npm run update-version

- name: Extract API docs from D source
working-directory: docs
run: npm run extract-docs

- name: Build documentation site
working-directory: docs
run: npm run build

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/dist

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
.dub
docs.json
__dummy.html

# Documentation site (Starlight/Astro)
docs/node_modules/
docs/dist/
docs/.astro/
docs/public/version.json
*.o
*.obj
*.lst
Expand Down
217 changes: 190 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ expect(testedValue).to.not.equal(42);
/// will output this message: Because of test reasons, true should equal `false`.
```

`because` also supports format strings for dynamic messages:

```D
foreach (i; 0..100) {
result.should.equal(expected).because("at iteration %s", i);
}
```

`withContext` attaches key-value debugging data:

```D
result.should.equal(expected)
.withContext("userId", 42)
.withContext("input", testInput);
/// On failure, displays: CONTEXT: userId = 42, input = ...
```

## Should

Expand Down Expand Up @@ -109,53 +125,190 @@ just add `not` before the assert name:
Assert.notEqual(testedValue, 42);
```

## Recording Evaluations

The `recordEvaluation` function allows you to capture the result of an assertion without throwing an exception on failure. This is useful for testing assertion behavior itself, or for inspecting the evaluation result programmatically.

```D
import fluentasserts.core.lifecycle : recordEvaluation;

unittest {
auto evaluation = ({
expect(5).to.equal(10);
}).recordEvaluation;

// Inspect the evaluation result
assert(evaluation.result.expected == "10");
assert(evaluation.result.actual == "5");
}
```

The function:
1. Takes a delegate containing the assertion to execute
2. Temporarily disables failure handling so the test doesn't abort
3. Returns the `Evaluation` struct containing the result

The `Evaluation.result` provides access to:
- `expected` - the expected value as a string
- `actual` - the actual value as a string
- `negated` - whether the assertion was negated with `.not`
- `missing` - array of missing elements (for collection comparisons)
- `extra` - array of extra elements (for collection comparisons)

This is particularly useful when writing tests for custom assertion operations or when you need to verify that assertions produce the correct error messages.

## Assertion Statistics

fluent-asserts tracks assertion counts for monitoring test behavior:

```D
import fluentasserts.core.lifecycle : Lifecycle;

// Run some assertions
expect(1).to.equal(1);
expect("hello").to.contain("ell");

// Access statistics
auto stats = Lifecycle.instance.statistics;
writeln("Total: ", stats.totalAssertions);
writeln("Passed: ", stats.passedAssertions);
writeln("Failed: ", stats.failedAssertions);

// Reset statistics
Lifecycle.instance.resetStatistics();
```

The `AssertionStatistics` struct contains:
- `totalAssertions` - Total number of assertions executed
- `passedAssertions` - Number of assertions that passed
- `failedAssertions` - Number of assertions that failed
- `reset()` - Resets all counters to zero

## Release Build Configuration

By default, fluent-asserts behaves like D's built-in `assert`: assertions are enabled in debug builds and disabled (become no-ops) in release builds. This allows you to use fluent-asserts as a replacement for `assert` in your production code without any runtime overhead in release builds.

**Default behavior:**
- Debug build: assertions enabled
- Release build (`dub build -b release` or `-release` flag): assertions disabled (no-op)

**Force enable in release builds:**

dub.sdl:
```sdl
versions "FluentAssertsDebug"
```

dub.json:
```json
{
"versions": ["FluentAssertsDebug"]
}
```

**Force disable in all builds:**

dub.sdl:
```sdl
versions "D_Disable_FluentAsserts"
```

dub.json:
```json
{
"versions": ["D_Disable_FluentAsserts"]
}
```

**Check at compile-time:**
```D
import fluent.asserts;

static if (fluentAssertsEnabled) {
// assertions are active
} else {
// assertions are disabled (release build)
}
```

## Custom Assert Handler

During unittest builds, the library automatically installs a custom handler for D's built-in `assert` statements. This provides fluent-asserts style error messages even when using standard `assert`:

```D
unittest {
assert(1 == 2, "math is broken");
// Output includes ACTUAL/EXPECTED formatting and source location
}
```

The handler is only active during `version(unittest)` builds, so it won't affect release builds. It is installed using `pragma(crt_constructor)`, which runs before druntime initialization. This approach avoids cyclic module dependency issues that would occur with `static this()`.

If you need to temporarily disable this handler during tests:

```D
import core.exception;

// Save and restore the handler
auto savedHandler = core.exception.assertHandler;
scope(exit) core.exception.assertHandler = savedHandler;

// Disable fluent handler
core.exception.assertHandler = null;
```

## Built in operations

- [above](api/above.md)
- [approximately](api/approximately.md)
- [beNull](api/beNull.md)
- [below](api/below.md)
- [between](api/between.md)
- [contain](api/contain.md)
- [containOnly](api/containOnly.md)
- [endWith](api/endWith.md)
- [equal](api/equal.md)
- [greaterOrEqualTo](api/greaterOrEqualTo.md)
- [greaterThan](api/greaterThan.md)
- [instanceOf](api/instanceOf.md)
- [lessOrEqualTo](api/lessOrEqualTo.md)
- [lessThan](api/lessThan.md)
- [startWith](api/startWith.md)
- [throwAnyException](api/throwAnyException.md)
- [throwException](api/throwException.md)
- [throwSomething](api/throwSomething.md)
- [withMessage](api/withMessage.md)
- [within](api/within.md)


### Memory Assertions

The library provides assertions for checking memory allocations:

```D
// Check GC allocations
({ auto arr = new int[100]; }).should.allocateGCMemory();
({ int x = 5; }).should.not.allocateGCMemory();

// Check non-GC allocations (malloc, etc.)
({
import core.stdc.stdlib : malloc, free;
auto p = malloc(1024);
free(p);
}).should.allocateNonGCMemory();
```

**Note:** Non-GC memory measurement uses process-wide metrics (`mallinfo` on Linux, `phys_footprint` on macOS). This is inherently unreliable during parallel test execution because allocations from other threads are included. For accurate non-GC memory testing, run tests single-threaded with `dub test -- -j1`.

# Extend the library

## Registering new operations

Even though this library has an extensive set of operations, sometimes a new operation might be needed to test your code. Operations are functions that recieve an `Evaluation` and returns an `IResult` list in case there was a failure. You can check any of the built in operations for a refference implementation.
Even though this library has an extensive set of operations, sometimes a new operation might be needed to test your code. Operations are functions that receive an `Evaluation` and modify it to indicate success or failure. The operation sets the `expected` and `actual` fields on `evaluation.result` when there is a failure. You can check any of the built in operations for a reference implementation.

```d
IResult[] customOperation(ref Evaluation evaluation) @safe nothrow {
...
void customOperation(ref Evaluation evaluation) @safe nothrow {
// Perform your check
bool success = /* your logic */;

if (!success) {
evaluation.result.expected = "expected value description";
evaluation.result.actual = "actual value description";
}
}
```

Once the operation is ready to use, it has to be registered with the global registry:

```d
static this() {
/// bind the type to different matchers
// bind the type to different matchers
Registry.instance.register!(SysTime, SysTime)("between", &customOperation);
Registry.instance.register!(SysTime, SysTime)("within", &customOperation);

/// or use * to match any type
// or use * to match any type
Registry.instance.register("*", "*", "customOperation", &customOperation);
}

```

## Registering new serializers
Expand All @@ -164,14 +317,24 @@ In order to setup an `Evaluation`, the actual and expected values need to be con

```d
static this() {
SerializerRegistry.instance.register(&jsonToString);
HeapSerializerRegistry.instance.register(&jsonToString);
}

string jsonToString(Json value) {
/// you can add here your custom serializer for Jsons
}
```

# Contributing

Areas for potential improvement:

- **Reduce Evaluator duplication** - `Evaluator`, `TrustedEvaluator`, and `ThrowableEvaluator` share similar code that could be consolidated with templates or mixins.
- **Simplify the Registry** - The type generalization logic could benefit from clearer naming or documentation.
- **Remove ddmp dependency** - For simpler diffs or no diffs, removing the ddmp dependency would simplify the build.
- **Consistent error messages** - Standardize error message patterns across operations for more predictable output.
- **Make source extraction optional** - Source code tokenization runs on every assertion; making it opt-in could improve performance.
- **GC allocation optimization** - Several hot paths use string/array concatenation that could be optimized with `Appender` or pre-allocation.

# License

Expand Down
Loading