forked from mathquill/mathquill
-
Notifications
You must be signed in to change notification settings - Fork 0
/
circle.yml
248 lines (215 loc) · 10.6 KB
/
circle.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# Okay so maybe everyone else already knows all this, but it took some time
# for Michael and I [Han] to really see how everything fits together.
#
# Basically, what we're doing here is automated browser testing, so CircleCI
# handles the automation, and Sauce Labs handles the browser testing.
# Specifically, Sauce Labs offers a REST API to run tests in browsers in VMs,
# and CircleCI can be configured to listen for git pushes and run local
# servers and call out to REST APIs to test against these local servers.
#
# The flow goes like this:
# - CircleCI notices/is notified of a git push
# - they pull and checkout and magically know to install dependencies and shit
# + https://circleci.com/docs/manually/
# - their magic works fine for MathQuill's dependencies but to run the tests,
# it foolishly runs `make test`, what an inconceivable mistake
# - that's where we come in: `circle.yml` lets us override the test script.
# + https://circleci.com/docs/configuration/
# - our `circle.yml` first installs and runs a tunnel to Sauce Labs
# - and runs `make server`
# - then it calls out to Sauce Labs' REST API to open browsers that reach
# back through the tunnel to access test pages on the local server
# + > Sauce Connect allows you to run a test server within the CircleCI
# > build container and expose it it (using a URL like `localhost:8080`)
# > to Sauce Labs’ browsers.
#
# https://circleci.com/docs/browser-testing-with-sauce-labs/
#
# - boom testing boom
# this file is based on https://github.com/circleci/sauce-connect/blob/a65e41c91e02550ce56c75740a422bebc4acbf6f/circle.yml
# via https://circleci.com/docs/browser-testing-with-sauce-labs/
dependencies:
cache_directories:
- ~/sauce-connect
pre:
- ? |-
# SauceConnect: download if not cached, and launch with retry
test $SAUCE_USERNAME && test $SAUCE_ACCESS_KEY || {
echo 'Sauce Labs credentials required. Sign up here: https://saucelabs.com/opensauce/'
exit 1
}
mkdir -p ~/sauce-connect
cd ~/sauce-connect
if [ -x sc-*-linux/bin/sc ]; then
echo Using cached sc-*-linux/bin/sc
else
time wget https://saucelabs.com/downloads/sc-latest-linux.tar.gz
time tar -xzf sc-latest-linux.tar.gz
fi
time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY \
--readyfile ~/sauce_is_ready
test -e ~/sauce_was_ready && exit
echo 'Sauce Connect failed, try redownloading (https://git.io/vSxsJ)'
rm -rf *
time wget https://saucelabs.com/downloads/sc-latest-linux.tar.gz
time tar -xzf sc-latest-linux.tar.gz
time sc-*-linux/bin/sc --user $SAUCE_USERNAME --api-key $SAUCE_ACCESS_KEY \
--readyfile ~/sauce_is_ready
test -e ~/sauce_was_ready && exit
echo 'ERROR: Exited twice without creating readyfile' \
| tee /dev/stderr > ~/sauce_is_ready
exit 1
:
background: true
test:
pre:
- |-
# 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 \
-d '{
"context": "ci/many-worlds",
"state": "success",
"description": "Try the tests on the Many-Worlds build of this commit:",
"target_url": "http://many-worlds.glitch.me/mathquill/mathquill/commit/'$CIRCLE_SHA1'/test/"
}'
# Safari on Sauce can only connect to port 3000, 4000, 7000, or 8000. Edge needs port 7000 or 8000.
# https://david263a.wordpress.com/2015/04/18/fixing-safari-cant-connect-to-localhost-issue-when-using-sauce-labs-connect-tunnel/
# https://support.saucelabs.com/customer/portal/questions/14368823-requests-to-localhost-on-microsoft-edge-are-failing-over-sauce-connect
- PORT=8000 make server:
background: true
# Wait for tunnel to be ready (`make server` is much faster, no need to wait for it)
- while [ ! -e ~/sauce_is_ready ]; do sleep 1; done; touch ~/sauce_was_ready; test -z "$(<~/sauce_is_ready)"
override:
- ? |-
# 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:
}
:
background: true
- |-
# 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)"
- |-
# Stitch together screenshots and diff against master
echo '0. Wait for screenshots to be ready'
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
done
echo '2. Download the latest screenshots from master'
echo
artifacts_json="$(curl https://circleci.com/api/v1/project/mathquill/mathquill/latest/artifacts?branch=master)"
echo
echo '/latest/artifacts?branch=master:'
echo
echo "$artifacts_json"
echo
mkdir $CIRCLE_ARTIFACTS/imgs/baseline/
baseline_imgs="$(echo "$artifacts_json" \
| jq -r '.[] | .url + " -o " + .pretty_path' \
| grep '\.png$' \
| grep -v '_DIFF\.png$' \
| grep -vF '/pieces/' \
| grep -vF '/baseline/' \
| sed "s:\$CIRCLE_ARTIFACTS/imgs/:$CIRCLE_ARTIFACTS/imgs/baseline/:")"
echo 'Baseline image URLs and files:'
echo
echo "$baseline_imgs"
echo
test -z "$baseline_imgs" && { echo 'No baseline images to download'; exit; }
curl $baseline_imgs
echo
echo '3. Generate image diffs'
echo
cd $CIRCLE_ARTIFACTS/imgs/
for file in $(ls *.png); do
# if evergreen browser, browser version of previous screenshot may not match,
# so replace previous browser version with glob
baseline="$(echo baseline/$(echo $file | sed 's/[^_]*_(evergreen)/*/; s/OS_X_.*/OS_X_*.png/' | tee /dev/stderr) | tee /dev/stderr)"
echo "Number of different pixels from baseline in $file:"
compare -metric AE $baseline $file ${file/%.png/_DIFF.png}
echo
done
true # ignore errors like "image widths or heights differ"
post:
- killall --wait sc; true # wait for Sauce Connect to close the tunnel; ignore errors since it's just cleanup