Switch CI to run tests outside of Docker

Running all our tests and lints and other CI tasks inside the built image makes
our CI more complex, harder to work on, and less reliable. Move all the CI
related tasks out of Docker.

A docker image is still built during PRs. A docker image is built and published
for commits to master and tags.
This commit is contained in:
Mike Cooper 2019-04-19 15:27:57 -07:00 коммит произвёл Michael Cooper
Родитель 6e8cae6cdb
Коммит c8dd652cd4
6 изменённых файлов: 213 добавлений и 141 удалений

Просмотреть файл

@ -4,66 +4,58 @@
# DOCKER_USER
# DOCKER_PASS
#
version: 2
jobs:
lint:
docker:
# Image with Python/Node and therapist pre-installed
- image: mozilla/cidockerbases:therapist-latest
environment:
# Use Test configuration so that development dependencies aren't needed
DJANGO_CONFIGURATION: "Test"
# Use an in-memory database so that the migrations check doesn't try to access a real database
DATABASE_URL: "sqlite://:memory:"
# Ignore warning and info checks about not applying migrations, and about missing geoip database
DJANGO_SILENCED_SYSTEM_CHECKS: "dockerflow.health.W001,normandy.recipes.I001,normandy.recipes.I002,normandy.recipes.I003,normandy.recipes.E006"
working_directory: ~/repo
steps:
- checkout
# Install python dependencies
- run:
name: Create virtualenv
command: |
python3 -m venv ~/venv
echo "source ~/venv/bin/activate" >> $BASH_ENV
- run:
name: Install python dependencies
command: pip install -U -r requirements/default.txt -c requirements/constraints.txt --require-hashes
# Download and cache node dependencies
command: pip install -r requirements/default.txt
- restore_cache:
keys:
- v2-dependencies-{{ checksum "yarn.lock" }}
# fallback to using the latest cache if no exact match is found
- v2-dependencies-
- run:
name: Install node dependencies
command: yarn install --frozen-lockfile
- save_cache:
paths:
- "node_modules"
key: v2-dependencies-{{ checksum "yarn.lock" }}
# Run lint suite using therapist
- run:
name: Lint
command: therapist run --use-tracked-files
build-test-publish:
docker-image-build:
docker:
- image: mozilla/cidockerbases:docker-latest
working_directory: ~/normandy
environment:
COMPOSE_FILE: ci/docker-compose.yml
steps:
- checkout
- setup_remote_docker:
version: 17.09.0-ce
docker_layer_caching: true
- run:
name: Create version.json
command: |
@ -75,58 +67,31 @@ jobs:
"$CIRCLE_PROJECT_REPONAME" \
"$CIRCLE_BUILD_URL" \
> version.json
- run:
name: Starting artifact collector
command: docker-compose run --user root artifact-collector
background: true
name: Build Docker image
command: docker build -t normandy:web .
- run:
name: Build and download Docker images
# Build all local images, and pull all remote images, so that later
# steps have everything they need already prepared. Note that "pull"
# requires an explicit list so it doesn't try to pull images that
# have already been built.
name: Save image into workspace
command: |
./bin/download_geolite2.sh
docker-compose build
docker-compose pull db
mkdir -p workspace
docker save -o workspace/normandy-web.tar normandy:web
gzip workspace/normandy-web.tar
- persist_to_workspace:
root: workspace
paths:
- normandy-web.tar.gz
docker-image-publish:
docker:
- image: mozilla/cidockerbases:docker-latest
steps:
- setup_remote_docker:
version: 17.09.0-ce
- attach_workspace:
at: workspace
- run:
name: Python Tests
command: docker-compose run web python-tests
- run:
name: Missing migrations
command: docker-compose run web migrations-check
- run:
name: Contract tests
command: |
docker-compose up -d web
docker-compose run web contracttest
docker-compose kill web
- run:
name: JavaScript tests
command: |
docker-compose up -d js-tests-browser
docker-compose up js-tests
docker-compose kill js-tests-browser
- run:
name: Copy Artifacts
when: always # run even if previous run commands failed
command: |
docker cp $(docker-compose ps -q artifact-collector):/artifacts /artifacts
ls /artifacts
- store_artifacts:
path: /artifacts
- store_test_results:
path: /artifacts/test_results
name: Load Docker image from workspace
command: docker load -i workspace/normandy-web.tar.gz
- run:
name: Push to Dockerhub
command: |
@ -184,26 +149,176 @@ jobs:
name: Deploy docs to gh-pages
command: gh-pages --dotfiles --message "[skip ci] Docs updates" --dist docs/_build/html
python-tests:
docker:
- image: circleci/python:3.7-node-browsers
- image: circleci/postgres:9.6
steps:
- checkout
- run:
name: Fetch Geolite database
command: ./bin/download_geolite2.sh
- run:
name: Create virtualenv
command: |
python3 -m venv ~/venv
echo "source ~/venv/bin/activate" >> $BASH_ENV
- run:
name: Install python dependencies
command: pip install -r requirements/default.txt
- run:
name: Install node dependencies
command: yarn install --frozen-lockfile
- run:
name: Python tests
command: |
mkdir test-reports
py.test normandy \
--tb=short -vvv \
--junitxml=test-reports/junit.xml
- store_test_results:
path: test-reports
- store_artifacts:
path: test-reports
js-tests:
docker:
- image: mozilla/cidockerbases:firefox-latest
steps:
- checkout
- run:
name: Install node dependencies
command: yarn install --frozen-lockfile
- run:
name: JS tests
environment:
JUNIT_REPORT_PATH: ./junit/
JUNIT_REPORT_NAME: test-results.xml
command: |
mkdir -p $JUNIT_REPORT_PATH
yarn karma start ./karma.conf.js --single-run
- store_test_results:
path: test-reports
- store_artifacts:
path: test-reports
contract-tests:
docker:
- image: circleci/python:3.7-node-browsers
- image: circleci/postgres:9.6
environment:
POSTGRES_USER: postgres
POSTGRES_DB: normandy
environment:
DJANGO_SETTINGS_MODULE: normandy.settings
DJANGO_CONFIGURATION: ProductionInsecure
steps:
- checkout
- run:
name: Fetch Geolite database
command: ./bin/download_geolite2.sh
- run:
name: Create virtualenv
command: |
python3 -m venv ~/venv
echo "source ~/venv/bin/activate" >> $BASH_ENV
- run:
name: Install python dependencies
command: pip install -r requirements/default.txt
- run:
name: Install node dependencies
command: yarn install --frozen-lockfile
- run:
name: Create version.json
command: |
printf '{"commit":"%s","version":"%s","source":"https://github.com/%s/%s","build":"%s"}\n' \
"$CIRCLE_SHA1" \
"${CIRCLE_TAG-}" \
"$CIRCLE_PROJECT_USERNAME" \
"$CIRCLE_PROJECT_REPONAME" \
"$CIRCLE_BUILD_URL" \
> version.json
- run:
name: Prepare DB
command: |
./manage.py migrate
./manage.py update_actions
- run:
name: Starting server
command: gunicorn normandy.wsgi:application --bind localhost:8000
background: true
- run:
name: Waiting for web server to be available
command: |
./ci/wait-for-it.sh localhost:8000 \
--timeout=30 --strict \
-- echo "Done waiting. It should work now."
- run:
name: Contract tests
command: |
mkdir test-reports
py.test contract-tests \
--tb=short -vvv \
--junitxml=test-reports/junit.xml \
--server http://localhost:8000
workflows:
version: 2
main:
jobs:
# By default CircleCI does not run any jobs on tags. To allow a job to
# run on a tag, we specify `filters.tags.only: /.*/`. That does not
# affect whether or not the job will run on PRs or on untagged master
# commits. By default all jobs listed run on all commits in PRs and on
# master (unless otherwise filtered).
# Group: Tests and lints
- python-tests:
filters:
tags:
only: /.*/
- js-tests:
filters:
tags:
only: /.*/
- lint:
# run on all tags, as well as the default of all other commits
filters:
tags:
only: /.*/
- build-test-publish:
requires:
- lint
# run on all tags, as well as the default of all other commits
- contract-tests:
filters:
tags:
only: /.*/
# Group: Building artifacts
- docs-build
- docker-image-build:
filters:
tags:
only: /.*/
# Group: Publish
# All of these should only run on master and tags
- docs-publish:
requires:
- docs-build
filters:
branches:
only: master
- docker-image-publish:
requires:
- python-tests
- js-tests
- contract-tests
- js-tests
- lint
- docker-image-build
filters:
branches:
only: master
tags:
only: /v.*/

