Bug 1345859 - Run ssl_gtests in parallel when multiple cores are available r=franziskus
authorTim Taubert <ttaubert@mozilla.com>
Wed, 15 Mar 2017 15:32:58 +0100
changeset 13210 dc5174017668b85009fd92a40d2654aee5040265
parent 13209 48e2bf231184503f7c03d703f3bcd5e057d48ca3
child 13211 cf81ccc154dd5fe5523444e425467af9b97d1dfa
push id2081
push userttaubert@mozilla.com
push dateWed, 15 Mar 2017 14:37:32 +0000
reviewersfranziskus
bugs1345859
Bug 1345859 - Run ssl_gtests in parallel when multiple cores are available r=franziskus Differential Revision: https://nss-review.dev.mozaws.net/D244
automation/taskcluster/docker-aarch64/setup.sh
automation/taskcluster/docker-fuzz/setup.sh
automation/taskcluster/docker/setup.sh
tests/common/init.sh
tests/common/parsegtestreport.sed
tests/ssl_gtests/ssl_gtests.sh
--- a/automation/taskcluster/docker-aarch64/setup.sh
+++ b/automation/taskcluster/docker-aarch64/setup.sh
@@ -16,16 +16,17 @@ add-apt-repository "deb http://ports.ubu
 # Update.
 apt-get -y update
 apt-get -y dist-upgrade
 
 apt_packages=()
 apt_packages+=('build-essential')
 apt_packages+=('ca-certificates')
 apt_packages+=('curl')
+apt_packages+=('libxml2-utils')
 apt_packages+=('zlib1g-dev')
 apt_packages+=('ninja-build')
 apt_packages+=('gyp')
 apt_packages+=('mercurial')
 
 # Install packages.
 apt-get install -y --no-install-recommends ${apt_packages[@]}
 
--- a/automation/taskcluster/docker-fuzz/setup.sh
+++ b/automation/taskcluster/docker-fuzz/setup.sh
@@ -11,16 +11,17 @@ apt-get install -y --no-install-recommen
 
 apt_packages=()
 apt_packages+=('build-essential')
 apt_packages+=('ca-certificates')
 apt_packages+=('curl')
 apt_packages+=('git')
 apt_packages+=('gyp')
 apt_packages+=('libssl-dev')
+apt_packages+=('libxml2-utils')
 apt_packages+=('ninja-build')
 apt_packages+=('pkg-config')
 apt_packages+=('zlib1g-dev')
 
 # Latest Mercurial.
 apt_packages+=('mercurial')
 apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 41BD8711B1F0EC2B0D85B91CF59CE3A8323293EE
 echo "deb http://ppa.launchpad.net/mercurial-ppa/releases/ubuntu xenial main" > /etc/apt/sources.list.d/mercurial.list
--- a/automation/taskcluster/docker/setup.sh
+++ b/automation/taskcluster/docker/setup.sh
@@ -11,16 +11,17 @@ apt-get install -y --no-install-recommen
 
 apt_packages=()
 apt_packages+=('build-essential')
 apt_packages+=('ca-certificates')
 apt_packages+=('curl')
 apt_packages+=('npm')
 apt_packages+=('git')
 apt_packages+=('golang-1.6')
+apt_packages+=('libxml2-utils')
 apt_packages+=('ninja-build')
 apt_packages+=('pkg-config')
 apt_packages+=('zlib1g-dev')
 
 # 32-bit builds
 apt_packages+=('lib32z1-dev')
 apt_packages+=('gcc-multilib')
 apt_packages+=('g++-multilib')
