or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

discovery.mdevents.mdexecution.mdframework.mdindex.mdlogging.md

events.mddocs/

0

# Event Handling

1

2

Event system for reporting test results, progress, and metadata with support for different test selection patterns and structured error reporting.

3

4

## Capabilities

5

6

### Event Interface

7

8

Core interface for test events fired during test execution.

9

10

```scala { .api }

11

/**

12

* An event fired by the test framework during a run.

13

* Contains complete information about test execution results.

14

*/

15

trait Event {

16

/**

17

* The fully qualified name of a class that can rerun the suite or test.

18

* @return class name for rerunning this test/suite

19

*/

20

def fullyQualifiedName(): String

21

22

/**

23

* The fingerprint of the test class whose fully qualified name is returned.

24

* If fingerprint.isModule indicates module, fullyQualifiedName excludes trailing $.

25

* @return fingerprint used to identify the test class

26

*/

27

def fingerprint(): Fingerprint

28

29

/**

30

* Additional information identifying the suite or test about which an event was fired.

31

* @return selector providing specific test/suite identification

32

*/

33

def selector(): Selector

34

35

/**

36

* Indicates the test result: success, failure, error, skipped, ignored, canceled, pending.

37

* @return status representing the test outcome

38

*/

39

def status(): Status

40

41

/**

42

* An OptionalThrowable associated with this Event.

43

* Contains exception information for failures and errors.

44

* @return optional throwable with error details

45

*/

46

def throwable(): OptionalThrowable

47

48

/**

49

* Execution time in milliseconds, or -1 if no duration available.

50

* @return duration in milliseconds or -1

51

*/

52

def duration(): Long

53

}

54

```

55

56

**Usage Examples:**

57

58

```scala

59

// Create success event for test method

60

class MyEvent(

61

className: String,

62

fp: Fingerprint,

63

sel: Selector,

64

stat: Status,

65

opt: OptionalThrowable,

66

dur: Long

67

) extends Event {

68

def fullyQualifiedName() = className

69

def fingerprint() = fp

70

def selector() = sel

71

def status() = stat

72

def throwable() = opt

73

def duration() = dur

74

}

75

76

// Create events for different outcomes

77

val successEvent = new MyEvent(

78

className = "com.example.MathTest",

79

fp = testFingerprint,

80

sel = new TestSelector("shouldAddNumbers"),

81

stat = Status.Success,

82

opt = new OptionalThrowable(), // empty

83

dur = 150L

84

)

85

86

val failureEvent = new MyEvent(

87

className = "com.example.MathTest",

88

fp = testFingerprint,

89

sel = new TestSelector("shouldDivideByZero"),

90

stat = Status.Failure,

91

opt = new OptionalThrowable(new AssertionError("Division by zero should throw")),

92

dur = 75L

93

)

94

95

val suiteEvent = new MyEvent(

96

className = "com.example.MathTest",

97

fp = testFingerprint,

98

sel = new SuiteSelector(),

99

stat = Status.Success,

100

opt = new OptionalThrowable(),

101

dur = 1200L

102

)

103

```

104

105

### EventHandler Interface

106

107

Interface for handling events fired by test frameworks during execution.

108

109

```scala { .api }

110

/**

111

* Interface implemented by clients that handle events fired by test frameworks.

112

* An event handler is passed to test framework via Task.execute method.

113

*/

114

trait EventHandler {

115

/**

116

* Handle an event fired by the test framework.

117

* @param event the event to handle

118

*/

119

def handle(event: Event): Unit

120

}

121

```

122

123

**Usage Examples:**

124

125

