1 /*
2  * Copyright (C) 2018 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 com.android.cts.apicoverage;
18 
19 
20 import com.android.cts.apicoverage.TestSuiteProto.*;
21 
22 import org.xml.sax.InputSource;
23 import org.xml.sax.XMLReader;
24 import org.xml.sax.helpers.XMLReaderFactory;
25 
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.FileInputStream;
29 import java.io.FileOutputStream;
30 import java.io.InputStream;
31 import java.io.IOException;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.MessageDigest;
37 import java.util.ArrayList;
38 import java.util.Enumeration;
39 import java.util.List;
40 import java.util.zip.ZipEntry;
41 import java.util.zip.ZipFile;
42 
43 import javax.xml.parsers.SAXParser;
44 import javax.xml.parsers.SAXParserFactory;
45 
46 import org.xml.sax.Attributes;
47 import org.xml.sax.SAXException;
48 import org.xml.sax.helpers.DefaultHandler;
49 
50 class TestSuiteContentReport {
51     // configuration option
52     private static final String NOT_SHARDABLE_TAG = "not-shardable";
53     // test class option
54     private static final String RUNTIME_HIT_TAG = "runtime-hint";
55     // com.android.tradefed.testtype.AndroidJUnitTest option
56     private static final String PACKAGE_TAG = "package";
57     // com.android.compatibility.common.tradefed.testtype.JarHostTest option
58     private static final String JAR_NAME_TAG = "jar";
59     // com.android.tradefed.testtype.GTest option
60     private static final String NATIVE_TEST_DEVICE_PATH_TAG = "native-test-device-path";
61     private static final String MODULE_TAG = "module-name";
62 
63     private static final String SUITE_API_INSTALLER_TAG = "com.android.tradefed.targetprep.suite.SuiteApkInstaller";
64     private static final String JAR_HOST_TEST_TAG = "com.android.compatibility.common.tradefed.testtype.JarHostTest";
65     // com.android.tradefed.targetprep.suite.SuiteApkInstaller option
66     private static final String TEST_FILE_NAME_TAG = "test-file-name";
67     // com.android.compatibility.common.tradefed.targetprep.FilePusher option
68     private static final String PUSH_TAG = "push";
69 
70     // test class
71     private static final String ANDROID_JUNIT_TEST_TAG = "com.android.tradefed.testtype.AndroidJUnitTest";
72 
73     // Target File Extensions
74     private static final String CONFIG_EXT_TAG = ".config";
75     private static final String JAR_EXT_TAG = ".jar";
76     private static final String APK_EXT_TAG = ".apk";
77     private static final String SO_EXT_TAG = ".so";
78     private static final String TEST_SUITE_HARNESS = "-tradefed.jar";
79     private static final String KNOWN_FAILURES_XML_FILE = "-known-failures.xml";
80 
81     private static final String OPTION_TAG = "option";
82     private static final String NAME_TAG = "name";
83     private static final String EXCLUDE_FILTER_TAG = "compatibility:exclude-filter";
84     private static final String VALUE_TAG = "value";
85 
86 
printUsage()87     private static void printUsage() {
88         System.out.println("Usage: test-suite-content-report [OPTION]...");
89         System.out.println();
90         System.out.println("Generates test suite content protocal buffer message.");
91         System.out.println();
92         System.out.println(
93                 "$ANDROID_HOST_OUT/bin/test-suite-content-report "
94                         + "-i out/host/linux-x86/cts/android-cts "
95                         + "-o ./cts-content.pb"
96                         + "-t ./cts-list.pb");
97         System.out.println();
98         System.out.println("Options:");
99         System.out.println("  -i PATH                path to the Test Suite Folder");
100         System.out.println("  -o FILE                output file of Test Content Protocal Buffer");
101         System.out.println("  -t FILE                output file of Test Case List Protocal Buffer");
102         System.out.println();
103         System.exit(1);
104     }
105 
106     /** Get the argument or print out the usage and exit. */
getExpectedArg(String[] args, int index)107     private static String getExpectedArg(String[] args, int index) {
108         if (index < args.length) {
109             return args[index];
110         } else {
111             printUsage();
112             return null;    // Never will happen because printUsage will call exit(1)
113         }
114     }
115 
parseTestSuiteFolder(String testSuitePath)116     public static TestSuiteContent parseTestSuiteFolder(String testSuitePath)
117             throws IOException, NoSuchAlgorithmException {
118 
119         TestSuiteContent.Builder testSuiteContent = TestSuiteContent.newBuilder();
120         testSuiteContent.addFileEntries(parseFolder(testSuiteContent, testSuitePath, testSuitePath));
121         return testSuiteContent.build();
122     }
123 
124     // Parse a file
parseFileMetadata(Entry.Builder fEntry, File file)125     private static FileMetadata parseFileMetadata(Entry.Builder fEntry, File file)
126             throws Exception {
127         if (file.getName().endsWith(CONFIG_EXT_TAG)) {
128             fEntry.setType(Entry.EntryType.CONFIG);
129             return parseConfigFile(file);
130         } else if (file.getName().endsWith(APK_EXT_TAG)) {
131             fEntry.setType(Entry.EntryType.APK);
132         } else if (file.getName().endsWith(JAR_EXT_TAG)) {
133             fEntry.setType(Entry.EntryType.JAR);
134         } else if (file.getName().endsWith(SO_EXT_TAG)) {
135             fEntry.setType(Entry.EntryType.SO);
136         } else {
137             // Just file in general
138             fEntry.setType(Entry.EntryType.FILE);
139         }
140         return null;
141     }
142 
parseConfigFile(File file)143     private static FileMetadata parseConfigFile(File file)
144             throws Exception {
145         XMLReader xmlReader = XMLReaderFactory.createXMLReader();
146         TestModuleConfigHandler testModuleXmlHandler = new TestModuleConfigHandler(file.getName());
147         xmlReader.setContentHandler(testModuleXmlHandler);
148         FileReader fileReader = null;
149         try {
150             fileReader = new FileReader(file);
151             xmlReader.parse(new InputSource(fileReader));
152             return testModuleXmlHandler.getFileMetadata();
153         } finally {
154             if (null != fileReader) {
155                 fileReader.close();
156             }
157         }
158     }
159 
160     private static class KnownFailuresXmlHandler extends DefaultHandler {
161         private TestSuiteContent.Builder mTsBld = null;
162 
KnownFailuresXmlHandler(TestSuiteContent.Builder tsBld)163         KnownFailuresXmlHandler(TestSuiteContent.Builder tsBld) {
164             mTsBld = tsBld;
165         }
166 
167         @Override
startElement(String uri, String localName, String name, Attributes attributes)168         public void startElement(String uri, String localName, String name, Attributes attributes)
169                 throws SAXException {
170             super.startElement(uri, localName, name, attributes);
171 
172             System.err.printf(
173                     "ele %s: %s: %s \n",
174                     localName, attributes.getValue(NAME_TAG), attributes.getValue(VALUE_TAG));
175             if (EXCLUDE_FILTER_TAG.equals(attributes.getValue(NAME_TAG))) {
176                 String kfFilter = attributes.getValue(VALUE_TAG).replace(' ', '.');
177                 mTsBld.addKnownFailures(kfFilter);
178             }
179         }
180     }
181 
parseKnownFailures(TestSuiteContent.Builder tsBld, File file)182     private static void parseKnownFailures(TestSuiteContent.Builder tsBld, File file)
183             throws Exception {
184 
185         ZipFile zip = new ZipFile(file);
186         try {
187             Enumeration<? extends ZipEntry> entries = zip.entries();
188             while (entries.hasMoreElements()) {
189                 ZipEntry entry = entries.nextElement();
190 
191                 if (entry.getName().endsWith(KNOWN_FAILURES_XML_FILE)) {
192                     SAXParserFactory spf = SAXParserFactory.newInstance();
193                     spf.setNamespaceAware(false);
194                     SAXParser saxParser = spf.newSAXParser();
195                     InputStream xmlStream = zip.getInputStream(entry);
196                     KnownFailuresXmlHandler kfXmlHandler = new KnownFailuresXmlHandler(tsBld);
197                     saxParser.parse(xmlStream, kfXmlHandler);
198                     xmlStream.close();
199                 }
200             }
201         } finally {
202             zip.close();
203         }
204     }
205 
206     // Parse a folder to add all entries
parseFolder(TestSuiteContent.Builder testSuiteContent, String fPath, String rPath)207     private static Entry.Builder parseFolder(TestSuiteContent.Builder testSuiteContent, String fPath, String rPath)
208             throws IOException, NoSuchAlgorithmException {
209         Entry.Builder folderEntry = Entry.newBuilder();
210 
211         File folder = new File(fPath);
212         File rFolder = new File(rPath);
213         Path folderPath = Paths.get(folder.getAbsolutePath());
214         Path rootPath = Paths.get(rFolder.getAbsolutePath());
215         String folderRelativePath = rootPath.relativize(folderPath).toString();
216         String folderId = getId(folderRelativePath);
217         File[] fileList = folder.listFiles();
218         Long folderSize = 0L;
219         List <Entry> entryList = new ArrayList<Entry> ();
220         for (File file : fileList){
221             if (file.isFile()){
222                 String fileRelativePath = rootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
223                 Entry.Builder fileEntry = Entry.newBuilder();
224                 fileEntry.setId(getId(fileRelativePath));
225                 fileEntry.setName(file.getName());
226                 fileEntry.setSize(file.length());
227                 fileEntry.setContentId(getFileContentId(file));
228                 fileEntry.setRelativePath(fileRelativePath);
229                 fileEntry.setParentId(folderId);
230                 try {
231                     FileMetadata fMetadata = parseFileMetadata(fileEntry, file);
232                     if (null != fMetadata) {
233                         fileEntry.setFileMetadata(fMetadata);
234                     }
235                     // get [cts]-known-failures.xml
236                     if (file.getName().endsWith(TEST_SUITE_HARNESS)) {
237                         parseKnownFailures(testSuiteContent, file);
238                     }
239                 } catch (Exception ex) {
240                     System.err.println(
241                             String.format("Cannot parse %s",
242                                     file.getAbsolutePath()));
243                     ex.printStackTrace();
244                 }
245                 testSuiteContent.addFileEntries(fileEntry);
246                 entryList.add(fileEntry.build());
247                 folderSize += file.length();
248             } else if (file.isDirectory()){
249                 Entry.Builder subFolderEntry = parseFolder(testSuiteContent, file.getAbsolutePath(), rPath);
250                 subFolderEntry.setParentId(folderId);
251                 testSuiteContent.addFileEntries(subFolderEntry);
252                 folderSize += subFolderEntry.getSize();
253                 entryList.add(subFolderEntry.build());
254             }
255         }
256 
257         folderEntry.setId(folderId);
258         folderEntry.setName(folderRelativePath);
259         folderEntry.setSize(folderSize);
260         folderEntry.setType(Entry.EntryType.FOLDER);
261         folderEntry.setContentId(getFolderContentId(folderEntry, entryList));
262         folderEntry.setRelativePath(folderRelativePath);
263         return folderEntry;
264     }
265 
getFileContentId(File file)266     private static String getFileContentId(File file)
267             throws IOException, NoSuchAlgorithmException {
268         MessageDigest md = MessageDigest.getInstance("SHA-256");
269         FileInputStream fis = new FileInputStream(file);
270 
271         byte[] dataBytes = new byte[10240];
272 
273         int nread = 0;
274         while ((nread = fis.read(dataBytes)) != -1) {
275           md.update(dataBytes, 0, nread);
276         };
277         byte[] mdbytes = md.digest();
278 
279         // Converts to Hex String
280         StringBuffer hexString = new StringBuffer();
281         for (int i=0;i<mdbytes.length;i++) {
282             hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
283         }
284         return hexString.toString();
285     }
286 
getFolderContentId(Entry.Builder folderEntry, List<Entry> entryList)287     private static String getFolderContentId(Entry.Builder folderEntry, List<Entry> entryList)
288             throws IOException, NoSuchAlgorithmException {
289         MessageDigest md = MessageDigest.getInstance("SHA-256");
290 
291         for (Entry entry: entryList) {
292             md.update(entry.getContentId().getBytes(StandardCharsets.UTF_8));
293         }
294         byte[] mdbytes = md.digest();
295 
296         // Converts to Hex String
297         StringBuffer hexString = new StringBuffer();
298         for (int i=0;i<mdbytes.length;i++) {
299             hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
300         }
301         return hexString.toString();
302     }
303 
getId(String name)304     private static String getId(String name)
305             throws IOException, NoSuchAlgorithmException {
306         MessageDigest md = MessageDigest.getInstance("SHA-256");
307         md.update(name.getBytes(StandardCharsets.UTF_8));
308         byte[] mdbytes = md.digest();
309 
310         // Converts to Hex String
311         StringBuffer hexString = new StringBuffer();
312         for (int i=0;i<mdbytes.length;i++) {
313             hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
314         }
315         return hexString.toString();
316     }
317 
318     // Iterates though all test suite content and prints them.
printTestSuiteContent(TestSuiteContent tsContent)319     static void printTestSuiteContent(TestSuiteContent tsContent) {
320         //Header
321         System.out.printf("no,type,name,size,relative path,id,content id,parent id,description,test class");
322         // test class header
323         System.out.printf(",%s,%s,%s,%s,%s",
324                 RUNTIME_HIT_TAG, PACKAGE_TAG, JAR_NAME_TAG, NATIVE_TEST_DEVICE_PATH_TAG, MODULE_TAG);
325         // target preparer header
326         System.out.printf(",%s,%s\n",
327                 TEST_FILE_NAME_TAG, PUSH_TAG);
328 
329         int i = 1;
330         for (Entry entry: tsContent.getFileEntriesList()) {
331             System.out.printf("%d,%s,%s,%d,%s,%s,%s,%s",
332                 i++, entry.getType(), entry.getName(), entry.getSize(),
333                 entry.getRelativePath(), entry.getId(), entry.getContentId(),
334                 entry.getParentId());
335 
336             if (Entry.EntryType.CONFIG == entry.getType()) {
337                 ConfigMetadata config = entry.getFileMetadata().getConfigMetadata();
338                 System.out.printf(",%s", entry.getFileMetadata().getDescription());
339                 List<Option> optList;
340                 List<ConfigMetadata.TestClass> testClassesList = config.getTestClassesList();
341                 String rtHit = "";
342                 String pkg = "";
343                 String jar = "";
344                 String ntdPath = "";
345                 String module = "";
346 
347                 for (ConfigMetadata.TestClass tClass : testClassesList) {
348                     System.out.printf(",%s", tClass.getTestClass());
349                     optList = tClass.getOptionsList();
350                     for (Option opt : optList) {
351                         if (RUNTIME_HIT_TAG.equalsIgnoreCase(opt.getName())) {
352                             rtHit = rtHit + opt.getValue() + " ";
353                         } else if (PACKAGE_TAG.equalsIgnoreCase(opt.getName())) {
354                             pkg = pkg + opt.getValue() + " ";
355                         } else if (JAR_NAME_TAG.equalsIgnoreCase(opt.getName())) {
356                             jar = jar + opt.getValue() + " ";
357                         } else if (NATIVE_TEST_DEVICE_PATH_TAG.equalsIgnoreCase(opt.getName())) {
358                             ntdPath = ntdPath + opt.getValue() + " ";
359                         } else if (MODULE_TAG.equalsIgnoreCase(opt.getName())) {
360                             module = module + opt.getValue() + " ";
361                         }
362                     }
363                 }
364                 System.out.printf(",%s,%s,%s,%s,%s", rtHit.trim(), pkg.trim(),
365                         jar.trim(), module.trim(), ntdPath.trim());
366 
367                 List<ConfigMetadata.TargetPreparer> tPrepList = config.getTargetPreparersList();
368                 String testFile = "";
369                 String pushList = "";
370                 for (ConfigMetadata.TargetPreparer tPrep : tPrepList) {
371                     optList = tPrep.getOptionsList();
372                     for (Option opt : optList) {
373                         if (TEST_FILE_NAME_TAG.equalsIgnoreCase(opt.getName())) {
374                             testFile = testFile + opt.getValue() + " ";
375                         } else if (PUSH_TAG.equalsIgnoreCase(opt.getName())) {
376                             pushList = pushList + opt.getValue() + " ";
377                         }
378                     }
379                 }
380                 System.out.printf(",%s,%s", testFile.trim(), pushList.trim());
381             }
382             System.out.printf("\n");
383         }
384 
385         System.out.printf("\nKnown Failures\n");
386         for (String kf : tsContent.getKnownFailuresList()) {
387             System.out.printf("%s\n", kf);
388         }
389     }
390 
main(String[] args)391     public static void main(String[] args)
392             throws IOException, NoSuchAlgorithmException {
393         String outputFilePath = "./tsContentMessage.pb";
394         String outputTestCaseListFilePath = "./tsTestCaseList.pb";
395         String tsPath = "";
396 
397         for (int i = 0; i < args.length; i++) {
398             if (args[i].startsWith("-")) {
399                 if ("-o".equals(args[i])) {
400                     outputFilePath = getExpectedArg(args, ++i);
401                 } else if ("-t".equals(args[i])) {
402                     outputTestCaseListFilePath = getExpectedArg(args, ++i);
403                 } else if ("-i".equals(args[i])) {
404                     tsPath = getExpectedArg(args, ++i);
405                     File file = new File(tsPath);
406                     // Only acception a folder
407                     if (!file.isDirectory()) {
408                         printUsage();
409                     }
410                 } else {
411                     printUsage();
412                 }
413             }
414         }
415 
416         TestSuiteContent tsContent = parseTestSuiteFolder(tsPath);
417 
418         // Write test suite content message to disk.
419         FileOutputStream output = new FileOutputStream(outputFilePath);
420         try {
421           tsContent.writeTo(output);
422         } finally {
423           output.close();
424         }
425 
426         // Read message from the file and print them out
427         TestSuiteContent tsContent1 =
428                 TestSuiteContent.parseFrom(new FileInputStream(outputFilePath));
429         printTestSuiteContent(tsContent1);
430     }
431 }
432