#!/usr/bin/env vpython
# Copyright 2015 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.

import copy
import doctest
import logging
import os
import subprocess
import sys
import unittest

import repo_test_util

import mock

from recipe_engine import fetch
from recipe_engine import package
from recipe_engine import package_io

TEST_AUTHOR = 'foo@example.com'
IRC = package_io.InfraRepoConfig.RELPATH


class MockIOThings(object):
  def setUp(self):
    super(MockIOThings, self).setUp()

    self.mock_os_patcher = mock.patch('recipe_engine.package.os')
    self.mock_os = self.mock_os_patcher.start()
    self.mock_os.path.join = os.path.join
    self.mock_os.path.dirname = os.path.dirname
    self.mock_os.sep = os.sep

    self.orig_subprocess = subprocess
    self.mock_subprocess_patcher = mock.patch(
        'recipe_engine.package.subprocess')
    self.mock_subprocess = self.mock_subprocess_patcher.start()
    self.mock_subprocess.PIPE = self.orig_subprocess.PIPE

  def tearDown(self):
    self.mock_subprocess_patcher.stop()
    self.mock_os_patcher.stop()

    super(MockIOThings, self).tearDown()


class TestGitRepoSpec(repo_test_util.RepoTest):
  def test_updates(self):
    repos = self.repo_setup({'a': []})

    spec = self.get_git_repo_spec(repos['a'])
    self.assertEqual([], spec.updates())

    revs = lambda cmetas: [m.revision for m in cmetas]

    c1 = self.commit_in_repo(repos['a'], message='c1')
    self.assertEqual([
        c1['revision']],
        revs(spec.updates()))

    c2 = self.commit_in_repo(repos['a'], message='c2')
    self.assertEqual([
        c1['revision'], c2['revision']],
        revs(spec.updates()))

    c3 = self.commit_in_repo(repos['a'], message='c3')
    self.assertEqual([
        c1['revision'], c2['revision'], c3['revision']],
        revs(spec.updates()))


class MockPackageFile(package_io.PackageFile):
  def __init__(self, path, text):
    self._text = text
    super(MockPackageFile, self).__init__(path)

  @property
  def path(self):
    return self._path

  def read_raw(self):
    return self._text

  def write(self, buf):
    pass


class TestPackageSpec(MockIOThings, unittest.TestCase):
  def setUp(self):
    super(TestPackageSpec, self).setUp()

    self.proto_text = '\n'.join([
      '{',
      '  "api_version": 2,',
      '  "deps": {',
      '    "bar": {',
      '      "branch": "superbar",',
      '      "revision": "deadd00d",',
      '      "url": "https://repo.com/bar.git"',
      '    },',
      '    "foo": {',
      '      "branch": "master",',
      '      "revision": "cafebeef",',
      '      "url": "https://repo.com/foo.git"',
      '    }',
      '  },',
      '  "project_id": "super_main_package",',
      '  "recipes_path": "path/to/recipes"',
      '}',
    ]) + '\n'
    self.package_file = MockPackageFile('repo/root/'+IRC, self.proto_text)
    self.context = package.PackageContext.from_package_pb(
        'repo/root', self.package_file.read())

  def test_dump_load_inverses(self):
    # Doubles as a test for equality reflexivity.
    package_spec = package.PackageSpec.from_package_pb(
      self.context, self.package_file.read())
    self.assertEqual(self.package_file.to_raw(package_spec.spec_pb),
                     self.proto_text)
    self.assertEqual(package.PackageSpec.from_package_pb(
      self.context, self.package_file.read()), package_spec)

  def test_dump_round_trips(self):
    proto_text = """
{"api_version": 2}
""".lstrip()
    package_file = MockPackageFile('repo/root/'+IRC, proto_text)
    package_spec = package.PackageSpec.from_package_pb(
      self.context, package_file.read())
    self.assertEqual(package_file.to_raw(package_spec.spec_pb),
                     '{\n  "api_version": 2\n}\n')

  def test_no_version(self):
    proto_text = """{
  "project_id": "foo",
  "recipes_path": "path/to/recipes"
}
"""
    package_file = MockPackageFile('repo/root/'+IRC, proto_text)

    with self.assertRaises(AssertionError):
      package.PackageSpec.from_package_pb(self.context, package_file.read())

  def test_unsupported_version(self):
    proto_text = """{
  "api_version": 99999999,
  "project_id": "fizzbar",
  "recipes_path": "path/to/recipes"
}"""
    package_file = MockPackageFile('repo/root/'+IRC, proto_text)

    with self.assertRaises(AssertionError):
      package.PackageSpec.from_package_pb(self.context, package_file.read())


class TestPackageDeps(MockIOThings, unittest.TestCase):

  def test_create_with_overrides(self):
    base_proto_text = """{
  "api_version": 2,
  "project_id": "base_package",
  "recipes_path": "path/to/recipes",
  "deps": {
    "foo": {
      "url": "https://repo.com/foo.git",
      "branch": "foobranch",
      "revision": "deadd00d"
    }
  }
}
"""
    base_package_file = MockPackageFile('base/'+IRC, base_proto_text)

    foo_proto_text = """{
  "api_version": 2,
  "project_id": "foo",
  "recipes_path": "path/to/recipes"
}"""
    foo_package_file = MockPackageFile('foo/'+IRC, foo_proto_text)

    with mock.patch.object(os.path, 'abspath', lambda x: x):
      context = package.PackageContext.from_package_pb(
        'base', foo_package_file.read())

    with mock.patch.object(package.GitRepoSpec, 'checkout') as checkout:
      with mock.patch.object(package.PathRepoSpec, 'spec_pb',
                             return_value=foo_package_file.read()):
        deps = package.PackageDeps.create(
          context, base_package_file, overrides={
            'foo': '/path/to/local/foo',
          })

      foo_deps = deps.get_package('foo')
      self.assertIsInstance(foo_deps.repo_spec, package.PathRepoSpec)
      self.assertEqual(foo_deps.repo_spec.path, '/path/to/local/foo')
      self.assertFalse(checkout.called)


def load_tests(_loader, tests, _ignore):
  tests.addTests(doctest.DocTestSuite(package))
  return tests


if __name__ == '__main__':
  if '-v' in sys.argv:
    logging.basicConfig(
      level=logging.DEBUG,
      handler=repo_test_util.CapturableHandler())
  result = unittest.main()