```scala

126

// Simple logging event handler

127

class LoggingEventHandler(logger: Logger) extends EventHandler {

128

def handle(event: Event): Unit = {

129

val testName = event.selector() match {

130

case suite: SuiteSelector => s"Suite: ${event.fullyQualifiedName()}"

131

case test: TestSelector => s"Test: ${event.fullyQualifiedName()}.${test.testName()}"

132

case nested: NestedTestSelector =>

133

s"Nested: ${event.fullyQualifiedName()}.${nested.suiteId()}.${nested.testName()}"

134

case _ => s"Unknown: ${event.fullyQualifiedName()}"

135

}

136

137

event.status() match {

138

case Status.Success =>

139

logger.info(s"✓ $testName (${event.duration()}ms)")

140

case Status.Failure =>

141

logger.error(s"✗ $testName (${event.duration()}ms)")

142

if (event.throwable().isDefined()) {

143

logger.trace(event.throwable().get())

144

}

145

case Status.Error =>

146

logger.error(s"⚠ $testName - ERROR (${event.duration()}ms)")

147

if (event.throwable().isDefined()) {

148

logger.trace(event.throwable().get())

149

}

150

case Status.Skipped =>

151

logger.warn(s"⚬ $testName - SKIPPED")

152

case Status.Ignored =>

153

logger.warn(s"⚬ $testName - IGNORED")

154

case Status.Canceled =>

155

logger.warn(s"⚬ $testName - CANCELED")

156

case Status.Pending =>

157

logger.warn(s"⚬ $testName - PENDING")

158

}

159

}

160

}

161

162

// Aggregating event handler

163

class TestResultCollector extends EventHandler {

164

private val results = mutable.Map[String, TestResult]()

165

166

def handle(event: Event): Unit = {

167

val key = s"${event.fullyQualifiedName()}.${selectorKey(event.selector())}"

168

results(key) = TestResult(

169

className = event.fullyQualifiedName(),

170

selector = event.selector(),

171

status = event.status(),

172

duration = event.duration(),

173

throwable = if (event.throwable().isDefined()) Some(event.throwable().get()) else None

174

)

175

}

176

177

def getResults(): Map[String, TestResult] = results.toMap

178

179

def getSummary(): TestSummary = {

180

val allResults = results.values.toSeq

181

TestSummary(

182

total = allResults.size,

183

passed = allResults.count(_.status == Status.Success),

184

failed = allResults.count(_.status == Status.Failure),

185

errors = allResults.count(_.status == Status.Error),

186

skipped = allResults.count(r =>

187

r.status == Status.Skipped || r.status == Status.Ignored || r.status == Status.Canceled),

188

pending = allResults.count(_.status == Status.Pending),

189

duration = allResults.map(_.duration).sum

190

)

191

}

192

}

193

```

194

195

### Status Enumeration

196

197

Represents the outcome of test execution with comprehensive status types.

198

199

```scala { .api }

200

/**

201

* Represents the status of running a test.

202

* Test frameworks can decide which statuses to use and their meanings.

203

*/

204

class Status private (name: String, ordinal: Int) extends Enum[Status](name, ordinal)

205

206

object Status {

207

/** Indicates a test succeeded. */

208

final val Success: Status

209

210

/** Indicates an "error" occurred during test execution. */

211

final val Error: Status

212

213

/** Indicates a "failure" occurred during test execution. */

214

final val Failure: Status

215

216

/** Indicates a test was skipped for any reason. */

217

final val Skipped: Status

218

219

/** Indicates a test was ignored (temporarily disabled with intention to fix). */

220

final val Ignored: Status

221

222

/**

223

* Indicates a test was canceled (unable to complete due to unmet precondition,

224

* such as database being offline).

225

*/

226

final val Canceled: Status

227

228

/**

229

* Indicates a test was declared as pending (with test code and/or

230

* production code as yet unimplemented).

231

*/

232

final val Pending: Status

233

234

/** Returns array of all status values. */

235

def values(): Array[Status]

236

237

/**

238

* Returns status by name.

239

* @param name status name

240

* @return Status instance

241

* @throws IllegalArgumentException if name not found

242

*/

243

def valueOf(name: String): Status

244

}

245

```

246

247

**Usage Examples:**

248

249

```scala

250

// Check status types

251

def handleTestResult(event: Event): Unit = {

252

event.status() match {

253

case Status.Success =>

254

println("Test passed!")

255

case Status.Failure =>

256

println(s"Test failed: ${event.throwable().get().getMessage}")

257

case Status.Error =>

258

println(s"Test error: ${event.throwable().get().getMessage}")

259

case Status.Skipped | Status.Ignored | Status.Canceled =>

260

println("Test was not executed")

261

case Status.Pending =>

262

println("Test is pending implementation")

263

}

264

}

265

266

// Create events with different statuses

267

val statuses = Status.values()

268

println(s"Available statuses: ${statuses.map(_.name).mkString(", ")}")

269

270

// Status by name lookup

271

val failureStatus = Status.valueOf("Failure")

272

assert(failureStatus == Status.Failure)

273

```

274

275

### OptionalThrowable

276

277

Container for optional exception information associated with test events.

278

279

```scala { .api }

280

/**

281

* An optional Throwable container.

282

* Used to carry exception information with test events.

283

*/

284

final class OptionalThrowable(exception: Throwable) extends Serializable {

285

/** Creates empty OptionalThrowable with no exception. */

286

def this()

287

288

/**

289

* Indicates whether this OptionalThrowable contains a Throwable.

290

* @return true if contains a Throwable

291

*/

292

def isDefined(): Boolean

293

294

/**

295

* Indicates whether this OptionalThrowable is empty.

296

* @return true if contains no Throwable

297

*/

298

def isEmpty(): Boolean

299

300

/**

301

* Returns the contained Throwable if defined.

302

* @return contained Throwable

303

* @throws IllegalStateException if not defined

304

*/

305

def get(): Throwable

306

}

307

```

308

309

**Usage Examples:**

310

311

