or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

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

execution.mddocs/

0

# Test Execution

1

2

Test execution system providing task creation, lifecycle management, and support for both synchronous and asynchronous JavaScript execution patterns.

3

4

## Capabilities

5

6

### Runner Interface

7

8

Manages the lifecycle of a test run, creating tasks from task definitions and coordinating distributed execution.

9

10

```scala { .api }

11

/**

12

* Represents one run of a suite of tests.

13

* Has a lifecycle: instantiation -> tasks() calls -> done()

14

* After done() is called, the Runner enters "spent" mode and tasks() throws IllegalStateException.

15

*/

16

trait Runner {

17

/**

18

* Returns an array of tasks that when executed will run tests and suites.

19

* @param taskDefs the TaskDefs for requested tasks

20

* @return an array of Tasks

21

* @throws IllegalStateException if invoked after done() has been called

22

*/

23

def tasks(taskDefs: Array[TaskDef]): Array[Task]

24

25

/**

26

* Indicates the client is done with this Runner instance.

27

* Must not call task() methods after this.

28

* In Scala.js, client must not call before all Task.execute completions.

29

* @return a possibly multi-line summary string, or empty string if no summary

30

* @throws IllegalStateException if called more than once

31

*/

32

def done(): String

33

34

/**

35

* Remote args that will be passed to Runner in a sub-process as remoteArgs.

36

* @return array of strings for sub-process remoteArgs

37

*/

38

def remoteArgs(): Array[String]

39

40

/** Returns the arguments that were used to create this Runner. */

41

def args: Array[String]

42

43

/**

44

* Scala.js specific: Message handling for controller/worker communication.

45

* For controller: invoked when worker sends message, can return response in Some

46

* For worker: invoked when controller responds, return value ignored

47

* @param msg message received

48

* @return optional response message (controller only)

49

*/

50

def receiveMessage(msg: String): Option[String]

51

52

/**

53

* Scala.js specific: Serialize a task for distribution to another runner.

54

* After calling this method, the passed task becomes invalid.

55

* @param task task to serialize

56

* @param serializer function to serialize TaskDef

57

* @return serialized task string

58

*/

59

def serializeTask(task: Task, serializer: TaskDef => String): String

60

61

/**

62

* Scala.js specific: Deserialize a task from another runner.

63

* The resulting task associates with this runner.

64

* @param task serialized task string

65

* @param deserializer function to deserialize TaskDef

66

* @return deserialized Task

67

*/

68

def deserializeTask(task: String, deserializer: String => TaskDef): Task

69

}

70

```

71

72

**Usage Examples:**

73

74

```scala

75

import sbt.testing._

76

77

class MyTestRunner(

78

val args: Array[String],

79

val remoteArgs: Array[String],

80

testClassLoader: ClassLoader

81

) extends Runner {

82

83

private var isSpent = false

84

private val completedTasks = mutable.Set[Task]()

85

86

def tasks(taskDefs: Array[TaskDef]): Array[Task] = {

87

if (isSpent) {

88

throw new IllegalStateException("Runner is spent - done() already called")

89

}

90

91

taskDefs.flatMap { taskDef =>

92

// Create task if we can handle this test class

93

if (canHandle(taskDef)) {

94

Some(new MyTask(taskDef, this))

95

} else {

96

None // Reject this task

97

}

98

}

99

}

100

101

def done(): String = {

102

if (isSpent) {

103

throw new IllegalStateException("done() already called")

104

}

105

isSpent = true

106

107

val summary = s"Completed ${completedTasks.size} tasks"

108

cleanup()

109

summary

110

}

111

112

// Message handling for distributed testing

113

def receiveMessage(msg: String): Option[String] = {

114

msg match {

115

case "status" => Some(s"completed:${completedTasks.size}")

116

case "shutdown" =>

117

cleanup()

118

Some("acknowledged")

119

case _ => None

120

}

121

}

122

123

// Task serialization for worker distribution

124

def serializeTask(task: Task, serializer: TaskDef => String): String = {

125

val taskDefStr = serializer(task.taskDef())

126

s"MyTask:$taskDefStr"

127

}

128

129

def deserializeTask(task: String, deserializer: String => TaskDef): Task = {

130

task match {

131

case s"MyTask:$taskDefStr" =>

132

val taskDef = deserializer(taskDefStr)

133

new MyTask(taskDef, this)

134

case _ =>

135

throw new IllegalArgumentException(s"Cannot deserialize task: $task")

136

}

137

}

138

}

139

```

