or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collections.mdconcurrency.mdcore-dsl.mddatetime.mdfilesystem.mdindex.mdnondeterministic.mdprimitives.mdreflection.mdresult.mdstrings.mdthrowable.mdtuples.mdtypes.md

nondeterministic.mddocs/

0

# Nondeterministic Testing

1

2

Support for testing asynchronous and eventually consistent systems with configurable retry strategies and timing controls.

3

4

## Capabilities

5

6

### Eventually Functions

7

8

Test assertions that may initially fail but should eventually succeed within a time limit.

9

10

```kotlin { .api }

11

/**

12

* Repeatedly execute test until it passes or timeout is reached

13

* Uses default configuration (5 seconds duration, 25ms interval)

14

* @param test The test code to execute repeatedly

15

* @return The result of the successful test execution

16

*/

17

suspend fun <T> eventually(test: suspend () -> T): T

18

19

/**

20

* Repeatedly execute test until it passes or specified duration elapses

21

* @param duration Maximum time to keep retrying

22

* @param test The test code to execute repeatedly

23

* @return The result of the successful test execution

24

*/

25

suspend fun <T> eventually(duration: Duration, test: suspend () -> T): T

26

27

/**

28

* Repeatedly execute test with custom configuration

29

* @param config Configuration for retry behavior

30

* @param test The test code to execute repeatedly

31

* @return The result of the successful test execution

32

*/

33

suspend fun <T> eventually(config: EventuallyConfiguration, test: suspend () -> T): T

34

```

35

36

**Usage Examples:**

37

38

```kotlin

39

import io.kotest.assertions.nondeterministic.eventually

40

import io.kotest.matchers.shouldBe

41

import kotlin.time.Duration.Companion.seconds

42

43

// Basic eventually with defaults (5 second timeout)

44

eventually {

45

remoteService.isHealthy() shouldBe true

46

}

47

48

// Custom timeout

49

eventually(10.seconds) {

50

database.countRecords() shouldBe 100

51

}

52

53

// With custom configuration

54

val config = eventuallyConfig {

55

duration = 30.seconds

56

interval = 100.milliseconds

57

initialDelay = 1.seconds

58

}

59

60

eventually(config) {

61

queue.isEmpty() shouldBe true

62

}

63

```

64

65

### Continually Functions

66

67

Test assertions that should remain true for a specified duration.

68

69

```kotlin { .api }

70

/**

71

* Repeatedly execute test and verify it continues to pass

72

* @param test The test code to execute repeatedly

73

* @return The result of the test execution

74

*/

75

suspend fun <T> continually(test: suspend () -> T): T

76

77

/**

78

* Repeatedly execute test for specified duration

79

* @param duration How long to keep testing

80

* @param test The test code to execute repeatedly

81

* @return The result of the test execution

82

*/

83

suspend fun <T> continually(duration: Duration, test: suspend () -> T): T

84

85

/**

86

* Repeatedly execute test with custom configuration

87

* @param config Configuration for continual testing

88

* @param test The test code to execute repeatedly

89

* @return The result of the test execution

90

*/

91

suspend fun <T> continually(config: ContinuallyConfiguration, test: suspend () -> T): T

92

```

93

94

**Usage Examples:**

95

96

```kotlin

97

import io.kotest.assertions.nondeterministic.continually

98

import io.kotest.matchers.shouldBe

99

100

// Verify service stays healthy for 30 seconds

101

continually(30.seconds) {

102

healthCheck.status shouldBe "OK"

103

}

104

105

// Verify memory usage stays under limit

106

continually {

107

Runtime.getRuntime().freeMemory() should beGreaterThan(1000000)

108

}

109

```

110

111

### Until Functions

112

113

Execute test repeatedly until it returns without throwing an exception.

114

115

```kotlin { .api }

116

/**

117

* Execute test repeatedly until it succeeds (doesn't throw)

118

* @param test The test code to execute repeatedly

119

* @return The result of the successful test execution

120

*/

121

suspend fun <T> until(test: suspend () -> T): T

122

123

/**

124

* Execute test repeatedly until it succeeds or timeout

125

* @param duration Maximum time to keep trying

126

* @param test The test code to execute repeatedly

127

* @return The result of the successful test execution

128

*/

129

suspend fun <T> until(duration: Duration, test: suspend () -> T): T

130

131

/**

132

* Execute test repeatedly with custom configuration

133

* @param config Configuration for retry behavior

134

* @param test The test code to execute repeatedly

135

* @return The result of the successful test execution

136

*/

137

suspend fun <T> until(config: UntilConfiguration, test: suspend () -> T): T

138

```

139

140

### Configuration Classes

141

142

Configuration objects for customizing retry behavior and intervals.

143

144

