forked from scijava/scijava-common
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathContextCreationTest.java
More file actions
535 lines (472 loc) · 18.3 KB
/
ContextCreationTest.java
File metadata and controls
535 lines (472 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
/*
* #%L
* SciJava Common shared library for SciJava software.
* %%
* Copyright (C) 2009 - 2020 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
package org.scijava;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.scijava.util.ArrayUtils.array;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.PluginIndex;
import org.scijava.plugin.PluginInfo;
import org.scijava.plugin.SciJavaPlugin;
import org.scijava.service.AbstractService;
import org.scijava.service.SciJavaService;
import org.scijava.service.Service;
import org.scijava.thread.ThreadService;
/**
* Tests {@link Context} creation with {@link Service} dependencies.
*
* @author Curtis Rueden
*/
public class ContextCreationTest {
/** Tests that a new empty {@link Context} indeed has no {@link Service}s. */
@Test
public void testEmpty() {
final Context context = new Context(true);
assertTrue(context.getServiceIndex().isEmpty());
assertFalse(context.getPluginIndex().isEmpty());
}
/**
* Tests {@link Context#Context(boolean, boolean)} with {@code (true, true)}.
*/
@Test
public void testNoPlugins() {
final Context context = new Context(true, true);
assertTrue(context.getServiceIndex().isEmpty());
assertTrue(context.getPluginIndex().isEmpty());
}
/**
* Tests that a new fully populated {@link Context} has all available core
* {@link Service}s, in the expected priority order.
*/
@Test
public void testFull() {
final Class<?>[] expected =
{ org.scijava.event.DefaultEventService.class,
org.scijava.app.DefaultAppService.class,
org.scijava.app.DefaultStatusService.class,
org.scijava.command.DefaultCommandService.class,
org.scijava.console.DefaultConsoleService.class,
org.scijava.convert.DefaultConvertService.class,
org.scijava.display.DefaultDisplayService.class,
org.scijava.download.DefaultDownloadService.class,
org.scijava.event.DefaultEventHistory.class,
org.scijava.input.DefaultInputService.class,
org.scijava.io.DefaultIOService.class,
org.scijava.io.DefaultRecentFileService.class,
org.scijava.io.handle.DefaultDataHandleService.class,
org.scijava.io.location.DefaultLocationService.class,
org.scijava.io.nio.DefaultNIOService.class,
org.scijava.main.DefaultMainService.class,
org.scijava.menu.DefaultMenuService.class,
org.scijava.module.DefaultModuleService.class,
org.scijava.object.DefaultObjectService.class,
org.scijava.options.DefaultOptionsService.class,
org.scijava.parse.DefaultParseService.class,
org.scijava.platform.DefaultPlatformService.class,
org.scijava.plugin.DefaultPluginService.class,
org.scijava.prefs.DefaultPrefService.class,
org.scijava.run.DefaultRunService.class,
org.scijava.script.DefaultScriptHeaderService.class,
org.scijava.script.DefaultScriptService.class,
org.scijava.script.process.DefaultScriptProcessorService.class,
org.scijava.startup.DefaultStartupService.class,
org.scijava.task.DefaultTaskService.class,
org.scijava.text.DefaultTextService.class,
org.scijava.text.io.DefaultTextIOService.class,
org.scijava.thread.DefaultThreadService.class,
org.scijava.tool.DefaultToolService.class,
org.scijava.ui.DefaultUIService.class,
org.scijava.ui.dnd.DefaultDragAndDropService.class,
org.scijava.welcome.DefaultWelcomeService.class,
org.scijava.widget.DefaultWidgetService.class,
org.scijava.log.StderrLogService.class,
org.scijava.platform.DefaultAppEventService.class,
org.scijava.cache.DefaultCacheService.class};
final Context context = new Context();
verifyServiceOrder(expected, context);
}
/**
* Tests that a new fully populated {@link Context} has exactly the same
* {@link Service}s available as one created with only {@link SciJavaService}
* implementations.
* <p>
* In other words: tests that all {@link Service}s implemented in SciJava
* Common are tagged with the {@link SciJavaService} interface.
* </p>
*/
@Test
public void testSciJavaServices() {
final Context full = new Context();
final Context sciJava = new Context(SciJavaService.class);
for (final Service s : full.getServiceIndex()) {
final Class<? extends Service> c = s.getClass();
final Service sjs = sciJava.getService(c);
if (sjs == null) fail("Not a SciJavaService? " + s.getClass().getName());
}
}
/**
* Tests that dependent {@link Service}s are automatically created and
* populated in downstream {@link Service} classes.
*/
@Test
public void testDependencies() {
final Context context = new Context(FooService.class);
// verify that the Foo service is there
final FooService fooService = context.getService(FooService.class);
assertNotNull(fooService);
assertSame(context, fooService.getContext());
// verify that the Bar service is there
final BarService barService = context.getService(BarService.class);
assertNotNull(barService);
assertSame(context, barService.getContext());
assertSame(barService, fooService.barService);
// verify that the *only* two services are Foo and Bar
assertEquals(2, context.getServiceIndex().size());
}
/**
* Tests that missing {@link Service}s are handled properly; specifically,
* that {@link IllegalArgumentException} gets thrown when attempting to create
* a {@link Context} requiring one directly.
*/
@Test
public void testMissingDirect() {
try {
new Context(MissingService.class);
fail("Expected IllegalArgumentException");
}
catch (final IllegalArgumentException exc) {
final String expectedMessage =
"No compatible service: " + MissingService.class.getName();
assertEquals(expectedMessage, exc.getMessage());
}
}
/**
* Tests that missing {@link Service}s are handled properly; specifically,
* that {@link IllegalArgumentException} gets thrown when attempting to create
* a {@link Context} requiring one transitively.
*/
@Test
public void testMissingTransitive() {
try {
new Context(ServiceRequiringMissingService.class);
fail("Expected IllegalArgumentException");
}
catch (final IllegalArgumentException exc) {
final String expectedMessage =
"Invalid service: " + ServiceRequiringMissingService.class.getName();
assertEquals(expectedMessage, exc.getMessage());
final String expectedCause =
"No compatible service: " + MissingService.class.getName();
assertEquals(expectedCause, exc.getCause().getMessage());
}
}
/**
* Tests that missing-but-optional {@link Service}s are handled properly;
* specifically, that {@link IllegalArgumentException} gets thrown when
* attempting to create a {@link Context} requiring one transitively.
* <p>
* A service marked {@link Optional}, but annotated without
* {@code required = false} from a dependent service, is assumed to be
* required for that dependent service.
* </p>
*/
@Test
public void testOptionalMissingTransitive() {
try {
new Context(ServiceRequiringOptionalMissingService.class);
fail("Expected IllegalArgumentException");
}
catch (final IllegalArgumentException exc) {
final String expectedMessage =
"Invalid service: " +
ServiceRequiringOptionalMissingService.class.getName();
assertEquals(expectedMessage, exc.getMessage());
final String expectedCause =
"No compatible service: " + OptionalMissingService.class.getName();
assertEquals(expectedCause, exc.getCause().getMessage());
}
}
/**
* Tests that missing {@link Service}s are handled properly in non-strict
* mode; specifically, that {@link IllegalArgumentException} is <em>not</em>
* thrown when attempting to create a {@link Context} requiring one directly.
*/
@Test
public void testNonStrictMissingDirect() {
final List<Class<? extends Service>> serviceClasses =
Context.serviceClassList(MissingService.class);
final Context context = new Context(serviceClasses, false);
assertEquals(0, context.getServiceIndex().size());
}
/**
* Tests that missing {@link Service}s are handled properly in non-strict
* mode; specifically, that {@link IllegalArgumentException} is <em>not</em>
* thrown when attempting to create a {@link Context} requiring one
* transitively.
*/
@Test
public void testNonStrictMissingTransitive() {
final List<Class<? extends Service>> serviceClasses =
Context.serviceClassList(MissingService.class);
final Context context = new Context(serviceClasses, false);
assertEquals(0, context.getServiceIndex().size());
}
/**
* Tests that missing-but-optional {@link Service}s are handled properly in
* non-strict mode; specifically, that {@link IllegalArgumentException} is
* <em>not</em> thrown when attempting to create a {@link Context} requiring
* one transitively.
* <p>
* A service marked {@link Optional}, but annotated without
* {@code required = false} from a dependent service, is assumed to be
* required for that dependent service.
* </p>
*/
@Test
public void testNonStrictOptionalMissingTransitive() {
final List<Class<? extends Service>> serviceClasses =
Context.serviceClassList(ServiceRequiringOptionalMissingService.class);
final Context context = new Context(serviceClasses, false);
final List<Service> services = context.getServiceIndex().getAll();
assertEquals(1, services.size());
assertSame(ServiceRequiringOptionalMissingService.class, services.get(0)
.getClass());
}
/**
* Verifies that the order plugins appear in the PluginIndex and Service list
* does not affect which services are loaded.
*/
@Test
public void testClassOrder() {
final int expectedSize = 2;
// Same order, Base first
Context c = createContext(//
pluginIndex(BaseImpl.class, ExtensionImpl.class), //
array(BaseService.class, ExtensionService.class));
assertEquals(expectedSize, c.getServiceIndex().size());
// Same order, Extension first
c = createContext(//
pluginIndex(ExtensionImpl.class, BaseImpl.class), //
array(ExtensionService.class, BaseService.class));
assertEquals(expectedSize, c.getServiceIndex().size());
// Different order, Extension first
c = createContext(//
pluginIndex(ExtensionImpl.class, BaseImpl.class), //
array(BaseService.class, ExtensionService.class));
assertEquals(expectedSize, c.getServiceIndex().size());
// Different order, Base first
c = createContext(//
pluginIndex(BaseImpl.class, ExtensionImpl.class), //
array(ExtensionService.class, BaseService.class));
assertEquals(expectedSize, c.getServiceIndex().size());
}
/**
* Verifies that the Service index created when using Abstract classes is the
* same as for interfaces.
*/
@Test
public void testAbstractClasslist() {
final Context cAbstract = createContext(//
pluginIndex(BaseImpl.class, ExtensionImpl.class), //
array(AbstractBase.class, AbstractExtension.class));
final Context cService = createContext(//
pluginIndex(BaseImpl.class, ExtensionImpl.class), //
array(BaseService.class, ExtensionService.class));
assertEquals(cService.getServiceIndex().size(), cAbstract.getServiceIndex()
.size());
}
/**
* Verify that if no services are explicitly passed, all subclasses of
* Service.class are discovered automatically.
*/
@Test
public void testNoServicesCtor() {
// create a 2-service context
final PluginIndex index = pluginIndex(BaseImpl.class, ExtensionImpl.class);
// Add another service, that is not indexed under Service.class
index.add(new PluginInfo<>(ThreadService.class.getName(),
SciJavaPlugin.class));
final Context c =
new Context(pluginIndex(BaseImpl.class, ExtensionImpl.class));
assertEquals(2, c.getServiceIndex().size());
}
/**
* Tests that missing-but-optional {@link Service}s are handled properly;
* specifically, that the {@link Context} is still created successfully when
* attempting to include a missing-but-optional service directly.
* <p>
* A service marked {@link Optional} is assumed to be optional for the context
* when requested for inclusion directly. (This behavior is, after all, one
* main reason for the {@link Optional} interface.)
* </p>
*/
@Test
public void testOptionalMissingDirect() {
final Context context = new Context(OptionalMissingService.class);
final OptionalMissingService optionalMissingService =
context.getService(OptionalMissingService.class);
assertNull(optionalMissingService);
// verify that there are *no* services in the context
assertEquals(0, context.getServiceIndex().size());
}
/**
* Tests that missing {@link Service}s marked with {@code required = false}
* are handled properly; specifically, that the {@link Context} is still
* created successfully when attempting to request (but not require) a missing
* service transitively.
*/
@Test
public void testNonRequiredMissingService() {
final Context context = new Context(ServiceWantingMissingService.class);
assertEquals(1, context.getServiceIndex().size());
final ServiceWantingMissingService serviceWantingMissingService =
context.getService(ServiceWantingMissingService.class);
assertNotNull(serviceWantingMissingService);
assertNull(serviceWantingMissingService.missingService);
final MissingService missingService =
context.getService(MissingService.class);
assertNull(missingService);
// verify that the *only* service is ServiceWantingMissing
assertEquals(1, context.getServiceIndex().size());
}
// -- Helper methods --
/**
* Checks the expected order vs. the order in the provided Context's
* ServiceIndex
*/
private void verifyServiceOrder(final Class<?>[] expected,
final Context context)
{
assertEquals(expected.length, context.getServiceIndex().size());
int index = 0;
for (final Service service : context.getServiceIndex()) {
assertSame(expected[index++], service.getClass());
}
}
/**
* Initializes and returns a Context given the provided PluginIndex and array
* of services.
*/
private Context createContext(final PluginIndex index,
final Class<? extends Service>[] services)
{
return new Context(Arrays.<Class<? extends Service>> asList(services),
index);
}
/**
* Creates a PluginIndex and adds all the provided classes as plugins, indexed
* under Service.class
*/
private PluginIndex pluginIndex(final Class<?>... plugins) {
final PluginIndex index = new PluginIndex(null);
for (final Class<?> c : plugins) {
index.add(new PluginInfo<>(c.getName(), Service.class));
}
return index;
}
// -- Helper classes --
/** A service which requires a {@link BarService}. */
public static class FooService extends AbstractService {
@Parameter
private BarService barService;
}
/** A service that is extended by {@link ExtensionService}. */
public static interface BaseService extends Service {
// NB: No implementation needed.
}
/** A service extending {@link BaseService}. */
public static interface ExtensionService extends BaseService {
// NB: No implementation needed.
}
/** A simple service with no dependencies. */
public static class BarService extends AbstractService {
// NB: No implementation needed.
}
/** A required service interface with no available implementation. */
public static interface MissingService extends Service {
// NB: Marker interface.
}
/** A optional service interface with no available implementation. */
public static interface OptionalMissingService extends Service, Optional {
// NB: Marker interface.
}
/** Abstract implementation of {@link BaseService}. */
public static abstract class AbstractBase extends AbstractService implements
BaseService
{
// NB: No implementation needed.
}
/** Abstract implementation of {@link ExtensionService}. */
public static abstract class AbstractExtension extends AbstractService
implements ExtensionService
{
// NB: No implementation needed.
}
/** Empty {@link BaseService} implementation. */
public static class BaseImpl extends AbstractBase {
// NB: No implementation needed.
}
/** Empty {@link ExtensionService} implementation. */
public static class ExtensionImpl extends AbstractExtension {
// NB: No implementation needed.
}
/** A service that is doomed to fail, for depending on a missing service. */
public static class ServiceRequiringMissingService extends AbstractService {
@Parameter
private MissingService missingService;
}
/**
* Another service that is doomed to fail. It depends on a missing service
* which, although optional, is marked with {@code required = true}
* (implicitly), and so cannot be filled.
*/
public static class ServiceRequiringOptionalMissingService extends
AbstractService
{
@Parameter
private OptionalMissingService optionalMissingService;
}
/**
* A service with a missing dependency marked {@code required = false}. This
* service should be able to be filled, since its missing service dependency
* is optional.
*/
public static class ServiceWantingMissingService extends AbstractService {
@Parameter(required = false)
private MissingService missingService;
}
}