Merge branch 'sigint' closes #5178
[arvados-dev.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_benchmark
51 apps/workbench_profile
52 doc
53 services/api
54 services/crunchstat
55 services/fuse
56 services/keepproxy
57 services/keepstore
58 services/nodemanager
59 sdk/cli
60 sdk/python
61 sdk/ruby
62 sdk/go/arvadosclient
63 sdk/go/keepclient
64 sdk/go/streamer
65
66 EOF
67
68 # First make sure to remove any ARVADOS_ variables from the calling
69 # environment that could interfere with the tests.
70 unset $(env | cut -d= -f1 | grep \^ARVADOS_)
71
72 # Reset other variables that could affect our [tests'] behavior by
73 # accident.
74 GITDIR=
75 GOPATH=
76 VENVDIR=
77 PYTHONPATH=
78 GEMHOME=
79
80 COLUMNS=80
81
82 leave_temp=
83 skip_install=
84
85 declare -A leave_temp
86 clear_temp() {
87     leaving=""
88     for var in VENVDIR GOPATH GITDIR GEMHOME
89     do
90         if [[ -z "${leave_temp[$var]}" ]]
91         then
92             if [[ -n "${!var}" ]]
93             then
94                 rm -rf "${!var}"
95             fi
96         else
97             leaving+=" $var=\"${!var}\""
98         fi
99     done
100     if [[ -n "$leaving" ]]; then
101         echo "Leaving behind temp dirs: $leaving"
102     fi
103 }
104
105 fatal() {
106     clear_temp
107     echo >&2 "Fatal: $* in ${FUNCNAME[1]} at ${BASH_SOURCE[1]} line ${BASH_LINENO[0]}"
108     exit 1
109 }
110
111 report_outcomes() {
112     for x in "${successes[@]}"
113     do
114         echo "Pass: $x"
115     done
116
117     if [[ ${#failures[@]} == 0 ]]
118     then
119         echo "All test suites passed."
120     else
121         echo "Failures (${#failures[@]}):"
122         for x in "${failures[@]}"
123         do
124             echo "Fail: $x"
125         done
126     fi
127 }
128
129 exit_cleanly() {
130     trap - INT
131     rotate_logfile "$WORKSPACE/apps/workbench/log/" "test.log"
132     stop_api
133     rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
134     report_outcomes
135     clear_temp
136     exit ${#failures}
137 }
138
139 sanity_checks() {
140   # Make sure WORKSPACE is set
141   if ! [[ -n "$WORKSPACE" ]]; then
142     echo >&2 "$helpmessage"
143     echo >&2
144     echo >&2 "Error: WORKSPACE environment variable not set"
145     echo >&2
146     exit 1
147   fi
148
149   # Make sure virtualenv is installed
150   `virtualenv --help >/dev/null 2>&1`
151
152   if [[ "$?" != "0" ]]; then
153     echo >&2
154     echo >&2 "Error: virtualenv could not be found"
155     echo >&2
156     exit 1
157   fi
158
159   # Make sure go is installed
160   `go env >/dev/null 2>&1`
161
162   if [[ "$?" != "0" ]]; then
163     echo >&2
164     echo >&2 "Error: go could not be found"
165     echo >&2
166     exit 1
167   fi
168
169   # Make sure gcc is installed
170   `gcc --help >/dev/null 2>&1`
171
172   if [[ "$?" != "0" ]]; then
173     echo >&2
174     echo >&2 "Error: gcc could not be found"
175     echo >&2
176     exit 1
177   fi
178 }
179
180 rotate_logfile() {
181   # $BUILD_NUMBER is set by Jenkins if this script is being called as part of a Jenkins run
182   if [[ -f "$1/$2" ]]; then
183     THEDATE=`date +%Y%m%d%H%M%S`
184     mv "$1/$2" "$1/$THEDATE-$BUILD_NUMBER-$2"
185     gzip "$1/$THEDATE-$BUILD_NUMBER-$2"
186   fi
187 }
188
189 declare -a failures
190 declare -A skip
191 declare -A testargs
192 skip[apps/workbench_profile]=1
193
194 while [[ -n "$1" ]]
195 do
196     arg="$1"; shift
197     case "$arg" in
198         --help)
199             echo >&2 "$helpmessage"
200             echo >&2
201             exit 1
202             ;;
203         --skip)
204             skipwhat="$1"; shift
205             skip[$skipwhat]=1
206             ;;
207         --only)
208             only="$1"; skip[$1]=""; shift
209             ;;
210         --skip-install)
211             skip_install=1
212             ;;
213         --leave-temp)
214             leave_temp[VENVDIR]=1
215             leave_temp[GOPATH]=1
216             leave_temp[GEMHOME]=1
217             ;;
218         *_test=*)
219             suite="${arg%%_test=*}"
220             args="${arg#*=}"
221             testargs["$suite"]="$args"
222             ;;
223         *=*)
224             eval export $(echo $arg | cut -d= -f1)=\"$(echo $arg | cut -d= -f2-)\"
225             ;;
226         *)
227             echo >&2 "$0: Unrecognized option: '$arg'. Try: $0 --help"
228             exit 1
229             ;;
230     esac
231 done
232
233 start_api() {
234     echo 'Starting API server...'
235     cd "$WORKSPACE" \
236         && eval $(python sdk/python/tests/run_test_server.py start --auth admin) \
237         && export ARVADOS_TEST_API_HOST="$ARVADOS_API_HOST" \
238         && export ARVADOS_TEST_API_INSTALLED="$$" \
239         && (env | egrep ^ARVADOS)
240 }
241
242 stop_api() {
243     if [[ -n "$ARVADOS_TEST_API_HOST" ]]; then
244         unset ARVADOS_TEST_API_HOST
245         cd "$WORKSPACE" \
246             && python sdk/python/tests/run_test_server.py stop
247     fi
248 }
249
250 interrupt() {
251     failures+=("($(basename $0) interrupted)")
252     exit_cleanly
253 }
254 trap interrupt INT
255
256 sanity_checks
257
258 echo "WORKSPACE=$WORKSPACE"
259
260 if [[ -z "$CONFIGSRC" ]] && [[ -d "$HOME/arvados-api-server" ]]; then
261     # Jenkins expects us to use this by default.
262     CONFIGSRC="$HOME/arvados-api-server"
263 fi
264
265 # Clean up .pyc files that may exist in the workspace
266 cd "$WORKSPACE"
267 find -name '*.pyc' -delete
268
269 # Set up temporary install dirs (unless existing dirs were supplied)
270 for tmpdir in VENVDIR GOPATH GEMHOME
271 do
272     if [[ -n "${!tmpdir}" ]]; then
273         leave_temp[$tmpdir]=1
274     else
275         eval $tmpdir=$(mktemp -d)
276     fi
277 done
278
279 setup_ruby_environment() {
280     if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
281       source "$HOME/.rvm/scripts/rvm"
282       using_rvm=true
283     elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
284       source "/usr/local/rvm/scripts/rvm"
285       using_rvm=true
286     else
287       using_rvm=false
288     fi
289
290     if [[ "$using_rvm" == true ]]; then
291         # If rvm is in use, we can't just put separate "dependencies"
292         # and "gems-under-test" paths to GEM_PATH: passenger resets
293         # the environment to the "current gemset", which would lose
294         # our GEM_PATH and prevent our test suites from running ruby
295         # programs (for example, the Workbench test suite could not
296         # boot an API server or run arv). Instead, we have to make an
297         # rvm gemset and use it for everything.
298
299         [[ `type rvm | head -n1` == "rvm is a function" ]] \
300             || fatal 'rvm check'
301
302         # Put rvm's favorite path back in first place (overriding
303         # virtualenv, which just put itself there). Ignore rvm's
304         # complaint about not being in first place already.
305         rvm use @default 2>/dev/null
306
307         # Create (if needed) and switch to an @arvados-tests
308         # gemset. (Leave the choice of ruby to the caller.)
309         rvm use @arvados-tests --create \
310             || fatal 'rvm gemset setup'
311
312         rvm env
313     else
314         # When our "bundle install"s need to install new gems to
315         # satisfy dependencies, we want them to go where "gem install
316         # --user-install" would put them. (However, if the caller has
317         # already set GEM_HOME, we assume that's where dependencies
318         # should be installed, and we should leave it alone.)
319
320         if [ -z "$GEM_HOME" ]; then
321             user_gempath="$(gem env gempath)"
322             export GEM_HOME="${user_gempath%%:*}"
323         fi
324         PATH="$(gem env gemdir)/bin:$PATH"
325
326         # When we build and install our own gems, we install them in our
327         # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
328         # PATH so integration tests prefer them over other versions that
329         # happen to be installed in $user_gempath, system dirs, etc.
330
331         tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
332         PATH="$tmpdir_gem_home/bin:$PATH"
333         export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
334
335         echo "Will install dependencies to $(gem env gemdir)"
336         echo "Will install arvados gems to $tmpdir_gem_home"
337         echo "Gem search path is GEM_PATH=$GEM_PATH"
338     fi
339 }
340
341 with_test_gemset() {
342     if [[ "$using_rvm" == true ]]; then
343         "$@"
344     else
345         GEM_HOME="$tmpdir_gem_home" "$@"
346     fi
347 }
348
349 export GOPATH
350 mkdir -p "$GOPATH/src/git.curoverse.com"
351 ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git" \
352     || fatal "symlink failed"
353
354 virtualenv --setuptools "$VENVDIR" || fatal "virtualenv $VENVDIR failed"
355 . "$VENVDIR/bin/activate"
356
357 # Note: this must be the last time we change PATH, otherwise rvm will
358 # whine a lot.
359 setup_ruby_environment
360
361 echo "PATH is $PATH"
362
363 if ! which bundler >/dev/null
364 then
365     gem install --user-install bundler || fatal 'Could not install bundler'
366 fi
367
368 # Needed for run_test_server.py which is used by certain (non-Python) tests.
369 echo "pip install -q PyYAML"
370 pip install -q PyYAML || fatal "pip install PyYAML failed"
371
372 checkexit() {
373     if [[ "$?" != "0" ]]; then
374         title "!!!!!! $1 FAILED !!!!!!"
375         failures+=("$1 (`timer`)")
376     else
377         successes+=("$1 (`timer`)")
378     fi
379 }
380
381 timer_reset() {
382     t0=$SECONDS
383 }
384
385 timer() {
386     echo -n "$(($SECONDS - $t0))s"
387 }
388
389 do_test() {
390     if [[ -z "${skip[$1]}" ]] && ( [[ -z "$only" ]] || [[ "$only" == "$1" ]] )
391     then
392         title "Running $1 tests"
393         timer_reset
394         if [[ "$2" == "go" ]]
395         then
396             go test ${testargs[$1]} "git.curoverse.com/arvados.git/$1"
397         elif [[ "$2" == "pip" ]]
398         then
399            cd "$WORKSPACE/$1" \
400                 && python setup.py test ${testargs[$1]}
401         elif [[ "$2" != "" ]]
402         then
403             "test_$2"
404         else
405             "test_$1"
406         fi
407         checkexit "$1 tests"
408         title "End of $1 tests (`timer`)"
409     else
410         title "Skipping $1 tests"
411     fi
412 }
413
414 do_install() {
415     if [[ -z "$skip_install" ]]
416     then
417         title "Running $1 install"
418         timer_reset
419         if [[ "$2" == "go" ]]
420         then
421             go get -t "git.curoverse.com/arvados.git/$1"
422         elif [[ "$2" == "pip" ]]
423         then
424             cd "$WORKSPACE/$1" \
425                 && python setup.py sdist rotate --keep=1 --match .tar.gz \
426                 && pip install -q --upgrade dist/*.tar.gz
427         elif [[ "$2" != "" ]]
428         then
429             "install_$2"
430         else
431             "install_$1"
432         fi
433         checkexit "$1 install"
434         title "End of $1 install (`timer`)"
435     else
436         title "Skipping $1 install"
437     fi
438 }
439
440 title () {
441     txt="********** $1 **********"
442     printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
443 }
444
445 bundle_install_trylocal() {
446     (
447         set -e
448         echo "(Running bundle install --local. 'could not find package' messages are OK.)"
449         if ! bundle install --local --no-deployment; then
450             echo "(Running bundle install again, without --local.)"
451             bundle install --no-deployment
452         fi
453         bundle package --all
454     )
455 }
456
457 install_doc() {
458     cd "$WORKSPACE/doc" \
459         && bundle_install_trylocal \
460         && rm -rf .site
461 }
462 do_install doc
463
464 install_ruby_sdk() {
465     with_test_gemset gem uninstall --force --all --executables arvados \
466         && cd "$WORKSPACE/sdk/ruby" \
467         && bundle_install_trylocal \
468         && gem build arvados.gemspec \
469         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-*.gem|head -n1`
470 }
471 do_install sdk/ruby ruby_sdk
472
473 install_cli() {
474     with_test_gemset gem uninstall --force --all --executables arvados-cli \
475         && cd "$WORKSPACE/sdk/cli" \
476         && bundle_install_trylocal \
477         && gem build arvados-cli.gemspec \
478         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-cli-*.gem|head -n1`
479 }
480 do_install sdk/cli cli
481
482 # Install the Python SDK early. Various other test suites (like
483 # keepproxy) bring up run_test_server.py, which imports the arvados
484 # module. We can't actually *test* the Python SDK yet though, because
485 # its own test suite brings up some of those other programs (like
486 # keepproxy).
487 declare -a pythonstuff
488 pythonstuff=(
489     sdk/python
490     services/fuse
491     services/nodemanager
492     )
493 for p in "${pythonstuff[@]}"
494 do
495     do_install "$p" pip
496 done
497
498 install_apiserver() {
499     cd "$WORKSPACE/services/api" \
500         && RAILS_ENV=test bundle_install_trylocal
501
502     rm -f config/environments/test.rb
503     cp config/environments/test.rb.example config/environments/test.rb
504
505     if [ -n "$CONFIGSRC" ]
506     then
507         for f in database.yml application.yml
508         do
509             cp "$CONFIGSRC/$f" config/ || fatal "$f"
510         done
511     fi
512
513     # Fill in a random secret_token and blob_signing_key for testing
514     SECRET_TOKEN=`echo 'puts rand(2**512).to_s(36)' |ruby`
515     BLOB_SIGNING_KEY=`echo 'puts rand(2**512).to_s(36)' |ruby`
516
517     sed -i'' -e "s:SECRET_TOKEN:$SECRET_TOKEN:" config/application.yml
518     sed -i'' -e "s:BLOB_SIGNING_KEY:$BLOB_SIGNING_KEY:" config/application.yml
519
520     # Set up empty git repo (for git tests)
521     GITDIR=$(mktemp -d)
522     sed -i'' -e "s:/var/cache/git:$GITDIR:" config/application.default.yml
523
524     rm -rf $GITDIR
525     mkdir -p $GITDIR/test
526     cd $GITDIR/test \
527         && git init \
528         && git config user.email "jenkins@ci.curoverse.com" \
529         && git config user.name "Jenkins, CI" \
530         && touch tmp \
531         && git add tmp \
532         && git commit -m 'initial commit'
533
534     # Clear out any lingering postgresql connections to arvados_test, so that we can drop it
535     # This assumes the current user is a postgresql superuser
536     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
537
538     cd "$WORKSPACE/services/api" \
539         && RAILS_ENV=test bundle exec rake db:drop \
540         && RAILS_ENV=test bundle exec rake db:setup \
541         && RAILS_ENV=test bundle exec rake db:fixtures:load
542 }
543 do_install services/api apiserver
544
545 declare -a gostuff
546 gostuff=(
547     services/crunchstat
548     services/keepstore
549     services/keepproxy
550     sdk/go/arvadosclient
551     sdk/go/keepclient
552     sdk/go/streamer
553     )
554 for g in "${gostuff[@]}"
555 do
556     do_install "$g" go
557 done
558
559 install_workbench() {
560     cd "$WORKSPACE/apps/workbench" \
561         && mkdir -p tmp/cache \
562         && RAILS_ENV=test bundle_install_trylocal
563 }
564 do_install apps/workbench workbench
565
566 test_doclinkchecker() {
567     (
568         set -e
569         cd "$WORKSPACE/doc"
570         ARVADOS_API_HOST=qr1hi.arvadosapi.com
571         # Make sure python-epydoc is installed or the next line won't
572         # do much good!
573         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
574     )
575 }
576 do_test doc doclinkchecker
577
578 stop_api
579
580 test_apiserver() {
581     cd "$WORKSPACE/services/api" \
582         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[services/api]}
583 }
584 do_test services/api apiserver
585
586 # Shortcut for when we're only running apiserver tests. This saves a bit of time,
587 # because we don't need to start up the api server for subsequent tests.
588 if [ ! -z "$only" ] && [ "$only" == "services/api" ]; then
589   rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
590   exit_cleanly
591 fi
592
593 start_api
594
595 test_ruby_sdk() {
596     cd "$WORKSPACE/sdk/ruby" \
597         && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
598 }
599 do_test sdk/ruby ruby_sdk
600
601 test_cli() {
602     cd "$WORKSPACE/sdk/cli" \
603         && mkdir -p /tmp/keep \
604         && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
605 }
606 do_test sdk/cli cli
607
608 for p in "${pythonstuff[@]}"
609 do
610     do_test "$p" pip
611 done
612
613 for g in "${gostuff[@]}"
614 do
615     do_test "$g" go
616 done
617
618 test_workbench() {
619     cd "$WORKSPACE/apps/workbench" \
620         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[apps/workbench]}
621 }
622 do_test apps/workbench workbench
623
624 test_workbench_benchmark() {
625     cd "$WORKSPACE/apps/workbench" \
626         && RAILS_ENV=test bundle exec rake test:benchmark ${testargs[apps/workbench_benchmark]}
627 }
628 do_test apps/workbench_benchmark workbench_benchmark
629
630 test_workbench_profile() {
631     cd "$WORKSPACE/apps/workbench" \
632         && RAILS_ENV=test bundle exec rake test:profile ${testargs[apps/workbench_profile]}
633 }
634 do_test apps/workbench_profile workbench_profile
635
636 exit_cleanly