#!/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 test_env

from recipe_engine import recipe_api, config


RECIPE_PROPERTY = recipe_api.BoundProperty.RECIPE_PROPERTY
MODULE_PROPERTY = recipe_api.BoundProperty.MODULE_PROPERTY


def make_prop(**kwargs):
  name = kwargs.pop('name', 'dumb_name')
  return recipe_api.Property(**kwargs).bind(
    name, RECIPE_PROPERTY, 'fake_repo::fake_recipe')


class TestProperties(test_env.RecipeEngineUnitTest):
  def testDefault(self):
    """Tests the default option of properties."""
    for val in (1, {}, 'test', None):
      prop = make_prop(default=val)
      self.assertEqual(val, prop.interpret(recipe_api.PROPERTY_SENTINEL, {}))

  def testRequired(self):
    """Tests that a required property errors when not provided."""
    prop = make_prop()
    with self.assertRaises(ValueError):
      prop.interpret(recipe_api.PROPERTY_SENTINEL, {})

  def testTypeSingle(self):
    """Tests a simple typed property."""
    prop = make_prop(kind=bool)
    with self.assertRaises(TypeError):
      prop.interpret(1, {})

    self.assertEqual(True, prop.interpret(True, {}))

  def testTypeFancy(self):
    """Tests a config style type property."""
    prop = make_prop(kind=config.List(int))
    for value in (1, 'hi', [3, 'test']):
      with self.assertRaises(TypeError):
        prop.interpret(value, {})

    self.assertEqual([2, 3], prop.interpret([2, 3], {}))

  def testFromEnviron(self):
    """Tests that properties can pick up values from environment."""
    prop = make_prop(default='def', from_environ='ENV_VAR')

    # Nothing is given => falls back to hardcoded default.
    self.assertEqual('def', prop.interpret(recipe_api.PROPERTY_SENTINEL, {}))
    # Only env var is given => uses it.
    self.assertEqual(
        'var', prop.interpret(recipe_api.PROPERTY_SENTINEL, {'ENV_VAR': 'var'}))
    # Explicit values override the environment.
    self.assertEqual('value', prop.interpret('value', {'ENV_VAR': 'var'}))

  def testValidTypes(self):
    check = recipe_api.BoundProperty.legal_name

    for test, result, is_param_name in (
        ('', False, False),
        ('.', False, False),
        ('foo', True, False),
        ('weird.param.name', True, False),
        ('weird.param.name', False, True),
        ('rietveld_url', True, False),):
      self.assertEqual(
          check(test, is_param_name=is_param_name), result,
          "name {} should be {}. is_param_name={}".format(
              test, result, is_param_name))

  def testParamName(self):
    """
    Tests setting a param name correctly carries through to a bound property.
    """
    prop = recipe_api.Property(param_name='b')
    bound = prop.bind('a', RECIPE_PROPERTY, 'fake_repo::fake_recipe')

    self.assertEqual('b', bound.param_name)

  def testParamNameDotted(self):
    """
    Tests setting a param name correctly carries through to a bound property.
    """
    prop = recipe_api.Property(param_name='good_name')
    bound = prop.bind('bad.name-time', RECIPE_PROPERTY,
                      'fake_repo::fake_recipe')

    self.assertEqual('good_name', bound.param_name)

  def testModuleName(self):
    """
    Tests declaring $repo_name/module properties.
    """
    prop = recipe_api.Property(param_name='foo')
    prop.bind('$fake_repo/fake_module', MODULE_PROPERTY,
              'fake_repo::fake_module')

    with self.assertRaises(ValueError):
      prop.bind('$fake_repo/wrong_module', MODULE_PROPERTY,
                'fake_repo::fake_module')

    with self.assertRaises(ValueError):
      prop.bind('$fake_repo/fake_module', RECIPE_PROPERTY,
                'fake_repo::fake_module:example')


