Turn hidden API lists into a single CSV

Maintaining multiple text files has become too cumbersome as adding
each new category of API requires changes across many projects.
This patch changes generate_hiddenapi_lists.py to produce a single
CSV file in the format:

  <api_signature>,<flag1>,...,<flagN>

It can accept legacy API list files as input (for existing
frameworks/base/config/hiddenapi-*.txt files) as well as per-package
CSVs produced by class2greylist.

Test: m, check lists have not changed
Test: phone boots
Test: tools/hiddenapi/generate_hiddenapi_lists_test.py
Change-Id: Iebcef426ec93ea1d72b662bbff91d4e068fa0a70
diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py
index 4716241..249f37d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists_test.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists_test.py
@@ -2,14 +2,14 @@
 #
 # Copyright (C) 2018 The Android Open Source Project
 #
-# Licensed under the Apache License, Version 2.0 (the "License");
+# 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,
+# 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.
@@ -18,90 +18,90 @@
 from generate_hiddenapi_lists import *
 
 class TestHiddenapiListGeneration(unittest.TestCase):
+    def test_init(self):
+        # Check empty lists
+        flags = FlagsDict([], [])
+        self.assertEquals(flags.generate_csv(), [])
 
-    def test_move_between_sets(self):
-        A = set([1, 2, 3, 4])
-        B = set([5, 6, 7, 8])
-        move_between_sets(set([2, 4]), A, B)
-        self.assertEqual(A, set([1, 3]))
-        self.assertEqual(B, set([2, 4, 5, 6, 7, 8]))
+        # Check valid input - two public and two private API signatures.
+        flags = FlagsDict(['A', 'B'], ['C', 'D'])
+        self.assertEquals(flags.generate_csv(),
+                          [ 'A,' + FLAG_WHITELIST, 'B,' + FLAG_WHITELIST, 'C', 'D' ])
 
-    def test_move_between_sets_fail_not_superset(self):
-        A = set([1, 2, 3, 4])
-        B = set([5, 6, 7, 8])
-        with self.assertRaises(AssertionError) as ar:
-            move_between_sets(set([0, 2]), A, B)
+        # Check invalid input - overlapping public/private API signatures.
+        with self.assertRaises(AssertionError):
+            flags = FlagsDict(['A', 'B'], ['B', 'C', 'D'])
 
-    def test_move_between_sets_fail_not_disjoint(self):
-        A = set([1, 2, 3, 4])
-        B = set([4, 5, 6, 7, 8])
-        with self.assertRaises(AssertionError) as ar:
-            move_between_sets(set([1, 4]), A, B)
+    def test_filter_apis(self):
+        # Initialize flags so that A and B are put on the whitelist and
+        # C, D, E are left unassigned. Try filtering for the unassigned ones.
+        flags = FlagsDict(['A', 'B'], ['C', 'D', 'E'])
+        filter_set = flags.filter_apis(lambda api, flags: not flags)
+        self.assertTrue(isinstance(filter_set, set))
+        self.assertEqual(filter_set, set([ 'C', 'D', 'E' ]))
 
-    def test_get_package_name(self):
-        self.assertEqual(get_package_name("Ljava/lang/String;->clone()V"), "Ljava/lang/")
+    def test_get_valid_subset_of_unassigned_keys(self):
+        # Create flags where only A is unassigned.
+        flags = FlagsDict(['A'], ['B', 'C'])
+        flags.assign_flag(FLAG_GREYLIST, set(['C']))
+        self.assertEquals(flags.generate_csv(),
+            [ 'A,' + FLAG_WHITELIST, 'B', 'C,' + FLAG_GREYLIST ])
 
-    def test_get_package_name_fail_no_arrow(self):
-        with self.assertRaises(AssertionError) as ar:
-            get_package_name("Ljava/lang/String;-clone()V")
-        with self.assertRaises(AssertionError) as ar:
-            get_package_name("Ljava/lang/String;>clone()V")
-        with self.assertRaises(AssertionError) as ar:
-            get_package_name("Ljava/lang/String;__clone()V")
-
-    def test_get_package_name_fail_no_package(self):
-        with self.assertRaises(AssertionError) as ar:
-            get_package_name("LString;->clone()V")
-
-    def test_all_package_names(self):
-        self.assertEqual(all_package_names(), set())
-        self.assertEqual(all_package_names(set(["Lfoo/Bar;->baz()V"])), set(["Lfoo/"]))
+        # Check three things:
+        # (1) B is selected as valid unassigned
+        # (2) A is not selected because it is assigned 'whitelist'
+        # (3) D is not selected because it is not a valid key
         self.assertEqual(
-            all_package_names(set(["Lfoo/Bar;->baz()V", "Lfoo/BarX;->bazx()I"])),
-            set(["Lfoo/"]))
-        self.assertEqual(
-            all_package_names(
-                set(["Lfoo/Bar;->baz()V"]),
-                set(["Lfoo/BarX;->bazx()I", "Labc/xyz/Mno;->ijk()J"])),
-            set(["Lfoo/", "Labc/xyz/"]))
+            flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ]))
 
