View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.shade;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileOutputStream;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InputStreamReader;
28  import java.io.OutputStream;
29  import java.lang.reflect.Field;
30  import java.net.URL;
31  import java.net.URLClassLoader;
32  import java.nio.charset.StandardCharsets;
33  import java.nio.file.Files;
34  import java.nio.file.Paths;
35  import java.nio.file.attribute.FileTime;
36  import java.time.temporal.ChronoUnit;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collections;
40  import java.util.Enumeration;
41  import java.util.LinkedHashSet;
42  import java.util.List;
43  import java.util.Set;
44  import java.util.jar.JarEntry;
45  import java.util.jar.JarFile;
46  import java.util.jar.JarInputStream;
47  import java.util.jar.JarOutputStream;
48  import java.util.stream.Collectors;
49  import java.util.zip.CRC32;
50  import java.util.zip.ZipEntry;
51  
52  import org.apache.maven.plugin.MojoExecutionException;
53  import org.apache.maven.plugins.shade.filter.Filter;
54  import org.apache.maven.plugins.shade.relocation.Relocator;
55  import org.apache.maven.plugins.shade.relocation.SimpleRelocator;
56  import org.apache.maven.plugins.shade.resource.AppendingTransformer;
57  import org.apache.maven.plugins.shade.resource.ComponentsXmlResourceTransformer;
58  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
59  import org.apache.maven.plugins.shade.resource.ServicesResourceTransformer;
60  import org.codehaus.plexus.util.IOUtil;
61  import org.codehaus.plexus.util.Os;
62  import org.junit.Assert;
63  import org.junit.ClassRule;
64  import org.junit.Test;
65  import org.junit.rules.TemporaryFolder;
66  import org.mockito.ArgumentCaptor;
67  import org.objectweb.asm.ClassReader;
68  import org.objectweb.asm.ClassVisitor;
69  import org.objectweb.asm.Opcodes;
70  import org.slf4j.Logger;
71  
72  import static java.util.Arrays.asList;
73  import static java.util.Collections.singleton;
74  import static java.util.Objects.requireNonNull;
75  import static org.codehaus.plexus.util.FileUtils.forceMkdir;
76  import static org.hamcrest.CoreMatchers.containsString;
77  import static org.hamcrest.CoreMatchers.hasItem;
78  import static org.hamcrest.CoreMatchers.hasItems;
79  import static org.hamcrest.CoreMatchers.is;
80  import static org.hamcrest.MatcherAssert.assertThat;
81  import static org.junit.Assert.assertEquals;
82  import static org.junit.Assert.assertFalse;
83  import static org.junit.Assert.assertTrue;
84  import static org.mockito.Mockito.doNothing;
85  import static org.mockito.Mockito.mock;
86  import static org.mockito.Mockito.when;
87  
88  /**
89   * @author Jason van Zyl
90   * @author Mauro Talevi
91   */
92  public class DefaultShaderTest {
93      private static final String[] EXCLUDES =
94              new String[] {"org/codehaus/plexus/util/xml/Xpp3Dom", "org/codehaus/plexus/util/xml/pull.*"};
95  
96      @ClassRule
97      public static final TemporaryFolder TEMPORARY_FOLDER = new TemporaryFolder();
98  
99      private static final String NEWLINE = "\n";
100 
101     @Test
102     public void testNoopWhenNotRelocated() throws IOException, MojoExecutionException {
103         File plexusJar = new File("src/test/jars/plexus-utils-1.4.1.jar");
104         File shadedOutput = new File("target/foo-custom_testNoopWhenNotRelocated.jar");
105 
106         Set<File> jars = new LinkedHashSet<>();
107         jars.add(new File("src/test/jars/test-project-1.0-SNAPSHOT.jar"));
108         jars.add(plexusJar);
109 
110         Relocator relocator = new SimpleRelocator(
111                 "org/codehaus/plexus/util/cli",
112                 "relocated/plexus/util/cli",
113                 Collections.emptyList(),
114                 Collections.emptyList());
115 
116         ShadeRequest shadeRequest = new ShadeRequest();
117         shadeRequest.setJars(jars);
118         shadeRequest.setRelocators(Collections.singletonList(relocator));
119         shadeRequest.setResourceTransformers(Collections.emptyList());
120         shadeRequest.setFilters(Collections.emptyList());
121         shadeRequest.setUberJar(shadedOutput);
122 
123         DefaultShader shader = newShader();
124         shader.shade(shadeRequest);
125 
126         try (JarFile originalJar = new JarFile(plexusJar);
127                 JarFile shadedJar = new JarFile(shadedOutput)) {
128             // ASM processes all class files. In doing so, it modifies them, even when not relocating anything.
129             // Before MSHADE-391, the processed files were written to the uber JAR, which did no harm, but made it
130             // difficult to find out by simple file comparison, if a file was actually relocated or not. Now, Shade
131             // makes sure to always write the original file if the class neither was relocated itself nor references
132             // other, relocated classes. So we are checking for regressions here.
133             assertTrue(areEqual(originalJar, shadedJar, "org/codehaus/plexus/util/Expand.class"));
134 
135             // Relocated files should always be different, because they contain different package names in their byte
136             // code. We should verify this anyway, in order to avoid an existing class file from simply being moved to
137             // another location without actually having been relocated internally.
138             assertFalse(areEqual(
139                     originalJar,
140                     shadedJar,
141                     "org/codehaus/plexus/util/cli/Arg.class",
142                     "relocated/plexus/util/cli/Arg.class"));
143         }
144         int result = 0;
145         for (String msg : debugMessages.getAllValues()) {
146             if ("Rewrote class bytecode: org/codehaus/plexus/util/cli/Arg.class".equals(msg)) {
147                 result |= 1;
148             } else if ("Keeping original class bytecode: org/codehaus/plexus/util/Expand.class".equals(msg)) {
149                 result |= 2;
150             }
151         }
152         assertEquals(3 /* 1 | 2 */, result);
153     }
154 
155     @Test
156     public void testOverlappingResourcesAreLogged() throws IOException, MojoExecutionException {
157         DefaultShader shader = newShader();
158 
159         // we will shade two jars and expect to see META-INF/MANIFEST.MF overlaps, this will always be true
160         // but this can lead to a broken deployment if intended for OSGi or so, so even this should be logged
161         Set<File> set = new LinkedHashSet<>();
162         set.add(new File("src/test/jars/test-project-1.0-SNAPSHOT.jar"));
163         set.add(new File("src/test/jars/plexus-utils-1.4.1.jar"));
164 
165         ShadeRequest shadeRequest = new ShadeRequest();
166         shadeRequest.setJars(set);
167         shadeRequest.setRelocators(Collections.<Relocator>emptyList());
168         shadeRequest.setResourceTransformers(Collections.<ResourceTransformer>emptyList());
169         shadeRequest.setFilters(Collections.<Filter>emptyList());
170         shadeRequest.setUberJar(new File("target/foo-custom_testOverlappingResourcesAreLogged.jar"));
171         shader.shade(shadeRequest);
172 
173         assertThat(
174                 warnMessages.getAllValues(),
175                 hasItem(containsString(
176                         "plexus-utils-1.4.1.jar, test-project-1.0-SNAPSHOT.jar define 1 overlapping resource:")));
177         assertThat(warnMessages.getAllValues(), hasItem(containsString("- META-INF/MANIFEST.MF")));
178         if (Os.isFamily(Os.FAMILY_WINDOWS)) {
179             assertThat(
180                     debugMessages.getAllValues(),
181                     hasItem(containsString(
182                             "We have a duplicate META-INF/MANIFEST.MF in src\\test\\jars\\plexus-utils-1.4.1.jar")));
183         } else {
184             assertThat(
185                     debugMessages.getAllValues(),
186                     hasItem(containsString(
187                             "We have a duplicate META-INF/MANIFEST.MF in src/test/jars/plexus-utils-1.4.1.jar")));
188         }
189     }
190 
191     @Test
192     public void testOverlappingResourcesAreLoggedExceptATransformerHandlesIt() throws Exception {
193         TemporaryFolder temporaryFolder = new TemporaryFolder();
194         try {
195             Set<File> set = new LinkedHashSet<>();
196             temporaryFolder.create();
197             File j1 = temporaryFolder.newFile("j1.jar");
198             try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(j1))) {
199                 jos.putNextEntry(new JarEntry("foo.txt"));
200                 jos.write("c1".getBytes(StandardCharsets.UTF_8));
201                 jos.closeEntry();
202             }
203             File j2 = temporaryFolder.newFile("j2.jar");
204             try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(j2))) {
205                 jos.putNextEntry(new JarEntry("foo.txt"));
206                 jos.write("c2".getBytes(StandardCharsets.UTF_8));
207                 jos.closeEntry();
208             }
209             set.add(j1);
210             set.add(j2);
211 
212             AppendingTransformer transformer = new AppendingTransformer();
213             Field resource = AppendingTransformer.class.getDeclaredField("resource");
214             resource.setAccessible(true);
215             resource.set(transformer, "foo.txt");
216 
217             ShadeRequest shadeRequest = new ShadeRequest();
218             shadeRequest.setJars(set);
219             shadeRequest.setRelocators(Collections.emptyList());
220             shadeRequest.setResourceTransformers(Collections.singletonList(transformer));
221             shadeRequest.setFilters(Collections.emptyList());
222             shadeRequest.setUberJar(new File("target/foo-custom_testOverlappingResourcesAreLogged.jar"));
223 
224             DefaultShader shaderWithTransformer = newShader();
225             shaderWithTransformer.shade(shadeRequest);
226 
227             assertThat(warnMessages.getAllValues().size(), is(0));
228 
229             DefaultShader shaderWithoutTransformer = newShader();
230             shadeRequest.setResourceTransformers(Collections.emptyList());
231             shaderWithoutTransformer.shade(shadeRequest);
232 
233             assertThat(
234                     warnMessages.getAllValues(),
235                     hasItems(containsString("j1.jar, j2.jar define 1 overlapping resource:")));
236             assertThat(warnMessages.getAllValues(), hasItems(containsString("- foo.txt")));
237         } finally {
238             temporaryFolder.delete();
239         }
240     }
241 
242     @Test
243     public void testShaderWithDefaultShadedPattern() throws Exception {
244         shaderWithPattern(null, new File("target/foo-default.jar"), EXCLUDES);
245     }
246 
247     @Test
248     public void testShaderWithStaticInitializedClass() throws Exception {
249         Shader s = newShader();
250 
251         Set<File> set = new LinkedHashSet<>();
252 
253         set.add(new File("src/test/jars/test-artifact-1.0-SNAPSHOT.jar"));
254 
255         List<Relocator> relocators = new ArrayList<>();
256 
257         relocators.add(new SimpleRelocator("org.apache.maven.plugins.shade", null, null, null));
258 
259         List<ResourceTransformer> resourceTransformers = new ArrayList<>();
260 
261         List<Filter> filters = new ArrayList<>();
262 
263         File file = new File("target/testShaderWithStaticInitializedClass.jar");
264 
265         ShadeRequest shadeRequest = new ShadeRequest();
266         shadeRequest.setJars(set);
267         shadeRequest.setUberJar(file);
268         shadeRequest.setFilters(filters);
269         shadeRequest.setRelocators(relocators);
270         shadeRequest.setResourceTransformers(resourceTransformers);
271 
272         s.shade(shadeRequest);
273 
274         try (URLClassLoader cl = new URLClassLoader(new URL[] {file.toURI().toURL()})) {
275             Class<?> c = cl.loadClass("hidden.org.apache.maven.plugins.shade.Lib");
276             Object o = c.newInstance();
277             assertEquals("foo.bar/baz", c.getDeclaredField("CONSTANT").get(o));
278         }
279     }
280 
281     @Test
282     public void testShaderWithCustomShadedPattern() throws Exception {
283         shaderWithPattern("org/shaded/plexus/util", new File("target/foo-custom.jar"), EXCLUDES);
284     }
285 
286     @Test
287     public void testShaderWithoutExcludesShouldRemoveReferencesOfOriginalPattern() throws Exception {
288         // FIXME: shaded jar should not include references to org/codehaus/* (empty dirs) or org.codehaus.* META-INF
289         // files.
290         shaderWithPattern(
291                 "org/shaded/plexus/util", new File("target/foo-custom-without-excludes.jar"), new String[] {});
292     }
293 
294     @Test
295     public void testHandleDirectory() throws Exception {
296         final File dir = TEMPORARY_FOLDER.getRoot();
297         // explode src/test/jars/test-artifact-1.0-SNAPSHOT.jar in this temp dir
298         try (JarInputStream in =
299                 new JarInputStream(Files.newInputStream(Paths.get("src/test/jars/test-artifact-1.0-SNAPSHOT.jar")))) {
300             JarEntry nextJarEntry;
301             while ((nextJarEntry = in.getNextJarEntry()) != null) {
302                 if (nextJarEntry.isDirectory()) {
303                     continue;
304                 }
305                 File out = new File(dir, nextJarEntry.getName());
306                 forceMkdir(out.getParentFile());
307                 try (OutputStream outputStream = Files.newOutputStream(out.toPath())) {
308                     IOUtil.copy(in, outputStream, (int) Math.max(nextJarEntry.getSize(), 512));
309                 }
310             }
311         }
312 
313         // do shade
314         File shade = new File("target/testHandleDirectory.jar");
315         shaderWithPattern("org/shaded/plexus/util", shade, new String[0], singleton(dir));
316 
317         // ensure directory was shaded properly
318         try (JarFile jar = new JarFile(shade)) {
319             List<String> entries = new ArrayList<>();
320             Enumeration<JarEntry> jarEntryEnumeration = jar.entries();
321             while (jarEntryEnumeration.hasMoreElements()) {
322                 JarEntry jarEntry = jarEntryEnumeration.nextElement();
323                 if (jarEntry.isDirectory()) {
324                     continue;
325                 }
326                 entries.add(jarEntry.getName());
327             }
328             Collections.sort(entries);
329             assertEquals(
330                     asList(
331                             "META-INF/maven/org.apache.maven.plugins.shade/test-artifact/pom.properties",
332                             "META-INF/maven/org.apache.maven.plugins.shade/test-artifact/pom.xml",
333                             "org/apache/maven/plugins/shade/Lib.class"),
334                     entries);
335         }
336     }
337 
338     @Test
339     public void testShaderWithRelocatedClassname() throws Exception {
340         DefaultShader s = newShader();
341 
342         Set<File> set = new LinkedHashSet<>();
343 
344         set.add(new File("src/test/jars/test-project-1.0-SNAPSHOT.jar"));
345 
346         set.add(new File("src/test/jars/plexus-utils-1.4.1.jar"));
347 
348         List<Relocator> relocators = new ArrayList<>();
349 
350         relocators.add(
351                 new SimpleRelocator("org/codehaus/plexus/util/", "_plexus/util/__", null, Collections.emptyList()));
352 
353         List<ResourceTransformer> resourceTransformers = new ArrayList<>();
354 
355         resourceTransformers.add(new ComponentsXmlResourceTransformer());
356 
357         List<Filter> filters = new ArrayList<>();
358 
359         File file = new File("target/foo-relocate-class.jar");
360 
361         ShadeRequest shadeRequest = new ShadeRequest();
362         shadeRequest.setJars(set);
363         shadeRequest.setUberJar(file);
364         shadeRequest.setFilters(filters);
365         shadeRequest.setRelocators(relocators);
366         shadeRequest.setResourceTransformers(resourceTransformers);
367 
368         s.shade(shadeRequest);
369 
370         try (URLClassLoader cl = new URLClassLoader(new URL[] {file.toURI().toURL()})) {
371             Class<?> c = cl.loadClass("_plexus.util.__StringUtils");
372             // first, ensure it works:
373             Object o = c.newInstance();
374             assertEquals("", c.getMethod("clean", String.class).invoke(o, (String) null));
375 
376             // now, check that its source file was rewritten:
377             String[] source = {null};
378             ClassReader classReader = new ClassReader(cl.getResourceAsStream("_plexus/util/__StringUtils.class"));
379             classReader.accept(
380                     new ClassVisitor(Opcodes.ASM4) {
381                         @Override
382                         public void visitSource(String arg0, String arg1) {
383                             super.visitSource(arg0, arg1);
384                             source[0] = arg0;
385                         }
386                     },
387                     ClassReader.SKIP_CODE);
388             assertEquals("__StringUtils.java", source[0]);
389         }
390     }
391 
392     @Test
393     public void testShaderWithNestedJar() throws Exception {
394         TemporaryFolder temporaryFolder = new TemporaryFolder();
395 
396         final String innerJarFileName = "inner.jar";
397 
398         temporaryFolder.create();
399         File innerJar = temporaryFolder.newFile(innerJarFileName);
400         try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(innerJar.toPath()))) {
401             jos.putNextEntry(new JarEntry("foo.txt"));
402             jos.write("c1".getBytes(StandardCharsets.UTF_8));
403             jos.closeEntry();
404         }
405 
406         ShadeRequest shadeRequest = new ShadeRequest();
407         shadeRequest.setJars(new LinkedHashSet<>(Collections.singleton(innerJar)));
408         shadeRequest.setFilters(Collections.emptyList());
409         shadeRequest.setRelocators(Collections.emptyList());
410         shadeRequest.setResourceTransformers(Collections.emptyList());
411         File shadedFile = temporaryFolder.newFile("shaded.jar");
412         shadeRequest.setUberJar(shadedFile);
413 
414         DefaultShader shader = newShader();
415         shader.shade(shadeRequest);
416 
417         FileTime lastModified = FileTime.from(
418                 Files.getLastModifiedTime(shadedFile.toPath()).toInstant().minus(5, ChronoUnit.SECONDS));
419 
420         Files.setLastModifiedTime(shadedFile.toPath(), lastModified);
421 
422         shader.shade(shadeRequest);
423         assertEquals(lastModified, Files.getLastModifiedTime(shadedFile.toPath()));
424 
425         temporaryFolder.delete();
426     }
427 
428     @Test
429     public void testShaderNoOverwrite() throws Exception {
430         TemporaryFolder temporaryFolder = new TemporaryFolder();
431 
432         final String innerJarFileName = "inner.jar";
433 
434         temporaryFolder.create();
435         File innerJar = temporaryFolder.newFile(innerJarFileName);
436         try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(innerJar))) {
437             jos.putNextEntry(new JarEntry("foo.txt"));
438             jos.write("c1".getBytes(StandardCharsets.UTF_8));
439             jos.closeEntry();
440         }
441 
442         File outerJar = temporaryFolder.newFile("outer.jar");
443         try (JarOutputStream jos = new JarOutputStream(new FileOutputStream(outerJar))) {
444             FileInputStream innerStream = new FileInputStream(innerJar);
445             byte[] bytes = IOUtil.toByteArray(innerStream, 32 * 1024);
446             innerStream.close();
447             writeEntryWithoutCompression(innerJarFileName, bytes, jos);
448         }
449 
450         ShadeRequest shadeRequest = new ShadeRequest();
451         shadeRequest.setJars(new LinkedHashSet<>(Collections.singleton(outerJar)));
452         shadeRequest.setFilters(new ArrayList<>());
453         shadeRequest.setRelocators(new ArrayList<>());
454         shadeRequest.setResourceTransformers(new ArrayList<>());
455         File shadedFile = temporaryFolder.newFile("shaded.jar");
456         shadeRequest.setUberJar(shadedFile);
457 
458         DefaultShader shader = newShader();
459         shader.shade(shadeRequest);
460 
461         JarFile shadedJarFile = new JarFile(shadedFile);
462         JarEntry entry = shadedJarFile.getJarEntry(innerJarFileName);
463 
464         // After shading, entry compression method should not be changed.
465         Assert.assertEquals(entry.getMethod(), ZipEntry.STORED);
466 
467         temporaryFolder.delete();
468     }
469 
470     @Test
471     public void testShaderWithDuplicateService() throws Exception {
472         TemporaryFolder temporaryFolder = new TemporaryFolder();
473         temporaryFolder.create();
474 
475         String serviceEntryName = "META-INF/services/my.foo.Service";
476         String serviceEntryValue = "my.foo.impl.Service1";
477 
478         File innerJar1 = temporaryFolder.newFile("inner1.jar");
479         try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(innerJar1.toPath()))) {
480             jos.putNextEntry(new JarEntry(serviceEntryName));
481             jos.write((serviceEntryValue + NEWLINE).getBytes(StandardCharsets.UTF_8));
482             jos.closeEntry();
483         }
484 
485         File innerJar2 = temporaryFolder.newFile("inner2.jar");
486         try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(innerJar2.toPath()))) {
487             jos.putNextEntry(new JarEntry(serviceEntryName));
488             jos.write((serviceEntryValue + NEWLINE).getBytes(StandardCharsets.UTF_8));
489             jos.closeEntry();
490         }
491 
492         ShadeRequest shadeRequest = new ShadeRequest();
493         shadeRequest.setJars(new LinkedHashSet<>(Arrays.asList(innerJar1, innerJar2)));
494         shadeRequest.setFilters(Collections.emptyList());
495         shadeRequest.setRelocators(Collections.emptyList());
496         shadeRequest.setResourceTransformers(Collections.singletonList(new ServicesResourceTransformer()));
497         File shadedFile = temporaryFolder.newFile("shaded.jar");
498         shadeRequest.setUberJar(shadedFile);
499 
500         DefaultShader shader = newShader();
501         shader.shade(shadeRequest);
502 
503         JarFile shadedJarFile = new JarFile(shadedFile);
504         JarEntry entry = shadedJarFile.getJarEntry(serviceEntryName);
505 
506         List<String> lines = new BufferedReader(
507                         new InputStreamReader(shadedJarFile.getInputStream(entry), StandardCharsets.UTF_8))
508                 .lines()
509                 .collect(Collectors.toList());
510 
511         // After shading, there should be a single input
512         Assert.assertEquals(Collections.singletonList(serviceEntryValue), lines);
513 
514         temporaryFolder.delete();
515     }
516 
517     @Test
518     public void testShaderWithSmallEntries() throws Exception {
519         TemporaryFolder temporaryFolder = new TemporaryFolder();
520 
521         final String innerJarFileName = "inner.jar";
522         int len;
523 
524         temporaryFolder.create();
525         File innerJar = temporaryFolder.newFile(innerJarFileName);
526         try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(innerJar.toPath()))) {
527             jos.putNextEntry(new JarEntry("foo.txt"));
528             byte[] bytes = "c1".getBytes(StandardCharsets.UTF_8);
529             len = bytes.length;
530             jos.write(bytes);
531             jos.closeEntry();
532         }
533 
534         ShadeRequest shadeRequest = new ShadeRequest();
535         shadeRequest.setJars(new LinkedHashSet<>(Collections.singleton(innerJar)));
536         shadeRequest.setFilters(new ArrayList<>());
537         shadeRequest.setRelocators(new ArrayList<>());
538         shadeRequest.setResourceTransformers(new ArrayList<>());
539         File shadedFile = temporaryFolder.newFile("shaded.jar");
540         shadeRequest.setUberJar(shadedFile);
541 
542         DefaultShader shader = newShader();
543         shader.shade(shadeRequest);
544 
545         JarFile shadedJarFile = new JarFile(shadedFile);
546         JarEntry entry = shadedJarFile.getJarEntry("foo.txt");
547 
548         // After shading, entry compression method should not be changed.
549         Assert.assertEquals(entry.getSize(), len);
550 
551         temporaryFolder.delete();
552     }
553 
554     private void writeEntryWithoutCompression(String entryName, byte[] entryBytes, JarOutputStream jos)
555             throws IOException {
556         final JarEntry entry = new JarEntry(entryName);
557         final int size = entryBytes.length;
558         final CRC32 crc = new CRC32();
559         crc.update(entryBytes, 0, size);
560         entry.setSize(size);
561         entry.setCompressedSize(size);
562         entry.setMethod(ZipEntry.STORED);
563         entry.setCrc(crc.getValue());
564         jos.putNextEntry(entry);
565         jos.write(entryBytes);
566         jos.closeEntry();
567     }
568 
569     private void shaderWithPattern(String shadedPattern, File jar, String[] excludes) throws Exception {
570         Set<File> set = new LinkedHashSet<>();
571         set.add(new File("src/test/jars/test-project-1.0-SNAPSHOT.jar"));
572         set.add(new File("src/test/jars/plexus-utils-1.4.1.jar"));
573         shaderWithPattern(shadedPattern, jar, excludes, set);
574     }
575 
576     private void shaderWithPattern(String shadedPattern, File jar, String[] excludes, Set<File> set) throws Exception {
577         DefaultShader s = newShader();
578 
579         List<Relocator> relocators = new ArrayList<>();
580 
581         relocators.add(new SimpleRelocator("org/codehaus/plexus/util", shadedPattern, null, Arrays.asList(excludes)));
582 
583         List<ResourceTransformer> resourceTransformers = new ArrayList<>();
584 
585         resourceTransformers.add(new ComponentsXmlResourceTransformer());
586 
587         List<Filter> filters = new ArrayList<>();
588 
589         ShadeRequest shadeRequest = new ShadeRequest();
590         shadeRequest.setJars(set);
591         shadeRequest.setUberJar(jar);
592         shadeRequest.setFilters(filters);
593         shadeRequest.setRelocators(relocators);
594         shadeRequest.setResourceTransformers(resourceTransformers);
595 
596         s.shade(shadeRequest);
597     }
598 
599     private DefaultShader newShader() {
600         return new DefaultShader(mockLogger());
601     }
602 
603     private ArgumentCaptor<String> debugMessages;
604 
605     private ArgumentCaptor<String> warnMessages;
606 
607     private Logger mockLogger() {
608         debugMessages = ArgumentCaptor.forClass(String.class);
609         warnMessages = ArgumentCaptor.forClass(String.class);
610         Logger logger = mock(Logger.class);
611         when(logger.isDebugEnabled()).thenReturn(true);
612         when(logger.isWarnEnabled()).thenReturn(true);
613         doNothing().when(logger).debug(debugMessages.capture());
614         doNothing().when(logger).warn(warnMessages.capture());
615         return logger;
616     }
617 
618     private boolean areEqual(final JarFile jar1, final JarFile jar2, final String entry) throws IOException {
619         return areEqual(jar1, jar2, entry, entry);
620     }
621 
622     private boolean areEqual(final JarFile jar1, final JarFile jar2, final String entry1, String entry2)
623             throws IOException {
624         try (InputStream s1 = jar1.getInputStream(
625                         requireNonNull(jar1.getJarEntry(entry1), entry1 + " in " + jar1.getName()));
626                 InputStream s2 = jar2.getInputStream(
627                         requireNonNull(jar2.getJarEntry(entry2), entry2 + " in " + jar2.getName()))) {
628             return Arrays.equals(IOUtil.toByteArray(s1), IOUtil.toByteArray(s2));
629         }
630     }
631 }