From 6f67027325dee574b12adfb92683664122d61c78 Mon Sep 17 00:00:00 2001 From: Dmytro Serdiuk Date: Mon, 22 Jun 2020 12:13:36 +0300 Subject: [PATCH] Run tests on multiple Bash and Git versions Now Elegant Git is tested on 2 Git and 3 Bash versions (6 environments in total). All testing happens within Docker containers that work in parallel. The overall workflow is the following: 1. `workflows` runs a given command 2. the command starts a needed number of containers in parallel, waits until they are exited, and reports the status Such a running scheme allows to run more tests and total duration is equal to the duration of the longest test execution. From the technical side, the worker containers don't package the appropriate workflows scripts, they just configure them by default. This allows making script changes without rebuilding the images. Also, as usual, all workers bind the source files in the container's volume and use them for the execution. --- .workflows/bats/Dockerfile | 61 +++++++- .workflows/bats/bats-workflows.bash | 1 - .workflows/docs/Dockerfile | 15 +- .workflows/docs/docs-workflows.bash | 23 ++- docs/index.md | 12 ++ workflows | 218 +++++++++++++++++----------- 6 files changed, 221 insertions(+), 109 deletions(-) diff --git a/.workflows/bats/Dockerfile b/.workflows/bats/Dockerfile index afbcef3..77e8616 100644 --- a/.workflows/bats/Dockerfile +++ b/.workflows/bats/Dockerfile @@ -1,15 +1,60 @@ ARG bashversion=3.2.57 +FROM alpine:3.12.0 as git +RUN apk add --no-cache \ + zlib-dev \ + openssl-dev \ + curl-dev \ + curl \ + expat-dev \ + perl-dev \ + python3-dev \ + pcre2-dev \ + asciidoc \ + xmlto \ + perl-error \ + tcl \ + tk \ + make \ + gcc \ + g++ +WORKDIR /src +ARG gitversion=2.26.2 +RUN curl --output git-${gitversion}.tar.gz \ + https://mirrors.edge.kernel.org/pub/software/scm/git/git-${gitversion}.tar.gz && \ + tar -xvzf git-${gitversion}.tar.gz +RUN export TO=/git && \ + mkdir -p ${TO} && \ + cd git-${gitversion} && \ + make prefix=/usr DESTDIR=${TO} NO_GETTEXT=YesPlease NO_REGEX=YesPlease ICONV_OMITS_BOM=Yes && \ + make prefix=/usr DESTDIR=${TO} NO_GETTEXT=YesPlease NO_REGEX=YesPlease ICONV_OMITS_BOM=Yes strip && \ + make prefix=/usr DESTDIR=${TO} NO_GETTEXT=YesPlease NO_REGEX=YesPlease ICONV_OMITS_BOM=Yes install && \ + # remove files that aren't part of standard package + rm /git/usr/libexec/git-core/git-cvs* && \ + rm /git/usr/libexec/git-core/git-daemon && \ + rm /git/usr/libexec/git-core/git-fast-import && \ + rm /git/usr/libexec/git-core/git-http-backend && \ + rm /git/usr/libexec/git-core/git-instaweb && \ + rm /git/usr/libexec/git-core/git-remote-testsvn && \ + rm /git/usr/libexec/git-core/git-shell && \ + rm /git/usr/libexec/git-core/git-svn && \ + rm /git/usr/libexec/git-core/*p4* && \ + rm /git/usr/libexec/git-core/mergetools/*p4* && \ + rm /git/usr/libexec/git-core/*email* && \ + rm /git/usr/libexec/git-core/*imap* + FROM bash:${bashversion} +ARG batsversion=v1.2.0 LABEL maintainer="Dmytro Serdiuk " \ - description="The image serves the environment for the testing of Elegant Git." -VOLUME /elegant-git + description="The image serves the environment for the testing of Elegant Git." \ + bashversion=${bashversion} \ + gitversion=${gitversion} \ + batsversion=${batsversion} WORKDIR /elegant-git +VOLUME /elegant-git ENV EG_ENABLE_TESTING true -ARG gitversion=2.26.2 -RUN apk add --no-cache git~=${gitversion} -ARG batsversion=v1.2.0 -RUN git clone --branch ${batsversion} --single-branch --depth 1 https://github.com/bats-core/bats-core.git; \ +COPY --from=git /git/usr/ /usr/ +RUN apk add --no-cache curl-dev && \ + git clone --branch ${batsversion} --single-branch --depth 1 https://github.com/bats-core/bats-core.git && \ cd bats-core && ./install.sh /usr/local && cd - && rm -r bats-core -COPY bats-workflows.bash /bats-workflows.bash -ENTRYPOINT [ "/bats-workflows.bash" ] +ENTRYPOINT [ ".workflows/bats/bats-workflows.bash" ] CMD ["help"] diff --git a/.workflows/bats/bats-workflows.bash b/.workflows/bats/bats-workflows.bash index 8380d09..fae619f 100755 --- a/.workflows/bats/bats-workflows.bash +++ b/.workflows/bats/bats-workflows.bash @@ -25,7 +25,6 @@ MESSAGE main() { case ${1} in - all_tests) all_tests ;; some_tests) shift; some_tests "${@}" ;; help) usage ;; *) "${@}" ;; diff --git a/.workflows/docs/Dockerfile b/.workflows/docs/Dockerfile index 623d799..601334b 100644 --- a/.workflows/docs/Dockerfile +++ b/.workflows/docs/Dockerfile @@ -1,16 +1,15 @@ -ARG pythonversion=3.8.3 -FROM python:${pythonversion}-alpine +FROM python:3.8.3-alpine LABEL maintainer="Dmytro Serdiuk " \ description="The image serves the environment for docs development of Elegant Git." +ARG bashversion=5.0.17 +ARG gitversion=2.26.2 +RUN apk add --no-cache bash=~${bashversion} git=~${gitversion} VOLUME /elegant-git WORKDIR /elegant-git ENV EG_ENABLE_TESTING true EXPOSE 80 -ENTRYPOINT [ "/docs-workflows.bash" ] +ENTRYPOINT [ ".workflows/docs/docs-workflows.bash" ] CMD ["help"] COPY docs/requirements.txt . -RUN python -m pip install --upgrade pip && \ - python -m pip install -r requirements.txt -ARG bashversion=5.0.17 -RUN apk add --no-cache bash~=${bashversion} -COPY .workflows/docs/docs-workflows.bash / +RUN python -m pip install --no-cache-dir --upgrade pip && \ + python -m pip install --no-cache-dir -r requirements.txt diff --git a/.workflows/docs/docs-workflows.bash b/.workflows/docs/docs-workflows.bash index 0ddca5f..70d2292 100755 --- a/.workflows/docs/docs-workflows.bash +++ b/.workflows/docs/docs-workflows.bash @@ -2,7 +2,7 @@ set -e generate() { - exec python .workflows/docs/docs.py + python .workflows/docs/docs.py } build() { @@ -10,14 +10,20 @@ build() { if test -z "${EG_ENABLE_TESTING}"; then site_directory=$(pwd)/elegnat-git-docs fi - exec python -m mkdocs build --clean --strict --site-dir ${site_directory} + python -m mkdocs build --clean --strict --site-dir ${site_directory} } preview() { exec python -m mkdocs serve --dev-addr 0.0.0.0:80 } -help() { +check() { + echo "Checking if there are uncommitted docs files ..." + git update-index --really-refresh + git diff-index --quiet HEAD docs +} + +usage() { cat < @@ -26,8 +32,17 @@ Available commands: generate generates fresh commands documentation build builds the static documentation site preview previews the documentation site + check shows whether 'docs' directory is committed or not MESSAGE } -${1} +main() { + case ${1} in + help) usage ;; + ci) generate && check ;; + *) "${@}" ;; + esac +} + +main "${@}" diff --git a/docs/index.md b/docs/index.md index b186c10..edaa31a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,6 +21,18 @@ picture-in-picture" allowfullscreen> > _Looks interesting? Go to [getting started](getting-started.md) guide or take a look for available [commands](commands.md)._ +Elegant Git has a rich test suit that executes on several Git and Bash versions. Please refer to +the table below to see the coverage matrix. + +`bash --version`|`git --version`|`git elegant --version` +---|---|--- +5.0.17|2.26.2|up to the latest +4.4.23|2.26.2|up to the latest +3.2.57|2.26.2|up to the latest +5.0.17|2.13.7|up to the latest +4.4.23|2.13.7|up to the latest +3.2.57|2.13.7|up to the latest + # Workflows While developing something, it may be required to format code prior to committing modifications or to open several URLs to report release notes after a new release. All these and similar actions, diff --git a/workflows b/workflows index 25187c4..f6fa44b 100755 --- a/workflows +++ b/workflows @@ -3,43 +3,49 @@ set -e source ./libexec/plugins/text -bash_3_2_57=3.2.57 +# bash versions +bash_3_2_57=3.2.57 # MACOS default +bash_4_4_23=4.4.23 bash_5_0_17=5.0.17 +# python versions python_3_8_3=3.8.3 +# git versions +git_2_13_7=2.13.7 git_2_26_2=2.26.2 -bashes=( - ${bash_5_0_17} - ${bash_3_2_57} -) -gits=( - ${git_2_26_2} -) -base_testing_worker=bash${bash_3_2_57}-git${git_2_26_2} + +--worker-tag() { + # usege: --worker-tag + echo ${1}-${2} +} eg_docker_organization=beeshive -eg_documents_repository=${eg_docker_organization}/elegant-git-docs-workflows -eg_documents_image=${eg_documents_repository}:python${python_3_8_3}-bash${bash_5_0_17} +eg_documents_repository=${eg_docker_organization}/elegant-git-docs-worker +eg_documents_bash=${bash_5_0_17} +eg_documents_git=${git_2_26_2} +eg_documents_image=${eg_documents_repository}:$(--worker-tag ${eg_documents_bash} ${eg_documents_git}) eg_bats_repository=${eg_docker_organization}/elegant-git-bats-worker -eg_bats_image=${eg_bats_repository}:${base_testing_worker} - +bats_worker_tags=( + $(--worker-tag ${bash_3_2_57} ${git_2_13_7}) + $(--worker-tag ${bash_4_4_23} ${git_2_13_7}) + $(--worker-tag ${bash_5_0_17} ${git_2_13_7}) + $(--worker-tag ${bash_3_2_57} ${git_2_26_2}) + $(--worker-tag ${bash_4_4_23} ${git_2_26_2}) + $(--worker-tag ${bash_5_0_17} ${git_2_26_2}) +) --publish-worker() { info-text "Select image to push:" - select tag in $(docker image ls --filter reference=${1} --format "{{.Repository}}:{{.Tag}}"); do + select tag in $(docker image ls --filter reference=${1} --format "{{.Repository}}:{{.Tag}}") exit; do + if test ${tag} = "exit"; then break; fi docker push ${tag} - break done } # docs workflows prepare-docs-worker() { - local bashversion=${bash_5_0_17} - local pythonversion=${python_3_8_3} docker build --tag ${eg_documents_image} \ - --label bashversion=${bashversion} \ - --label pythonversion=${pythonversion} \ - --build-arg bashversion=${bashversion} \ - --build-arg pythonversion=${pythonversion} \ + --build-arg bashversion=${eg_documents_bash} \ + --build-arg gitversion=${eg_documents_git} \ --file .workflows/docs/Dockerfile . } @@ -68,100 +74,130 @@ serve-docs() { # docs workflows repository() { - info-text "Start container..." - docker run --interactive --tty --detach --rm \ - --name repository \ - --workdir /tmp/elegant-git-repo \ - --mount type=bind,source=$(pwd),target=/elegant-git \ - ${eg_bats_image} bash - info-text "Init repository..." - docker exec --interactive --tty repository bash -c " - cd /elegant-git - source tests/addons-repo.bash - source tests/addons-common.bash - repo-new - " - info-text "Install Elegant Git..." - docker exec --interactive --tty repository bash -c " - cd /elegant-git - .workflows/installation-workflows.bash install - " - info-text "Ready! Enjoy experiments..." - docker attach repository + info-text "Select worker:" + select worker in ${bats_worker_tags[*]} exit; do + if test ${worker} = "exit"; then break; fi + info-text "Start container..." + docker run --interactive --tty --detach --rm \ + --name repository \ + --workdir /tmp/elegant-git-repo \ + --mount type=bind,source=$(pwd),target=/elegant-git \ + ${eg_bats_repository}:${worker} bash + info-text "Init repository..." + docker exec --interactive --tty repository bash -c " + cd /elegant-git + source tests/addons-repo.bash + source tests/addons-common.bash + repo-new + " + info-text "Install Elegant Git..." + docker exec --interactive --tty repository bash -c " + cd /elegant-git + .workflows/installation-workflows.bash install + " + info-text "Ready! Enjoy experiments..." + docker attach repository + done } # testing workflows prepare-bats-worker() { - info-text "Select Bash and Git version:" - select bashv in ${bashes[*]}; do - bashversion=${bashv} - break - done - select gitv in ${gits[*]}; do - gitversion=${gitv} - break - done local batsversion=v1.2.0 - docker build --tag ${eg_bats_repository}:bash${bashversion}-git${gitversion} \ - --label bashversion=${bashversion} \ - --label gitversion=${gitversion} \ - --label batsversion=${batsversion} \ - --build-arg bashversion=${bashversion} \ - --build-arg gitversion=${gitversion} \ - --build-arg batsversion=${batsversion} \ - .workflows/bats + info-text "Select worker:" + select worker in ${bats_worker_tags[*]} exit; do + if test ${worker} = "exit"; then break; fi + docker build --tag ${eg_bats_repository}:${worker} \ + --build-arg bashversion=${worker%%-*} \ + --build-arg gitversion=${worker##*-} \ + --build-arg batsversion=${batsversion} \ + .workflows/bats + done } publish-bats-worker() { --publish-worker ${eg_bats_repository} } -testing() { - echo $@ - docker run --interactive --rm \ - --mount type=bind,source=$(pwd),target=/elegant-git \ - ${eg_bats_image} some_tests "$@" +--bats-workers() { + # usage: --bats-workers [ ...] + local task=${1}; shift + for tag in ${bats_worker_tags[*]}; do + docker run --detach \ + --mount type=bind,source=$(pwd),target=/elegant-git \ + --name elegant-git-${task}-${tag} \ + ${eg_bats_repository}:${tag} "${@}" + done } -__fail() { - error-box $@ - exit 1 +testing() { + --bats-workers some-bats some_tests "$@" + --report-containerized-jobs } test-commands-docs() { - ( - generate-docs - git update-index --really-refresh - git diff-index --quiet HEAD docs - ) || __fail "'docs/commands.md' is not up to date. Please run './workflows generate-docs' and commit the changes." + docker run --detach \ + --mount type=bind,source=$(pwd),target=/elegant-git \ + --name elegant-git-test-commands-docs \ + ${eg_documents_image} ci } test-docs-site() { - { - docker run --interactive --rm \ - --mount type=bind,source=$(pwd),target=/elegant-git \ - ${eg_documents_image} build - } || __fail "Unable to build the documentation site." + docker run --detach \ + --name elegant-git-test-docs-site \ + --mount type=bind,source=$(pwd),target=/elegant-git \ + ${eg_documents_image} build } test-installation() { - ( - docker run --interactive --rm \ - --mount type=bind,source=$(pwd),target=/elegant-git \ - ${eg_bats_image} bash -c " + --bats-workers installation bash -c " .workflows/installation-workflows.bash install .workflows/installation-workflows.bash smoke-tests " - ) || __fail "Installation test is failed." } ci() { - docker run --interactive --rm \ - --mount type=bind,source=$(pwd),target=/elegant-git \ - ${eg_bats_image} all_tests + --bats-workers all-bats all_tests test-commands-docs test-docs-site test-installation + --report-containerized-jobs +} + +--report-containerized-jobs() { + info-box "Waiting for containerized jobs are completed ..." + while test -n "$(docker container ls --filter name=elegant-git --format {{.Names}})"; do + local containers=($(docker container ls --filter name=elegant-git --format {{.Names}})) + info-text "${#containers[*]} jobs still working ..." + sleep 3 + done + info-box "Reporting job statuses ..." + local passes=($(docker container ls --all --filter exited=0 --format {{.Names}})) + for passed in ${passes[*]} ; do + info-text "${passed} job is passed." + done + if test ${#passes[*]} -ne 0 ; then + info-text "Remove containers for passed jobs ..." + docker container rm -f ${passes[*]} + fi + local failures=($(docker container ls --all --filter name=elegant-git --format {{.Names}})) + for failed in ${failures[*]} ; do + error-text "${failed} job is failed." + docker logs ${failed} + done + if test ${#failures[*]} -ne 0 ; then + info-text "Remove containers for failed jobs ..." + docker container rm -f ${failures[*]} + fi + if test ${#failures[*]} -ne 0; then + error-box "${#passes[*]} jobs are passed and ${#failures[*]} are failed." + error-text "Failed jobs:" + for fail in ${failures[*]}; do + error-text "- ${fail}" + done + exit 1 + else + info-box "${#passes[*]} jobs are passed." + fi } robot() { @@ -172,6 +208,7 @@ robot() { for command_file in ${command_files[@]}; do local command_name=$(basename ${command_file}) if ! test -f tests/${command_name}.bats; then continue; fi + info-box "Run tests for ${command_file}" ( testing ${command_name} && info-text "'${command_file}' testing is passed." ) || { error-text "'${command_name}' testing is failed." smart_testing_status=failed @@ -184,6 +221,7 @@ robot() { continue # already tested fi if ! test -f tests/${file_name}; then continue; fi + info-box "Run tests in ${file_name}" ( testing ${file_name} && info-text "'${file_name}' testing is passed.") || { error-text "'${file_name}' testing is failed." smart_testing_status=failed @@ -210,8 +248,8 @@ Available commands: preview-docs runs a site with the current documentation (http://localhost) serve-docs runs 'generate-docs' and 'preview-docs' testing modifications - prepare-bats-worker builds a new '${eg_bats_image}' image - publish-bats-worker pushes the '${eg_bats_image}' image + prepare-bats-worker builds a new '${eg_bats_repository}' image + publish-bats-worker pushes the '${eg_bats_repository}' image testing runs bats tests; accepts a optional pattern for tests filtering ("${BASH_SOURCE[0]} testing work" run all tests which have the word in the test name) @@ -250,8 +288,9 @@ main() { if [[ -z ${command} ]]; then question-text "Please select a command:" echo "" - select any in ${commands[@]}; do - command=${any} + select choise in ${commands[@]} exit; do + if test ${choise} = "exit"; then exit; fi + command=${choise} if test ${command} = testing; then question-text "Please give the tests pattern: " read args @@ -263,6 +302,9 @@ main() { fi info-box "Run:" ${command} ${args:-${@}} ${command} ${args:-${@}} + if [[ ${command} =~ "test-" ]]; then + --report-containerized-jobs + fi } main ${@}