#!/usr/bin/env python
#
# Copyright 2018 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.

"""Deploys and runs a test package on a Fuchsia target."""

import argparse
import os
import runner_logs
import sys
import tempfile

from common_args import AddCommonArgs, AddTargetSpecificArgs, \
                        ConfigureLogging, GetDeploymentTargetForArgs
from net_test_server import SetupTestServer
from run_test_package import RunTestPackage, RunTestPackageArgs, SystemLogReader
from runner_exceptions import HandleExceptionAndReturnExitCode
from runner_logs import RunnerLogManager
from symbolizer import BuildIdsPaths

DEFAULT_TEST_SERVER_CONCURRENCY = 4

TEST_DATA_DIR = '/tmp'
TEST_FILTER_PATH = TEST_DATA_DIR + '/test_filter.txt'
TEST_LLVM_PROFILE_PATH = TEST_DATA_DIR + '/llvm-profile'
TEST_PERF_RESULT_PATH = TEST_DATA_DIR + '/test_perf_summary.json'
TEST_RESULT_PATH = TEST_DATA_DIR + '/test_summary.json'

TEST_REALM_NAME = 'chromium_tests'


def AddTestExecutionArgs(arg_parser):
  test_args = arg_parser.add_argument_group('testing',
                                            'Test execution arguments')
  test_args.add_argument('--gtest_filter',
                         help='GTest filter to use in place of any default.')
  test_args.add_argument(
      '--gtest_repeat',
      help='GTest repeat value to use. This also disables the '
      'test launcher timeout.')
  test_args.add_argument(
      '--test-launcher-retry-limit',
      help='Number of times that test suite will retry failing '
      'tests. This is multiplicative with --gtest_repeat.')
  test_args.add_argument('--test-launcher-shard-index',
                         type=int,
                         default=os.environ.get('GTEST_SHARD_INDEX'),
                         help='Index of this instance amongst swarming shards.')
  test_args.add_argument('--test-launcher-total-shards',
                         type=int,
                         default=os.environ.get('GTEST_TOTAL_SHARDS'),
                         help='Total number of swarming shards of this suite.')
  test_args.add_argument('--gtest_break_on_failure',
                         action='store_true',
                         default=False,
                         help='Should GTest break on failure; useful with '
                         '--gtest_repeat.')
  test_args.add_argument('--single-process-tests',
                         action='store_true',
                         default=False,
                         help='Runs the tests and the launcher in the same '
                         'process. Useful for debugging.')
  test_args.add_argument('--test-launcher-batch-limit',
                         type=int,
                         help='Sets the limit of test batch to run in a single '
                         'process.')
  # --test-launcher-filter-file is specified relative to --out-dir,
  # so specifying type=os.path.* will break it.
  test_args.add_argument(
      '--test-launcher-filter-file',
      default=None,
      help='Filter file(s) passed to target test process. Use ";" to separate '
      'multiple filter files ')
  test_args.add_argument('--test-launcher-jobs',
                         type=int,
                         help='Sets the number of parallel test jobs.')
  test_args.add_argument('--test-launcher-summary-output',
                         help='Where the test launcher will output its json.')
  test_args.add_argument('--enable-test-server',
                         action='store_true',
                         default=False,
                         help='Enable Chrome test server spawner.')
  test_args.add_argument(
      '--test-launcher-bot-mode',
      action='store_true',
      default=False,
      help='Informs the TestLauncher to that it should enable '
      'special allowances for running on a test bot.')
  test_args.add_argument('--isolated-script-test-output',
                         help='If present, store test results on this path.')
  test_args.add_argument(
      '--isolated-script-test-perf-output',
      help='If present, store chartjson results on this path.')
  test_args.add_argument('--use-run-test-component',
                         default=False,
                         action='store_true',
                         help='Run the test package hermetically using '
                         'run-test-component, rather than run.')
  test_args.add_argument(
      '--code-coverage',
      default=False,
      action='store_true',
      help='Gather code coverage information and place it in '
      'the output directory.')
  test_args.add_argument('--code-coverage-dir',
                         default=os.getcwd(),
                         help='Directory to place code coverage information. '
                         'Only relevant when --code-coverage set to true. '
                         'Defaults to current directory.')
  test_args.add_argument('--child-arg',
                         action='append',
                         help='Arguments for the test process.')
  test_args.add_argument('child_args',
                         nargs='*',
                         help='Arguments for the test process.')


