Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .bootstrap-registry
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ KeymanHosts.php
KeymanSentry.php
KeymanVersion.php
MarkdownHost.php
tests.inc.sh
54 changes: 53 additions & 1 deletion _common/docker.inc.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Common docker functions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Common docker functions.
# shellcheck shell=bash
# Keyman is copyright (C) SIL Global. MIT License.
#
# Common docker functions.


source _common/tests.inc.sh

function get_docker_image_id() {
local IMAGE_NAME=$1
echo "$(docker images -q $IMAGE_NAME)"
Expand Down Expand Up @@ -153,4 +155,54 @@ function start_docker_container() {
fi

builder_echo green "Listening on http://$HOST:$PORT"
}
}

#
# Test PHP site in a docker container
#
# Parameters:
# 1: CONTAINER_DESC desc of container to test
# 2: CONTAINER_PORT localhost http port for container
# 3: TEST_PATH path to start link check at, under http://localhost:CONTAINER_PORT
# 4..: SKIP_PATHS paths to skip crawling in link check, optional
#
# Builder options --no-unit-test, --no-lint, --no-link-check are honoured
#
function test_docker_container() {
local CONTAINER_DESC="$1"
local CONTAINER_PORT="$2"
local TEST_PATH="$3"
shift 3
local SKIP_PATHS=("$*")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local SKIP_PATHS=("$*")
local SKIP_PATHS=("$@")


local LINK_RESULT

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be initialized, otherwise we return uninitialized value if we don't do link checks.

Suggested change
local LINK_RESULT
local LINK_RESULT=0

echo "TIER_TEST" > tier.txt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of tier.txt ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used to specify the tier across server and script contexts consistently (as env vars may not be available in http server context). See:

# Get the site BUILDER_TIER and use that to determine builder script environment
if [[ ! -z ${KEYMANHOSTS_TIER+x} ]]; then
BUILDER_TIER="${KEYMANHOSTS_TIER}"
elif [[ -f "$(dirname "$THIS_SCRIPT")/tier.txt" ]]; then
BUILDER_TIER=$(cat "$(dirname "$THIS_SCRIPT")/tier.txt")
else
BUILDER_TIER=TIER_DEVELOPMENT
fi

if(isset($env['KEYMANHOSTS_TIER']) && in_array($env['KEYMANHOSTS_TIER'],
[KeymanHosts::TIER_DEVELOPMENT, KeymanHosts::TIER_STAGING,
KeymanHosts::TIER_PRODUCTION, KeymanHosts::TIER_TEST])) {
$this->tier = $env['KEYMANHOSTS_TIER'];
} else if(file_exists(__DIR__ . '/../tier.txt')) {
$this->tier = trim(file_get_contents(__DIR__ . '/../tier.txt'));
} else {
$this->tier = KeymanHosts::TIER_DEVELOPMENT;
}


# from commands.inc.sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which commands.inc.sh ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Left behind from a refactor


do_test_record_start_time

if ! builder_has_option --no-unit-test; then
builder_echo blue "---- PHP unit tests"
do_test_unit_tests "${CONTAINER_DESC}"
fi

if ! builder_has_option --no-lint; then
builder_echo blue "---- Lint PHP files"
do_test_lint "${CONTAINER_DESC}"
fi

if ! builder_has_option --no-link-check; then
builder_echo blue "---- Testing links"

LINK_RESULT=0
do_test_links "http://localhost:${CONTAINER_PORT}" "$TEST_PATH" "${SKIP_PATHS[@]}" || LINK_RESULT=$?
builder_echo blue "Done checking links; linkinator exit code: ${LINK_RESULT}"
do_test_print_link_report

do_test_print_container_error_logs "${CONTAINER_DESC}"
fi

rm tier.txt
return "$LINK_RESULT"
}
93 changes: 93 additions & 0 deletions _common/tests.inc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# shellcheck shell=bash

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# shellcheck shell=bash
# shellcheck shell=bash
# Keyman is copyright (C) SIL Global. MIT License.


