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
```