def main():
  parser = argparse.ArgumentParser()
  AddTestExecutionArgs(parser)
  AddCommonArgs(parser)
  AddTargetSpecificArgs(parser)
  args = parser.parse_args()

  # Flag out_dir is required for tests launched with this script.
  if not args.out_dir:
    raise ValueError("out-dir must be specified.")

  # Code coverage uses runtests, which calls run_test_component.
  if args.code_coverage:
    args.use_run_test_component = True

  ConfigureLogging(args)

  child_args = []
  if args.test_launcher_shard_index != None:
    child_args.append(
        '--test-launcher-shard-index=%d' % args.test_launcher_shard_index)
  if args.test_launcher_total_shards != None:
    child_args.append(
        '--test-launcher-total-shards=%d' % args.test_launcher_total_shards)
  if args.single_process_tests:
    child_args.append('--single-process-tests')
  if args.test_launcher_bot_mode:
    child_args.append('--test-launcher-bot-mode')
  if args.test_launcher_batch_limit:
    child_args.append('--test-launcher-batch-limit=%d' %
                       args.test_launcher_batch_limit)

  # Only set --test-launcher-jobs if the caller specifies it, in general.
  # If the caller enables the test-server then we need to launch the right
  # number of instances to match the maximum number of parallel test jobs, so
  # in that case we set --test-launcher-jobs based on the number of CPU cores
  # specified for the emulator to use.
  test_concurrency = None
  if args.test_launcher_jobs:
    test_concurrency = args.test_launcher_jobs
  elif args.enable_test_server:
    if args.device == 'device':
      test_concurrency = DEFAULT_TEST_SERVER_CONCURRENCY
    else:
      test_concurrency = args.cpu_cores
  if test_concurrency:
    child_args.append('--test-launcher-jobs=%d' % test_concurrency)

  if args.gtest_filter:
    child_args.append('--gtest_filter=' + args.gtest_filter)
  if args.gtest_repeat:
    child_args.append('--gtest_repeat=' + args.gtest_repeat)
    child_args.append('--test-launcher-timeout=-1')
  if args.test_launcher_retry_limit:
    child_args.append(
        '--test-launcher-retry-limit=' + args.test_launcher_retry_limit)
  if args.gtest_break_on_failure:
    child_args.append('--gtest_break_on_failure')
  if args.test_launcher_summary_output:
    child_args.append('--test-launcher-summary-output=' + TEST_RESULT_PATH)
  if args.isolated_script_test_output:
    child_args.append('--isolated-script-test-output=' + TEST_RESULT_PATH)
  if args.isolated_script_test_perf_output:
    child_args.append('--isolated-script-test-perf-output=' +
                      TEST_PERF_RESULT_PATH)

  if args.child_arg:
    child_args.extend(args.child_arg)
  if args.child_args:
    child_args.extend(args.child_args)

  test_realms = []
  if args.use_run_test_component:
    test_realms = [TEST_REALM_NAME]

  try:
    with GetDeploymentTargetForArgs(args) as target, \
         SystemLogReader() as system_logger, \
         RunnerLogManager(args.runner_logs_dir, BuildIdsPaths(args.package)):
      target.Start()

      if args.system_log_file and args.system_log_file != '-':
        system_logger.Start(target, args.package, args.system_log_file)

      if args.test_launcher_filter_file:
        test_launcher_filter_files = args.test_launcher_filter_file.split(';')
        with tempfile.NamedTemporaryFile('a+b') as combined_filter_file:
          for filter_file in test_launcher_filter_files:
            with open(filter_file, 'r') as f:
              combined_filter_file.write(f.read())
          combined_filter_file.seek(0)
          target.PutFile(combined_filter_file.name,
                         TEST_FILTER_PATH,
                         for_package=args.package_name,
                         for_realms=test_realms)
          child_args.append('--test-launcher-filter-file=' + TEST_FILTER_PATH)

      test_server = None
      if args.enable_test_server:
        assert test_concurrency
        test_server = SetupTestServer(target, test_concurrency,
                                      args.package_name, test_realms)

      run_package_args = RunTestPackageArgs.FromCommonArgs(args)
      if args.use_run_test_component:
        run_package_args.test_realm_label = TEST_REALM_NAME
        run_package_args.use_run_test_component = True
      returncode = RunTestPackage(args.out_dir, target, args.package,
                                  args.package_name, child_args,
                                  run_package_args)

      if test_server:
        test_server.Stop()

      if args.code_coverage:
        # Copy all the files in the profile directory. /* is used instead
        # of recursively copying due to permission issues for the latter.
        target.GetFile(TEST_LLVM_PROFILE_PATH + '/*', args.code_coverage_dir)

      if args.test_launcher_summary_output:
        target.GetFile(TEST_RESULT_PATH,
                       args.test_launcher_summary_output,
                       for_package=args.package_name,
                       for_realms=test_realms)

      if args.isolated_script_test_output:
        target.GetFile(TEST_RESULT_PATH,
                       args.isolated_script_test_output,
                       for_package=args.package_name,
                       for_realms=test_realms)

      if args.isolated_script_test_perf_output:
        target.GetFile(TEST_PERF_RESULT_PATH,
                       args.isolated_script_test_perf_output,
                       for_package=args.package_name,
                       for_realms=test_realms)

      return returncode

  except:
    return HandleExceptionAndReturnExitCode()


if __name__ == '__main__':
  sys.exit(main())
