or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

content.mdhelpers.mdindex.mdmatchers.mdtest-cases.mdtest-execution.mdtest-results.mdtwisted-support.md

content.mddocs/

0

# Content Attachments

1

2

Attach arbitrary content (files, logs, screenshots, debug data) to test results for enhanced debugging and result reporting capabilities.

3

4

## Capabilities

5

6

### Core Content System

7

8

MIME-like content objects for attaching data to test results.

9

10

```python { .api }

11

class Content:

12

"""

13

A MIME-like Content object for test attachments.

14

15

Content objects can be serialized to bytes and provide

16

structured data attachment capabilities for test results.

17

"""

18

19

def __init__(self, content_type, get_bytes):

20

"""

21

Create a Content object.

22

23

Args:

24

content_type (ContentType): MIME content type

25

get_bytes (callable): Function returning byte iterator

26

"""

27

28

def iter_bytes(self):

29

"""

30

Iterate over bytestrings of the serialized content.

31

32

Yields:

33

bytes: Chunks of content data

34

"""

35

36

def iter_text(self):

37

"""

38

Iterate over text of the serialized content.

39

40

Only valid for text MIME types. Uses ISO-8859-1 if

41

no charset parameter is present.

42

43

Yields:

44

str: Text chunks

45

46

Raises:

47

ValueError: If content type is not text/*

48

"""

49

50

def as_text(self):

51

"""

52

Return all content as text string.

53

54

Loads all content into memory. For large content,

55

use iter_text() instead.

56

57

Returns:

58

str: Complete text content

59

60

Raises:

61

ValueError: If content type is not text/*

62

"""

63

64

def __eq__(self, other):

65

"""

66

Compare content objects for equality.

67

68

Args:

69

other (Content): Content to compare with

70

71

Returns:

72

bool: True if content types and data are equal

73

"""

74

75

class TracebackContent(Content):

76

"""

77

Content object for Python tracebacks.

78

79

Specialized content type for capturing and

80

attaching exception tracebacks to test results.

81

"""

82

83

def __init__(self, err, test):

84

"""

85

Create traceback content from exception.

86

87

Args:

88

err: Exception information (type, value, traceback)

89

test: Test case that generated the exception

90

"""

91

```

92

93

### Content Type System

94

95

MIME content type representation for proper content handling.

96

97

```python { .api }

98

class ContentType:

99

"""

100

A content type from IANA media types registry.

101

102

Represents MIME content types with parameters

103

for proper content handling and display.

104

"""

105

106

def __init__(self, primary_type, sub_type, parameters=None):

107

"""

108

Create a ContentType.

109

110

Args:

111

primary_type (str): Primary type (e.g., "text", "application")

112

sub_type (str): Sub type (e.g., "plain", "json")

113

parameters (dict): Optional type parameters

114

"""

115

116

@property

117

def type(self):

118

"""

119

Primary content type.

120

121

Returns:

122

str: Primary type (e.g., "text")

123

"""

124

125

@property

126

def subtype(self):

127

"""

128

Content subtype.

129

130

Returns:

131

str: Sub type (e.g., "plain")

132

"""

133

134

@property

135

def parameters(self):

136

"""

137

Content type parameters.

138

139

Returns:

140

dict: Parameters like charset, boundary, etc.

141

"""

142

143

def __repr__(self):

144

"""

145

String representation of content type.

146

147

Returns:

148

str: MIME type string (e.g., "text/plain; charset=utf8")

149

"""

150

151

# Predefined content types

152

JSON = ContentType("application", "json")

153

UTF8_TEXT = ContentType("text", "plain", {"charset": "utf8"})

154

```

155

156

### Content Creation Functions

157

158

Utility functions for creating common content types.

159

160

```python { .api }

161

def text_content(text):

162

"""

163

Create text content from string.

164

165

Args:

166

text (str): Text content to attach

167

168

Returns:

169

Content: Text content object with UTF-8 encoding

170

"""

171

172

def json_content(json_data):

173

"""

174

Create JSON content from Python objects.

175

176

Args:

177

json_data: Python object to serialize as JSON

178

179

Returns:

180

Content: JSON content object

181

"""

182

183

def content_from_file(path, content_type=None, chunk_size=DEFAULT_CHUNK_SIZE):

184

"""

185

Create content object from file.

186

187

Args:

188

path (str): File path to read

189

content_type (ContentType): Optional content type

190

chunk_size (int): Read chunk size

191

192

Returns:

193

Content: File content object

194

"""

195

196

def content_from_stream(stream, content_type, seek_offset=None,

197

seek_whence=0, chunk_size=DEFAULT_CHUNK_SIZE):

198

"""

199

Create content object from stream/file-like object.

200

201

Args:

202

stream: File-like object to read from

203

content_type (ContentType): Content type

204

seek_offset (int): Optional seek position

205

seek_whence (int): Seek reference point

206

chunk_size (int): Read chunk size

207

208

Returns:

209

Content: Stream content object

210

"""

211

212

def attach_file(test, name, path, content_type=None):

213

"""

214

Attach file content to test results.

215

216

Convenience function for attaching files to test cases

217

with automatic content type detection.

218

219

Args:

220

test: TestCase to attach content to

221

name (str): Attachment name/identifier

222

path (str): File path to attach

223

content_type (ContentType): Optional content type

224

"""

225

```

