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

import copy
import unittest

import results_merger

GOOD_JSON_TEST_RESULT_0 = {
  'tests': {
    'car': {
      'honda': {'expected': 'PASS', 'actual': 'PASS'},
      'toyota': {'expected': 'FAIL', 'actual': 'FAIL'},
    },
    'computer': {'dell': {'expected': 'PASS', 'actual': 'PASS'}},
  },
  'interrupted': False,
  'version': 3,
  'seconds_since_epoch': 1406662289.76,
  'num_failures_by_type': {'FAIL': 0, 'PASS': 2},
  'layout_tests_dir': 'abc',
}

GOOD_JSON_TEST_RESULT_1 = {
  'tests': {
    'car': {
      'tesla': {'expected': 'PASS', 'actual': 'PASS'},
    },
    'burger': {'mcdonald': {'expected': 'PASS', 'actual': 'PASS'}},
  },
  'interrupted': False,
  'version': 3,
  'seconds_since_epoch': 1406662283.11,
  'num_failures_by_type': {'FAIL': 0, 'PASS': 2},
  'layout_tests_dir': '123',
}

GOOD_JSON_TEST_RESULT_2 = {
  'tests': {
    'car': {
      'mercedes': {'expected': 'PASS', 'actual': 'FAIL'},
    },
    'burger': {'in n out': {'expected': 'PASS', 'actual': 'PASS'}},
  },
  'interrupted': True,
  'version': 3,
  'seconds_since_epoch': 1406662200.01,
  'num_failures_by_type': {'FAIL': 1, 'PASS': 1},
}

GOOD_JSON_TEST_RESULT_MERGED = {
  'tests': {
    'car': {
      'tesla': {'expected': 'PASS', 'actual': 'PASS'},
      'mercedes': {'expected': 'PASS', 'actual': 'FAIL'},
      'honda': {'expected': 'PASS', 'actual': 'PASS'},
      'toyota': {'expected': 'FAIL', 'actual': 'FAIL'},
    },
    'computer': {'dell': {'expected': 'PASS', 'actual': 'PASS'}},
    'burger': {
      'mcdonald': {'expected': 'PASS', 'actual': 'PASS'},
      'in n out': {'expected': 'PASS', 'actual': 'PASS'},
    },
  },
  'interrupted': True,
  'version': 3,
  'seconds_since_epoch': 1406662200.01,
  'num_failures_by_type': {'FAIL': 1, 'PASS': 5},
  'layout_tests_dir': '123',
}


def extend(initial, add):
  out = copy.deepcopy(initial)
  out.update(add)
  return out


def remove(initial, keys):
  out = copy.deepcopy(initial)
  for k in keys:
    del out[k]
  return out


