Browse Source

Introduce test matrix (#1821)

* Introduce test matrix and proper WildFly test

* Restore test to old version until matrix is merged

* Add workflow to publish test matrix

* Satisfy checkstyle

* Polish

* Use docker build args instead of replacing tokens
Nikita Salnikov-Tarnovski 4 years ago
parent
commit
71f1a79100

+ 35 - 0
.github/workflows/build-test-matrix.yaml

@@ -0,0 +1,35 @@
+name: Build test matrix images
+
+on:
+  push:
+    paths:
+      - 'smoke-tests/matrix/**'
+      - '.github/workflows/build-test-matrix.yaml'
+    branches: 'master'
+  workflow_dispatch:
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up JDK 11 for running Gradle
+        uses: actions/setup-java@v1
+        with:
+          java-version: 11
+
+      - name: Cache gradle dependencies
+        uses: burrunan/gradle-cache-action@v1.5
+        with:
+          job-id: matrix-smoke
+
+      - name: Login to GitHub Package Registry
+        uses: docker/login-action@v1
+        with:
+          registry: ghcr.io
+          username: ${{ github.repository_owner }}
+          password: ${{ secrets.GHCR_TOKEN }}
+
+      - name: Build Docker Image
+        run: ./gradlew buildMatrix pushMatrix
+        working-directory: smoke-tests/matrix

+ 2 - 5
smoke-tests/README.md

@@ -1,8 +1,5 @@
 # Smoke Tests
 # Smoke Tests
 Assert that various applications will start up with the JavaAgent without any obvious ill effects.
 Assert that various applications will start up with the JavaAgent without any obvious ill effects.
 
 
-Each subproject underneath `smoke-tests` is a single smoke test. Each test does the following
-* Launch the application with stdout and stderr logged to `$buildDir/reports/server.log`
-* For web servers, run a spock test which does 200 requests to an endpoint on the server and asserts on an expected response.
-
-Note that there is nothing special about doing 200 requests. 200 is simply an arbitrarily large number to exercise the server.
+Each subproject underneath `smoke-tests` produces one or more docker images containing some application
+under the test. Various tests in the main module then use them to run the appropriate tests.

+ 6 - 0
smoke-tests/matrix/README.md

@@ -0,0 +1,6 @@
+# Smoke Test Environment Matrix
+This project builds docker images containing a simple test web application deployed to various
+application servers or servlet containers. For each server several relevant versions are chosen.
+In addition we build separate images for several support major java versions.
+This way we can test our agent with many different combinations of runtime environment,
+its version and running on different JVM versions from different vendors.

+ 93 - 0
smoke-tests/matrix/build.gradle

@@ -0,0 +1,93 @@
+import com.bmuschko.gradle.docker.tasks.image.DockerBuildImage
+import com.bmuschko.gradle.docker.tasks.image.DockerPushImage
+
+plugins {
+  id "war"
+  id "com.bmuschko.docker-remote-api" version "6.6.1"
+}
+
+compileJava {
+  options.release.set(8)
+}
+
+repositories {
+  jcenter()
+}
+dependencies {
+  implementation("javax.servlet:javax.servlet-api:3.0.1")
+}
+
+def buildMatrixTask = tasks.create("buildMatrix") {
+  group = "build"
+  description = "Builds all Docker images for the test matrix"
+}
+
+Set<String> matrix = []
+tasks.create("pushMatrix", DockerPushImage) {
+  group = "publishing"
+  description = "Push all Docker images for the test matrix"
+  dependsOn(buildMatrixTask)
+  images.set(matrix)
+}
+
+def targets = [
+  "jetty"  : [
+    "9.4.35"      : ["8", "11", "15"],
+    "10.0.0.beta3": ["11", "15"],
+  ],
+  "tomcat" : [
+    "7.0.107"   : ["8"],
+    "8.5.60"    : ["8", "11"],
+    "9.0.40"    : ["8", "11"],
+    "10.0.0-M10": ["8", "11"]
+  ],
+  "payara" : [
+    "5.2020.6"      : ["8"],
+    "5.2020.6-jdk11": ["11"]
+  ],
+  "wildfly": [
+    "13.0.0.Final": ["8", "11", "15"],
+    "17.0.1.Final": ["8", "11", "15"],
+    "21.0.0.Final": ["8", "11", "15"]
+  ],
+  "liberty": [
+    "20.0.0.12": ["8", "11", "15", "8-jdk-openj9", "11-jdk-openj9", "15-jdk-openj9"]
+  ]
+]
+
+def dockerWorkingDir = new File(project.buildDir, "docker")
+
+targets.each { server, data ->
+  data.forEach { version, jdks ->
+    jdks.forEach { jdk ->
+      def dockerfile = "${server}.dockerfile"
+
+      def prepareTask = tasks.register("${server}ImagePrepare-$version-jdk$jdk", Copy) {
+        def warTask = project.tasks.war
+        it.dependsOn(warTask)
+        it.into(dockerWorkingDir)
+        it.from("src")
+        it.from(warTask.archiveFile) {
+          rename { _ -> "app.war" }
+        }
+      }
+
+      def image = "ghcr.io/open-telemetry/java-test-containers:$server-$version-jdk$jdk"
+      matrix.add(image)
+      def buildTask = tasks.register("${server}Image-$version-jdk$jdk", DockerBuildImage) {
+        it.dependsOn(prepareTask)
+        group = "build"
+        description = "Builds Docker image with $server $version on JDK $jdk"
+
+        it.inputDir.set(dockerWorkingDir)
+        it.images.add(image)
+        it.dockerFile.set(new File(dockerWorkingDir, dockerfile))
+        it.buildArgs.set(["version": version, "jdk": jdk])
+      }
+
+      buildMatrixTask.dependsOn(buildTask)
+    }
+  }
+}
+
+assemble.dependsOn(buildMatrixTask)

BIN
smoke-tests/matrix/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
smoke-tests/matrix/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionSha256Sum=3239b5ed86c3838a37d983ac100573f64c1f3fd8e1eb6c89fa5f9529b5ec091d

+ 185 - 0
smoke-tests/matrix/gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 104 - 0
smoke-tests/matrix/gradlew.bat

@@ -0,0 +1,104 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
smoke-tests/matrix/settings.gradle

@@ -0,0 +1 @@
+rootProject.name = 'matrix'

+ 22 - 0
smoke-tests/matrix/src/jetty.dockerfile

@@ -0,0 +1,22 @@
+ARG version
+ARG jdk
+FROM jetty:${version}-jre11-slim as jetty
+
+FROM adoptopenjdk:${jdk}
+ENV JETTY_HOME /usr/local/jetty
+ENV JETTY_BASE /var/lib/jetty
+ENV TMPDIR /tmp/jetty
+ENV PATH $JETTY_HOME/bin:$PATH
+
+COPY --from=jetty $JETTY_HOME $JETTY_HOME
+COPY --from=jetty $JETTY_BASE $JETTY_BASE
+COPY --from=jetty $TMPDIR $TMPDIR
+
+WORKDIR $JETTY_BASE
+COPY --from=jetty docker-entrypoint.sh generate-jetty-start.sh /
+
+COPY app.war $JETTY_BASE/webapps/ROOT.war
+
+EXPOSE 8080
+ENTRYPOINT ["/docker-entrypoint.sh"]
+CMD ["java","-jar","/usr/local/jetty/start.jar"]

+ 23 - 0
smoke-tests/matrix/src/liberty.dockerfile

@@ -0,0 +1,23 @@
+ARG version
+ARG jdk
+FROM open-liberty:${version}-full-java11-openj9 as liberty
+
+FROM adoptopenjdk:${jdk}
+ENV CONFIG /config
+ENV LIBERTY /opt/ol
+ENV PATH=/opt/ol/wlp/bin:/opt/ol/docker/:/opt/ol/helpers/build:$PATH \
+    LOG_DIR=/logs \
+    WLP_OUTPUT_DIR=/opt/ol/wlp/output \
+    WLP_SKIP_MAXPERMSIZE=true
+
+COPY --from=liberty $CONFIG $CONFIG
+COPY --from=liberty $LIBERTY $LIBERTY
+
+COPY --chown=1001:0 liberty.xml /config/server.xml
+COPY --chown=1001:0 app.war /config/apps/
+RUN configure.sh
+
+EXPOSE 8080
+
+ENTRYPOINT ["/opt/ol/helpers/runtime/docker-server.sh"]
+CMD ["/opt/ol/wlp/bin/server", "run", "defaultServer"]

+ 8 - 0
smoke-tests/matrix/src/liberty.xml

@@ -0,0 +1,8 @@
+<server description="Sample Liberty server">
+  <variable name="default.http.port" defaultValue="8080"/>
+
+  <webApplication location="app.war" contextRoot="/" />
+  <mpMetrics authentication="false"/>
+
+  <httpEndpoint host="*" httpPort="${default.http.port}" id="defaultHttpEndpoint"/>
+</server>

+ 54 - 0
smoke-tests/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/GreetingServlet.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.splunk.opentelemetry.appservers.javaee;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Objects;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class GreetingServlet extends HttpServlet {
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    String path = (req.getContextPath() + "/headers").replace("//", "/");
+    URL url = new URL("http", "localhost", req.getLocalPort(), path);
+    URLConnection urlConnection = url.openConnection();
+    ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+    try (InputStream remoteInputStream = urlConnection.getInputStream()) {
+      long bytesRead = transfer(remoteInputStream, buffer);
+      String responseBody = buffer.toString("UTF-8");
+      ServletOutputStream outputStream = resp.getOutputStream();
+      outputStream.print(
+          bytesRead
+              + " bytes read by "
+              + urlConnection.getClass().getName()
+              + "\n"
+              + responseBody);
+      outputStream.flush();
+    }
+  }
+
+  // We have to run on Java 8, so no Java 9 stream transfer goodies for us.
+  private long transfer(InputStream from, OutputStream to) throws IOException {
+    Objects.requireNonNull(to, "out");
+    long transferred = 0;
+    byte[] buffer = new byte[65535];
+    int read;
+    while ((read = from.read(buffer, 0, buffer.length)) >= 0) {
+      to.write(buffer, 0, read);
+      transferred += read;
+    }
+    return transferred;
+  }
+}

