1#!/usr/bin/env python3
2#
3# Copyright (C) 2020 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
17import contextlib
18import io
19import unittest
20
21from unittest.mock import *
22from post_process_props import *
23
24class PropTestCase(unittest.TestCase):
25  def test_createFromLine(self):
26    p = Prop.from_line("# this is comment")
27    self.assertTrue(p.is_comment())
28    self.assertEqual("", p.name)
29    self.assertEqual("", p.value)
30    self.assertFalse(p.is_optional())
31    self.assertEqual("# this is comment", str(p))
32
33    for line in ["a=b", "a = b", "a= b", "a =b", "  a=b   "]:
34      p = Prop.from_line(line)
35      self.assertFalse(p.is_comment())
36      self.assertEqual("a", p.name)
37      self.assertEqual("b", p.value)
38      self.assertFalse(p.is_optional())
39      self.assertEqual("a=b", str(p))
40
41    for line in ["a?=b", "a ?= b", "a?= b", "a ?=b", "  a?=b   "]:
42      p = Prop.from_line(line)
43      self.assertFalse(p.is_comment())
44      self.assertEqual("a", p.name)
45      self.assertEqual("b", p.value)
46      self.assertTrue(p.is_optional())
47      self.assertEqual("a?=b", str(p))
48
49  def test_makeAsComment(self):
50    p = Prop.from_line("a=b")
51    p.comments.append("# a comment")
52    self.assertFalse(p.is_comment())
53
54    p.make_as_comment()
55    self.assertTrue(p.is_comment())
56    self.assertEqual("# a comment\n#a=b", str(p))
57
58class PropListTestcase(unittest.TestCase):
59  def setUp(self):
60    content = """
61    # comment
62    foo=true
63    bar=false
64    qux?=1
65    # another comment
66    foo?=false
67    """
68    self.patcher = patch("post_process_props.open", mock_open(read_data=content))
69    self.mock_open = self.patcher.start()
70    self.props = PropList("file")
71
72  def tearDown(self):
73    self.patcher.stop()
74    self.props = None
75
76  def test_readFromFile(self):
77    self.assertEqual(4, len(self.props.get_all_props()))
78    expected = [
79        ("foo", "true", False),
80        ("bar", "false", False),
81        ("qux", "1", True),
82        ("foo", "false", True)
83    ]
84    for i,p in enumerate(self.props.get_all_props()):
85      self.assertEqual(expected[i][0], p.name)
86      self.assertEqual(expected[i][1], p.value)
87      self.assertEqual(expected[i][2], p.is_optional())
88      self.assertFalse(p.is_comment())
89
90    self.assertEqual(set(["foo", "bar", "qux"]), self.props.get_all_names())
91
92    self.assertEqual("true", self.props.get_value("foo"))
93    self.assertEqual("false", self.props.get_value("bar"))
94    self.assertEqual("1", self.props.get_value("qux"))
95
96    # there are two assignments for 'foo'
97    self.assertEqual(2, len(self.props.get_props("foo")))
98
99  def test_putNewProp(self):
100    self.props.put("new", "30")
101
102    self.assertEqual(5, len(self.props.get_all_props()))
103    last_prop = self.props.get_all_props()[-1]
104    self.assertEqual("new", last_prop.name)
105    self.assertEqual("30", last_prop.value)
106    self.assertFalse(last_prop.is_optional())
107
108  def test_putExistingNonOptionalProp(self):
109    self.props.put("foo", "NewValue")
110
111    self.assertEqual(4, len(self.props.get_all_props()))
112    foo_prop = self.props.get_props("foo")[0]
113    self.assertEqual("foo", foo_prop.name)
114    self.assertEqual("NewValue", foo_prop.value)
115    self.assertFalse(foo_prop.is_optional())
116    self.assertEqual("# Value overridden by post_process_props.py. " +
117                     "Original value: true\nfoo=NewValue", str(foo_prop))
118
119  def test_putExistingOptionalProp(self):
120    self.props.put("qux", "2")
121
122    self.assertEqual(5, len(self.props.get_all_props()))
123    last_prop = self.props.get_all_props()[-1]
124    self.assertEqual("qux", last_prop.name)
125    self.assertEqual("2", last_prop.value)
126    self.assertFalse(last_prop.is_optional())
127    self.assertEqual("# Auto-added by post_process_props.py\nqux=2",
128                     str(last_prop))
129
130  def test_deleteNonOptionalProp(self):
131    props_to_delete = self.props.get_props("foo")[0]
132    props_to_delete.delete(reason="testing")
133
134    self.assertEqual(3, len(self.props.get_all_props()))
135    self.assertEqual("# Removed by post_process_props.py because testing\n" +
136                     "#foo=true", str(props_to_delete))
137
138  def test_deleteOptionalProp(self):
139    props_to_delete = self.props.get_props("qux")[0]
140    props_to_delete.delete(reason="testing")
141
142    self.assertEqual(3, len(self.props.get_all_props()))
143    self.assertEqual("# Removed by post_process_props.py because testing\n" +
144                     "#qux?=1", str(props_to_delete))
145
146  def test_overridingNonOptional(self):
147    props_to_be_overridden = self.props.get_props("foo")[1]
148    self.assertTrue("true", props_to_be_overridden.value)
149
150    self.assertTrue(override_optional_props(self.props))
151
152    # size reduced to 3 because foo?=false was overridden by foo=true
153    self.assertEqual(3, len(self.props.get_all_props()))
154
155    self.assertEqual(1, len(self.props.get_props("foo")))
156    self.assertEqual("true", self.props.get_props("foo")[0].value)
157
158    self.assertEqual("# Removed by post_process_props.py because " +
159                     "overridden by foo=true\n#foo?=false",
160                     str(props_to_be_overridden))
161
162  def test_overridingOptional(self):
163    content = """
164    # comment
165    qux?=2
166    foo=true
167    bar=false
168    qux?=1
169    # another comment
170    foo?=false
171    """
172    with patch('post_process_props.open', mock_open(read_data=content)) as m:
173      props = PropList("hello")
174
175      props_to_be_overridden = props.get_props("qux")[0]
176      self.assertEqual("2", props_to_be_overridden.value)
177
178      self.assertTrue(override_optional_props(props))
179
180      self.assertEqual(1, len(props.get_props("qux")))
181      self.assertEqual("1", props.get_props("qux")[0].value)
182      # the only left optional assignment becomes non-optional
183      self.assertFalse(props.get_props("qux")[0].is_optional())
184
185      self.assertEqual("# Removed by post_process_props.py because " +
186                       "overridden by qux?=1\n#qux?=2",
187                       str(props_to_be_overridden))
188
189  def test_overridingDuplicated(self):
190    content = """
191    # comment
192    foo=true
193    bar=false
194    qux?=1
195    foo=false
196    # another comment
197    foo?=false
198    """
199    with patch("post_process_props.open", mock_open(read_data=content)) as m:
200      stderr_redirect = io.StringIO()
201      with contextlib.redirect_stderr(stderr_redirect):
202        props = PropList("hello")
203
204        # fails due to duplicated foo=true and foo=false
205        self.assertFalse(override_optional_props(props))
206
207        self.assertEqual("error: found duplicate sysprop assignments:\n" +
208                         "foo=true\nfoo=false\n", stderr_redirect.getvalue())
209
210  def test_overridingDuplicatedWithSameValue(self):
211    content = """
212    # comment
213    foo=true
214    bar=false
215    qux?=1
216    foo=true
217    # another comment
218    foo?=false
219    """
220    with patch("post_process_props.open", mock_open(read_data=content)) as m:
221      stderr_redirect = io.StringIO()
222      with contextlib.redirect_stderr(stderr_redirect):
223        props = PropList("hello")
224        optional_prop = props.get_props("foo")[2] # the last foo?=false one
225
226        # we have duplicated foo=true and foo=true, but that's allowed
227        # since they have the same value
228        self.assertTrue(override_optional_props(props))
229
230        # foo?=false should be commented out
231        self.assertEqual("# Removed by post_process_props.py because " +
232                         "overridden by foo=true\n#foo?=false",
233                         str(optional_prop))
234
235  def test_allowDuplicates(self):
236    content = """
237    # comment
238    foo=true
239    bar=false
240    qux?=1
241    foo=false
242    # another comment
243    foo?=false
244    """
245    with patch("post_process_props.open", mock_open(read_data=content)) as m:
246      stderr_redirect = io.StringIO()
247      with contextlib.redirect_stderr(stderr_redirect):
248        props = PropList("hello")
249
250        # we have duplicated foo=true and foo=false, but that's allowed
251        # because it's explicitly allowed
252        self.assertTrue(override_optional_props(props, allow_dup=True))
253
254  def test_validateGrfProps(self):
255    stderr_redirect = io.StringIO()
256    with contextlib.redirect_stderr(stderr_redirect):
257      props = PropList("hello")
258      props.put("ro.board.first_api_level","25")
259
260      # ro.board.first_api_level must be less than or equal to the sdk version
261      self.assertFalse(validate_grf_props(props, 20))
262      self.assertTrue(validate_grf_props(props, 26))
263      self.assertTrue(validate_grf_props(props, 35))
264
265      # manually set ro.board.api_level to an invalid value
266      props.put("ro.board.api_level","20")
267      self.assertFalse(validate_grf_props(props, 26))
268
269      props.get_all_props()[-1].make_as_comment()
270      # manually set ro.board.api_level to a valid value
271      props.put("ro.board.api_level","26")
272      self.assertTrue(validate_grf_props(props, 26))
273      # ro.board.api_level must be less than or equal to the sdk version
274      self.assertFalse(validate_grf_props(props, 25))
275
276if __name__ == '__main__':
277    unittest.main(verbosity=2)
278