or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cancellation-testing.mdcheckpointing-migration.mdfault-tolerance-recovery.mdindex.mdoperator-lifecycle.mdplugin-testing.mdruntime-utilities.mdsession-window-testing.mdstate-backend-restore.mdtest-data-utilities.md

plugin-testing.mddocs/

0

# Plugin Testing Framework

1

2

Framework for testing Flink's plugin system and service provider interface (SPI) implementations. This framework enables validation of plugin loading, dependency isolation, and service discovery mechanisms.

3

4

## Capabilities

5

6

### Plugin Test Base

7

8

Abstract base class providing framework for testing Flink plugin system functionality and SPI implementations.

9

10

```java { .api }

11

/**

12

* Base class for testing Flink plugin system and SPI implementations

13

*/

14

public abstract class PluginTestBase {

15

16

/**

17

* Test plugin loading and initialization

18

* @throws Exception if plugin loading test fails

19

*/

20

protected abstract void testPluginLoading() throws Exception;

21

22

/**

23

* Test plugin dependency isolation and classloader separation

24

* @param pluginPath path to plugin JAR file

25

* @throws Exception if dependency isolation test fails

26

*/

27

protected void testPluginDependencyIsolation(String pluginPath) throws Exception;

28

29

/**

30

* Test service provider interface discovery and loading

31

* @param serviceInterface service interface class

32

* @param expectedImplementations expected number of implementations

33

* @throws Exception if SPI discovery test fails

34

*/

35

protected <T> void testServiceProviderDiscovery(

36

Class<T> serviceInterface,

37

int expectedImplementations) throws Exception;

38

39

/**

40

* Test plugin configuration and parameter passing

41

* @param pluginConfig configuration properties for plugin

42

* @throws Exception if plugin configuration test fails

43

*/

44

protected void testPluginConfiguration(Properties pluginConfig) throws Exception;

45

46

/**

47

* Test plugin lifecycle management (load, initialize, shutdown)

48

* @param pluginIdentifier unique identifier for plugin

49

* @throws Exception if plugin lifecycle test fails

50

*/

51

protected void testPluginLifecycle(String pluginIdentifier) throws Exception;

52

53

/**

54

* Validate plugin classloader isolation

55

* @param pluginClassLoader plugin's classloader

56

* @param parentClassLoader parent application classloader

57

* @return boolean indicating proper isolation

58

*/

59

protected boolean validateClassLoaderIsolation(

60

ClassLoader pluginClassLoader,

61

ClassLoader parentClassLoader);

62

63

/**

64

* Create test plugin configuration with specified properties

65

* @param pluginName name of the plugin

66

* @param pluginVersion version of the plugin

67

* @param configProperties additional configuration properties

68

* @return PluginConfiguration for testing

69

*/

70

protected PluginConfiguration createTestPluginConfiguration(

71

String pluginName,

72

String pluginVersion,

73

Map<String, String> configProperties);

74

}

75

```

76

77

### Plugin Service Interfaces

78

79

Test implementations of service provider interfaces for validating plugin discovery and loading.

80

81

```java { .api }

82

/**

83

* Test service provider interface for plugin testing

84

*/

85

public interface TestPluginService {

86

87

/**

88

* Get service name identifier

89

* @return String service name

90

*/

91

String getServiceName();

92

93

/**

94

* Get service version

95

* @return String service version

96

*/

97

String getServiceVersion();

98

99

/**

100

* Initialize service with configuration

101

* @param config service configuration properties

102

* @throws Exception if initialization fails

103

*/

104

void initialize(Map<String, String> config) throws Exception;

105

106

/**

107

* Execute service operation for testing

108

* @param input input data for service operation

109

* @return String result of service operation

110

* @throws Exception if service operation fails

111

*/

112

String executeOperation(String input) throws Exception;

113

114

/**

115

* Shutdown service and cleanup resources

116

* @throws Exception if shutdown fails

117

*/

118

void shutdown() throws Exception;

119

}

120

121

/**

122

* Test implementation of plugin service interface

123

*/

124

public class TestPluginServiceImpl implements TestPluginService {

125

126

/**

127

* Constructor for test plugin service implementation

128

*/

129

public TestPluginServiceImpl();

130

131

@Override

132

public String getServiceName();

133

134

@Override

135

public String getServiceVersion();

136

137

@Override

138

public void initialize(Map<String, String> config) throws Exception;

139

140

@Override

141

public String executeOperation(String input) throws Exception;

142

143

@Override

144

public void shutdown() throws Exception;

145

146

/**

147

* Check if service is properly initialized

148

* @return boolean indicating initialization status

149

*/

150

public boolean isInitialized();

151

152

/**

153

* Get service configuration

154

* @return Map of configuration properties

155

*/

156

public Map<String, String> getConfiguration();

157

}

158

159

/**

160

* Alternative test service implementation for multiple provider testing

161

*/

162

public class AlternativeTestPluginServiceImpl implements TestPluginService {

163

164

@Override

165

public String getServiceName();

166

167

@Override

168

public String getServiceVersion();

169

170

@Override

171

public void initialize(Map<String, String> config) throws Exception;

172

173

@Override

174

public String executeOperation(String input) throws Exception;

175

176

@Override

177

public void shutdown() throws Exception;

178

}

179

```

