1 /* 2 * Copyright (C) 2023 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 package libcore.tools.generator.noncts; 18 19 import com.android.json.stream.JsonWriter; 20 21 import com.beust.jcommander.JCommander; 22 import com.beust.jcommander.Parameter; 23 import com.beust.jcommander.converters.FileConverter; 24 25 import org.objectweb.asm.ClassReader; 26 import org.objectweb.asm.Type; 27 import org.objectweb.asm.tree.AnnotationNode; 28 import org.objectweb.asm.tree.ClassNode; 29 import org.objectweb.asm.tree.MethodNode; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.io.PrintStream; 36 import java.io.PrintWriter; 37 import java.io.UncheckedIOException; 38 import java.io.Writer; 39 import java.util.ArrayList; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.Map; 43 import java.util.Set; 44 import java.util.TreeMap; 45 import java.util.TreeSet; 46 import java.util.zip.ZipEntry; 47 import java.util.zip.ZipFile; 48 49 import vogar.expect.Expectation; 50 import vogar.expect.ExpectationStore; 51 import vogar.expect.ModeId; 52 import vogar.expect.util.Log; 53 import vogar.expect.util.LogOutput; 54 55 public class Main { 56 57 58 private static class MainArgs { 59 60 @Parameter(names = "-h", help = true, description = "Shows this help message") 61 public boolean help = false; 62 63 @Parameter(converter = FileConverter.class, description = "list of jar files") 64 public List<File> inputFiles = new ArrayList<>(); 65 } 66 main(String[] argv)67 public static void main(String[] argv) { 68 MainArgs mainArgs = new MainArgs(); 69 70 JCommander jCommander = JCommander.newBuilder() 71 .addObject(mainArgs) 72 .build(); 73 jCommander.parse(argv); 74 75 if (mainArgs.help) { 76 jCommander.usage(); 77 return; 78 } 79 80 // See "libcore-non-cts-tests-txt" soong module where System.out is piped into 81 // skippedCtsTest.txt 82 PrintStream out = System.out; 83 out.println("/* Do not modify directly."); 84 out.println(" * Generated by tools/non-cts-json-generator/update_skippedCtsTest.sh"); 85 out.println(" * which dumps all @NonCts tests."); 86 out.println(" */"); 87 88 89 NonCtsAnnotationReader reader = new NonCtsAnnotationReader(mainArgs.inputFiles).parse(); 90 ExpectationStore baseStore = readBaseExpectationStore(); 91 ExpectationWriter writer = ExpectationWriter.from(baseStore, reader); 92 try { 93 writer.write(out); 94 } catch (IOException e) { 95 throw new UncheckedIOException(e); 96 } 97 } 98 readBaseExpectationStore()99 private static ExpectationStore readBaseExpectationStore() { 100 Log.setOutput(new LogOutput() { 101 @Override 102 public void verbose(String s) { 103 System.err.println(s); 104 } 105 106 @Override 107 public void warn(String message) { 108 System.err.println(message); 109 } 110 111 @Override 112 public void warn(String message, List<String> list) { 113 System.err.printf(message + '\n', list.toArray()); 114 } 115 116 @Override 117 public void nativeOutput(String outputLine) { 118 System.err.println(outputLine); 119 } 120 121 @Override 122 public void info(String s) {} 123 124 @Override 125 public void info(String message, Throwable throwable) {} 126 }); 127 try { 128 return ExpectationStore.parseResources(Main.class, 129 Set.of("/skippedCtsTest_manual_base.txt"), ModeId.HOST); 130 } catch (IOException e) { 131 throw new UncheckedIOException(e); 132 } 133 } 134 135 /** 136 * Read {@link libcore.test.annotation.NonCts} annotations from the .class files in the give 137 * jar files. 138 */ 139 private static class NonCtsAnnotationReader { 140 private final List<NonCtsEntry> mEntries = new ArrayList<>(); 141 142 private final List<File> mJarFiles; NonCtsAnnotationReader(List<File> jarFiles)143 public NonCtsAnnotationReader(List<File> jarFiles) { 144 mJarFiles = jarFiles; 145 } 146 getEntries()147 public List<NonCtsEntry> getEntries() { 148 return mEntries; 149 } 150 parse()151 public NonCtsAnnotationReader parse() { 152 for (File jarFile : mJarFiles) { 153 try (ZipFile zipFile = new ZipFile(jarFile)) { 154 var zipEntries = zipFile.entries(); 155 while (zipEntries.hasMoreElements()) { 156 ZipEntry zipEntry = zipEntries.nextElement(); 157 if (!zipEntry.getName().endsWith(".class")) { 158 continue; 159 } 160 161 ClassNode classNode = parseClass(zipFile, zipEntry); 162 storeIfNonCts(classNode); 163 String className = Type.getObjectType(classNode.name).getClassName(); 164 for (MethodNode methodNode : classNode.methods) { 165 storeIfNonCts(className, methodNode); 166 } 167 } 168 } catch (IOException e) { 169 throw new UncheckedIOException(e); 170 } 171 } 172 173 return this; 174 } parseClass(ZipFile zipFile, ZipEntry zipEntry)175 private static ClassNode parseClass(ZipFile zipFile, ZipEntry zipEntry) throws IOException { 176 try (InputStream in = zipFile.getInputStream(zipEntry)) { 177 ClassReader classReader = new ClassReader(in); 178 ClassNode node = new ClassNode(); 179 classReader.accept(node, 0); 180 return node; 181 } 182 } 183 storeIfNonCts(String className, MethodNode node)184 private void storeIfNonCts(String className, MethodNode node) { 185 if (node.visibleAnnotations == null) { 186 return; 187 } 188 189 String methodName = className + "#" + node.name; 190 storeIfNonCts(node.visibleAnnotations, methodName); 191 } 192 storeIfNonCts(ClassNode node)193 private void storeIfNonCts(ClassNode node) { 194 if (node.visibleAnnotations == null) { 195 return; 196 } 197 198 String className = Type.getObjectType(node.name).getClassName(); 199 storeIfNonCts(node.visibleAnnotations, className); 200 } 201 storeIfNonCts(List<AnnotationNode> visibleAnnotations, String name)202 private void storeIfNonCts(List<AnnotationNode> visibleAnnotations, String name) { 203 visibleAnnotations.stream() 204 .filter(a -> "Llibcore/test/annotation/NonCts;".equals(a.desc)) 205 .map(a -> { 206 Long bug = -1L; 207 String desc = null; 208 for (int i = 0; i < a.values.size() - 1; i = i + 2) { 209 if (!(a.values.get(i) instanceof String key)) { 210 continue; 211 } 212 switch(key) { 213 case "bug" -> bug = (Long) a.values.get(i + 1); 214 case "reason" -> desc = (String) a.values.get(i + 1); 215 } 216 } 217 return new NonCtsEntry(name, bug, desc); 218 }) 219 .findFirst() 220 .ifPresent(mEntries::add); 221 } 222 } 223 224 225 /** 226 * Write the expectation into the format read by {@link vogar.expect.ExpectationStore}. 227 */ 228 public static class ExpectationWriter { 229 private final TreeMap<NonCtsCluster.Key, NonCtsCluster> clusterMap; 230 ExpectationWriter(TreeMap<NonCtsCluster.Key, NonCtsCluster> clusterMap)231 private ExpectationWriter(TreeMap<NonCtsCluster.Key, NonCtsCluster> clusterMap) { 232 this.clusterMap = clusterMap; 233 } 234 from(ExpectationStore baseStore, NonCtsAnnotationReader annotationReader)235 public static ExpectationWriter from(ExpectationStore baseStore, 236 NonCtsAnnotationReader annotationReader) { 237 TreeMap<NonCtsCluster.Key, NonCtsCluster> clusterMap = new TreeMap<>(); 238 for (NonCtsEntry entry : annotationReader.getEntries()) { 239 NonCtsCluster.Key key = new NonCtsCluster.Key(entry); 240 clusterMap.compute(key, (k, cluster) -> { 241 if (cluster == null) { 242 cluster = new NonCtsCluster(k); 243 } 244 cluster.add(entry.name()); 245 return cluster; 246 }); 247 } 248 for (var entry : baseStore.getAllOutComes().entrySet()) { 249 NonCtsCluster.Key key = new NonCtsCluster.Key(entry.getValue()); 250 String testName = entry.getKey(); 251 clusterMap.compute(key, (k, cluster) -> { 252 if (cluster == null) { 253 cluster = new NonCtsCluster(k); 254 } 255 cluster.add(testName); 256 return cluster; 257 }); 258 } 259 260 return new ExpectationWriter(clusterMap); 261 } 262 write(OutputStream out)263 public void write(OutputStream out) throws IOException { 264 Writer writer = new PrintWriter(out); 265 JsonWriter jsonWriter = new JsonWriter(writer); 266 jsonWriter.setIndent(" "); 267 jsonWriter.beginArray(); 268 269 for (NonCtsCluster cluster : clusterMap.values()) { 270 jsonWriter.beginObject(); 271 if (cluster.key.bug != -1) { 272 jsonWriter.name("bug"); 273 jsonWriter.value(cluster.key.bug); 274 } 275 String description = cluster.key.description; 276 if (description == null) { 277 description = ""; 278 } 279 jsonWriter.name("description"); 280 jsonWriter.value(description); 281 282 jsonWriter.name("names"); 283 jsonWriter.beginArray(); 284 for (String testName : cluster.testNames) { 285 jsonWriter.value(testName); 286 } 287 288 jsonWriter.endArray(); 289 jsonWriter.endObject(); 290 } 291 292 jsonWriter.endArray(); 293 294 jsonWriter.flush(); 295 } 296 } 297 298 /** 299 * This data class is very similar {@link vogar.expect.Expectation}, but contains only the name 300 * and attributes supported by {@link libcore.test.annotation.NonCts}. 301 */ NonCtsEntry(String name, long bug, String description)302 public record NonCtsEntry(String name, long bug, String description) {} 303 304 public static final class NonCtsCluster { 305 public final Key key; 306 307 public final TreeSet<String> testNames = new TreeSet<>(); 308 309 public record Key(long bug, String description) implements Comparable<Key> { 310 311 private static final Comparator<Key> COMPARATOR = Comparator.comparing(Key::bug) 312 .thenComparing(Key::description); Key(NonCtsEntry entry)313 public Key(NonCtsEntry entry) { 314 this(entry.bug, entry.description); 315 } Key(Expectation e)316 public Key(Expectation e) { 317 this(e.getBug(), e.getDescription()); 318 } 319 320 @Override compareTo(Key key2)321 public int compareTo(Key key2) { 322 return COMPARATOR.compare(this, key2); 323 } 324 } 325 NonCtsCluster(Key key)326 public NonCtsCluster(Key key) { 327 this.key = key; 328 } 329 add(String testName)330 public NonCtsCluster add(String testName) { 331 testNames.add(testName); 332 return this; 333 } 334 } 335 } 336 337