140

141

### Task Interface

142

143

Represents an executable unit of work that runs tests and can produce additional tasks.

144

145

```scala { .api }

146

/**

147

* A task to execute.

148

* Can be any job, but primarily intended for running tests and/or supplying more tasks.

149

*/

150

trait Task {

151

/**

152

* A possibly zero-length array of string tags associated with this task.

153

* Used for task scheduling and resource management.

154

* @return array of this task's tags

155

*/

156

def tags(): Array[String]

157

158

/**

159

* Executes this task, possibly returning new tasks to execute.

160

* @param eventHandler event handler to fire events during the run

161

* @param loggers array of loggers to emit log messages during the run

162

* @return possibly empty array of new tasks for the client to execute

163

*/

164

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

165

166

/**

167

* Scala.js specific: Async execute with continuation support.

168

* This method will be called instead of synchronous execute in JavaScript environments.

169

* @param eventHandler event handler to fire events during the run

170

* @param loggers array of loggers to emit log messages during the run

171

* @param continuation called with result tasks when execution completes

172

*/

173

def execute(eventHandler: EventHandler, loggers: Array[Logger],

174

continuation: Array[Task] => Unit): Unit

175

176

/**

177

* Returns the TaskDef that was used to request this Task.

178

* @return the TaskDef used to request this Task

179

*/

180

def taskDef(): TaskDef

181

}

182

```

183

184

**Usage Examples:**

185

186

```scala

187

class MyTask(taskDef: TaskDef, runner: Runner) extends Task {

188

189

def tags(): Array[String] = {

190

// Tag CPU-intensive tests

191

if (isCpuIntensive(taskDef.fullyQualifiedName())) {

192

Array("cpu-intensive")

193

} else {

194

Array.empty

195

}

196

}

197

198

// Synchronous execution (JVM)

199

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

200

val logger = loggers.headOption.getOrElse(NoOpLogger)

201

202

try {

203

logger.info(s"Running test: ${taskDef.fullyQualifiedName()}")

204

205

// Load and execute test class

206

val testInstance = loadTestClass(taskDef.fullyQualifiedName())

207

val results = runTests(testInstance, taskDef.selectors())

208

209

// Fire events for each test result

210

results.foreach { result =>

211

val event = createEvent(result, taskDef)

212

eventHandler.handle(event)

213

}

214

215

// Return any nested tasks

216

createNestedTasks(results)

217

218

} catch {

219

case t: Throwable =>

220

logger.error(s"Test execution failed: ${t.getMessage}")

221

val errorEvent = createErrorEvent(t, taskDef)

222

eventHandler.handle(errorEvent)

223

Array.empty

224

}

225

}

226

227

// Asynchronous execution (JavaScript)

228

def execute(eventHandler: EventHandler, loggers: Array[Logger],

229

continuation: Array[Task] => Unit): Unit = {

230

val logger = loggers.headOption.getOrElse(NoOpLogger)

231

232

// Use JavaScript's async capabilities

233

scala.scalajs.js.timers.setTimeout(0) {

234

try {

235

logger.info(s"Async running test: ${taskDef.fullyQualifiedName()}")

236

237

val testInstance = loadTestClass(taskDef.fullyQualifiedName())

238

239

// Run async tests with Promise-based execution

240

runAsyncTests(testInstance, taskDef.selectors()) { results =>

241

results.foreach { result =>

242

val event = createEvent(result, taskDef)

243

eventHandler.handle(event)

244

}

245

246

val nestedTasks = createNestedTasks(results)

247

continuation(nestedTasks)

248

}

249

250

} catch {

251

case t: Throwable =>

252

logger.error(s"Async test execution failed: ${t.getMessage}")

253

val errorEvent = createErrorEvent(t, taskDef)

254

eventHandler.handle(errorEvent)

255

continuation(Array.empty)

256

}

257

}

258

}

259

260

def taskDef(): TaskDef = taskDef

261

}

262

```

