Skip to content
Snippets Groups Projects
odoo_scripts_common 12.3 KiB
Newer Older
# vim: set shiftwidth=2 softtabstop=2:
#
# Common functions and env var.
#
# This file is meant to be sourced.

# assume we are called from the project home
project_home=$PWD
project_name=$(basename "$project_home")

if ! type python3 > /dev/null; then
    python="python2"
else
    python="python3"
fi

# handy colorized variables
esc=$(printf '\033')
RESET="${esc}[0m"
DEBUG="[${esc}[36mDEBUG${RESET}]"
INFO="[${esc}[34mINFO ${RESET}]"
WARN="[${esc}[33mWARN ${RESET}]"
FATAL="[${esc}[41m${esc}[37mFATAL${RESET}]"
GREEN="${esc}[32m"
OK="[${GREEN} OK${RESET}  ]"
RED="${esc}[31m"
KO="[${esc}[41m${esc}[37m KO  ${RESET}]"
WHITE_BOLD="${esc}[1;37m"

function read_configuration_key () {
  # Read a configuration key from any ini file
  # first argument is file path
  # second argument is section
  # third argument is the key to use
  # fourth argument is default value, if any
  # fifth argument is separator, default to a space
  file=$1
  section=$2
  configuration_key=$3
  default_value=$4
  separator=${5- }
  echo $($python -B -c "from six.moves import configparser ; c = configparser.ConfigParser() ; c.read('${file}') ; print('$separator'.join(c.get('$section', '$configuration_key').split()) if c.has_option('$section', '$configuration_key') else '$default_value')")
}

function read_odoo_scripts_configuration_key () {
  # Return configuration value from odoo_scripts configuration file
  # first argument is the key to use
  # second argument is default value, if any
  configuration_key=${1}
  default_value=$2
  read_configuration_key "${project_home}/setup.cfg" odoo_scripts $configuration_key $default_value
}

function read_odoo_scripts_expandable_configuration_key () {
  # return configuration value by appending any expanded values from defined super projects. glob key as appropriate, for module for example.
  # first argument is the key to use
  # second optional argument is the separator to use (default to comma)
  configuration_key=${1}
  separator=${2-,}
  # read expanded configurations, only done with projects in python 3
  if type python3 > /dev/null; then
    configuration_value=$(python3 -B -c "from odoo_scripts.config import Configuration ; c = Configuration('$project_home') ; print('$separator'.join(c.$configuration_key))")
  else
    # read only local values
    configuration_value=$($python -B -c "from six.moves import configparser ; import os ; c = configparser.ConfigParser() ; c.read(os.path.join('$project_home', 'setup.cfg')) ; print('$separator'.join(c.get('odoo_scripts', '$configuration_key').split() if c.has_option('odoo_scripts', '$configuration_key') else []))")
    if [[ $configuration_key == modules ]]; then
      configuration_value=$($python -B -c "import os;from glob import glob;print('$separator'.join([file for path in '${configuration_value}'.split('$separator') for file in glob(path) if os.path.isdir(file)]))")
    fi
  fi
  echo $configuration_value
}

# function to be used in pipes
function colorize () {
  cat | sed -E \
  -e "s/(.*) (TEST) (.*) (ERROR)(.*)/${esc}[2m\1${esc}[22m ${esc}[34m${esc}[7m\2${esc}[27m \3${esc}[0m ${esc}[41m${esc}[97m${esc}[1m\4${esc}[49m${esc}[31m\5${esc}[0m/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (TEST) ([^ ]+) ([^:]+:) (OK)?(.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[36m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;39m\5 ${esc}[36m${esc}[7m\6${esc}[27m${esc}[39m${esc}[36m\7$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (INFO) ([^ ]+) (odoo\.addons\.[^:]*\.tests\.[^:]*:) (Starting .*)/\1 ${esc}[2m\2${esc}[22m ${esc}[36m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;39m\5 ${esc}[36m\6$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (INFO) ([^ ]+) (odoo\.(tests\.(runner|result|stats)|modules\.module):) (.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[36m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;39m\5 ${esc}[36m\8$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (INFO) ([^ ]+) (o(doo|penerp)\.(modules\.loading|tools\.translate)|openerp\.addons\.base\.ir\.ir_translation|odoo\.addons\.base\.models\.ir_module): (.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[32m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;22m\5: ${esc}[38;5;64m\8$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (INFO) ([^ ]+) ([^:]*:) (=+)/\1 ${esc}[2m\2${esc}[22m ${esc}[32m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;28m\5 ${esc}[31m\6$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (INFO) ([^ ]+) ([^:]*:)( ?[^=].*)/\1 ${esc}[2m\2${esc}[22m ${esc}[32m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;28m\5${esc}[32m\6$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (DEBUG) ([^ ]+) ([^:]*:) (.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[34m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;24m\5 ${esc}[34m\6$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (WARNING) ([^ ]+) ([^:]*:) (.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[33m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;94m\5 ${esc}[33m\6$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (ERROR) ([^ ]+) (odoo\.(tests\.(runner|result|stats)|modules\.module):) (FAIL)?(.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[35m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;88m\5 ${esc}[35m${esc}[7m\8${esc}[27m\9$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (ERROR) ([^ ]+) ([^:]*:) (FAIL)?(.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[31m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[38;5;88m\5 ${esc}[31m${esc}[7m\6${esc}[27m\7$RESET/" \
  -e "s/^([[:digit:]]{4,}-[[:digit:]]{2}-[[:digit:]]{2} [[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2},[[:digit:]]{3}) ([[:digit:]]+) (CRITICAL) ([^ ]+) ([^:]*:) (.*)/\1 ${esc}[2m\2${esc}[22m ${esc}[1;31m${esc}[7m\3${esc}[0m ${esc}[2m\4${esc}[22m ${esc}[31m\5 ${esc}[1;31m\6$RESET/" \
  -e "s/ FAILED/ ${esc}[41m${esc}[97m${esc}[1mFAILED${esc}[0m/" \
}