+ 42 - 0
smoke-tests/matrix/src/main/java/com/splunk/opentelemetry/appservers/javaee/HeaderDumpingServlet.java

@@ -0,0 +1,42 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.splunk.opentelemetry.appservers.javaee;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class HeaderDumpingServlet extends HttpServlet {
+
+  @Override
+  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+    PrintWriter response = resp.getWriter();
+    Enumeration<String> headerNames = req.getHeaderNames();
+    while (headerNames.hasMoreElements()) {
+      String headerName = headerNames.nextElement();
+      response.write(headerName + ": ");
+
+      List<String> headers = Collections.list(req.getHeaders(headerName));
+      if (headers.size() == 1) {
+        response.write(headers.get(0));
+      } else {
+        response.write("[");
+        for (String header : headers) {
+          response.write("  " + header + ",\n");
+        }
+        response.write("]");
+      }
+      response.write("\n");
+    }
+
+    response.flush();
+  }
+}

+ 3 - 0
smoke-tests/matrix/src/main/webapp/WEB-INF/glassfish-web.xml

@@ -0,0 +1,3 @@
+<glassfish-web-app>
+  <context-root>/</context-root>
+</glassfish-web-app>

+ 22 - 0
smoke-tests/matrix/src/main/webapp/WEB-INF/web.xml

