Merge branch '5416-arv-git-httpd' refs #5416
[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         --retry)
219             retry=1
220             ;;
221         *_test=*)
222             suite="${arg%%_test=*}"
223             args="${arg#*=}"
224             testargs["$suite"]="$args"
225             ;;
226         *=*)
227             eval export $(echo $arg | cut -d= -f1)=\"$(echo $arg | cut -d= -f2-)\"
228             ;;
229         *)
230             echo >&2 "$0: Unrecognized option: '$arg'. Try: $0 --help"
231             exit 1
232             ;;
233     esac
234 done
235
236 start_api() {
237     echo 'Starting API server...'
238     cd "$WORKSPACE" \
239         && eval $(python sdk/python/tests/run_test_server.py start --auth admin) \
240         && export ARVADOS_TEST_API_HOST="$ARVADOS_API_HOST" \
241         && export ARVADOS_TEST_API_INSTALLED="$$" \
242         && (env | egrep ^ARVADOS)
243 }
244
245 stop_api() {
246     if [[ -n "$ARVADOS_TEST_API_HOST" ]]; then
247         unset ARVADOS_TEST_API_HOST
248         cd "$WORKSPACE" \
249             && python sdk/python/tests/run_test_server.py stop
250     fi
251 }
252
253 interrupt() {
254     failures+=("($(basename $0) interrupted)")
255     exit_cleanly
256 }
257 trap interrupt INT
258
259 sanity_checks
260
261 echo "WORKSPACE=$WORKSPACE"
262
263 if [[ -z "$CONFIGSRC" ]] && [[ -d "$HOME/arvados-api-server" ]]; then
264     # Jenkins expects us to use this by default.
265     CONFIGSRC="$HOME/arvados-api-server"
266 fi
267
268 # Clean up .pyc files that may exist in the workspace
269 cd "$WORKSPACE"
270 find -name '*.pyc' -delete
271
272 # Set up temporary install dirs (unless existing dirs were supplied)
273 for tmpdir in VENVDIR GOPATH GEMHOME
274 do
275     if [[ -n "${!tmpdir}" ]]; then
276         leave_temp[$tmpdir]=1
277     else
278         eval $tmpdir=$(mktemp -d)
279     fi
280 done
281
282 setup_ruby_environment() {
283     if [[ -s "$HOME/.rvm/scripts/rvm" ]] ; then
284       source "$HOME/.rvm/scripts/rvm"
285       using_rvm=true
286     elif [[ -s "/usr/local/rvm/scripts/rvm" ]] ; then
287       source "/usr/local/rvm/scripts/rvm"
288       using_rvm=true
289     else
290       using_rvm=false
291     fi
292
293     if [[ "$using_rvm" == true ]]; then
294         # If rvm is in use, we can't just put separate "dependencies"
295         # and "gems-under-test" paths to GEM_PATH: passenger resets
296         # the environment to the "current gemset", which would lose
297         # our GEM_PATH and prevent our test suites from running ruby
298         # programs (for example, the Workbench test suite could not
299         # boot an API server or run arv). Instead, we have to make an
300         # rvm gemset and use it for everything.
301
302         [[ `type rvm | head -n1` == "rvm is a function" ]] \
303             || fatal 'rvm check'
304
305         # Put rvm's favorite path back in first place (overriding
306         # virtualenv, which just put itself there). Ignore rvm's
307         # complaint about not being in first place already.
308         rvm use @default 2>/dev/null
309
310         # Create (if needed) and switch to an @arvados-tests
311         # gemset. (Leave the choice of ruby to the caller.)
312         rvm use @arvados-tests --create \
313             || fatal 'rvm gemset setup'
314
315         rvm env
316     else
317         # When our "bundle install"s need to install new gems to
318         # satisfy dependencies, we want them to go where "gem install
319         # --user-install" would put them. (However, if the caller has
320         # already set GEM_HOME, we assume that's where dependencies
321         # should be installed, and we should leave it alone.)
322
323         if [ -z "$GEM_HOME" ]; then
324             user_gempath="$(gem env gempath)"
325             export GEM_HOME="${user_gempath%%:*}"
326         fi
327         PATH="$(gem env gemdir)/bin:$PATH"
328
329         # When we build and install our own gems, we install them in our
330         # $GEMHOME tmpdir, and we want them to be at the front of GEM_PATH and
331         # PATH so integration tests prefer them over other versions that
332         # happen to be installed in $user_gempath, system dirs, etc.
333
334         tmpdir_gem_home="$(env - PATH="$PATH" HOME="$GEMHOME" gem env gempath | cut -f1 -d:)"
335         PATH="$tmpdir_gem_home/bin:$PATH"
336         export GEM_PATH="$tmpdir_gem_home:$(gem env gempath)"
337
338         echo "Will install dependencies to $(gem env gemdir)"
339         echo "Will install arvados gems to $tmpdir_gem_home"
340         echo "Gem search path is GEM_PATH=$GEM_PATH"
341     fi
342 }
343
344 with_test_gemset() {
345     if [[ "$using_rvm" == true ]]; then
346         "$@"
347     else
348         GEM_HOME="$tmpdir_gem_home" "$@"
349     fi
350 }
351
352 export GOPATH
353 mkdir -p "$GOPATH/src/git.curoverse.com"
354 ln -sfn "$WORKSPACE" "$GOPATH/src/git.curoverse.com/arvados.git" \
355     || fatal "symlink failed"
356
357 virtualenv --setuptools "$VENVDIR" || fatal "virtualenv $VENVDIR failed"
358 . "$VENVDIR/bin/activate"
359
360 # Note: this must be the last time we change PATH, otherwise rvm will
361 # whine a lot.
362 setup_ruby_environment
363
364 echo "PATH is $PATH"
365
366 if ! which bundler >/dev/null
367 then
368     gem install --user-install bundler || fatal 'Could not install bundler'
369 fi
370
371 # Needed for run_test_server.py which is used by certain (non-Python) tests.
372 echo "pip install -q PyYAML"
373 pip install -q PyYAML || fatal "pip install PyYAML failed"
374
375 checkexit() {
376     if [[ "$1" != "0" ]]; then
377         title "!!!!!! $2 FAILED !!!!!!"
378         failures+=("$2 (`timer`)")
379     else
380         successes+=("$2 (`timer`)")
381     fi
382 }
383
384 timer_reset() {
385     t0=$SECONDS
386 }
387
388 timer() {
389     echo -n "$(($SECONDS - $t0))s"
390 }
391
392 do_test() {
393     while ! do_test_once ${@} && [[ "$retry" == 1 ]]
394     do
395         read -p 'Try again? [Y/n] ' x
396         if [[ "$x" != "y" ]] && [[ "$x" != "" ]]
397         then
398             break
399         fi
400     done
401 }
402
403 do_test_once() {
404     if [[ -z "${skip[$1]}" ]] && ( [[ -z "$only" ]] || [[ "$only" == "$1" ]] )
405     then
406         title "Running $1 tests"
407         timer_reset
408         if [[ "$2" == "go" ]]
409         then
410             go test ${testargs[$1]} "git.curoverse.com/arvados.git/$1"
411         elif [[ "$2" == "pip" ]]
412         then
413            cd "$WORKSPACE/$1" \
414                 && python setup.py test ${testargs[$1]}
415         elif [[ "$2" != "" ]]
416         then
417             "test_$2"
418         else
419             "test_$1"
420         fi
421         result="$?"
422         checkexit $result "$1 tests"
423         title "End of $1 tests (`timer`)"
424         return $result
425     else
426         title "Skipping $1 tests"
427     fi
428 }
429
430 do_install() {
431     if [[ -z "$skip_install" ]]
432     then
433         title "Running $1 install"
434         timer_reset
435         if [[ "$2" == "go" ]]
436         then
437             go get -t "git.curoverse.com/arvados.git/$1"
438         elif [[ "$2" == "pip" ]]
439         then
440             cd "$WORKSPACE/$1" \
441                 && python setup.py sdist rotate --keep=1 --match .tar.gz \
442                 && pip install -q --upgrade dist/*.tar.gz
443         elif [[ "$2" != "" ]]
444         then
445             "install_$2"
446         else
447             "install_$1"
448         fi
449         checkexit $? "$1 install"
450         title "End of $1 install (`timer`)"
451     else
452         title "Skipping $1 install"
453     fi
454 }
455
456 title () {
457     txt="********** $1 **********"
458     printf "\n%*s%s\n\n" $((($COLUMNS-${#txt})/2)) "" "$txt"
459 }
460
461 bundle_install_trylocal() {
462     (
463         set -e
464         echo "(Running bundle install --local. 'could not find package' messages are OK.)"
465         if ! bundle install --local --no-deployment; then
466             echo "(Running bundle install again, without --local.)"
467             bundle install --no-deployment
468         fi
469         bundle package --all
470     )
471 }
472
473 install_doc() {
474     cd "$WORKSPACE/doc" \
475         && bundle_install_trylocal \
476         && rm -rf .site
477 }
478 do_install doc
479
480 install_ruby_sdk() {
481     with_test_gemset gem uninstall --force --all --executables arvados \
482         && cd "$WORKSPACE/sdk/ruby" \
483         && bundle_install_trylocal \
484         && gem build arvados.gemspec \
485         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-*.gem|head -n1`
486 }
487 do_install sdk/ruby ruby_sdk
488
489 install_cli() {
490     with_test_gemset gem uninstall --force --all --executables arvados-cli \
491         && cd "$WORKSPACE/sdk/cli" \
492         && bundle_install_trylocal \
493         && gem build arvados-cli.gemspec \
494         && with_test_gemset gem install --no-ri --no-rdoc `ls -t arvados-cli-*.gem|head -n1`
495 }
496 do_install sdk/cli cli
497
498 # Install the Python SDK early. Various other test suites (like
499 # keepproxy) bring up run_test_server.py, which imports the arvados
500 # module. We can't actually *test* the Python SDK yet though, because
501 # its own test suite brings up some of those other programs (like
502 # keepproxy).
503 declare -a pythonstuff
504 pythonstuff=(
505     sdk/python
506     services/fuse
507     services/nodemanager
508     )
509 for p in "${pythonstuff[@]}"
510 do
511     do_install "$p" pip
512 done
513
514 install_apiserver() {
515     cd "$WORKSPACE/services/api" \
516         && RAILS_ENV=test bundle_install_trylocal
517
518     rm -f config/environments/test.rb
519     cp config/environments/test.rb.example config/environments/test.rb
520
521     if [ -n "$CONFIGSRC" ]
522     then
523         for f in database.yml application.yml
524         do
525             cp "$CONFIGSRC/$f" config/ || fatal "$f"
526         done
527     fi
528
529     # Fill in a random secret_token and blob_signing_key for testing
530     SECRET_TOKEN=`echo 'puts rand(2**512).to_s(36)' |ruby`
531     BLOB_SIGNING_KEY=`echo 'puts rand(2**512).to_s(36)' |ruby`
532
533     sed -i'' -e "s:SECRET_TOKEN:$SECRET_TOKEN:" config/application.yml
534     sed -i'' -e "s:BLOB_SIGNING_KEY:$BLOB_SIGNING_KEY:" config/application.yml
535
536     # Set up empty git repo (for git tests)
537     GITDIR=$(mktemp -d)
538     sed -i'' -e "s:/var/cache/git:$GITDIR:" config/application.default.yml
539
540     rm -rf $GITDIR
541     mkdir -p $GITDIR/test
542     cd $GITDIR/test \
543         && git init \
544         && git config user.email "jenkins@ci.curoverse.com" \
545         && git config user.name "Jenkins, CI" \
546         && touch tmp \
547         && git add tmp \
548         && git commit -m 'initial commit'
549
550     # Clear out any lingering postgresql connections to arvados_test, so that we can drop it
551     # This assumes the current user is a postgresql superuser
552     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
553
554     cd "$WORKSPACE/services/api" \
555         && RAILS_ENV=test bundle exec rake db:drop \
556         && RAILS_ENV=test bundle exec rake db:setup \
557         && RAILS_ENV=test bundle exec rake db:fixtures:load
558 }
559 do_install services/api apiserver
560
561 declare -a gostuff
562 gostuff=(
563     services/arv-git-httpd
564     services/crunchstat
565     services/keepstore
566     services/keepproxy
567     sdk/go/arvadosclient
568     sdk/go/keepclient
569     sdk/go/streamer
570     )
571 for g in "${gostuff[@]}"
572 do
573     do_install "$g" go
574 done
575
576 install_workbench() {
577     cd "$WORKSPACE/apps/workbench" \
578         && mkdir -p tmp/cache \
579         && RAILS_ENV=test bundle_install_trylocal
580 }
581 do_install apps/workbench workbench
582
583 test_doclinkchecker() {
584     (
585         set -e
586         cd "$WORKSPACE/doc"
587         ARVADOS_API_HOST=qr1hi.arvadosapi.com
588         # Make sure python-epydoc is installed or the next line won't
589         # do much good!
590         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
591     )
592 }
593 do_test doc doclinkchecker
594
595 stop_api
596
597 test_apiserver() {
598     cd "$WORKSPACE/services/api" \
599         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[services/api]}
600 }
601 do_test services/api apiserver
602
603 # Shortcut for when we're only running apiserver tests. This saves a bit of time,
604 # because we don't need to start up the api server for subsequent tests.
605 if [ ! -z "$only" ] && [ "$only" == "services/api" ]; then
606   rotate_logfile "$WORKSPACE/services/api/log/" "test.log"
607   exit_cleanly
608 fi
609
610 start_api
611
612 test_ruby_sdk() {
613     cd "$WORKSPACE/sdk/ruby" \
614         && bundle exec rake test TESTOPTS=-v ${testargs[sdk/ruby]}
615 }
616 do_test sdk/ruby ruby_sdk
617
618 test_cli() {
619     cd "$WORKSPACE/sdk/cli" \
620         && mkdir -p /tmp/keep \
621         && KEEP_LOCAL_STORE=/tmp/keep bundle exec rake test TESTOPTS=-v ${testargs[sdk/cli]}
622 }
623 do_test sdk/cli cli
624
625 for p in "${pythonstuff[@]}"
626 do
627     do_test "$p" pip
628 done
629
630 for g in "${gostuff[@]}"
631 do
632     do_test "$g" go
633 done
634
635 test_workbench() {
636     cd "$WORKSPACE/apps/workbench" \
637         && RAILS_ENV=test bundle exec rake test TESTOPTS=-v ${testargs[apps/workbench]}
638 }
639 do_test apps/workbench workbench
640
641 test_workbench_benchmark() {
642     cd "$WORKSPACE/apps/workbench" \
643         && RAILS_ENV=test bundle exec rake test:benchmark ${testargs[apps/workbench_benchmark]}
644 }
645 do_test apps/workbench_benchmark workbench_benchmark
646
647 test_workbench_profile() {
648     cd "$WORKSPACE/apps/workbench" \
649         && RAILS_ENV=test bundle exec rake test:profile ${testargs[apps/workbench_profile]}
650 }
651 do_test apps/workbench_profile workbench_profile
652
653 exit_cleanly