# The analyze method does several things:
# - print a resume of failure, errors and passes
# - create a metrics file with the number of tests (when the CI environment variable equals true)
# - return an error code based on the parsing of the log file. This is meant to be used on Odoo that did not exit with
#   a non-zero value when tests fail.
# The method takes the logfile as its only argument.
function analyze () {
  logfile=$1
  # Odoo >= 14 detection
  # Odoo 17 uses odoo.tests.result rather than odoo.tests.runner
  if [ $(grep -P 'odoo\.tests\.(runner|result): ([[:digit:]]+) failed, [[:digit:]]+ error\(s\) of [[:digit:]]+ tests when loading database' $logfile --count) -gt 0 ];
  then
    # Valid with odoo 14, 15, 16, 17
    # Add junit tests results to qunit tests results
    tests_ran=$(grep -P 'odoo\.tests\.(runner|result): ([[:digit:]]+) failed, [[:digit:]]+ error\(s\) of \K[[:digit:]]+' $logfile -o)
    quilt_tests_ran=$(grep -P ': ([[:digit:]]+) / [[:digit:]]+ tests failed.' $logfile --only-matching | sed -n "s,: [^ ]* / \([^ ]\),\1,p" | perl -ne '$sum += $_ } { print $sum')
    tests_failures=$(grep -P 'odoo\.tests\.(runner|result): \K([[:digit:]]+)' $logfile --only-matching)
    quilt_tests_failures=$(grep -P ': ([[:digit:]]+) / [[:digit:]]+ tests failed.' $logfile --only-matching | sed -n "s,: \([^ ]*\) /.*,\1,p" | perl -ne '$sum += $_ } { print $sum')
    tests_errors=$(grep -P 'odoo\.tests\.(runner|result): ([[:digit:]]+) failed, \K[[:digit:]]+' $logfile -o)
    import_errors_number=0
  else
    # Valid with odoo 7, 11, 13
    tests_ran=$(grep -P 'o(penerp|doo).(modules.module|[^ ]+): Ran \K([[:digit:]]+)' $logfile -o | perl -ne '$sum += $_ } { print $sum')
    if [ -z "$tests_ran" ];
    then
      tests_ran=0
    fi
    tests_failures=$(grep -P 'o(penerp|doo).(modules.module|[^ ]+): Module [a-zA-Z0-9._-]+: \K([[:digit:]]+)' $logfile -o | perl -ne '$sum += $_ } { print $sum')
    # that might be doable in the perl above
    if [ -z "$tests_failures" ];
    then
      tests_failures=0
    fi
    tests_errors=$(grep -P 'o(penerp|doo).(modules.module|[^ ]+): Module [a-z0-9_]+: [[:digit:]]+ failures, \K([[:digit:]]+)' $logfile -o | perl -ne '$sum += $_ } { print $sum')
    if [ -z "$tests_errors" ];
    then
      tests_errors=0
    fi
    import_errors=$(grep -P 'o(penerp|doo).(modules.module|[^ ]+): Can not `import [a-z0-9_]+`.' $logfile)
    import_errors_number=$(grep -P 'o(penerp|doo).(modules.module|[^ ]+): Can not `import [a-z0-9_]+`.' $logfile -c)
    if [[ "$import_errors_number" -ne 0 ]];
    then
      test_errors=$((test_errors+import_errors_number))
    fi
  fi
  # create an OpenMetrics metrics file
  if [ "$CI" = "true" ];
  then
    echo -e "odoo_scripts_tests_ran $tests_ran\nodoo_scripts_tests_failures $tests_failures\nodoo_scripts_tests_error $tests_error\n# EOF\n" > metrics
  fi
  if [ "$ODOO_TYPE" = 'odoo7' ] || [ "$ODOO_TYPE" = 'odoo8' ] || [ "$ODOO_TYPE" = 'odoo9' ] || [ "$ODOO_TYPE" = 'odoo10' ] || [ "$ODOO_TYPE" = 'odoo11' ];
  then
    ok=$(grep ' OK' $logfile -c)
  else
    ok=0
  fi
  # odoo 13 uses FAIL as the error message, in the form: FAIL: TestReorderingRule.test_reordering_rule
  failed=$(grep ' FAIL\(ED\|:\)\| CRITICAL\| ERROR [^ ]* openerp.modules.module: Can not .import openerp\.addons\.\| TEST .* ERROR:\|Failed to initialize database\| ERROR \([[:alnum:]]\|_\)* .\+\.test_.\+ ERROR: \|invalid module names, ignored:' $logfile -c)
  if [[ -n "$import_errors_number" ]];
  then
    failed=$((failed+$import_errors_number))
  fi
  warnings=$(grep ' WARNING ' $logfile -c)
  # XXX Not sure this is needed anymore with the tests_failures/tests_errors detection
  if [[ $failed -eq 0 ]];
  then
    # simple way to detect yaml failure
    failed=$(grep 'o(penerp|doo).modules.loading: At least one test failed when loading the modules.' $logfile -c)
  fi
  echo ''
  echo "${esc}[9m-----$RESET ${esc}[7m${esc}[1mTest results$RESET ${esc}[9m-----$RESET"
  printf "$tests_failures failed, $tests_errors error(s) of $tests_ran tests"
  if [ "$ODOO_TYPE" = 'odoo7' ] || [ "$ODOO_TYPE" = 'odoo8' ] || [ "$ODOO_TYPE" = 'odoo9' ] || [ "$ODOO_TYPE" = 'odoo10' ];
  then
    echo " ($ok modules passing)"
  else
    if [ "$ODOO_TYPE" = 'odoo11' ];
    then
      echo " ($ok test files passing)"
    else
      echo ''
    fi
  fi
  echo ''

  if [[ $failed -gt 0 ]];
  then
    echo -e "${RED}Failures${RESET}:"

    # Highlight the failure matches in red (31).
    GREP_COLORS='mt=0;31' grep "CRITICAL\|FAIL\(ED\|:\)" $logfile
    GREP_COLORS='mt=0;31' grep --color=always "ERROR [^ ]* openerp\.tools\.yaml_import:" $logfile
    GREP_COLORS='mt=0;31' grep --color=always "ERROR [^ ]* openerp.modules.module: Can not \`import openerp\.addons\..*\`" $logfile
    # this one is for odoo 7
    GREP_COLORS='mt=0;31' grep --color=always "TEST .* ERROR:" $logfile
    # this one is for odoo 13 for tests that produces errors (failures are matched with FAIL: )
    GREP_COLORS='mt=0;31' grep --color=always "ERROR [^ ]* .\+\.test_.\+ ERROR:" $logfile
    GREP_COLORS='mt=0;31' grep --color=always "invalid module names, ignored" $logfile
    if [[ -n "$import_errors" ]];
    then
      echo "$import_errors"
    fi
  fi

  echo ''
  if [[ $tests_failures -gt 0 ]] || [[ $tests_errors -gt 0 ]];
  then
    echo "$FATAL Tests failed (found failure and errors in Ran N failed/failures, M errors)"
    return 42
  fi
  if [[ $failed -gt 0 ]];
  then
    echo "$FATAL Tests failed (found failure with grep)"
    return 20
  fi
  if [[ "${FAIL_ON_WARNING:-False}" != "False" ]];
  then
    if [[ $warnings -gt 0 ]];
    then
      echo "$FATAL Tests failed ($warnings WARNING found)"
      return 2
    fi
  fi
  return 0
}

# Make ODOO_TYPE available to files that sources this file.
ODOO_TYPE=${ODOO_TYPE:-$(read_odoo_scripts_configuration_key odoo_type odoo8)}