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