View Javadoc
1   package org.apache.maven.plugins.shade.resource.rule;
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 static java.lang.annotation.ElementType.METHOD;
23  import static java.lang.annotation.RetentionPolicy.RUNTIME;
24  import static org.junit.Assert.assertEquals;
25  import static org.junit.Assert.assertNotNull;
26  import static org.junit.Assert.assertTrue;
27  import static org.junit.Assert.fail;
28  
29  import java.io.ByteArrayInputStream;
30  import java.io.ByteArrayOutputStream;
31  import java.io.IOException;
32  import java.lang.annotation.Retention;
33  import java.lang.annotation.Target;
34  import java.nio.charset.StandardCharsets;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.Map;
38  import java.util.jar.JarEntry;
39  import java.util.jar.JarInputStream;
40  import java.util.jar.JarOutputStream;
41  
42  import org.apache.maven.plugins.shade.relocation.Relocator;
43  import org.apache.maven.plugins.shade.resource.ResourceTransformer;
44  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
45  import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter;
46  import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
47  import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
48  import org.codehaus.plexus.component.configurator.expression.DefaultExpressionEvaluator;
49  import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
50  import org.codehaus.plexus.configuration.PlexusConfiguration;
51  import org.junit.rules.TestRule;
52  import org.junit.runner.Description;
53  import org.junit.runners.model.Statement;
54  
55  public class TransformerTesterRule implements TestRule
56  {
57      @Override
58      public Statement apply( final Statement base, final Description description )
59      {
60          return new Statement()
61          {
62              @Override
63              public void evaluate() throws Throwable
64              {
65                  final TransformerTest spec = description.getAnnotation( TransformerTest.class );
66                  if ( spec == null )
67                  {
68                      base.evaluate();
69                      return;
70                  }
71  
72                  final Map<String, String> jar;
73                  try
74                  {
75                      final ResourceTransformer transformer = createTransformer(spec);
76                      visit(spec, transformer);
77                      jar = captureOutput(transformer);
78                  }
79                  catch ( final Exception ex )
80                  {
81                      if ( Exception.class.isAssignableFrom( spec.expectedException() ) )
82                      {
83                          assertTrue(
84                                  ex.getClass().getName(),
85                                  spec.expectedException().isAssignableFrom( ex.getClass() ) );
86                          return;
87                      }
88                      else
89                      {
90                          throw ex;
91                      }
92                  }
93                  asserts(spec, jar);
94              }
95          };
96      }
97  
98      private void asserts( final TransformerTest spec, final Map<String, String> jar)
99      {
100         if ( spec.strictMatch() && jar.size() != spec.expected().length )
101         {
102             fail( "Strict match test failed: " + jar );
103         }
104         for ( final Resource expected : spec.expected() )
105         {
106             final String content = jar.get( expected.path() );
107             assertNotNull( expected.path(), content );
108             assertTrue(
109                     expected.path() + ", expected=" + expected.content() + ", actual=" + content,
110                     content.replace( System.lineSeparator(), "\n" ) .matches( expected.content() ) );
111         }
112     }
113 
114     private Map<String, String> captureOutput(final ResourceTransformer transformer ) throws IOException
115     {
116         final ByteArrayOutputStream out = new ByteArrayOutputStream();
117         try ( final JarOutputStream jar = new JarOutputStream( out ) )
118         {
119             transformer.modifyOutputStream( jar );
120         }
121 
122         final Map<String, String> created = new HashMap<>();
123         try ( final JarInputStream jar = new JarInputStream( new ByteArrayInputStream( out.toByteArray() ) ) )
124         {
125             JarEntry entry;
126             while ( ( entry = jar.getNextJarEntry() ) != null )
127             {
128                 created.put( entry.getName(), read( jar ) );
129             }
130         }
131         return created;
132     }
133 
134     private void visit( final TransformerTest spec, final ResourceTransformer transformer ) throws IOException
135     {
136         for ( final Resource resource : spec.visited() )
137         {
138             if ( transformer.canTransformResource( resource.path() ))
139             {
140                 transformer.processResource(
141                         resource.path(),
142                         new ByteArrayInputStream( resource.content().getBytes(StandardCharsets.UTF_8) ),
143                         Collections.<Relocator>emptyList() );
144             }
145         }
146     }
147 
148     private String read(final JarInputStream jar) throws IOException
149     {
150         final StringBuilder builder = new StringBuilder();
151         final byte[] buffer = new byte[512];
152         int read;
153         while ( (read = jar.read(buffer) ) >= 0 )
154         {
155             builder.append( new String( buffer, 0, read ) );
156         }
157         return builder.toString();
158     }
159 
160     private ResourceTransformer createTransformer(final TransformerTest spec)
161     {
162         final ConverterLookup lookup = new DefaultConverterLookup();
163         try
164         {
165             final ConfigurationConverter converter = lookup.lookupConverterForType( spec.transformer() );
166             final PlexusConfiguration configuration = new DefaultPlexusConfiguration( "configuration" );
167             for ( final Property property : spec.configuration() )
168             {
169                 configuration.addChild( property.name(), property.value() );
170             }
171             return ResourceTransformer.class.cast(
172                     converter.fromConfiguration( lookup, configuration,  spec.transformer(), spec.transformer(),
173                         Thread.currentThread().getContextClassLoader(),
174                         new DefaultExpressionEvaluator() ) );
175         }
176         catch (final ComponentConfigurationException e)
177         {
178             throw new IllegalStateException(e);
179         }
180     }
181 
182     /**
183      * Enables to describe a test without having to implement the logic itself.
184      */
185     @Target(METHOD)
186     @Retention(RUNTIME)
187     public @interface TransformerTest
188     {
189         /**
190          * @return the list of resource the transformer will process.
191          */
192         Resource[] visited();
193 
194         /**
195          * @return the expected output created by the transformer.
196          */
197         Resource[] expected();
198 
199         /**
200          * @return true if only expected resources must be found.
201          */
202         boolean strictMatch() default true;
203 
204         /**
205          * @return type of transformer to use.
206          */
207         Class<?> transformer();
208 
209         /**
210          * @return transformer configuration.
211          */
212         Property[] configuration();
213 
214         /**
215          * @return if set to an exception class it ensures it is thrown during the processing.
216          */
217         Class<?> expectedException() default Object.class;
218     }
219 
220     @Target(METHOD)
221     @Retention(RUNTIME)
222     public @interface Property
223     {
224         String name();
225         String value();
226     }
227 
228     @Target(METHOD)
229     @Retention(RUNTIME)
230     public @interface Resource
231     {
232         String path();
233         String content();
234     }
235 }