1 package org.apache.maven.plugins.shade.resource.rule;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
186
187 @Target(METHOD)
188 @Retention(RUNTIME)
189 public @interface TransformerTest
190 {
191
192
193
194 Resource[] visited();
195
196
197
198
199 Resource[] expected();
200
201
202
203
204 boolean strictMatch() default true;
205
206
207
208
209 Class<?> transformer();
210
211
212
213
214 Property[] configuration();
215
216
217
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 }