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