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

"""Tests for enum_preprocess.py.

This test suite containss various tests for the C++ -> Java enum generator.
"""

import collections
import optparse
import os
import sys
import unittest

import java_cpp_enum
from java_cpp_enum import EnumDefinition, GenerateOutput, GetScriptName
from java_cpp_enum import HeaderParser

sys.path.append(os.path.join(os.path.dirname(__file__), "gyp"))
from util import build_utils

class TestPreprocess(unittest.TestCase):
  def testOutput(self):
    definition = EnumDefinition(original_enum_name='ClassName',
                                enum_package='some.package',
                                entries=[('E1', 1), ('E2', '2 << 2')])
    output = GenerateOutput('path/to/file', definition)
    expected = """
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file is autogenerated by
//     %s
// From
//     path/to/file

package some.package;

public class ClassName {
  public static final int E1 = 1;
  public static final int E2 = 2 << 2;
}
"""
    self.assertEqual(expected % GetScriptName(), output)

  def testParseSimpleEnum(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum EnumName {
        VALUE_ZERO,
        VALUE_ONE,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(1, len(definitions))
    definition = definitions[0]
    self.assertEqual('EnumName', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual(collections.OrderedDict([('VALUE_ZERO', 0),
                                              ('VALUE_ONE', 1)]),
                     definition.entries)

  def testParseBitShifts(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum EnumName {
        VALUE_ZERO = 1 << 0,
        VALUE_ONE = 1 << 1,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(1, len(definitions))
    definition = definitions[0]
    self.assertEqual('EnumName', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual(collections.OrderedDict([('VALUE_ZERO', '1 << 0'),
                                              ('VALUE_ONE', '1 << 1')]),
                     definition.entries)

  def testParseClassNameOverride(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      // GENERATED_JAVA_CLASS_NAME_OVERRIDE: OverrideName
      enum EnumName {
        FOO
      };

      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      // GENERATED_JAVA_CLASS_NAME_OVERRIDE: OtherOverride
      enum PrefixTest {
        PREFIX_TEST_A,
        PREFIX_TEST_B,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(2, len(definitions))
    definition = definitions[0]
    self.assertEqual('OverrideName', definition.class_name)

    definition = definitions[1]
    self.assertEqual('OtherOverride', definition.class_name)
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 1)]),
                     definition.entries)

  def testParseTwoEnums(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum EnumOne {
        ENUM_ONE_A = 1,
        // Comment there
        ENUM_ONE_B = A,
      };

      enum EnumIgnore {
        C, D, E
      };

      // GENERATED_JAVA_ENUM_PACKAGE: other.package
      // GENERATED_JAVA_PREFIX_TO_STRIP: P_
      enum EnumTwo {
        P_A,
        P_B
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(2, len(definitions))
    definition = definitions[0]
    self.assertEqual('EnumOne', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual(collections.OrderedDict([('A', '1'),
                                              ('B', 'A')]),
                     definition.entries)

    definition = definitions[1]
    self.assertEqual('EnumTwo', definition.class_name)
    self.assertEqual('other.package', definition.enum_package)
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 1)]),
                     definition.entries)

  def testParseThrowsOnUnknownDirective(self):
    test_data = """
      // GENERATED_JAVA_UNKNOWN: Value
      enum EnumName {
        VALUE_ONE,
      };
    """.split('\n')
    with self.assertRaises(Exception):
      HeaderParser(test_data).ParseDefinitions()

  def testParseReturnsEmptyListWithoutDirectives(self):
    test_data = """
      enum EnumName {
        VALUE_ONE,
      };
    """.split('\n')
    self.assertEqual([], HeaderParser(test_data).ParseDefinitions())

  def testParseEnumClass(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum class Foo {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(1, len(definitions))
    definition = definitions[0]
    self.assertEqual('Foo', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual(collections.OrderedDict([('A', 0)]),
                     definition.entries)

  def testParseEnumStruct(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum struct Foo {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(1, len(definitions))
    definition = definitions[0]
    self.assertEqual('Foo', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual(collections.OrderedDict([('A', 0)]),
                     definition.entries)

  def testParseFixedTypeEnum(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum Foo : int {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(1, len(definitions))
    definition = definitions[0]
    self.assertEqual('Foo', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual('int', definition.fixed_type)
    self.assertEqual(collections.OrderedDict([('A', 0)]),
                     definition.entries)

  def testParseFixedTypeEnumClass(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum class Foo: unsigned short {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual(1, len(definitions))
    definition = definitions[0]
    self.assertEqual('Foo', definition.class_name)
    self.assertEqual('test.namespace', definition.enum_package)
    self.assertEqual('unsigned short', definition.fixed_type)
    self.assertEqual(collections.OrderedDict([('A', 0)]),
                     definition.entries)

  def testParseUnknownFixedTypeRaises(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: test.namespace
      enum class Foo: foo_type {
        FOO_A,
      };
    """.split('\n')
    with self.assertRaises(Exception):
      HeaderParser(test_data).ParseDefinitions()

  def testParseSimpleMultiLineDirective(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: (
      //   test.namespace)
      // GENERATED_JAVA_CLASS_NAME_OVERRIDE: Bar
      enum Foo {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual('test.namespace', definitions[0].enum_package)
    self.assertEqual('Bar', definitions[0].class_name)

  def testParseMultiLineDirective(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: (te
      //   st.name
      //   space)
      enum Foo {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual('test.namespace', definitions[0].enum_package)

  def testParseMultiLineDirectiveWithOtherDirective(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: (
      //   test.namespace)
      // GENERATED_JAVA_CLASS_NAME_OVERRIDE: (
      //   Ba
      //   r
      //   )
      enum Foo {
        FOO_A,
      };
    """.split('\n')
    definitions = HeaderParser(test_data).ParseDefinitions()
    self.assertEqual('test.namespace', definitions[0].enum_package)
    self.assertEqual('Bar', definitions[0].class_name)

  def testParseMalformedMultiLineDirectiveWithOtherDirective(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: (
      //   test.name
      //   space
      // GENERATED_JAVA_CLASS_NAME_OVERRIDE: Bar
      enum Foo {
        FOO_A,
      };
    """.split('\n')
    with self.assertRaises(Exception):
      HeaderParser(test_data).ParseDefinitions()

  def testParseMalformedMultiLineDirective(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: (
      //   test.name
      //   space
      enum Foo {
        FOO_A,
      };
    """.split('\n')
    with self.assertRaises(Exception):
      HeaderParser(test_data).ParseDefinitions()

  def testParseMalformedMultiLineDirectiveShort(self):
    test_data = """
      // GENERATED_JAVA_ENUM_PACKAGE: (
      enum Foo {
        FOO_A,
      };
    """.split('\n')
    with self.assertRaises(Exception):
      HeaderParser(test_data).ParseDefinitions()

  def testEnumValueAssignmentNoneDefined(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('A', None)
    definition.AppendEntry('B', None)
    definition.AppendEntry('C', None)
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 1),
                                              ('C', 2)]),
                     definition.entries)

  def testEnumValueAssignmentAllDefined(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('A', '1')
    definition.AppendEntry('B', '2')
    definition.AppendEntry('C', '3')
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', '1'),
                                              ('B', '2'),
                                              ('C', '3')]),
                     definition.entries)

  def testEnumValueAssignmentReferences(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('A', None)
    definition.AppendEntry('B', 'A')
    definition.AppendEntry('C', None)
    definition.AppendEntry('D', 'C')
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 0),
                                              ('C', 1),
                                              ('D', 1)]),
                     definition.entries)

  def testEnumValueAssignmentSet(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('A', None)
    definition.AppendEntry('B', '2')
    definition.AppendEntry('C', None)
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 2),
                                              ('C', 3)]),
                     definition.entries)

  def testEnumValueAssignmentSetReferences(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('A', None)
    definition.AppendEntry('B', 'A')
    definition.AppendEntry('C', 'B')
    definition.AppendEntry('D', None)
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 0),
                                              ('C', 0),
                                              ('D', 1)]),
                     definition.entries)

  def testEnumValueAssignmentRaises(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('A', None)
    definition.AppendEntry('B', 'foo')
    definition.AppendEntry('C', None)
    with self.assertRaises(Exception):
      definition.Finalize()

  def testExplicitPrefixStripping(self):
    definition = EnumDefinition(original_enum_name='c', enum_package='p')
    definition.AppendEntry('P_A', None)
    definition.AppendEntry('B', None)
    definition.AppendEntry('P_C', None)
    definition.AppendEntry('P_LAST', 'P_C')
    definition.prefix_to_strip = 'P_'
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 1),
                                              ('C', 2),
                                              ('LAST', 2)]),
                     definition.entries)

  def testImplicitPrefixStripping(self):
    definition = EnumDefinition(original_enum_name='ClassName',
                                enum_package='p')
    definition.AppendEntry('CLASS_NAME_A', None)
    definition.AppendEntry('CLASS_NAME_B', None)
    definition.AppendEntry('CLASS_NAME_C', None)
    definition.AppendEntry('CLASS_NAME_LAST', 'CLASS_NAME_C')
    definition.Finalize()
    self.assertEqual(collections.OrderedDict([('A', 0),
                                              ('B', 1),
                                              ('C', 2),
                                              ('LAST', 2)]),
                     definition.entries)

  def testImplicitPrefixStrippingRequiresAllConstantsToBePrefixed(self):
    definition = EnumDefinition(original_enum_name='Name',
                                enum_package='p')
    definition.AppendEntry('A', None)
    definition.AppendEntry('B', None)
    definition.AppendEntry('NAME_LAST', None)
    definition.Finalize()
    self.assertEqual(['A', 'B', 'NAME_LAST'], definition.entries.keys())

  def testGenerateThrowsOnEmptyInput(self):
    with self.assertRaises(Exception):
      original_do_parse = java_cpp_enum.DoParseHeaderFile
      try:
        java_cpp_enum.DoParseHeaderFile = lambda _: []
        java_cpp_enum.DoGenerate('dir', ['file'])
      finally:
        java_cpp_enum.DoParseHeaderFile = original_do_parse

def main(argv):
  parser = optparse.OptionParser()
  parser.add_option("--stamp", help="File to touch on success.")
  options, _ = parser.parse_args(argv)

  suite = unittest.TestLoader().loadTestsFromTestCase(TestPreprocess)
  unittest.TextTestRunner(verbosity=0).run(suite)

  if options.stamp:
    build_utils.Touch(options.stamp)

if __name__ == '__main__':
  main(sys.argv[1:])