@@ -0,0 +1,22 @@
+<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
+         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
+         version="3.1">
+    <servlet>
+        <servlet-name>Headers</servlet-name>
+        <servlet-class>com.splunk.opentelemetry.appservers.javaee.HeaderDumpingServlet</servlet-class>
+    </servlet>
+    <servlet>
+        <servlet-name>Greeting</servlet-name>
+        <servlet-class>com.splunk.opentelemetry.appservers.javaee.GreetingServlet</servlet-class>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>Headers</servlet-name>
+        <url-pattern>/headers</url-pattern>
+    </servlet-mapping>
+    <servlet-mapping>
+        <servlet-name>Greeting</servlet-name>
+        <url-pattern>/greeting</url-pattern>
+    </servlet-mapping>
+</web-app>

+ 8 - 0
smoke-tests/matrix/src/payara.dockerfile

@@ -0,0 +1,8 @@
+ARG version
+ARG jdk
+
+FROM payara/server-full:${version}
+
+RUN rm ${PAYARA_DIR}/glassfish/modules/phonehome-bootstrap.jar
+
+COPY app.war $DEPLOY_DIR

+ 6 - 0
smoke-tests/matrix/src/tomcat.dockerfile

@@ -0,0 +1,6 @@
+ARG version
+ARG jdk
+
+FROM tomcat:${version}-jdk${jdk}-adoptopenjdk-hotspot
+
+COPY app.war /usr/local/tomcat/webapps/ROOT.war

+ 46 - 0
smoke-tests/matrix/src/wildfly.dockerfile

