mirror of
https://github.com/YunoHost/package_check.git
synced 2024-09-03 20:06:20 +02:00
Usage de snapshot pour les installations de base. Réduit la durée des tests en évitant de réinstaller l'application plusieurs fois avec les mêmes paramètres.
1288 lines
36 KiB
Bash
Executable file
1288 lines
36 KiB
Bash
Executable file
#!/bin/bash
|
|
|
|
#=================================================
|
|
# Grab the script directory
|
|
#=================================================
|
|
|
|
if [ "${0:0:1}" == "/" ]; then script_dir="$(dirname "$0")"; else script_dir="$(echo $PWD/$(dirname "$0" | cut -d '.' -f2) | sed 's@/$@@')"; fi
|
|
|
|
#=================================================
|
|
# Starting and checking
|
|
#=================================================
|
|
# Generic functions
|
|
#=================================================
|
|
|
|
clean_exit () {
|
|
# Exit and remove all temp files
|
|
# $1 = exit code
|
|
|
|
# Deactivate LXC network
|
|
LXC_TURNOFF
|
|
|
|
# Remove temporary files
|
|
rm -f "$temp_log"
|
|
rm -f "$temp_result"
|
|
rm -f "$script_dir/url_output"
|
|
rm -f "$script_dir/curl_print"
|
|
rm -f "$script_dir/manifest_extract"
|
|
|
|
# Remove the application which been tested
|
|
if [ -n "$package_path" ]; then
|
|
rm -rf "$package_path"
|
|
fi
|
|
|
|
# Remove the lock file
|
|
rm -f "$lock_file"
|
|
|
|
exit $1
|
|
}
|
|
|
|
#=================================================
|
|
# Check and read CLI arguments
|
|
#=================================================
|
|
|
|
echo ""
|
|
|
|
# Init arguments value
|
|
gitbranch=""
|
|
force_install_ok=0
|
|
interrupt=0
|
|
notice=0
|
|
build_lxc=0
|
|
bash_mode=0
|
|
|
|
# If no arguments provided
|
|
if [ "$#" -eq 0 ]
|
|
then
|
|
# Print the help and exit
|
|
notice=1
|
|
else
|
|
# Reduce the arguments for getopts
|
|
arguments="$*"
|
|
arguments=${arguments//--branch=/-b }
|
|
arguments=${arguments//--force-install-ok/-f}
|
|
arguments=${arguments//--interrupt/-i}
|
|
arguments=${arguments//--help/-h}
|
|
arguments=${arguments//--build-lxc/-l}
|
|
arguments=${arguments//--bash-mode/-y}
|
|
|
|
# Read and parse all the arguments
|
|
# Use a function here, to use standart arguments $@ and use more simply getopts and shift.
|
|
parse_arg () {
|
|
while [ $# -ne 0 ]
|
|
do
|
|
# Initialize the index of getopts
|
|
OPTIND=1
|
|
# Parse with getopts only if the argument begin by -
|
|
if [ ${1:0:1} = "-" ]
|
|
then
|
|
getopts ":b:fihly " parameter
|
|
case $parameter in
|
|
b)
|
|
# --branch=branch-name
|
|
gitbranch="$OPTARG"
|
|
;;
|
|
f)
|
|
# --force-install-ok
|
|
force_install_ok=1
|
|
;;
|
|
i)
|
|
# --interrupt
|
|
interrupt=1
|
|
;;
|
|
h)
|
|
# --help
|
|
notice=1
|
|
;;
|
|
l)
|
|
# --build-lxc
|
|
build_lxc=1
|
|
;;
|
|
y)
|
|
# --bash-mode
|
|
bash_mode=1
|
|
;;
|
|
\?)
|
|
echo "Invalid argument: -$OPTARG" >&2
|
|
notice=1
|
|
;;
|
|
:)
|
|
echo "-$OPTARG parameter requires an argument." >&2
|
|
notice=1
|
|
;;
|
|
esac
|
|
else
|
|
app_arg="$1"
|
|
fi
|
|
shift
|
|
done
|
|
}
|
|
|
|
# Call parse_arg and pass the modified list of args.
|
|
parse_arg $arguments
|
|
fi
|
|
|
|
# Prevent a conflict between --interrupt and --bash-mode
|
|
if [ $interrupt -eq 1 ] && [ $bash_mode -eq 1 ]
|
|
then
|
|
echo "You can't use --interrupt and --bash-mode together !"
|
|
notice=1
|
|
fi
|
|
|
|
# Print help
|
|
if [ $notice -eq 1 ]
|
|
then
|
|
cat << EOF
|
|
|
|
Usage:
|
|
package_check.sh [OPTION]... PACKAGE_TO_CHECK
|
|
-b, --branch=BRANCH
|
|
Specify a branch to check.
|
|
-f, --force-install-ok
|
|
Force following test even if all install have failed.
|
|
-i, --interrupt
|
|
Force auto_remove value, break before each remove.
|
|
-h, --help
|
|
Display this notice.
|
|
-l, --build-lxc
|
|
Install LXC and build the container if necessary.
|
|
-y, --bash-mode
|
|
Do not ask for continue check. Ignore auto_remove.
|
|
EOF
|
|
exit 0
|
|
fi
|
|
|
|
#=================================================
|
|
# Check if the lock file exist
|
|
#=================================================
|
|
|
|
lock_file="$script_dir/pcheck.lock"
|
|
|
|
if test -e "$lock_file"
|
|
then
|
|
# If the lock file exist
|
|
echo "The lock file $lock_file is present. Package check would not continue."
|
|
if [ $bash_mode -ne 1 ]; then
|
|
echo -n "Do you want to continue anymore? (y/n) :"
|
|
read answer
|
|
fi
|
|
# Set the answer at lowercase only
|
|
answer=${answer,,}
|
|
if [ "${answer:0:1}" != "y" ]
|
|
then
|
|
echo "Cancel Package check execution"
|
|
exit 0
|
|
fi
|
|
fi
|
|
# Create the lock file
|
|
touch "$lock_file"
|
|
|
|
#=================================================
|
|
# Check the internet connectivity
|
|
#=================================================
|
|
|
|
# Try to ping yunohost.org
|
|
ping -q -c 2 yunohost.org > /dev/null 2>&1
|
|
if [ "$?" -ne 0 ]; then
|
|
# If fail, try to ping another domain
|
|
ping -q -c 2 framasoft.org > /dev/null 2>&1
|
|
if [ "$?" -ne 0 ]; then
|
|
# If ping failed twice, it's seems the internet connection is down.
|
|
echo "\e[91mUnable to connect to internet.\e[0m"
|
|
|
|
# Remove the lock file
|
|
rm -f "$lock_file"
|
|
# And exit
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
#=================================================
|
|
# Upgrade Package check
|
|
#=================================================
|
|
|
|
git_repository=https://github.com/YunoHost/package_check
|
|
version_file="$script_dir/pcheck_version"
|
|
|
|
check_version="$(git ls-remote $git_repository | cut -f 1 | head -n1)"
|
|
|
|
# If the version file exist, check for an upgrade
|
|
if [ -e "$version_file" ]
|
|
then
|
|
# Check if the last commit on the repository match with the current version
|
|
if [ "$check_version" != "$(cat "$version_file")" ]
|
|
then
|
|
# If the versions don't matches. Do an upgrade
|
|
echo -e "\e[97m\e[1mUpgrade Package check...\n\e[0m"
|
|
|
|
# Build the upgrade script
|
|
cat > "$script_dir/upgrade_script.sh" << EOF
|
|
|
|
#!/bin/bash
|
|
# Clone in another directory
|
|
git clone --quiet $git_repository "$script_dir/upgrade"
|
|
cp -a "$script_dir/upgrade/." "$script_dir/."
|
|
rm -r "$script_dir/upgrade"
|
|
# Update the version file
|
|
echo "$check_version" > "$version_file"
|
|
rm "$script_dir/pcheck.lock"
|
|
# Execute package check by replacement of this process
|
|
exec "$script_dir/package_check.sh" "$arguments"
|
|
EOF
|
|
|
|
# Give the execution right
|
|
chmod +x "$script_dir/upgrade_script.sh"
|
|
|
|
# Start the upgrade script by replacement of this process
|
|
exec "$script_dir/upgrade_script.sh"
|
|
fi
|
|
fi
|
|
|
|
# Update the version file
|
|
echo "$check_version" > "$version_file"
|
|
|
|
#=================================================
|
|
# Upgrade Package linter
|
|
#=================================================
|
|
|
|
git_repository=https://github.com/YunoHost/package_linter
|
|
version_file="$script_dir/plinter_version"
|
|
|
|
check_version="$(git ls-remote $git_repository | cut -f 1 | head -n1)"
|
|
|
|
# If the version file exist, check for an upgrade
|
|
if [ -e "$version_file" ]
|
|
then
|
|
# Check if the last commit on the repository match with the current version
|
|
if [ "$check_version" != "$(cat "$version_file")" ]
|
|
then
|
|
# If the versions don't matches. Do an upgrade
|
|
echo -e "\e[97m\e[1mUpgrade Package linter...\n\e[0m"
|
|
|
|
# Clone in another directory
|
|
git clone --quiet https://github.com/YunoHost/package_linter "$script_dir/package_linter_tmp"
|
|
|
|
# And replace
|
|
cp -a "$script_dir/package_linter_tmp/." "$script_dir/package_linter/."
|
|
sudo rm -r "$script_dir/package_linter_tmp"
|
|
fi
|
|
else
|
|
echo -e "\e[97mInstall Package linter.\n\e[0m"
|
|
git clone --quiet $git_repository "$script_dir/package_linter"
|
|
fi
|
|
|
|
# Update the version file
|
|
echo "$check_version" > "$version_file"
|
|
|
|
#=================================================
|
|
# Get variables from the config file
|
|
#=================================================
|
|
|
|
pcheck_config="$script_dir/config"
|
|
build_script="$script_dir/sub_scripts/lxc_build.sh"
|
|
|
|
if [ -e "$pcheck_config" ]
|
|
then
|
|
# Read the config file if it exists
|
|
ip_range=$(grep PLAGE_IP= "$pcheck_config" | cut -d '=' -f2)
|
|
main_domain=$(grep DOMAIN= "$pcheck_config" | cut -d '=' -f2)
|
|
yuno_pwd=$(grep YUNO_PWD= "$pcheck_config" | cut -d '=' -f2)
|
|
lxc_name=$(grep LXC_NAME= "$pcheck_config" | cut -d '=' -f2)
|
|
lxc_bridge=$(grep LXC_BRIDGE= "$pcheck_config" | cut -d '=' -f2)
|
|
main_iface=$(grep iface= "$pcheck_config" | cut -d '=' -f2)
|
|
fi
|
|
|
|
# Use default value from the build script if needed
|
|
if [ -z "$ip_range" ]; then
|
|
ip_range=$(grep "|| PLAGE_IP=" "$build_script" | cut -d '"' -f4)
|
|
echo -e "# Ip range for the container\nPLAGE_IP=$ip_range\n" >> "$pcheck_config"
|
|
fi
|
|
if [ -z "$main_domain" ]; then
|
|
main_domain=$(grep "|| DOMAIN=" "$build_script" | cut -d '=' -f2)
|
|
echo -e "# Test domain\nDOMAIN=$main_domain\n" >> "$pcheck_config"
|
|
fi
|
|
if [ -z "$yuno_pwd" ]; then
|
|
yuno_pwd=$(grep "|| YUNO_PWD=" "$build_script" | cut -d '=' -f2)
|
|
echo -e "# YunoHost password, in the container\nYUNO_PWD=$yuno_pwd\n" >> "$pcheck_config"
|
|
fi
|
|
if [ -z "$lxc_name" ]; then
|
|
lxc_name=$(grep "|| LXC_NAME=" "$build_script" | cut -d '=' -f2)
|
|
echo -e "# Container name\nLXC_NAME=$lxc_name\n" >> "$pcheck_config"
|
|
fi
|
|
if [ -z "$lxc_bridge" ]; then
|
|
lxc_bridge=$(grep "|| LXC_BRIDGE=" "$build_script" | cut -d '=' -f2)
|
|
echo -e "# Bridge name\nLXC_BRIDGE=$lxc_bridge\n" >> "$pcheck_config"
|
|
fi
|
|
|
|
if [ -z "$main_iface" ]; then
|
|
# Try to determine the main iface
|
|
main_iface=$(sudo route | grep default | awk '{print $8;}')
|
|
if [ -z $main_iface ]
|
|
then
|
|
echo -e "\e[91mUnable to find the name of the main iface.\e[0m"
|
|
|
|
# Remove the lock file
|
|
rm -f "$lock_file"
|
|
# And exit
|
|
exit 1
|
|
fi
|
|
# Store the main iface in the config file
|
|
echo -e "# Main host iface\niface=$main_iface\n" >> "$pcheck_config"
|
|
fi
|
|
|
|
#=================================================
|
|
# Check the user who try to execute this script
|
|
#=================================================
|
|
|
|
setup_user_file="$script_dir/sub_scripts/setup_user"
|
|
if [ -e "$setup_user_file" ]
|
|
then
|
|
# Compare the current user and the user stored in $setup_user_file
|
|
authorised_user="$(cat "$setup_user_file")"
|
|
if [ "$(whoami)" != "$authorised_user" ]
|
|
then
|
|
echo -e "\e[91mThis script need to be executed by the user $setup_user_file !\nThe current user is $(whoami).\e[0m"
|
|
clean_exit 1
|
|
fi
|
|
else
|
|
echo -e "\e[93mUnable to define the user who authorised to use package check. Please fill the file $setup_user_file\e[0m"
|
|
fi
|
|
|
|
#=================================================
|
|
# Define globals variables
|
|
#=================================================
|
|
|
|
# Complete result log. Complete log of YunoHost
|
|
complete_log="$script_dir/Complete.log"
|
|
# Partial YunoHost log, just the log for the current test
|
|
temp_log="$script_dir/temp_yunohost-cli.log"
|
|
# Temporary result log
|
|
temp_result="$script_dir/temp_result.log"
|
|
# Result log with warning and error only
|
|
test_result="$script_dir/Test_results.log"
|
|
# Real YunoHost log
|
|
yunohost_log="/var/lib/lxc/$lxc_name/rootfs/var/log/yunohost/yunohost-cli.log"
|
|
|
|
sub_domain="sous.$main_domain"
|
|
test_user=package_checker
|
|
|
|
#=================================================
|
|
# Load all functions
|
|
#=================================================
|
|
|
|
source "$script_dir/sub_scripts/launcher.sh"
|
|
source "$script_dir/sub_scripts/testing_process.sh"
|
|
source "$script_dir/sub_scripts/log_extractor.sh"
|
|
source /usr/share/yunohost/helpers
|
|
|
|
#=================================================
|
|
# Check LXC
|
|
#=================================================
|
|
|
|
# Check if lxc is already installed
|
|
if dpkg-query -W -f '${Status}' "lxc" 2>/dev/null | grep -q "ok installed"
|
|
then
|
|
# If lxc is installed, check if the container is already built.
|
|
if ! sudo lxc-ls | grep -q "$lxc_name"
|
|
then
|
|
if [ $build_lxc -eq 1 ]
|
|
then
|
|
# If lxc's not installed and build_lxc set. Asks to build the container.
|
|
build_lxc=2
|
|
else
|
|
ECHO_FORMAT "LXC is not installed or the container $lxc_name doesn't exist.\n" "red"
|
|
ECHO_FORMAT "Use the script 'lxc_build.sh' to fix them.\n" "red"
|
|
clean_exit 1
|
|
fi
|
|
fi
|
|
elif [ $build_lxc -eq 1 ]
|
|
then
|
|
# If lxc's not installed and build_lxc set. Asks to build the container.
|
|
build_lxc=2
|
|
fi
|
|
|
|
if [ $build_lxc -eq 2 ]
|
|
then
|
|
# Install LXC and build the container before continue.
|
|
"$script_dir/sub_scripts/lxc_build.sh"
|
|
fi
|
|
|
|
# Stop and restore the LXC container. In case of previous incomplete execution.
|
|
LXC_STOP
|
|
# Deactivate LXC network
|
|
LXC_TURNOFF
|
|
|
|
#=================================================
|
|
# Determine if it's a CI environment
|
|
#=================================================
|
|
|
|
# By default, it's a standalone execution.
|
|
type_exec_env=0
|
|
if [ -e "$script_dir/../config" ]
|
|
then
|
|
# CI environment
|
|
type_exec_env=1
|
|
fi
|
|
if [ -e "$script_dir/../auto_build/auto.conf" ]
|
|
then
|
|
# Official CI environment
|
|
type_exec_env=2
|
|
fi
|
|
|
|
#=================================================
|
|
# Pick up the package
|
|
#=================================================
|
|
|
|
echo "Pick up the package which will be tested."
|
|
|
|
# Remove the previous package if it's still here.
|
|
rm -rf "$script_dir"/*_check
|
|
|
|
package_dir="$(basename "$app_arg")_check"
|
|
package_path="$script_dir/$package_dir"
|
|
|
|
# If the package is in a git repository
|
|
if echo "$app_arg" | grep -Eq "https?:\/\/"
|
|
then
|
|
# Clone the repository
|
|
git clone $app_arg $gitbranch "$package_path"
|
|
|
|
# If it's a local directory
|
|
else
|
|
# Do a copy in the directory of Package check
|
|
cp -a "$app_arg" "$package_path"
|
|
fi
|
|
|
|
# Check if the package directory is really here.
|
|
if [ ! -d "$package_path" ]; then
|
|
ECHO_FORMAT "Unable to find the directory $package_path for the package...\n" "red"
|
|
clean_exit 1
|
|
fi
|
|
|
|
# Remove the .git directory.
|
|
rm -rf "$package_path/.git"
|
|
|
|
|
|
|
|
|
|
#=================================================
|
|
# Determine and print the results
|
|
#=================================================
|
|
|
|
TEST_RESULTS () {
|
|
|
|
# Print the test result
|
|
print_result () {
|
|
# Print the name of the test
|
|
ECHO_FORMAT "$1: "
|
|
|
|
# Complete with spaces according to the lenght of the previous string. To align the results
|
|
local i=0
|
|
for i in `seq ${#1} 30`
|
|
do
|
|
echo -n " "
|
|
done
|
|
|
|
# Print the result of this test
|
|
if [ $2 -eq 1 ]
|
|
then
|
|
ECHO_FORMAT "SUCCESS\n" "lgreen"
|
|
elif [ $2 -eq -1 ]
|
|
then
|
|
ECHO_FORMAT "FAIL\n" "red"
|
|
else
|
|
ECHO_FORMAT "Not evaluated.\n" "white"
|
|
fi
|
|
}
|
|
|
|
# Print the result for each test
|
|
echo -e "\n\n"
|
|
print_result "Package linter" $RESULT_linter
|
|
print_result "Installation" $RESULT_global_setup
|
|
print_result "Deleting" $RESULT_global_remove
|
|
print_result "Installation in a sub path" $RESULT_check_sub_dir
|
|
print_result "Deleting from a sub path" $RESULT_check_remove_sub_dir
|
|
print_result "Installation on the root" $RESULT_check_root
|
|
print_result "Deleting from root" $RESULT_check_remove_root
|
|
print_result "Upgrade" $RESULT_check_upgrade
|
|
print_result "Installation in private mode" $RESULT_check_private
|
|
print_result "Installation in public mode" $RESULT_check_public
|
|
print_result "Multi-instance installations" $RESULT_check_multi_instance
|
|
print_result "Malformed path" $RESULT_check_path
|
|
print_result "Port already used" $RESULT_check_port
|
|
print_result "Backup" $RESULT_check_backup
|
|
print_result "Restore" $RESULT_check_restore
|
|
print_result "Change URL" $RESULT_change_url
|
|
|
|
|
|
|
|
# Determine the level for this app
|
|
|
|
# Each level can has 5 different values
|
|
# 0 -> If this level can't be validated
|
|
# 1 -> If this level is forced. Even if the tests fails
|
|
# 2 -> Indicates the tests had previously validated this level
|
|
# auto -> This level has not a value yet.
|
|
# na -> This level will not checked, but it'll be ignored in the final sum
|
|
|
|
# Set default values for level, if they're empty.
|
|
test -n "${level[1]}" || level[1]=auto
|
|
test -n "${level[2]}" || level[2]=auto
|
|
test -n "${level[3]}" || level[3]=auto
|
|
test -n "${level[4]}" || level[4]=0
|
|
test -n "${level[5]}" || level[5]=auto
|
|
test -n "${level[6]}" || level[6]=auto
|
|
test -n "${level[7]}" || level[7]=auto
|
|
test -n "${level[8]}" || level[8]=0
|
|
test -n "${level[9]}" || level[9]=0
|
|
test -n "${level[10]}" || level[10]=0
|
|
|
|
# Check if the level can be changed
|
|
level_can_change () {
|
|
# If the level is set at auto, it's waiting for a change
|
|
# And if it's set at 2, its value can be modified by a new result
|
|
if [ "${level[$1]}" == "auto" ] || [ "${level[$1]}" -eq 2 ]
|
|
then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Evaluate the first level
|
|
# -> The package can be install and remove.
|
|
if level_can_change 1
|
|
then
|
|
# Validated if at least one install and remove work fine
|
|
if [ $RESULT_global_setup -eq 1 ] && \
|
|
[ $RESULT_global_remove -eq 1 ]
|
|
then level[1]=2
|
|
else level[1]=0
|
|
fi
|
|
fi
|
|
|
|
# Evaluate the second level
|
|
# -> The package can be install and remove in all tested configurations.
|
|
if level_can_change 2
|
|
then
|
|
# Validated if none install failed
|
|
if [ $RESULT_check_sub_dir -ne -1 ] && \
|
|
[ $RESULT_check_remove_sub_dir -ne -1 ] && \
|
|
[ $RESULT_check_root -ne -1 ] && \
|
|
[ $RESULT_check_remove_root -ne -1 ] && \
|
|
[ $RESULT_check_private -ne -1 ] && \
|
|
[ $RESULT_check_public -ne -1 ] && \
|
|
[ $RESULT_check_multi_instance -ne -1 ]
|
|
then level[2]=2
|
|
else level[2]=0
|
|
fi
|
|
fi
|
|
|
|
# Evaluate the third level
|
|
# -> The package can be upgraded from the same version.
|
|
if level_can_change 3
|
|
then
|
|
# Validated if the upgrade is ok. Or if the upgrade has been not tested but already validated before.
|
|
if [ $RESULT_check_upgrade -eq 1 ] || \
|
|
( [ $RESULT_check_upgrade -ne -1 ] && \
|
|
[ "${level[3]}" == "2" ] )
|
|
then level[3]=2
|
|
else level[3]=0
|
|
fi
|
|
fi
|
|
|
|
# Evaluate the fifth level
|
|
# -> The package have no error with package linter
|
|
if level_can_change 5
|
|
then
|
|
# Validated if Linter is ok. Or if Linter has been not tested but already validated before.
|
|
if [ $RESULT_linter -eq 1 ] || \
|
|
( [ $RESULT_linter -ne -1 ] && \
|
|
[ "${level[5]}" == "2" ] )
|
|
then level[5]=2
|
|
else level[5]=0
|
|
fi
|
|
fi
|
|
|
|
# Evaluate the sixth level
|
|
# -> The package can be backup and restore without error
|
|
if level_can_change 6
|
|
then
|
|
# Check the YEP 1.7 (https://github.com/YunoHost/doc/blob/master/packaging_apps_guidelines_fr.md#yep-17---ajouter-lapp-%C3%A0-lorganisation-yunohost-apps---valid%C3%A9--manuel--official-)
|
|
# Default value, YEP 1.7 not checked
|
|
YEP17=-1
|
|
if echo "$app_arg" | grep --extended-regexp --quiet "https?:\/\/"
|
|
then
|
|
# If the app have been picked from github, check if this app was under the YunoHost-Apps organisation
|
|
# YEP17 will be equal to 1 if the app was under the YunoHost-Apps organisation
|
|
YEP17=$(echo "$app_arg" | grep --ignore-case --count "github.com/YunoHost-Apps/")
|
|
[ $YEP17 -eq 1 ] || ECHO_FORMAT "This app doesn't respect the YEP 1.7 ! (https://yunohost.org/#/packaging_apps_guidelines_fr)\n" "red"
|
|
fi
|
|
|
|
# Validated if backup and restore are ok. Or if backup and restore have been not tested but already validated before.
|
|
if ( [ $RESULT_check_backup -eq 1 ] && \
|
|
[ $RESULT_check_restore -eq 1 ] ) || \
|
|
( [ $RESULT_check_backup -ne -1 ] && \
|
|
[ $RESULT_check_restore -ne -1 ] && \
|
|
[ "${level[6]}" == "2" ] ) && \
|
|
[ $YEP17 -ne 0 ]
|
|
then level[6]=2
|
|
else level[6]=0
|
|
fi
|
|
fi
|
|
|
|
# Evaluate the seventh level
|
|
# -> None errors in all tests performed
|
|
if level_can_change 7
|
|
then
|
|
# Validated if none errors is happened.
|
|
if [ $RESULT_global_setup -ne -1 ] && \
|
|
[ $RESULT_global_remove -ne -1 ] && \
|
|
[ $RESULT_check_sub_dir -ne -1 ] && \
|
|
[ $RESULT_check_remove_sub_dir -ne -1 ] && \
|
|
[ $RESULT_check_remove_root -ne -1 ] && \
|
|
[ $RESULT_check_upgrade -ne -1 ] && \
|
|
[ $RESULT_check_private -ne -1 ] && \
|
|
[ $RESULT_check_public -ne -1 ] && \
|
|
[ $RESULT_check_multi_instance -ne -1 ] && \
|
|
[ $RESULT_check_path -ne -1 ] && \
|
|
[ $RESULT_check_port -ne -1 ] && \
|
|
[ $RESULT_check_backup -ne -1 ] && \
|
|
[ $RESULT_check_restore -ne -1 ] && \
|
|
[ $RESULT_change_url -ne -1 ]
|
|
then level[7]=2
|
|
else level[7]=0
|
|
fi
|
|
fi
|
|
|
|
# Initialize the global level
|
|
global_level=0
|
|
|
|
# Calculate the final level
|
|
for i in `seq 1 10`
|
|
do
|
|
|
|
# If there is a level still at 'auto', it's a mistake.
|
|
if [ "${level[i]}" == "auto" ]
|
|
then
|
|
# So this level will set at 0.
|
|
level[i]=0
|
|
|
|
# If the level is at 'na', it will be ignored
|
|
elif [ "${level[i]}" == "na" ]
|
|
then
|
|
continue
|
|
|
|
# If the level is at 1 or 2. The global level will be set at this level
|
|
elif [ "${level[i]}" -ge 1 ]
|
|
then
|
|
global_level=$i
|
|
|
|
# But, if the level is at 0, the loop stop here
|
|
# Like that, the global level rise while none level have failed
|
|
else
|
|
break
|
|
fi
|
|
done
|
|
|
|
# If some witness files was missing, it's a big error ! So, the level fall immediately at 0.
|
|
if [ $RESULT_witness -eq 1 ]
|
|
then
|
|
ECHO_FORMAT "Some witness files has been deleted during those tests ! It's a very bad thing !\n" "red" "bold"
|
|
global_level=0
|
|
fi
|
|
|
|
|
|
# Then, print the levels
|
|
# Print the global level
|
|
ECHO_FORMAT "Level of this application: $global_level\n" "white" "bold"
|
|
|
|
# And print the value for each level
|
|
for i in `seq 1 10`
|
|
do
|
|
ECHO_FORMAT "\t Level $i: "
|
|
if [ "${level[$i]}" == "na" ]; then
|
|
ECHO_FORMAT "N/A\n"
|
|
elif [ "${level[$i]}" -ge 1 ]; then
|
|
ECHO_FORMAT "1\n" "white" "bold"
|
|
else
|
|
ECHO_FORMAT "0\n"
|
|
fi
|
|
done
|
|
}
|
|
|
|
#=================================================
|
|
# Parsing and performing tests
|
|
#=================================================
|
|
# Check if a check_process file exist
|
|
#=================================================
|
|
|
|
check_file=1
|
|
check_process="$package_path/check_process"
|
|
|
|
if [ ! -e "$check_process" ]
|
|
then
|
|
ECHO_FORMAT "\nUnable to find a check_process file.\n" "red"
|
|
ECHO_FORMAT "Package check will be used in lower mode.\n" "lyellow"
|
|
check_file=0
|
|
fi
|
|
|
|
#=================================================
|
|
# Initialize tests
|
|
#=================================================
|
|
|
|
# Purge some log files
|
|
> "$complete_log"
|
|
> "$test_result"
|
|
> "$script_dir/lxc_boot.log"
|
|
|
|
# Initialize LXC network
|
|
LXC_INIT
|
|
|
|
# Default values for check_process and TESTING_PROCESS
|
|
initialize_values() {
|
|
# Test results
|
|
RESULT_witness=0
|
|
RESULT_linter=0
|
|
RESULT_global_setup=0
|
|
RESULT_global_remove=0
|
|
RESULT_check_sub_dir=0
|
|
RESULT_check_root=0
|
|
RESULT_check_remove_sub_dir=0
|
|
RESULT_check_remove_root=0
|
|
RESULT_check_upgrade=0
|
|
RESULT_check_backup=0
|
|
RESULT_check_restore=0
|
|
RESULT_check_private=0
|
|
RESULT_check_public=0
|
|
RESULT_check_multi_instance=0
|
|
RESULT_check_path=0
|
|
RESULT_check_port=0
|
|
RESULT_change_url=0
|
|
|
|
# auto_remove parameter
|
|
if [ $interrupt -eq 1 ]; then
|
|
auto_remove=0
|
|
else
|
|
auto_remove=1
|
|
fi
|
|
|
|
# Number of tests to proceed
|
|
all_test=0
|
|
|
|
# Default path
|
|
test_path=/check
|
|
}
|
|
|
|
#=================================================
|
|
# Parse the check_process
|
|
#=================================================
|
|
|
|
# Parse the check_process only if it's exist
|
|
if [ $check_file -eq 1 ]
|
|
then
|
|
ECHO_FORMAT "Parsing of check_process file\n"
|
|
|
|
# Remove all commented lines in the check_process
|
|
sed --in-place '/^#/d' "$check_process"
|
|
# Remove all spaces at the beginning of the lines
|
|
sed --in-place 's/^[ \t]*//g' "$check_process"
|
|
|
|
# Check if a string can be find in the current line
|
|
check_line () {
|
|
return $(echo "$line" | grep -q "$1")
|
|
}
|
|
|
|
# Search a string in the partial check_process
|
|
find_string () {
|
|
echo $(grep "$1" "$partial_check_process")
|
|
}
|
|
|
|
# Extract a section found between $1 and $2 from the file $3
|
|
extract_section () {
|
|
# Erase the partial check_process
|
|
> "$partial_check_process"
|
|
local source_file="$3"
|
|
local extract=0
|
|
local line=""
|
|
while read line
|
|
do
|
|
# Extract the line
|
|
if [ $extract -eq 1 ]
|
|
then
|
|
# Check if the line is the second line to found
|
|
if check_line "$2"; then
|
|
# Break the loop to finish the extract process
|
|
break;
|
|
fi
|
|
# Copy the line in the partial check_process
|
|
echo $line >> "$partial_check_process"
|
|
fi
|
|
|
|
# Search for the first line
|
|
if check_line "$1"; then
|
|
# Activate the extract process
|
|
extract=1
|
|
fi
|
|
done < "$source_file"
|
|
}
|
|
|
|
# Use 2 partial files, to keep one for a whole tests serie
|
|
partial1="${check_process}_part1"
|
|
partial2="${check_process}_part2"
|
|
|
|
|
|
# Extract the level section
|
|
partial_check_process=$partial1
|
|
extract_section "^;;; Levels" ";; " "$check_process"
|
|
|
|
# Get the value associated to each level
|
|
for i in `seq 1 10`
|
|
do
|
|
# Find the line for this level
|
|
line=$(find_string "^Level $i=")
|
|
# And get the value
|
|
level[$i]=$(echo "$line" | cut -d'=' -f2)
|
|
done
|
|
|
|
|
|
# Extract the Options section
|
|
partial_check_process=$partial1
|
|
extract_section "^;;; Options" ";; " "$check_process"
|
|
|
|
# Try to find a optionnal email address to notify the maintainer
|
|
# In this case, this email will be used instead of the email from the manifest.
|
|
dest="$(echo $(find_string "^Email=") | cut -d '=' -f2)"
|
|
|
|
# Try to find a optionnal option for the grade of notification
|
|
notification_grade="$(echo $(find_string "^Notification=") | cut -d '=' -f2)"
|
|
|
|
|
|
# Parse each tests serie
|
|
while read tests_serie
|
|
do
|
|
|
|
# Initialize the values for this serie of tests
|
|
initialize_values
|
|
|
|
# Break after the first tests serie
|
|
if [ $all_test -ne 0 ] && [ $bash_mode -ne 1 ]; then
|
|
read -p "Press a key to start the next tests serie..." < /dev/tty
|
|
fi
|
|
|
|
# Use the second file to extract the whole section of a tests serie
|
|
partial_check_process=$partial2
|
|
|
|
# Extract the section of the current tests serie
|
|
extract_section "^$tests_serie" "^;;" "$check_process"
|
|
# Parse all infos about arguments of manifest
|
|
# Extract the manifest arguments section from the second partial file
|
|
partial_check_process=$partial1
|
|
extract_section "^; Manifest" "^; " "$partial2"
|
|
|
|
# Initialize the arguments list
|
|
manifest_arguments=""
|
|
|
|
# Read each arguments and store them
|
|
while read line
|
|
do
|
|
# Extract each argument by removing spaces or tabulations before a parenthesis
|
|
add_arg="$(echo $line | sed 's/[ *|\t*](.*//')"
|
|
# Remove all double quotes
|
|
add_arg="${add_arg//\"/}"
|
|
# Then add this argument and follow it by &
|
|
manifest_arguments="${manifest_arguments}${add_arg}&"
|
|
done < "$partial_check_process"
|
|
|
|
# Try to find all specific arguments needed for the tests
|
|
keep_name_arg_only () {
|
|
# Find the line for the given argument
|
|
local argument=$(find_string "($1")
|
|
# If a line exist for this argument
|
|
if [ -n "$argument" ]; then
|
|
# Keep only the name of the argument
|
|
echo "$(echo "$argument" | cut -d '=' -f1)"
|
|
fi
|
|
}
|
|
domain_arg=$(keep_name_arg_only "DOMAIN")
|
|
user_arg=$(keep_name_arg_only "USER")
|
|
port_arg=$(keep_name_arg_only "PORT")
|
|
path_arg=$(keep_name_arg_only "PATH")
|
|
# Get the path value
|
|
if [ -n "$path_arg" ]
|
|
then
|
|
line="$(find_string "(PATH")"
|
|
# Keep only the part after the =
|
|
line="$(echo "$line" | grep -o "path=.* " | cut -d "=" -f2)"
|
|
# And remove " et spaces to keep only the path.
|
|
line="${line//[\" ]/}"
|
|
# If this path is not empty or equal to /. It's become the new default path value.
|
|
if [ ${#line} -gt 1 ]; then
|
|
test_path="$line"
|
|
fi
|
|
fi
|
|
public_arg=$(keep_name_arg_only "PUBLIC")
|
|
# Find the values for public and private
|
|
if [ -n "$public_arg" ]
|
|
then
|
|
line=$(find_string "(PUBLIC")
|
|
public_public_arg=$(echo "$line" | grep -o "|public=[[:alnum:]]*" | cut -d "=" -f2)
|
|
public_private_arg=$(echo "$line" | grep -o "|private=[[:alnum:]]*" | cut -d "=" -f2)
|
|
fi
|
|
|
|
if echo "$LIGNE" | grep -q "(PATH)"; then # Path dans le manifest
|
|
MANIFEST_PATH=$(echo "$LIGNE" | cut -d '=' -f1) # Récupère la clé du manifest correspondant au path
|
|
parse_path=$(echo "$LIGNE" | cut -d '"' -f2) # Lit le path du check_process
|
|
if [ -n "$parse_path" ]; then # Si le path n'est pas null, utilise ce path au lieu de la valeur par défaut.
|
|
PATH_TEST=$(echo "$LIGNE" | cut -d '"' -f2)
|
|
fi
|
|
LIGNE=$(echo "$LIGNE" | cut -d '(' -f1) # Retire l'indicateur de clé de manifest à la fin de la ligne
|
|
fi
|
|
|
|
# Parse all tests to perform
|
|
# Extract the checks options section from the second partial file
|
|
extract_section "^; Checks" "^; " "$partial2"
|
|
|
|
read_check_option () {
|
|
# Find the line for the given check option
|
|
local line=$(find_string "^$1=")
|
|
# Get only the value
|
|
local value=$(echo "$line" | cut -d '=' -f2)
|
|
# And return this value
|
|
if [ "${value:0:1}" = "1" ]
|
|
then
|
|
echo 1
|
|
else
|
|
echo 0
|
|
fi
|
|
}
|
|
|
|
count_test () {
|
|
# Increase the number of test, if this test is set at 1.
|
|
test "$1" -eq 1 && all_test=$((all_test+1))
|
|
}
|
|
|
|
pkg_linter=$(read_check_option pkg_linter)
|
|
count_test $pkg_linter
|
|
setup_sub_dir=$(read_check_option setup_sub_dir)
|
|
count_test $setup_sub_dir
|
|
setup_root=$(read_check_option setup_root)
|
|
count_test $setup_root
|
|
setup_nourl=$(read_check_option setup_nourl)
|
|
count_test $setup_nourl
|
|
setup_private=$(read_check_option setup_private)
|
|
count_test $setup_private
|
|
setup_public=$(read_check_option setup_public)
|
|
count_test $setup_public
|
|
upgrade=$(read_check_option upgrade)
|
|
count_test $upgrade
|
|
backup_restore=$(read_check_option backup_restore)
|
|
count_test $backup_restore
|
|
multi_instance=$(read_check_option multi_instance)
|
|
count_test $multi_instance
|
|
incorrect_path=$(read_check_option incorrect_path)
|
|
count_test $incorrect_path
|
|
port_already_use=$(read_check_option port_already_use)
|
|
count_test $port_already_use
|
|
change_url=$(read_check_option change_url)
|
|
count_test $change_url
|
|
|
|
# For port_already_use, check if there is also a port number
|
|
if [ $port_already_use -eq 1 ]
|
|
then
|
|
line=$(find_string "^port_already_use=")
|
|
# If there is port number
|
|
if echo "$line" | grep -q "([0-9]*)"
|
|
then
|
|
# Store the port number in port_arg and prefix it by # to means that not really a manifest arg
|
|
port_arg="#$(echo "$line" | cut -d '(' -f2 | cut -d ')' -f1)"
|
|
fi
|
|
fi
|
|
|
|
# Launch all tests successively
|
|
TESTING_PROCESS
|
|
# Print the final results of the tests
|
|
TEST_RESULTS
|
|
|
|
# Destroy all snapshots other than snap0
|
|
destroy_temporary_snapshot
|
|
|
|
done <<< "$(grep "^;; " "$check_process")"
|
|
|
|
# No check_process file. Try to parse the manifest.
|
|
else
|
|
# Initialize the values for this serie of tests
|
|
initialize_values
|
|
|
|
manifest_extract="$script_dir/manifest_extract"
|
|
|
|
# Extract the informations from the manifest with the Bram's sly snake script.
|
|
python "$script_dir/sub_scripts/manifest_parsing.py" "$package_path/manifest.json" > "$manifest_extract"
|
|
|
|
# Default tests
|
|
pkg_linter=1
|
|
setup_sub_dir=1
|
|
setup_root=1
|
|
setup_nourl=0
|
|
upgrade=1
|
|
setup_private=1
|
|
setup_public=1
|
|
backup_restore=1
|
|
multi_instance=1
|
|
incorrect_path=1
|
|
port_already_use=0
|
|
change_url=0
|
|
all_test=$((all_test+9))
|
|
|
|
|
|
# Read each arguments and store them
|
|
while read line
|
|
do
|
|
# Read each argument and pick up the first value. Then replace : by =
|
|
add_arg="$(echo $line | cut -d ':' -f1,2 | sed s/:/=/)"
|
|
# Then add this argument and follow it by &
|
|
manifest_arguments="${manifest_arguments}${add_arg}&"
|
|
done < "$manifest_extract"
|
|
|
|
# Search a string in the partial check_process
|
|
find_string () {
|
|
echo $(grep "$1" "$manifest_extract")
|
|
}
|
|
|
|
# Try to find all specific arguments needed for the tests
|
|
keep_name_arg_only () {
|
|
# Find the line for the given argument
|
|
local argument=$(find_string "$1")
|
|
# If a line exist for this argument
|
|
if [ -n "$argument" ]; then
|
|
# Keep only the name of the argument
|
|
echo "$(echo "$argument" | cut -d ':' -f1)"
|
|
fi
|
|
}
|
|
domain_arg=$(keep_name_arg_only ":ynh.local")
|
|
path_arg=$(keep_name_arg_only "path:")
|
|
user_arg=$(keep_name_arg_only "user:\|admin:")
|
|
public_arg=$(keep_name_arg_only "is_public:")
|
|
# Find the values for public and private
|
|
if [ -n "$public_arg" ]
|
|
then
|
|
line=$(find_string "is_public:")
|
|
# Assume the first value is public and the second is private.
|
|
public_public_arg=$(echo "$line" | cut -d ":" -f2)
|
|
public_private_arg=$(echo "$line" | cut -d ":" -f3)
|
|
fi
|
|
|
|
count_test () {
|
|
# Decrease the number of test, if this test is not already removed.
|
|
if [ $1 -eq 1 ]; then
|
|
all_test=$((all_test-1))
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Disable some tests if the manifest key doesn't be found
|
|
if [ -z "$domain_arg" ]
|
|
then
|
|
ECHO_FORMAT "The manifest key for domain didn't be find.\n" "lyellow"
|
|
setup_sub_dir=0
|
|
count_test "$setup_root" || setup_root=0
|
|
count_test "$multi_instance" || multi_instance=0
|
|
count_test "$incorrect_path" || incorrect_path=0
|
|
setup_nourl=1
|
|
fi
|
|
if [ -z "$path_arg" ]
|
|
then
|
|
ECHO_FORMAT "The manifest key for path didn't be find.\n" "lyellow"
|
|
count_test "$setup_root" || setup_root=0
|
|
count_test "$multi_instance" || multi_instance=0
|
|
count_test "$incorrect_path" || incorrect_path=0
|
|
fi
|
|
if [ -z "$public_arg" ]
|
|
then
|
|
ECHO_FORMAT "The manifest key for public didn't be find.\n" "lyellow"
|
|
setup_private=0
|
|
setup_public=0
|
|
all_test=$((all_test-2))
|
|
fi
|
|
# Remove the multi-instance test if this parameter is set at false in the manifest.
|
|
if grep multi_instance "$package_path/manifest.json" | grep -q false
|
|
then
|
|
count_test "$multi_instance" || multi_instance=0
|
|
fi
|
|
|
|
# Launch all tests successively
|
|
TESTING_PROCESS
|
|
# Print the final results of the tests
|
|
TEST_RESULTS
|
|
fi
|
|
|
|
echo "You can find the complete log of these tests in $complete_log"
|
|
|
|
|
|
|
|
#=================================================
|
|
# Notification grade
|
|
#=================================================
|
|
|
|
notif_grade () {
|
|
# Check the level of notification from the check_process.
|
|
# Echo 1 if the grade is reached
|
|
|
|
compare_grade ()
|
|
{
|
|
if echo "$notification_grade" | grep -q "$1"; then
|
|
echo 1
|
|
else
|
|
echo 0
|
|
fi
|
|
}
|
|
|
|
case "$1" in
|
|
all)
|
|
# If 'all' is needed, only a grade of notification at 'all' can match
|
|
compare_grade "^all$"
|
|
;;
|
|
change)
|
|
# If 'change' is needed, notification at 'all' or 'change' can match
|
|
compare_grade "^all$\|^change$"
|
|
;;
|
|
down)
|
|
# If 'down' is needed, notification at 'all', 'change' or 'down' match
|
|
compare_grade "^all$\|^change$\|^down$"
|
|
;;
|
|
*)
|
|
echo 0
|
|
;;
|
|
esac
|
|
}
|
|
|
|
#=================================================
|
|
# Inform of the results by XMPP and/or by mail
|
|
#=================================================
|
|
|
|
send_mail=0
|
|
|
|
# Keep only the name of the app
|
|
app_name=${package_dir%_ynh_check}
|
|
|
|
# If package check it's in the official CI environment
|
|
# Check the level variation
|
|
if [ $type_exec_env -eq 2 ]
|
|
then
|
|
|
|
# Get the job name, stored in the work_list
|
|
job=$(head -n1 "$script_dir/../work_list" | cut -d ';' -f 3)
|
|
|
|
# Identify the type of test, stable (0), testing (1) or unstable (2)
|
|
# Default stable
|
|
test_type=0
|
|
message=""
|
|
if echo "$job" | grep -q "(testing)"
|
|
then
|
|
message="(TESTING) "
|
|
test_type=1
|
|
elif echo "$job" | grep -q "(unstable)"
|
|
then
|
|
message="(UNSTABLE) "
|
|
test_type=2
|
|
fi
|
|
|
|
# Build the log path (and replace all space by %20 in the job name)
|
|
if [ -n "$job" ]; then
|
|
job_log="/job/${job// /%20}/lastBuild/console"
|
|
fi
|
|
|
|
# If it's a test on testing or unstable
|
|
if [ $test_type -gt 0 ]
|
|
then
|
|
# Remove unstable or testing of the job name to find its stable version in the level list
|
|
job="${job% (*)}"
|
|
fi
|
|
|
|
# Get the previous level, found in the file list_level_stable
|
|
previous_level=$(grep "$job" "$script_dir/../auto_build/list_level_stable" | cut -d: -f2)
|
|
|
|
# Print the variation of the level. If this level is different than 0
|
|
if [ $global_level -gt 0 ]
|
|
then
|
|
message="${message}Application $app_name"
|
|
# If non previous level was found
|
|
if [ -z "$previous_level" ]; then
|
|
message="$message just reach the level $global_level"
|
|
send_mail=$(notif_grade all)
|
|
# If the level stays the same
|
|
elif [ $global_level -eq $previous_level ]; then
|
|
message="$message stays at level $global_level"
|
|
# Need notification at 'all' to notify by email
|
|
send_mail=$(notif_grade all)
|
|
# If the level go up
|
|
elif [ $global_level -gt $previous_level ]; then
|
|
message="$message rise from level $previous_level to level $global_level"
|
|
# Need notification at 'change' to notify by email
|
|
send_mail=$(notif_grade change)
|
|
# If the level go down
|
|
elif [ $global_level -lt $previous_level ]; then
|
|
message="$message go down from level $previous_level to level $global_level"
|
|
# Need notification at 'down' to notify by email
|
|
send_mail=$(notif_grade down)
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# If the app completely failed and obtained 0
|
|
if [ $global_level -eq 0 ]
|
|
then
|
|
message="${message}Application $app_name has completely failed the continuous integration tests"
|
|
|
|
# Always send an email if the app failed
|
|
send_mail=1
|
|
fi
|
|
|
|
# The mail subject is the message to send, before any logs informations
|
|
subject="[YunoHost] $message"
|
|
|
|
# If the test was perform in the official CI environment
|
|
# Add the log address
|
|
# And inform with xmpp
|
|
if [ $type_exec_env -eq 2 ]
|
|
then
|
|
|
|
# Build the address of the server from auto.conf
|
|
ci_path=$(grep "DOMAIN=" "$script_dir/../auto_build/auto.conf" | cut -d= -f2)/$(grep "CI_PATH=" "$script_dir/../auto_build/auto.conf" | cut -d= -f2)
|
|
|
|
# Add the log adress to the message
|
|
message="$message on https://$ci_path$job_log"
|
|
|
|
# Send a xmpp notification on the chat room "apps"
|
|
# Only for a test with the stable version of YunoHost
|
|
if [ $test_type -eq 0 ]
|
|
then
|
|
"$script_dir/../auto_build/xmpp_bot/xmpp_post.sh" "$message" > /dev/null 2>&1
|
|
fi
|
|
fi
|
|
|
|
# Send a mail to main maintainer according to notification option in the check_process.
|
|
# Only if package check is in a CI environment (Official or not)
|
|
if [ $type_exec_env -ge 1 ] && [ $send_mail -eq 1 ]
|
|
then
|
|
|
|
# Add a 'from' header for the official CI only.
|
|
if [ $type_exec_env -eq 2 ]; then
|
|
from_yuno="-a \"From: yunohost@yunohost.org\""
|
|
fi
|
|
|
|
# Get the maintainer email from the manifest. If it doesn't found if the check_process
|
|
if [ -z "$dest" ]; then
|
|
dest=$(grep '\"email\": ' "$package_path/manifest.json" | cut -d '"' -f 4)
|
|
fi
|
|
|
|
# Send the message by mail, if a address has been find
|
|
if [ -n "$dest" ]; then
|
|
mail $from_yuno -s "$subject" "$dest" <<< "$message"
|
|
fi
|
|
fi
|
|
|
|
#=================================================
|
|
# Clean and exit
|
|
#=================================================
|
|
|
|
clean_exit 0
|