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