# Record the start time for unit tests for later log review
function do_test_record_start_time() {
TEST_START_TIME=$(date -Is -u)
}

# Run unit tests through phpunit
#
# Parameters
# 1: CONTAINER container_desc to run on
#
function do_test_unit_tests() {
local CONTAINER="$1"
docker exec "${CONTAINER}" sh -c "vendor/bin/phpunit --testdox ${builder_extra_params[*]}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
docker exec "${CONTAINER}" sh -c "vendor/bin/phpunit --testdox ${builder_extra_params[*]}"
docker exec "${CONTAINER}" sh -c "vendor/bin/phpunit --testdox ${builder_extra_params[@]}"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can use ${x[@]} here because that will quote at the wrong level -- it needs to remain inside the quotes for the parameter following sh -c.

}

# Lint .php files for obvious errors
#
# Parameters
# 1: CONTAINER container_desc to run on
#
function do_test_lint() {
local CONTAINER="$1"
docker exec "${CONTAINER}" sh -c "find . -name '*.php' | grep -v '/vendor/' | xargs -n 1 -d '\\n' php -l"
}

## Check links on live local server using linkinator
#
# Parameters
# 1: baseURL the top level URL for the site
# 2: testPath path under baseURL to start testing, e.g. /
# 3[,4..]: skipPaths list of paths (under baseURL) to skip crawling, optional
#
function do_test_links() {
local baseURL="$1"
local testPath="$2"
shift 2
local skipPaths skip skipParams=()
if [[ $# -gt 1 ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if [[ $# -gt 1 ]]; then
if [[ $# -ge 1 ]]; then

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, bingo, if I have do_test_links a b "$*" rather than do_test_links a b "$@", then I end up with a spurious blank parameter...

$* vs $@ is such an annoying distinction 😁

skipPaths=("$*")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
skipPaths=("$*")
skipPaths=("$@")

else
skipPaths=()
fi

for skip in "${skipPaths[@]}"; do
skipParams+=(--skip "^${baseURL}${skip}")
done

npx https://github.com/keymanapp/linkinator \
"${baseURL}${testPath}" \
--clean-urls \
--concurrency 50 \
--format json \
--output-filename linkinator-results.json \
--skip "^(?!${baseURL})" \
"${skipParams[@]}" \
--recurse \
--redirects verify \
--retry-errors \
--root-path "${baseURL}"
}

# Print summary of results from linkinator
function do_test_print_link_report() {
echo ----------------------------------------------------------------------
echo Link check summary
echo ----------------------------------------------------------------------
# Emit full JSON detail for broken links (may not be necessary)
jq '.links[] | select(.state != "OK")' < linkinator-results.json
echo
echo
# Emit a summary report
jq -r '.links[] | select(.state != "OK") | "\(.state)[\(.status)]: \(.parent) --> \(.url)"' < linkinator-results.json
}

# Scan logs recorded on container since start of tests to find any reported PHP
# errors (note, depends on 'php7' string)
#
# Parameters
# 1: CONTAINER container_desc to run on
#
function do_test_print_container_error_logs() {
local CONTAINER="$1"
if docker container logs "${CONTAINER}" --since "${TEST_START_TIME}" 2>&1 | grep -q 'php7'; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add some check somewhere that we're still running the expected PHP version that outputs php7?

Makes me a bit nervous that PHP gets upgraded and we forget to upgrade this string which then causes silent failure (or rather non-failure) of this function.

@mcdurdin mcdurdin Jun 16, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested against PHP 8 and the version number has been removed from the string. Have gone with the following (tested) string which works with PHP 7 and 8 running through Apache:

'\[php7?:(error|warn|notice)\]'

The format of messages may of course change again at any time in the future but we can only do so much...

echo 'PHP reported errors or warnings:'
docker container logs "${CONTAINER}" --since "${TEST_START_TIME}" 2>&1 | grep 'php7'
return 1
else
echo 'No PHP errors found'
return 0
fi
}
Loading