# Copyright (C) 2020 The Android Open Source Project
#
# 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,
# 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.
"""Test manifest diff."""
import unittest
import xml.etree.ElementTree as ET

from treble.split import manifest_diff


class ManifestDiffTest(unittest.TestCase):

  def _assertEqualCanonical(self, change1, change2):
    def _canonicalize(change):
      return {
          identifier : ' '.join(sorted(value.split(' ')))
          for identifier, value in change.items()
      }
    return self.assertEqual(_canonicalize(change1), _canonicalize(change2))

  def test_project_changes(self):
    p1 = ET.fromstring("""<project attr1="hello">
      <linkfile src="newfile2" dest="notneeded" />
      <linkfile src="oldfile1" dest="dest1" />
      <copyfile src="oldfile2" dest="dest2" />
    </project>
    """)
    p2 = ET.fromstring("""<project>
      <linkfile src="newfile1" dest="dest1" />
      <copyfile src="newfile2" dest="dest2" />
      <copyfile src="somefile" dest="addedfile" />
    </project>
    """)
    changes = manifest_diff.project_changes(p1, p2, set())
    self.assertEqual(changes.linkfiles.added, {})
    self._assertEqualCanonical(
        changes.linkfiles.removed,
        {'notneeded': '<linkfile src="newfile2" dest="notneeded" />'})
    self.assertEqual(
        changes.linkfiles.modified, {
            'dest1':
                manifest_diff.ChangeMap(modified={
                    'src': manifest_diff.Change('oldfile1', 'newfile1')
                })
        })
    self._assertEqualCanonical(
        changes.copyfiles.added,
        {'addedfile': '<copyfile src="somefile" dest="addedfile" />'})
    self.assertEqual(changes.copyfiles.removed, {})
    self.assertEqual(
        changes.copyfiles.modified, {
            'dest2':
                manifest_diff.ChangeMap(modified={
                    'src': manifest_diff.Change('oldfile2', 'newfile2')
                })
        })
    self.assertEqual(
        changes.attributes,
        manifest_diff.ChangeMap(
            added={}, removed={'attr1': 'hello'}, modified={}))

  def test_project_changes_same(self):
    p1 = ET.fromstring("""<project attr1="hello">
      <linkfile src="newfile2" dest="notneeded" />
      <linkfile src="oldfile1" dest="dest1" />
      <copyfile src="oldfile2" dest="dest2" />
    </project>
    """)
    changes = manifest_diff.project_changes(p1, p1, set())
    self.assertFalse(changes)

  def test_compare_single_node_elements(self):
    m1 = ET.fromstring("""<manifest>
      <default revision='dev' remote='aosp' />
      <repo-hooks />
    </manifest>
    """)
    m2 = ET.fromstring("""<manifest>
      <default revision='release' />
    </manifest>
    """)
    changes = manifest_diff.compare_single_node_elements(m1, m2, set())
    self.assertEqual(changes.added, {})
    self._assertEqualCanonical(changes.removed, {'repo-hooks': '<repo-hooks />'})
    self.assertEqual(
        changes.modified, {
            'default':
                manifest_diff.ChangeMap(
                    added={},
                    removed={'remote': 'aosp'},
                    modified={
                        'revision': manifest_diff.Change('dev', 'release')
                    })
        })

  def test_compare_remote_elements(self):
    m1 = ET.fromstring("""<manifest>
      <remote revision="dev" name="aosp" fetch="https://aosp-source.com" />
      <remote name="android" fetch="https://android-source.com" attr="test" />
      <repo-hooks />
    </manifest>
    """)
    m2 = ET.fromstring("""<manifest>
      <remote revision="dev" name="android" fetch="https://android-source.com"
        attr="test2"/>
    </manifest>
    """)
    changes = manifest_diff.compare_remote_elements(m1, m2, set())
    self.assertEqual(changes.added, {})
    self._assertEqualCanonical(
        changes.removed, {
            'aosp':
                '<remote revision="dev" name="aosp" fetch="https://aosp-source.com" />'
        })
    self.assertEqual(
        changes.modified, {
            'android':
                manifest_diff.ChangeMap(
                    added={'revision': 'dev'},
                    removed={},
                    modified={'attr': manifest_diff.Change('test', 'test2')})
        })

  def test_compare_project_elements(self):
    m1 = ET.fromstring("""<manifest>
      <project name="platform/project1" path="system/project1" />
      <project name="platform/project2" path="system/project2" />
      <project name="platform/project3" path="system/project3" />
    </manifest>""")
    m2 = ET.fromstring("""<manifest>
      <project name="platform/project1" path="system/project1" />
      <project name="system/project2" />
      <project name="platform/project4" path="system/project4" />
    </manifest>""")
    changes = manifest_diff.compare_project_elements(m1, m2, set())
    self._assertEqualCanonical(
        changes.added, {
            'system/project4':
                '<project name="platform/project4" path="system/project4" />'
        })
    self._assertEqualCanonical(
        changes.removed, {
            'system/project3':
                '<project name="platform/project3" path="system/project3" />'
        })
    self.assertEqual(
        changes.modified, {
            'system/project2':
                manifest_diff.ProjectChanges(
                    attributes=manifest_diff.ChangeMap(
                        added={},
                        removed={},
                        modified={
                            'name':
                                manifest_diff.Change('platform/project2',
                                                     'system/project2')
                        }))
        })