Просмотреть файл

@ -25,6 +25,14 @@ actions:
yarn-audit:
run: yarn audit
missing-migirations:
include: "*.py"
run: |
./manage.py makemigrations --check --no-input --dry-run recipes studies || (
echo "You probably have migrations that need to be created" && exit 1
)
shortcuts:
lint:
flags:

Просмотреть файл

@ -34,6 +34,8 @@ ENV DJANGO_SETTINGS_MODULE=normandy.settings \
NEW_RELIC_CONFIG_FILE=newrelic.ini
EXPOSE $PORT
ENTRYPOINT ["/bin/bash", "/app/bin/run.sh"]
CMD ["start"]
CMD $CMD_PREFIX gunicorn \
--log-file - \
--worker-class ${GUNICORN_WORKER_CLASS:-sync} \
--max-requests ${GUNICORN_MAX_REQUESTS:-0} \
normandy.wsgi:application

Просмотреть файл

@ -1,64 +0,0 @@
#!/usr/bin/env bash
set -eo pipefail
usage() {
echo "usage: ./bin/run.sh python-tests|js-tests|start|migrations-check"
exit 1
}
function start_gunicorn {
$CMD_PREFIX gunicorn \
--log-file - \
--worker-class ${GUNICORN_WORKER_CLASS:-sync} \
--max-requests ${GUNICORN_MAX_REQUESTS:-0} \
normandy.wsgi:application
}
[ $# -lt 1 ] && usage
case $1 in
migrations-check)
./manage.py migrate
echo "Checking that all migrations have been made"
# The mozilla-django-product-details has a bug in that calling `./manage.py makemigrations`
# on it will actually create a new migration (.py) file. So, be specific and only do this
# migration check for *our* apps.
# See https://github.com/mozilla/django-product-details/issues/68
./manage.py makemigrations --check --no-input --dry-run recipes studies || (
echo "You probably have migrations that need to be created" && exit 1
)
;;
python-tests)
echo "Running Python tests"
junit_path=$ARTIFACTS_PATH/test_results/python_tests
mkdir -p $junit_path
py.test -vv --junitxml=$junit_path/junit.xml normandy/
;;
js-tests)
echo "Running Karma"
node ci/karma-ci.js
;;
first-start)
echo "Starting the gunicorn server the first time"
./manage.py migrate
./manage.py update_actions
start_gunicorn
;;
start)
start_gunicorn
;;
contracttest)
echo "Waiting for web server to start"
./ci/wait-for-it.sh -t 30 web:8000 -- echo "Done waiting. It should work now."
echo "Running acceptance tests"
junit_path=$ARTIFACTS_PATH/test_results/contract_tests
mkdir -p $junit_path
py.test contract-tests/ \
-vv \
--server http://web:8000 \
--junitxml=$junit_path/junit.xml
;;
*)
exec "$@"
;;
esac

Просмотреть файл

@ -4,6 +4,8 @@ var path = require('path');
var karma = require('karma');
var karmaConfig = require('karma/lib/config');
const JUNIT_OUTPUT = process.env.JUNIT_OUTPUT || '/artifacts/test_results/karma';
var config = karmaConfig.parseConfig(
path.join(__dirname, '/../karma.conf.js'),
{
@ -11,7 +13,7 @@ var config = karmaConfig.parseConfig(
oneShot: true,
reporters: ['spec', 'junit'],
junitReporter: {
outputDir: '/artifacts/test_results/karma/',
outputDir: JUNIT_OUTPUT,
},
hostname: '0.0.0.0',
},

Просмотреть файл

@ -58,7 +58,7 @@ module.exports = function (config) {
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Firefox'],
browsers: ['FirefoxHeadless'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
@ -69,5 +69,14 @@ module.exports = function (config) {
concurrency: Infinity,
};
if (process.env.JUNIT_REPORT_PATH) {
karmaConfig.reporters.push('junit');
karmaConfig.junitReporter = {
outputDir: process.env.JUNIT_REPORT_PATH,
outputFile: process.env.JUNIT_REPORT_NAME,
useBrowserNAme: false,
};
}
config.set(karmaConfig);
};