```scala

312

// Create OptionalThrowable with exception

313

val withException = new OptionalThrowable(new RuntimeException("Test failed"))

314

if (withException.isDefined()) {

315

println(s"Exception: ${withException.get().getMessage}")

316

}

317

318

// Create empty OptionalThrowable

319

val empty = new OptionalThrowable()

320

assert(empty.isEmpty())

321

322

// Safe exception handling

323

def safeGetException(opt: OptionalThrowable): Option[Throwable] = {

324

if (opt.isDefined()) Some(opt.get()) else None

325

}

326

327

// Create events with optional exceptions

328

val successEvent = createEvent(Status.Success, new OptionalThrowable())

329

val failureEvent = createEvent(

330

Status.Failure,

331

new OptionalThrowable(new AssertionError("Expected 5 but was 3"))

332

)

333

```

334

335

## Event Patterns

336

337

### Event Creation in Tasks

338

339

Tasks should create and fire events for all test outcomes:

340

341

```scala

342

class MyTask(taskDef: TaskDef) extends Task {

343

def execute(eventHandler: EventHandler, loggers: Array[Logger]): Array[Task] = {

344

val startTime = System.currentTimeMillis()

345

346

try {

347

// Execute test logic

348

val result = runTest(taskDef.fullyQualifiedName(), taskDef.selectors())

349

val duration = System.currentTimeMillis() - startTime

350

351

result match {

352

case TestSuccess =>

353

val event = new MyEvent(

354

taskDef.fullyQualifiedName(),

355

taskDef.fingerprint(),

356

new SuiteSelector(),

357

Status.Success,

358

new OptionalThrowable(),

359

duration

360

)

361

eventHandler.handle(event)

362

363

case TestFailure(assertion) =>

364

val event = new MyEvent(

365

taskDef.fullyQualifiedName(),

366

taskDef.fingerprint(),

367

new SuiteSelector(),

368

Status.Failure,

369

new OptionalThrowable(assertion),

370

duration

371

)

372

eventHandler.handle(event)

373

}

374

375

Array.empty // No nested tasks

376

377

} catch {

378

case t: Throwable =>

379

val duration = System.currentTimeMillis() - startTime

380

val errorEvent = new MyEvent(

381

taskDef.fullyQualifiedName(),

382

taskDef.fingerprint(),

383

new SuiteSelector(),

384

Status.Error,

385

new OptionalThrowable(t),

386

duration

387

)

388

eventHandler.handle(errorEvent)

389

Array.empty

390

}

391

}

392

}

393

```

394

395

### Selector-Specific Events

396

397

Different selector types should fire appropriate events:

398

399

```scala

400

def fireEventsForSelectors(

401

selectors: Array[Selector],

402

className: String,

403

fingerprint: Fingerprint,

404

eventHandler: EventHandler

405

): Unit = {

406

407

selectors.foreach {

408

case _: SuiteSelector =>

409

// Fire suite-level event

410

val event = createSuiteEvent(className, fingerprint, Status.Success)

411

eventHandler.handle(event)

412

413

case testSel: TestSelector =>

414

// Fire individual test event

415

val event = createTestEvent(className, fingerprint, testSel, Status.Success)

416

eventHandler.handle(event)

417

418

case nestedSel: NestedTestSelector =>

419

// Fire nested test event

420

val event = createNestedTestEvent(className, fingerprint, nestedSel, Status.Success)

421

eventHandler.handle(event)

422

423

case wildcardSel: TestWildcardSelector =>

424

// Fire events for matching tests

425

val matchingTests = findTestsMatching(wildcardSel.testWildcard())

426

matchingTests.foreach { testName =>

427

val testSelector = new TestSelector(testName)

428

val event = createTestEvent(className, fingerprint, testSelector, Status.Success)

429

eventHandler.handle(event)

430

}

431

}

432

}

433

```

434

435

## Error Handling

436

437

### Exception Information

438

439

Events should include detailed exception information for failures and errors:

440

441

```scala

442

def createFailureEvent(

443

className: String,

444

fingerprint: Fingerprint,

445

selector: Selector,

446

exception: Throwable,

447

duration: Long

448

): Event = {

449

450

val status = exception match {

451

case _: AssertionError => Status.Failure

452

case _: Exception => Status.Error

453

case _ => Status.Error

454

}

455

456

new MyEvent(

457

className,

458

fingerprint,

459

selector,

460

status,

461

new OptionalThrowable(exception),

462

duration

463

)

464

}

465

```

466

467

### Event Handler Error Recovery

468

469

Event handlers should handle processing errors gracefully:

470

471

```scala

472

class RobustEventHandler extends EventHandler {

473

def handle(event: Event): Unit = {

474

try {

475

processEvent(event)

476

} catch {

477

case t: Throwable =>

478

// Log error but don't propagate to avoid breaking test execution

479

System.err.println(s"Event handler error: ${t.getMessage}")

480

t.printStackTrace()

481

}

482

}

483

484

private def processEvent(event: Event): Unit = {

485

// Event processing logic that might throw

486

}

487

}

488

```