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(Statement base, Description description) {
56 return new Statement() {
57 @Override
58 public void evaluate() throws Throwable {
59 TransformerTest spec = description.getAnnotation(TransformerTest.class);
60 if (spec == null) {
61 base.evaluate();
62 return;
63 }
64
65 Map<String, String> jar;
66 try {
67 ReproducibleResourceTransformer transformer = createTransformer(spec);
68 visit(spec, transformer);
69 jar = captureOutput(transformer);
70 } catch (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(TransformerTest spec, 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(ReproducibleResourceTransformer transformer) throws IOException {
99 ByteArrayOutputStream out = new ByteArrayOutputStream();
100 try (JarOutputStream jar = new JarOutputStream(out)) {
101 transformer.modifyOutputStream(jar);
102 }
103
104 Map<String, String> created = new HashMap<>();
105 try (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(TransformerTest spec, ReproducibleResourceTransformer transformer) throws IOException {
115 for (Resource resource : spec.visited()) {
116 if (transformer.canTransformResource(resource.path())) {
117 transformer.processResource(
118 resource.path(),
119 new ByteArrayInputStream(resource.content().getBytes(StandardCharsets.UTF_8)),
120 Collections.<Relocator>emptyList(),
121 0);
122 }
123 }
124 }
125
126 private String read(JarInputStream jar) throws IOException {
127 StringBuilder builder = new StringBuilder();
128 byte[] buffer = new byte[512];
129 int read;
130 while ((read = jar.read(buffer)) >= 0) {
131 builder.append(new String(buffer, 0, read));
132 }
133 return builder.toString();
134 }
135
136 private ReproducibleResourceTransformer createTransformer(TransformerTest spec) {
137 ConverterLookup lookup = new DefaultConverterLookup();
138 try {
139 ConfigurationConverter converter = lookup.lookupConverterForType(spec.transformer());
140 PlexusConfiguration configuration = new DefaultPlexusConfiguration("configuration");
141 for (Property property : spec.configuration()) {
142 configuration.addChild(property.name(), property.value());
143 }
144 return (ReproducibleResourceTransformer) converter.fromConfiguration(
145 lookup,
146 configuration,
147 spec.transformer(),
148 spec.transformer(),
149 Thread.currentThread().getContextClassLoader(),
150 new DefaultExpressionEvaluator());
151 } catch (ComponentConfigurationException e) {
152 throw new IllegalStateException(e);
153 }
154 }
155
156
157
158
159 @Target(METHOD)
160 @Retention(RUNTIME)
161 public @interface TransformerTest {
162
163
164
165 Resource[] visited();
166
167
168
169
170 Resource[] expected();
171
172
173
174
175 boolean strictMatch() default true;
176
177
178
179
180 Class<?> transformer();
181
182
183
184
185 Property[] configuration();
186
187
188
189
190 Class<?> expectedException() default Object.class;
191 }
192
193 @Target(METHOD)
194 @Retention(RUNTIME)
195 public @interface Property {
196 String name();
197
198 String value();
199 }
200
201 @Target(METHOD)
202 @Retention(RUNTIME)
203 public @interface Resource {
204 String path();
205
206 String content();
207 }
208 }