TiagoMSSantos/MobileRT

View on GitHub
scripts/helper_functions.sh

Summary

Maintainability
Test Coverage
#!/usr/bin/env sh

###############################################################################
# README
###############################################################################
# This script contains a bunch of helper functions for the shell scripts.
###############################################################################
###############################################################################

# Helper command for compilation scripts.
helpCompile() {
  echo 'Usage: cmd [-h] [-t type] [-c compiler] [-r recompile]';
  return 1;
}

# Helper command for Android compilation scripts.
helpCompileAndroid() {
  echo 'Usage: cmd [-h] [-t type] [-f cpu_architecture] [-c compiler] [-r recompile] [-a android_api_version]';
  return 1;
}

# Helper command for Android run tests scripts.
helpTestAndroid() {
  echo 'Usage: cmd [-h] [-t type] [-f cpu_architecture] [-r run_test] [-a android_api_version] [-k kill_previous]';
  return 1;
}

# Helper command for compilation scripts.
helpCheck() {
  echo 'Usage: cmd [-h] [-f cpu_architecture] [-a android_api_version]';
  return 1;
}

# Argument parser for compilation scripts.
parseArgumentsToCompile() {
  # Reset the index of the last option argument processed by the getopts.
  OPTIND=0;
  while getopts "ht:c:r:" opt; do
    case ${opt} in
      t )
        export type="${OPTARG}";
        echo "Setting type: ${type}";
        ;;
      c )
        export compiler="${OPTARG}";
        echo "Setting compiler: ${compiler}";
        ;;
      r )
        export recompile="${OPTARG}";
        echo "Setting recompile: ${recompile}";
        ;;
      h )
        helpCompile;
        ;;
      \? )
        helpCompile;
        ;;
    esac
  done
}

# Argument parser for Android compilation scripts.
parseArgumentsToCompileAndroid() {
  # Reset the index of the last option argument processed by the getopts.
  OPTIND=0;
  while getopts "ht:c:r:a:f:" opt; do
    case ${opt} in
      a )
        export android_api_version="${OPTARG}";
        ;;
      t )
        export type="${OPTARG}";
        ;;
      c )
        export compiler="${OPTARG}";
        checkCommand "${compiler}";
        ;;
      r )
        export recompile="${OPTARG}";
        ;;
      f )
        export cpu_architecture="${OPTARG}";
        ;;
      h )
        helpCompileAndroid;
        ;;
      \? )
        helpCompileAndroid;
        ;;
    esac
  done
}

# Argument parser for Android run tests scripts.
parseArgumentsToTestAndroid() {
  # Reset the index of the last option argument processed by the getopts.
  OPTIND=0;
  while getopts "ht:r:k:a:f:" opt; do
    case ${opt} in
      a )
        export android_api_version="${OPTARG}";
        ;;
      t )
        export type="${OPTARG}";
        ;;
      r )
        export run_test="${OPTARG}";
        ;;
      k )
        export kill_previous="${OPTARG}";
        ;;
      f )
        export cpu_architecture="${OPTARG}";
        ;;
      h )
        helpTestAndroid;
        ;;
      \? )
        helpTestAndroid;
        ;;
    esac
  done
}

# Argument parser for linter scripts.
parseArgumentsToCheck() {
  # Reset the index of the last option argument processed by the getopts.
  OPTIND=0;
  while getopts "ha:f:" opt; do
    case ${opt} in
      a )
        export android_api_version="${OPTARG}";
        ;;
      f )
        export cpu_architecture="${OPTARG}";
        ;;
      h )
        helpCheck;
        ;;
      \? )
        helpCheck;
        ;;
    esac
  done
}

# Call function multiple times until it fails and exit the process.
callCommandUntilError() {
  echo '';
  echo "Calling until error '$*'";
  _retry=0;
  set +e;
  "$@";
  lastResult=${?};
  while [ "${lastResult}" -eq 0 ] && [ ${_retry} -lt 5 ]; do
    _retry=$(( _retry + 1 ));
    "$@";
    lastResult=${?};
    echo "Retry: ${_retry} of command '$*'; result: '${lastResult}'";
    sleep 2;
  done
  set -e;
  if [ "${lastResult}" -eq 0 ]; then
    echo "$*: success - '${lastResult}'";
  else
    echo "$*: failed - '${lastResult}'";
    echo '';
    exit "${lastResult}";
  fi
}