class TestProtoProperties(test_env.RecipeEngineUnitTest):
  def setUp(self):
    super(TestProtoProperties, self).setUp()
    self.deps = self.FakeRecipeDeps()

    main = self.deps.main_repo
    with main.write_file('recipe_proto/proto_props/props.proto') as proto:
      proto.write('''
        syntax = "proto3";
        message Props {
          string best_prop = 1;
          int32 worst_prop = 2;
        }
        message ModProps {
          string mod_prop = 1;
        }
        message EnvProps {
          string STR_ENVVAR = 1;
          int32  NUM_ENVVAR = 2;
        }
      ''')

  def testRecipeProperties(self):
    main = self.deps.main_repo

    with main.write_recipe('recipe') as recipe:
      recipe.imports = ['from PB.proto_props import props']
      recipe.DEPS += ['recipe_engine/properties']
      recipe.PROPERTIES = 'props.Props'
      recipe.ENV_PROPERTIES = 'props.EnvProps'
      recipe.RunSteps_args += ['props', 'env_props']
      recipe.RunSteps.write('''
        api.step('dump', ['echo', '[ normal prop:', props.best_prop, ']'])
        api.step('dump', ['echo', '[ env prop:', env_props.STR_ENVVAR, ']'])
      ''')

    output, retcode = main.recipes_py(
        'run', 'recipe', 'best_prop="best property"', env={
          'STR_ENVVAR': 'coolio',
        })
    self.assertEqual(retcode, 0, output)
    self.assertIn('[ normal prop: best property ]', output)
    self.assertIn('[ env prop: coolio ]', output)

  def testModuleProperties(self):
    main = self.deps.main_repo

    with main.write_module('modname') as mod:
      mod.imports = ['from PB.proto_props import props']
      mod.PROPERTIES = 'props.ModProps'
      mod.GLOBAL_PROPERTIES = 'props.Props'
      mod.ENV_PROPERTIES = 'props.EnvProps'
      mod.api.write('''
        def __init__(self, props, global_props, env_props, **kwargs):
          super(ModnameApi, self).__init__(**kwargs)
          self.value = global_props.best_prop
          self.mod_value = props.mod_prop
          self.env_value_str = env_props.STR_ENVVAR
          self.env_value_num = env_props.NUM_ENVVAR
      ''')

    with main.write_recipe('recipe') as recipe:
      recipe.DEPS += ['modname']
      recipe.RunSteps.write('''
        api.step('dump global', ['echo', '[ global:', api.modname.value, ']'])
        api.step('dump mod', ['echo', '[ mod:', api.modname.mod_value, ']'])
        api.step('dump env str', ['echo', '[ env str:', api.modname.env_value_str, ']'])
        api.step('dump env num', ['echo', '[ env num:', api.modname.env_value_num, ']'])
      ''')

    output, retcode = main.recipes_py(
        'run', 'recipe', 'best_prop="best property"',
        '$main/modname={"mod_prop": "mod property"}',
        env={
          'STR_ENVVAR': 'env property',
          'NUM_ENVVAR': '9000',
        })
    self.assertEqual(retcode, 0, output)
    self.assertIn('[ global: best property ]', output)
    self.assertIn('[ mod: mod property ]', output)
    self.assertIn('[ env str: env property ]', output)
    self.assertIn('[ env num: 9000 ]', output)

  def testBadPropertyType(self):
    main = self.deps.main_repo

    with main.write_recipe('recipe') as recipe:
      recipe.imports = ['from PB.proto_props import props']
      recipe.PROPERTIES = 'props.Props'
      recipe.RunSteps_args += ['properties']

    output, retcode = main.recipes_py('run', 'recipe',
                                      'worst_prop="invalid value"')
    self.assertNotEqual(retcode, 0, output)
    self.assertRegexpMatches(output, r'ParseError.*worst_prop.*invalid value')


if __name__ == '__main__':
  test_env.main()
