diff --git a/.dockerignore b/.dockerignore index 0ac0e85..e69de29 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +0,0 @@ -* -!app/ -!config/ diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 51b0abf..9716cc9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,15 +12,63 @@ env: KOSLI_FLOW: ${{ vars.KOSLI_FLOW }} # dashboard-ci KOSLI_TRAIL: ${{ github.sha }} KOSLI_SONAR_API_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }} + AWS_ACCOUNT_ID: ${{ vars.AWS_ACCOUNT_ID }} + AWS_ECR_ID: ${{ vars.AWS_ECR_ID }} + AWS_REGION: ${{ vars.AWS_REGION }} SERVICE_NAME: ${{ github.event.repository.name }} # dashboard + IMAGE_TAR_FILENAME: /tmp/${{ github.event.repository.name }}:${{ github.sha }}.tar jobs: + setup: + runs-on: ubuntu-latest + outputs: + aws_account_id: ${{ steps.vars.outputs.aws_account_id }} + ecr_registry: ${{ steps.vars.outputs.ecr_registry }} + aws_region: ${{ steps.vars.outputs.aws_region }} + gh_actions_iam_role_name: ${{ steps.vars.outputs.gh_actions_iam_role_name }} + service_name: ${{ steps.vars.outputs.service_name }} + image_tag: ${{ steps.vars.outputs.image_tag }} + image_name: ${{ steps.vars.outputs.image_name }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Prepare outputs for workflow jobs + id: vars + run: | + IMAGE_TAG=${GITHUB_SHA:0:7} + ECR_REGISTRY="${AWS_ECR_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com" + IMAGE_NAME="${ECR_REGISTRY}/${SERVICE_NAME}:${IMAGE_TAG}" + + echo "aws_account_id=${AWS_ACCOUNT_ID}" >> ${GITHUB_OUTPUT} + echo "ecr_registry=${ECR_REGISTRY}" >> ${GITHUB_OUTPUT} + echo "aws_region=${AWS_REGION}" >> ${GITHUB_OUTPUT} + echo "gh_actions_iam_role_name=gh_actions_services" >> ${GITHUB_OUTPUT} + echo "service_name=${SERVICE_NAME}" >> ${GITHUB_OUTPUT} + echo "image_tag=${IMAGE_TAG}" >> ${GITHUB_OUTPUT} + echo "image_name=${IMAGE_NAME}" >> ${GITHUB_OUTPUT} + + - name: Setup Kosli CLI + if: ${{ github.ref == 'refs/heads/main' }} + uses: kosli-dev/setup-cli-action@v2 + with: + version: ${{ vars.KOSLI_CLI_VERSION }} + + - name: Begin Kosli Trail + if: ${{ github.ref == 'refs/heads/main' }} + run: + kosli begin trail "${{ env.KOSLI_TRAIL }}" + --flow="${{ env.KOSLI_FLOW }}" + --template-file=.kosli.yml + + pull-request: if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest - needs: [] + needs: [setup] permissions: id-token: write contents: read @@ -42,9 +90,9 @@ jobs: --name=pull-request - lint: + rubocop-lint: runs-on: ubuntu-latest - needs: [] + needs: [setup] steps: - uses: actions/checkout@v4 with: @@ -53,7 +101,7 @@ jobs: - name: Run Rubocop linter on source id: lint run: - make lint + make rubocop_lint - name: Setup Kosli CLI if: ${{ github.ref == 'refs/heads/main' && (success() || failure()) }} @@ -66,12 +114,12 @@ jobs: run: | KOSLI_COMPLIANT=$([ "${{ steps.lint.outcome }}" == 'success' ] && echo true || echo false) kosli attest generic \ - --name=dashboard.lint + --name=dashboard.rubocop-lint snyk-code-scan: runs-on: ubuntu-latest - needs: [] + needs: [setup] env: SARIF_FILENAME: snyk.code.scan.json steps: @@ -108,7 +156,7 @@ jobs: sonarcloud-scan: runs-on: ubuntu-latest - needs: [] + needs: [setup] steps: - uses: actions/checkout@v4 with: @@ -132,27 +180,14 @@ jobs: --name=dashboard.sonarcloud-scan - short-sha: - runs-on: ubuntu-latest - needs: [] - outputs: - value: ${{ steps.variable.outputs.value }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Set outputs - id: variable - run: - echo "value=${GITHUB_SHA:0:7}" >> ${GITHUB_OUTPUT} - - - image: + build-image: runs-on: ubuntu-latest - needs: [short-sha] + needs: [setup] env: - IMAGE_NAME: cyberdojo/${{ github.event.repository.name }}:${{ needs.short-sha.outputs.value }} + IMAGE_NAME: ${{ needs.setup.outputs.image_name }} + permissions: + id-token: write + contents: write outputs: tag: ${{ steps.variables.outputs.tag }} name: ${{ steps.variables.outputs.name }} @@ -162,12 +197,20 @@ jobs: with: fetch-depth: 1 - - uses: docker/login-action@v3 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 with: - username: ${{ secrets.DOCKER_USER }} - password: ${{ secrets.DOCKER_PASS }} + aws-region: ${{ needs.setup.outputs.aws_region }} + role-duration-seconds: 900 + role-session-name: ${{ github.event.repository.name }} + role-to-assume: arn:aws:iam::${{ needs.setup.outputs.aws_account_id }}:role/${{ needs.setup.outputs.gh_actions_iam_role_name }} + mask-aws-account-id: no - - name: Build and push image to Registry + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build and push image to ECR uses: docker/build-push-action@v6 id: docker_build with: @@ -180,12 +223,23 @@ jobs: - name: Make variables available to following jobs id: variables run: | - TAG=${{ needs.short-sha.outputs.value }} + TAG=${{ needs.setup.outputs.image_tag }} FINGERPRINT=$(echo ${{ steps.docker_build.outputs.digest }} | sed 's/.*://') echo "tag=${TAG}" >> ${GITHUB_OUTPUT} echo "name=${IMAGE_NAME}" >> ${GITHUB_OUTPUT} echo "fingerprint=${FINGERPRINT}" >> ${GITHUB_OUTPUT} + - name: Tar Docker image + run: | + docker pull ${{ env.IMAGE_NAME }} + docker image save ${{ env.IMAGE_NAME }} --output ${{ env.IMAGE_TAR_FILENAME }} + + - name: Cache Docker image + uses: actions/cache@v4 + with: + path: ${{ env.IMAGE_TAR_FILENAME }} + key: ${{ env.IMAGE_NAME }} + - name: Setup Kosli CLI if: ${{ github.ref == 'refs/heads/main' }} uses: kosli-dev/setup-cli-action@v2 @@ -202,20 +256,61 @@ jobs: unit-tests: runs-on: ubuntu-latest - needs: [image] + needs: [build-image] steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - - name: Run tests + - name: Retrieve Docker image from cache + uses: actions/cache@v4 + with: + path: ${{ env.IMAGE_TAR_FILENAME }} + key: ${{ needs.build-image.outputs.name }} + + - name: Load Docker image + run: + docker image load --input ${{ env.IMAGE_TAR_FILENAME }} + + - name: Run unit tests + run: + make test_server + + - name: Get Unit test coverage + id: coverage + run: + make coverage_server + + - name: Setup Kosli CLI + if: ${{ github.ref == 'refs/heads/main' && (success() || failure()) }} + uses: kosli-dev/setup-cli-action@v2 + with: + version: ${{ vars.KOSLI_CLI_VERSION }} + + - name: Attest JUnit test evidence to Kosli + if: ${{ github.ref == 'refs/heads/main' && (success() || failure()) }} + env: + KOSLI_FINGERPRINT: ${{ needs.build-image.outputs.fingerprint }} run: - ./sh/run_tests_with_coverage.sh + kosli attest junit + --name=dashboard.unit-test + --results-dir=./reports/server/junit + + - name: Attest coverage evidence to Kosli + if: ${{ github.ref == 'refs/heads/main' && (success() || failure()) }} + env: + KOSLI_FINGERPRINT: ${{ needs.build-image.outputs.fingerprint }} + run: | + KOSLI_COMPLIANT=$([ "${{ steps.coverage.outcome }}" == 'success' ] && echo true || echo false) + kosli attest generic \ + --description="unit-test branch-coverage and metrics" \ + --name=dashboard.unit-test-coverage \ + --user-data=./reports/server/coverage_metrics.json snyk-container-scan: runs-on: ubuntu-latest - needs: [image] + needs: [build-image] env: SARIF_FILENAME: snyk.container.scan.json steps: @@ -223,12 +318,22 @@ jobs: with: fetch-depth: 1 + - name: Retrieve Docker image from cache + uses: actions/cache@v4 + with: + path: ${{ env.IMAGE_TAR_FILENAME }} + key: ${{ needs.build-image.outputs.name }} + + - name: Load Docker image + run: + docker image load --input ${{ env.IMAGE_TAR_FILENAME }} + - name: Setup Snyk uses: snyk/actions/setup@master - name: Run Snyk container scan env: - IMAGE_NAME: ${{ needs.image.outputs.name }} + IMAGE_NAME: ${{ needs.build-image.outputs.name }} SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} run: snyk container test "${IMAGE_NAME}" @@ -246,7 +351,7 @@ jobs: - name: Attest evidence to Kosli if: ${{ github.ref == 'refs/heads/main' && (success() || failure()) }} env: - KOSLI_FINGERPRINT: ${{ needs.image.outputs.fingerprint }} + KOSLI_FINGERPRINT: ${{ needs.build-image.outputs.fingerprint }} run: kosli attest snyk --name=dashboard.snyk-container-scan @@ -256,7 +361,7 @@ jobs: sdlc-control-gate: if: ${{ github.ref == 'refs/heads/main' }} runs-on: ubuntu-latest - needs: [image, lint, pull-request, unit-tests, snyk-container-scan, snyk-code-scan, sonarcloud-scan] + needs: [build-image, rubocop-lint, pull-request, unit-tests, snyk-container-scan, snyk-code-scan, sonarcloud-scan] steps: - name: Setup Kosli CLI uses: kosli-dev/setup-cli-action@v2 @@ -265,15 +370,15 @@ jobs: - name: Kosli SDLC gate to short-circuit the workflow env: - IMAGE_NAME: ${{ needs.image.outputs.name }} - KOSLI_FINGERPRINT: ${{ needs.image.outputs.fingerprint }} + IMAGE_NAME: ${{ needs.build-image.outputs.name }} + KOSLI_FINGERPRINT: ${{ needs.build-image.outputs.fingerprint }} run: kosli assert artifact "${IMAGE_NAME}" approve-deployment-to-beta: runs-on: ubuntu-latest - needs: [image, sdlc-control-gate] + needs: [build-image, sdlc-control-gate] environment: name: staging url: https://beta.cyber-dojo.org @@ -289,23 +394,23 @@ jobs: - name: Report approval of deployment to Kosli env: - IMAGE_NAME: ${{ needs.image.outputs.name }} - KOSLI_FINGERPRINT: ${{ needs.image.outputs.fingerprint }} + IMAGE_NAME: ${{ needs.build-image.outputs.name }} + KOSLI_FINGERPRINT: ${{ needs.build-image.outputs.fingerprint }} KOSLI_ENVIRONMENT: aws-beta run: kosli report approval "${IMAGE_NAME}" --approver="${{ github.actor }}" deploy-to-beta: - needs: [image, approve-deployment-to-beta] + needs: [build-image, approve-deployment-to-beta] uses: ./.github/workflows/sub_deploy_to_beta.yml with: - IMAGE_TAG: ${{ needs.image.outputs.tag }} + IMAGE_TAG: ${{ needs.build-image.outputs.tag }} approve-deployment-to-prod: runs-on: ubuntu-latest - needs: [image, deploy-to-beta] + needs: [build-image, deploy-to-beta] environment: name: production url: https://cyber-dojo.org @@ -321,8 +426,8 @@ jobs: - name: Report approval of deployment to Kosli env: - IMAGE_NAME: ${{ needs.image.outputs.name }} - KOSLI_FINGERPRINT: ${{ needs.image.outputs.fingerprint }} + IMAGE_NAME: ${{ needs.build-image.outputs.name }} + KOSLI_FINGERPRINT: ${{ needs.build-image.outputs.fingerprint }} KOSLI_ENVIRONMENT: aws-prod run: kosli report approval "${IMAGE_NAME}" @@ -330,10 +435,10 @@ jobs: deploy-to-prod: - needs: [image, approve-deployment-to-prod] + needs: [build-image, approve-deployment-to-prod] uses: ./.github/workflows/sub_deploy_to_prod.yml with: - IMAGE_TAG: ${{ needs.image.outputs.tag }} + IMAGE_TAG: ${{ needs.build-image.outputs.tag }} # The cyberdojo/versioner refresh-env.sh script @@ -345,9 +450,9 @@ jobs: push-latest: runs-on: ubuntu-latest - needs: [image, deploy-to-prod] + needs: [build-image, deploy-to-prod] env: - IMAGE_NAME: ${{ needs.image.outputs.name }} + IMAGE_NAME: ${{ needs.build-image.outputs.name }} steps: - uses: docker/login-action@v3 with: @@ -357,5 +462,5 @@ jobs: - name: Tag image to :latest and push to Dockerhub Registry run: | docker pull "${IMAGE_NAME}" - docker tag "${IMAGE_NAME}" cyberdojo/${{ env.SERVICE_NAME }}:latest - docker push cyberdojo/${{ env.SERVICE_NAME }}:latest + docker tag "${IMAGE_NAME}" cyberdojo/dashboard:latest + docker push cyberdojo/dashboard:latest diff --git a/.gitignore b/.gitignore index 47c6c34..c0baa86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ snyk.container.scan.json -test/server/reports/* -test/server/reports/.* - -test/client/reports/* -test/client/reports/.* +reports/* +reports/.* diff --git a/.kosli.yml b/.kosli.yml index daf8037..23c9101 100644 --- a/.kosli.yml +++ b/.kosli.yml @@ -7,7 +7,7 @@ trail: artifacts: - name: dashboard attestations: - - name: lint + - name: rubocop-lint type: generic - name: snyk-code-scan type: snyk @@ -15,3 +15,7 @@ trail: type: snyk - name: sonarcloud-scan type: sonar + - name: unit-test + type: junit + - name: unit-test-coverage + type: generic diff --git a/.rubocop.yml b/.rubocop.yml index 7f1674c..f18f20d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,114 +3,127 @@ AllCops: Layout/LineLength: Exclude: - - test/simplecov_json.rb + - test/simplecov_formatter_json.rb - test/data/cyber-dojo/kata_test_data.rb -Naming/VariableNumber: +Layout/SpaceInsideArrayLiteralBrackets: Exclude: - - test/data/cyber-dojo/kata_test_data.rb + - test/server/check_test_metrics.rb -Naming/MethodName: +Layout/SpaceInsideReferenceBrackets: Exclude: - - test/data/cyber-dojo/kata_test_data.rb + - test/server/check_test_metrics.rb -Naming/MethodParameterName: +Lint/IneffectiveAccessModifier: Exclude: - - test/id58_test_base.rb - - app/helpers/td_gapper.rb - - app/app_base.rb + - source/server/app/app_base.rb Metrics/AbcSize: Exclude: - - test/simplecov_json.rb + - test/simplecov_formatter_json.rb - test/id58_test_base.rb - - app/helpers/td_gapper.rb - - app/helpers/gatherer.rb - - app/helpers/avatars_progress.rb + - test/server/check_test_metrics.rb + - source/server/app/helpers/td_gapper.rb + - source/server/app/helpers/gatherer.rb + - source/server/app/helpers/avatars_progress.rb Metrics/BlockLength: Exclude: - test/server/td_gapper_test.rb - - app/app_base.rb + - source/server/app/app_base.rb Metrics/ClassLength: Exclude: - test/server/td_gapper_test.rb +Metrics/CyclomaticComplexity: + Exclude: + - test/id58_test_base.rb + - source/server/app/helpers/td_gapper.rb + - source/server/app/helpers/avatars_progress.rb + Metrics/ModuleLength: Exclude: - test/data/cyber-dojo/kata_test_data.rb Metrics/MethodLength: Exclude: - - test/simplecov_json.rb - - test/data/cyber-dojo/kata_test_data.rb - test/id58_test_base.rb - - app/helpers/td_gapper.rb - - app/helpers/gatherer.rb - - app/helpers/avatars_progress.rb + - test/slim_json_reporter.rb + - test/simplecov_formatter_json.rb + - test/data/cyber-dojo/kata_test_data.rb + - test/server/check_test_metrics.rb + - source/server/app/helpers/td_gapper.rb + - source/server/app/helpers/gatherer.rb + - source/server/app/helpers/avatars_progress.rb Metrics/PerceivedComplexity: Exclude: - - app/helpers/td_gapper.rb - - app/helpers/avatars_progress.rb + - source/server/app/helpers/td_gapper.rb + - source/server/app/helpers/avatars_progress.rb -Metrics/CyclomaticComplexity: +Naming/MethodName: + Exclude: + - test/data/cyber-dojo/kata_test_data.rb + +Naming/MethodParameterName: Exclude: - test/id58_test_base.rb - - app/helpers/td_gapper.rb - - app/helpers/avatars_progress.rb + - source/server/app/helpers/td_gapper.rb + - source/server/app/app_base.rb -Lint/IneffectiveAccessModifier: +Naming/VariableNumber: Exclude: - - app/app_base.rb + - test/data/cyber-dojo/kata_test_data.rb -Style/ClassVars: +Security/Eval: Exclude: - - test/id58_test_base.rb + - test/server/check_test_metrics.rb -Style/FormatStringToken: +Style/CaseEquality: Exclude: - test/id58_test_base.rb + - source/client/code/app_base.rb + - source/server/config/config.ru + - source/server/app/app_base.rb -Style/CaseEquality: +Style/ClassAndModuleChildren: + Exclude: + - test/slim_json_reporter.rb + +Style/ClassVars: Exclude: - test/id58_test_base.rb - - test/client/code/app_base.rb - - config/config.ru - - app/app_base.rb Style/Documentation: Exclude: - - app/http_json_hash/unpacker.rb - - app/http_json_hash/service_error.rb - - app/http_json_hash/service.rb - - app/http_json_hash/requester.rb - - test/simplecov_json.rb + - test/slim_json_reporter.rb + - test/simplecov_formatter_json.rb + - test/id58_test_base.rb - test/server/test_base.rb - test/server/td_gapper_test.rb - - test/server/td_gapper_test.rb - test/server/gather_test.rb - test/server/avatars_progress_test.rb - - test/id58_test_base.rb - test/data/cyber-dojo/kata_test_data.rb - - test/client/code/http_json_hash/unpacker.rb - - test/client/code/http_json_hash/service_error.rb - - test/client/code/http_json_hash/service.rb - - test/client/code/http_json_hash/requester.rb - - test/client/code/externals.rb - - test/client/code/external_saver.rb - - test/client/code/external_http.rb - - test/client/code/external_dashboard.rb - - test/client/code/dashboard.rb - - test/client/code/app_base.rb - - test/client/code/app.rb - - app/helpers/td_gapper.rb - - app/helpers/light.rb - - app/helpers/app_helpers.rb - - app/externals.rb - - app/external_time.rb - - app/external_saver.rb - - app/external_http.rb - - app/app_base.rb - - app/app.rb + - source/**/*.rb + +Style/DocumentDynamicEvalDefinition: + Exclude: + - test/server/check_test_metrics.rb + +Style/EvalWithLocation: + Exclude: + - test/server/check_test_metrics.rb + +Style/FormatString: + Exclude: + - test/server/check_test_metrics.rb + +Style/FormatStringToken: + Exclude: + - test/id58_test_base.rb + - test/server/check_test_metrics.rb + +Style/TrailingCommaInArrayLiteral: + Exclude: + - test/server/check_test_metrics.rb diff --git a/Dockerfile b/Dockerfile index 1414544..ffdd566 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM cyberdojo/sinatra-base:11ddc45 +FROM cyberdojo/sinatra-base:026c095 LABEL maintainer=jon@jaggersoft.com WORKDIR /dashboard -COPY . . +COPY source/server . ARG COMMIT_SHA ENV SHA=${COMMIT_SHA} diff --git a/Makefile b/Makefile index 74961c9..0e43424 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,18 @@ -SHORT_SHA := $(shell git rev-parse HEAD | head -c7) -IMAGE_NAME := cyberdojo/dashboard:${SHORT_SHA} +image_server: + @${PWD}/bin/build_image.sh server -.PHONY: all image test lint snyk demo image +test_server: + @${PWD}/bin/run_tests.sh server -all: image test lint snyk demo +coverage_server: + @${PWD}/bin/check_coverage.sh server -image: - ${PWD}/build_test.sh -bo +rubocop_lint: + @${PWD}/bin/rubocop_lint.sh -test: - ${PWD}/build_test.sh - -lint: - docker run --rm --volume "${PWD}:/app" cyberdojo/rubocop --raise-cop-error - -snyk-container: image - snyk container test ${IMAGE_NAME} \ - --file=Dockerfile \ - --json-file-output=snyk.container.scan.json \ - --policy-path=.snyk +snyk_container_scan: + @${PWD}/bin/snyk_container_scan.sh demo: - ${PWD}/demo.sh + @${PWD}/bin/demo.sh diff --git a/app/helpers/app_helpers.rb b/app/helpers/app_helpers.rb deleted file mode 100644 index 8a08148..0000000 --- a/app/helpers/app_helpers.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -require_relative 'gatherer' -require_relative 'avatars_progress' - -module AppHelpers - def time - externals.time - end -end diff --git a/bin/build_image.sh b/bin/build_image.sh new file mode 100755 index 0000000..8f6e7a8 --- /dev/null +++ b/bin/build_image.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -Eeu + +export ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +source "${ROOT_DIR}/bin/lib.sh" + +show_help() +{ + local -r MY_NAME=$(basename "${BASH_SOURCE[0]}") + cat <<- EOF + + Use: ${MY_NAME} {server|client} + + Options: + server - build the server image (local only) + client - build the client image (local and CI workflow) + +EOF +} + +check_args() +{ + case "${1:-}" in + '-h' | '--help') + show_help + exit 0 + ;; + 'server' | 'client') + ;; + '') + show_help + stderr "no argument - must be 'client' or 'server'" + exit 42 + ;; + *) + show_help + stderr "argument is '${1:-}' - must be 'client' or 'server'" + exit 42 + esac +} + +build_image() +{ + check_args "$@" + + local -r type="${1}" + + if [ -n "${CI:-}" ] && [ "${type}" == 'server' ] ; then + stderr "In CI workflow - use previous docker/build-push-action@v6 GitHub Action" + exit 42 + fi + + exit_non_zero_unless_installed docker + export $(echo_versioner_env_vars) + + containers_down + remove_old_images + + docker compose build --build-arg COMMIT_SHA="${COMMIT_SHA}" server + #if [ "${type}" == 'client' ]; then + # docker compose build --build-arg COMMIT_SHA="${COMMIT_SHA}" client + #fi + + local -r image_name="${CYBER_DOJO_DASHBOARD_IMAGE}:${CYBER_DOJO_DASHBOARD_TAG}" + local -r sha_in_image=$(docker run --rm --entrypoint="" "${image_name}" sh -c 'echo -n ${SHA}') + if [ "${COMMIT_SHA}" != "${sha_in_image}" ]; then + echo "ERROR: unexpected env-var inside image ${image_name}" + echo "expected: 'SHA=${COMMIT_SHA}'" + echo " actual: 'SHA=${sha_in_image}'" + exit 42 + fi + + # Tag image-name for local development where dashboard's name comes from echo-versioner-env-vars + if [ "${type}" == 'server' ]; then + docker tag "${image_name}" "cyberdojo/dashboard:${CYBER_DOJO_DASHBOARD_TAG}" + echo "CYBER_DOJO_DASHBOARD_SHA=${CYBER_DOJO_DASHBOARD_SHA}" + echo "CYBER_DOJO_DASHBOARD_TAG=${CYBER_DOJO_DASHBOARD_TAG}" + fi +} + +build_image "$@" \ No newline at end of file diff --git a/bin/check_coverage.sh b/bin/check_coverage.sh new file mode 100755 index 0000000..6cfc3f1 --- /dev/null +++ b/bin/check_coverage.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +set -Eeu + +export ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +source "${ROOT_DIR}/bin/lib.sh" + +show_help() +{ + local -r MY_NAME=$(basename "${BASH_SOURCE[0]}") + cat <<- EOF + + Use: ${MY_NAME} {server|client} + + Check test coverage (and other metrics) for tests run from inside the client or server container only + +EOF +} + +check_args() +{ + case "${1:-}" in + '-h' | '--help') + show_help + exit 0 + ;; + 'server' | 'client') + ;; + '') + show_help + stderr "no argument - must be 'client' or 'server'" + exit 42 + ;; + *) + show_help + stderr "argument is '${1:-}' - must be 'client' or 'server'" + exit 42 + esac +} + +check_coverage() +{ + check_args "$@" + export $(echo_versioner_env_vars) + + local -r TYPE="${1}" # {server|client} + local -r TEST_LOG=test.log + local -r HOST_TEST_DIR="${ROOT_DIR}/test/${TYPE}" + local -r HOST_REPORTS_DIR="${ROOT_DIR}/reports/${TYPE}" # where report json files have been written to + local -r CONTAINER_TMP_DIR=/tmp + + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/${TEST_LOG}" + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/test_metrics.json" + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/coverage_metrics.json" + exit_non_zero_unless_file_exists "${HOST_TEST_DIR}/check_test_metrics.rb" + + set +e + docker run \ + --read-only \ + --rm \ + --entrypoint="" \ + --env COVERAGE_ROOT="${CONTAINER_TMP_DIR}" \ + --env COVERAGE_CODE_TAB_NAME=app \ + --env COVERAGE_TEST_TAB_NAME=test \ + --volume ${HOST_REPORTS_DIR}/test_metrics.json:${CONTAINER_TMP_DIR}/test_metrics.json:ro \ + --volume ${HOST_REPORTS_DIR}/coverage_metrics.json:${CONTAINER_TMP_DIR}/coverage_metrics.json:ro \ + --volume ${HOST_TEST_DIR}/check_test_metrics.rb:${CONTAINER_TMP_DIR}/check_test_metrics.rb:ro \ + "${CYBER_DOJO_DASHBOARD_IMAGE}:${CYBER_DOJO_DASHBOARD_TAG}" \ + sh -c "ruby ${CONTAINER_TMP_DIR}/check_test_metrics.rb" \ + | tee -a "${HOST_REPORTS_DIR}/${TEST_LOG}" + + local -r STATUS=${PIPESTATUS[0]} + set -e + + echo "${TYPE} coverage status == ${STATUS}" + echo + return "${STATUS}" +} + +check_coverage "$@" diff --git a/demo.sh b/bin/demo.sh similarity index 51% rename from demo.sh rename to bin/demo.sh index 2fb84f4..b6fdf0d 100755 --- a/demo.sh +++ b/bin/demo.sh @@ -1,19 +1,11 @@ #!/usr/bin/env bash set -Eeu -export MY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -export SCRIPTS_DIR="${MY_DIR}/sh" +export ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +source "${ROOT_DIR}/bin/lib.sh" +#source "${SCRIPTS_DIR}/containers_up_healthy_and_clean.sh" +export $(echo_versioner_env_vars) -source "${SCRIPTS_DIR}/lib.sh" -source "${SCRIPTS_DIR}/build_images.sh" -source "${SCRIPTS_DIR}/config.sh" -source "${SCRIPTS_DIR}/containers_down.sh" -source "${SCRIPTS_DIR}/containers_up_healthy_and_clean.sh" -source "${SCRIPTS_DIR}/copy_in_saver_test_data.sh" - -export $(docker run --rm cyberdojo/versioner) - -#- - - - - - - - - - - - - - - - - - - - - - - - - - - curl_smoke_test() { echo curl log in $(log_filename) @@ -23,12 +15,11 @@ curl_smoke_test() curl_json_body_200 ready curl_json_body_200 sha - curl_plain_200 assets/app.css 'Content-Type: text/css' - curl_plain_200 assets/app.js 'Content-Type: application/javascript' + curl_plain_200 assets/app.css 'content-type: text/css' + curl_plain_200 assets/app.js 'content-type: application/javascript' curl_plain_200 show/FxWwrr dashboard-page } -#- - - - - - - - - - - - - - - - - - - - - - - - - - - curl_json() { local -r route="${1}" # eg ready @@ -44,14 +35,13 @@ curl_json() > "$(log_filename)" 2>&1 } -#- - - - - - - - - - - - - - - - - - - - - - - - - - - curl_json_body_200() { local -r route="${1}" # eg ready echo -n "GET ${route} => 200 ...|" if curl_json "${route}" && grep --quiet 200 "$(log_filename)"; then local -r result=$(tail -n 1 "$(log_filename)") - echo "${result}" + echo "${result} SUCCESS" else echo FAILED echo @@ -60,7 +50,6 @@ curl_json_body_200() fi } -#- - - - - - - - - - - - - - - - - - - - - - - - - - - curl_plain() { local -r route="${1}" # eg dashboard/choose @@ -73,7 +62,6 @@ curl_plain() > "$(log_filename)" 2>&1 } -#- - - - - - - - - - - - - - - - - - - - - - - - - - - curl_plain_200() { local -r route="${1}" # eg dashboard/choose @@ -90,21 +78,28 @@ curl_plain_200() fi } -#- - - - - - - - - - - - - - - - - - - - - - - - - - - log_filename() { echo -n /tmp/dashboard.log; } -#- - - - - - - - - - - - - - - - - - - - - - - - - - - -build_images server -build_images client -build_images nginx -docker compose up --detach nginx -server_up_healthy_and_clean $(server_name) -copy_in_saver_test_data -curl_smoke_test -if [ "${1:-}" == '--no-browser' ]; then - containers_down -else - open "http://localhost/dashboard/show/REf1t8?auto_refresh=true&minute_columns=true" - open "http://localhost/dashboard/show/FxWwrr?auto_refresh=true&minute_columns=true" - open "http://localhost/dashboard/show/LyQpFr?auto_refresh=true&minute_columns=true" -fi +server_port() { echo "${CYBER_DOJO_DASHBOARD_PORT}"; } + + +demo() +{ + docker compose build --build-arg COMMIT_SHA="${COMMIT_SHA}" server + docker compose build --build-arg COMMIT_SHA="${COMMIT_SHA}" client + docker compose build --build-arg COMMIT_SHA="${COMMIT_SHA}" nginx + docker compose --progress=plain up --detach --no-build --wait --wait-timeout=10 nginx + docker compose --progress=plain up --detach --no-build --wait --wait-timeout=10 server + exit_non_zero_unless_started_cleanly + copy_in_saver_test_data + curl_smoke_test + if [ "${1:-}" == '--no-browser' ]; then + containers_down + else + open "http://localhost/dashboard/show/REf1t8?auto_refresh=true&minute_columns=true" + open "http://localhost/dashboard/show/FxWwrr?auto_refresh=true&minute_columns=true" + open "http://localhost/dashboard/show/LyQpFr?auto_refresh=true&minute_columns=true" + fi +} + +demo "$@" \ No newline at end of file diff --git a/bin/lib.sh b/bin/lib.sh new file mode 100755 index 0000000..5df936b --- /dev/null +++ b/bin/lib.sh @@ -0,0 +1,159 @@ +#!/usr/bin/env bash +set -Eeu + +echo_versioner_env_vars() +{ + local -r sha="$(cd "${ROOT_DIR}" && git rev-parse HEAD)" + echo COMMIT_SHA="${sha}" + + docker run --rm cyberdojo/versioner + + echo CYBER_DOJO_DASHBOARD_SHA="${sha}" + echo CYBER_DOJO_DASHBOARD_TAG="${sha:0:7}" + + echo CYBER_DOJO_DASHBOARD_CLIENT_IMAGE=cyberdojo/dashboard-client + echo CYBER_DOJO_DASHBOARD_CLIENT_PORT=9999 + + echo CYBER_DOJO_DASHBOARD_CLIENT_USER=nobody + echo CYBER_DOJO_DASHBOARD_SERVER_USER=nobody + # + echo CYBER_DOJO_DASHBOARD_CLIENT_CONTAINER_NAME=test_dashboard_client + echo CYBER_DOJO_DASHBOARD_SERVER_CONTAINER_NAME=test_dashboard_server + # + local -r AWS_ACCOUNT_ID=244531986313 + local -r AWS_REGION=eu-central-1 + echo CYBER_DOJO_DASHBOARD_IMAGE="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/dashboard" +} + +containers_down() +{ + docker compose down --remove-orphans --volumes +} + +remove_old_images() +{ + echo Removing old images + local -r dil=$(docker image ls --format "{{.Repository}}:{{.Tag}}" | grep dashboard) + remove_all_but_latest "${dil}" "${CYBER_DOJO_DASHBOARD_CLIENT_IMAGE}" + remove_all_but_latest "${dil}" "${CYBER_DOJO_DASHBOARD_IMAGE}" + remove_all_but_latest "${dil}" cyberdojo/dashboard +} + +remove_all_but_latest() +{ + # Keep latest in the cache + local -r docker_image_ls="${1}" + local -r name="${2}" + for image_name in $(echo "${docker_image_ls}" | grep "${name}:") + do + if [ "${image_name}" != "${name}:latest" ]; then + docker image rm "${image_name}" + fi + done + docker system prune --force +} + +exit_non_zero_unless_file_exists() +{ + local -r filename="${1}" + if [ ! -f "${filename}" ]; then + stderr "${filename} does not exist" + exit 42 + fi +} + +exit_non_zero_unless_installed() +{ + for dependent in "$@" + do + if ! installed "${dependent}" ; then + stderr "${dependent} is not installed!" + exit 42 + fi + done +} + +installed() +{ + if hash "${1}" &> /dev/null; then + true + else + false + fi +} + +stderr() +{ + local -r message="${1}" + >&2 echo "ERROR: ${message}" +} + +copy_in_saver_test_data() +{ + local -r SAVER_CID=$(docker ps --filter status=running --format '{{.Names}}' | grep "saver") + local -r SRC_PATH=${ROOT_DIR}/test/data/cyber-dojo + local -r DEST_PATH=/cyber-dojo + # You cannot docker cp to a tmpfs, so tar-piping instead... + cd ${SRC_PATH} \ + && tar --no-xattrs -c . \ + | docker exec -i ${SAVER_CID} tar x -C ${DEST_PATH} +} + +exit_non_zero_unless_started_cleanly() +{ + local -r SERVICE_NAME=server + # Handle known warnings (eg waiting on Gem upgrade) + # local -r SHADOW_WARNING="server.rb:(.*): warning: shadowing outer local variable - filename" + # DOCKER_LOG=$(strip_known_warning "${DOCKER_LOG}" "${SHADOW_WARNING}") + if [ "$(top_5)" != "$(clean_top_5)" ]; then + echo + echo "${SERVICE_NAME} did not start cleanly: docker log..." + echo 'expected------------------' + echo "$(clean_top_5)" + echo + echo 'actual--------------------' + echo "$(top_5)" + echo + echo 'diff--------------------' + grep -Fxvf <(clean_top_5) <(top_5) + echo + exit 42 + fi +} + +top_5() +{ + echo_docker_log | head -5 +} + +clean_top_5() +{ + # 1st 5 lines on Puma + local -r L1="Puma starting in single mode..." + local -r L2='* Puma version: 6.4.3 (ruby 3.3.5-p100) ("The Eagle of Durango")' + local -r L3="* Min threads: 0" + local -r L4="* Max threads: 5" + local -r L5="* Environment: production" + # + local -r all5="$(printf "%s\n%s\n%s\n%s\n%s" "${L1}" "${L2}" "${L3}" "${L4}" "${L5}")" + echo "${all5}" +} + +echo_docker_log() +{ + docker logs "${CYBER_DOJO_DASHBOARD_SERVER_CONTAINER_NAME}" 2>&1 +} + +strip_known_warning() +{ + local -r DOCKER_LOG="${1}" + local -r KNOWN_WARNING="${2}" + local -r STRIPPED=$(echo -n "${DOCKER_LOG}" | grep --invert-match -E "${KNOWN_WARNING}") + if [ "${DOCKER_LOG}" != "${STRIPPED}" ]; then + echo "Known service start-up warning found: ${KNOWN_WARNING}" + else + echo "Known service start-up warning NOT found: ${KNOWN_WARNING}" + exit 42 + fi + echo "${STRIPPED}" +} diff --git a/bin/rubocop_lint.sh b/bin/rubocop_lint.sh new file mode 100755 index 0000000..1a4ee41 --- /dev/null +++ b/bin/rubocop_lint.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -Eeu + +export ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +docker run --rm --volume "${ROOT_DIR}:/app" cyberdojo/rubocop --raise-cop-error diff --git a/bin/run_tests.sh b/bin/run_tests.sh new file mode 100755 index 0000000..71dce3e --- /dev/null +++ b/bin/run_tests.sh @@ -0,0 +1,124 @@ +#!/usr/bin/env bash +set -Eeu + +export ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +source "${ROOT_DIR}/bin/lib.sh" + +show_help() +{ + local -r MY_NAME=$(basename "${BASH_SOURCE[0]}") + cat <<- EOF + + Use: ${MY_NAME} {server|client} [ID...] + + Options: + client - run tests inside the client container only + server - run tests inside the server container only + ID... - run tests matching these identifiers only + + Example: + ${MY_NAME} server 0D6 + ... + Finished tests in 0.027497s, 72.7358 tests/s, 72.7358 assertions/s. + 2 tests, 2 assertions, 0 failures, 0 errors, 0 skips + ... + +EOF +} + +check_args() +{ + case "${1:-}" in + '-h' | '--help') + show_help + exit 0 + ;; + 'server') + export $(echo_versioner_env_vars) + export CONTAINER_NAME="${CYBER_DOJO_DASHBOARD_SERVER_CONTAINER_NAME}" + export USER="${CYBER_DOJO_DASHBOARD_SERVER_USER}" + ;; + 'client') + export $(echo_versioner_env_vars) + export CONTAINER_NAME="${CYBER_DOJO_DASHBOARD_CLIENT_CONTAINER_NAME}" + export USER="${CYBER_DOJO_DASHBOARD_CLIENT_USER}" + ;; + '') + show_help + stderr "no argument - must be 'client' or 'server'" + exit 42 + ;; + *) + show_help + stderr "first argument is '${1:-}' - must be 'client' or 'server'" + exit 42 + esac +} + +run_tests() +{ + check_args "$@" + exit_non_zero_unless_installed docker + export SERVICE_NAME="${1}" + # Don't do a build here, because in CI workflow, server image is built with GitHub Action + docker compose --progress=plain up --no-build --wait --wait-timeout=10 "${SERVICE_NAME}" + + exit_non_zero_unless_started_cleanly + copy_in_saver_test_data + + local -r TYPE="${1}" # {server|client} + local -r TEST_LOG=test.log + local -r CONTAINER_COVERAGE_DIR="/tmp/reports" + local -r HOST_REPORTS_DIR="${ROOT_DIR}/reports/${TYPE}" + + echo '==================================' + echo "Running ${TYPE} tests" + echo '==================================' + + # CONTAINER_NAME is running with read-only:true and a non-root user (in docker-compose.yml). I want to + # keep those settings in the docker-exec call below since that is how I want the microservice to run. + # The [docker exec run.sh] is creating coverage files which I process on the host after it completes. + # I've tried using an :rw volume mount (eg /reports) in docker-compose.yml and writing the + # coverage files to /reports, so they automatically end up on the host. I cannot find a way that works + # on both my M2 laptop, and in the CI workflow. So I am writing the coverage files to /tmp and + # tar-piping them out. + + set +e + docker exec \ + --env COVERAGE_CODE_TAB_NAME=app \ + --env COVERAGE_TEST_TAB_NAME=test \ + --user "${USER}" \ + "${CONTAINER_NAME}" \ + sh -c "/dashboard/test/run.sh ${CONTAINER_COVERAGE_DIR} ${TEST_LOG} ${TYPE} ${*:2}" + local -r STATUS=$? + set -e + + rm -rf "${HOST_REPORTS_DIR}" &> /dev/null || true + mkdir -p "${HOST_REPORTS_DIR}" &> /dev/null || true + + docker exec --user "${USER}" "${CONTAINER_NAME}" tar Ccf "${CONTAINER_COVERAGE_DIR}" - . \ + | tar Cxf "${HOST_REPORTS_DIR}" - + + # Check we generated the expected files. + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/${TEST_LOG}" + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/index.html" + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/test_metrics.json" + exit_non_zero_unless_file_exists "${HOST_REPORTS_DIR}/coverage_metrics.json" + + echo "${TYPE} test branch-coverage report is at:" + echo "${HOST_REPORTS_DIR}/index.html" + echo + echo "${TYPE} test status == ${STATUS}" + echo + + if [ "${STATUS}" != 0 ]; then + echo Docker logs "${CONTAINER_NAME}" + echo + docker logs "${CONTAINER_NAME}" 2>&1 + fi + + return ${STATUS} +} + +run_tests "$@" \ No newline at end of file diff --git a/bin/snyk_container_scan.sh b/bin/snyk_container_scan.sh new file mode 100755 index 0000000..e80ebb7 --- /dev/null +++ b/bin/snyk_container_scan.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -Eeu + +export ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +source "${ROOT_DIR}/bin/lib.sh" +export $(echo_versioner_env_vars) + +snyk container test ${CYBER_DOJO_DASHBOARD_IMAGE}:${CYBER_DOJO_DASHBOARD_TAG} \ + --file="${ROOT_DIR}/Dockerfile" \ + --json-file-output="${ROOT_DIR}/snyk.container.scan.json" \ + --policy-path="${ROOT_DIR}/.snyk" diff --git a/build_test.sh b/build_test.sh deleted file mode 100755 index 7fa5d37..0000000 --- a/build_test.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -export MY_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -export SCRIPTS_DIR="${MY_DIR}/sh" - -source "${SCRIPTS_DIR}/lib.sh" -source "${SCRIPTS_DIR}/build_images.sh" -source "${SCRIPTS_DIR}/config.sh" -source "${SCRIPTS_DIR}/containers_down.sh" -source "${SCRIPTS_DIR}/containers_up_healthy_and_clean.sh" -source "${SCRIPTS_DIR}/copy_in_saver_test_data.sh" -source "${SCRIPTS_DIR}/exit_non_zero_unless_installed.sh" -source "${SCRIPTS_DIR}/exit_zero_if_build_only.sh" -source "${SCRIPTS_DIR}/exit_zero_if_show_help.sh" -source "${SCRIPTS_DIR}/test_in_containers.sh" -source "${SCRIPTS_DIR}/echo_versioner_env_vars.sh" -export $(echo_versioner_env_vars) - -#- - - - - - - - - - - - - - - - - - - - - - - -exit_zero_if_show_help "$@" -exit_non_zero_unless_installed docker - -build_images server -exit_zero_if_build_only "$@" -server_up_healthy_and_clean server -#client_up_healthy_and_clean "$@" -copy_in_saver_test_data -test_in_containers server # no client tests -containers_down diff --git a/docker-compose.yml b/docker-compose.yml index 5e3e4b5..b47c90e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,21 +12,20 @@ services: user: root client: - image: cyberdojo/dashboard-client:${COMMIT_TAG} - user: nobody + image: ${CYBER_DOJO_DASHBOARD_CLIENT_IMAGE}:${CYBER_DOJO_DASHBOARD_TAG} + user: ${CYBER_DOJO_DASHBOARD_CLIENT_USER} build: args: [ COMMIT_SHA ] - context: test/client - container_name: test_dashboard_client + context: source/client + container_name: ${CYBER_DOJO_DASHBOARD_CLIENT_CONTAINER_NAME} depends_on: - server env_file: [ .env ] - ports: [ 9999:9999 ] + ports: [ "${CYBER_DOJO_DASHBOARD_CLIENT_PORT}:${CYBER_DOJO_DASHBOARD_CLIENT_PORT}" ] read_only: true - restart: "no" + restart: no volumes: - - ./test/client:/dashboard:ro - - ./test:/test:ro + - ./test:/dashboard/test:ro - type: tmpfs target: /tmp tmpfs: @@ -34,22 +33,21 @@ services: size: 10485760 # 10MB server: - image: cyberdojo/dashboard:${COMMIT_TAG} - user: nobody + image: ${CYBER_DOJO_DASHBOARD_IMAGE}:${CYBER_DOJO_DASHBOARD_TAG} + user: ${CYBER_DOJO_DASHBOARD_SERVER_USER} build: - context: . args: [ COMMIT_SHA ] - container_name: test_dashboard_server + context: . + container_name: ${CYBER_DOJO_DASHBOARD_SERVER_CONTAINER_NAME} depends_on: - differ - saver env_file: [ .env ] - ports: [ 4527:4527 ] + ports: [ "${CYBER_DOJO_DASHBOARD_PORT}:${CYBER_DOJO_DASHBOARD_PORT}" ] read_only: true - restart: "no" + restart: no volumes: - - ./app:/app:ro - - ./test:/test:ro + - ./test:/dashboard/test:ro - type: tmpfs target: /tmp tmpfs: @@ -73,7 +71,7 @@ services: env_file: [ .env ] init: true read_only: true - restart: "no" + restart: no tmpfs: - /cyber-dojo:uid=19663,gid=65533 - /tmp:uid=19663,gid=65533 diff --git a/sh/build_images.sh b/sh/build_images.sh deleted file mode 100755 index 656c285..0000000 --- a/sh/build_images.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -# - - - - - - - - - - - - - - - - - - - - - - -build_images() -{ - if [ "${1:-}" == "" ]; then - echo - echo "ERROR: no argument supplied" - exit_zero_if_show_help --help - fi - local -r dil=$(docker image ls --format "{{.Repository}}:{{.Tag}}" --filter=reference="$(server_image)*:*") - remove_old_images "${dil:-}" - build_tagged_images "$@" -} - -# - - - - - - - - - - - - - - - - - - - - - - -build_tagged_images() -{ - local -r target="${1:-$(server_name)}" - - docker compose \ - build \ - --build-arg COMMIT_SHA=$(commit_sha) "${target}" - - if [ "${target}" == $(server_name) ]; then - docker tag $(server_image):$(image_tag) $(server_image):latest - check_embedded_env_var "$(server_image):latest" - fi - if [ "${target}" == $(client_name) ]; then - docker tag $(client_image):$(image_tag) $(client_image):latest - check_embedded_env_var "$(client_image):latest" - fi - - echo - echo "echo CYBER_DOJO_${SERVICE_NAME}_SHA=$(image_sha)" - echo "echo CYBER_DOJO_${SERVICE_NAME}_TAG=$(image_tag)" - echo -} - -# - - - - - - - - - - - - - - - - - - - - - - -check_embedded_env_var() -{ - local -r image_name="${1}" - local -r expected="$(commit_sha)" - local -r actual="$(sha_in_image ${image_name})" - echo "Checking SHA env-var is embedded inside ${image_name}" - if [ "${expected}" == "${actual}" ]; then - echo It is - else - echo "ERROR: unexpected env-var inside image ${image_name}" - echo "expected: 'SHA=${expected}'" - echo " actual: 'SHA=${actual}'" - exit 42 - fi -} - -# - - - - - - - - - - - - - - - - - - - - - - -sha_in_image() -{ - local -r image_name="${1}" - docker run --rm ${image_name} sh -c 'echo -n ${SHA}' -} - -# - - - - - - - - - - - - - - - - - - - - - - -remove_old_images() -{ - echo Removing old images - local -r dil="${1:-}" - remove_all_but_latest "$(server_image)" "${dil}" - remove_all_but_latest "$(client_image)" "${dil}" -} - -# - - - - - - - - - - - - - - - - - - - - - - -remove_all_but_latest() -{ - local -r name="${1}" - local -r docker_image_ls="${2:-}" - for v in `echo "${docker_image_ls}" | grep "${name}:"` - do - if [ "${v}" != "${name}:latest" ]; then - if [ "${v}" != "${name}:" ]; then - if [ "${v}" != "${name}:$(image_tag)" ]; then - docker image rm "${v}" - fi - fi - fi - done - docker system prune --force -} - diff --git a/sh/config.sh b/sh/config.sh deleted file mode 100755 index eae2a05..0000000 --- a/sh/config.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -commit_sha() { echo -n $(cd "$(repo_root)" && git rev-parse HEAD); } - -image_sha() { commit_sha; } -image_tag() { image_sha | cut -c1-7; } - -export COMMIT_TAG=$(image_tag) -export SERVICE_NAME=DASHBOARD -export SERVICE_NAME_LOWER=$(echo "${SERVICE_NAME}" | tr '[:upper:]' '[:lower:]') - -SERVER_IMAGE="CYBER_DOJO_${SERVICE_NAME}_IMAGE" # from cyberdojo/versioner -SERVER_PORT="CYBER_DOJO_${SERVICE_NAME}_PORT" # from cyberdojo/versioner - -server_name() { echo -n server; } -server_image() { echo -n "${!SERVER_IMAGE}"; } -server_port() { echo -n "${!SERVER_PORT}"; } -server_user() { echo -n nobody; } -server_container() { echo -n test_${SERVICE_NAME_LOWER}_server; } - -client_name() { echo -n client; } -client_image() { echo -n "cyberdojo/${SERVICE_NAME_LOWER}-client"; } -client_port() { echo -n 9999; } -client_user() { echo -n nobody; } -client_container() { echo -n test_${SERVICE_NAME_LOWER}_client; } - -sources_dir() { echo -n sources; } -tests_dir() { echo -n test; } \ No newline at end of file diff --git a/sh/containers_down.sh b/sh/containers_down.sh deleted file mode 100755 index 1e102f6..0000000 --- a/sh/containers_down.sh +++ /dev/null @@ -1,9 +0,0 @@ - -# - - - - - - - - - - - - - - - - - - - - - - -containers_down() -{ - docker compose \ - down \ - --remove-orphans \ - --volumes -} diff --git a/sh/containers_up_healthy_and_clean.sh b/sh/containers_up_healthy_and_clean.sh deleted file mode 100644 index 5719569..0000000 --- a/sh/containers_up_healthy_and_clean.sh +++ /dev/null @@ -1,121 +0,0 @@ - -# - - - - - - - - - - - - - - - - - - - -server_up_healthy_and_clean() -{ - if [ "${1}" == $(server_name) ]; then - export CONTAINER_NAME="$(server_container)" - export CONTAINER_PORT="$(server_port)" - export CONTAINER_USER="$(server_user)" - docker compose up --detach $(server_name) - exit_non_zero_unless_healthy - exit_non_zero_unless_started_cleanly - fi -} - -# - - - - - - - - - - - - - - - - - - - -client_up_healthy_and_clean() -{ - if [ "${1}" == $(client_name) ]; then - export CONTAINER_NAME="$(client_container)" - export CONTAINER_PORT="$(client_port)" - export CONTAINER_USER="$(client_user)" - docker compose up --detach $(client_name) - exit_non_zero_unless_healthy - exit_non_zero_unless_started_cleanly - fi -} - -# - - - - - - - - - - - - - - - - - - - -exit_non_zero_unless_healthy() -{ - echo - local -r MAX_TRIES=50 - printf "Waiting until ${CONTAINER_NAME} is healthy" - for _ in $(seq ${MAX_TRIES}) - do - if healthy; then - echo; echo "${CONTAINER_NAME} is healthy." - return - else - printf . - sleep 0.1 - fi - done - echo; echo "${CONTAINER_NAME} not healthy after ${MAX_TRIES} tries." - echo_docker_log - echo - exit 42 -} - -# - - - - - - - - - - - - - - - - - - - -healthy() -{ - docker ps --filter health=healthy --format '{{.Names}}' | grep -q "${CONTAINER_NAME}" -} - -# - - - - - - - - - - - - - - - - - - - -exit_non_zero_unless_started_cleanly() -{ - # Handle known warnings (eg waiting on Gem upgrade) - #local -r SHADOW_WARNING="server.rb:(.*): warning: shadowing outer local variable - filename" - #DOCKER_LOG=$(strip_known_warning "${DOCKER_LOG}" "${SHADOW_WARNING}") - - echo - echo "Checking if ${SERVICE_NAME} started cleanly." - if [ "$(top_5)" == "$(clean_top_5)" ]; then - echo "${SERVICE_NAME} started cleanly." - else - echo "${SERVICE_NAME} did not start cleanly: docker log..." - echo 'expected------------------' - echo "$(clean_top_5)" - echo - echo 'actual--------------------' - echo "$(top_5)" - echo - echo 'diff--------------------' - grep -Fxvf <(clean_top_5) <(top_5) - echo - exit 42 - fi -} - -# - - - - - - - - - - - - - - - - - - - -top_5() -{ - echo_docker_log | head -5 -} - -# - - - - - - - - - - - - - - - - - - - -clean_top_5() -{ - # 1st 5 lines on Puma - local -r L1="Puma starting in single mode..." - local -r L2='* Puma version: 6.4.3 (ruby 3.3.5-p100) ("The Eagle of Durango")' - local -r L3="* Min threads: 0" - local -r L4="* Max threads: 5" - local -r L5="* Environment: production" - # - local -r all5="$(printf "%s\n%s\n%s\n%s\n%s" "${L1}" "${L2}" "${L3}" "${L4}" "${L5}")" - echo "${all5}" -} - -# - - - - - - - - - - - - - - - - - - - -echo_docker_log() -{ - docker logs "${CONTAINER_NAME}" 2>&1 -} - -# - - - - - - - - - - - - - - - - - - - -strip_known_warning() -{ - local -r DOCKER_LOG="${1}" - local -r KNOWN_WARNING="${2}" - local STRIPPED=$(echo -n "${DOCKER_LOG}" | grep --invert-match -E "${KNOWN_WARNING}") - if [ "${DOCKER_LOG}" != "${STRIPPED}" ]; then - echo "Known service start-up warning found: ${KNOWN_WARNING}" - else - echo "Known service start-up warning NOT found: ${KNOWN_WARNING}" - exit 42 - fi - echo "${STRIPPED}" -} diff --git a/sh/copy_in_saver_test_data.sh b/sh/copy_in_saver_test_data.sh deleted file mode 100644 index 284f2cc..0000000 --- a/sh/copy_in_saver_test_data.sh +++ /dev/null @@ -1,12 +0,0 @@ - -# - - - - - - - - - - - - - - - - - - - - - - - - - - -copy_in_saver_test_data() -{ - local -r SRC_PATH=$(repo_root)/$(tests_dir)/data/cyber-dojo - local -r SAVER_CID=$(docker ps --filter status=running --format '{{.Names}}' | grep "saver") - local -r DEST_PATH=/cyber-dojo - # You cannot docker cp to a tmpfs, so tar-piping instead... - cd ${SRC_PATH} \ - && tar -c . \ - | docker exec -i ${SAVER_CID} tar x -C ${DEST_PATH} -} diff --git a/sh/echo_versioner_env_vars.sh b/sh/echo_versioner_env_vars.sh deleted file mode 100755 index bd47fdb..0000000 --- a/sh/echo_versioner_env_vars.sh +++ /dev/null @@ -1,9 +0,0 @@ - -# - - - - - - - - - - - - - - - - - - - - - - - - -echo_versioner_env_vars() -{ - local -r sha="$(cd "$(repo_root)" && git rev-parse HEAD)" - docker run --rm cyberdojo/versioner - echo CYBER_DOJO_DASHBOARD_SHA="${sha}" - echo CYBER_DOJO_DASHBOARD_TAG="${sha:0:7}" -} diff --git a/sh/exit_non_zero_unless_installed.sh b/sh/exit_non_zero_unless_installed.sh deleted file mode 100644 index 357faf9..0000000 --- a/sh/exit_non_zero_unless_installed.sh +++ /dev/null @@ -1,22 +0,0 @@ - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -exit_non_zero_unless_installed() -{ - printf "Checking ${1} is installed..." - if ! installed "${1}" ; then - stderr 'ERROR: ${1} is not installed!' - exit 42 - else - echo It is - fi -} - -# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -installed() -{ - if hash "${1}" 2> /dev/null; then - true - else - false - fi -} diff --git a/sh/exit_zero_if_build_only.sh b/sh/exit_zero_if_build_only.sh deleted file mode 100755 index f2b82bb..0000000 --- a/sh/exit_zero_if_build_only.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -# - - - - - - - - - - - - - - - - - - - - - - - - - - -exit_zero_if_build_only() -{ - if build_only_arg "${1:-}" ; then - build_tagged_images - exit 0 - fi -} - -# - - - - - - - - - - - - - - - - - - - - - - - - - - -build_only_arg() -{ - [ "${1:-}" == '--build-only' ] || [ "${1:-}" == '-bo' ] -} diff --git a/sh/exit_zero_if_show_help.sh b/sh/exit_zero_if_show_help.sh deleted file mode 100755 index 943ac68..0000000 --- a/sh/exit_zero_if_show_help.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -#- - - - - - - - - - - - - - - - - - - - - - -exit_zero_if_show_help() -{ - local -r MY_NAME=$(basename "${BASH_SOURCE[0]}") - if [ "${1:-}" == '-h' ] || [ "${1:-}" == '--help' ]; then - echo - echo "Use: ${MY_NAME} $(server_name) [ID...]" - echo "Use: ${MY_NAME} $(client_name) [ID...]" - echo "Use: ${MY_NAME} -h|--help" - echo 'Options:' - echo " $(server_name) run the tests from inside the $(server_name)" - echo " $(client_name) run the tests from inside the $(client_name)" - echo ' ID... only run the tests matching these identifiers' - echo ' -h|--help show this help' - echo - exit 0 - fi -} diff --git a/sh/lib.sh b/sh/lib.sh deleted file mode 100644 index 7098dcd..0000000 --- a/sh/lib.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -repo_root() -{ - git rev-parse --show-toplevel -} -export -f repo_root - diff --git a/sh/run_tests_with_coverage.sh b/sh/run_tests_with_coverage.sh deleted file mode 100755 index 1ab929f..0000000 --- a/sh/run_tests_with_coverage.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -export SCRIPTS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -source "${SCRIPTS_DIR}/lib.sh" -source "${SCRIPTS_DIR}/build_images.sh" -source "${SCRIPTS_DIR}/config.sh" -source "${SCRIPTS_DIR}/containers_down.sh" -source "${SCRIPTS_DIR}/containers_up_healthy_and_clean.sh" -source "${SCRIPTS_DIR}/copy_in_saver_test_data.sh" -source "${SCRIPTS_DIR}/exit_non_zero_unless_installed.sh" -source "${SCRIPTS_DIR}/exit_zero_if_build_only.sh" -source "${SCRIPTS_DIR}/exit_zero_if_show_help.sh" -source "${SCRIPTS_DIR}/test_in_containers.sh" -source "${SCRIPTS_DIR}/echo_versioner_env_vars.sh" -export $(echo_versioner_env_vars) - -#- - - - - - - - - - - - - - - - - - - - - - - -exit_zero_if_show_help "$@" -exit_non_zero_unless_installed docker - -build_images server -exit_zero_if_build_only "$@" -server_up_healthy_and_clean server -copy_in_saver_test_data -test_in_containers server -containers_down diff --git a/sh/test_in_containers.sh b/sh/test_in_containers.sh deleted file mode 100755 index b464af4..0000000 --- a/sh/test_in_containers.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash -set -Eeu - -# - - - - - - - - - - - - - - - - - - - - - - - - - - -test_in_containers() -{ - if [ "${1}" == "$(server_name)" ]; then - shift - run_tests "$(server_user)" "$(server_container)" "$(server_name)" "${@:-}" - elif [ "${1}" == "$(client_name)" ]; then - shift - run_tests "$(client_user)" "$(client_container)" "$(client_name)" "${@:-}" - fi - echo All passed -} - -# - - - - - - - - - - - - - - - - - - - - - - - - - - -run_tests() -{ - local -r USER="${1}" # eg nobody - local -r CONTAINER_NAME="${2}" # eg test_X_server - local -r TYPE="${3}" # eg client|server - - echo '==================================' - echo "Running ${TYPE} tests" - echo '==================================' - - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Run tests (with branch coverage) inside the container. - - local -r COVERAGE_CODE_TAB_NAME=code - local -r COVERAGE_TEST_TAB_NAME=test - #local -r reports_dir_name=reports - - #local -r tmp_dir=/tmp - local -r CONTAINER_TMP_DIR=/tmp - - #local -r coverage_root=/${CONTAINER_TMP_DIR}/${reports_dir_name} - local -r CONTAINER_COVERAGE_DIR="${CONTAINER_TMP_DIR}/reports" - - #local -r test_log=test.log - local -r TEST_LOG=test.log - - set +e - docker exec \ - --env COVERAGE_CODE_TAB_NAME=${COVERAGE_CODE_TAB_NAME} \ - --env COVERAGE_TEST_TAB_NAME=${COVERAGE_TEST_TAB_NAME} \ - --user "${USER}" \ - "${CONTAINER_NAME}" \ - sh -c "/test/run.sh ${CONTAINER_COVERAGE_DIR} ${TEST_LOG} ${TYPE} ${*:4}" - set -e - - - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Extract test-run results and coverage data from the container. - # You can't [docker cp] from a tmpfs, so tar-piping coverage out - - #local -r tests_type_dir="$(repo_root)/$(tests_dir)/${TYPE}" - local -r TESTS_TYPE_DIR="$(repo_root)/$(tests_dir)/${TYPE}" - - docker exec \ - "${CONTAINER_NAME}" \ - tar Ccf \ - "$(dirname "${CONTAINER_COVERAGE_DIR}")" \ - - "$(basename "${CONTAINER_COVERAGE_DIR}")" \ - | tar Cxf "${TESTS_TYPE_DIR}/" - - - - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Process test-run results and coverage data. - - #local -r reports_dir=${tests_type_dir}/${reports_dir_name} - local -r HOST_REPORTS_DIR=${TESTS_TYPE_DIR}/reports - mkdir -p "${HOST_REPORTS_DIR}" - - set +e - - docker run \ - --env COVERAGE_CODE_TAB_NAME=${COVERAGE_CODE_TAB_NAME} \ - --env COVERAGE_TEST_TAB_NAME=${COVERAGE_TEST_TAB_NAME} \ - --rm \ - --volume ${HOST_REPORTS_DIR}/${TEST_LOG}:${CONTAINER_TMP_DIR}/${TEST_LOG}:ro \ - --volume ${HOST_REPORTS_DIR}/index.html:${CONTAINER_TMP_DIR}/index.html:ro \ - --volume ${HOST_REPORTS_DIR}/coverage.json:${CONTAINER_TMP_DIR}/coverage.json:ro \ - --volume ${TESTS_TYPE_DIR}/metrics.rb:/app/metrics.rb:ro \ - cyberdojo/check-test-results:latest \ - sh -c "ruby /app/check_test_results.rb \ - ${CONTAINER_TMP_DIR}/${TEST_LOG} \ - ${CONTAINER_TMP_DIR}/index.html \ - ${CONTAINER_TMP_DIR}/coverage.json" \ - | tee -a ${HOST_REPORTS_DIR}/${TEST_LOG} - - local -r status=${PIPESTATUS[0]} - set -e - - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Tell caller where the results are... - - echo "${TYPE} test coverage at" - echo "${HOST_REPORTS_DIR}/index.html" - echo "${TYPE} test status == ${status}" - if [ "${status}" != '0' ]; then - echo Docker logs "${CONTAINER_NAME}" - echo - docker logs "${CONTAINER_NAME}" - fi - return ${status} -} diff --git a/sonar-project.properties b/sonar-project.properties index 9b7d53e..b7ac1e7 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,5 +1,14 @@ sonar.projectKey=cyber-dojo_dashboard sonar.organization=cyber-dojo + sonar.issue.ignore.multicriteria=e1 sonar.issue.ignore.multicriteria.e1.ruleKey=docker:S6470 sonar.issue.ignore.multicriteria.e1.resourceKey=**/Dockerfile + +sonar.issue.ignore.multicriteria=e2 +sonar.issue.ignore.multicriteria.e2.ruleKey=Web:S5256 +sonar.issue.ignore.multicriteria.e2.resourceKey=source/server/app/views/traffic_lights.erb + +sonar.coverage.exclusions=**/** +sonar.tests=test/ +sonar.source=source/server/ \ No newline at end of file diff --git a/test/client/.dockerignore b/source/client/.dockerignore similarity index 100% rename from test/client/.dockerignore rename to source/client/.dockerignore diff --git a/test/client/Dockerfile b/source/client/Dockerfile similarity index 88% rename from test/client/Dockerfile rename to source/client/Dockerfile index 773938c..436c143 100644 --- a/test/client/Dockerfile +++ b/source/client/Dockerfile @@ -1,4 +1,4 @@ -FROM cyberdojo/sinatra-base:11ddc45 +FROM cyberdojo/sinatra-base:026c095 LABEL maintainer=jon@jaggersoft.com WORKDIR /app diff --git a/test/client/code/app.rb b/source/client/code/app.rb similarity index 100% rename from test/client/code/app.rb rename to source/client/code/app.rb diff --git a/test/client/code/app_base.rb b/source/client/code/app_base.rb similarity index 100% rename from test/client/code/app_base.rb rename to source/client/code/app_base.rb diff --git a/test/client/code/dashboard.rb b/source/client/code/dashboard.rb similarity index 100% rename from test/client/code/dashboard.rb rename to source/client/code/dashboard.rb diff --git a/test/client/code/external_dashboard.rb b/source/client/code/external_dashboard.rb similarity index 100% rename from test/client/code/external_dashboard.rb rename to source/client/code/external_dashboard.rb diff --git a/test/client/code/external_http.rb b/source/client/code/external_http.rb similarity index 100% rename from test/client/code/external_http.rb rename to source/client/code/external_http.rb diff --git a/test/client/code/external_saver.rb b/source/client/code/external_saver.rb similarity index 100% rename from test/client/code/external_saver.rb rename to source/client/code/external_saver.rb diff --git a/test/client/code/externals.rb b/source/client/code/externals.rb similarity index 100% rename from test/client/code/externals.rb rename to source/client/code/externals.rb diff --git a/test/client/code/http_json_hash/requester.rb b/source/client/code/http_json_hash/requester.rb similarity index 100% rename from test/client/code/http_json_hash/requester.rb rename to source/client/code/http_json_hash/requester.rb diff --git a/app/http_json_hash/service.rb b/source/client/code/http_json_hash/service.rb similarity index 100% rename from app/http_json_hash/service.rb rename to source/client/code/http_json_hash/service.rb diff --git a/app/http_json_hash/service_error.rb b/source/client/code/http_json_hash/service_error.rb similarity index 100% rename from app/http_json_hash/service_error.rb rename to source/client/code/http_json_hash/service_error.rb diff --git a/test/client/code/http_json_hash/unpacker.rb b/source/client/code/http_json_hash/unpacker.rb similarity index 100% rename from test/client/code/http_json_hash/unpacker.rb rename to source/client/code/http_json_hash/unpacker.rb diff --git a/app/silently.rb b/source/client/code/silently.rb similarity index 100% rename from app/silently.rb rename to source/client/code/silently.rb diff --git a/test/client/config/config.ru b/source/client/config/config.ru similarity index 100% rename from test/client/config/config.ru rename to source/client/config/config.ru diff --git a/test/client/config/healthcheck.sh b/source/client/config/healthcheck.sh similarity index 100% rename from test/client/config/healthcheck.sh rename to source/client/config/healthcheck.sh diff --git a/config/puma.rb b/source/client/config/puma.rb similarity index 100% rename from config/puma.rb rename to source/client/config/puma.rb diff --git a/test/client/config/up.sh b/source/client/config/up.sh similarity index 100% rename from test/client/config/up.sh rename to source/client/config/up.sh diff --git a/app/app.rb b/source/server/app/app.rb similarity index 92% rename from app/app.rb rename to source/server/app/app.rb index 757e87b..2665daf 100644 --- a/app/app.rb +++ b/source/server/app/app.rb @@ -2,7 +2,8 @@ require_relative 'app_base' require_relative 'prober' -require_relative 'helpers/app_helpers' +require_relative 'helpers/gatherer' +require_relative 'helpers/avatars_progress' class App < AppBase def initialize(externals) @@ -16,8 +17,6 @@ def initialize(externals) get_delegate(Prober, :ready?) get_delegate(Prober, :sha) - # - - - - - - - - - - - - - - - - get '/show/:id', provides: [:html] do @id = params[:id] respond_to do |wants| @@ -27,8 +26,6 @@ def initialize(externals) end end - # - - - - - - - - - - - - - - - - get '/heartbeat/:id', provides: [:json] do # Process all traffic-lights into minute columns here in Ruby # which can easily handle integers (unlike JS). @@ -43,8 +40,6 @@ def initialize(externals) end end - # - - - - - - - - - - - - - - - - get '/progress/:id', provides: [:json] do respond_to do |wants| wants.json do @@ -55,7 +50,8 @@ def initialize(externals) private - helpers AppHelpers + helpers AvatarsProgressHelper + helpers GathererHelper def altered(indexes, gapped) indexes.to_h do |kata_id, group_index| @@ -98,8 +94,6 @@ def light_json(light) element end - # - - - - - - - - - - - - - - - - def modified(ticks) ticks.transform_values do |v| dhm(v) diff --git a/app/app_base.rb b/source/server/app/app_base.rb similarity index 92% rename from app/app_base.rb rename to source/server/app/app_base.rb index 4f7b1bb..34b69f3 100644 --- a/app/app_base.rb +++ b/source/server/app/app_base.rb @@ -20,8 +20,6 @@ def initialize(externals) set :port, ENV.fetch('PORT', nil) set :environment, Sprockets::Environment.new - # - - - - - - - - - - - - - - - - - - - - - - - environment.append_path('app/assets/images') def self.jquery_dialog_image(name) @@ -35,8 +33,6 @@ def self.jquery_dialog_image(name) jquery_dialog_image('ui-icons_ffffff_256x240.png') jquery_dialog_image('ui-bg_diagonals-thick_20_666666_40x40.png') - # - - - - - - - - - - - - - - - - - - - - - - - environment.append_path('app/assets/stylesheets') environment.css_compressor = :sassc @@ -49,8 +45,6 @@ def self.jquery_dialog_image(name) end end - # - - - - - - - - - - - - - - - - - - - - - - - environment.append_path('app/assets/javascripts') environment.js_compressor = Uglifier.new(harmony: true) @@ -77,8 +71,6 @@ def self.get_delegate(klass, name) end end - # - - - - - - - - - - - - - - - - - - - - - - - def json_args symbolized(json_payload) end @@ -102,8 +94,6 @@ def json_hash_parse(body) raise 'body is not JSON' end - # - - - - - - - - - - - - - - - - - - - - - - - set :show_exceptions, false error do diff --git a/app/assets/images/ui-bg_diagonals-thick_20_666666_40x40.png b/source/server/app/assets/images/ui-bg_diagonals-thick_20_666666_40x40.png similarity index 100% rename from app/assets/images/ui-bg_diagonals-thick_20_666666_40x40.png rename to source/server/app/assets/images/ui-bg_diagonals-thick_20_666666_40x40.png diff --git a/app/assets/images/ui-icons_222222_256x240.png b/source/server/app/assets/images/ui-icons_222222_256x240.png similarity index 100% rename from app/assets/images/ui-icons_222222_256x240.png rename to source/server/app/assets/images/ui-icons_222222_256x240.png diff --git a/app/assets/images/ui-icons_ffffff_256x240.png b/source/server/app/assets/images/ui-icons_ffffff_256x240.png similarity index 100% rename from app/assets/images/ui-icons_ffffff_256x240.png rename to source/server/app/assets/images/ui-icons_ffffff_256x240.png diff --git a/app/assets/javascripts/app.js b/source/server/app/assets/javascripts/app.js similarity index 100% rename from app/assets/javascripts/app.js rename to source/server/app/assets/javascripts/app.js diff --git a/app/assets/javascripts/chart.min.js b/source/server/app/assets/javascripts/chart.min.js similarity index 100% rename from app/assets/javascripts/chart.min.js rename to source/server/app/assets/javascripts/chart.min.js diff --git a/app/assets/javascripts/get_json.js b/source/server/app/assets/javascripts/get_json.js similarity index 100% rename from app/assets/javascripts/get_json.js rename to source/server/app/assets/javascripts/get_json.js diff --git a/app/assets/javascripts/hover_tips.js b/source/server/app/assets/javascripts/hover_tips.js similarity index 100% rename from app/assets/javascripts/hover_tips.js rename to source/server/app/assets/javascripts/hover_tips.js diff --git a/app/assets/javascripts/jquery.min.js b/source/server/app/assets/javascripts/jquery.min.js similarity index 100% rename from app/assets/javascripts/jquery.min.js rename to source/server/app/assets/javascripts/jquery.min.js diff --git a/app/assets/javascripts/jquery.scroll-into-view.js b/source/server/app/assets/javascripts/jquery.scroll-into-view.js similarity index 100% rename from app/assets/javascripts/jquery.scroll-into-view.js rename to source/server/app/assets/javascripts/jquery.scroll-into-view.js diff --git a/app/assets/javascripts/jquery.ui.min.js b/source/server/app/assets/javascripts/jquery.ui.min.js similarity index 100% rename from app/assets/javascripts/jquery.ui.min.js rename to source/server/app/assets/javascripts/jquery.ui.min.js diff --git a/app/assets/javascripts/lib.js b/source/server/app/assets/javascripts/lib.js similarity index 100% rename from app/assets/javascripts/lib.js rename to source/server/app/assets/javascripts/lib.js diff --git a/app/assets/javascripts/lib_avatar_name.js b/source/server/app/assets/javascripts/lib_avatar_name.js similarity index 100% rename from app/assets/javascripts/lib_avatar_name.js rename to source/server/app/assets/javascripts/lib_avatar_name.js diff --git a/app/assets/javascripts/pie_chart.js b/source/server/app/assets/javascripts/pie_chart.js similarity index 100% rename from app/assets/javascripts/pie_chart.js rename to source/server/app/assets/javascripts/pie_chart.js diff --git a/app/assets/javascripts/refresh.js b/source/server/app/assets/javascripts/refresh.js similarity index 100% rename from app/assets/javascripts/refresh.js rename to source/server/app/assets/javascripts/refresh.js diff --git a/app/assets/javascripts/review_url.js b/source/server/app/assets/javascripts/review_url.js similarity index 100% rename from app/assets/javascripts/review_url.js rename to source/server/app/assets/javascripts/review_url.js diff --git a/app/assets/javascripts/time_tick.js b/source/server/app/assets/javascripts/time_tick.js similarity index 100% rename from app/assets/javascripts/time_tick.js rename to source/server/app/assets/javascripts/time_tick.js diff --git a/app/assets/javascripts/url_param.js b/source/server/app/assets/javascripts/url_param.js similarity index 100% rename from app/assets/javascripts/url_param.js rename to source/server/app/assets/javascripts/url_param.js diff --git a/app/assets/stylesheets/age.scss b/source/server/app/assets/stylesheets/age.scss similarity index 100% rename from app/assets/stylesheets/age.scss rename to source/server/app/assets/stylesheets/age.scss diff --git a/app/assets/stylesheets/app.css b/source/server/app/assets/stylesheets/app.css similarity index 100% rename from app/assets/stylesheets/app.css rename to source/server/app/assets/stylesheets/app.css diff --git a/app/assets/stylesheets/avatar-image.scss b/source/server/app/assets/stylesheets/avatar-image.scss similarity index 100% rename from app/assets/stylesheets/avatar-image.scss rename to source/server/app/assets/stylesheets/avatar-image.scss diff --git a/app/assets/stylesheets/checkboxes.scss b/source/server/app/assets/stylesheets/checkboxes.scss similarity index 100% rename from app/assets/stylesheets/checkboxes.scss rename to source/server/app/assets/stylesheets/checkboxes.scss diff --git a/app/assets/stylesheets/collapsed-columns.scss b/source/server/app/assets/stylesheets/collapsed-columns.scss similarity index 100% rename from app/assets/stylesheets/collapsed-columns.scss rename to source/server/app/assets/stylesheets/collapsed-columns.scss diff --git a/app/assets/stylesheets/column.scss b/source/server/app/assets/stylesheets/column.scss similarity index 100% rename from app/assets/stylesheets/column.scss rename to source/server/app/assets/stylesheets/column.scss diff --git a/app/assets/stylesheets/controls.scss b/source/server/app/assets/stylesheets/controls.scss similarity index 100% rename from app/assets/stylesheets/controls.scss rename to source/server/app/assets/stylesheets/controls.scss diff --git a/app/assets/stylesheets/dashboard.scss b/source/server/app/assets/stylesheets/dashboard.scss similarity index 100% rename from app/assets/stylesheets/dashboard.scss rename to source/server/app/assets/stylesheets/dashboard.scss diff --git a/app/assets/stylesheets/diff-filename.scss b/source/server/app/assets/stylesheets/diff-filename.scss similarity index 100% rename from app/assets/stylesheets/diff-filename.scss rename to source/server/app/assets/stylesheets/diff-filename.scss diff --git a/app/assets/stylesheets/diff-line-count.scss b/source/server/app/assets/stylesheets/diff-line-count.scss similarity index 100% rename from app/assets/stylesheets/diff-line-count.scss rename to source/server/app/assets/stylesheets/diff-line-count.scss diff --git a/app/assets/stylesheets/fixed-column.scss b/source/server/app/assets/stylesheets/fixed-column.scss similarity index 100% rename from app/assets/stylesheets/fixed-column.scss rename to source/server/app/assets/stylesheets/fixed-column.scss diff --git a/app/assets/stylesheets/jquery-ui-1.8.16.custom.scss b/source/server/app/assets/stylesheets/jquery-ui-1.8.16.custom.scss similarity index 100% rename from app/assets/stylesheets/jquery-ui-1.8.16.custom.scss rename to source/server/app/assets/stylesheets/jquery-ui-1.8.16.custom.scss diff --git a/app/assets/stylesheets/lib/all.scss b/source/server/app/assets/stylesheets/lib/all.scss similarity index 100% rename from app/assets/stylesheets/lib/all.scss rename to source/server/app/assets/stylesheets/lib/all.scss diff --git a/app/assets/stylesheets/lib/body.scss b/source/server/app/assets/stylesheets/lib/body.scss similarity index 100% rename from app/assets/stylesheets/lib/body.scss rename to source/server/app/assets/stylesheets/lib/body.scss diff --git a/app/assets/stylesheets/lib/button.scss b/source/server/app/assets/stylesheets/lib/button.scss similarity index 100% rename from app/assets/stylesheets/lib/button.scss rename to source/server/app/assets/stylesheets/lib/button.scss diff --git a/app/assets/stylesheets/lib/colour.scss b/source/server/app/assets/stylesheets/lib/colour.scss similarity index 100% rename from app/assets/stylesheets/lib/colour.scss rename to source/server/app/assets/stylesheets/lib/colour.scss diff --git a/app/assets/stylesheets/lib/dialog.scss b/source/server/app/assets/stylesheets/lib/dialog.scss similarity index 100% rename from app/assets/stylesheets/lib/dialog.scss rename to source/server/app/assets/stylesheets/lib/dialog.scss diff --git a/app/assets/stylesheets/lib/edged-panel.scss b/source/server/app/assets/stylesheets/lib/edged-panel.scss similarity index 100% rename from app/assets/stylesheets/lib/edged-panel.scss rename to source/server/app/assets/stylesheets/lib/edged-panel.scss diff --git a/app/assets/stylesheets/lib/fonts.scss b/source/server/app/assets/stylesheets/lib/fonts.scss similarity index 100% rename from app/assets/stylesheets/lib/fonts.scss rename to source/server/app/assets/stylesheets/lib/fonts.scss diff --git a/app/assets/stylesheets/lib/hover-tip.scss b/source/server/app/assets/stylesheets/lib/hover-tip.scss similarity index 100% rename from app/assets/stylesheets/lib/hover-tip.scss rename to source/server/app/assets/stylesheets/lib/hover-tip.scss diff --git a/app/assets/stylesheets/lib/right-align.scss b/source/server/app/assets/stylesheets/lib/right-align.scss similarity index 100% rename from app/assets/stylesheets/lib/right-align.scss rename to source/server/app/assets/stylesheets/lib/right-align.scss diff --git a/app/assets/stylesheets/lib/rounded-corner.scss b/source/server/app/assets/stylesheets/lib/rounded-corner.scss similarity index 100% rename from app/assets/stylesheets/lib/rounded-corner.scss rename to source/server/app/assets/stylesheets/lib/rounded-corner.scss diff --git a/app/assets/stylesheets/minute-box.scss b/source/server/app/assets/stylesheets/minute-box.scss similarity index 100% rename from app/assets/stylesheets/minute-box.scss rename to source/server/app/assets/stylesheets/minute-box.scss diff --git a/app/assets/stylesheets/panels.scss b/source/server/app/assets/stylesheets/panels.scss similarity index 100% rename from app/assets/stylesheets/panels.scss rename to source/server/app/assets/stylesheets/panels.scss diff --git a/app/assets/stylesheets/time-tick.scss b/source/server/app/assets/stylesheets/time-tick.scss similarity index 100% rename from app/assets/stylesheets/time-tick.scss rename to source/server/app/assets/stylesheets/time-tick.scss diff --git a/app/assets/stylesheets/top-bar.scss b/source/server/app/assets/stylesheets/top-bar.scss similarity index 100% rename from app/assets/stylesheets/top-bar.scss rename to source/server/app/assets/stylesheets/top-bar.scss diff --git a/app/assets/stylesheets/traffic-light-count.scss b/source/server/app/assets/stylesheets/traffic-light-count.scss similarity index 100% rename from app/assets/stylesheets/traffic-light-count.scss rename to source/server/app/assets/stylesheets/traffic-light-count.scss diff --git a/app/assets/stylesheets/traffic-lights.scss b/source/server/app/assets/stylesheets/traffic-lights.scss similarity index 100% rename from app/assets/stylesheets/traffic-lights.scss rename to source/server/app/assets/stylesheets/traffic-lights.scss diff --git a/app/external_http.rb b/source/server/app/external_http.rb similarity index 100% rename from app/external_http.rb rename to source/server/app/external_http.rb diff --git a/app/external_saver.rb b/source/server/app/external_saver.rb similarity index 100% rename from app/external_saver.rb rename to source/server/app/external_saver.rb diff --git a/app/external_time.rb b/source/server/app/external_time.rb similarity index 100% rename from app/external_time.rb rename to source/server/app/external_time.rb diff --git a/app/externals.rb b/source/server/app/externals.rb similarity index 100% rename from app/externals.rb rename to source/server/app/externals.rb diff --git a/app/helpers/avatars_progress.rb b/source/server/app/helpers/avatars_progress.rb similarity index 97% rename from app/helpers/avatars_progress.rb rename to source/server/app/helpers/avatars_progress.rb index 3de298f..6bd0d31 100644 --- a/app/helpers/avatars_progress.rb +++ b/source/server/app/helpers/avatars_progress.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # mixin -module AppHelpers +module AvatarsProgressHelper module_function def avatars_progress diff --git a/app/helpers/gatherer.rb b/source/server/app/helpers/gatherer.rb similarity index 91% rename from app/helpers/gatherer.rb rename to source/server/app/helpers/gatherer.rb index 1f2401a..41b8492 100644 --- a/app/helpers/gatherer.rb +++ b/source/server/app/helpers/gatherer.rb @@ -4,7 +4,7 @@ require_relative 'light' # mixin -module AppHelpers +module GathererHelper module_function def gather @@ -26,7 +26,7 @@ def gather created = Time.mktime(*manifest['created']) args = [created, seconds_per_column, max_seconds_uncollapsed] gapper = TdGapper.new(*args) - @gapped = gapper.fully_gapped(@all_lights, time.now) + @gapped = gapper.fully_gapped(@all_lights, externals.time.now) @time_ticks = gapper.time_ticks(@gapped) end diff --git a/app/helpers/light.rb b/source/server/app/helpers/light.rb similarity index 100% rename from app/helpers/light.rb rename to source/server/app/helpers/light.rb diff --git a/app/helpers/td_gapper.rb b/source/server/app/helpers/td_gapper.rb similarity index 94% rename from app/helpers/td_gapper.rb rename to source/server/app/helpers/td_gapper.rb index 7725b21..ffd2516 100644 --- a/app/helpers/td_gapper.rb +++ b/source/server/app/helpers/td_gapper.rb @@ -7,8 +7,6 @@ def initialize(start, seconds_per_td, max_seconds_uncollapsed) @max_seconds_uncollapsed = max_seconds_uncollapsed end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def fully_gapped(all_lights, now) s = stats(all_lights, now) vertical_bleed(s) @@ -60,8 +58,6 @@ def fully_gapped(all_lights, now) strip(s[:katas]) end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def time_ticks(gapped) return {} if gapped == {} @@ -77,8 +73,6 @@ def time_ticks(gapped) ticks end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def stats(all_lights, now) obj = { katas: {}, td_nos: [0, n(now)] } # eg td_nos: [0,99] @@ -100,8 +94,6 @@ def stats(all_lights, now) # eg td_nos: [ 0,5,7,11,99 ] end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def vertical_bleed(s) s[:td_nos].each do |n| s[:katas].each_value do |td_map| @@ -114,8 +106,6 @@ def vertical_bleed(s) # } end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def collapsed_table(td_nos) max_uncollapsed_tds = @max_seconds_uncollapsed / @seconds_per_td obj = {} @@ -145,8 +135,6 @@ def collapsed_table(td_nos) # } end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def strip(gapped) # remove lightless columns from both ends return gapped if gapped == {} @@ -170,8 +158,6 @@ def strip(gapped) gapped end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - def number(light) n(light.time) end diff --git a/app/http_json_hash/requester.rb b/source/server/app/http_json_hash/requester.rb similarity index 100% rename from app/http_json_hash/requester.rb rename to source/server/app/http_json_hash/requester.rb diff --git a/test/client/code/http_json_hash/service.rb b/source/server/app/http_json_hash/service.rb similarity index 100% rename from test/client/code/http_json_hash/service.rb rename to source/server/app/http_json_hash/service.rb diff --git a/test/client/code/http_json_hash/service_error.rb b/source/server/app/http_json_hash/service_error.rb similarity index 100% rename from test/client/code/http_json_hash/service_error.rb rename to source/server/app/http_json_hash/service_error.rb diff --git a/app/http_json_hash/unpacker.rb b/source/server/app/http_json_hash/unpacker.rb similarity index 100% rename from app/http_json_hash/unpacker.rb rename to source/server/app/http_json_hash/unpacker.rb diff --git a/app/prober.rb b/source/server/app/prober.rb similarity index 100% rename from app/prober.rb rename to source/server/app/prober.rb diff --git a/test/client/code/silently.rb b/source/server/app/silently.rb similarity index 100% rename from test/client/code/silently.rb rename to source/server/app/silently.rb diff --git a/app/views/age.erb b/source/server/app/views/age.erb similarity index 100% rename from app/views/age.erb rename to source/server/app/views/age.erb diff --git a/app/views/auto_refresh_checkbox.erb b/source/server/app/views/auto_refresh_checkbox.erb similarity index 100% rename from app/views/auto_refresh_checkbox.erb rename to source/server/app/views/auto_refresh_checkbox.erb diff --git a/app/views/controls.erb b/source/server/app/views/controls.erb similarity index 100% rename from app/views/controls.erb rename to source/server/app/views/controls.erb diff --git a/app/views/error.erb b/source/server/app/views/error.erb similarity index 100% rename from app/views/error.erb rename to source/server/app/views/error.erb diff --git a/app/views/id.erb b/source/server/app/views/id.erb similarity index 100% rename from app/views/id.erb rename to source/server/app/views/id.erb diff --git a/app/views/layout.erb b/source/server/app/views/layout.erb similarity index 100% rename from app/views/layout.erb rename to source/server/app/views/layout.erb diff --git a/app/views/logo.erb b/source/server/app/views/logo.erb similarity index 100% rename from app/views/logo.erb rename to source/server/app/views/logo.erb diff --git a/app/views/manifest.erb b/source/server/app/views/manifest.erb similarity index 100% rename from app/views/manifest.erb rename to source/server/app/views/manifest.erb diff --git a/app/views/minute_columns_checkbox.erb b/source/server/app/views/minute_columns_checkbox.erb similarity index 100% rename from app/views/minute_columns_checkbox.erb rename to source/server/app/views/minute_columns_checkbox.erb diff --git a/app/views/progress_button.erb b/source/server/app/views/progress_button.erb similarity index 100% rename from app/views/progress_button.erb rename to source/server/app/views/progress_button.erb diff --git a/app/views/show.erb b/source/server/app/views/show.erb similarity index 100% rename from app/views/show.erb rename to source/server/app/views/show.erb diff --git a/app/views/top_bar.erb b/source/server/app/views/top_bar.erb similarity index 100% rename from app/views/top_bar.erb rename to source/server/app/views/top_bar.erb diff --git a/app/views/traffic_lights.erb b/source/server/app/views/traffic_lights.erb similarity index 100% rename from app/views/traffic_lights.erb rename to source/server/app/views/traffic_lights.erb diff --git a/config/config.ru b/source/server/config/config.ru similarity index 100% rename from config/config.ru rename to source/server/config/config.ru diff --git a/config/healthcheck.sh b/source/server/config/healthcheck.sh similarity index 100% rename from config/healthcheck.sh rename to source/server/config/healthcheck.sh diff --git a/test/client/config/puma.rb b/source/server/config/puma.rb similarity index 100% rename from test/client/config/puma.rb rename to source/server/config/puma.rb diff --git a/config/up.sh b/source/server/config/up.sh similarity index 100% rename from config/up.sh rename to source/server/config/up.sh diff --git a/test/coverage.rb b/test/coverage.rb index 3a24bc6..43bf1a8 100644 --- a/test/coverage.rb +++ b/test/coverage.rb @@ -1,21 +1,22 @@ # frozen_string_literal: true require 'simplecov' -require_relative 'simplecov_json' +require_relative 'simplecov_formatter_json' SimpleCov.start do enable_coverage :branch filters.clear - # add_filter("test/id58_test_base.rb") - coverage_dir(ENV.fetch('COVERAGE_ROOT', nil)) + add_filter('test/id58_test_base.rb') + coverage_dir(ENV.fetch('COVERAGE_ROOT')) # add_group('debug') { |source| puts source.filename; false } - code_tab = ENV.fetch('COVERAGE_CODE_TAB_NAME', nil) - test_tab = ENV.fetch('COVERAGE_TEST_TAB_NAME', nil) - add_group(code_tab) { |source| source.filename =~ %r{^/app/} } - add_group(test_tab) { |source| source.filename =~ %r{^/test/.*_test\.rb$} } + code_tab = ENV.fetch('COVERAGE_CODE_TAB_NAME') + test_tab = ENV.fetch('COVERAGE_TEST_TAB_NAME') + add_group(code_tab) { |source| source.filename =~ %r{^/dashboard/app/} } + add_group(test_tab) { |source| source.filename =~ %r{^/dashboard/test/} } end -SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([ - SimpleCov::Formatter::HTMLFormatter, - SimpleCov::Formatter::JSONFormatter - ]) +formatters = [ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::JSONFormatter +] +SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new(formatters) diff --git a/test/data/readme.txt b/test/data/readme.txt index 2769ee9..5a6f100 100644 --- a/test/data/readme.txt +++ b/test/data/readme.txt @@ -1,5 +1,5 @@ This cyber-dojo/ dir holds test data for saver. -It is tar-piped into the saver container in the sh/test_in_containers.sh script. +It is tar-piped into the saver container in copy_in_saver_test_data() in bin/lib.sh saver ----- diff --git a/test/id58_test_base.rb b/test/id58_test_base.rb index 3205a1a..ed0c669 100644 --- a/test/id58_test_base.rb +++ b/test/id58_test_base.rb @@ -2,10 +2,18 @@ require 'English' require 'minitest/autorun' -require 'rack/test' +require 'minitest/reporters' +require_relative 'slim_json_reporter' + +reporters = [ + Minitest::Reporters::DefaultReporter.new, + Minitest::Reporters::SlimJsonReporter.new, + Minitest::Reporters::JUnitReporter.new("#{ENV.fetch('COVERAGE_ROOT')}/junit") +] +Minitest::Reporters.use!(reporters) def require_source(required) - require_relative "../dashboard/app/#{required}" + require_relative "../app/#{required}" end class Id58TestBase < Minitest::Test @@ -19,8 +27,6 @@ def initialize(arg) @@seen_ids = [] @@timings = {} - # - - - - - - - - - - - - - - - - - - - - - - - def self.test(id58_suffix, *lines, &test_block) source = test_block.source_location source_file = File.basename(source[0]) @@ -59,8 +65,6 @@ def trimmed(s) end end - # - - - - - - - - - - - - - - - - - - - - - - - Minitest.after_run do slow = @@timings.select { |_name, secs| secs > 0.000 } sorted = slow.sort_by { |_name, secs| -secs }.to_h @@ -74,8 +78,6 @@ def trimmed(s) puts end - # - - - - - - - - - - - - - - - - - - - - - - - ID58_ALPHABET = %w[ 0 1 2 3 4 5 6 7 8 9 A B C D E F G H J K L M N P Q R S T U V W X Y Z @@ -113,13 +115,9 @@ def self.checked_id58(id58_suffix, lines) id58 end - # - - - - - - - - - - - - - - - - - - - - - - - def id58_setup; end def id58_teardown; end - # - - - - - - - - - - - - - - - - - - - - - - - attr_reader :id58, :name58 end diff --git a/test/run.sh b/test/run.sh index 6c31bf1..95b75db 100755 --- a/test/run.sh +++ b/test/run.sh @@ -1,10 +1,10 @@ #!/bin/bash -Eeu -# dashboard +set -Eeu -readonly MY_DIR="$( cd "$( dirname "${0}" )" && pwd )" +readonly MY_DIR="$(cd "$( dirname "${0}" )" && pwd)" export COVERAGE_ROOT="${1}" # /tmp/coverage readonly TEST_LOG="${2}" # test.log -readonly TYPE="${3}" # client|server +readonly TYPE="${3}" # {client|server} shift; shift; shift readonly TEST_FILES=(${MY_DIR}/${TYPE}/*_test.rb) @@ -21,4 +21,7 @@ mkdir -p ${COVERAGE_ROOT} set +e ruby -e "${SCRIPT}" -- ${TEST_ARGS[@]} 2>&1 | tee ${COVERAGE_ROOT}/${TEST_LOG} +STATUS=${PIPESTATUS[0]} set -e + +exit "${STATUS}" diff --git a/test/server/avatars_progress_test.rb b/test/server/avatars_progress_test.rb index 7a6f0ef..6f90ce5 100644 --- a/test/server/avatars_progress_test.rb +++ b/test/server/avatars_progress_test.rb @@ -2,19 +2,19 @@ require_relative 'test_base' require_relative '../data/cyber-dojo/kata_test_data' -require_source 'helpers/app_helpers' +require_source 'helpers/gatherer' +require_source 'helpers/avatars_progress' class AvatarsProgressTest < TestBase def self.id58_prefix '0D6' end - include AppHelpers + include AvatarsProgressHelper + include KataTestData attr_reader :params - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - test 'b46', 'contract-test v0' do @params = { id: V0_GROUP_ID } @@ -30,8 +30,6 @@ def self.id58_prefix assert_equal expected, actual end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - test 'b47', 'contract-test v1' do @params = { id: V1_GROUP_ID } @@ -52,6 +50,4 @@ def self.id58_prefix actual = avatars_progress assert_equal expected, actual end - - include KataTestData end diff --git a/test/server/check_test_metrics.rb b/test/server/check_test_metrics.rb new file mode 100644 index 0000000..6b8e1f8 --- /dev/null +++ b/test/server/check_test_metrics.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +# Uses data from two json files: +# - reports/server/test_metrics.json generated in slim_json_reporter.rb by minitest. See id58_test_base.rb +# - reports/server/coverage_metrics.json generated in simplecov_formatter_json.rb by simplecov. See coverage.rb + +require 'json' + +def coloured(arg) + red = 31 + green = 32 + colourize(arg ? green : red, arg) +end + +def colourize(code, word) + "\e[#{code}m #{word} \e[0m" +end + +def table_data + cov_root = ENV.fetch('COVERAGE_ROOT') + stats = JSON.parse(File.read("#{cov_root}/test_metrics.json")) + + cov_json = JSON.parse(File.read("#{cov_root}/coverage_metrics.json")) + test_cov = cov_json['groups'][ENV.fetch('COVERAGE_TEST_TAB_NAME')] + code_cov = cov_json['groups'][ENV.fetch('COVERAGE_CODE_TAB_NAME')] + + [ + [ nil ], + [ 'test.count', stats['test_count'], '>=', 15 ], + [ 'test.duration', stats['total_time'], '<=', 10 ], + [ nil ], + [ 'test.failures', stats['failure_count'], '<=', 0 ], + [ 'test.errors', stats['error_count' ], '<=', 0 ], + [ 'test.skips', stats['skip_count' ], '<=', 0 ], + [ nil ], + [ 'test.lines.total', test_cov['lines' ]['total' ], '<=', 238 ], + [ 'test.lines.missed', test_cov['lines' ]['missed'], '<=', 5 ], + [ 'test.branches.total', test_cov['branches']['total' ], '<=', 0 ], + [ 'test.branches.missed', test_cov['branches']['missed'], '<=', 0 ], + [ nil ], + [ 'code.lines.total', code_cov['lines' ]['total' ], '<=', 388 ], + [ 'code.lines.missed', code_cov['lines' ]['missed'], '<=', 95 ], + [ 'code.branches.total', code_cov['branches']['total' ], '<=', 50 ], + [ 'code.branches.missed', code_cov['branches']['missed'], '<=', 25 ], + ] +end + +results = [] +table_data.each do |name, value, op, limit| + if name.nil? + puts + next + end + # puts "name=#{name}, value=#{value}, op=#{op}, limit=#{limit}" # debug + result = eval("#{value} #{op} #{limit}") + puts '%s | %s %s %s | %s' % [ + name.rjust(25), value.to_s.rjust(5), " #{op}", limit.to_s.rjust(5), coloured(result) + ] + results << result +end +puts +exit results.all? diff --git a/test/server/gather_test.rb b/test/server/gather_test.rb index 8937292..b58579e 100644 --- a/test/server/gather_test.rb +++ b/test/server/gather_test.rb @@ -1,19 +1,18 @@ # frozen_string_literal: true require_relative 'test_base' -require_source 'helpers/app_helpers' +require_source 'helpers/gatherer' +require_source 'helpers/avatars_progress' class GatheredTest < TestBase def self.id58_prefix '450' end - include AppHelpers + include GathererHelper attr_reader :params - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - test 's46', 'contract-test for gather from saved cyber-dojo group v0' do id = 'chy6BJ' @@ -32,8 +31,6 @@ def self.id58_prefix gather_check(expected_indexes, expected_lights) end - # - - - - - - - - - - - - - - - - - - - - - - - - - - - - test 's47', 'contract-test for gather from saved cyber-dojo group v1' do id = 'LyQpFr' diff --git a/test/server/metrics.rb b/test/server/metrics.rb deleted file mode 100644 index 5edcf75..0000000 --- a/test/server/metrics.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -# Values are low; legacy models/ code is being phased -# out in favour of external.saver micro-service. - -# max values used by cyberdojo/check-test-results image -# which is called from sh/test_in_containers.sh - -MAX = { - failures: 0, - errors: 0, - warnings: 0, - skips: 0, - - duration: 10, - - app: { - lines: { - total: 388, - missed: 95 - }, - branches: { - total: 50, - missed: 25 - } - }, - - test: { - lines: { - total: 528, - missed: 0 - }, - branches: { - total: 0, - missed: 0 - } - } -}.freeze diff --git a/test/server/test_base.rb b/test/server/test_base.rb index 740c4f0..01647fd 100644 --- a/test/server/test_base.rb +++ b/test/server/test_base.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true +require 'rack/test' require_relative '../id58_test_base' require_source 'app' require_source 'externals' class TestBase < Id58TestBase - include Rack::Test::Methods #  [1] + include Rack::Test::Methods - #  [1] def app App.new(externals) end diff --git a/test/simplecov_json.rb b/test/simplecov_formatter_json.rb similarity index 97% rename from test/simplecov_json.rb rename to test/simplecov_formatter_json.rb index 6b349b6..3a521ce 100644 --- a/test/simplecov_json.rb +++ b/test/simplecov_formatter_json.rb @@ -41,7 +41,7 @@ def output_filepath end def output_filename - 'coverage.json' + 'coverage_metrics.json' end def output_message(result) diff --git a/test/slim_json_reporter.rb b/test/slim_json_reporter.rb new file mode 100644 index 0000000..dc5c9ad --- /dev/null +++ b/test/slim_json_reporter.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'json' +require 'minitest/reporters' + +class Minitest::Reporters::SlimJsonReporter < Minitest::Reporters::BaseReporter + def report + super + filename = "#{ENV.fetch('COVERAGE_ROOT')}/test_metrics.json" + metrics = { + total_time: format('%.2f', total_time), + assertion_count: assertions, + test_count: count, + failure_count: failures, + error_count: errors, + skip_count: skips + } + File.write(filename, JSON.pretty_generate(metrics)) + end +end