bin/test
#!/usr/bin/env bash
: <<'COPYRIGHT'
Copyright (c) Vaimo Group. All rights reserved.
See LICENSE_VAIMO.txt for license details.
COPYRIGHT
script_path=${BASH_SOURCE[0]}
script_root=$(dirname $(test -L ${script_path} && readlink -f ${script_path} || echo ${script_path}))
source ${script_root}/lib/output.sh
run_tests() {
local scenario_root=${1}
local copy_target=${2}
local composer_version=$(composer --version|sed 's/Composer version \(.*\)/\1/g'|cut -d ' ' -f1)
local scenario_label=$(cat ${scenario_root}/.label)
_line '-' 80
_info "SCENARIO:" "${scenario}" "${scenario_label}"
_line '-' 80
rm -rf patches 2>/dev/null
rm patches.json 2>/dev/null
rm output.out 2>/dev/null
rm output.txt 2>/dev/null
local original_ifs=${IFS}
local php_script=$(cat <<EOF
if (file_exists('${copy_target}/composer.json')) {
\$config = json_decode(file_get_contents('${copy_target}/composer.json'), true);
echo \$config['name'];
}
EOF
)
patches_owner=$(php -r "${php_script}" 2>/dev/null)
local commands=$(
cat ${scenario_root}/.commands \
|sed 's|{{OWNER-ROOT}}|'${copy_target}'|g' \
|sed 's|{{PATCHES-OWNER}}|'${patches_owner}'|g' \
)
IFS=$'\n'
for file in $(ls -1 ${scenario_root}/files) ; do
cp -R ${scenario_root}/files/${file} ${copy_target}/${file}
done
IFS=${original_ifs}
local should_fail
local system_call
IFS=$'\n'
for line in ${commands} ; do
should_fail=0
system_call=0
if [ "${line:0:1}" == "!" ] ; then
_warning "(EXPECTING FAILURE)"
should_fail=1
fi
line=$(echo ${line}|sed 's/^\!*//g')
if [ "${line:0:1}" == "#" ] ; then
continue
fi
if [ "${line:0:1}" == "@" ] ; then
local command=$(_trim_string ${line:1}|cut -d' ' -f1)
local args=$(_trim_string ${line:1}|cut -d' ' -f2-)
local php_script=
if [ "${command}" == "queue_update" ] ; then
local target=$(echo ${args}|cut -d' ' -f2)
local installed_config="vendor/composer/installed.json"
local php_script=$(cat <<EOF
if (file_exists('${installed_config}')) {
\$config = json_decode(file_get_contents('${installed_config}'), true);
if (version_compare('${composer_version}', '2.0', '<')) {
\$packagesConfig = \$config;
} else {
\$packagesConfig = \$config['packages'];
}
\$names = array_map(function (\$item) {
return isset(\$item['name']) ? \$item['name'] : '';
}, \$packagesConfig);
\$index = array_search('${target}', \$names, true);
if (\$index !== false) {
\$packagesConfig[\$index]['version'] = '0.0.0';
\$packagesConfig[\$index]['version_normalized'] = '0.0.0';
if (version_compare('${composer_version}', '2.0', '<')) {
\$config = \$packagesConfig;
} else {
\$config['packages'] = \$packagesConfig;
}
file_put_contents('${installed_config}', json_encode(\$config, JSON_PRETTY_PRINT));
}
}
EOF
)
fi
if [ "${command}" == "queue_install" ] ; then
local target=$(echo ${args}|cut -d' ' -f2)
local installed_config="vendor/composer/installed.json"
local php_script=$(cat <<EOF
if (file_exists('${installed_config}')) {
\$config = json_decode(file_get_contents('${installed_config}'), true);
if (version_compare('${composer_version}', '2.0', '<')) {
\$packagesConfig = \$config;
} else {
\$packagesConfig = \$config['packages'];
}
\$names = array_map(function (\$item) {
return isset(\$item['name']) ? \$item['name'] : '';
}, \$packagesConfig);
\$index = array_search('${target}', \$names, true);
if (\$index !== false) {
unset(\$packagesConfig[\$index]);
if (version_compare('${composer_version}', '2.0', '<')) {
\$config = \$packagesConfig;
} else {
\$config['packages'] = \$packagesConfig;
}
file_put_contents('${installed_config}', json_encode(\$config, JSON_PRETTY_PRINT));
}
}
EOF
)
fi
if [ "${command}" == "config" ] ; then
local path=$(echo ${args}|cut -d' ' -f1)
local value=$(echo ${args}|cut -d' ' -f2)
local package_config="composer.json"
local php_script=$(cat <<EOF
if (file_exists('${package_config}')) {
\$config = json_decode(file_get_contents('${package_config}'), true);
\$pathSteps = explode('/', '${path}');
\$node = &\$config;
foreach(\$pathSteps as \$step) {
if (!isset(\$node[\$step])) {
\$node[\$step] = array();
}
\$node = &\$node[\$step];
}
\$node = ${value};
unset(\$node);
file_put_contents('${package_config}', json_encode(\$config, JSON_PRETTY_PRINT));
}
EOF
)
fi
if [ "${php_script}" != "" ] ; then
php -r "${php_script}" 2>/dev/null
continue
fi
if [ "${command}" == "assert_changes" ] ; then
if assert_patches "${copy_target}" ; then
continue
fi
return 1
fi
if [ "${command}" == "abort" ] ; then
return 0
fi
if [ "${command}" == "assert" ] ; then
if process_assertions "${args}" ; then
continue
fi
return 1
fi
continue
fi
if [ "${line:0:1}" == ">" ] ; then
system_call=1
fi
line=$(echo ${line}|sed 's/^>*//g')
if [ ${system_call} -eq 0 ] ; then
apply_patches "${line}" "output.out"
else
local line_label=${line}
if [ "${line_label:${#line_label} - 1}" == "#" ] ; then
line_label=$(echo ${line_label}|rev|cut -d'#' -f1-2|rev)
fi
if [ "${line_label}" != "${line}" ] ; then
_warning "$(_trim_string $(echo ${line_label}|tr -d '#'))"
else
_warning "SYSTEM: $(_trim_string ${line_label})"
fi
eval "${line}"
fi
local result=${?}
if ( [ ${result} -eq 0 ] && [ ${should_fail} -eq 0 ]) \
|| ([ ${result} -gt 0 ] && [ ${should_fail} -eq 1 ] \
) ; then
continue
fi
return 1
done
IFS=${original_ifs}
if ! assert_patches "${copy_target}" ; then
return 1
fi
if ! assert_scenario_output "${patches_owner}" "${scenario_root}" "output.out" ; then
return 1
fi
return 0
}
assert_patches() {
local patches_root=${1}
local assertions=
if [ -d ${patches_root}/patches ] ; then
for patch_file in $(find -L ${patches_root}/patches -type f) ; do
assertions=${assertions}$'\n'$(cat ${patch_file}|grep '@assert'|sed 's/^@assert //g')
done
fi
if [ "${assertions}" == "" ] ; then
return 0
fi
if process_assertions "${assertions}" ; then
return 0
fi
return 1
}
_is_macosx() {
if [[ "${OSTYPE}" == "darwin"* ]]; then
return 0
fi
return 1
}
assert_scenario_output() {
local patches_owner=${1}
local scenario_root=${2}
local output_file=${3}
local composer_version=$(composer --version 2>/dev/null|sed 's/Composer version \(.*\)/\1/g'|cut -d ' ' -f1)
local composer_major=${composer_version%%.*}
local expected_output_file="${scenario_root}/.output"
local output_filters="${scenario_root}/.filters"
if _is_macosx ; then
if [ "${composer_major}" = "2" ] && [ -f ${expected_output_file}-v2-darwin ] ; then
expected_output_file="${expected_output_file}-v2-darwin"
elif [ -f ${expected_output_file}-darwin ] ; then
expected_output_file="${expected_output_file}-darwin"
fi
elif [ "${composer_major}" = "2" ] && [ -f ${expected_output_file}-v2 ] ; then
expected_output_file="${expected_output_file}-v2"
fi
if [ ! -f ${output_file} ] ; then
return 0
fi
cat ${output_file} \
|perl -pe 's/\e\[[0-9;]*m(?:\e\[K)?//g' \
|grep -v "^\s\s\s\s[0-9]\+/[0-9]\+:" \
|grep -v "^\s\s\s\sFinished:\ssuccess:\s" \
|grep -v "^You are running composer with xdebug enabled" \
|grep -v "^Writing lock file" \
|grep -v "^Generating autoload files" \
|grep -v "^Nothing to install or update" \
|grep -v "^Loading composer repositories with package information" \
|grep -v "^Updating dependencies (including require-dev)" \
|grep -v "^Installing dependencies (including require-dev) from lock file" \
|grep -v "^patch:apply" \
|grep -v "^patch:list" \
|grep -v "^patch:validate" \
|grep -v "^patch:redo" \
|grep -v "^patch:undo" \
|grep -v "You are using an outdated version of Composer" \
|grep -v "packages you are using are looking for funding" \
|grep -v "Use the \`composer fund\` command to find out more" \
|grep -v "^Composer is operating slower than normal because you have Xdebug enabled" \
|grep -v "^You are running composer with Xdebug enabled" \
|grep -v "Xdebug: \[Step Debug\] Could not connect to debugging client" \
|grep -v "^Installing dependencies from lock file" \
|grep -v "^Verifying lock file contents can be installed on current platform" \
|grep -v "^Nothing to install, update or remove" \
|grep -v "^Updating dependencies" \
|grep -v "^Nothing to modify in lock file" \
|grep -v "^Warning: The lock file is not up to date with the latest changes in" \
|sed 's/ *$//g' \
> output.txt
if [ -f ${output_filters} ] ; then
local original_ifs=${IFS}
IFS=$'\n'
for filter in $(cat ${output_filters}) ; do
sed -i.bak ${filter} output.txt
rm output.txt.bak
done
IFS=${original_ifs}
fi
rm ${output_file} 2>/dev/null
if [ ! -f ${expected_output_file} ] ; then
return 0
fi
cat ${expected_output_file}|sed 's|{{PATCHES-OWNER}}|'${patches_owner}'|g' > output.exp
local diff_out=$(diff -u --ignore-trailing-space output.exp output.txt|grep -v '^\(+++\|---\|@@\)')
if [ "${diff_out}" == "" ] ; then
return 0
fi
echo ''
_error "ERROR: output differs from what is expected:"
echo "- EXPECTED"
echo "+ ACTUAL"
_line '↓' 80
echo "${diff_out}"
_line '↑' 80
echo ''
return 1
}
process_assertions() {
local assertions=${1}
local original_ifs=${IFS}
IFS=$'\n'
for assertion in ${assertions} ; do
if [ "${assertion}" == "" ] ; then
continue
fi
if assert ${assertion} ; then
continue
fi
assertion_error "${assertion}"
return 1
done
IFS=${original_ifs}
return 0
}
apply_patches() {
local args=${1}
local out_file=${2}
local command="composer ${args} --ansi"
if [ "${VERBOSE}" == "1" ] ; then
command="${command} -vvv"
fi
if [ "${out_file}" != "" ] ; then
command="${command} &> >(tee -a ${out_file})"
fi
cols=$(tput cols)
rows=$(tput lines)
(
stty rows 50 cols 1000
eval "${command}; return \${PIPESTATUS[0]}"
)
local result=${?}
stty rows ${rows} cols ${cols}
return ${result};
}
assert() {
assertion=${1}
invert=${2}
local name=$(echo ${assertion}|cut -d',' -f1)
local before=$(echo ${assertion}|cut -d',' -f2)
local after=$(echo ${assertion}|cut -d',' -f3)
local target=$(echo ${assertion}|cut -d',' -f4)
if [ "${target}" = "" ] ; then
target="src/example.txt"
fi
local file_path="vendor/${name}/${target}"
local contents=$(cat ${file_path})
if [ "${invert}" != "" ] && [ "${invert}" != "0" ] ; then
local tmp=${before}
before=${after}
after=${tmp}
fi
if echo "${contents}"|grep -qw "${after}" && [ "${before}" == "" ] ; then
return 0
fi
if echo "${contents}"|grep -qw "^${before}" && [ "${before}" == "${after}" ] ; then
return 0
fi
if ! echo "${contents}"|grep -qw "^${before}" && echo "${contents}"|grep -qw "^${after}" ; then
return 0
fi
return 1
}
assertion_error() {
assertion=${1}
invert=${2}
echo ${assertion}
local name=$(echo ${assertion}|cut -d',' -f1)
local before=$(echo ${assertion}|cut -d',' -f2)
local after=$(echo ${assertion}|cut -d',' -f3)
local target=$(echo ${assertion}|cut -d',' -f4)
if [ "${target}" = "" ] ; then
target="src/example.txt"
fi
local file_path="vendor/${name}/${target}"
local contents=$(cat ${file_path})
if [ "${invert}" != "" ] && [ "${invert}" != "0" ] ; then
local tmp=${before}
before=${after}
after=${tmp}
fi
_error "TARGET: ${file_path}"
_error "ASSERTION: ${before} => ${after}"
}
reset_packages() {
local scenarios_root=${1}
local installation_root=${2}
local install_info_path="${installation_root}/vendor/composer/installed.json"
local lock_path="${installation_root}/composer.lock"
if [ -f ${install_info_path} ] ; then
filters=$(grep -r "@package" ${scenarios_root}|sed 's/.*@package\(.*\)/\1/g'|sort|uniq|tr '\n' '|'|tr -d ' '|tr -d '\r')
perl -i.org -pe "s%(\s\"name\":\s\")(${filters}|vaimo/composer-patches-target)(.*)\",%\1__\2\3\",%g" \
${install_info_path}
rm ${install_info_path}.org
local php_script=$(cat <<EOF
if (file_exists('${lock_path}')) {
\$config = json_decode(file_get_contents('${lock_path}'), true);
foreach (['packages', 'packages-dev'] as \$group) {
\$names = array_map(function (\$item) { return \$item['name']; }, \$config[\$group]);
\$matches = preg_grep('|vaimo/composer-patches-target|', \$names);
foreach (array_keys(\$matches) as \$index) {
\$config[\$group][\$index]['extra']['patches_applied'] = [];
}
}
file_put_contents('${lock_path}', json_encode(\$config, JSON_PRETTY_PRINT));
}
EOF
)
php -r "${php_script}" 2>/dev/null
fi
composer install --ansi --no-plugins &>/dev/null
}
sanitize() {
local copy_target=${1}
rm -rf ${copy_target}/patches 2>/dev/null
rm ${copy_target}/patches.json 2>/dev/null
rm -rf modules 2>/dev/null
rm -rf scenarios 2>/dev/null
}
sanitize_scenario() {
local scenario_root=${1}
local copy_target=${2}
for file in $(ls -1 ${scenario_root}/files) ; do
rm -rf ${copy_target}/${file} 2>/dev/null
done
}
get_installation_root() {
local installation_name=${1}
(
cd installations/${installation_name} &>/dev/null
pwd
)
}
scenario_filter=${1}
if echo "${scenario_filter}"|grep -q ':' ; then
installation_filter=$(echo "${scenario_filter}"|cut -d':' -f1)
scenario_filter=$(echo "${scenario_filter}"|cut -d':' -f2)
else
installation_filter=".*"
fi
if [ "${scenario_filter}" == "" ] ; then
scenario_filter=".*"
fi
(
package_root=$(pwd)
cd ${package_root}/test &>/dev/null
sandbox_root=$(pwd)
installations=$(ls -1 installations)
scenarios_root=$(pwd)/scenarios
scenarios=$(ls -1 scenarios)
if [ "${PURGE}" == "1" ] ; then
for installation in ${installations} ; do
installation_root=$(get_installation_root ${installation})
rm "${installation_root}/composer.lock" 2>/dev/null
rm -rf "${installation_root}/vendor" 2>/dev/null
done
fi
for installation in ${installations} ; do
if ! echo ${installation}|grep -q "${installation_filter}" ; then
continue
fi
installation_root=$(get_installation_root ${installation})
installation_label=$(cat ${installation_root}/.label)
scenarios_skip=$(cat ${installation_root}/.skip 2>/dev/null)
_line '=' 80
_title "INSTALLATION:" "${installation}" "${installation_label}"
_line '=' 80
(
cd ${installation_root}
copy_target=$(cat .target 2>/dev/null)
if [ "${copy_target}" != "" ] ; then
copy_target="${installation_root}/${copy_target}"
else
copy_target="${installation_root}"
fi
sanitize "${copy_target}"
rm -rf ${installation_root}/modules
cp -R ${sandbox_root}/modules ${installation_root}
if [ ! -f composer.lock ] ; then
composer install --ansi --no-plugins
else
composer install --ansi --no-plugins &>/dev/null
fi
cp composer.json composer.json.org
cp composer.lock composer.lock.org
for scenario in ${scenarios} ; do
if [ "${scenarios_skip}" != "" ] && echo "${scenarios_skip}"|grep -q "^${scenario}$" ; then
continue
fi
if ! echo ${scenario}|grep -q "${scenario_filter}" ; then
continue
fi
reset_packages "${scenarios_root}" "${installation_root}"
scenario_root=${sandbox_root}/scenarios/${scenario}
if ! run_tests "${scenario_root}" "${copy_target}" ; then
cdiff=$(diff composer.json.org composer.json)
cp composer.json.org composer.json
cp composer.lock.org composer.lock
if [ "${cdiff}" != "" ] ; then
_info "RESTORING INSTALLATION"
composer install --no-plugins
fi
sanitize "${copy_target}"
sanitize_scenario "${scenario_root}" "${copy_target}"
_error "FAILED: $(basename $(pwd)):$(basename ${scenario_root})"
exit 1
else
cdiff=$(diff composer.json.org composer.json)
cp composer.json.org composer.json
cp composer.lock.org composer.lock
if [ "${cdiff}" != "" ] ; then
_info "RESTORING INSTALLATION"
composer install --no-plugins
fi
sanitize_scenario "${scenario_root}" "${copy_target}"
fi
done
echo ''
sanitize
exit 0
)
if [ ${?} != 0 ] ; then
_line '▚' 80
_error "FAIL"
exit 1
fi
done
_line '▚' 80
_info "SUCCESS"
exit 0
)