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