View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.module.auto
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.instrumental.module.auto;
20  
21  import static io.earcam.instrumental.module.auto.Reader.reader;
22  import static java.nio.charset.StandardCharsets.UTF_8;
23  import static org.hamcrest.MatcherAssert.assertThat;
24  import static org.hamcrest.Matchers.*;
25  import static org.ops4j.pax.tinybundles.core.TinyBundles.bundle;
26  
27  import java.io.ByteArrayInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.UncheckedIOException;
31  import java.util.ArrayList;
32  import java.util.Arrays;
33  import java.util.Comparator;
34  import java.util.HashMap;
35  import java.util.HashSet;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Objects;
39  import java.util.Set;
40  import java.util.function.BiConsumer;
41  import java.util.jar.Manifest;
42  
43  import org.hamcrest.Matchers;
44  import org.hamcrest.core.IsAnything;
45  import org.junit.jupiter.api.Nested;
46  import org.junit.jupiter.api.Test;
47  import org.objectweb.asm.Label;
48  import org.objectweb.asm.signature.SignatureReader;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  import io.earcam.acme.AcmeAnnotation;
53  import io.earcam.acme.Annotated;
54  import io.earcam.acme.ImportsSlf4jApi;
55  import io.earcam.instrumental.module.auto.Reader;
56  import io.earcam.instrumental.reflect.Resources;
57  import io.earcam.utilitarian.charstar.CharSequences;
58  
59  @SuppressWarnings("squid:S2187")  // SonarQube false-positive; tests are only in @Nested
60  public class ReaderTest {
61  
62  	// EARCAM_SNIPPET_BEGIN: test-support
63  	private static String cn(Class<?> type)
64  	{
65  		return type.getCanonicalName();
66  	}
67  
68  
69  	private static String pkg(Class<?> type)
70  	{
71  		return type.getPackage().getName();
72  	}
73  	// EARCAM_SNIPPET_END: test-support
74  
75  	@Nested
76  	class Annotations {
77  
78  		@Test
79  		void readsAnnotationsAndTheirValuesByDefault()
80  		{
81  			// @formatter:off
82  			// EARCAM_SNIPPET_BEGIN: do-read-annotations
83  			Map<String, Set<String>> imports = new HashMap<>();
84  
85  			reader()
86  					.addImportListener(imports::put)
87  					.processClass(Resources.classAsBytes(Annotated.class));
88  
89  			assertThat(imports, is(aMapWithSize(1)));
90  
91  			assertThat(imports, hasEntry(equalTo(cn(Annotated.class)), 
92  				containsInAnyOrder(
93  					cn(Object.class),
94  					cn(Comparable.class),
95  					cn(Class.class),
96  					cn(Throwable.class),
97  					cn(OutOfMemoryError.class),
98  					cn(Integer.class),
99  
100 					cn(AcmeAnnotation.class),
101 
102 					cn(CharSequences.class),
103 					cn(Test.class),
104 					cn(Label.class),
105 					cn(SignatureReader.class),
106 					cn(IllegalStateException.class),
107 					cn(Matchers.class),
108 					cn(IsAnything.class),
109 					cn(Comparator.class),
110 					cn(Objects.class),
111 					cn(UncheckedIOException.class))));
112 			// EARCAM_SNIPPET_END: do-read-annotations
113 			// @formatter:on
114 		}
115 
116 
117 		@Test
118 		void readingAnnotationsAndTheirValuesCanBeDisabled()
119 		{
120 			Map<String, Set<String>> imports = new HashMap<>();
121 
122 			// EARCAM_SNIPPET_BEGIN: do-not-read-annotations
123 			reader()
124 					.ignoreAnnotations()
125 					.addImportListener(imports::put)
126 					.processClass(Resources.classAsBytes(Annotated.class));
127 
128 			assertThat(imports, is(aMapWithSize(1)));
129 
130 			assertThat(imports, hasEntry(equalTo(cn(Annotated.class)), containsInAnyOrder(
131 					cn(Object.class),
132 					cn(Comparable.class),
133 					cn(Class.class),
134 					cn(Throwable.class),
135 					cn(OutOfMemoryError.class),
136 					cn(Integer.class))));
137 			// EARCAM_SNIPPET_END: do-not-read-annotations
138 		}
139 	}
140 
141 	@Nested
142 	class FromClass {
143 
144 		@Nested
145 		class Simple {
146 
147 			@Test
148 			void importListenerOnly() throws IOException
149 			{
150 				// EARCAM_SNIPPET_BEGIN: imports-simple-class
151 				Map<String, Set<String>> imports = new HashMap<>();
152 
153 				reader()
154 						.addImportListener(imports::put)
155 						.processClass(Resources.classAsBytes(ImportsSlf4jApi.class));
156 
157 				assertThat(imports, is(aMapWithSize(1)));
158 
159 				assertThat(imports, hasEntry(equalTo(cn(ImportsSlf4jApi.class)), containsInAnyOrder(
160 						cn(Arrays.class),
161 						cn(Logger.class),
162 						cn(LoggerFactory.class),
163 						cn(Object.class),
164 						cn(Class.class),
165 						cn(StringBuilder.class),
166 						cn(String.class))));
167 				// EARCAM_SNIPPET_END: imports-simple-class
168 			}
169 
170 		}
171 	}
172 
173 	@Nested
174 	class FromJar {
175 
176 		@Nested
177 		class Simple {
178 
179 			@Test
180 			void importListenerOnly() throws IOException
181 			{
182 				Map<String, Set<String>> imports = new HashMap<>();
183 
184 				InputStream jar = bundle()
185 						.add(ImportsSlf4jApi.class)
186 						.add("irrelevant/file.txt", new ByteArrayInputStream("hello".getBytes(UTF_8)))
187 						.build();
188 
189 				reader()
190 						.addImportListener(imports::put)
191 						.processJar(jar);
192 
193 				assertThat(imports, is(aMapWithSize(1)));
194 
195 				assertThat(imports, hasEntry(equalTo(cn(ImportsSlf4jApi.class)), containsInAnyOrder(
196 						cn(Arrays.class),
197 						cn(Logger.class),
198 						cn(LoggerFactory.class),
199 						cn(Object.class),
200 						cn(Class.class),
201 						cn(StringBuilder.class),
202 						cn(String.class))));
203 			}
204 
205 
206 			@Test
207 			void manifestListenerOnly() throws IOException
208 			{
209 				InputStream jar = bundle()
210 						.add(ImportsSlf4jApi.class)
211 						.add("irrelevant/file.txt", new ByteArrayInputStream("hello".getBytes(UTF_8)))
212 						.build();
213 
214 				List<Manifest> manifests = new ArrayList<>();
215 
216 				reader()
217 						.addManifestListener(manifests::add)
218 						.processJar(jar);
219 
220 				assertThat(manifests, hasSize(1));
221 			}
222 
223 
224 			@Test
225 			void additionalBytecodeListener() throws IOException
226 			{
227 				InputStream jar = bundle()
228 						.add(ImportsSlf4jApi.class)
229 						.add("irrelevant/file.txt", new ByteArrayInputStream("hello".getBytes(UTF_8)))
230 						.build();
231 
232 				Map<String, Set<String>> imports = new HashMap<>();
233 				List<byte[]> listener = new ArrayList<>();
234 
235 				reader()
236 						.addImportListener(imports::put)
237 						.addByteCodeListener(listener::add)
238 						.processJar(jar);
239 
240 				assertThat(listener, hasSize(1));
241 			}
242 
243 
244 			@Test
245 			void listenerAndTypeReducers() throws IOException
246 			{
247 				// EARCAM_SNIPPET_BEGIN: map-jar-reduced-to-packages
248 				Map<String, Set<String>> imports = new HashMap<>();
249 
250 				InputStream jar = bundle()
251 						.add(ImportsSlf4jApi.class)
252 						.add("irrelevant/file.txt", new ByteArrayInputStream("hello".getBytes(UTF_8)))
253 						.build();
254 
255 				BiConsumer<String, Set<String>> listener = imports::put;
256 
257 				reader()
258 						.addImportListener(listener)
259 						.setImportedTypeReducer(Reader::typeToPackageReducer)
260 						.setImportingTypeReducer(Reader::typeToPackageReducer)
261 						.processJar(jar);
262 
263 				assertThat(imports, is(aMapWithSize(1)));
264 
265 				Set<String> expected = new HashSet<>(Arrays.asList(
266 						pkg(Arrays.class),
267 						pkg(Logger.class),
268 						pkg(LoggerFactory.class),
269 						pkg(Object.class),
270 						pkg(Class.class),
271 						pkg(StringBuilder.class),
272 						pkg(String.class)));
273 
274 				assertThat(imports, hasEntry(equalTo(pkg(ImportsSlf4jApi.class)), equalTo(expected)));
275 				// EARCAM_SNIPPET_END: map-jar-reduced-to-packages
276 			}
277 		}
278 	}
279 }