226

227

### Content Constants

228

229

Configuration constants for content handling.

230

231

```python { .api }

232

DEFAULT_CHUNK_SIZE = 4096

233

"""Default chunk size for reading content streams."""

234

235

STDOUT_LINE = "\nStdout:\n%s"

236

"""Format string for stdout content presentation."""

237

238

STDERR_LINE = "\nStderr:\n%s"

239

"""Format string for stderr content presentation."""

240

```

241

242

## Usage Examples

243

244

### Basic Content Attachment

245

246

```python

247

import testtools

248

from testtools.content import text_content, json_content

249

250

class MyTest(testtools.TestCase):

251

252

def test_with_debug_info(self):

253

# Attach text debug information

254

debug_info = "Processing started at 10:30 AM\nUser: alice\nMode: batch"

255

self.addDetail('debug_log', text_content(debug_info))

256

257

# Attach structured data

258

state = {

259

'user_id': 12345,

260

'session': 'abc-def-123',

261

'permissions': ['read', 'write'],

262

'timestamp': '2023-10-15T10:30:00Z'

263

}

264

self.addDetail('application_state', json_content(state))

265

266

# Perform test

267

result = process_user_request()

268

self.assertEqual(result.status, 'success')

269

270

def test_with_file_attachment(self):

271

# Generate test data file

272

test_data_file = '/tmp/test_data.csv'

273

with open(test_data_file, 'w') as f:

274

f.write('name,age,city\nAlice,30,Boston\nBob,25,Seattle\n')

275

276

# Attach the file to test results

277

testtools.content.attach_file(

278

self,

279

'input_data',

280

test_data_file,

281

testtools.content_type.ContentType('text', 'csv')

282

)

283

284

# Test file processing

285

result = process_csv_file(test_data_file)

286

self.assertEqual(len(result), 2)

287

```

288

289

### Content from Files and Streams

290

291

```python

292

import testtools

293

from testtools.content import content_from_file, content_from_stream

294

import io

295

296

class FileProcessingTest(testtools.TestCase):

297

298

def test_log_file_processing(self):

299

log_file = '/var/log/application.log'

300

301

# Attach log file content

302

if os.path.exists(log_file):

303

log_content = content_from_file(

304

log_file,

305

testtools.content_type.UTF8_TEXT,

306

chunk_size=8192

307

)

308

self.addDetail('application_logs', log_content)

309

310

# Test log processing

311

result = analyze_logs(log_file)

312

self.assertGreater(result.warning_count, 0)

313

314

def test_stream_processing(self):

315

# Create in-memory stream

316

data_stream = io.StringIO("line1\nline2\nline3\n")

317

318

# Attach stream content

319

stream_content = content_from_stream(

320

data_stream,

321

testtools.content_type.UTF8_TEXT,

322

seek_offset=0

323

)

324

self.addDetail('input_stream', stream_content)

325

326

# Test stream processing

327

data_stream.seek(0) # Reset for actual processing

328

result = process_stream(data_stream)

329

self.assertEqual(len(result), 3)

330

```

331

332

### Advanced Content Usage

333

334

