# Copyright (c) 2019-2020, 2022-2023 Qualcomm Technologies, Inc.
# All Rights Reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.

import os
import SCons.Script
import subprocess

def exists(env):
  return True

def generate(env):
  if not env.OfftargetIsEnabled() or env.IsKeyEnable('QTEEEMU_IMAGE'):
    return
  env.AddMethod(do_bare_test,"DoBareTest")
  env.AddMethod(do_googletest_test,"DoGoogleTest")
  env.AddMethod(run_test_executable,"RunTestExecutable")
  base_env = env

  #the standalone sdk comes with the gtest objects so no need to do this
  if not env.StandaloneSdk():
    for p in ['x86-32', 'x86-64']:
      #we only want to generate these objects once
      if 'GTEST_LIB' + p.replace('-','_') in env['ROOT_GLOBAL_ENV']:
        continue
      env = base_env.Clone()
      env.Replace(OFF_TARGET = True)
      target = 'unittest32' if p == 'x86-32' else 'unittest64'

      aliases = ['unittest', target]
      specific_tag = 'UNITTESTING64_IMAGE' if target == 'unittest64' else 'UNITTESTING32_IMAGE'

      env.InitImageVars(
        alias_list = aliases,       # list of aliases, unique name index [0]
        proc = p,                   # proc settings
        config = 'apps',            # config settings
        build_tags = ['APPS_PROC', 'UNITTESTING_IMAGE', specific_tag],
      )

      env.Tool('offtarget_toolchain',toolpath=['${BUILD_ROOT}/ssg/bsp/build/scripts/'])
      env.InitBuildConfig(False)

      SRCPATH = "${BUILD_ROOT}/ssg/bsp/unittests"
      env.VariantDir('${BUILDPATH}', SRCPATH, duplicate=0)

      env.Append(CPPPATH=['${BUILD_ROOT}/ssg/bsp/unittests/'])
      env.Append(CPPPATH=['${BUILD_ROOT}/ssg/bsp/unittests/gtest'])
      env.Replace(APP_EXEC_MODE=p)
      gtest_object = [env.Object('$BUILDPATH/gtest/gtest-all.cc'),env.Object('$BUILDPATH/src/gtest_main.cpp')]

      env['ROOT_GLOBAL_ENV']['GTEST_LIB' + p.replace('-','_')] = env.Flatten(gtest_object)
      env.Append(LINKFLAGS=' -pthread ')
      env.Append(CXXFLAGS=' -std=c++11 ')
      env.Append(LINKFLAGS=' -std=c++11 ')


def do_googletest_test(env, prog_name, sources, includes=None, **kwargs):
  proc = 'x86-32' if env['PROC'] == 'scorpion' else 'x86-64'
  env = env.Clone()
  if env.StandaloneSdk():
    gtest_objs = ['$SDK_ROOT/libs/' + proc + '/gtest_main.o',
                  '$SDK_ROOT/libs/' + proc + '/gmock-all.o',
                  '$SDK_ROOT/libs/' + proc + '/gtest-all.o']
  else:
    gtest_objs = env['ROOT_GLOBAL_ENV']['GTEST_LIB' + proc.replace('-','_')]
  env.Append(CPPPATH=['${BUILD_ROOT}/ssg/bsp/unittests/'])
  env.Append(CPPPATH=['${BUILD_ROOT}/ssg/bsp/unittests/gtest'])
  env.Append(LINKFLAGS=' -pthread ')
  env.Append(CXXFLAGS=' -std=c++11 ')
  env.Append(LINKFLAGS=' -std=c++11 ')

  if kwargs.get('user_libs') and isinstance(kwargs.get('user_libs'), list):
    kwargs['user_libs'].extend(gtest_objs)
  else:
    kwargs['user_libs'] = gtest_objs

  return do_bare_test(env, prog_name, sources, includes, doing_google_test=True, **kwargs)

def do_bare_test(env, prog_name, sources, includes=None, **kwargs):
  if not kwargs.get('doing_google_test'):
    #we already cloned in do_googletest_test
    env = env.Clone()

  env['OFF_TARGET'] = True
  if env.StandaloneSdk():
    env.Tool('offtarget_toolchain',toolpath=['${SDK_ROOT}/scripts/'])
  else:
    env.Tool('offtarget_toolchain',toolpath=['${BUILD_ROOT}/ssg/bsp/build/scripts/'])


  test64only = kwargs.get('test64only')
  test32only = kwargs.get('test32only')

  if test64only and test32only:
    raise ValueError('error: cant specify test64only=True and test32only=True at the same time')

  if test32only and env.IsKeyEnable('UNITTESTING64_IMAGE'):
    return
  if test64only and env.IsKeyEnable('UNITTESTING32_IMAGE'):
    return

  if 'linker_script' in kwargs:
    linker_script = kwargs['linker_script']
    scl_path = os.path.join('$BUILDPATH', os.path.splitext(os.path.basename(linker_script))[0])
    scl = env.SclBuilder(scl_path, linker_script)
    kwargs['LINKFLAGS'] = env['LINKFLAGS'] + ' -Wl,-T,' + env.RealPath(scl[0].get_abspath())

  if not includes:
    includes = []

  prog = env.OfftargetClientAppBuilder(sources=sources,includes=includes,image=prog_name,no_emulation_libs=True,**kwargs)

  if 'linker_script' in kwargs:
    env.Depends(prog, scl)

  env.RunTestExecutable(prog)
  aliases = env.Flatten([kwargs.get('alias', env.get('IMAGE_ALIASES','')), prog_name])
  a = env.Alias(aliases, prog)
  env.AlwaysBuild(prog)

  return a

def run_test_executable(env, prog):
  prog_path = prog[0].abspath
  tools_prefix = env.subst(env.get("OFFTARGET_TOOLS_PREFIX", ""))

  if env.DoLineCoverage():
    raw_prof_path = prog_path + '.profraw'
    indexed_prof_path = prog_path + '.profdata'
    action = 'report' if not env.get('OFFTARGET_LINECOVERAGE_VERBOSE') else 'show'
    cmd = ['LLVM_PROFILE_FILE={} '.format(raw_prof_path) + prog_path,
    env['PROFDATA'] + ' merge -sparse {} -o {}'.format(raw_prof_path,indexed_prof_path),
    env['COV'] + ' {} {} -instr-profile={}'.format(action, prog_path, indexed_prof_path)]
    cmd.insert(0,
      env.get("LD_LIBRARY_PATH_STR","")
      + tools_prefix
      + "/usr/bin/time "
      + tools_prefix
      + "/usr/bin/timeout -k 30s 5m "
    )
  else:
    cmd = (env.get("LD_LIBRARY_PATH_STR","")
    + env.GetSanitizerCommandlineOptions()
    + tools_prefix
    + "/usr/bin/time "
    + tools_prefix
    + "/usr/bin/timeout -k 30s 5m "
    + prog_path)

  def run_cmd_safely(target, source, env):
    try:
      print(f"Run offtarget test: {env.subst(cmd)}\n")
      subprocess.check_call(env.subst(cmd), shell=True)
    except subprocess.CalledProcessError as e:
      if e.returncode == 124:
        print(f"ERROR: Offtarget testing of {str(target[0])} timed out\n")
      else:
        print(f"ERROR: Offtarget testing of {str(target[0])} failed\n")
      print(f"Here is the command to reproduce: {env.subst(cmd)}\n")
      raise e

  env.AddPostAction(prog, run_cmd_safely)