--- a/tests/common/init.sh
+++ b/tests/common/init.sh
@@ -175,19 +175,17 @@ if [ -z "${INIT_SOURCED}" -o "${INIT_SOU
 
 #html functions to give the resultfiles a consistant look
     html() #########################    write the results.html file
     {      # 3 functions so we can put targets in the output.log easier
         echo $* >>${RESULTS}
     }
     increase_msg_id()
     {
-        MSG_ID=`cat ${MSG_ID_FILE}`
-        MSG_ID=`expr ${MSG_ID} + 1`
-        echo ${MSG_ID} > ${MSG_ID_FILE}
+        MSG_ID=$(( ${MSG_ID} + 1 ))
     }
     html_passed_ignore_core()
     {
         increase_msg_id
         html "<TR><TD>#${MSG_ID}: $1 ${HTML_PASSED}"
         echo "${SCRIPTNAME}: #${MSG_ID}: $* - PASSED"
     }
     html_passed()
@@ -640,19 +638,17 @@ if [ -z "${INIT_SOURCED}" -o "${INIT_SOU
 
     TOTAL_GRP_NUM=3
 
     RELOAD_CRL=1
 
     NSS_DEFAULT_DB_TYPE="dbm"
     export NSS_DEFAULT_DB_TYPE
 
-    MSG_ID_FILE="${HOSTDIR}/id"
     MSG_ID=0
-    echo ${MSG_ID} > ${MSG_ID_FILE}
 
     #################################################
     # Interoperability testing constatnts
     #
     # if suite is setup for testing, IOPR_HOSTADDR_LIST should have
     # at least one host name(FQDN)
     # Example   IOPR_HOSTADDR_LIST="goa1.SFBay.Sun.COM"
 
--- a/tests/common/parsegtestreport.sed
+++ b/tests/common/parsegtestreport.sed
@@ -1,8 +1,9 @@
 /\<testcase/{
   s/^.* name="\([^"]*\)" value_param="\([^"]*\)" status="\([^"]*\)" time="[^"]*" classname="\([^"]*\)".*$/\3 '\4: \1 \2'/
   t end
   s/^.* name="\([^"]*\)" status="\([^"]*\)" time="[^"]*" classname="\([^"]*\)".*$/\2 '\3: \1'/
   t end
 }
 d
 : end
+s/&quot;/"/g
--- a/tests/ssl_gtests/ssl_gtests.sh
+++ b/tests/ssl_gtests/ssl_gtests.sh
@@ -122,33 +122,108 @@ ssl_gtest_init()
 ssl_gtest_start()
 {
   if [ ! -f ${BINDIR}/ssl_gtest ]; then
     html_unknown "Skipping ssl_gtest (not built)"
     return
   fi
 
   SSLGTESTREPORT="${SSLGTESTDIR}/report.xml"
-  PARSED_REPORT="${SSLGTESTDIR}/report.parsed"
-  echo "executing ssl_gtest"
-  ${BINDIR}/ssl_gtest -d "${SSLGTESTDIR}" --gtest_output=xml:"${SSLGTESTREPORT}" \
-                                          --gtest_filter="${GTESTFILTER-*}"
-  html_msg $? 0 "ssl_gtest run successfully"
-  echo "executing sed to parse the xml report"
-  sed -f ${COMMON}/parsegtestreport.sed "${SSLGTESTREPORT}" > "${PARSED_REPORT}"
-  echo "processing the parsed report"
-  cat "${PARSED_REPORT}" | while read result name; do
-    if [ "$result" = "notrun" ]; then
-      echo "$name" SKIPPED
-    elif [ "$result" = "run" ]; then
+
+  local nshards=1
+  local prefix=""
+  local postfix=""
+
+  export -f parallel_fallback
+
+  # Determine the number of chunks.
+  if [ -n "$GTESTFILTER" ]; then
+    echo "DEBUG: Not parallelizing ssl_gtests because \$GTESTFILTER is set"
+  elif type parallel 2>/dev/null; then
+    nshards=$(parallel --number-of-cores || 1)
+  fi
+
+  if [ "$nshards" != 1 ]; then
+    local indices=$(for ((i=0; i<$nshards; i++)); do echo $i; done)
+    prefix="parallel -j$nshards --line-buffer --halt soon,fail=1"
+    postfix="\&\& exit 0 \|\| exit 1 ::: $indices"
+  fi
+
+  echo "DEBUG: ssl_gtests will be divided into $nshards chunk(s)"
+
+  # Run tests.
+  ${prefix:-parallel_fallback} \
+    GTEST_SHARD_INDEX={} \
+      GTEST_TOTAL_SHARDS=$nshards \
+        DYLD_LIBRARY_PATH="${DIST}/${OBJDIR}/lib" \
+          ${BINDIR}/ssl_gtest -d "${SSLGTESTDIR}" \
+            --gtest_output=xml:"${SSLGTESTREPORT}.{}" \
+            --gtest_filter="${GTESTFILTER-*}" \
+            $postfix
+
+  html_msg $? 0 "ssl_gtests ran successfully"
+
+  # Parse XML report(s).
+  if type xmllint &>/dev/null; then
+    echo "DEBUG: Using xmllint to parse GTest XML report(s)"
+    parse_report
+  else
+    echo "DEBUG: Falling back to legacy XML report parsing using only sed"
+    parse_report_legacy
+  fi
+}
+
+# Helper function used when 'parallel' isn't available.
+parallel_fallback()
+{
+  eval ${*//\{\}/0}
+}
+
+parse_report()
+{
+  # Check XML reports for normal test runs and failures.
+  local successes=$(parse_report_xpath "//testcase[@status='run'][count(*)=0]")
+  local failures=$(parse_report_xpath "//failure/..")
+
+  # Print all tests that succeeded.
+  while read result name; do
+    html_passed_ignore_core "$name"
+  done <<< "$successes"
+
+  # Print failing tests.
+  if [ -n "$failures" ]; then
+    printf "\nFAILURES:\n=========\n"
+
+    while read result name; do
+      html_failed_ignore_core "$name"
+    done <<< "$failures"
+
+    printf "\n"
+  fi
+}
+
+parse_report_xpath()
+{
+  # Query the XML report with the given XPath pattern.
+  xmllint --xpath "$1" "${SSLGTESTREPORT}".* 2>/dev/null | \
+    # Insert newlines to help sed.
+    sed $'s/<testcase/\\\n<testcase/g' | \
+    # Use sed to parse the report.
+    sed -f "${COMMON}/parsegtestreport.sed"
+}
+
+# This legacy report parser can't actually detect failures. It always relied
+# on the binary's exit code. Print the tests we ran to keep the old behavior.
+parse_report_legacy()
+{
+  while read result name && [ -n "$name" ]; do
+    if [ "$result" = "run" ]; then
       html_passed_ignore_core "$name"
-    else
-      html_failed_ignore_core "$name"
     fi
-  done
+  done <<< "$(sed -f "${COMMON}/parsegtestreport.sed" "${SSLGTESTREPORT}".*)"
 }
 
 ssl_gtest_cleanup()
 {
   cd ${QADIR}
   . common/cleanup.sh
 }