Skip to content

Commit fe134c0

Browse files
committed
feat: Add release build configuration details to documentation and improve assertion handling
1 parent e30dfd2 commit fe134c0

File tree

9 files changed

+255
-33
lines changed

9 files changed

+255
-33
lines changed

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,53 @@ The `Evaluation.result` provides access to:
141141

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

144+
## Release Build Configuration
145+
146+
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.
147+
148+
**Default behavior:**
149+
- Debug build: assertions enabled
150+
- Release build (`dub build -b release` or `-release` flag): assertions disabled (no-op)
151+
152+
**Force enable in release builds:**
153+
154+
dub.sdl:
155+
```sdl
156+
versions "FluentAssertsDebug"
157+
```
158+
159+
dub.json:
160+
```json
161+
{
162+
"versions": ["FluentAssertsDebug"]
163+
}
164+
```
165+
166+
**Force disable in all builds:**
167+
168+
dub.sdl:
169+
```sdl
170+
versions "D_Disable_FluentAsserts"
171+
```
172+
173+
dub.json:
174+
```json
175+
{
176+
"versions": ["D_Disable_FluentAsserts"]
177+
}
178+
```
179+
180+
**Check at compile-time:**
181+
```D
182+
import fluent.asserts;
183+
184+
static if (fluentAssertsEnabled) {
185+
// assertions are active
186+
} else {
187+
// assertions are disabled (release build)
188+
}
189+
```
190+
144191
## Custom Assert Handler
145192

146193
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`:

docs/src/content/docs/guide/configuration.mdx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,75 @@ not ok - [1, 2, 3] should contain 5. 5 is missing from [1, 2, 3].
130130
...
131131
```
132132

133+
## Release Build Configuration
134+
135+
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.
136+
137+
### Default Behavior
138+
139+
| Build Type | Assertions |
140+
|------------|------------|
141+
| Debug (default) | Enabled |
142+
| Release (`-release` or `dub build -b release`) | Disabled (no-op) |
143+
144+
### Version Flags
145+
146+
You can override the default behavior using version flags.
147+
148+
**Force enable in release builds:**
149+
150+
import { Tabs, TabItem } from '@astrojs/starlight/components';
151+
152+
<Tabs>
153+
<TabItem label="dub.sdl">
154+
```sdl
155+
versions "FluentAssertsDebug"
156+
```
157+
</TabItem>
158+
<TabItem label="dub.json">
159+
```json
160+
{
161+
"versions": ["FluentAssertsDebug"]
162+
}
163+
```
164+
</TabItem>
165+
</Tabs>
166+
167+
**Force disable in all builds:**
168+
169+
<Tabs>
170+
<TabItem label="dub.sdl">
171+
```sdl
172+
versions "D_Disable_FluentAsserts"
173+
```
174+
</TabItem>
175+
<TabItem label="dub.json">
176+
```json
177+
{
178+
"versions": ["D_Disable_FluentAsserts"]
179+
}
180+
```
181+
</TabItem>
182+
</Tabs>
183+
184+
### Compile-Time Check
185+
186+
You can check at compile-time whether assertions are enabled:
187+
188+
```d
189+
import fluent.asserts;
190+
191+
static if (fluentAssertsEnabled) {
192+
// assertions are active
193+
writeln("Running with assertions enabled");
194+
} else {
195+
// assertions are disabled (release build)
196+
writeln("Assertions disabled for performance");
197+
}
198+
```
199+
200+
This is useful for conditionally including assertion-related code or logging.
201+
133202
## Next Steps
134203

135204
- Learn about [Core Concepts](/guide/core-concepts/)

docs/src/content/docs/guide/installation.mdx

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@ title: Installation
33
description: How to install fluent-asserts in your D project
44
---
55

6+
import { Tabs, TabItem } from '@astrojs/starlight/components';
7+
68
## Using DUB (Recommended)
79

