1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package io.earcam.instrumental.archive;
20
21 import static io.earcam.instrumental.archive.Archive.archive;
22 import static io.earcam.instrumental.archive.AsJar.asJar;
23 import static io.earcam.instrumental.archive.Hamcrest.present;
24 import static io.earcam.instrumental.reflect.Names.typeToResourceName;
25 import static java.lang.Boolean.TRUE;
26 import static java.nio.charset.StandardCharsets.UTF_8;
27 import static java.util.jar.Attributes.Name.MAIN_CLASS;
28 import static java.util.stream.Collectors.toList;
29 import static org.hamcrest.MatcherAssert.assertThat;
30 import static org.hamcrest.Matchers.allOf;
31 import static org.hamcrest.Matchers.contains;
32 import static org.hamcrest.Matchers.containsInAnyOrder;
33 import static org.hamcrest.Matchers.empty;
34 import static org.hamcrest.Matchers.equalTo;
35 import static org.hamcrest.Matchers.hasEntry;
36 import static org.hamcrest.Matchers.hasSize;
37 import static org.hamcrest.Matchers.is;
38 import static org.hamcrest.Matchers.nullValue;
39 import static org.junit.jupiter.api.Assertions.fail;
40
41 import java.io.InputStream;
42 import java.util.Comparator;
43 import java.util.List;
44 import java.util.ServiceLoader;
45 import java.util.function.Predicate;
46 import java.util.jar.Attributes;
47 import java.util.jar.Attributes.Name;
48 import java.util.jar.Manifest;
49 import java.util.stream.Stream;
50 import java.util.stream.StreamSupport;
51
52 import org.junit.jupiter.api.Test;
53
54 import io.earcam.acme.CaseInsensitiveToStringComparator;
55 import io.earcam.acme.DummyMain;
56 import io.earcam.acme.WhitespaceIgnorantToStringComparator;
57 import io.earcam.acme.other.Other;
58 import io.earcam.instrumental.lade.ClassLoaders;
59 import io.earcam.instrumental.lade.InMemoryClassLoader;
60 import io.earcam.utilitarian.io.IoStreams;
61
62 public class AsJarTest {
63
64 @Test
65 void emptyArchiveHasManifest()
66 {
67 Archive archive = archive()
68 .configured(asJar())
69 .toObjectModel();
70
71 assertThat(archive.contents(), is(empty()));
72
73 assertValidJarManifest(archive);
74 }
75
76
77 private void assertValidJarManifest(Archive archive)
78 {
79 assertThat(archive.manifest(), is(present()));
80
81 String manifestVersion = archive.manifest().get().getMainAttributes().getValue(Name.MANIFEST_VERSION);
82 assertThat(manifestVersion, is(equalTo(AbstractAsJarBuilder.V1_0)));
83 }
84
85
86 @Test
87 void singleContent()
88 {
89 String name = "/path/to/some.file";
90 byte[] contents = "Some Content".getBytes(UTF_8);
91
92 Archive archive = archive()
93 .configured(asJar())
94 .with(name, contents)
95 .toObjectModel();
96
97 assertThat(archive.contents(), hasSize(1));
98 assertThat(archive.contents(), contains(new ArchiveResource(name, contents)));
99
100 assertValidJarManifest(archive);
101 }
102
103
104 @Test
105 void mainClass()
106 {
107
108
109 Archive archive = archive()
110 .configured(asJar()
111 .launching(DummyMain.class))
112 .toObjectModel();
113
114
115
116 assertValidJarManifest(archive);
117
118 Attributes attributes = archive.manifest().get().getMainAttributes();
119 assertThat(attributes, hasEntry(MAIN_CLASS, cn(DummyMain.class)));
120
121 assertThat(archive.contents(), contains(new ArchiveResource(typeToResourceName(DummyMain.class), new byte[0])));
122 }
123
124
125 @Test
126 void invalidMainClassFailsFastWhenNoSuchMethodNameFound()
127 {
128 try {
129 asJar().launching(AsJar.class);
130 fail();
131 } catch(IllegalArgumentException e) {}
132 }
133
134
135 @Test
136 void invalidMainClassIgnoredWhenValidationDisabled()
137 {
138 asJar()
139 .disableValidation()
140 .launching(AsJar.class);
141 }
142
143
144 public static boolean main(String[] ahhargs)
145 {
146 return false;
147 }
148
149
150 @Test
151 void invalidMainClassFailsFastWhenMainMethodInvalid()
152 {
153 try {
154 asJar().launching(AsJarTest.class);
155 fail();
156 } catch(IllegalArgumentException e) {}
157 }
158
159
160 @Test
161 void manifestMainAttributesAreMerged()
162 {
163
164 Manifest manifest = new Manifest();
165 manifest.getMainAttributes().putValue("Existing-Header", "Existing Value");
166
167 Archive archive = archive()
168 .configured(asJar()
169 .mergingManifest(manifest)
170 .launching(DummyMain.class))
171 .toObjectModel();
172
173 assertValidJarManifest(archive);
174
175 Attributes attributes = archive.manifest().get().getMainAttributes();
176 assertThat(attributes, allOf(
177 hasEntry(MAIN_CLASS, cn(DummyMain.class)),
178 hasEntry(new Name("Existing-Header"), "Existing Value")));
179 }
180
181
182 @Test
183 void manifestNamedEntriesAreMerged()
184 {
185
186 Manifest manifestA = new Manifest();
187 Attributes entryA = new Attributes();
188 entryA.putValue("Merged", "By A");
189 manifestA.getEntries().put("named", entryA);
190
191 Manifest manifestB = new Manifest();
192 Attributes entryB = new Attributes();
193 entryB.putValue("Merged-From", "B");
194 manifestB.getEntries().put("named", entryB);
195
196 Archive archive = archive()
197 .configured(asJar()
198 .mergingManifest(manifestA)
199 .mergingManifest(manifestB)
200 .withManifestHeader("Main", "Attribute"))
201 .toObjectModel();
202
203 assertValidJarManifest(archive);
204
205 Attributes attributes = archive.manifest().get().getAttributes("named");
206
207 Attributes expected = new Attributes();
208 expected.putValue("Merged", "By A");
209 expected.putValue("Merged-From", "B");
210 assertThat(attributes, is(equalTo(expected)));
211
212 assertThat(archive.manifest().get().getMainAttributes(), hasEntry(new Name("Main"), "Attribute"));
213 }
214
215
216 @Test
217 void mainClassFailsFastIfMainMethodNotFound()
218 {
219 try {
220 asJar().launching(AsJarTest.class);
221 fail();
222 } catch(Exception e) {}
223 }
224
225
226 @Test
227 void singleSpiSingleImplementation()
228 {
229 byte[] archive = archive()
230 .configured(asJar()
231 .providing(Comparator.class, CaseInsensitiveToStringComparator.class))
232 .toByteArray();
233
234 try(InMemoryClassLoader classLoader = ClassLoaders.inMemoryClassLoader().jar(archive)) {
235
236 List<String> implementations = spiServices(Comparator.class, classLoader);
237
238 assertThat(implementations, contains(cn(CaseInsensitiveToStringComparator.class)));
239 }
240 }
241
242
243 private static String cn(Class<?> type)
244 {
245 return type.getCanonicalName();
246 }
247
248
249 @Test
250 void singleSpiMultipleImplementations()
251 {
252
253
254 byte[] archive = archive().configured(asJar()
255 .providing(Comparator.class,
256 CaseInsensitiveToStringComparator.class,
257 WhitespaceIgnorantToStringComparator.class))
258 .toByteArray();
259
260 ClassLoader loader = ClassLoaders.inMemoryClassLoader().jar(archive);
261
262 List<String> imps = spiServices(Comparator.class, loader);
263
264 assertThat(imps, containsInAnyOrder(
265 cn(CaseInsensitiveToStringComparator.class),
266 cn(WhitespaceIgnorantToStringComparator.class)));
267
268
269 }
270
271
272 @Test
273 void singleSpiMultipleImplementationsAsInputStream()
274 {
275
276 InputStream input = archive().configured(asJar()
277 .providing(Comparator.class,
278 CaseInsensitiveToStringComparator.class,
279 WhitespaceIgnorantToStringComparator.class))
280 .toInputStream();
281
282 byte[] archive = IoStreams.readAllBytes(input);
283
284 ClassLoader loader = ClassLoaders.inMemoryClassLoader().jar(archive);
285
286 List<String> imps = spiServices(Comparator.class, loader);
287
288 assertThat(imps, containsInAnyOrder(
289 cn(CaseInsensitiveToStringComparator.class),
290 cn(WhitespaceIgnorantToStringComparator.class)));
291
292 }
293
294
295 private List<String> spiServices(Class<?> service, ClassLoader classLoader)
296 {
297 return streamSpiServices(service, classLoader)
298 .map(i -> cn(i.getClass()))
299 .collect(toList());
300 }
301
302
303 private <T> Stream<T> streamSpiServices(Class<T> service, ClassLoader classLoader)
304 {
305 return StreamSupport.stream(ServiceLoader.load(service, classLoader).spliterator(), false);
306 }
307
308
309 @Test
310 void providingFailsFastWhenServicesDoesNotImplementInterface()
311 {
312 try {
313 asJar().providing(Comparator.class, Other.class);
314 fail();
315 } catch(IllegalArgumentException e) {}
316 }
317
318
319 @Test
320 void invalidProvidingIgnoredWhenValidationDisabled()
321 {
322 asJar()
323 .disableValidation()
324 .providing(Comparator.class, Other.class);
325 }
326
327
328 @Test
329 void sealing()
330 {
331 Predicate<String> packageMatcher = Predicate.isEqual(pkg(WhitespaceIgnorantToStringComparator.class));
332
333 Archive jar = archive()
334 .configured(asJar()
335 .sealing(packageMatcher))
336 .with(WhitespaceIgnorantToStringComparator.class, Other.class)
337 .toObjectModel();
338
339 Manifest manifest = jar.manifest().get();
340
341 assertThat(manifest.getAttributes(pkg(Other.class)), is(nullValue()));
342 assertThat(manifest.getAttributes(pkg(WhitespaceIgnorantToStringComparator.class)), hasEntry(Name.SEALED, TRUE.toString()));
343 }
344
345
346 private String pkg(Class<?> type)
347 {
348 return type.getPackage().getName();
349 }
350 }