-    def test_move_all(self):
-        src = set([ "abc", "xyz" ])
-        dst = set([ "def" ])
-        move_all(src, dst)
-        self.assertEqual(src, set())
-        self.assertEqual(dst, set([ "abc", "def", "xyz" ]))
+    def test_parse_and_merge_csv(self):
+        flags = FlagsDict(['A'], ['B'])
+        self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ])
 
-    def test_move_from_packages(self):
-        src = set([ "Lfoo/bar/ClassA;->abc()J",        # will be moved
-                    "Lfoo/bar/ClassA;->def()J",        # will be moved
-                    "Lcom/pkg/example/ClassD;->ijk:J", # not moved: different package
-                    "Lfoo/bar/xyz/ClassC;->xyz()Z" ])  # not moved: subpackage
-        dst = set()
-        packages = set([ "Lfoo/bar/" ])
-        move_from_packages(packages, src, dst)
-        self.assertEqual(
-            src, set([ "Lfoo/bar/xyz/ClassC;->xyz()Z", "Lcom/pkg/example/ClassD;->ijk:J" ]))
-        self.assertEqual(
-            dst, set([ "Lfoo/bar/ClassA;->abc()J", "Lfoo/bar/ClassA;->def()J" ]))
+        # Test empty CSV entry.
+        flags.parse_and_merge_csv(['B'])
+        self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ])
 
-    def test_move_serialization(self):
-        # All the entries should be moved apart from the last one
-        src = set([ "Lfoo/bar/ClassA;->readObject(Ljava/io/ObjectInputStream;)V",
-                    "Lfoo/bar/ClassA;->readObjectNoData()V",
-                    "Lfoo/bar/ClassA;->readResolve()Ljava/lang/Object;",
-                    "Lfoo/bar/ClassA;->serialVersionUID:J",
-                    "Lfoo/bar/ClassA;->serialPersistentFields:[Ljava/io/ObjectStreamField;",
-                    "Lfoo/bar/ClassA;->writeObject(Ljava/io/ObjectOutputStream;)V",
-                    "Lfoo/bar/ClassA;->writeReplace()Ljava/lang/Object;",
-                    # Should not be moved as signature does not match
-                    "Lfoo/bar/ClassA;->readObject(Ljava/io/ObjectInputStream;)I"])
-        expectedToMove = len(src) - 1
-        dst = set()
-        packages = set([ "Lfoo/bar/" ])
-        move_serialization(src, dst)
-        self.assertEqual(len(src), 1)
-        self.assertEqual(len(dst), expectedToMove)
+        # Test assigning an already assigned flag.
+        flags.parse_and_merge_csv(['A,' + FLAG_WHITELIST])
+        self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ])
+
+        # Test new additions.
+        flags.parse_and_merge_csv([
+            'A,' + FLAG_GREYLIST,
+            'B,' + FLAG_BLACKLIST + ',' + FLAG_GREYLIST_MAX_O ])
+        self.assertEqual(flags.generate_csv(),
+            [ 'A,' + FLAG_GREYLIST + "," + FLAG_WHITELIST,
+              'B,' + FLAG_BLACKLIST + "," + FLAG_GREYLIST_MAX_O ])
+
+        # Test unknown API signature.
+        with self.assertRaises(AssertionError):
+            flags.parse_and_merge_csv([ 'C' ])
+
+        # Test unknown flag.
+        with self.assertRaises(AssertionError):
+            flags.parse_and_merge_csv([ 'A,foo' ])
+
+    def test_assign_flag(self):
+        flags = FlagsDict(['A'], ['B'])
+        self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ])
+
+        # Test assigning an already assigned flag.
+        flags.assign_flag(FLAG_WHITELIST, set([ 'A' ]))
+        self.assertEquals(flags.generate_csv(), [ 'A,' + FLAG_WHITELIST, 'B' ])
+
+        # Test new additions.
+        flags.assign_flag(FLAG_GREYLIST, set([ 'A', 'B' ]))
+        self.assertEquals(flags.generate_csv(),
+            [ 'A,' + FLAG_GREYLIST + "," + FLAG_WHITELIST, 'B,' + FLAG_GREYLIST ])
+
+        # Test invalid API signature.
+        with self.assertRaises(AssertionError):
+            flags.assign_flag(FLAG_WHITELIST, set([ 'C' ]))
+
+        # Test invalid flag.
+        with self.assertRaises(AssertionError):
+            flags.assign_flag('foo', set([ 'A' ]))
 
 if __name__ == '__main__':
     unittest.main()