# Call function multiple times until it doesn't fail and then return.
# Every attempt are made with a 3 seconds delay.
# Parameters:
# * The maximum number of retries.
callCommandUntilSuccess() {
  echo '';
  echo "Calling until success '$*'";
  _retry=0;
  _maxRetries=${1};
  set +e;
  # Perform a shift of the parameters, so it's ignored the 1st element (number of retries).
  shift;
  "${@}";
  lastResult=${?};
  echo "result: '${lastResult}'";
  # Android API 33 can take more than 1 minute to boot.
  while [ "${lastResult}" -ne 0 ] && [ "${_retry}" -lt "${_maxRetries}" ]; do
    _retry=$(( _retry + 1 ));
    "${@}";
    lastResult=${?};
    echo "Retry: ${_retry} of command '$*'; result: '${lastResult}'";
    sleep 3;
  done
  set -e;
  if [ "${lastResult}" -eq 0 ]; then
    echo "'$*': success";
  else
    echo "'$*': failed";
    exit "${lastResult}";
  fi
}

# Call an ADB shell function multiple times until it doesn't fail and then return.
callAdbShellCommandUntilSuccess() {
  echo '';
  _retry=0;
  set +e;
  echo "Calling ADB shell command until success '$*'";
  output=$("$@");
  echo "Output of command: '${output}'";
  lastResult=$(echo "${output}" | grep '::.*::' | sed 's/:://g'| tr -d '[:space:]');
  echo "result: '${lastResult}'";
  while [ "${lastResult}" != '0' ] && [ ${_retry} -lt 60 ]; do
    _retry=$(( _retry + 1 ));
    output=$("$@");
    echo "Output of command: '${output}'";
    lastResult=$(echo "${output}" | grep '::.*::' | sed 's/:://g' | tr -d '[:space:]');
    echo "Retry: ${_retry} of command '$*'; result: '${lastResult}'";
    sleep 3;
  done
  set -e;
  if [ "${lastResult}" = '0' ]; then
    echo "'$*': success";
  else
    echo "'$*': failed";
    exit "${lastResult}";
  fi
}

# Outputs the exit code received by argument and exits the current process with
# that exit code.
# Parameters:
# * Error code
# * Text to be printed
printCommandExitCode() {
  echo '#####################################################################';
  echo 'Results:';
  if [ "${1}" = '0' ]; then
    echo "${2}: success (${1})";
  else
    echo "${2}: failed (${1})";
    exit "${1}";
  fi
}

# Check command is available.
# Parameters:
# * command to check if is available.
checkCommand() {
  if command -v "${@}" > /dev/null; then
    echo "Command '$*' installed!";
  else
    echo "Command '$*' is NOT installed.";
    if uname -a | grep -iq 'msys' && echo "$*" | grep -iq 'python'; then
      echo "Detected Windows OS, so ignoring not having 'python' ...";
      return 0;
    fi
    exit 1;
  fi
}

# Capitalize 1st letter.
capitalizeFirstletter() {
  res="$(echo "${1}" | cut -c 1 | tr '[:lower:]' '[:upper:]')$(echo "${1}" | cut -c 2-)";
  echo "${res}";
}

# Parallelize building of MobileRT.
parallelizeBuild() {
  uname -a;
  if uname -a | grep -iq 'msys' && command -v nproc > /dev/null; then
    NCPU_CORES="$(nproc --all)";
    echo "Assuming Windows with ${NCPU_CORES} cores.";
  elif uname -a | grep -iq 'linux' && command -v nproc > /dev/null; then
    NCPU_CORES="$(nproc --all)";
    echo "Assuming Linux with ${NCPU_CORES} cores.";
  elif uname -a | grep -iq 'darwin' && command -v sysctl > /dev/null; then
    NCPU_CORES="$(sysctl -n hw.logicalcpu)";
    echo "Assuming MacOS with ${NCPU_CORES} cores.";
  fi
  export NCPU_CORES;
}

# Check the files that were modified in the last few minutes.
checkLastModifiedFiles() {
  MINUTES=15;
  echo '#####################################################################';
  echo 'Files modified in home:';
  find ~/ -type f -mmin -${MINUTES} -print 2> /dev/null | grep -v "mozilla" | grep -v "thunderbird" | grep -v "java" || true;
  echo '#####################################################################';
  echo 'Files modified in workspace:';
  find . -type f -mmin -${MINUTES} -print 2> /dev/null || true;
  echo '#####################################################################';
}