180

181

### Plugin Management Utilities

182

183

Utility classes for plugin loading, management, and validation operations.

184

185

```java { .api }

186

/**

187

* Utilities for plugin loading and management testing

188

*/

189

public class PluginTestUtils {

190

191

/**

192

* Load plugin from JAR file with isolated classloader

193

* @param pluginJarPath path to plugin JAR file

194

* @param parentClassLoader parent classloader for isolation

195

* @return PluginClassLoader for the loaded plugin

196

* @throws Exception if plugin loading fails

197

*/

198

public static PluginClassLoader loadPluginFromJar(

199

String pluginJarPath,

200

ClassLoader parentClassLoader) throws Exception;

201

202

/**

203

* Discover service implementations using SPI mechanism

204

* @param serviceInterface service interface class

205

* @param pluginClassLoader classloader containing service implementations

206

* @return List of service implementation instances

207

* @throws Exception if service discovery fails

208

*/

209

public static <T> List<T> discoverServices(

210

Class<T> serviceInterface,

211

ClassLoader pluginClassLoader) throws Exception;

212

213

/**

214

* Validate plugin JAR structure and required files

215

* @param pluginJarPath path to plugin JAR file

216

* @return PluginValidationResult containing validation details

217

* @throws Exception if validation process fails

218

*/

219

public static PluginValidationResult validatePluginJar(String pluginJarPath) throws Exception;

220

221

/**

222

* Create test plugin JAR with specified services

223

* @param outputPath path for created plugin JAR

224

* @param serviceImplementations service implementations to include

225

* @param pluginMetadata metadata for plugin descriptor

226

* @throws Exception if plugin JAR creation fails

227

*/

228

public static void createTestPluginJar(

229

String outputPath,

230

List<Class<?>> serviceImplementations,

231

PluginMetadata pluginMetadata) throws Exception;

232

233

/**

234

* Test plugin dependency isolation by attempting to load conflicting dependencies

235

* @param pluginClassLoader plugin's isolated classloader

236

* @param dependencyClass class that should be isolated

237

* @return boolean indicating successful dependency isolation

238

*/

239

public static boolean testDependencyIsolation(

240

ClassLoader pluginClassLoader,

241

Class<?> dependencyClass);

242

}

243

244

/**

245

* Plugin classloader with isolation capabilities for testing

246

*/

247

public class PluginClassLoader extends URLClassLoader {

248

249

/**

250

* Constructor for plugin classloader

251

* @param urls URLs containing plugin classes and resources

252

* @param parent parent classloader

253

* @param isolatedPackages packages to isolate from parent

254

*/

255

public PluginClassLoader(URL[] urls, ClassLoader parent, Set<String> isolatedPackages);

256

257

@Override

258

public Class<?> loadClass(String name) throws ClassNotFoundException;

259

260

/**

261

* Check if package is isolated in this classloader

262

* @param packageName package name to check

263

* @return boolean indicating package isolation

264

*/

265

public boolean isPackageIsolated(String packageName);

266

267

/**

268

* Get list of isolated packages

269

* @return Set of isolated package names

270

*/

271

public Set<String> getIsolatedPackages();

272

}

273

```

274

275

### Plugin Configuration and Metadata

276

277