```kotlin { .api }

145

/**

146

* Configuration for eventually testing

147

*/

148

data class EventuallyConfiguration(

149

val duration: Duration = 5.seconds,

150

val interval: Duration = 25.milliseconds,

151

val initialDelay: Duration = Duration.ZERO,

152

val listener: EventuallyListener = NoopEventuallyListener

153

)

154

155

/**

156

* Builder for creating EventuallyConfiguration

157

*/

158

class EventuallyConfigurationBuilder {

159

var duration: Duration = 5.seconds

160

var interval: Duration = 25.milliseconds

161

var initialDelay: Duration = Duration.ZERO

162

var listener: EventuallyListener = NoopEventuallyListener

163

}

164

165

/**

166

* Configuration for continually testing

167

*/

168

data class ContinuallyConfiguration<T>(

169

val duration: Duration = 1.seconds,

170

val interval: Duration = 25.milliseconds,

171

val listener: ContinuallyListener<T> = NoopContinuallyListener

172

)

173

174

/**

175

* Configuration for until testing

176

*/

177

data class UntilConfiguration(

178

val duration: Duration = 5.seconds,

179

val interval: Duration = 25.milliseconds,

180

val listener: UntilListener = NoopUntilListener

181

)

182

```

183

184

**Configuration Usage:**

185

186

```kotlin

187

import io.kotest.assertions.nondeterministic.*

188

189

// Create configuration using builder

190

val config = eventuallyConfig {

191

duration = 60.seconds // Total timeout

192

interval = 500.milliseconds // Time between attempts

193

initialDelay = 2.seconds // Wait before first attempt

194

listener = { attempt, error ->

195

println("Attempt $attempt failed: ${error.message}")

196

}

197

}

198

199

eventually(config) {

200

externalApi.getData() shouldBe expectedData

201

}

202

```

203

204

### Event Listeners

205

206

Monitor retry attempts and failures during nondeterministic testing.

207

208

```kotlin { .api }

209

/**

210

* Listener for eventually test attempts and failures

211

*/

212

typealias EventuallyListener = suspend (Int, Throwable) -> Unit

213

214

/**

215

* Listener for continually test executions

216

*/

217

typealias ContinuallyListener<T> = suspend (Int, T) -> Unit

218

219

/**

220

* Listener for until test attempts and failures

221

*/

222

typealias UntilListener = suspend (Int, Throwable) -> Unit

223

224

/**

225

* No-op implementation that ignores all events

226

*/

227

object NoopEventuallyListener : EventuallyListener {

228

override suspend fun invoke(attempt: Int, error: Throwable) {}

229

}

230

```

231

232

### Interval Functions

233

234

Advanced retry strategies with customizable backoff patterns.

235

236

```kotlin { .api }

237

/**

238

* Interface for generating intervals between retry attempts

239

*/

240

interface DurationFn {

241

fun next(iteration: Int): Duration

242

}

243

244

/**

245

* Fibonacci backoff strategy with maximum duration limit

246

*/

247

class FibonacciIntervalFn(private val max: Duration) : DurationFn {

248

override fun next(iteration: Int): Duration

249

}

250

251

/**

252

* Exponential backoff strategy

253

*/

254

class ExponentialIntervalFn(

255

private val base: Duration = 25.milliseconds,

256

private val factor: Double = 2.0,

257

private val max: Duration = 1.seconds

258

) : DurationFn {

259

override fun next(iteration: Int): Duration

260

}

261

262

/**

263

* Create a fibonacci backoff function with maximum duration

264

* @receiver Base duration for calculations

265

* @param max Maximum duration to cap the backoff

266

* @return FibonacciIntervalFn instance

267

*/

268

fun Duration.fibonacci(max: Duration): FibonacciIntervalFn

269

```

270

271

**Advanced Usage Examples:**

272

273

```kotlin

274

import io.kotest.assertions.nondeterministic.*

275

276

// Using fibonacci backoff

277

val fibConfig = eventuallyConfig {

278

duration = 2.minutes

279

interval = FibonacciIntervalFn(10.seconds)

280

}

281

282

// Using exponential backoff

283

val expConfig = eventuallyConfig {

284

duration = 1.minutes

285

interval = ExponentialIntervalFn(

286

base = 100.milliseconds,

287

factor = 1.5,

288

max = 5.seconds

289

)

290

}

291

292

// Monitor retry attempts

293

val monitoredConfig = eventuallyConfig {

294

duration = 30.seconds

295

listener = { attempt, error ->

296

logger.warn("Retry attempt $attempt failed", error)

297

if (attempt > 10) {

298

metrics.increment("high_retry_count")

299

}

300

}

301

}

302

```

303

304

## Error Handling

305

306

Nondeterministic testing functions provide detailed error information:

307

308

- **Timeout errors**: Include total duration, attempt count, and last failure

309

- **Listener errors**: Exceptions in listeners don't affect test execution

310

- **Configuration validation**: Invalid durations or intervals cause immediate failure

311

- **Thread safety**: All functions are thread-safe and work with coroutines

312

313

All functions throw `AssertionError` when the final attempt fails, preserving the original assertion failure message.