# These unittests are run in PRESUBMIT, but not by recipe_simulation_test, hence
# to avoid false alert on missing coverage by recipe_simulation_test, we mark
# these code as no cover.
class MergingTest(unittest.TestCase):  # pragma: no cover
  maxDiff = None  # Show full diff if assertion fail

  def test_merge_tries(self):
    self.assertEqual(
      {'a': 'A', 'b': {'c': 'C'}},
      results_merger.merge_tries({'a': 'A', 'b': {}}, {'b': {'c': 'C'}}),
    )

  def test_merge_tries_unmergable(self):
    with self.assertRaisesRegex(results_merger.MergeException, 'a:b'):
      results_merger.merge_tries({'a': {'b': 'A'}}, {'a': {'b': 'C'}})

  def test_merge_metadata(self):
    metadata1 = {'metadata': {'tags': ['foo', 'bar']}}
    metadata2 = {'metadata': {'tags': ['foo', 'bat']}}
    merged_results = results_merger.merge_test_results(
      [
        extend(GOOD_JSON_TEST_RESULT_0, metadata1),
        extend(GOOD_JSON_TEST_RESULT_1, metadata2),
      ]
    )
    self.assertEqual(merged_results['metadata']['tags'], ['foo', 'bat'])

  def test_merge_json_test_results_nop(self):
    good_json_results = (
      GOOD_JSON_TEST_RESULT_0,
      GOOD_JSON_TEST_RESULT_1,
      GOOD_JSON_TEST_RESULT_2,
      GOOD_JSON_TEST_RESULT_MERGED,
    )
    for j in good_json_results:
      # Clone so we can check the input dictionaries are not modified
      a = copy.deepcopy(j)
      self.assertEqual(results_merger.merge_test_results([a]), j)
      self.assertEqual(a, j)

  def test_merge_json_test_results_invalid_version(self):
    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          extend(GOOD_JSON_TEST_RESULT_0, {'version': 5}),
        ]
      )

    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          GOOD_JSON_TEST_RESULT_0,
          extend(GOOD_JSON_TEST_RESULT_1, {'version': 5}),
        ]
      )

  def test_merge_json_test_results_missing_version(self):
    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          remove(GOOD_JSON_TEST_RESULT_0, ['version']),
        ]
      )

    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          GOOD_JSON_TEST_RESULT_0,
          remove(GOOD_JSON_TEST_RESULT_1, ['version']),
        ]
      )

  def test_merge_json_test_results_invalid_extra(self):
    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          extend(GOOD_JSON_TEST_RESULT_0, {'extra': True}),
        ]
      )

    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          GOOD_JSON_TEST_RESULT_0,
          extend(GOOD_JSON_TEST_RESULT_1, {'extra': True}),
        ]
      )

  def test_merge_json_test_results_missing_required(self):
    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          remove(GOOD_JSON_TEST_RESULT_0, ['interrupted']),
        ]
      )

    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          GOOD_JSON_TEST_RESULT_0,
          remove(GOOD_JSON_TEST_RESULT_1, ['interrupted']),
        ]
      )

  def test_merge_json_test_results_multiple(self):
    self.assertEqual(
      results_merger.merge_test_results(
        [
          GOOD_JSON_TEST_RESULT_0,
          GOOD_JSON_TEST_RESULT_1,
          GOOD_JSON_TEST_RESULT_2,
        ]
      ),
      GOOD_JSON_TEST_RESULT_MERGED,
    )

  def test_merge_json_test_results_optional_matches(self):
    self.assertEqual(
      results_merger.merge_test_results(
        [
          extend(GOOD_JSON_TEST_RESULT_0, {'path_delimiter': '.'}),
          extend(GOOD_JSON_TEST_RESULT_1, {'path_delimiter': '.'}),
          extend(GOOD_JSON_TEST_RESULT_2, {'path_delimiter': '.'}),
        ]
      ),
      extend(GOOD_JSON_TEST_RESULT_MERGED, {'path_delimiter': '.'}),
    )

  def test_merge_json_test_results_optional_differs(self):
    with self.assertRaises(results_merger.MergeException):
      results_merger.merge_test_results(
        [
          extend(GOOD_JSON_TEST_RESULT_0, {'path_delimiter': '.'}),
          extend(GOOD_JSON_TEST_RESULT_1, {'path_delimiter': '.'}),
          extend(GOOD_JSON_TEST_RESULT_2, {'path_delimiter': '/'}),
        ]
      )

  def test_merge_json_test_results_optional_count(self):
    self.assertEqual(
      results_merger.merge_test_results(
        [
          extend(GOOD_JSON_TEST_RESULT_0, {'fixable': 1}),
          extend(GOOD_JSON_TEST_RESULT_1, {'fixable': 2}),
          extend(GOOD_JSON_TEST_RESULT_2, {'fixable': 3}),
        ]
      ),
      extend(GOOD_JSON_TEST_RESULT_MERGED, {'fixable': 6}),
    )

  def test_merge_nothing(self):
    self.assertEqual(results_merger.merge_test_results([]), {})


# TODO(tansell): Make this test fail properly, currently fails with an
# AttributeError.
#  def test_merge_test_name_conflict(self):
#    self.maxDiff = None  # Show full diff if assertion fail
#    with self.assertRaises(results_merger.MergeException):
#      results_merger.merge_test_results(
#        [GOOD_JSON_TEST_RESULT_0, GOOD_JSON_TEST_RESULT_0])

if __name__ == '__main__':
  unittest.main()  # pragma: no cover
