#!/usr/bin/env vpython3
# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for gold_utils.

Example usage:
  vpython3 gold_utils_test.py
"""

# pylint: disable=protected-access

import contextlib
import os
from pathlib import Path
import sys
import tempfile
import unittest
from unittest import mock

path_root = Path(__file__).parents[2]
sys.path.append(str(path_root))

from pylib.constants import host_paths
from pylib.utils import gold_utils

with host_paths.SysPath(host_paths.BUILD_PATH):
    from skia_gold_common import unittest_utils

from pyfakefs import fake_filesystem_unittest  # pylint: disable=import-error

createSkiaGoldArgs = unittest_utils.createSkiaGoldArgs


def assertArgWith(test, arg_list, arg, value):
    i = arg_list.index(arg)
    test.assertEqual(arg_list[i + 1], value)


class AndroidSkiaGoldSessionDiffTest(fake_filesystem_unittest.TestCase):
    def setUp(self):
        self.setUpPyfakefs()
        self._working_dir = tempfile.mkdtemp()
        self._json_keys = tempfile.NamedTemporaryFile(delete=False).name

    @mock.patch.object(
        gold_utils.AndroidSkiaGoldSession, '_RunCmdForRcAndOutput'
    )
    def test_commandCommonArgs(self, cmd_mock):
        cmd_mock.return_value = (None, None)
        args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=False)
        sgp = gold_utils.AndroidSkiaGoldProperties(args)
        session = gold_utils.AndroidSkiaGoldSession(
            self._working_dir,
            sgp,
            self._json_keys,
            'corpus',
            instance='instance',
        )
        session.Diff('name', 'png_file', None)
        call_args = cmd_mock.call_args[0][0]
        self.assertIn('diff', call_args)
        assertArgWith(self, call_args, '--corpus', 'corpus')
        # TODO(skbug.com/10610): Remove the -public once we go back to using the
        # non-public instance, or add a second test for testing that the correct
        # instance is chosen if we decide to support both depending on what the
        # user is authenticated for.
        assertArgWith(self, call_args, '--instance', 'instance-public')
        assertArgWith(self, call_args, '--input', 'png_file')
        assertArgWith(self, call_args, '--test', 'name')
        # TODO(skbug.com/10611): Re-add this assert and remove the check for the
        # absence of the directory once we switch back to using the proper working
        # directory.
        # assertArgWith(self, call_args, '--work-dir', self._working_dir)
        self.assertNotIn(self._working_dir, call_args)
        i = call_args.index('--out-dir')
        # The output directory should be a subdirectory of the working directory.
        self.assertIn(self._working_dir, call_args[i + 1])


class AndroidSkiaGoldSessionDiffLinksTest(fake_filesystem_unittest.TestCase):
    class FakeArchivedFile:
        def __init__(self, path):
            self.name = path

        def Link(self):
            return 'file://' + self.name

    class FakeOutputManager:
        def __init__(self):
            self.output_dir = tempfile.mkdtemp()

        @contextlib.contextmanager
        def ArchivedTempfile(self, image_name, _, __):
            filepath = os.path.join(self.output_dir, image_name)
            yield AndroidSkiaGoldSessionDiffLinksTest.FakeArchivedFile(filepath)

    def setUp(self):
        self.setUpPyfakefs()
        self._working_dir = tempfile.mkdtemp()
        self._json_keys = tempfile.NamedTemporaryFile(delete=False).name
        self._timestamp_patcher = mock.patch.object(gold_utils, '_GetTimestamp')
        self._timestamp_mock = self._timestamp_patcher.start()
        self.addCleanup(self._timestamp_patcher.stop)

    def test_outputManagerUsed(self):
        self._timestamp_mock.return_value = 'ts0'
        args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True)
        sgp = gold_utils.AndroidSkiaGoldProperties(args)
        session = gold_utils.AndroidSkiaGoldSession(
            self._working_dir, sgp, self._json_keys, None, None
        )
        with open(
            os.path.join(self._working_dir, 'input-inputhash.png'), 'w'
        ) as f:
            f.write('input')
        with open(
            os.path.join(self._working_dir, 'closest-closesthash.png'), 'w'
        ) as f:
            f.write('closest')
        with open(os.path.join(self._working_dir, 'diff.png'), 'w') as f:
            f.write('diff')

        output_manager = AndroidSkiaGoldSessionDiffLinksTest.FakeOutputManager()
        session._StoreDiffLinks('foo', output_manager, self._working_dir)

        copied_input = os.path.join(
            output_manager.output_dir, 'given_foo_ts0.png'
        )
        copied_closest = os.path.join(
            output_manager.output_dir, 'closest_foo_ts0.png'
        )
        copied_diff = os.path.join(
            output_manager.output_dir, 'diff_foo_ts0.png'
        )
        with open(copied_input) as f:
            self.assertEqual(f.read(), 'input')
        with open(copied_closest) as f:
            self.assertEqual(f.read(), 'closest')
        with open(copied_diff) as f:
            self.assertEqual(f.read(), 'diff')

        self.assertEqual(
            session.GetGivenImageLink('foo'), 'file://' + copied_input
        )
        self.assertEqual(
            session.GetClosestImageLink('foo'), 'file://' + copied_closest
        )
        self.assertEqual(
            session.GetDiffImageLink('foo'), 'file://' + copied_diff
        )

    def test_diffLinksDoNotClobber(self):
        """Tests that multiple calls to store links does not clobber files."""

        def side_effect():
            side_effect.count += 1
            return f'ts{side_effect.count}'

        side_effect.count = -1
        self._timestamp_mock.side_effect = side_effect

        args = createSkiaGoldArgs(git_revision='a', local_pixel_tests=True)
        sgp = gold_utils.AndroidSkiaGoldProperties(args)
        session = gold_utils.AndroidSkiaGoldSession(
            self._working_dir, sgp, self._json_keys, None, None
        )
        with open(
            os.path.join(self._working_dir, 'input-inputhash.png'), 'w'
        ) as f:
            f.write('first input')
        with open(
            os.path.join(self._working_dir, 'closest-closesthash.png'), 'w'
        ) as f:
            f.write('first closest')
        with open(os.path.join(self._working_dir, 'diff.png'), 'w') as f:
            f.write('first diff')

        output_manager = AndroidSkiaGoldSessionDiffLinksTest.FakeOutputManager()
        session._StoreDiffLinks('foo', output_manager, self._working_dir)

        # Store links normally once.
        first_copied_input = os.path.join(
            output_manager.output_dir, 'given_foo_ts0.png'
        )
        first_copied_closest = os.path.join(
            output_manager.output_dir, 'closest_foo_ts0.png'
        )
        first_copied_diff = os.path.join(
            output_manager.output_dir, 'diff_foo_ts0.png'
        )
        with open(first_copied_input) as f:
            self.assertEqual(f.read(), 'first input')
        with open(first_copied_closest) as f:
            self.assertEqual(f.read(), 'first closest')
        with open(first_copied_diff) as f:
            self.assertEqual(f.read(), 'first diff')

        with open(
            os.path.join(self._working_dir, 'input-inputhash.png'), 'w'
        ) as f:
            f.write('second input')
        with open(
            os.path.join(self._working_dir, 'closest-closesthash.png'), 'w'
        ) as f:
            f.write('second closest')
        with open(os.path.join(self._working_dir, 'diff.png'), 'w') as f:
            f.write('second diff')

        self.assertEqual(
            session.GetGivenImageLink('foo'), 'file://' + first_copied_input
        )
        self.assertEqual(
            session.GetClosestImageLink('foo'), 'file://' + first_copied_closest
        )
        self.assertEqual(
            session.GetDiffImageLink('foo'), 'file://' + first_copied_diff
        )

        # Store links again and check that the new data is surfaced.
        session._StoreDiffLinks('foo', output_manager, self._working_dir)

        second_copied_input = os.path.join(
            output_manager.output_dir, 'given_foo_ts1.png'
        )
        second_copied_closest = os.path.join(
            output_manager.output_dir, 'closest_foo_ts1.png'
        )
        second_copied_diff = os.path.join(
            output_manager.output_dir, 'diff_foo_ts1.png'
        )
        with open(second_copied_input) as f:
            self.assertEqual(f.read(), 'second input')
        with open(second_copied_closest) as f:
            self.assertEqual(f.read(), 'second closest')
        with open(second_copied_diff) as f:
            self.assertEqual(f.read(), 'second diff')

        self.assertEqual(
            session.GetGivenImageLink('foo'), 'file://' + second_copied_input
        )
        self.assertEqual(
            session.GetClosestImageLink('foo'),
            'file://' + second_copied_closest,
        )
        self.assertEqual(
            session.GetDiffImageLink('foo'), 'file://' + second_copied_diff
        )

        # Check to make sure the first images still exist on disk and are unchanged.
        with open(first_copied_input) as f:
            self.assertEqual(f.read(), 'first input')
        with open(first_copied_closest) as f:
            self.assertEqual(f.read(), 'first closest')
        with open(first_copied_diff) as f:
            self.assertEqual(f.read(), 'first diff')


if __name__ == '__main__':
    unittest.main(verbosity=2)