```python

335

import testtools

336

from testtools.content import Content, ContentType

337

import gzip

338

import json

339

340

class AdvancedContentTest(testtools.TestCase):

341

342

def test_with_compressed_content(self):

343

# Create custom compressed content

344

original_data = json.dumps({

345

'large_dataset': list(range(10000)),

346

'metadata': {'compression': 'gzip'}

347

})

348

349

def get_compressed_bytes():

350

compressed = gzip.compress(original_data.encode('utf-8'))

351

yield compressed

352

353

compressed_content = Content(

354

ContentType('application', 'json', {'encoding': 'gzip'}),

355

get_compressed_bytes

356

)

357

358

self.addDetail('compressed_data', compressed_content)

359

360

# Test with large dataset

361

result = process_large_dataset()

362

self.assertIsNotNone(result)

363

364

def test_with_binary_content(self):

365

# Attach binary file (e.g., screenshot)

366

screenshot_path = '/tmp/test_screenshot.png'

367

if os.path.exists(screenshot_path):

368

binary_content = content_from_file(

369

screenshot_path,

370

ContentType('image', 'png')

371

)

372

self.addDetail('failure_screenshot', binary_content)

373

374

# Test UI functionality

375

result = interact_with_ui()

376

self.assertTrue(result.success)

377

378

def test_multiple_content_types(self):

379

# Attach multiple types of debugging content

380

381

# Configuration data

382

config = {'debug': True, 'timeout': 30}

383

self.addDetail('configuration', json_content(config))

384

385

# Log excerpts

386

recent_logs = get_recent_log_entries()

387

self.addDetail('recent_logs', text_content('\n'.join(recent_logs)))

388

389

# Performance metrics

390

metrics = measure_performance()

391

self.addDetail('performance_metrics', json_content(metrics))

392

393

# Environment information

394

env_info = {

395

'python_version': sys.version,

396

'platform': platform.platform(),

397

'memory_usage': get_memory_usage()

398

}

399

self.addDetail('environment', json_content(env_info))

400

401

# Run the actual test

402

result = complex_operation()

403

self.assertEqual(result.status, 'completed')

404

```

405

406

### Content in Test Fixtures

407

408

```python

409

import testtools

410

from testtools.content import text_content

411

from fixtures import Fixture

412

413

class LoggingFixture(Fixture):

414

"""Fixture that captures logs and attaches them to tests."""

415

416

def __init__(self):

417

super().__init__()

418

self.log_entries = []

419

420

def _setUp(self):

421

# Set up log capture

422

self.original_handler = setup_log_capture(self.log_entries)

423

self.addCleanup(restore_log_handler, self.original_handler)

424

425

def get_log_content(self):

426

"""Get captured logs as content."""

427

return text_content('\n'.join(self.log_entries))

428

429

class MyIntegrationTest(testtools.TestCase):

430

431

def test_with_log_capture(self):

432

# Use logging fixture

433

log_fixture = self.useFixture(LoggingFixture())

434

435

# Perform operations that generate logs

436

service = ExternalService()

437

result = service.complex_operation()

438

439

# Attach captured logs

440

self.addDetail('operation_logs', log_fixture.get_log_content())

441

442

# Verify results

443

self.assertEqual(result.status, 'success')

444

self.assertIn('INFO', '\n'.join(log_fixture.log_entries))

445

```

446

447

### Content with Test Result Analysis

448

449

```python

450

import testtools

451

from testtools.content import json_content

452

453

class TestResultAnalyzer(testtools.TestResult):

454

"""Custom result class that analyzes attached content."""

455

456

def __init__(self):

457

super().__init__()

458

self.content_analysis = {}

459

460

def addSuccess(self, test, details=None):

461

super().addSuccess(test, details)

462

if details:

463

self.analyze_content(test.id(), details)

464

465

def addFailure(self, test, err, details=None):

466

super().addFailure(test, err, details)

467

if details:

468

self.analyze_content(test.id(), details)

469

470

def analyze_content(self, test_id, details):

471

"""Analyze attached content for insights."""

472

analysis = {}

473

474

for name, content in details.items():

475

if content.content_type.type == 'application' and \

476

content.content_type.subtype == 'json':

477

# Analyze JSON content

478

try:

479

data = json.loads(content.as_text())

480

analysis[name] = {

481

'type': 'json',

482

'keys': list(data.keys()) if isinstance(data, dict) else None,

483

'size': len(str(data))

484

}

485

except:

486

analysis[name] = {'type': 'json', 'error': 'parse_failed'}

487

488

elif content.content_type.type == 'text':

489

# Analyze text content

490

text = content.as_text()

491

analysis[name] = {

492

'type': 'text',

493

'lines': len(text.split('\n')),

494

'chars': len(text),

495

'contains_error': 'ERROR' in text.upper()

496

}

497

498

self.content_analysis[test_id] = analysis

499

500

def get_content_summary(self):

501

"""Get summary of all attached content."""

502

return json_content(self.content_analysis)

503

504

# Usage

505

result = TestResultAnalyzer()

506

suite.run(result)

507

508

# Get content analysis

509

content_summary = result.get_content_summary()

510

print("Content analysis:", content_summary.as_text())

511

```