Configuration and metadata classes for plugin testing scenarios.

278

279

```java { .api }

280

/**

281

* Configuration for plugin testing scenarios

282

*/

283

public class PluginConfiguration {

284

285

/**

286

* Constructor for plugin configuration

287

* @param pluginName name of the plugin

288

* @param pluginVersion version of the plugin

289

* @param configProperties configuration properties

290

*/

291

public PluginConfiguration(

292

String pluginName,

293

String pluginVersion,

294

Map<String, String> configProperties);

295

296

/**

297

* Get plugin name

298

* @return String plugin name

299

*/

300

public String getPluginName();

301

302

/**

303

* Get plugin version

304

* @return String plugin version

305

*/

306

public String getPluginVersion();

307

308

/**

309

* Get configuration properties

310

* @return Map of configuration key-value pairs

311

*/

312

public Map<String, String> getConfigProperties();

313

314

/**

315

* Get specific configuration property value

316

* @param key property key

317

* @return String property value or null if not found

318

*/

319

public String getProperty(String key);

320

321

/**

322

* Set configuration property

323

* @param key property key

324

* @param value property value

325

*/

326

public void setProperty(String key, String value);

327

}

328

329

/**

330

* Metadata for plugin descriptor and JAR creation

331

*/

332

public class PluginMetadata {

333

334

/**

335

* Constructor for plugin metadata

336

* @param pluginId unique plugin identifier

337

* @param pluginName human-readable plugin name

338

* @param version plugin version

339

* @param description plugin description

340

*/

341

public PluginMetadata(

342

String pluginId,

343

String pluginName,

344

String version,

345

String description);

346

347

/**

348

* Get plugin identifier

349

* @return String plugin ID

350

*/

351

public String getPluginId();

352

353

/**

354

* Get plugin name

355

* @return String plugin name

356

*/

357

public String getPluginName();

358

359

/**

360

* Get plugin version

361

* @return String version

362

*/

363

public String getVersion();

364

365

/**

366

* Get plugin description

367

* @return String description

368

*/

369

public String getDescription();

370

371

/**

372

* Get required dependencies

373

* @return List of required dependency specifications

374

*/

375

public List<DependencySpecification> getRequiredDependencies();

376

377

/**

378

* Add required dependency

379

* @param dependency dependency specification to add

380

*/

381

public void addRequiredDependency(DependencySpecification dependency);

382

}

383

384

/**

385

* Result of plugin validation operations

386

*/

387

public class PluginValidationResult {

388

389

/**

390

* Check if plugin validation was successful

391

* @return boolean indicating validation success

392

*/

393

public boolean isValid();

394

395

/**

396

* Get list of validation errors

397

* @return List of validation error messages

398

*/

399

public List<String> getValidationErrors();

400

401

/**

402

* Get list of validation warnings

403

* @return List of validation warning messages

404

*/

405

public List<String> getValidationWarnings();

406

407

/**

408

* Get discovered service implementations

409

* @return List of service implementation class names

410

*/

411

public List<String> getDiscoveredServices();

412

413

/**

414

* Check if required plugin descriptor is present

415

* @return boolean indicating descriptor presence

416

*/

417

public boolean hasPluginDescriptor();

418

}

419

420

/**

421

* Specification for plugin dependencies

422

*/

423

public class DependencySpecification {

424

425

/**

426

* Constructor for dependency specification

427

* @param groupId dependency group identifier

428

* @param artifactId dependency artifact identifier

429

* @param version dependency version

430

* @param scope dependency scope (compile, runtime, test)

431

*/

432

public DependencySpecification(

433

String groupId,

434

String artifactId,

435

String version,

436

String scope);

437

438

/**

439

* Get dependency coordinates as string

440

* @return String representation of dependency coordinates

441

*/

442

public String getCoordinates();

443

}

444

```

445

446

**Usage Examples:**

447

448

