Commit 24dd437d authored by Charles Ferguson's avatar Charles Ferguson
Browse files

Update CI scripts with more flexible configuration

parent bb26119f
*.swp
*.pyc
build-docs
*.pyo
/build-docs
/.project
venv
venv3
.coverage
/venv
/venv3
/perllib
/output_*
/.coverage
/cover_db
htmlcov
cover
coverage.xml
nosetests.xml
/test_results
.noseids
__pycache__
# Definition for CI within GitLab
# Note:
# Prefix any job name with a '.' to disable.
# Default caching paths, between jobs
cache:
paths:
- perllib
- venv
- venv3
#### Build Jobs ####
lint:
stage: build
script:
- bash scripts/lint
- bash scripts/run lint
docs:
stage: build
script:
- bash scripts/run docs
artifacts:
paths:
- build-docs
python2:
#### Test Jobs ####
test:
stage: test
script:
- bash scripts/test
- bash scripts/run test
dependencies: []
python3:
test-python3:
stage: test
script:
- bash scripts/test -3
- bash scripts/run test -3
dependencies: []
docs:
stage: build
coverage:
stage: test
script:
- bash scripts/docs
- bash scripts/run coverage
coverage: '/^Overall Coverage: (\d+(?:\.\d+)?)%.*$/'
dependencies: []
artifacts:
paths:
- build-docs
- test_results
#### Stages to execute ####
stages:
- build
- test
# Configuration for this project's test, etc
# coverage_limit
# version
# version_git_tag
# {python,perl}_enabled
# <action>_{python,perl}_tool
# test_{python,perl}_files
# lint_{python,perl}_files
# docs_{python,perl}_files
# coverage_{python,perl}_ignore
......@@ -5,7 +5,14 @@
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# I want pylint to report the files as it processes them.
# This way, we can see which files are actually being handled.
# However, there does not appear to be any switch to do this.
# So instead, we have this horrible hack where we insert a new
# function into the linter which will print out the modules
# being processed.
init-hook=exec("_x=PyLinter.set_current_module\ndef _new_set_module(self, module, file=None, oldfunc=_x):\n print('Checking %s%s' % (module, ' ('+file+')' if file else ''))\n return oldfunc(self, module, file)\n\nPyLinter.set_current_module = _new_set_module")
# Profiled execution.
profile=no
......@@ -54,7 +61,7 @@ enable=all
# Disable all messages we disagree with.
# N.B. Although the documentation suggests we can safely use multiple lines for 'enable' and 'disable' keywords, this is
# not true: they must all be listed on one long line.
disable=missing-docstring, too-few-public-methods, too-many-locals, too-many-branches, too-many-public-methods, fixme, too-many-statements, too-many-return-statements, too-many-instance-attributes, too-many-arguments, too-many-lines, invalid-name, locally-disabled, suppressed-message, superfluous-parens
disable=missing-docstring, too-few-public-methods, too-many-locals, too-many-branches, too-many-public-methods, fixme, too-many-statements, too-many-return-statements, too-many-instance-attributes, too-many-arguments, too-many-lines, invalid-name, locally-disabled, suppressed-message, superfluous-parens, file-ignored
# TODO We need to re-enable the following checks at some point:
# - bad-continuation
......@@ -376,6 +383,5 @@ int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
#!/bin/false /bin/bash
##
# Common functions used by the test code.
#
set -eo pipefail
scripts="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd -P)"
root="$(cd "$(dirname "${scripts}")" > /dev/null && pwd -P)"
##
# Convert a string to a boolean return code
function bool() {
local value="$1"
if [[ '-yes-true-on-1-enable-enabled-' =~ -${value,,*}- ]] ; then
return 0
elif [[ '-no-false-off-0-disable-disabled-' =~ -${value,,*}- ]] ; then
return 1
fi
# Put a warning out on stderr so that we know.
echo "Boolean parameter '$1' not recognised" >&2
return 1
}
##
# Read a configuration parameter from the project's config.
#
# Option '-e' can be used to expand wildcards.
# Option '-b' can be used to return a boolean state for the string.
#
# @param $1 The key to read from the configuration file
# @param $2 The default value to return if the config is not set
function config() {
if [ "$1" = '-e' ] ; then
shift
expand_filenames "$(config "$@")"
return
elif [ "$1" = '-b' ] ; then
shift
bool "$(config "$@")"
return
fi
local key="$1"
local default="${2:-}"
if [[ ! -f "${root}/project.config" ]] ; then
# No configuration file
echo "${default}"
elif grep -q "^${key}: \?" "${root}/project.config" ; then
# Key found, so we can return it
grep "^${key}: \?" "${root}/project.config" | sed "s/^${key}: \?//" || true
else
# Key not found in configuration file
echo "${default}"
fi
}
##
# Expand a set of filenames from wildcards.
#
# @param $@ filenames with wildcards
function expand_filenames() {
(
# If nothing matches a wildcard string, return empty
shopt -s nullglob
# Allow '**' to be a subdirectory match
shopt -s globstar
eval echo "$*"
)
}
##
# Find the tool configuration for a given action.
#
# Most actions require a particular tool to be used.
# These can be configured in the project configuration file and will
# fall back to the language configuration.#
#
# project.config:
# <action>_<language>_tool
# common.<language> variable:
# <language>_tool_<action>
#
# The ordering of names it in line with each file's definitions,
# based on how the files are to be used.
#
# Commonly, the tool configuration will contain either:
# <module-name>
# OR
# <module-name> <version>
#
# The expansion of these names can be performed with the function
# 'tool_config_expansion'.
function tool_expansion() {
local lang="$1"
local action="$2"
local toolvar="${lang}_tool_${action}"
local default="${!toolvar}"
config "${action}_${lang}_tool" "$default"
}
##
# Return the configuration for the tool in use for a given action.
#
# The actions may use different tools (eg 'nose' or 'unittest' for
# the 'test' action, in python), with different versions. Each version
# requires a different configuration.
#
# This function will expand the configuration given by the user (or the
# default) and return configuration entries.
#
# Given the configuration value for a <language>/<action> combination
# (see the 'tool_expansion' function), this function expands the variables
# to give the specific configuration.
#
# If the configuration contains a string previxed by equal ('='), the
# following content is considered the literal configuration to return.
#
# If only a single module name was given in the configuration, then the
# default tool version is used.
# This default tool version is looked up in the environment variable
# <language>_toolversion_<module>
#
# If the version number was given in the configuration, it is used
# verbatim.
#
# The configuration itself resides in:
# <language>_tool_<module>_<version-with-underscores>
# The version has periods ('.') replaced with underscores ('_').
# The content of this variable is returned.
function tool_config_expansion() {
local lang="$1"
local action="$2"
local toolpair="$(tool_expansion "$lang" "$action")"
if [[ "$toolpair" == '' ]] ; then
echo "Tool configuration for language '${lang}', action '${action}' is not known" >&2
exit 1
fi
if [[ "${toolpair:0:1}" == '=' ]] ; then
# They specified a literal configuration
echo "${toolpair:1}"
return 0
fi
local tool
local version
if [[ "$toolpair" =~ \ ]] ; then
tool="${toolpair%% *}"
version="${toolpair#* }"
else
tool="$toolpair"
local var="${lang}_toolversion_${tool}"
version="${!var}"
if [[ "${version}" == '' ]] ; then
echo "Default version for language '${lang}' tool '${tool}' is not known" >&2
exit 1
fi
fi
local confvar="${lang}_tool_${tool}_${version//./_}"
echo "${!confvar}"
return 0
}
##
# Check if a named function exists
#
# @param $1 function name to check
function is_function() {
local funcname="$1"
if [ "$(type -t "$funcname")" == 'function' ] ; then
return 0
fi
return 1
}
##
# Call the named function if it exists.
#
# @param $1 function name to call
function call_if_function() {
local funcname="$1"
shift
if is_function "$funcname" ; then
"$funcname" "$@"
fi
}
function all_setup_test() {
local running="${1:-tests}"
echo ">>> Running ${running}"
tests_passed=0
tests_failed=0
tests_total=0
}
function all_setup_coverage() {
all_setup_test coverage
coverage_percentage='<unknown>'
coverage_limit=$(config coverage_limit 0)
}
function all_setup_lint() {
echo '>>> Lint code'
lint_passed=0
lint_failed=0
lint_total=0
}
function all_setup_docs() {
echo '>>> Building documentation'
docs_passed=0
docs_failed=0
docs_total=0
}
function all_end_test() {
if [[ "$tests_failed" != "0" ]] ; then
echo "<<< Tests FAILED ($tests_failed test files failed, $tests_passed test files passed)"
exit 1
fi
echo "<<< Tests passed ($tests_passed test files)"
exit 0
}
function all_end_coverage() {
echo "Overall Coverage: ${coverage_percentage}% (limit is ${coverage_limit}%)"
if [[ "${coverage_percentage}" != '<unknown>' && \
"${coverage_percentage%.*}" -lt "${coverage_limit}" ]] ; then
echo "<<< Coverage failed (${coverage_percentage%.*}% < ${coverage_limit}%)"
exit 1
fi
echo "<<< Coverage passed"
exit 0
}
function all_end_lint() {
if [[ "$lint_failed" != "0" ]] ; then
echo "<<< Lint FAILED ($lint_failed checks failed, $lint_passed checks passed)"
exit 1
fi
echo "<<< Lint passed ($lint_passed checks)"
exit 0
}
function all_end_docs() {
if [[ "$docs_failed" != "0" ]] ; then
echo '<<< Documentation failed'
exit 1
fi
echo '<<< Documentation built'
exit 0
}
##
# Provide help messages.
#
# @param $1 Name of the action, or '' if none
function help_message() {
local action="$1"
local global_message
local action_message
if [[ "$action" == '' ]] ; then
echo "Syntax: $0 <global-options> {${actions// /|}} <action-options>"
else
echo "Syntax: $0 <global-options> $action <action-options>"
fi
global_message=$'Global options:\n'
for lang in $languages ; do
if ! active "${lang}" "${action}" ; then
continue
fi
if is_function "${lang}_help_any" ; then
echo -n "$global_message"
global_message=''
"${lang}_help_any" "$action" | sed 's/^/ /'
fi
done
if [[ "$action" != '' ]] ; then
action_message=$'\nOptions for action '"'$action'"$':\n'
for lang in $languages ; do
if ! active "${lang}" "${action}" ; then
continue
fi
if is_function "${lang}_help_${action}" ; then
echo -n "$action_message"
action_message=''
"${lang}_help_${action}" "$action" | sed 's/^/ /'
fi
done
fi
exit 0
}
##
# Check if a language/action combination is active or not
#
# @param $1 Language to check
# @param $2 Action to check, or '' to check if any active
function active() {
local lang="$1"
local action="$2"
if ! config -b "${lang}_enabled" 'true' ; then
return 1
fi
if [[ "$action" != '' ]] ; then
"${lang}_active" "$action"
return
fi
for action in $actions ; do
if "${lang}_active" "$action" ; then
return 0
fi
done
return 1
}
##
# Execute one of the actions based on the languages and activations
# we have.
#
# @param $1 Name of the action ('test', 'coverage', 'docs', 'lint')
function run_action() {
local action
local opt
local value
local optfunc
local handled
# Process any additional arguments that we were passed.
local next_param=false
for param in "$@" ; do
if "$next_param" "$param" ; then
next_param=false
continue
fi
if [[ "${param:0:1}" == '-' ]] ; then
if [[ "${param:0:2}" == '--' ]] ; then
param="${param:2}"
else
param="${param:1}"
fi
if [[ "${param}" == 'h' || "${param}" == 'help' ]] ; then
# Help is special, as we'll call each language's functions
help_message "$action"
exit 0
else
handled=false
if [[ "$param" =~ = ]] ; then
value=${param#*=}
opt=${param%%=*}
else
value=''
opt=${param}
fi
optfunc=${opt//-/_}
for lang in $languages ; do
if ! active "$lang" "$action" ; then
continue
fi
if [[ -z "$value" ]] ; then
if is_function "${lang}_switch_any_${optfunc}" ; then
"${lang}_switch_any_${optfunc}" "$action"
handled=true
elif is_function "${lang}_switch_${action}_${optfunc}" ; then
"${lang}_switch_${action}_${optfunc}" "$action"
handled=true
elif is_function "${lang}_param_any_${optfunc}" ; then
next_param="${lang}_param_any_${optfunc}"
handled=true
elif is_function "${lang}_param_${action}_${optfunc}" ; then
next_param="${lang}_param_${action}_${optfunc}"
handled=true
fi
else
# We were given a parameter, so we handle this now
if is_function "${lang}_param_any_${optfunc}" ; then
"${lang}_param_any_${optfunc}" "${value}"
handled=true
elif is_function "${lang}_param_${action}_${optfunc}" ; then
"${lang}_param_${action}_${optfunc}" "${value}"
handled=true
fi
fi
done
if ! "$handled" ; then
echo "Option '$opt' is not understood" >&2
exit 1
fi
fi
else
if [[ "$action" == '' ]] ; then
action="$param"
else
echo "Positional parameters are not supported ('$param')" >&2
exit 1
fi
fi
done
if [[ "$action" == '' || "$action" == 'help' ]] ; then
help_message
fi
# Begin the action, and set up any variables needed.
if ! is_function "all_setup_${action}" ; then
echo "Action '${action}' not known" >&2
exit 1
fi
"all_setup_${action}"
for lang in $languages ; do
if active "$lang" "$action" ; then
# 1. Build and use an environment in which the tests can function.
# This may be CPAN, VirtualEnv, or some other specialism.
call_if_function "${lang}_environment_${action}"
# 2. Configure the tests, with environment variables, clear up
# any left-over resources that we do not want.
call_if_function "${lang}_setup_${action}"
# 3. Execute the action, and update the state variables used by
# the 'all_setup_*' functions.
call_if_function "${lang}_run_${action}"
# 4. Process the results of the action, generating reports,
# compressing files, or other operations that are needed.
call_if_function "${lang}_process_${action}"
fi
done
# Report the results of the action, and call exit appropriately.
"all_end_${action}"
}
# Load the scripts for the languages
languages=""
for file in $(expand_filenames "${scripts}"'/common.*') ; do
languages="$languages ${file##*.}"
source "${scripts}/common.${file##*.}"
done
actions="test coverage lint docs"
#!/bin/false /bin/bash
##
# Common functions for Perl.
#
set -eo pipefail
##
# Is perl active for this action?
#
# @param $1 the action we are attempting to perform
function perl_active() {
local action="$1"
local files=''
if [[ "$action" == 'test' || "$action" == 'coverage' ]] ; then
files="$(config -e test_perl_files '*-test.pl')"
elif [[ "$action" == 'lint' ]] ; then
files="$(config -e lint_perl_files '*.pl *.pm')"
else