1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.archive.osgi;
20
21 import static io.earcam.instrumental.archive.Archive.archive;
22 import static io.earcam.instrumental.archive.osgi.AsOsgiBundle.asOsgiBundle;
23 import static io.earcam.instrumental.module.osgi.BundleInfoBuilder.bundle;
24 import static io.earcam.instrumental.module.osgi.BundleManifestHeaders.IMPORT_PACKAGE;
25 import static io.earcam.instrumental.module.osgi.Clause.clause;
26 import static io.earcam.instrumental.module.osgi.ClauseParameters.EMPTY_PARAMETERS;
27 import static io.earcam.instrumental.module.osgi.ClauseParameters.ClauseParameter.attribute;
28 import static java.nio.charset.StandardCharsets.UTF_8;
29 import static org.hamcrest.MatcherAssert.assertThat;
30 import static org.hamcrest.Matchers.contains;
31 import static org.hamcrest.Matchers.containsString;
32 import static org.hamcrest.Matchers.equalTo;
33 import static org.hamcrest.Matchers.is;
34 import static org.hamcrest.Matchers.not;
35 import static org.hamcrest.Matchers.nullValue;
36 import static org.junit.jupiter.api.Assertions.fail;
37
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.util.Collections;
41 import java.util.List;
42 import java.util.function.Predicate;
43 import java.util.jar.Manifest;
44
45 import org.junit.jupiter.api.DisplayName;
46 import org.junit.jupiter.api.Nested;
47 import org.junit.jupiter.api.Test;
48
49 import io.earcam.acme.DummyInterface;
50 import io.earcam.acme.TakesExt;
51 import io.earcam.acme.auto.ImportsUnexceptional;
52 import io.earcam.acme.ext.Ext;
53 import io.earcam.acme.internal.DummyActivator;
54 import io.earcam.instrumental.archive.Archive;
55 import io.earcam.instrumental.archive.osgi.auto.AbstractPackageBundleMapper;
56 import io.earcam.instrumental.module.osgi.BundleInfo;
57 import io.earcam.instrumental.module.osgi.BundleInfoBuilder;
58 import io.earcam.instrumental.module.osgi.ClauseParameters;
59 import io.earcam.instrumental.module.osgi.ClauseParameters.ClauseParameter;
60 import io.earcam.instrumental.reflect.Names;
61 import io.earcam.unexceptional.Exceptional;
62
63 public class AsOsgiBundleTest {
64
65 @Nested
66 public class SymbolicNames {
67
68 @Test
69 public void emptyBundle()
70 {
71 Archive archive = archive()
72 .configured(asOsgiBundle()
73 .named("foo"))
74 .toObjectModel();
75
76 BundleInfo bundleInfo = bundleInfoFrom(archive);
77
78 assertThat(bundleInfo.symbolicName(), is(equalTo(clause("foo"))));
79 }
80
81
82 @Test
83 public void parameterizedSymbolicName()
84 {
85 ClauseParameters parameters = attribute("attri", "bute").directive("direct", "ive");
86
87 Archive archive = archive()
88 .configured(asOsgiBundle()
89 .named("sym.nom", parameters))
90 .toObjectModel();
91
92 BundleInfo bundleInfo = bundleInfoFrom(archive);
93
94 assertThat(bundleInfo.symbolicName(), is(equalTo(clause("sym.nom", parameters))));
95 }
96 }
97
98
99 private static BundleInfo bundleInfoFrom(Archive archive)
100 {
101 Manifest manifest = archive.manifest().orElseThrow(AssertionError::new);
102 ByteArrayOutputStream baos = new ByteArrayOutputStream();
103 Exceptional.accept(manifest::write, baos);
104 BundleInfoBuilder builder = BundleInfoBuilder.bundleFrom(new ByteArrayInputStream(baos.toByteArray()));
105 return builder.construct();
106 }
107
108
109 private static String pkg(Class<?> type)
110 {
111 return type.getPackage().getName();
112 }
113
114
115 private static byte[] bytes(String text)
116 {
117 return text.getBytes(UTF_8);
118 }
119
120 @Nested
121 public class Exports {
122
123 @Test
124 public void singleExportedClassWithNoParameters()
125 {
126 Archive archive = archive()
127 .configured(asOsgiBundle()
128 .named("mandatory.value")
129 .exporting(DummyInterface.class))
130 .toObjectModel();
131
132 BundleInfo bundleInfo = bundleInfoFrom(archive);
133
134 assertThat(bundleInfo.exportPackage(), contains(clause(pkg(DummyInterface.class))));
135 assertThat(archive.content(Names.typeToResourceName(DummyInterface.class)), is(not(nullValue())));
136 }
137
138
139 @Test
140 public void exportPatternWithParameters()
141 {
142 Predicate<String> matcher = Predicate.isEqual(pkg(DummyInterface.class));
143 ClauseParameters parameters = ClauseParameter.attribute("attr", "ibute");
144
145 Archive archive = archive()
146 .configured(asOsgiBundle()
147 .named("mandatory.value")
148 .exporting(matcher, parameters))
149 .with(DummyInterface.class)
150 .with(DummyActivator.class)
151 .toObjectModel();
152
153 BundleInfo bundleInfo = bundleInfoFrom(archive);
154
155 assertThat(bundleInfo.exportPackage(), contains(clause(pkg(DummyInterface.class), parameters)));
156 }
157
158
159 @DisplayName("Given multiple export patterns, When multiple potential matches occur, Then only first pattern applied")
160 @Test
161 public void givenMultipleExportPatternsWhenMultiplePotentialMatchesOccurThenOnlyFirstPatternApplied()
162 {
163 Predicate<String> matcher = Predicate.isEqual(pkg(DummyInterface.class));
164 ClauseParameters parameters1 = ClauseParameter.attribute("matcher", "1").directive("matcherNum", "one");
165 ClauseParameters parameters2 = ClauseParameter.attribute("matcher", "2");
166
167 Archive archive = archive()
168 .configured(asOsgiBundle()
169 .named("mandatory.sym.nom")
170 .exporting(matcher, parameters1)
171 .exporting(matcher, parameters2))
172 .with(DummyInterface.class)
173 .with(DummyActivator.class)
174 .toObjectModel();
175
176 BundleInfo bundleInfo = bundleInfoFrom(archive);
177
178 assertThat(bundleInfo.exportPackage(), contains(clause(pkg(DummyInterface.class), parameters1)));
179 }
180
181
182 @Test
183 public void exportPatternDoesNotExportResourcePaths() throws ReflectiveOperationException
184 {
185
186 Archive archive = archive()
187 .configured(asOsgiBundle()
188 .named("syn.nom")
189 .exporting(s -> true, EMPTY_PARAMETERS))
190 .with(DummyInterface.class)
191 .with("some/resource/path/config.properties", bytes("key1=valueA\nkey2=valueB"))
192 .with("/some/other/path/legal.md", bytes("IANAL Sorry"))
193 .toObjectModel();
194
195 BundleInfo bundleInfo = bundleInfoFrom(archive);
196
197 assertThat(bundleInfo.exportPackage(), contains(clause(pkg(DummyInterface.class))));
198 }
199
200
201 @Test
202 public void exportPatternDoesNotExportTheDefaultPackage() throws ReflectiveOperationException
203 {
204
205 Class<?> noPackage = getClass().getClassLoader().loadClass("NoPackage");
206 assertThat(noPackage, is(not(nullValue())));
207
208 Archive archive = archive()
209 .configured(asOsgiBundle()
210 .named("syn.nom")
211 .exporting(s -> true, EMPTY_PARAMETERS))
212 .with(DummyInterface.class)
213 .with(noPackage)
214 .toObjectModel();
215
216 BundleInfo bundleInfo = bundleInfoFrom(archive);
217
218 assertThat(bundleInfo.exportPackage(), contains(clause(pkg(DummyInterface.class))));
219 }
220 }
221
222 @Nested
223 public class Activators {
224
225 @Test
226 public void validActivator()
227 {
228
229 Archive archive = archive()
230 .configured(asOsgiBundle()
231 .named("sym.nom")
232 .withActivator(DummyActivator.class))
233 .toObjectModel();
234
235
236 BundleInfo bundleInfo = bundleInfoFrom(archive);
237
238 assertThat(bundleInfo.activator(), is(equalTo(cn(DummyActivator.class))));
239 }
240
241
242 private String cn(Class<?> type)
243 {
244 return type.getCanonicalName();
245 }
246
247
248 @Test
249 public void invalidActivator()
250 {
251 try {
252 asOsgiBundle()
253 .named("mandatory.value")
254 .withActivator(DummyInterface.class);
255 fail();
256 } catch(IllegalArgumentException e) {
257
258 }
259 }
260
261
262 @Test
263 public void invalidActivatorIgnoredWhenValidationDisabled()
264 {
265 Archive archive = archive()
266 .configured(asOsgiBundle()
267 .disableValidation()
268 .named("mandatory.value")
269 .withActivator(DummyInterface.class))
270 .toObjectModel();
271
272 BundleInfo bundleInfo = bundleInfoFrom(archive);
273
274 assertThat(bundleInfo.activator(), is(equalTo(cn(DummyInterface.class))));
275 }
276 }
277
278 @Nested
279 public class PatternImports {
280
281 @Test
282 public void importWithParameters()
283 {
284 String paquet = "com.acme.core.of.cores.root.of.all.apis";
285 Archive archive = archive()
286 .configured(asOsgiBundle()
287 .named("mandatory.value")
288 .importing(paquet))
289 .with(DummyInterface.class)
290 .toObjectModel();
291
292 BundleInfo bundleInfo = bundleInfoFrom(archive);
293
294 assertThat(bundleInfo.importPackage(), contains(clause(paquet)));
295 }
296
297 }
298
299 @Nested
300 public class AutoImports {
301
302 @Test
303 void autoImportsViaDefaultClasspathBundleMapper()
304 {
305 Archive built = archive()
306 .configured(asOsgiBundle()
307 .named("dependee")
308 .autoImporting())
309 .with(ImportsUnexceptional.class)
310 .with("some/resource.file", "Hello, World".getBytes(UTF_8))
311 .toObjectModel();
312
313 String value = built.manifest().get().getMainAttributes().getValue(IMPORT_PACKAGE.header());
314
315 assertThat(value, containsString(Exceptional.class.getPackage().getName()));
316 }
317
318
319 @Test
320 void autoImportsViaSuppliedPackageBundleMapper()
321 {
322 ClauseParameters version = attribute("version", "0.0.1-alpha");
323 String exported = pkg(Ext.class);
324
325 BundleInfo dependency = bundle()
326 .symbolicName("dependency")
327 .exportPackages(exported, version)
328 .construct();
329
330 PackageBundleMapper mapper = new AbstractPackageBundleMapper() {
331 @Override
332 protected List<BundleInfo> bundles()
333 {
334 return Collections.singletonList(dependency);
335 }
336 };
337
338 Archive built = archive()
339 .configured(asOsgiBundle()
340 .named("dependee")
341 .autoImporting(mapper))
342 .with(TakesExt.class)
343 .toObjectModel();
344
345 String value = built.manifest().get().getMainAttributes().getValue(IMPORT_PACKAGE.header());
346
347 assertThat(value, is(equalTo(clause(exported, version).toString())));
348 }
349
350
351 @Test
352 void autoImportsFailsWhenImportsUnmapped()
353 {
354
355 PackageBundleMapper mapper = new AbstractPackageBundleMapper() {
356 @Override
357 protected List<BundleInfo> bundles()
358 {
359 return Collections.emptyList();
360 }
361 };
362
363 try {
364 archive()
365 .configured(asOsgiBundle()
366 .named("dependee")
367 .autoImporting(mapper))
368 .with(TakesExt.class)
369 .toObjectModel();
370 fail();
371 } catch(IllegalStateException e) {
372
373 }
374 }
375
376
377 @Test
378 void unmappedImportsIgnoredWhenValidationDisabled()
379 {
380
381 PackageBundleMapper mapper = new AbstractPackageBundleMapper() {
382 @Override
383 protected List<BundleInfo> bundles()
384 {
385 return Collections.emptyList();
386 }
387 };
388
389 archive()
390 .configured(asOsgiBundle()
391 .disableValidation()
392 .named("dependee")
393 .autoImporting(mapper))
394 .with(TakesExt.class)
395 .toObjectModel();
396 }
397
398 }
399 }