263

264

### TaskDef Class

265

266

Bundles information used to request a Task from a test framework.

267

268

```scala { .api }

269

/**

270

* Information bundle used to request a Task from a test framework.

271

* @param fullyQualifiedName fully qualified name of the test class

272

* @param fingerprint indicates how the test suite was identified

273

* @param explicitlySpecified whether test class was explicitly specified by user

274

* @param selectors array of Selectors determining suites and tests to run

275

*/

276

final class TaskDef(

277

fullyQualifiedName: String,

278

fingerprint: Fingerprint,

279

explicitlySpecified: Boolean,

280

selectors: Array[Selector]

281

) extends Serializable {

282

283

/** The fully qualified name of the test class requested by this TaskDef. */

284

def fullyQualifiedName(): String

285

286

/** The fingerprint that the test class requested by this TaskDef matches. */

287

def fingerprint(): Fingerprint

288

289

/**

290

* Indicates whether the test class was "explicitly specified" by the user.

291

* True for commands like: test-only com.mycompany.myproject.WholeNameSpec

292

* False for commands like: test-only *WholeNameSpec or test

293

*/

294

def explicitlySpecified(): Boolean

295

296

/**

297

* Selectors describing the nature of the Task requested by this TaskDef.

298

* Can indicate direct user requests or "rerun" of previously run tests.

299

*/

300

def selectors(): Array[Selector]

301

}

302

```

303

304

**Usage:**

305

306

```scala

307

// Create TaskDef for explicit test class

308

val taskDef = new TaskDef(

309

fullyQualifiedName = "com.example.MyTestSuite",

310

fingerprint = mySubclassFingerprint,

311

explicitlySpecified = true,

312

selectors = Array(new SuiteSelector())

313

)

314

315

// Create TaskDef for specific test method

316

val testTaskDef = new TaskDef(

317

fullyQualifiedName = "com.example.MyTestSuite",

318

fingerprint = mySubclassFingerprint,

319

explicitlySpecified = false,

320

selectors = Array(new TestSelector("shouldCalculateCorrectly"))

321

)

322

```

323

324

## Execution Lifecycle

325

326

1. **Task Creation**: Runner.tasks() creates Task instances from TaskDef array

327

2. **Task Scheduling**: Client schedules tasks based on tags and resources

328

3. **Synchronous/Async Execution**: Task.execute() called with appropriate method

329

4. **Event Reporting**: Tasks fire events through EventHandler during execution

330

5. **Nested Task Generation**: Tasks can return additional tasks for execution

331

6. **Completion**: All tasks complete and Runner.done() is called

332

7. **Cleanup**: Runner resources cleaned up and becomes "spent"

333

334

## Error Handling

335

336

### IllegalStateException

337

338

- **Runner.tasks()**: Thrown if called after Runner.done()

339

- **Runner.done()**: Thrown if called multiple times

340

- **JavaScript execution**: Thrown if done() called before all task continuations complete

341

342

```scala

343

// Proper error handling in runner

344

def tasks(taskDefs: Array[TaskDef]): Array[Task] = {

345

synchronized {

346

if (isSpent) {

347

throw new IllegalStateException("Runner is spent")

348

}

349

// ... create tasks

350

}

351

}

352

```