TestSuiteContentReport PoC to collect Test Suite Release Contents

Bug: 71688723
Test: make cts-test-coverage
Change-Id: Id2939a9873e46b16abdc9140de38ce960ff38dce
diff --git a/tools/cts-api-coverage/Android.mk b/tools/cts-api-coverage/Android.mk
index c66f7d4..7b56e21 100644
--- a/tools/cts-api-coverage/Android.mk
+++ b/tools/cts-api-coverage/Android.mk
@@ -14,23 +14,51 @@
 
 LOCAL_PATH := $(call my-dir)
 
-# the hat script
+# the cts-api-coverage script
 # ============================================================
 include $(CLEAR_VARS)
 LOCAL_IS_HOST_MODULE := true
 LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_MODULE := cts-api-coverage
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    compatibility-common-util-devicesidelib
 LOCAL_SRC_FILES := etc/$(LOCAL_MODULE)
 LOCAL_ADDITIONAL_DEPENDENCIES := $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE)$(COMMON_JAVA_PACKAGE_SUFFIX)
 
 include $(BUILD_PREBUILT)
 
-# the other stuff
+# the ndk-api-report script
 # ============================================================
-subdirs := $(addprefix $(LOCAL_PATH)/,$(addsuffix /Android.mk, \
-		src \
-	))
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE := ndk-api-report
+LOCAL_SRC_FILES := etc/$(LOCAL_MODULE)
 
