• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3# Copyright (C) 2019 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#
17"""Unit tests for process_compat_config.py."""
18
19import difflib
20import io
21import unittest
22import xml.dom.minidom
23from inspect import currentframe, getframeinfo
24
25import process_compat_config
26
27def here():
28    f = currentframe().f_back
29    return "%s:%d" % (getframeinfo(f).filename, f.f_lineno)
30
31class ProcessCompatConfigTest(unittest.TestCase):
32
33    def setUp(self):
34        self.merger = process_compat_config.ConfigMerger(detect_conflicts = True)
35        self.stderr = io.StringIO()
36        self.merger.write_errors_to = self.stderr
37        self.xml = io.BytesIO()
38
39    def remove_white_space_text_nodes(self, node):
40        remove = []
41        # Find any child nodes that are just white space, and add them to a list
42        # to remove. Do not remove the child while iterating as that prevents
43        # the following node from being seen.
44        for child in node.childNodes:
45            if child.nodeType == node.ELEMENT_NODE:
46                self.remove_white_space_text_nodes(child)
47            elif child.nodeType == node.TEXT_NODE:
48                if str.isspace(child.data):
49                    remove.append(child)
50        # Remove any child nodes that were just white space.
51        for child in remove:
52            node.removeChild(child)
53            child.unlink()
54
55    def parse_xml(self, text):
56        node = xml.dom.minidom.parseString(text)
57        # Remove any white space text nodes as they are irrelevant.
58        self.remove_white_space_text_nodes(node)
59        return node.toprettyxml()
60
61    def assert_same_xml(self, got, expected):
62        got = self.parse_xml(got)
63        expected = self.parse_xml(expected)
64        diffs = [diff for diff in difflib.ndiff(got.split('\n'), expected.split('\n')) if not diff.startswith(" ")]
65        self.assertEqual("", "\n".join(diffs), msg="Got unexpected diffs in XML")
66
67    def test_no_config_to_merge(self):
68        self.merger.write(self.xml)
69        self.assert_same_xml(self.xml.getvalue(), "<config />")
70
71    def test_merge_one_file(self):
72        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE" /></config>'), here())
73        self.merger.write(self.xml)
74        self.assert_same_xml(self.xml.getvalue(), '<config><compat-change id="1234" name="TEST_CHANGE" /></config>')
75
76    def test_merge_two_files(self):
77        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE" /></config>'), here())
78        self.merger.merge(io.BytesIO(b'<config><compat-change id="1235" name="TEST_CHANGE2" /></config>'), here())
79        self.merger.write(self.xml)
80        self.assert_same_xml(self.xml.getvalue(),
81            '<config><compat-change id="1234" name="TEST_CHANGE" /><compat-change id="1235" name="TEST_CHANGE2" /></config>')
82
83    def test_merge_two_files_metadata(self):
84        self.merger.merge(io.BytesIO(
85            b'<config><compat-change id="1234" name="TEST_CHANGE"><meta-data definedIn="some.Class" sourcePosition="some.java:1" />'
86            b'</compat-change></config>'), here())
87        self.merger.merge(io.BytesIO(
88            b'<config><compat-change id="1235" name="TEST_CHANGE2"><meta-data definedIn="other.Class" sourcePosition="other.java:2" />'
89            b'</compat-change></config>'), here())
90        self.merger.write(self.xml)
91        self.assert_same_xml(self.xml.getvalue(), b'<config>'
92            b'<compat-change id="1234" name="TEST_CHANGE"><meta-data definedIn="some.Class" sourcePosition="some.java:1" /></compat-change>'
93            b'<compat-change id="1235" name="TEST_CHANGE2"><meta-data definedIn="other.Class" sourcePosition="other.java:2" /></compat-change>'
94            b'</config>')
95
96    def test_write_device_config_metadata_stripped(self):
97        self.merger.merge(io.BytesIO(
98            b'<config><compat-change id="1234" name="TEST_CHANGE"><meta-data definedIn="some.Class" sourcePosition="file.java:1"/>'
99            b'</compat-change></config>'), here())
100        self.merger.write_device_config(self.xml)
101        self.assert_same_xml(self.xml.getvalue(), b'<config>'
102            b'<compat-change id="1234" name="TEST_CHANGE" />'
103            b'</config>')
104
105    def test_merge_two_files_duplicate_id(self):
106        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE" /></config>'), here())
107        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE2" /></config>'), here())
108        self.assertIn(r'ERROR: Duplicate definitions for compat change with ID 1234', self.stderr.getvalue())
109        with self.assertRaisesRegex(Exception, ' 1 .*error'):
110            self.merger.write(self.xml)
111
112    def test_merge_two_files_duplicate_name(self):
113        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE" /></config>'), here())
114        self.merger.merge(io.BytesIO(b'<config><compat-change id="1235" name="TEST_CHANGE" /></config>'), here())
115        self.assertIn(r'ERROR: Duplicate definitions for compat change with name TEST_CHANGE', self.stderr.getvalue())
116        with self.assertRaisesRegex(Exception, ' 1 .*error'):
117            self.merger.write(self.xml)
118
119    def test_merge_two_files_duplicate_id_allow_duplicates(self):
120        self.merger = process_compat_config.ConfigMerger(detect_conflicts = False)
121        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE" /></config>'), here())
122        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE2" /></config>'), here())
123        self.merger.write(self.xml)
124
125    def test_merge_two_files_duplicate_name_allow_duplicates(self):
126        self.merger = process_compat_config.ConfigMerger(detect_conflicts = False)
127        self.merger.merge(io.BytesIO(b'<config><compat-change id="1234" name="TEST_CHANGE" /></config>'), here())
128        self.merger.merge(io.BytesIO(b'<config><compat-change id="1235" name="TEST_CHANGE" /></config>'), here())
129        self.merger.write(self.xml)
130
131if __name__ == '__main__':
132  unittest.main(verbosity=2)
133