```java

449

import org.apache.flink.test.plugin.PluginTestBase;

450

import org.apache.flink.test.plugin.jar.*;

451

452

// Basic plugin testing

453

public class BasicPluginTest extends PluginTestBase {

454

455

@Test

456

public void testBasicPluginLoading() throws Exception {

457

testPluginLoading();

458

}

459

460

@Override

461

protected void testPluginLoading() throws Exception {

462

// Create test plugin configuration

463

Map<String, String> configProps = new HashMap<>();

464

configProps.put("test.property", "test.value");

465

configProps.put("service.timeout", "30000");

466

467

PluginConfiguration config = createTestPluginConfiguration(

468

"test-plugin", "1.0.0", configProps);

469

470

// Test plugin configuration

471

testPluginConfiguration(config.getConfigProperties());

472

473

// Test plugin lifecycle

474

testPluginLifecycle("test-plugin-001");

475

}

476

477

@Test

478

public void testServiceProviderDiscovery() throws Exception {

479

// Test SPI discovery for TestPluginService

480

testServiceProviderDiscovery(TestPluginService.class, 2);

481

}

482

483

@Test

484

public void testPluginDependencyIsolation() throws Exception {

485

// Create test plugin JAR

486

String pluginJarPath = "/tmp/test-plugin.jar";

487

488

PluginMetadata metadata = new PluginMetadata(

489

"test-plugin", "Test Plugin", "1.0.0",

490

"Plugin for testing dependency isolation");

491

492

PluginTestUtils.createTestPluginJar(

493

pluginJarPath,

494

Arrays.asList(TestPluginServiceImpl.class),

495

metadata);

496

497

// Test dependency isolation

498

testPluginDependencyIsolation(pluginJarPath);

499

}

500

}

501

502

// Advanced plugin testing scenarios

503

public class AdvancedPluginTest extends PluginTestBase {

504

505

@Test

506

public void testMultipleServiceImplementations() throws Exception {

507

// Create plugin with multiple service implementations

508

String pluginJarPath = "/tmp/multi-service-plugin.jar";

509

510

PluginMetadata metadata = new PluginMetadata(

511

"multi-service-plugin", "Multi-Service Plugin", "2.0.0",

512

"Plugin with multiple service implementations");

513

514

// Add dependency specifications

515

metadata.addRequiredDependency(new DependencySpecification(

516

"org.apache.flink", "flink-core", "2.1.0", "provided"));

517

518

PluginTestUtils.createTestPluginJar(

519

pluginJarPath,

520

Arrays.asList(

521

TestPluginServiceImpl.class,

522

AlternativeTestPluginServiceImpl.class),

523

metadata);

524

525

// Validate plugin JAR

526

PluginValidationResult validationResult =

527

PluginTestUtils.validatePluginJar(pluginJarPath);

528

529

assertTrue(validationResult.isValid());

530

assertEquals(2, validationResult.getDiscoveredServices().size());

531

assertTrue(validationResult.hasPluginDescriptor());

532

533

// Load plugin and discover services

534

PluginClassLoader pluginClassLoader = PluginTestUtils.loadPluginFromJar(

535

pluginJarPath, getClass().getClassLoader());

536

537

List<TestPluginService> services = PluginTestUtils.discoverServices(

538

TestPluginService.class, pluginClassLoader);

539

540

assertEquals(2, services.size());

541

542

// Test each service implementation

543

for (TestPluginService service : services) {

544

Map<String, String> config = new HashMap<>();

545

config.put("test.mode", "advanced");

546

547

service.initialize(config);

548

assertTrue(service instanceof TestPluginServiceImpl ||

549

service instanceof AlternativeTestPluginServiceImpl);

550

551

String result = service.executeOperation("test-input");

552

assertNotNull(result);

553

554

service.shutdown();

555

}

556

}

557

558

@Test

559

public void testClassLoaderIsolation() throws Exception {

560

String pluginJarPath = "/tmp/isolation-test-plugin.jar";

561

562

// Create plugin with isolated dependencies

563

PluginMetadata metadata = new PluginMetadata(

564

"isolation-plugin", "Isolation Test Plugin", "1.0.0",

565

"Plugin for testing classloader isolation");

566

567

PluginTestUtils.createTestPluginJar(

568

pluginJarPath,

569

Arrays.asList(TestPluginServiceImpl.class),

570

metadata);

571

572

// Load plugin with isolated classloader

573

Set<String> isolatedPackages = Set.of(

574

"org.apache.flink.test.plugin.custom",

575

"com.example.plugin.isolated");

576

577

PluginClassLoader pluginClassLoader = new PluginClassLoader(

578

new URL[]{new File(pluginJarPath).toURI().toURL()},

579

getClass().getClassLoader(),

580

isolatedPackages);

581

582

// Validate classloader isolation

583

boolean isolationValid = validateClassLoaderIsolation(

584

pluginClassLoader, getClass().getClassLoader());

585

assertTrue(isolationValid);

586

587

// Test dependency isolation

588

boolean dependencyIsolated = PluginTestUtils.testDependencyIsolation(

589

pluginClassLoader, TestPluginService.class);

590

assertTrue(dependencyIsolated);

591

592

// Verify isolated packages

593

for (String packageName : isolatedPackages) {

594

assertTrue(pluginClassLoader.isPackageIsolated(packageName));

595

}

596

}

597

598

@Test

599

public void testPluginConfigurationParsing() throws Exception {

600

// Create complex plugin configuration

601

Map<String, String> complexConfig = new HashMap<>();

602

complexConfig.put("service.name", "advanced-test-service");

603

complexConfig.put("service.threads", "10");

604

complexConfig.put("service.timeout.connect", "5000");

605

complexConfig.put("service.timeout.read", "30000");

606

complexConfig.put("service.retry.attempts", "3");

607

complexConfig.put("service.retry.backoff", "exponential");

608

609

PluginConfiguration config = createTestPluginConfiguration(

610

"advanced-plugin", "3.0.0", complexConfig);

611

612

// Test configuration access

613

assertEquals("advanced-test-service", config.getProperty("service.name"));

614

assertEquals("10", config.getProperty("service.threads"));

615

assertNull(config.getProperty("nonexistent.property"));

616

617

// Test configuration modification

618

config.setProperty("service.debug", "true");

619

assertEquals("true", config.getProperty("service.debug"));

620

621

// Test plugin configuration

622

testPluginConfiguration(config.getConfigProperties());

623

}

624

}

625

626

// Plugin lifecycle testing

627

public class PluginLifecycleTest extends PluginTestBase {

628

629

@Test

630

public void testCompletePluginLifecycle() throws Exception {

631

String pluginId = "lifecycle-test-plugin";

632

633

// Test complete lifecycle: load -> initialize -> execute -> shutdown

634

testPluginLifecycle(pluginId);

635

}

636

637

@Override

638

protected void testPluginLoading() throws Exception {

639

// Custom plugin loading test implementation

640

String pluginJarPath = createTestPluginJar();

641

642

// Load plugin

643

PluginClassLoader classLoader = PluginTestUtils.loadPluginFromJar(

644

pluginJarPath, getClass().getClassLoader());

645

646

// Discover and instantiate services

647

List<TestPluginService> services = PluginTestUtils.discoverServices(

648

TestPluginService.class, classLoader);

649

650

assertFalse(services.isEmpty());

651

652

// Test each service lifecycle

653

for (TestPluginService service : services) {

654

// Initialize

655

Map<String, String> config = createServiceConfig();

656

service.initialize(config);

657

658

if (service instanceof TestPluginServiceImpl) {

659

assertTrue(((TestPluginServiceImpl) service).isInitialized());

660

}

661

662

// Execute operations

663

String result1 = service.executeOperation("test-input-1");

664

String result2 = service.executeOperation("test-input-2");

665

666

assertNotNull(result1);

667

assertNotNull(result2);

668

assertNotEquals(result1, result2); // Should produce different results

669

670

// Shutdown

671

service.shutdown();

672

}

673

}

674

675

private String createTestPluginJar() throws Exception {

676

String jarPath = "/tmp/lifecycle-test-plugin.jar";

677

678

PluginMetadata metadata = new PluginMetadata(

679

"lifecycle-plugin", "Lifecycle Test Plugin", "1.0.0",

680

"Plugin for testing complete lifecycle");

681

682

PluginTestUtils.createTestPluginJar(

683

jarPath,

684

Arrays.asList(TestPluginServiceImpl.class),

685

metadata);

686

687

return jarPath;

688

}

689

690

private Map<String, String> createServiceConfig() {

691

Map<String, String> config = new HashMap<>();

692

config.put("service.id", "lifecycle-test-service");

693

config.put("service.version", "1.0.0");

694

config.put("test.mode", "lifecycle");

695

return config;

696

}

697

}

698

```