# Check if a path exists.
# Parameters:
# * path that should exist
# * file that should also exist in the provided path
checkPathExists() {
  du -h -d 1 "${1}" > /dev/null;
  returnValue="$?";
  if [ "${returnValue}" = '0' ] && [ $# -eq 1 ] ; then
    return 0;
  fi
  _validateFileExistsAndHasSomeContent "${1}"/"${2}";
}

# Change the mode of all binaries/scripts to be able to be executed.
# Parameters:
# * Optional - path to MobileRT
prepareBinaries() {
  rootDir="${1:-${PWD}}";
  chmod +x "${rootDir}"/test-reporter-latest-linux-amd64;
  chmod +x "${rootDir}"/test-reporter-latest-darwin-amd64;
}

# Private method which kills a process that is using a file.
_killProcessUsingFile() {
  processes_using_file=$(lsof "${1}" | tail -n +2 | tr -s ' ');
  _retry=0;
  while [ "${processes_using_file}" != '' ] && [ ${_retry} -lt 5 ]; do
    _retry=$(( _retry + 1 ));
    echo "processes_using_file: '${processes_using_file}'";
    process_id_using_file=$(echo "${processes_using_file}" | tr -s ' ' | cut -d ' ' -f 2 | head -1);
    if ps aux | grep -i "${process_id_using_file}" | grep -iq 'android-studio'; then
      echo "Not killing process: '${process_id_using_file}' because it is the Android Studio";
      return 0;
    else
      echo "Going to kill this process: '${process_id_using_file}'";
      kill -KILL "${process_id_using_file}" || true;
    fi
    processes_using_file=$(lsof "${1}" | tail -n +2 | tr -s ' ' || true);
  done
}

# Method which kills the processes that are using a port.
killProcessesUsingPort() {
  processes_using_port=$(lsof -i ":${1}" | tail -n +2 | tr -s ' ' | cut -d ' ' -f 2);
  _retry=0;
  while [ "${processes_using_port}" != '' ] && [ ${_retry} -lt 5 ]; do
    _retry=$(( _retry + 1 ));
    echo "processes_using_port: '${processes_using_port}'";
    process_id_using_port=$(echo "${processes_using_port}" | head -1);
    echo "Going to kill this process: '${process_id_using_port}'";
    kill -KILL "${process_id_using_port}" || true;
    processes_using_port=$(lsof -i ":${1}" | tail -n +2 | tr -s ' ' | cut -d ' ' -f 2 || true);
  done
}

# Delete all old build files (commonly called ".fuse_hidden<id>") that might not be able to be
# deleted due to some process still using it. So this method detects which process uses them and
# kills it first.
clearOldBuildFiles() {
  files_being_used=$(find . -iname "*.fuse_hidden*" || true);
  retry_files=0;
  while [ "${files_being_used}" != '' ] && [ ${retry_files} -lt 10 ]; do
    retry_files=$(( retry_files + 1 ));
    echo "files_being_used: '${files_being_used}'";
    # Changing IFS to allow finding commands in paths with spaces.
    old_IFS="${IFS}";
    IFS="$(printf '\n')";
    for file_being_used in ${files_being_used}; do
      echo "file_being_used: '${file_being_used}'";
      retry_file=0;
      while [ -f "${file_being_used}" ] && [ ${retry_file} -lt 2 ]; do
        retry_file=$(( retry_file + 1 ));
        _killProcessUsingFile "${file_being_used}";
        echo 'sleeping 1 sec';
        sleep 1;
        rm "${file_being_used}" || true;
      done
    done;
    IFS="${old_IFS}";
    files_being_used=$(find . -iname "*.fuse_hidden*" | grep -i ".fuse_hidden" || true);
  done
}

# Create the reports' folders.
# Also delete any logs previously created.
createReportsFolders() {
  echo 'Creating reports folders.';
  rm -rf build/reports;
  rm -rf app/build/reports;
  mkdir -p build/reports;
  mkdir -p app/build/reports;
  echo 'Created reports folders.';
}

# Validate MobileRT native lib was compiled.
validateNativeLibCompiled() {
  set +e;
  nativeLib=$(find . -iname "*mobilert*.a" -or -iname "*mobilert*.dll*" -or -iname "*mobilert*.so");
  find . -iname "*mobilert*.a" -or -iname "*mobilert*.dll*" -or -iname "*mobilert*.so" 2> /dev/null;
  set -e;
  echo "nativeLib: ${nativeLib}";
  if [ "$(echo "${nativeLib}" | wc -l)" -eq 0 ]; then
    exit 1;
  fi
}

# Extract and check files from downloaded artifact.
# This functions expects to receive a path where a zip file is stored, in order
# to extract it there.
# Parameters:
# * path where a zip file is stored (from the artifact) to be extracted
extractFilesFromArtifact() {
  du -h -d 1 "${1}";
  ls -lahp "${1}";

  # Unzip every zip file found.
  find "${1}" -maxdepth 1 -iname "*.zip" | while read -r filename; do
    echo "Unzipping file: ${filename}";
    unzip -o -d "${1}" "${filename}";
    find "${1}" -maxdepth 1 -iname "*.zip" | while read -r filenameInside; do
      echo "Unzipping file that was inside the previous zip: ${filenameInside}";
      unzip -o -d "${1}" "${filenameInside}";
      rm -v -- "${filenameInside}" || true;
    done;
    rm -v -- "${filename}" || true;
  done;

  # Delete every zip file found.
  find "${1}" -maxdepth 1 -iname "*.zip" | while read -r filename; do
    rm -v -- "${filename}" || true;
  done;

  du -h -d 1 "${1}";
  ls -lahp "${1}";
}

# Compact files for an artifact to be uploaded.
# Parameters:
# * path of a folder to be compacted
# * name for the new zip file
zipFilesForArtifact() {
  pathName=$(basename "${1}");
  du -h -d 1 "${1}";
  ls -lahp "${1}";

  oldpath=$(pwd);
  cd "${1}" || exit 1;

  echo "Zipping path: ${pathName}";
  zip -9 -v -r "${2}" ./*;
  cd "${oldpath}" || exit 1;

  du -h -d 1 "${1}";
  ls -lahp "${1}";
}

# Generate code coverage.
generateCodeCoverage() {
  lcov -c -d . --no-external -o code_coverage_test.info;
  lcov -a code_coverage_base.info -a code_coverage_test.info -o code_coverage.info;
  lcov --remove code_coverage.info '*third_party*' '*build*' '*Unit_Testing*' -o code_coverage_filtered.info;
  genhtml code_coverage_filtered.info -o code_coverage_report --no-branch-coverage -t MobileRT_code_coverage;
  _validateCodeCoverage;
}

# Validate generated files for code coverage.
_validateCodeCoverage() {
  _validateFileExistsAndHasSomeContent code_coverage_base.info;
  _validateFileExistsAndHasSomeContent code_coverage_test.info;
  _validateFileExistsAndHasSomeContent code_coverage.info;
  _validateFileExistsAndHasSomeContent code_coverage_filtered.info;
}

# Add command to the PATH environment variable.
# Parameters
# 1) command name
addCommandToPath() {
  echo "Adding '${1}' to PATH.";
  if command -v "${1}" > /dev/null; then
    echo "Command '${1}' already available.";
    return 0;
  fi
  visualStudioVersion=$(ls -la "/c/Program Files/Microsoft Visual Studio/" | grep ".*[[:digit:]]$" | awk '{print $(NF)}' | tail -1 || true);
  echo "Detected Visual Studio version: ${visualStudioVersion}";
  msvcVersion=$(ls -la "/c/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/VC/Tools/MSVC/" | grep "[[:digit:]]\.[[:digit:]].*" | awk '{print $(NF)}' | tail -1 || true);
  echo "Detected Visual Studio C++ Compiler version: ${msvcVersion}";
  COMMAND_PATHS=$(find \
    /opt/intel/oneapi/compiler \
    ~/.. \
    /usr \
    "/c/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/VC/Tools/MSVC/${msvcVersion}/bin/Hostx64/x64" \
    "/c/Program Files/Microsoft Visual Studio/${visualStudioVersion}/Enterprise/MSBuild/Current/Bin/amd64" \
    -type f -iname "${1}" -or -iname "${1}.exe" 2> /dev/null | grep -i "bin" || true);
  # Changing IFS to allow finding commands in paths with spaces.
  old_IFS="${IFS}";
  IFS="$(printf '\n')";
  for COMMAND_PATH in ${COMMAND_PATHS}; do
    echo "Command path to executable: ${COMMAND_PATH}";
    echo "Command location Unix: ${COMMAND_PATH%/"${1}"}";
    echo "Command location Windows: ${COMMAND_PATH%/"${1}.exe"}";
    export PATH="${PATH}:${COMMAND_PATH%/"${1}"}";
    export PATH="${PATH}:${COMMAND_PATH%/"${1}.exe"}";
  done;
  IFS="${old_IFS}";
  if [ -z "${COMMAND_PATHS}" ]; then
    echo "Command '${1}' was not found in the system.";
    return 1;
  fi

  echo "PATH: ${PATH}";
}

# Validate whether a file exists or not.
# Also check if the file has some content.
# Parameters
# 1) path to file
_validateFileExistsAndHasSomeContent() {
  filePath="${1}";
  if [ ! -f "${filePath}" ]; then
    echo "File '${filePath}' does NOT exist." >&2;
    return 1;
  fi
  if [ ! -s "${filePath}" ]; then
    echo "File '${filePath}' is empty." >&2;
    return 1;
  fi
  fileSize=$(cat "${filePath}" | wc -w | tr -d '[:space:]');
  if [ "${fileSize}" = '' ]; then
    return 2;
  fi
  if [ "${fileSize}" -lt 110 ]; then
    echo "File '${filePath}' contains '${fileSize}' words, which is less than 110 words." >&2;
    cat "${filePath}";
    return 1;
  fi
}
###############################################################################
###############################################################################