@@ -0,0 +1,46 @@
+ARG jdk
+FROM adoptopenjdk:${jdk}
+
+# Create a user and group used to launch processes
+# The user ID 1000 is the default for the first "regular" user on Fedora/RHEL,
+# so there is a high chance that this ID will be equal to the current user
+# making it easier to use volumes (no permission issues)
+RUN groupadd -r jboss -g 1000 && useradd -u 1000 -r -g jboss -m -d /opt/jboss -s /sbin/nologin -c "JBoss user" jboss && \
+    chmod 755 /opt/jboss
+
+# Set the working directory to jboss' user home directory
+WORKDIR /opt/jboss
+
+# Specify the user which should be used to execute all commands below
+USER jboss
+
+# Set the WILDFLY_VERSION env variable
+ARG version
+ENV WILDFLY_VERSION=${version}
+ENV JBOSS_HOME /opt/jboss/wildfly
+
+USER root
+RUN echo curl -O https://download.jboss.org/wildfly/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz
+# Add the WildFly distribution to /opt, and make wildfly the owner of the extracted tar content
+# Make sure the distribution is available from a well-known place
+RUN cd $HOME \
+    && curl -O https://download.jboss.org/wildfly/$WILDFLY_VERSION/wildfly-$WILDFLY_VERSION.tar.gz \
+    && tar xf wildfly-$WILDFLY_VERSION.tar.gz \
+    && mv $HOME/wildfly-$WILDFLY_VERSION $JBOSS_HOME \
+    && rm wildfly-$WILDFLY_VERSION.tar.gz \
+    && chown -R jboss:0 ${JBOSS_HOME} \
+    && chmod -R g+rw ${JBOSS_HOME}
+
+# Ensure signals are forwarded to the JVM process correctly for graceful shutdown
+ENV LAUNCH_JBOSS_IN_BACKGROUND true
+
+USER jboss
+
+# Expose the ports we're interested in
+EXPOSE 8080
+
+# Set the default command to run on boot
+# This will boot WildFly in the standalone mode and bind to all interface
+CMD ["/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0"]
+
+COPY app.war /opt/jboss/wildfly/standalone/deployments/ROOT.war

+ 1 - 1
smoke-tests/springboot/build.gradle

@@ -27,7 +27,7 @@ dependencies {
 }
 }
 
 
 compileJava {
 compileJava {
-  options.release = 8
+  options.release.set(8)
 }
 }
 
 
 def targetJDK = project.hasProperty("targetJDK") ? project.targetJDK : 11
 def targetJDK = project.hasProperty("targetJDK") ? project.targetJDK : 11

+ 81 - 0
smoke-tests/src/test/groovy/io/opentelemetry/smoketest/TraceInspector.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.smoketest;
+
+import com.google.protobuf.ByteString;
+import io.opentelemetry.api.trace.TraceId;
+import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
+import io.opentelemetry.proto.common.v1.AnyValue;
+import io.opentelemetry.proto.common.v1.KeyValue;
+import io.opentelemetry.proto.trace.v1.ResourceSpans;
+import io.opentelemetry.proto.trace.v1.Span;
+import java.util.Collection;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class TraceInspector {
+  final Collection<ExportTraceServiceRequest> traces;
+
+  public TraceInspector(Collection<ExportTraceServiceRequest> traces) {
+    this.traces = traces;
+  }
+
+  public Stream<Span> getSpanStream() {
+    return traces.stream()
+        .flatMap(it -> it.getResourceSpansList().stream())
+        .flatMap(it -> it.getInstrumentationLibrarySpansList().stream())
+        .flatMap(it -> it.getSpansList().stream());
+  }
+
+  public Stream<AnyValue> findResourceAttribute(String attributeKey) {
+    return traces.stream()
+        .flatMap(it -> it.getResourceSpansList().stream())
+        .flatMap(it -> it.getResource().getAttributesList().stream())
+        .filter(it -> it.getKey().equals(attributeKey))
+        .map(KeyValue::getValue);
+  }
+
+  public long countFilteredResourceAttributes(String attributeName, Object attributeValue) {
+    return traces.stream()
+        .flatMap(it -> it.getResourceSpansList().stream())
+        .map(ResourceSpans::getResource)
+        .flatMap(it -> it.getAttributesList().stream())
+        .filter(a -> a.getKey().equals(attributeName))
+        .map(a -> a.getValue().getStringValue())
+        .filter(s -> s.equals(attributeValue))
+        .count();
+  }
+
+  public long countFilteredAttributes(String attributeName, Object attributeValue) {
+    return getSpanStream()
+        .flatMap(s -> s.getAttributesList().stream())
+        .filter(a -> a.getKey().equals(attributeName))
+        .map(a -> a.getValue().getStringValue())
+        .filter(s -> s.equals(attributeValue))
+        .count();
+  }
+
+  protected int countSpansByName(String spanName) {
+    return (int) getSpanStream().filter(it -> it.getName().equals(spanName)).count();
+  }
+
+  protected int countSpansByKind(Span.SpanKind spanKind) {
+    return (int) getSpanStream().filter(it -> it.getKind().equals(spanKind)).count();
+  }
+
+  public int size() {
+    return traces.size();
+  }
+
+  public Set<String> getTraceIds() {
+    return getSpanStream()
+        .map(Span::getTraceId)
+        .map(ByteString::toByteArray)
+        .map(TraceId::bytesToHex)
+        .collect(Collectors.toSet());
+  }
+}