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.assertNotNull;
25  import static org.junit.Assert.assertTrue;
26  import static org.junit.Assert.fail;
27  
28  import java.io.ByteArrayInputStream;
29  import java.io.ByteArrayOutputStream;
30  import java.io.IOException;
31  import java.lang.annotation.Retention;
32  import java.lang.annotation.Target;
33  import java.nio.charset.StandardCharsets;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.Map;
37  import java.util.jar.JarEntry;
38  import java.util.jar.JarInputStream;
39  import java.util.jar.JarOutputStream;
40  
41  import org.apache.maven.plugins.shade.relocation.Relocator;
42  import org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer;
43  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
44  import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter;
45  import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
46  import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
47  import org.codehaus.plexus.component.configurator.expression.DefaultExpressionEvaluator;
48  import org.codehaus.plexus.configuration.DefaultPlexusConfiguration;
49  import org.codehaus.plexus.configuration.PlexusConfiguration;
50  import org.junit.rules.TestRule;
51  import org.junit.runner.Description;
52  import org.junit.runners.model.Statement;
53  
54  public class TransformerTesterRule
55      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 ReproducibleResourceTransformer 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 ReproducibleResourceTransformer transformer )
115         throws IOException
116     {
117         final ByteArrayOutputStream out = new ByteArrayOutputStream();
118         try ( final JarOutputStream jar = new JarOutputStream( out ) )
119         {
120             transformer.modifyOutputStream( jar );
121         }
122 
123         final Map<String, String> created = new HashMap<>();
124         try ( final JarInputStream jar = new JarInputStream( new ByteArrayInputStream( out.toByteArray() ) ) )
125         {
126             JarEntry entry;
127             while ( ( entry = jar.getNextJarEntry() ) != null )
128             {
129                 created.put( entry.getName(), read( jar ) );
130             }
131         }
132         return created;
133     }
134 
135     private void visit( final TransformerTest spec, final ReproducibleResourceTransformer transformer )
136         throws IOException
137     {
138         for ( final Resource resource : spec.visited() )
139         {
140             if ( transformer.canTransformResource( resource.path() ) )
141             {
142                 transformer.processResource(
143                         resource.path(),
144                         new ByteArrayInputStream( resource.content().getBytes(StandardCharsets.UTF_8) ),
145                         Collections.<Relocator>emptyList(), 0 );
146             }
147         }
148     }
149 
150     private String read(final JarInputStream jar) throws IOException
151     {
152         final StringBuilder builder = new StringBuilder();
153         final byte[] buffer = new byte[512];
154         int read;
155         while ( (read = jar.read(buffer) ) >= 0 )
156         {
157             builder.append( new String( buffer, 0, read ) );
158         }
159         return builder.toString();
160     }
161 
162     private ReproducibleResourceTransformer createTransformer(final TransformerTest spec)
163     {
164         final ConverterLookup lookup = new DefaultConverterLookup();
165         try
166         {
167             final ConfigurationConverter converter = lookup.lookupConverterForType( spec.transformer() );
168             final PlexusConfiguration configuration = new DefaultPlexusConfiguration( "configuration" );
169             for ( final Property property : spec.configuration() )
170             {
171                 configuration.addChild( property.name(), property.value() );
172             }
173             return ReproducibleResourceTransformer.class.cast(
174                     converter.fromConfiguration( lookup, configuration,  spec.transformer(), spec.transformer(),
175                         Thread.currentThread().getContextClassLoader(),
176                         new DefaultExpressionEvaluator() ) );
177         }
178         catch (final ComponentConfigurationException e)
179         {
180             throw new IllegalStateException(e);
181         }
182     }
183 
184     /**
185      * Enables to describe a test without having to implement the logic itself.
186      */
187     @Target(METHOD)
188     @Retention(RUNTIME)
189     public @interface TransformerTest
190     {
191         /**
192          * @return the list of resource the transformer will process.
193          */
194         Resource[] visited();
195 
196         /**
197          * @return the expected output created by the transformer.
198          */
199         Resource[] expected();
200 
201         /**
202          * @return true if only expected resources must be found.
203          */
204         boolean strictMatch() default true;
205 
206         /**
207          * @return type of transformer to use.
208          */
209         Class<?> transformer();
210 
211         /**
212          * @return transformer configuration.
213          */
214         Property[] configuration();
215 
216         /**
217          * @return if set to an exception class it ensures it is thrown during the processing.
218          */
219         Class<?> expectedException() default Object.class;
220     }
221 
222     @Target(METHOD)
223     @Retention(RUNTIME)
224     public @interface Property
225     {
226         String name();
227         String value();
228     }
229 
230     @Target(METHOD)
231     @Retention(RUNTIME)
232     public @interface Resource
233     {
234         String path();
235         String content();
236     }
237 }