1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package com.android.tools.layoutlib.create;
19 
20 import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
21 import com.android.tools.layoutlib.create.AsmAnalyzer.Result;
22 import com.android.tools.layoutlib.create.ICreateInfo.MethodReplacer;
23 
24 import org.junit.Test;
25 import org.objectweb.asm.ClassReader;
26 
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.TreeMap;
35 
36 import static org.junit.Assert.assertArrayEquals;
37 import static org.junit.Assert.assertEquals;
38 import static org.junit.Assert.assertNotNull;
39 
40 /**
41  * Unit tests for some methods of {@link AsmAnalyzer}.
42  */
43 public class AsmAnalyzerTest {
44     private static final List<String> MOCK_ANDROID_JAR;
45     private static final String[] DEFAULT_EXCLUDES = new String[]{"notjava.lang.JavaClass"};
46     private static final String[] DEFAULT_INCLUDE_FILES = new String[]{"mock_android/data/data*"};
47 
48     static {
49         List<String> mockJar = new ArrayList<>();
50         URL url = AsmAnalyzerTest.class.getClassLoader().getResource("data/mock_android.jar");
51         assert url != null : "Unable to locate mock_android.jar";
url.getFile()52         mockJar.add(url.getFile());
53         MOCK_ANDROID_JAR = Collections.unmodifiableList(mockJar);
54     }
55 
getDefaultAnalyzer()56     private static AsmAnalyzer getDefaultAnalyzer() {
57         MockLog log = new MockLog();
58         return new AsmAnalyzer(log, MOCK_ANDROID_JAR, null ,
59                 null /* includeGlobs */, DEFAULT_EXCLUDES, DEFAULT_INCLUDE_FILES,
60                 new MethodReplacer[] {});
61     }
62 
63     @Test
testParseZip()64     public void testParseZip() throws IOException {
65         Map<String, ClassReader> map = new TreeMap<>();
66         Map<String, InputStream> filesFound = new TreeMap<>();
67 
68         getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, map, filesFound);
69 
70         assertArrayEquals(new String[] {
71                 "mock_android.fake.FakeClass",
72                 "mock_android.fake.InnerTest",
73                 "mock_android.fake.InnerTest$1",
74                 "mock_android.fake.InnerTest$DerivingClass",
75                 "mock_android.fake.InnerTest$MyGenerics1",
76                 "mock_android.fake.InnerTest$MyIntEnum",
77                 "mock_android.fake.InnerTest$MyStaticInnerClass",
78                 "mock_android.fake.InnerTest$NotStaticInner1",
79                 "mock_android.fake.InnerTest$NotStaticInner2",
80                 "mock_android.fake.subpackage.SubpackageClassA",
81                 "mock_android.fake.subpackage.SubpackageClassB",
82                 "mock_android.fake.subpackage.SubpackageClassC",
83                 "mock_android.fake.subpackage.SubpackageClassC$InnerClass",
84                 "mock_android.fake.subpackage.SubpackageClassC$StaticInnerClass",
85                 "mock_android.fake2.FakeClass",
86                 "mock_android.fake2.keep.DoNotRemove",
87                 "mock_android.util.EmptyArray",
88                 "mock_android.util.NotNeeded",
89                 "mock_android.view.LibLoader",
90                 "mock_android.view.View",
91                 "mock_android.view.ViewGroup",
92                 "mock_android.view.ViewGroup$LayoutParams",
93                 "mock_android.view.ViewGroup$MarginLayoutParams",
94                 "mock_android.widget.LinearLayout",
95                 "mock_android.widget.LinearLayout$LayoutParams",
96                 "mock_android.widget.TableLayout",
97                 "mock_android.widget.TableLayout$LayoutParams",
98                 "notjava.lang.JavaClass",
99             },
100             map.keySet().toArray());
101         assertArrayEquals(new String[] {"mock_android/data/dataFile"},
102             filesFound.keySet().toArray());
103     }
104 
105     @Test
testFindClass()106     public void testFindClass() throws IOException {
107         Map<String, ClassReader> zipClasses = new TreeMap<>();
108         Map<String, InputStream> filesFound = new TreeMap<>();
109 
110         getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, zipClasses, filesFound);
111         TreeMap<String, ClassReader> found = new TreeMap<>();
112 
113         ClassReader cr = AsmAnalyzer.findClass("mock_android.view.ViewGroup$LayoutParams",
114                 zipClasses, found);
115 
116         assertNotNull(cr);
117         assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
118         assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
119                 found.keySet().toArray());
120         assertArrayEquals(new ClassReader[] { cr }, found.values().toArray());
121     }
122 
123     @Test
testInclude()124     public void testInclude() throws IOException {
125         AsmAnalyzer analyzer = new AsmAnalyzer(new MockLog(), MOCK_ANDROID_JAR, null,
126                 new String[] {
127                     "mock_android.util.EmptyArray", // Single class select
128                     "mock_android.fake.**", // Multi package select
129                     "mock_android.fake2.*", // Exclude subpackages select
130                 },
131                 DEFAULT_EXCLUDES,
132                 DEFAULT_INCLUDE_FILES,
133                 new MethodReplacer[] {});
134         Result result = analyzer.analyze();
135         assertArrayEquals(new String[] {
136                         "mock_android.fake.InnerTest$NotStaticInner1",
137                         "mock_android.fake.FakeClass",
138                         "mock_android.util.EmptyArray",
139                         "mock_android.fake2.FakeClass",
140                         "mock_android.fake.InnerTest$DerivingClass",
141                         "mock_android.fake.InnerTest$NotStaticInner2",
142                         "mock_android.fake.subpackage.SubpackageClassC$InnerClass",
143                         "mock_android.fake.InnerTest$MyGenerics1",
144                         "mock_android.fake.InnerTest$MyIntEnum",
145                         "mock_android.fake.InnerTest$MyStaticInnerClass",
146                         "mock_android.fake.InnerTest",
147                         "mock_android.fake.subpackage.SubpackageClassB",
148                         "mock_android.fake.subpackage.SubpackageClassA",
149                         "mock_android.fake.InnerTest$1",
150                         "mock_android.fake.subpackage.SubpackageClassC$StaticInnerClass",
151                         "mock_android.fake.subpackage.SubpackageClassC",
152                 },
153                 result.getFound().keySet().toArray());
154     }
155 
156     @Test
testFindClassesDerivingFrom()157     public void testFindClassesDerivingFrom() throws IOException {
158         Map<String, ClassReader> zipClasses = new TreeMap<>();
159         Map<String, InputStream> filesFound = new TreeMap<>();
160 
161         getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, zipClasses, filesFound);
162         TreeMap<String, ClassReader> found = new TreeMap<>();
163 
164         AsmAnalyzer.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
165 
166         assertArrayEquals(new String[] {
167                 "mock_android.view.View",
168                 "mock_android.view.ViewGroup",
169                 "mock_android.widget.LinearLayout",
170                 "mock_android.widget.TableLayout",
171             },
172             found.keySet().toArray());
173 
174         for (String key : found.keySet()) {
175             ClassReader value = found.get(key);
176             assertNotNull("No value for " + key, value);
177             assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
178         }
179     }
180 
181     @Test
testDependencyVisitor()182     public void testDependencyVisitor() throws IOException {
183         Map<String, ClassReader> zipClasses = new TreeMap<>();
184         Map<String, InputStream> filesFound = new TreeMap<>();
185 
186         getDefaultAnalyzer().parseZip(MOCK_ANDROID_JAR, zipClasses, filesFound);
187         TreeMap<String, ClassReader> keep = new TreeMap<>();
188         TreeMap<String, ClassReader> new_keep = new TreeMap<>();
189         TreeMap<String, ClassReader> in_deps = new TreeMap<>();
190         TreeMap<String, ClassReader> out_deps = new TreeMap<>();
191 
192         ClassReader cr = AsmAnalyzer.findClass("mock_android.widget.LinearLayout", zipClasses, keep);
193         DependencyVisitor visitor = getDefaultAnalyzer().getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
194 
195         // get first level dependencies
196         cr.accept(visitor, 0 /* flags */);
197 
198         assertArrayEquals(new String[] {
199                 "mock_android.util.EmptyArray",
200                 "mock_android.view.ViewGroup",
201                 "mock_android.widget.LinearLayout$LayoutParams",
202             },
203             out_deps.keySet().toArray());
204 
205         in_deps.putAll(out_deps);
206         out_deps.clear();
207 
208         // get second level dependencies
209         for (ClassReader cr2 : in_deps.values()) {
210             cr2.accept(visitor, 0 /* flags */);
211         }
212 
213         assertArrayEquals(new String[] {
214                 "mock_android.view.View",
215                 "mock_android.view.ViewGroup$LayoutParams",
216                 "mock_android.view.ViewGroup$MarginLayoutParams",
217             },
218             out_deps.keySet().toArray());
219 
220         in_deps.putAll(out_deps);
221         out_deps.clear();
222 
223         // get third level dependencies (there are none)
224         for (ClassReader cr2 : in_deps.values()) {
225             cr2.accept(visitor, 0 /* flags */);
226         }
227         keep.putAll(new_keep);
228 
229         assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
230         assertArrayEquals(new String[] {
231                 "mock_android.widget.LinearLayout",
232                 "notjava.lang.JavaClass",
233         }, keep.keySet().toArray());
234     }
235 }
236