810
The easiest way to use fluent-asserts is through [DUB](https://code.dlang.org/), the D package manager.
911

10-
### Add to dub.json
11-
12+
<Tabs>
13+
<TabItem label="dub.sdl">
14+
```sdl
15+
dependency "fluent-asserts" version="~>1.0.1"
16+
```
17+
</TabItem>
18+
<TabItem label="dub.json">
1219
```json
1320
{
1421
"dependencies": {
1522
"fluent-asserts": "~>1.0.1"
1623
}
1724
}
1825
```
19-
20-
### Or add to dub.sdl
21-
22-
```sdl
23-
dependency "fluent-asserts" version="~>1.0.1"
24-
```
26+
</TabItem>
27+
</Tabs>
2528

2629
Then run:
2730

@@ -75,6 +78,12 @@ dub test
7578
dub test --compiler=ldc2
7679
```
7780

81+
## Release Builds
82+
83+
By default, fluent-asserts assertions are disabled in release builds (similar to D's built-in `assert`). This means you can use fluent-asserts in production code without runtime overhead in release builds.
84+
85+
See [Configuration](/guide/configuration/#release-build-configuration) for details on how to control this behavior.
86+
7887
## Integration with Test Frameworks
7988

8089
fluent-asserts integrates seamlessly with D test frameworks.
@@ -85,6 +94,14 @@ fluent-asserts integrates seamlessly with D test frameworks.
8594

8695
Add trial to your project:
8796

97+
<Tabs>
98+
<TabItem label="dub.sdl">
99+
```sdl
100+
dependency "fluent-asserts" version="~>1.0.1"
101+
dependency "trial" version="~>0.8.0-beta.7"
102+
```
103+
</TabItem>
104+
<TabItem label="dub.json">
88105
```json
89106
{
90107
"dependencies": {
@@ -93,6 +110,8 @@ Add trial to your project:
93110
}
94111
}
95112
```
113+
</TabItem>
114+
</Tabs>
96115

97116
Run your tests with:
98117

docs/src/content/docs/guide/introduction.mdx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,18 @@ The `Evaluation.result` provides access to:
101101
- `missing` - array of missing elements (for collection comparisons)
102102
- `extra` - array of extra elements (for collection comparisons)
103103

104+
## Release Builds
105+
106+
Like D's built-in `assert`, fluent-asserts assertions are disabled in release builds. This means you can use them in production code without runtime overhead:
107+
108+
```d
109+
// In debug builds: assertion is checked
110+
// In release builds: this is a no-op
111+
expect(value).to.be.greaterThan(0);
112+
```
113+
114+
See [Configuration](/guide/configuration/#release-build-configuration) for how to control this behavior.
115+
104116
## Custom Assert Handler
105117

106118
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`:

source/fluent/asserts.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module fluent.asserts;
22

33
public import fluentasserts.core.base;
4+
public import fluentasserts.core.config : fluentAssertsEnabled;
45

56
version(Have_fluent_asserts_vibe) {
67
public import fluentasserts.vibe.json;

source/fluentasserts/core/config.d

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,39 @@ enum OutputFormat {
1111
tap
1212
}
1313

14+
/// Compile-time check for whether assertions are enabled.
15+
///
16+
/// By default, assertions are enabled in debug builds and disabled in release builds.
17+
/// This allows using fluent-asserts as a replacement for D's built-in assert
18+
/// while maintaining the same release-build behavior.
19+
///
20+
/// Build configurations:
21+
/// - Debug build (default): assertions enabled
22+
/// - Release build (`-release` or `dub build -b release`): assertions disabled (no-op)
23+
/// - Force disable: add version `D_Disable_FluentAsserts` to disable even in debug
24+
/// - Force enable in release: add version `FluentAssertsDebug` to enable in release builds
25+
///
26+
/// Example dub.sdl configuration to force enable in release:
27+
/// ---
28+
/// versions "FluentAssertsDebug"
29+
/// ---
30+
///
31+
/// Example dub.sdl configuration to always disable:
32+
/// ---
33+
/// versions "D_Disable_FluentAsserts"
34+
/// ---
35+
version (D_Disable_FluentAsserts) {
36+
enum fluentAssertsEnabled = false;
37+
} else version (release) {
38+
version (FluentAssertsDebug) {
39+
enum fluentAssertsEnabled = true;
40+
} else {
41+
enum fluentAssertsEnabled = false;
42+
}
43+
} else {
44+
enum fluentAssertsEnabled = true;
45+
}
46+
1447
/// Singleton configuration struct for fluent-asserts.
1548
/// Provides centralized access to all configurable settings.
1649
struct FluentAssertsConfig {

source/fluentasserts/core/expect.d

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import fluentasserts.operations.comparison.approximately : approximatelyOp = app
2929
import fluentasserts.operations.exception.throwable : throwAnyExceptionOp = throwAnyException, throwExceptionOp = throwException, throwAnyExceptionWithMessageOp = throwAnyExceptionWithMessage, throwExceptionWithMessageOp = throwExceptionWithMessage, throwSomethingOp = throwSomething, throwSomethingWithMessageOp = throwSomethingWithMessage;
3030
import fluentasserts.operations.memory.gcMemory : allocateGCMemoryOp = allocateGCMemory;
3131
import fluentasserts.operations.memory.nonGcMemory : allocateNonGCMemoryOp = allocateNonGCMemory;
32-
import fluentasserts.core.config : config = FluentAssertsConfig;
32+
import fluentasserts.core.config : config = FluentAssertsConfig, fluentAssertsEnabled;
3333

3434
import std.datetime : Duration, SysTime;
3535

@@ -67,6 +67,13 @@ string truncateForMessage(const(char)[] value) @trusted nothrow {
6767
private {
6868
Evaluation _evaluation;
6969
int refCount;
70+
bool _initialized;
71+
}
72+
73+
/// Returns true if this Expect was properly initialized.
74+
/// Used to skip processing for no-op assertions in release builds.
75+
bool isInitialized() const @nogc nothrow {
76+
return _initialized;
7077
}
7178

7279
/// Returns a reference to the underlying evaluation.
@@ -79,6 +86,7 @@ string truncateForMessage(const(char)[] value) @trusted nothrow {
7986
/// Initializes the evaluation state and sets up the initial message.
8087
/// Source parsing is deferred until assertion failure for performance.
8188
this(ValueEvaluation value) @trusted {
89+
_initialized = true;
8290
_evaluation.id = Lifecycle.instance.beginEvaluation(value);
8391
_evaluation.currentValue = value;
8492
_evaluation.source = SourceResult.create(value.fileName[].idup, value.line);
@@ -105,12 +113,18 @@ string truncateForMessage(const(char)[] value) @trusted nothrow {
105113
/// Increments the source's refCount so only the last copy triggers finalization.
106114
this(ref return scope Expect another) @trusted nothrow {
107115
this._evaluation = another._evaluation;
116+
this._initialized = another._initialized;
108117
this.refCount = 0; // New copy starts with 0
109118
another.refCount++; // Prevent source from finalizing
110119
}
111120

112121
/// Destructor. Finalizes the evaluation when reference count reaches zero.
122+
/// Does nothing if the Expect was never initialized (e.g., in release builds).
113123
~this() {
124+
if (!_initialized) {
125+
return;
126+
}
127+
114128
refCount--;
115129

116130
if(refCount < 0) {
@@ -536,31 +550,36 @@ string truncateForMessage(const(char)[] value) @trusted nothrow {
536550

537551
/// Creates an Expect from a callable delegate.
538552
/// Executes the delegate and captures any thrown exception.
553+
/// In release builds (unless FluentAssertsDebug is set), this is a no-op.
539554
Expect expect(void delegate() callable, const string file = __FILE__, const size_t line = __LINE__, string prependText = null) @trusted {
540-
ValueEvaluation value;
541-
value.typeNames.put("callable");
542-
543-
try {
544-
if(callable !is null) {
545-
callable();
546-
} else {
547-
value.typeNames.clear();
548-
value.typeNames.put("null");
555+
static if (!fluentAssertsEnabled) {
556+
return Expect.init;
557+
} else {
558+
ValueEvaluation value;
559+
value.typeNames.put("callable");
560+
561+
try {
562+
if(callable !is null) {
563+
callable();
564+
} else {
565+
value.typeNames.clear();
566+
value.typeNames.put("null");
567+
}
568+
} catch(Exception e) {
569+
value.throwable = e;
570+
value.meta["Exception"] = "yes";
571+
} catch(Throwable t) {
572+
value.throwable = t;
573+
value.meta["Throwable"] = "yes";
549574
}
550-
} catch(Exception e) {
551-
value.throwable = e;
552-
value.meta["Exception"] = "yes";
553-
} catch(Throwable t) {
554-
value.throwable = t;
555-
value.meta["Throwable"] = "yes";
556-
}
557575

558-
value.fileName = toHeapString(file);
559-
value.line = line;
560-
value.prependText = toHeapString(prependText);
576+
value.fileName = toHeapString(file);
577+
value.line = line;
578+
value.prependText = toHeapString(prependText);
561579

562-
auto result = Expect(value);
563-
return result;
580+
auto result = Expect(value);
581+
return result;
582+
}
564583
}
565584

566585
/// Creates an Expect struct from a lazy value.
@@ -570,7 +589,12 @@ Expect expect(void delegate() callable, const string file = __FILE__, const size
570589
/// line = Source line (auto-filled)
571590
/// prependText = Optional text to prepend to the value display
572591
/// Returns: An Expect struct for fluent assertions
592+
/// In release builds (unless FluentAssertsDebug is set), this is a no-op.
573593
Expect expect(T)(lazy T testedValue, const string file = __FILE__, const size_t line = __LINE__, string prependText = null) @trusted {
574-
auto result = Expect(testedValue.evaluate(file, line, prependText).evaluation);
575-
return result;
594+
static if (!fluentAssertsEnabled) {
595+
return Expect.init;
596+
} else {
597+
auto result = Expect(testedValue.evaluate(file, line, prependText).evaluation);
598+
return result;
599+
}
576600
}

0 commit comments

Comments
 (0)