-include $(subdirs)
+include $(BUILD_PREBUILT)
+
+# cts-api-coverage java library
+# ============================================================
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+    $(call all-subdir-java-files) \
+    $(call all-proto-files-under, proto)
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/proto/
+
+LOCAL_JAVA_RESOURCE_DIRS := res
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+  compatibility-host-util \
+  dexlib2
+
+LOCAL_MODULE := cts-api-coverage
+
+# This tool is not checking any dependencies or metadata, so all of the
+# dependencies of all of the tests must be on its classpath. This is
+# super fragile.
+LOCAL_STATIC_JAVA_LIBRARIES += \
+        platformprotos
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/MANIFEST.mf b/tools/cts-api-coverage/MANIFEST.mf
similarity index 100%
rename from tools/cts-api-coverage/src/MANIFEST.mf
rename to tools/cts-api-coverage/MANIFEST.mf
diff --git a/tools/cts-api-coverage/proto/testsuite.proto b/tools/cts-api-coverage/proto/testsuite.proto
new file mode 100644
index 0000000..45a8a71
--- /dev/null
+++ b/tools/cts-api-coverage/proto/testsuite.proto
@@ -0,0 +1,70 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [START declaration]
+syntax = "proto3";
+package com_android_cts_apicoverage;
+// [END declaration]
+
+// [START java_declaration]
+option java_package = "com.android.cts.apicoverage";
+option java_outer_classname = "TestSuiteProto";
+// [END java_declaration]
+
+// [START messages]
+// An entry in a Test Suire Release messages: cts, etc.
+message Entry {
+    // Entry ID
+    string id = 1;
+    // Name
+    string name = 2;
+
+    enum EntryType {
+        FOLDER = 0;
+        FILE = 1;
+        CONFIG = 2;
+        JAR = 3;
+        APK = 4;
+        EXE = 5;
+        SO = 6;
+    }
+
+    // Type
+    EntryType type = 3;
+    // Size
+    int64 size = 4;
+    // Content ID
+    string content_id = 5;
+    // Parent entry ID
+    string parent_id = 6;
+    // Relative path
+    string relative_path = 7;
+}
+
+// A Test Suite Release: cts, etc.
+message TestSuiteContent {
+    // Entry ID
+    string id = 1;
+    // Name
+    string name = 2;
+    // Version
+    string version = 3;
+    // Build ID
+    string bid = 4;
+    // Content ID
+    string content_id = 5;
+    // File Entries
+    repeated Entry file_entries = 6;
+}
+// [END messages]
diff --git a/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/res/api-coverage.xsl
similarity index 100%
rename from tools/cts-api-coverage/src/res/api-coverage.xsl
rename to tools/cts-api-coverage/res/api-coverage.xsl
diff --git a/tools/cts-api-coverage/src/Android.mk b/tools/cts-api-coverage/src/Android.mk
deleted file mode 100644
index fcf7ad4..0000000
--- a/tools/cts-api-coverage/src/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-
-# cts-api-coverage java library
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_PROTOC_OPTIMIZE_TYPE := full
-LOCAL_JAVA_RESOURCE_DIRS := res 
-LOCAL_JAR_MANIFEST := MANIFEST.mf
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-  compatibility-host-util \
-  dexlib2
-
-LOCAL_MODULE := cts-api-coverage
-
-# This tool is not checking any dependencies or metadata, so all of the
-# dependencies of all of the tests must be on its classpath. This is
-# super fragile.
-LOCAL_STATIC_JAVA_LIBRARIES += \
-        platformprotos
-
-include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
new file mode 100644
index 0000000..e9ab1be
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+
+import com.android.cts.apicoverage.TestSuiteProto.Entry;
+import com.android.cts.apicoverage.TestSuiteProto.Entry.EntryType;
+import com.android.cts.apicoverage.TestSuiteProto.TestSuiteContent;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.List;
+
+class TestSuiteContentReport {
+    private static void printUsage() {
+        System.out.println("Usage: test-suite-content-report [OPTION]...");
+        System.out.println();
+        System.out.println("Generates test suite content protocal buffer message.");
+        System.out.println();
+        System.out.println(
+                "$ANDROID_HOST_OUT/bin/test-suite-content-report "
+                        + "-i out/host/linux-x86/cts/android-cts "
+                        + "-o ./cts-content.pb");
+        System.out.println();
+        System.out.println("Options:");
+        System.out.println("  -o FILE                output file or standard out if not given");
+        System.out.println("  -i PATH                path to the Test Suite Folder");
+        System.out.println();
+        System.exit(1);
+    }
+
+    /** Get the argument or print out the usage and exit. */
+    private static String getExpectedArg(String[] args, int index) {
+        if (index < args.length) {
+            return args[index];
+        } else {
+            printUsage();
+            return null;    // Never will happen because printUsage will call exit(1)
+        }
+    }
+
+    public static TestSuiteContent parseTestSuiteFolder(String testSuitePath)
+            throws IOException, NoSuchAlgorithmException {
+
+        TestSuiteContent.Builder testSuiteContent = TestSuiteContent.newBuilder();
+        testSuiteContent.addFileEntries(parseFolder(testSuiteContent, testSuitePath, testSuitePath));
+        return testSuiteContent.build();
+    }
+
+    // Parse a folder to add all entries
+    private static Entry.Builder parseFolder(TestSuiteContent.Builder testSuiteContent, String fPath, String rPath)
+            throws IOException, NoSuchAlgorithmException {
+        Entry.Builder folderEntry = Entry.newBuilder();
+
+        File folder = new File(fPath);
+        File rFolder = new File(rPath);
+        Path folderPath = Paths.get(folder.getAbsolutePath());
+        Path rootPath = Paths.get(rFolder.getAbsolutePath());
+        String folderRelativePath = rootPath.relativize(folderPath).toString();
+        String folderId = getId(folderRelativePath);
+        File[] fileList = folder.listFiles();
+        Long folderSize = 0L;
+        List <Entry> entryList = new ArrayList<Entry> ();
+        for (File file : fileList){
+            if (file.isFile()){
+                String fileRelativePath = rootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
+                Entry.Builder fileEntry = Entry.newBuilder();
+                fileEntry.setId(getId(fileRelativePath));
+                fileEntry.setName(file.getName());
+                fileEntry.setSize(file.length());
+                fileEntry.setType(EntryType.FILE);
+                fileEntry.setContentId(getFileContentId(file));
+                fileEntry.setRelativePath(fileRelativePath);
+                fileEntry.setParentId(folderId);
+                testSuiteContent.addFileEntries(fileEntry);
+                entryList.add(fileEntry.build());
+                folderSize += file.length();
+            } else if (file.isDirectory()){
+                Entry.Builder subFolderEntry = parseFolder(testSuiteContent, file.getAbsolutePath(), rPath);
+                subFolderEntry.setParentId(folderId);
+                testSuiteContent.addFileEntries(subFolderEntry);
+                folderSize += subFolderEntry.getSize();
+                entryList.add(subFolderEntry.build());
+            }
+        }
+
+        folderEntry.setId(folderId);
+        folderEntry.setName(folderRelativePath);
+        folderEntry.setSize(folderSize);
+        folderEntry.setType(EntryType.FOLDER);
+        folderEntry.setContentId(getFolderContentId(folderEntry, entryList));
+        folderEntry.setRelativePath(folderRelativePath);
+        return folderEntry;
+    }
+
+    private static String getFileContentId(File file)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        FileInputStream fis = new FileInputStream(file);
+
+        byte[] dataBytes = new byte[10240];
+
+        int nread = 0;
+        while ((nread = fis.read(dataBytes)) != -1) {
+          md.update(dataBytes, 0, nread);
+        };
+        byte[] mdbytes = md.digest();
+
+        // Converts to Hex String
+        StringBuffer hexString = new StringBuffer();
+        for (int i=0;i<mdbytes.length;i++) {
+            hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+        }
+        return hexString.toString();
+    }
+
+    private static String getFolderContentId(Entry.Builder folderEntry, List<Entry> entryList)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+
+        for (Entry entry: entryList) {
+            md.update(entry.getContentId().getBytes(StandardCharsets.UTF_8));
+        }
+        byte[] mdbytes = md.digest();
+
+        // Converts to Hex String
+        StringBuffer hexString = new StringBuffer();
+        for (int i=0;i<mdbytes.length;i++) {
+            hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+        }
+        return hexString.toString();
+    }
+
+    private static String getId(String name)
+            throws IOException, NoSuchAlgorithmException {
+        MessageDigest md = MessageDigest.getInstance("SHA-256");
+        md.update(name.getBytes(StandardCharsets.UTF_8));
+        byte[] mdbytes = md.digest();
+
+        // Converts to Hex String
+        StringBuffer hexString = new StringBuffer();
+        for (int i=0;i<mdbytes.length;i++) {
+            hexString.append(Integer.toHexString(0xFF & mdbytes[i]));
+        }
+        return hexString.toString();
+    }
+
+    // Iterates though all test suite content and prints them.
+    static void Print(TestSuiteContent tsContent) {
+        System.out.printf("no,name,size,relativePath,id,cid,pid\n");
+        int i = 1;
+        for(Entry entry: tsContent.getFileEntriesList()) {
+            System.out.printf("%d,%s,%d,%s,%s,%s,%s\n", i++,
+                entry.getName(), entry.getSize(),
+                entry.getRelativePath(),
+                entry.getId(), entry.getContentId(),entry.getParentId());
+        }
+    }
+
+    public static void main(String[] args)
+            throws IOException, NoSuchAlgorithmException {
+        String outputFilePath = "./tsContentMessage.pb";
+        String tsPath = "";
+
+        for (int i = 0; i < args.length; i++) {
+            if (args[i].startsWith("-")) {
+                if ("-o".equals(args[i])) {
+                    outputFilePath = getExpectedArg(args, ++i);
+                } else if ("-i".equals(args[i])) {
+                    tsPath = getExpectedArg(args, ++i);
+                    File file = new File(tsPath);
+                    if (file.isDirectory()) {
+                        //
+                    } else {
+                        printUsage();
+                    }
+                } else {
+                    printUsage();
+                }
+            }
+        }
+
+        TestSuiteContent tsContent = parseTestSuiteFolder(tsPath);
+
+        // Write test suite content message to disk.
+        FileOutputStream output = new FileOutputStream(outputFilePath);
+        try {
+          tsContent.writeTo(output);
+        } finally {
+          output.close();
+        }
+
+        // Read message from the file and print them out
+        TestSuiteContent tsContent1 =
+          TestSuiteContent.parseFrom(new FileInputStream(outputFilePath));
+        Print(tsContent1);
+    }
+}