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