1#!/usr/bin/env python
2#
3# Copyright (C) 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16"""Unit tests for verify_overlaps_test.py."""
17import io
18import unittest
19
20import verify_overlaps as vo
21
22
23class TestDetectOverlaps(unittest.TestCase):
24
25    @staticmethod
26    def read_flag_trie_from_string(csvdata):
27        with io.StringIO(csvdata) as f:
28            return vo.read_flag_trie_from_stream(f)
29
30    @staticmethod
31    def read_signature_csv_from_string_as_dict(csvdata):
32        with io.StringIO(csvdata) as f:
33            return vo.read_signature_csv_from_stream_as_dict(f)
34
35    @staticmethod
36    def extract_subset_from_monolithic_flags_as_dict_from_string(
37            monolithic, patterns):
38        with io.StringIO(patterns) as f:
39            return vo.extract_subset_from_monolithic_flags_as_dict_from_stream(
40                monolithic, f)
41
42    extractInput = """
43Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
44Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
45Ljava/util/zip/ZipFile;-><clinit>()V,blocked
46Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;,blocked
47Ljava/lang/Character;->serialVersionUID:J,sdk
48Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V,blocked
49"""
50
51    def test_extract_subset_signature(self):
52        monolithic = self.read_flag_trie_from_string(
53            TestDetectOverlaps.extractInput)
54
55        patterns = "Ljava/lang/Object;->hashCode()I"
56
57        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
58            monolithic, patterns)
59        expected = {
60            "Ljava/lang/Object;->hashCode()I": {
61                None: ["public-api", "system-api", "test-api"],
62                "signature": "Ljava/lang/Object;->hashCode()I",
63            },
64        }
65        self.assertEqual(expected, subset)
66
67    def test_extract_subset_class(self):
68        monolithic = self.read_flag_trie_from_string(
69            TestDetectOverlaps.extractInput)
70
71        patterns = "java/lang/Object"
72
73        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
74            monolithic, patterns)
75        expected = {
76            "Ljava/lang/Object;->hashCode()I": {
77                None: ["public-api", "system-api", "test-api"],
78                "signature": "Ljava/lang/Object;->hashCode()I",
79            },
80            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
81                None: ["blocked"],
82                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
83            },
84        }
85        self.assertEqual(expected, subset)
86
87    def test_extract_subset_outer_class(self):
88        monolithic = self.read_flag_trie_from_string(
89            TestDetectOverlaps.extractInput)
90
91        patterns = "java/lang/Character"
92
93        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
94            monolithic, patterns)
95        expected = {
96            "Ljava/lang/Character$UnicodeScript;"
97            "->of(I)Ljava/lang/Character$UnicodeScript;": {
98                None: ["blocked"],
99                "signature": "Ljava/lang/Character$UnicodeScript;"
100                             "->of(I)Ljava/lang/Character$UnicodeScript;",
101            },
102            "Ljava/lang/Character;->serialVersionUID:J": {
103                None: ["sdk"],
104                "signature": "Ljava/lang/Character;->serialVersionUID:J",
105            },
106        }
107        self.assertEqual(expected, subset)
108
109    def test_extract_subset_nested_class(self):
110        monolithic = self.read_flag_trie_from_string(
111            TestDetectOverlaps.extractInput)
112
113        patterns = "java/lang/Character$UnicodeScript"
114
115        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
116            monolithic, patterns)
117        expected = {
118            "Ljava/lang/Character$UnicodeScript;"
119            "->of(I)Ljava/lang/Character$UnicodeScript;": {
120                None: ["blocked"],
121                "signature": "Ljava/lang/Character$UnicodeScript;"
122                             "->of(I)Ljava/lang/Character$UnicodeScript;",
123            },
124        }
125        self.assertEqual(expected, subset)
126
127    def test_extract_subset_package(self):
128        monolithic = self.read_flag_trie_from_string(
129            TestDetectOverlaps.extractInput)
130
131        patterns = "java/lang/*"
132
133        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
134            monolithic, patterns)
135        expected = {
136            "Ljava/lang/Character$UnicodeScript;"
137            "->of(I)Ljava/lang/Character$UnicodeScript;": {
138                None: ["blocked"],
139                "signature": "Ljava/lang/Character$UnicodeScript;"
140                             "->of(I)Ljava/lang/Character$UnicodeScript;",
141            },
142            "Ljava/lang/Character;->serialVersionUID:J": {
143                None: ["sdk"],
144                "signature": "Ljava/lang/Character;->serialVersionUID:J",
145            },
146            "Ljava/lang/Object;->hashCode()I": {
147                None: ["public-api", "system-api", "test-api"],
148                "signature": "Ljava/lang/Object;->hashCode()I",
149            },
150            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
151                None: ["blocked"],
152                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
153            },
154            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V": {
155                None: ["blocked"],
156                "signature": "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
157            },
158        }
159        self.assertEqual(expected, subset)
160
161    def test_extract_subset_recursive_package(self):
162        monolithic = self.read_flag_trie_from_string(
163            TestDetectOverlaps.extractInput)
164
165        patterns = "java/**"
166
167        subset = self.extract_subset_from_monolithic_flags_as_dict_from_string(
168            monolithic, patterns)
169        expected = {
170            "Ljava/lang/Character$UnicodeScript;"
171            "->of(I)Ljava/lang/Character$UnicodeScript;": {
172                None: ["blocked"],
173                "signature": "Ljava/lang/Character$UnicodeScript;"
174                             "->of(I)Ljava/lang/Character$UnicodeScript;",
175            },
176            "Ljava/lang/Character;->serialVersionUID:J": {
177                None: ["sdk"],
178                "signature": "Ljava/lang/Character;->serialVersionUID:J",
179            },
180            "Ljava/lang/Object;->hashCode()I": {
181                None: ["public-api", "system-api", "test-api"],
182                "signature": "Ljava/lang/Object;->hashCode()I",
183            },
184            "Ljava/lang/Object;->toString()Ljava/lang/String;": {
185                None: ["blocked"],
186                "signature": "Ljava/lang/Object;->toString()Ljava/lang/String;",
187            },
188            "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V": {
189                None: ["blocked"],
190                "signature": "Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V",
191            },
192            "Ljava/util/zip/ZipFile;-><clinit>()V": {
193                None: ["blocked"],
194                "signature": "Ljava/util/zip/ZipFile;-><clinit>()V",
195            },
196        }
197        self.assertEqual(expected, subset)
198
199    def test_read_trie_duplicate(self):
200        with self.assertRaises(Exception) as context:
201            self.read_flag_trie_from_string("""
202Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
203Ljava/lang/Object;->hashCode()I,blocked
204""")
205        self.assertTrue("Duplicate signature: Ljava/lang/Object;->hashCode()I"
206                        in str(context.exception))
207
208    def test_read_trie_missing_member(self):
209        with self.assertRaises(Exception) as context:
210            self.read_flag_trie_from_string("""
211Ljava/lang/Object,public-api,system-api,test-api
212""")
213        self.assertTrue(
214            "Invalid signature: Ljava/lang/Object, "
215            "does not identify a specific member" in str(context.exception))
216
217    def test_match(self):
218        monolithic = self.read_signature_csv_from_string_as_dict("""
219Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
220""")
221        modular = self.read_signature_csv_from_string_as_dict("""
222Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
223""")
224        mismatches = vo.compare_signature_flags(monolithic, modular,
225                                                ["blocked"])
226        expected = []
227        self.assertEqual(expected, mismatches)
228
229    def test_mismatch_overlapping_flags(self):
230        monolithic = self.read_signature_csv_from_string_as_dict("""
231Ljava/lang/Object;->toString()Ljava/lang/String;,public-api
232""")
233        modular = self.read_signature_csv_from_string_as_dict("""
234Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
235""")
236        mismatches = vo.compare_signature_flags(monolithic, modular,
237                                                ["blocked"])
238        expected = [
239            (
240                "Ljava/lang/Object;->toString()Ljava/lang/String;",
241                ["public-api", "system-api", "test-api"],
242                ["public-api"],
243            ),
244        ]
245        self.assertEqual(expected, mismatches)
246
247    def test_mismatch_monolithic_blocked(self):
248        monolithic = self.read_signature_csv_from_string_as_dict("""
249Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
250""")
251        modular = self.read_signature_csv_from_string_as_dict("""
252Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
253""")
254        mismatches = vo.compare_signature_flags(monolithic, modular,
255                                                ["blocked"])
256        expected = [
257            (
258                "Ljava/lang/Object;->toString()Ljava/lang/String;",
259                ["public-api", "system-api", "test-api"],
260                ["blocked"],
261            ),
262        ]
263        self.assertEqual(expected, mismatches)
264
265    def test_mismatch_modular_blocked(self):
266        monolithic = self.read_signature_csv_from_string_as_dict("""
267Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
268""")
269        modular = self.read_signature_csv_from_string_as_dict("""
270Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
271""")
272        mismatches = vo.compare_signature_flags(monolithic, modular,
273                                                ["blocked"])
274        expected = [
275            (
276                "Ljava/lang/Object;->toString()Ljava/lang/String;",
277                ["blocked"],
278                ["public-api", "system-api", "test-api"],
279            ),
280        ]
281        self.assertEqual(expected, mismatches)
282
283    def test_match_treat_missing_from_modular_as_blocked(self):
284        monolithic = self.read_signature_csv_from_string_as_dict("")
285        modular = self.read_signature_csv_from_string_as_dict("""
286Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
287""")
288        mismatches = vo.compare_signature_flags(monolithic, modular,
289                                                ["blocked"])
290        expected = [
291            (
292                "Ljava/lang/Object;->toString()Ljava/lang/String;",
293                ["public-api", "system-api", "test-api"],
294                [],
295            ),
296        ]
297        self.assertEqual(expected, mismatches)
298
299    def test_mismatch_treat_missing_from_modular_as_blocked(self):
300        monolithic = self.read_signature_csv_from_string_as_dict("""
301Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
302""")
303        modular = {}
304        mismatches = vo.compare_signature_flags(monolithic, modular,
305                                                ["blocked"])
306        expected = [
307            (
308                "Ljava/lang/Object;->hashCode()I",
309                ["blocked"],
310                ["public-api", "system-api", "test-api"],
311            ),
312        ]
313        self.assertEqual(expected, mismatches)
314
315    def test_blocked_missing_from_modular(self):
316        monolithic = self.read_signature_csv_from_string_as_dict("""
317Ljava/lang/Object;->hashCode()I,blocked
318""")
319        modular = {}
320        mismatches = vo.compare_signature_flags(monolithic, modular,
321                                                ["blocked"])
322        expected = []
323        self.assertEqual(expected, mismatches)
324
325    def test_match_treat_missing_from_modular_as_empty(self):
326        monolithic = self.read_signature_csv_from_string_as_dict("")
327        modular = self.read_signature_csv_from_string_as_dict("""
328Ljava/lang/Object;->toString()Ljava/lang/String;,public-api,system-api,test-api
329""")
330        mismatches = vo.compare_signature_flags(monolithic, modular, [])
331        expected = [
332            (
333                "Ljava/lang/Object;->toString()Ljava/lang/String;",
334                ["public-api", "system-api", "test-api"],
335                [],
336            ),
337        ]
338        self.assertEqual(expected, mismatches)
339
340    def test_mismatch_treat_missing_from_modular_as_empty(self):
341        monolithic = self.read_signature_csv_from_string_as_dict("""
342Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
343""")
344        modular = {}
345        mismatches = vo.compare_signature_flags(monolithic, modular, [])
346        expected = [
347            (
348                "Ljava/lang/Object;->hashCode()I",
349                [],
350                ["public-api", "system-api", "test-api"],
351            ),
352        ]
353        self.assertEqual(expected, mismatches)
354
355    def test_empty_missing_from_modular(self):
356        monolithic = self.read_signature_csv_from_string_as_dict("""
357Ljava/lang/Object;->hashCode()I
358""")
359        modular = {}
360        mismatches = vo.compare_signature_flags(monolithic, modular, [])
361        expected = []
362        self.assertEqual(expected, mismatches)
363
364
365if __name__ == "__main__":
366    unittest.main(verbosity=2)
367