Skip to content

Commit

Permalink
Merge branch 'master' into merge-with-upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
jwmerrill authored Jan 25, 2023
2 parents 4d01da6 + b161c58 commit b15ce1b
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 140 deletions.
161 changes: 46 additions & 115 deletions circle.yml → .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
#
# then translated from 1.0 to 2.0 with: https://circleci.com/docs/2.0/config-translation/

version: 2
version: 2.1
# browser-tools orb that provides installers for various browsers: https://circleci.com/developer/orbs/orb/circleci/browser-tools
orbs:
browser-tools: circleci/[email protected]
jobs:
build:
working_directory: ~/mathquill/mathquill
Expand All @@ -44,18 +47,12 @@ jobs:
environment:
CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
# In CircleCI 1.0 we used a pre-configured image with a large number of languages and other packages.
# In CircleCI 2.0 you can now specify your own image, or use one of our pre-configured images.
# The following configuration line tells CircleCI to use the specified docker image as the runtime environment for you job.
# We have selected a pre-built image that mirrors the build environment we use on
# the 1.0 platform, but we recommend you choose an image more tailored to the needs
# of each job. For more information on choosing an image (or alternatively using a
# VM instead of a container) see https://circleci.com/docs/2.0/executor-types/
# To see the list of pre-built images that CircleCI provides for most common languages see
# https://circleci.com/docs/2.0/circleci-images/
# Docker image with browser testing tools: https://discuss.circleci.com/t/legacy-convenience-image-deprecation/41034#browser-testing-4
docker:
- image: circleci/node:lts-browsers
- image: cimg/node:lts-browsers
steps:
# install browsers: https://circleci.com/developer/orbs/orb/circleci/browser-tools
- browser-tools/install-browser-tools
# Machine Setup
# If you break your build into multiple jobs with workflows, you will probably want to do the parts of this that are relevant in each
# The following `checkout` command checks out your code to your working directory. In 1.0 we did this implicitly. In 2.0 you can choose where in the course of a job your code should be checked out.
Expand All @@ -70,11 +67,11 @@ jobs:
- restore_cache:
keys:
# This branch if available
- v1-dep-{{ .Branch }}-
- v3-dep-{{ .Branch }}-
# Default branch if not
- v1-dep-master-
- v3-dep-master-
# Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly
- v1-dep-
- v3-dep-
# This is based on your 1.0 configuration file or project settings
- run:
command: |-
Expand All @@ -90,8 +87,8 @@ jobs:
if [ -x sc-*-linux/bin/sc ]; then
echo Using cached sc-*-linux/bin/sc
else
time wget https://saucelabs.com/downloads/sc-4.7.1-linux.tar.gz
time tar -xzf sc-4.7.1-linux.tar.gz
time wget https://saucelabs.com/downloads/sc-4.8.1-linux.tar.gz
time tar -xzf sc-4.8.1-linux.tar.gz
fi
time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY \
Expand All @@ -100,8 +97,8 @@ jobs:
echo 'Sauce Connect failed, try redownloading (https://git.io/vSxsJ)'
rm -rf *
time wget https://saucelabs.com/downloads/sc-4.7.1-linux.tar.gz
time tar -xzf sc-4.7.1-linux.tar.gz
time wget https://saucelabs.com/downloads/sc-4.8.1-linux.tar.gz
time tar -xzf sc-4.8.1-linux.tar.gz
time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY \
--readyfile ~/sauce_is_ready
Expand All @@ -111,13 +108,27 @@ jobs:
| tee /dev/stderr > ~/sauce_is_ready
exit 1
background: true
- run: |-
# Format build name $MQ_CI_BUILD_NAME
# https://circleci.com/docs/environment-variables/
build_name="CircleCI build #$CIRCLE_BUILD_NUM"
if [ $CIRCLE_PR_NUMBER ]; then
build_name="$build_name: PR #$CIRCLE_PR_NUMBER"
[ "$CIRCLE_BRANCH" ] && build_name="$build_name ($CIRCLE_BRANCH)"
else
build_name="$build_name: $CIRCLE_BRANCH"
fi
build_name="$build_name @ ${CIRCLE_SHA1:0:7}"
export MQ_CI_BUILD_NAME="$build_name"
# The following line was run implicitly in your 1.0 builds based on what CircleCI inferred about the structure of your project. In 2.0 you need to be explicit about which commands should be run. In some cases you can discard inferred commands if they are not relevant to your project.
- run: if [ -z "${NODE_ENV:-}" ]; then export NODE_ENV=test; fi
- run: export PATH="~/mathquill/mathquill/node_modules/.bin:$PATH"
- run: npm install
- run: npm install wd
- run: sudo apt-get install -y imagemagick
# Save dependency cache
- save_cache:
key: v1-dep-{{ .Branch }}-{{ epoch }}
key: v3-dep-{{ .Branch }}-{{ epoch }}
paths:
# This is a broad list of cache paths to include many possible development environments
# You can probably delete some of these entries
Expand All @@ -132,13 +143,15 @@ jobs:
# These cache paths were specified in the 1.0 config
- ~/sauce-connect
- ./node_modules
# Based on: https://xyxyx.org/posts/2019-04-09-saving-the-debian-package-cache-on-circleci.html
- /var/cache/apt/archives
# Test
# This would typically be a build job when using workflows, possibly combined with build
# This is based on your 1.0 configuration file or project settings
- run: |-
# Generate link to Many-Worlds build and add to GitHub Commit Status
curl -i -X POST https://api.github.com/repos/mathquill/mathquill/statuses/$CIRCLE_SHA1 \
-u MathQuillBot:$GITHUB_STATUS_API_KEY \
-u laughinghan:$GITHUB_STATUS_ACCESS_TOKEN \
-d '{
"context": "ci/many-worlds",
"state": "success",
Expand All @@ -152,114 +165,32 @@ jobs:
command: PORT=8000 make server
background: true
# Wait for tunnel to be ready (`make server` is much faster, no need to wait for it)
- run: while [ ! -e ~/sauce_is_ready ]; do sleep 1; done; touch ~/sauce_was_ready; test -z "$(<~/sauce_is_ready)"
- run: |-
while [ ! -e ~/sauce_is_ready ]; do sleep 1; done
touch ~/sauce_was_ready
cat ~/sauce_is_ready
# this is how you check if a string starts with another string in Bash
# https://stackoverflow.com/a/2172367/362030
[[ "$(<~/sauce_is_ready)" != ERROR:* ]]
# This is based on your 1.0 configuration file or project settings
- run:
command: |-
# Screenshots: capture in the background while running unit tests
mkdir -p $CIRCLE_TEST_REPORTS/mocha
# CircleCI expects test results to be reported in an JUnit/xUnit-style XML file:
# https://circleci.com/docs/test-metadata/#a-namemochajsamocha-for-nodejs
# Our unit tests are in a browser, so they can't write to a file, and Sauce
# apparently truncates custom data in their test result reports, so instead we
# POST to this trivial Node server on localhost:9000 that writes the body of
# any POST request to $CIRCLE_TEST_REPORTS/junit/test-results.xml
node -e '
require("http").createServer(function(req, res) {
res.setHeader("Access-Control-Allow-Origin", "*");
req.pipe(process.stdout);
req.on("end", res.end.bind(res));
})
.listen(9000);
console.error("listening on http://0.0.0.0:9000/");
' 2>&1 >$CIRCLE_TEST_REPORTS/junit/test-results.xml | {
# ^ note: `2>&1` must precede `>$CIRCLE_TEST_REPORTS/...` because
# shell redirect is like assignment; if it came after, then both
# stdout and stderr would be written to `xunit.xml` and nothing
# would be piped into here
head -1 # wait for "listening on ..." to be logged
# https://circleci.com/docs/environment-variables/
build_name="CircleCI build #$CIRCLE_BUILD_NUM"
if [ $CIRCLE_PR_NUMBER ]; then
build_name="$build_name: PR #$CIRCLE_PR_NUMBER"
[ "$CIRCLE_BRANCH" ] && build_name="$build_name ($CIRCLE_BRANCH)"
else
build_name="$build_name: $CIRCLE_BRANCH"
fi
build_name="$build_name @ ${CIRCLE_SHA1:0:7}"
export MQ_CI_BUILD_NAME="$build_name"
time { test -d node_modules/wd || npm install wd; }
time node script/screenshots.js http://localhost:8000/test/visual.html \
&& touch ~/screenshots_are_ready || echo EXIT STATUS $? | tee /dev/stderr > ~/screenshots_are_ready:
}
time node script/screenshots.js http://localhost:8000/test/visual.html \
&& touch ~/screenshots_are_ready || echo EXIT STATUS $? | tee /dev/stderr > ~/screenshots_are_ready:
background: true
- run: |-
# Unit tests in the browser
echo '1. Launch tests'
echo
# https://circleci.com/docs/environment-variables/
build_name="CircleCI build #$CIRCLE_BUILD_NUM"
if [ $CIRCLE_PR_NUMBER ]; then
build_name="$build_name: PR #$CIRCLE_PR_NUMBER"
[ "$CIRCLE_BRANCH" ] && build_name="$build_name ($CIRCLE_BRANCH)"
else
build_name="$build_name: $CIRCLE_BRANCH"
fi
build_name="$build_name @ ${CIRCLE_SHA1:0:7}"
# "build" and "customData" parameters from:
# https://wiki.saucelabs.com/display/DOCS/Test+Configuration+Options#TestConfigurationOptions-TestAnnotation
set -o pipefail
curl -i -X POST https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests \
-u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
-H 'Content-Type: application/json' \
-d '{
"name": "Unit tests, Mocha",
"build": "'"$build_name"'",
"customData": {"build_url": "'"$CIRCLE_BUILD_URL"'"},
"framework": "mocha",
"url": "http://localhost:8000/test/unit.html?post_xunit_to=http://localhost:9000",
"platforms": [["", "Chrome", ""]]
}' \
| tee /dev/stderr | tail -1 > js-tests.json
echo '2. Wait for tests to finish:'
echo
# > Make the request multiple times as the tests run until the response
# > contains `completed: true` to the get the final results.
# https://wiki.saucelabs.com/display/DOCS/JavaScript+Unit+Testing+Methods
while true # Bash has no do...while >:(
do
sleep 5
curl -i -X POST https://saucelabs.com/rest/v1/$SAUCE_USERNAME/js-tests/status \
-u $SAUCE_USERNAME:$SAUCE_ACCESS_KEY \
-H 'Content-Type: application/json' \
-d @js-tests.json \
| tee /dev/stderr | tail -1 > status.json
# deliberately do `... != false` rather than `... == true`
# because unexpected values should break rather than infinite loop
[ "$(jq .completed <status.json)" != false ] && break
done
echo '3. Exit with non-zero status code if any unit tests failed'
exit "$(jq '.["js tests"][0].result.failures' <status.json)"
node script/unit_test_webdriver.js http://localhost:8000/test/unit.html?xunit=true
- run: |-
# Stitch together screenshots and diff against master
echo '0. Wait for screenshots to be ready'
while [ ! -e ~/screenshots_are_ready ]; do sleep 1; done
time while [ ! -e ~/screenshots_are_ready ]; do sleep 1; done
test -z "$(<~/screenshots_are_ready)" || exit 1
echo '1. Stitch together pieces'
for img in $(ls $CIRCLE_ARTIFACTS/imgs/pieces/); do
convert $(ls -1 $CIRCLE_ARTIFACTS/imgs/pieces/$img/*.png | sort -n) -append $CIRCLE_ARTIFACTS/imgs/$img.png
time for img in $(ls $CIRCLE_ARTIFACTS/imgs/pieces/); do
time convert $(ls -1 $CIRCLE_ARTIFACTS/imgs/pieces/$img/*.png | sort -n) -append $CIRCLE_ARTIFACTS/imgs/$img.png
done
echo '2. Download the latest screenshots from master'
Expand Down
69 changes: 69 additions & 0 deletions script/unit_test_webdriver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// This script assumes the following:
// 1. You've installed wd with `npm install wd'.
// 2. You've set the environment variables $SAUCE_USERNAME and $SAUCE_ACCESS_KEY.
// 3. If the environment variable $CIRCLE_TEST_REPORTS is not set images will be saved in /tmp
// 4. The argument is a URL of test/unit.html?xunit=true
//
// The intention of this script is that it will be ran from CircleCI
//
// Example usage:
// node unit_test_webdriver.js http://localhost:9292/test/unit.html?xunit=true

var wd = require('wd');
var fs = require('fs');
var url = process.argv[2];
var username = process.env.SAUCE_USERNAME;
var accessKey = process.env.SAUCE_ACCESS_KEY;
var build_name = process.env.MQ_CI_BUILD_NAME;
var baseDir = process.env.CIRCLE_TEST_REPORTS;
if (!baseDir) {
console.error('No $CIRCLE_TEST_REPORTS found, for testing do something like `CIRCLE_TEST_REPORTS=/tmp node script/unit_test_webdriver.js`');
process.exit(1);
}

var success = true

var browserDriver = wd.promiseChainRemote('ondemand.saucelabs.com', 80, username, accessKey);
browserDriver.init({
browserName: 'Chrome',
platform: 'macOS 12',
build: build_name,
name: 'Unit tests',
customData: { build_url: process.env.CIRCLE_BUILD_URL },
})
.get(url)
.then(willLog('get', url))
.setAsyncScriptTimeout(2**31 - 1)
.then(willLog('setAsyncScriptTimeout(2**31 - 1)'))
.safeExecuteAsync('window.xunitCallback = arguments[0];')
.then(willLog('waited for xunitCallback()'))
.then(function(resultsXML) {
var lines = resultsXML.split('\n');
console.log('Got results XML (' + lines.length + ' lines):\n' + lines.slice(0, 10).join('\n') + '\n...');

fs.writeFileSync(baseDir + '/test-results.xml', resultsXML);
console.log('Wrote to ' + baseDir + '/test-results.xml');

const [_, failures, errors] = lines[0].match(/ failures="(\d+)" errors="(\d+)"/)
console.log(failures + ' failures, ' + errors + ' errors');
success = (parseInt(failures) + parseInt(errors) === 0);
})
.fail(function(err) {
console.log('ERROR:', JSON.stringify(err, null, 2));
success = false;
})
.then(function() {
return browserDriver.sauceJobStatus(success);
})
.quit()
.then(function() {
process.exit(success ? 0 : 1);
});

function willLog() {
var msg = [].join.call(arguments, ' ');
return function(value) {
console.log(msg);
return value;
};
}
29 changes: 4 additions & 25 deletions test/unit.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@

<!-- configure mocha and chai -->
<script type="text/javascript">
var post_xunit_to = new URLSearchParams(location.search).get('post_xunit_to');
var outputXunit = new URLSearchParams(location.search).get('xunit');

mocha.setup({
ui: 'tdd',
reporter: post_xunit_to ? 'xunit' : 'html',
reporter: outputXunit ? 'xunit' : 'html'
bail: false
});
</script>
Expand Down Expand Up @@ -58,31 +58,10 @@ <h1>Unit Tests</h1>
teardown(function() { $('#mock').empty(); });

function runXUnit() {
var xunit = '';
Mocha.process.stdout.write = function(line) { xunit += line; };
var runner = mocha.run();

// the following is based on https://github.com/saucelabs-sample-scripts/JavaScript/blob/4946c5cf0ab7325dce5562881dba7c28e30989e5/reporting_mocha.js
var failedTests = [];
runner.on('fail', function(test, err) {
function flattenTitles(test) {
var titles = [];
while (test.parent.title) {
titles.push(test.parent.title);
test = test.parent;
}
return titles.reverse();
}

failedTests.push({name: test.title, result: false, message: err.message, stack: err.stack, titles: flattenTitles(test) });
});

runner.on('end', function() {
setTimeout(function() {
$.post(post_xunit_to, xunit).complete(function() {
window.mochaResults = runner.stats;
window.mochaResults.reports = failedTests;
});
if (window.xunitCallback) xunitCallback(xunit);
});
});
}
Expand Down Expand Up @@ -223,7 +202,7 @@ <h1>Unit Tests</h1>
}
}

if (post_xunit_to) {
if (outputXunit) {
runXUnit();
} else {
runJenkins();
Expand Down

0 comments on commit b15ce1b

Please sign in to comment.