Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,16 @@ test_published_artifacts:
- source .gitlab/gitlab-utils.sh
- gitlab_section_start "collect-reports" "Collecting reports"
- .gitlab/collect_reports.sh --destination ./check_reports --move
- .gitlab/collect_results.sh
- gitlab_section_end "collect-reports"
artifacts:
when: always
paths:
- ./check_reports
- ./results
- '.gradle/daemon/*/*.out.log'
reports:
junit: results/*.xml
retry:
max: 2
when:
Expand Down Expand Up @@ -516,15 +520,21 @@ muzzle:
after_script:
- *container_info
- *cgroup_info
- *set_datadog_api_keys
- source .gitlab/gitlab-utils.sh
- gitlab_section_start "collect-reports" "Collecting reports"
- .gitlab/collect_reports.sh
- .gitlab/collect_results.sh
- .gitlab/upload_ciapp.sh $CACHE_TYPE
- gitlab_section_end "collect-reports"
artifacts:
when: always
paths:
- ./reports
- ./results
- '.gradle/daemon/*/*.out.log'
reports:
junit: results/*.xml

muzzle-dep-report:
extends: .gradle_build
Expand Down
12 changes: 7 additions & 5 deletions .gitlab/collect_results.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,17 @@ do
# E.g. for the example path: tomcat-5.5_forkedTest_TEST-TomcatServletV1ForkedTest.xml
AGGREGATED_FILE_NAME=$(echo "$RESULT_XML_FILE" | rev | cut -d "/" -f 1,2,5 | rev | tr "/" "_")
echo -n " as $AGGREGATED_FILE_NAME"
cp "$RESULT_XML_FILE" "$TEST_RESULTS_DIR/$AGGREGATED_FILE_NAME"
TARGET_DIR="$TEST_RESULTS_DIR"
mkdir -p "$TARGET_DIR"
cp "$RESULT_XML_FILE" "$TARGET_DIR/$AGGREGATED_FILE_NAME"
# Insert file attribute to testcase XML nodes
get_source_file
sed -i "/<testcase/ s|\(time=\"[^\"]*\"\)|\1 file=\"$file_path\"|g" "$TEST_RESULTS_DIR/$AGGREGATED_FILE_NAME"
sed -i "/<testcase/ s|\(time=\"[^\"]*\"\)|\1 file=\"$file_path\"|g" "$TARGET_DIR/$AGGREGATED_FILE_NAME"
# Replace Java Object hashCode by marker in testcase XML nodes to get stable test names
sed -i '/<testcase/ s/@[0-9a-f]\{5,\}/@HASHCODE/g' "$TEST_RESULTS_DIR/$AGGREGATED_FILE_NAME"
sed -i '/<testcase/ s/@[0-9a-f]\{5,\}/@HASHCODE/g' "$TARGET_DIR/$AGGREGATED_FILE_NAME"
# Replace random port numbers by marker in testcase XML nodes to get stable test names
sed -i '/<testcase/ s/localhost:[0-9]\{2,5\}/localhost:PORT/g' "$TEST_RESULTS_DIR/$AGGREGATED_FILE_NAME"
if cmp -s "$RESULT_XML_FILE" "$TEST_RESULTS_DIR/$AGGREGATED_FILE_NAME"; then
sed -i '/<testcase/ s/localhost:[0-9]\{2,5\}/localhost:PORT/g' "$TARGET_DIR/$AGGREGATED_FILE_NAME"
if cmp -s "$RESULT_XML_FILE" "$TARGET_DIR/$AGGREGATED_FILE_NAME"; then
echo ""
else
echo -n " (non-stable test names detected)"
Expand Down
44 changes: 29 additions & 15 deletions .gitlab/upload_ciapp.sh
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
#!/usr/bin/env bash
SERVICE_NAME="dd-trace-java"
CACHE_TYPE=$1
TEST_JVM=$2
TEST_JVM=${2:-}

# CI_JOB_NAME, CI_NODE_INDEX, and CI_NODE_TOTAL are read from GitLab CI environment

# JAVA_???_HOME are set in the base image for each used JDK https://github.com/DataDog/dd-trace-java-docker-build/blob/master/Dockerfile#L86
JAVA_HOME="JAVA_${TEST_JVM}_HOME"
JAVA_BIN="${!JAVA_HOME}/bin/java"
if [ ! -x "$JAVA_BIN" ]; then
JAVA_BIN=$(which java)
JAVA_PROPS=""
if [ -n "$TEST_JVM" ]; then
JAVA_BIN=""
if [[ "$TEST_JVM" =~ ^[A-Za-z0-9_]+$ ]]; then
JAVA_HOME_VAR="JAVA_${TEST_JVM}_HOME"
JAVA_HOME_VALUE="${!JAVA_HOME_VAR}"
if [ -n "$JAVA_HOME_VALUE" ] && [ -x "$JAVA_HOME_VALUE/bin/java" ]; then
JAVA_BIN="$JAVA_HOME_VALUE/bin/java"
fi
fi
if [ -z "$JAVA_BIN" ]; then
JAVA_BIN="$(command -v java)"
fi
JAVA_PROPS=$($JAVA_BIN -XshowSettings:properties -version 2>&1)
fi

# Extract Java properties from the JVM used to run the tests
JAVA_PROPS=$($JAVA_BIN -XshowSettings:properties -version 2>&1)
java_prop() {
local PROP_NAME=$1
echo "$JAVA_PROPS" | grep "$PROP_NAME" | head -n1 | cut -d'=' -f2 | xargs
Expand All @@ -27,11 +35,23 @@ junit_upload() {
# Build custom tags array directly from arguments
local custom_tags_args=()

# Extract job base name from CI_JOB_NAME (strip matrix suffix)
# Extract job base name from CI_JOB_NAME.
# Handles:
# - matrix suffix format: "job-name: [value, 1/6]" -> "job-name"
# - split suffix format: "job-name 1/6" -> "job-name"
local job_base_name="${CI_JOB_NAME%%:*}"
job_base_name="$(echo "$job_base_name" | sed -E 's/[[:space:]]+[0-9]+\/[0-9]+$//')"

# Add custom test configuration tags
custom_tags_args+=(--tags "test.configuration.jvm:${TEST_JVM}")
if [ -n "$TEST_JVM" ]; then
custom_tags_args+=(--tags "test.configuration.jvm:${TEST_JVM}")
custom_tags_args+=(--tags "runtime.name:$(java_prop java.runtime.name)")
custom_tags_args+=(--tags "runtime.vendor:$(java_prop java.vendor)")
custom_tags_args+=(--tags "runtime.version:$(java_prop java.version)")
custom_tags_args+=(--tags "os.architecture:$(java_prop os.arch)")
custom_tags_args+=(--tags "os.platform:$(java_prop os.name)")
custom_tags_args+=(--tags "os.version:$(java_prop os.version)")
fi
if [ -n "$CI_NODE_INDEX" ] && [ -n "$CI_NODE_TOTAL" ]; then
custom_tags_args+=(--tags "test.configuration.split:${CI_NODE_INDEX}/${CI_NODE_TOTAL}")
fi
Expand All @@ -43,12 +63,6 @@ junit_upload() {
datadog-ci junit upload --service $SERVICE_NAME \
--logs \
--tags "test.traits:{\"category\":[\"$CACHE_TYPE\"]}" \
--tags "runtime.name:$(java_prop java.runtime.name)" \
--tags "runtime.vendor:$(java_prop java.vendor)" \
--tags "runtime.version:$(java_prop java.version)" \
--tags "os.architecture:$(java_prop os.arch)" \
--tags "os.platform:$(java_prop os.name)" \
--tags "os.version:$(java_prop os.version)" \
--tags "git.repository_url:https://github.com/DataDog/dd-trace-java" \
"${custom_tags_args[@]}" \
./results
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,20 @@ class MuzzlePlugin : Plugin<Project> {
project.afterEvaluate {
// use runAfter to set up task finalizers in version order
var runAfter: TaskProvider<MuzzleTask> = muzzleTask
val muzzleReportTasks = mutableListOf<TaskProvider<MuzzleTask>>()

project.extensions.getByType<MuzzleExtension>().directives.forEach { directive ->
project.logger.debug("configuring {}", directive)

if (directive.isCoreJdk) {
runAfter = addMuzzleTask(directive, null, project, runAfter, muzzleBootstrap, muzzleTooling)
muzzleReportTasks.add(runAfter)
} else {
val range = resolveVersionRange(directive, system, session)

muzzleDirectiveToArtifacts(directive, range).forEach {
runAfter = addMuzzleTask(directive, it, project, runAfter, muzzleBootstrap, muzzleTooling)
muzzleReportTasks.add(runAfter)
}

if (directive.assertInverse) {
Expand All @@ -139,15 +142,21 @@ class MuzzlePlugin : Plugin<Project> {

muzzleDirectiveToArtifacts(inverseDirective, inverseRange).forEach {
runAfter = addMuzzleTask(inverseDirective, it, project, runAfter, muzzleBootstrap, muzzleTooling)
muzzleReportTasks.add(runAfter)
}
}
}
}
project.logger.info("configured $directive")
}

if (muzzleReportTasks.isEmpty() && !project.extensions.getByType<MuzzleExtension>().directives.any { it.assertPass }) {
muzzleReportTasks.add(muzzleTask)
}

val timingTask = project.tasks.register<MuzzleEndTask>("muzzle-end") {
startTimeMs.set(startTime)
muzzleResultFiles.from(muzzleReportTasks.map { it.flatMap { task -> task.result } })
}
// last muzzle task to run
runAfter.configure {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,167 @@
package datadog.gradle.plugin.muzzle.tasks

import datadog.gradle.plugin.muzzle.pathSlug
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
import java.io.File
import java.io.StringWriter
import javax.xml.stream.XMLOutputFactory

abstract class MuzzleEndTask : AbstractMuzzleTask() {
@get:Input
abstract val startTimeMs: Property<Long>

@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
abstract val muzzleResultFiles: ConfigurableFileCollection

@get:OutputFile
val resultsFile = project
.layout
.buildDirectory
.file("test-results/muzzle/TEST-muzzle-${project.pathSlug}.xml")

@get:OutputFile
val resultsFile = project.rootProject
val legacyResultsFile = project.rootProject
.layout
.buildDirectory
.file("${MUZZLE_TEST_RESULTS}/${project.pathSlug}_muzzle/results.xml")

@TaskAction
fun generatesResultFile() {
val report = buildJUnitReport()
writeReportFile(project.file(resultsFile), renderReportXml(report), "muzzle junit")
writeReportFile(project.file(legacyResultsFile), renderLegacyReportXml(report.durationSeconds), "muzzle legacy")
}

private fun buildJUnitReport(): MuzzleJUnitReport {
val endTimeMs = System.currentTimeMillis()
val seconds = (endTimeMs - startTimeMs.get()).toDouble() / 1000.0
with(project.file(resultsFile)) {
parentFile.mkdirs()
writeText(
"""
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="$name" tests="1" id="0" time="$seconds">
<testcase name="$name" time="$seconds"/>
</testsuite>
""".trimIndent()
)
project.logger.info("Wrote muzzle results report to\n $this")
val testCases = muzzleResultFiles.files
.sortedBy { it.name }
.map { resultFile ->
val taskName = resultFile.name.removeSuffix(".txt")
when {
!resultFile.exists() -> {
MuzzleJUnitCase(
name = taskName,
failureMessage = "Muzzle result file missing",
failureText = "Expected ${resultFile.path}"
)
}

resultFile.readText() == "PASSING" -> MuzzleJUnitCase(name = taskName)
else -> {
MuzzleJUnitCase(
name = taskName,
failureMessage = "Muzzle validation failed",
failureText = resultFile.readText()
)
}
}
}
return MuzzleJUnitReport(
suiteName = project.path,
module = project.path,
className = "muzzle.${project.pathSlug}",
durationSeconds = seconds,
testCases = testCases
)
}

private fun renderReportXml(report: MuzzleJUnitReport): String {
val output = StringWriter()
val xmlWriter = XMLOutputFactory.newInstance().createXMLStreamWriter(output)
with(xmlWriter) {
try {
writeStartDocument("UTF-8", "1.0")
writeCharacters("\n")
writeStartElement("testsuite")
writeAttribute("name", report.suiteName)
writeAttribute("tests", report.testCases.size.toString())
writeAttribute("failures", report.failures.toString())
writeAttribute("errors", "0")
writeAttribute("skipped", "0")
writeAttribute("time", report.durationSeconds.toString())
writeCharacters("\n")

writeStartElement("properties")
writeCharacters("\n")
writeEmptyElement("property")
writeAttribute("name", "category")
writeAttribute("value", "muzzle")
writeCharacters("\n")
writeEmptyElement("property")
writeAttribute("name", "module")
writeAttribute("value", report.module)
writeCharacters("\n")
writeEndElement()
writeCharacters("\n")

report.testCases.forEach { testCase ->
writeStartElement("testcase")
writeAttribute("classname", report.className)
writeAttribute("name", testCase.name)
writeAttribute("time", "0")
if (testCase.failureMessage != null) {
writeCharacters("\n")
writeStartElement("failure")
writeAttribute("message", testCase.failureMessage)
writeCharacters(testCase.failureText ?: "")
writeEndElement()
writeCharacters("\n")
}
writeEndElement()
writeCharacters("\n")
}
writeEndElement()
writeEndDocument()
flush()
} finally {
close()
}
}
return output.toString()
}

private fun writeReportFile(file: File, xml: String, label: String) {
file.parentFile.mkdirs()
file.writeText(xml)
project.logger.info("Wrote $label report to\n $file")
}

private fun renderLegacyReportXml(durationSeconds: Double): String {
return """
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="$name" tests="1" id="0" time="$durationSeconds">
<testcase name="$name" time="$durationSeconds"/>
</testsuite>
""".trimIndent()
}

private data class MuzzleJUnitReport(
val suiteName: String,
val module: String,
val className: String,
val durationSeconds: Double,
val testCases: List<MuzzleJUnitCase>
) {
val failures: Int
get() = testCases.count { it.failureMessage != null }
}

private data class MuzzleJUnitCase(
val name: String,
val failureMessage: String? = null,
val failureText: String? = null
)

companion object {
private const val MUZZLE_TEST_RESULTS = "muzzle-test-results"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,8 @@ abstract class MuzzleTask @Inject constructor(
@get:Optional
val muzzleDirective: Property<MuzzleDirective> = objects.property()

// This output is only used to make the task cacheable, this is not exposed
@get:OutputFile
@get:Optional
protected val result: RegularFileProperty = objects.fileProperty().convention(
val result: RegularFileProperty = objects.fileProperty().convention(
project.layout.buildDirectory.file("reports/$name.txt")
)

Expand Down
Loading