Add some more sanity checks in run-tests.sh, and refactor that part of the script
[arvados.git] / jenkins / run-tests.sh
1 #!/bin/bash
2
3 read -rd "\000" helpmessage <<EOF
4 $(basename $0): Install and test Arvados components.
5
6 Exit non-zero if any tests fail.
7
8 Syntax:
9         $(basename $0) WORKSPACE=/path/to/arvados [options]
10
11 Options:
12
13 --skip FOO     Do not test the FOO component.
14 --only FOO     Do not test anything except the FOO component.
15 --leave-temp   Do not remove GOPATH, virtualenv, and other temp dirs at exit.
16                Instead, show which directories were used this time so they
17                can be reused in subsequent invocations.
18 --skip-install Do not run any install steps. Just run tests.
19                You should provide GOPATH, GEMHOME, and VENVDIR options
20                from a previous invocation if you use this option.
21 WORKSPACE=path Arvados source tree to test.
22 CONFIGSRC=path Dir with api server config files to copy into source tree.
23                (If none given, leave config files alone in source tree.)
24 services/api_test="TEST=test/functional/arvados/v1/collections_controller_test.rb"
25                Restrict apiserver tests to the given file
26 sdk/python_test="--test-suite test.test_keep_locator"
27                Restrict Python SDK tests to the given class
28 apps/workbench_test="TEST=test/integration/pipeline_instances_test.rb"
29                Restrict Workbench tests to the given file
30 ARVADOS_DEBUG=1
31                Print more debug messages
32 envvar=value   Set \$envvar to value. Primarily useful for WORKSPACE,
33                *_test, and other examples shown above.
34
35 Assuming --skip-install is not given, all components are installed
36 into \$GOPATH, \$VENDIR, and \$GEMHOME before running any tests. Many
37 test suites depend on other components being installed, and installing
38 everything tends to be quicker than debugging dependencies.
39
40 As a special concession to the current CI server config, CONFIGSRC
41 defaults to $HOME/arvados-api-server if that directory exists.
42
43 More information and background:
44
45 https://arvados.org/projects/arvados/wiki/Running_tests
46
47 Available tests:
48
49 apps/workbench
50 apps/workbench_performance
51 doc
52 services/api
53 services/crunchstat
54 services/fuse
55 services/keepproxy
56 services/keepstore
57 services/nodemanager
58 sdk/cli
59 sdk/python
60 sdk/ruby
61 sdk/go/arvadosclient
62 sdk/go/keepclient
63 sdk/go/streamer
64
65 EOF
66
67 # First make sure to remove any ARVADOS_ variables from the calling
68 # environment that could interfere with the tests.
69 unset $(env | cut -d= -f1 | grep \^ARVADOS_)
70
71 # Reset other variables that could affect our [tests'] behavior by
72 # accident.
73 GITDIR=
74 GOPATH=
75 VENVDIR=
76 PYTHONPATH=
77 GEMHOME=
78
79 COLUMNS=80
80
81 leave_temp=
82 skip_install=
83
84 declare -A leave_temp
85 clear_temp() {
86     leaving=""
87     for var in VENVDIR GOPATH GITDIR GEMHOME
88     do
89         if [[ -z "${leave_temp[$var]}" ]]
90         then
91             if [[ -n "${!var}" ]]
92             then
93                 rm -rf "${!var}"
94             fi
95         else
96             leaving+=" $var=\"${!var}\""
97         fi
98     done
99     if [[ -n "$leaving" ]]; then
100         echo "Leaving behind temp dirs: $leaving"
101     fi
102 }
103
104 fatal() {
105     clear_temp
106     echo >&2 "Fatal: $* in ${FUNCNAME[1]} at ${BASH_SOURCE[1]} line ${BASH_LINENO[0]}"
107     exit 1
108 }
109
110 report_outcomes() {
111     for x in "${successes[@]}"
112     do
113         echo "Pass: $x"
114     done
115
116     if [[ ${#failures[@]} == 0 ]]
117     then
118         echo "All test suites passed."
119     else
120         echo "Failures (${#failures[@]}):"
121         for x in "${failures[@]}"
122         do
123             echo "Fail: $x"
124         done
125     fi
126 }
127
128 sanity_checks() {
129   # Make sure WORKSPACE is set
130   if ! [[ -n "$WORKSPACE" ]]; then
131     echo >&2 "$helpmessage"
132     echo >&2
133     echo >&2 "Error: WORKSPACE environment variable not set"
134     echo >&2
135     exit 1
136   fi
137
138   # Make sure virtualenv is installed
139   `virtualenv --help >/dev/null 2>&1`
140
141   if [[ "$?" != "0" ]]; then
142     echo >&2
143     echo >&2 "Error: virtualenv could not be found"
144     echo >&2
145     exit 1
146   fi
147
148   # Make sure go is installed
149   `go env >/dev/null 2>&1`
150
151   if [[ "$?" != "0" ]]; then
152     echo >&2
153     echo >&2 "Error: go could not be found"
154     echo >&2
155     exit 1
156   fi
157
158   # Make sure gcc is installed
159   `gcc --help >/dev/null 2>&1`
160
161   if [[ "$?" != "0" ]]; then
162     echo >&2
163     echo >&2 "Error: gcc could not be found"
164     echo >&2
165     exit 1
166   fi
167
168 }
169
170 declare -a failures
171 declare -A skip
172 declare -A testargs
173
174 while [[ -n "$1" ]]
175 do
176     arg="$1"; shift
177     case "$arg" in
178         --help)
179             echo >&2 "$helpmessage"
180             echo >&2
181             exit 1
182             ;;
183         --skip)
184             skipwhat="$1"; shift
185             skip[$skipwhat]=1
186             ;;
187         --only)
188             only="$1"; shift
189             ;;
190         --skip-install)
191             skip_install=1
192             ;;
193         --leave-temp)
194             leave_temp[VENVDIR]=1
195             leave_temp[GOPATH]=1
196             leave_temp[GEMHOME]=1
197             ;;
198         *_test=*)
199             suite="${arg%%_test=*}"
200             args="${arg#*=}"
201             testargs["$suite"]="$args"
202             ;;
203         *=*)
204             eval export $(echo $arg | cut -d= -f1)=\"$(echo $arg | cut -d= -f2-)\"
205             ;;
206         *)
207             echo >&2 "$0: Unrecognized option: '$arg'. Try: $0 --help"
208             exit 1
209             ;;
210     esac
211 done
212
213 sanity_checks
214
215 echo "WORKSPACE=$WORKSPACE"
216
217 if [[ -z "$CONFIGSRC" ]] && [[ -d "$HOME/arvados-api-server" ]]; then
218     # Jenkins expects us to use this by default.
219     CONFIGSRC="$HOME/arvados-api-server"
220 fi
221
222 # Clean up .pyc files that may exist in the workspace
223 cd "$WORKSPACE"
224 find -name '*.pyc' -delete
225
226 # Set up temporary install dirs (unless existing dirs were supplied)
227 for tmpdir in VENVDIR GOPATH GEMHOME
228 do
229     if [[ -n "${!tmpdir}" ]]; then
230         leave_temp[$tmpdir]=1
231     else
232         eval $tmpdir=$(mktemp -d)
233     fi
234 done
235
236 setup_ruby_environment() {
237     if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
238       source "$HOME/.rvm/scripts/rvm"
239       using_rvm=true
240     elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
241       source "/usr/local/rvm/scripts/rvm"
242       using_rvm=true
243     else
244       using_rvm=false
245     fi
246
247     if [[ "$using_rvm" == true ]]; then
248         # If rvm is in use, we can't just put separate "dependencies"
249         # and "gems-under-test" paths to GEM_PATH: passenger resets
250         # the environment to the "current gemset", which would lose
251         # our GEM_PATH and prevent our test suites from running ruby
252         # programs (for example, the Workbench test suite could not
253         # boot an API server or run arv). Instead, we have to make an
254         # rvm gemset and use it for everything.
255
256         [[ `type rvm | head -n1` == "rvm is a function" ]] \
257             || fatal 'rvm check'
258
259         # Put rvm's favorite path back in first place (overriding
260         # virtualenv, which just put itself there). Ignore rvm's
261         # complaint about not being in first place already.
262         rvm use @default 2>/dev/null
263
264         # Create (if needed) and switch to an @arvados-tests
265         # gemset. (Leave the choice of ruby to the caller.)
266         rvm use @arvados-tests --create \
267             || fatal 'rvm gemset setup'
268
269         rvm env
270     else
271         # When our "bundle install"s need to install new gems to
272         # satisfy dependencies, we want them to go where "gem install
273         # --user-install" would put them. (However, if the caller has
274         # already set GEM_HOME, we assume that's where dependencies
275         # should be installed, and we should leave it alone.)
276
277         if [ -z "$GEM_HOME" ]; then
278             user_gempath="$(gem env gempath)"
279             export GEM_HOME="${user_gempath%%:*}"
280         fi
281         PATH="$(gem env gemdir)/bin:$PATH"
282
283         # When we build and install our own gems, we install them in our
284         # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
285         # PATH so integration tests prefer them over other versions that
286         # happen to be installed in $user_gempath, system dirs, etc.
287
288         tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
289         PATH="$tmpdir_gem_home/bin:$PATH"
290         export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
291
292         echo "Will install dependencies to $(gem env gemdir)"
293         echo "Will install arvados gems to $tmpdir_gem_home"
294         echo "Gem search path is GEM_PATH=$GEM_PATH"
295     fi
296 }
297
298 with_test_gemset() {
299     if [[ "$using_rvm" == true ]]; then
300         "$@"
301     else
302         GEM_HOME="$tmpdir_gem_home" "$@"
303     fi
304 }
305
306 export GOPATH
307 mkdir -p "$GOPATH/src/git.curoverse.com"
308 ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git" \
309     || fatal "symlink failed"
310
311 virtualenv --setuptools "$VENVDIR" || fatal "virtualenv $VENVDIR failed"
312 . "$VENVDIR/bin/activate"
313
314 # Note: this must be the last time we change PATH, otherwise rvm will
315 # whine a lot.
316 setup_ruby_environment
317
318 echo "PATH is $PATH"
319
320 if ! which bundler >/dev/null
321 then
322     gem install --user-install bundler || fatal 'Could not install bundler'
323 fi
324
325 # Needed for run_test_server.py which is used by certain (non-Python) tests.
326 pip install PyYAML
327
328 checkexit() {
329     if [[ "$?" != "0" ]]; then
330         title "!!!!!! $1 FAILED !!!!!!"
331         failures+=("$1 (`timer`)")
332     else
333         successes+=("$1 (`timer`)")
334     fi
335 }
336
337 timer_reset() {
338     t0=$SECONDS
339 }
340
341 timer() {
342     echo -n "$(($SECONDS - $t0))s"
343 }
344
345 do_test() {
346     if [[ -z "${skip[$1]}" ]] && ( [[ -z "$only" ]] || [[ "$only" == "$1" ]] )
347     then
348         title "Running $1 tests"
349         timer_reset
350         if [[ "$2" == "go" ]]
351         then
352             go test ${testargs[$1]} "git.curoverse.com/arvados.git/$1"
353         elif [[ "$2" == "pip" ]]
354         then
355            # Other test suites can depend on tests_require
356            # dependencies of this package. For example, keepproxy runs
357            # run_test_server.py, which depends on the yaml package,
358            # which is in sdk/python's tests_require but not
359            # install_requires, and therefore does not get installed by
360            # setuptools until we run "setup.py test" *and* install the
361            # .egg files that setup.py downloads.
362            cd "$WORKSPACE/$1" \
363                 && python setup.py test ${testargs[$1]} \
364                 && (easy_install *.egg || true)
365         elif [[ "$2" != "" ]]
366         then
367             "test_$2"
368         else
369             "test_$1"
370         fi
371         checkexit "$1 tests"
372         title "End of $1 tests (`timer`)"
373     else
374         title "Skipping $1 tests"
375     fi
376 }
377
378 do_install() {
379     if [[ -z "$skip_install" ]]
380     then
381         title "Running $1 install"
382         timer_reset
383         if [[ "$2" == "go" ]]
384         then
385             go get -t "git.curoverse.com/arvados.git/$1"
386         elif [[ "$2" == "pip" ]]
387         then
388             cd "$WORKSPACE/$1" \
389                 && python setup.py sdist rotate --keep=1 --match .tar.gz \
390                 && pip install --upgrade dist/*.tar.gz
391         elif [[ "$2" != "" ]]
392         then
393             "install_$2"
394         else
395             "install_$1"
396         fi
397         checkexit "$1 install"
398         title "End of $1 install (`timer`)"
399     else
400         title "Skipping $1 install"
401     fi
402 }
403
404 title () {
405     txt="********** $1 **********"
406     printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
407 }
408
409 install_doc() {
410     cd "$WORKSPACE/doc"
411     bundle install --no-deployment
412     rm -rf .site
413 }
414 do_install doc
415
416 install_ruby_sdk() {
417     with_test_gemset gem uninstall --force --all --executables arvados \
418         && cd "$WORKSPACE/sdk/ruby" \
419         && bundle install --no-deployment \
420         && gem build arvados.gemspec \
421         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-*.gem|head -n1`
422 }
423 do_install sdk/ruby ruby_sdk
424
425 install_cli() {
426     with_test_gemset gem uninstall --force --all --executables arvados-cli \
427         && cd "$WORKSPACE/sdk/cli" \
428         && bundle install --no-deployment \
429         && gem build arvados-cli.gemspec \
430         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-cli-*.gem|head -n1`
431 }
432 do_install sdk/cli cli
433
434 # Install the Python SDK early. Various other test suites (like
435 # keepproxy) bring up run_test_server.py, which imports the arvados
436 # module. We can't actually *test* the Python SDK yet though, because
437 # its own test suite brings up some of those other programs (like
438 # keepproxy).
439 declare -a pythonstuff
440 pythonstuff=(
441     sdk/python
442     services/fuse
443     services/nodemanager
444     )
445 for p in "${pythonstuff[@]}"
446 do
447     do_install "$p" pip
448 done
449
450 install_apiserver() {
451     cd "$WORKSPACE/services/api"
452     RAILS_ENV=test bundle install --no-deployment
453
454     rm -f config/environments/test.rb
455     cp config/environments/test.rb.example config/environments/test.rb
456
457     if [ -n "$CONFIGSRC" ]
458     then
459         for f in database.yml application.yml
460         do
461             cp "$CONFIGSRC/$f" config/ || fatal "$f"
462         done
463     fi
464
465     # Fill in a random secret_token and blob_signing_key for testing
466     SECRET_TOKEN=`echo 'puts rand(2**512).to_s(36)' |ruby`
467     BLOB_SIGNING_KEY=`echo 'puts rand(2**512).to_s(36)' |ruby`
468
469     sed -i'' -e "s:SECRET_TOKEN:$SECRET_TOKEN:" config/application.yml
470     sed -i'' -e "s:BLOB_SIGNING_KEY:$BLOB_SIGNING_KEY:" config/application.yml
471
472     # Set up empty git repo (for git tests)
473     GITDIR=$(mktemp -d)
474     sed -i'' -e "s:/var/cache/git:$GITDIR:" config/application.default.yml
475
476     rm -rf $GITDIR
477     mkdir -p $GITDIR/test
478     cd $GITDIR/test \
479         && git init \
480         && git config user.email "jenkins@ci.curoverse.com" \
481         && git config user.name "Jenkins, CI" \
482         && touch tmp \
483         && git add tmp \
484         && git commit -m 'initial commit'
485
486     # Clear out any lingering postgresql connections to arvados_test, so that we can drop it
487     # This assumes the current user is a postgresql superuser
488     psql arvados_test -c "SELECT pg_terminate_backend (pg_stat_activity.procpid::int) FROM pg_stat_activity WHERE pg_stat_activity.datname = 'arvados_test';" 2>/dev/null
489
490     cd "$WORKSPACE/services/api" \
491         && RAILS_ENV=test bundle exec rake db:drop \
492         && RAILS_ENV=test bundle exec rake db:create \
493         && RAILS_ENV=test bundle exec rake db:setup
494 }
495 do_install services/api apiserver
496
497 declare -a gostuff
498 gostuff=(
499     services/crunchstat
500     services/keepstore
501     services/keepproxy
502     sdk/go/arvadosclient
503     sdk/go/keepclient
504     sdk/go/streamer
505     )
506 for g in "${gostuff[@]}"
507 do
508     do_install "$g" go
509 done
510
511 install_workbench() {
512     cd "$WORKSPACE/apps/workbench" \
513         && RAILS_ENV=test bundle install --no-deployment
514 }
515 do_install apps/workbench workbench
516
517 test_doclinkchecker() {
518     cd "$WORKSPACE/doc"
519     # Make sure python-epydoc is installed or the next line won't do much good!
520     ARVADOS_API_HOST=qr1hi.arvadosapi.com
521     PYTHONPATH=$WORKSPACE/sdk/python/ bundle exec rake linkchecker baseurl=file://$WORKSPACE/doc/.site/ arvados_workbench_host=workbench.$ARVADOS_API_HOST arvados_api_host=$ARVADOS_API_HOST
522     unset ARVADOS_API_HOST
523 }
524 do_test doc doclinkchecker
525
526 test_ruby_sdk() {
527     cd "$WORKSPACE/sdk/ruby" \
528         && bundle install --no-deployment \
529         && bundle exec rake test ${testargs[sdk/ruby]}
530 }
531 do_test sdk/ruby ruby_sdk
532
533 test_cli() {
534     cd "$WORKSPACE/sdk/cli" \
535         && bundle install --no-deployment \
536         && mkdir -p /tmp/keep \
537         && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test ${testargs[sdk/cli]}
538 }
539 do_test sdk/cli cli
540
541 test_apiserver() {
542     cd "$WORKSPACE/services/api"
543     RAILS_ENV=test bundle exec rake test ${testargs[apiserver]}
544 }
545 do_test services/api apiserver
546
547 # We must test sdk/python before testing services/keepproxy, because
548 # keepproxy depends on sdk/python's test dependencies.
549 for p in "${pythonstuff[@]}"
550 do
551     do_test "$p" pip
552 done
553
554 for g in "${gostuff[@]}"
555 do
556     do_test "$g" go
557 done
558
559 test_workbench() {
560     cd "$WORKSPACE/apps/workbench" \
561         && RAILS_ENV=test bundle exec rake test ${testargs[workbench]}
562 }
563 do_test apps/workbench workbench
564
565 test_workbench_performance() {
566     cd "$WORKSPACE/apps/workbench" \
567         && RAILS_ENV=test bundle exec rake test:benchmark
568 }
569 do_test apps/workbench_performance workbench_performance
570
571 report_outcomes
572 clear_temp
573
574 exit ${#failures}