diff --git a/.gitignore b/.gitignore index f5ee785..97c2131 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ target/ dependency-reduced-pom.xml -*.iml -.idea/ -/bin/ -*~ +# Gradle +.gradle/ +**/build/ +!**/src/**/build/ diff --git a/README.md b/README.md index db31b60..8f4a1c2 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ This project operates under the following constraints: 1. **Changes must not be _breaking_**; programs built to compile and run using version 0.0.1 artifacts from this project must be able to compile and run with version 0.0.2 artifacts from this project. -1. **This project should not be _branched_**. Maintaining multiple versions +2. **This project should not be _branched_**. Maintaining multiple versions of this project is not desirable. -1. The `tools` module may not rely on any third-party artifacts other than: +3. The `tools` module may not rely on any third-party artifacts other than: * `org.slf4j:slf4j-api` * `com.github.spotbugs:spotbugs-annotations` -1. The `test-tools` module may rely on test-support artifacts in +4. The `test-tools` module may rely on test-support artifacts in common use though these artifacts should generally be optional for consumers of the `test-tools` module. For example, the following artifacts might be used: @@ -24,9 +24,3 @@ This project operates under the following constraints: If an artifact on which `test-tools` relies makes a breaking change, introduce new artifact containing the breaking components -- not a new version of the `test-tools` artifact. - -## Notes - -* While the project is currently designed to produce artifacts operable under Java 8, the complete Javadoc - will not be produced unless a Java 11 runtime is used during the build. (The plugin used to generate - diagrams included in the Javadoc requires running under Java 11.) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..3cf9423 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,40 @@ +import java.time.Duration + +plugins { + base + id("io.github.gradle-nexus.publish-plugin") version "1.3.0" + id("org.nosphere.apache.rat") +} + +val defaultVersion: String by project +val sonatypeUser: String by project +val sonatypePwd: String by project + +group = "org.terracotta" +version = defaultVersion + +repositories { + mavenCentral() // See https://github.com/eskatos/creadur-rat-gradle/issues/26 +} + +tasks { + rat { + approvedLicense("Apache License Version 2.0") + include("buildSrc/src/**") + exclude("**/org.terracotta.java-conventions.gradle.kts") + } +} + +nexusPublishing { + repositories { + sonatype { + username = sonatypeUser + password = sonatypePwd + } + } + // Sonatype is often very slow in these operations: + transitionCheckOptions { + delayBetween = Duration.ofSeconds((findProperty("delayBetweenRetriesInSeconds") ?: "10").toString().toLong()) + maxRetries = (findProperty("numberOfRetries") ?: "100").toString().toInt() + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000..88347cf --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +repositories { + // Use the plugin portal to apply community plugins in convention plugins. + gradlePluginPortal() +} + +dependencies { + implementation("net.sourceforge.plantuml:plantuml:1.2023.11") + implementation("com.github.spotbugs.snom:spotbugs-gradle-plugin:5.0.13") + implementation("org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:0.8.0") +} + +gradlePlugin { + plugins { + create("plantUml") { + id = "org.terracotta.utilities.plugins.plantuml" + implementationClass = "org.terracotta.utilities.plugins.PlantUmlPlugin" + } + } +} diff --git a/buildSrc/src/main/kotlin/org.terracotta.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/org.terracotta.java-conventions.gradle.kts new file mode 100644 index 0000000..e8299b7 --- /dev/null +++ b/buildSrc/src/main/kotlin/org.terracotta.java-conventions.gradle.kts @@ -0,0 +1,236 @@ +import com.github.spotbugs.snom.SpotBugsPlugin +import com.github.spotbugs.snom.SpotBugsTask +import org.gradle.api.Action +import org.gradle.api.attributes.java.TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE +import org.gradle.api.plugins.JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME +import java.util.regex.Pattern + +plugins { + `java-library` + `maven-publish` + signing + id("checkstyle") + id("com.github.spotbugs") + id("org.nosphere.apache.rat") +} + +val slf4jBaseVersion: String by project +val slf4jUpperVersion: String by project +val slf4jRangeVersion by extra("[${slf4jBaseVersion},${slf4jUpperVersion})") +val logbackBaseVersion: String by project +val logbackUpperVersion: String by project +val logbackRangeVersion by extra("[${logbackBaseVersion},${logbackUpperVersion})") +val junitVersion: String by project +val hamcrestVersion: String by project +val mockitoVersion: String by project +val spotbugsAnnotationsVersion: String by project + +group = "org.terracotta" +version = project.rootProject.version + +repositories { + mavenLocal() + maven { + url = uri("https://repo.maven.apache.org/maven2/") + } +} + +dependencies { + api("org.slf4j:slf4j-api:${slf4jRangeVersion}") + compileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsAnnotationsVersion}") + + testCompileOnly("com.github.spotbugs:spotbugs-annotations:${spotbugsAnnotationsVersion}") + testImplementation("org.hamcrest:hamcrest:${hamcrestVersion}") + testImplementation("junit:junit:${junitVersion}") { + exclude(mapOf("group" to "org.hamcrest")) + } + testImplementation("org.mockito:mockito-core:${mockitoVersion}") +} + +configurations.all { + // Spotbugs has an internal SLF4J conflict ... + if (SpotBugsPlugin.CONFIG_NAME != this.name) { + resolutionStrategy { + failOnVersionConflict() + } + } +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + withSourcesJar() + withJavadocJar() +} + +publishing { + repositories { + // For publication testing ... + maven(uri(layout.buildDirectory.dir("publishing-repository"))) { + name = "buildLocal" + } + } + + publications.register("maven") { + from(components["java"]) + + pom { + description.convention(project.provider { project.description }) + + licenses { + license { + name.convention("The Apache License, Version 2.0") + url.convention("https://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + developers { + developer { + name.convention("Terracotta Engineers") + email.convention("dev-internal@terracottatech.com") + organization.convention("Terracotta Inc., a wholly-owned subsidiary of Software AG USA, Inc.") + organizationUrl.convention("https://terracotta.org") + } + } + scm { + connection.convention("scm:git:https://github.com/terracotta-oss/terracotta-utilities.git") + developerConnection.convention("scm:git:git@github.com:terracotta-oss/terracotta-utilities.git") + url.convention("https://github.com/terracotta-oss/terracotta-utilities") + } + } + } +} + +checkstyle { + toolVersion = "9.3" // Latest version supporting Java 8 + configFile = rootDir.resolve("config/checkstyle.xml") + configDirectory.convention(layout.projectDirectory.dir("config/checkstyle")) +} + +tasks { + withType { + options.encoding = "UTF-8" + options.isDeprecation = true + options.isWarnings = true + options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror")) + } + + withType { + isFailOnError = false + + (options as StandardJavadocDocletOptions).apply { + quiet() + encoding = "UTF-8" + author(false) + showFromProtected() + use(true) + + // Avoid complaints about tablew@summary + addBooleanOption("Xdoclint:all,-accessibility", true) // https://discuss.gradle.org/t/how-to-send-x-option-to-javadoc/23384/5 + } + } + + test { + useJUnit() + } + + withType { + reports { + register("xml") { + required.set(false) + } + register("html") { + required.set(true) + } + } + } + + spotbugsTest { + enabled = false + } + + rat { + approvedLicense("Apache License Version 2.0") + exclude("build/**") // Avoid implicit dependency error on task outputs + exclude("target/**") + exclude("build.gradle.kts") + } + + // CopySpec common to executable and sources Jars + val pomAndLicense = project.copySpec { + into("META-INF/maven/${project.group}/${project.name}") { + from({ tasks.named("generatePomFileForMavenPublication") }) + rename(Pattern.quote("pom-default.xml"), "pom.xml") + } + into("") { + from(project.rootProject.rootDir.resolve("LICENSE")) + } + } + + val versionPattern = Pattern.compile("(?\\d+)\\.(?\\d+)\\..*") + @Throws(InvalidUserCodeException::class) + fun specVersion(version: String) : String { + val matcher = versionPattern.matcher(version) + return if (matcher.matches()) { + matcher.group("major") + "." + matcher.group("minor") + } else { + throw InvalidUserCodeException("Version string \"${version}\" is not in major.minor. format") + } + } + + // Manifest construct common to executable and sources Jars + val commonManifest = Action { + val jdkSpec = configurations.named(RUNTIME_ELEMENTS_CONFIGURATION_NAME) + .map { c -> JavaVersion.toVersion(c.attributes.getAttribute(TARGET_JVM_VERSION_ATTRIBUTE) ?: 8) } + + attributes( + linkedMapOf( + "Build-Jdk-Spec" to jdkSpec, + "Built-By" to System.getProperty("user.name"), + "Build-Jdk" to System.getProperty("java.version"), + "Specification-Title" to project.description, + "Specification-Version" to specVersion(project.version.toString()), + "Implementation-Title" to project.name, + "Implementation-Vendor-Id" to project.group, + "Implementation-Version" to project.version, + ) + ) + } + + jar { + with(pomAndLicense) + manifest(commonManifest) + } + + named("sourcesJar") { + with(pomAndLicense) + manifest(commonManifest) + } +} + +signing { + /* + * By default, signing uses the 'signing.keyId', 'signing.secretKeyRingFile', and 'signing.password' + * values provided through the gradle.properties file (or some other Gradle property-setting mechanism). + * This scheme does not require special signatory setup methods. + * + * For CI-based publishing, use of the 'ORG_GRADLE_PROJECT_{signingKey,signingPassword,signingKeyId}' + * environment variables supply an ASCII-armored key and require the use of the 'useInMemoryPgpKeys' + * setup method. + * + * If 'ORG_GRADLE_PROJECT_signingKey' environment variable is set, use the 'useInMemoryPgpKeys' + * method; otherwise, the default, property-based method is used. + */ + if (hasProperty("signingKey")) { + val signingKey: String by project + val signingPassword: String by project + if (hasProperty("signingKeyId")) { + val signingKeyId: String by project + useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) + } else { + useInMemoryPgpKeys(signingKey, signingPassword) + } + } + + // Require signing when requested -- either explicitly or via "publish" + setRequired({ gradle.taskGraph.hasTask(tasks.named("signMavenPublication").get()) }) + sign(publishing.publications) +} diff --git a/buildSrc/src/main/kotlin/org/terracotta/utilities/plugins/PlantUmlPlugin.kt b/buildSrc/src/main/kotlin/org/terracotta/utilities/plugins/PlantUmlPlugin.kt new file mode 100644 index 0000000..0541474 --- /dev/null +++ b/buildSrc/src/main/kotlin/org/terracotta/utilities/plugins/PlantUmlPlugin.kt @@ -0,0 +1,175 @@ +/* + * Copyright 2023 Terracotta, Inc., a Software AG company. + * + * 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 + * + * http://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. + */ +package org.terracotta.utilities.plugins + +import net.sourceforge.plantuml.Option +import net.sourceforge.plantuml.SourceFileReader +import net.sourceforge.plantuml.file.FileGroup +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.internal.tasks.JvmConstants +import org.gradle.api.logging.LogLevel +import org.gradle.api.plugins.JvmEcosystemPlugin +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.execution.MultipleBuildFailures +import org.gradle.external.javadoc.JavadocOptionFileOption +import org.gradle.external.javadoc.StandardJavadocDocletOptions +import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import java.io.File +import java.nio.charset.StandardCharsets +import javax.inject.Inject + +/** + * Uses PlantUML to generate diagrams described in comments blocks in Java source files. + * + * @author Clifford W. Johnson + * @see Generate diagrams with Javadoc / Legacy Javadoc + */ +class PlantUmlPlugin : Plugin { + override fun apply(project: Project) { + + project.plugins.withType().configureEach { + project.extensions.configure { + configureEach { + val sourceSet = this + val sourceSetName = this.name + val javaSourceSet = this.java + + // Register a PlantUML task for this sourceSet + val plantUml = project.tasks.register(this.getTaskName("generate", "plantuml"), PlantUmlTask::class.java) { + this.group = JvmConstants.DOCUMENTATION_GROUP + this.description = "Generates PlantUML diagrams within ${sourceSetName} Java source files" + this.outputDirectory.convention(project.layout.buildDirectory.dir("generated/plantuml/${sourceSetName}")) + this.charset.convention(StandardCharsets.UTF_8.name()) + this.verbose.convention(project.logging.level == LogLevel.DEBUG) + this.plantUmlExcludes.convention(emptyList()) + this.source = javaSourceSet + } + + // Connect the Javadoc task associated with this SourceSet to the PlantUML task just registered + project.tasks.withType(Javadoc::class.java) + .configureEach { + if (sourceSet.javadocTaskName.equals(this.name)) { + dependsOn(plantUml) + options { + (this as StandardJavadocDocletOptions).docFilesSubDirs(true) + this.addOption( + project.objects.newInstance(LazyFileJavadocOptionFileOption::class.java, + "sourcepath", plantUml.flatMap { it.outputDirectory }) + ) + } + } + } + } + } + } + } +} + +/** + * Provides a lazily-configurable Javadoc option for a file value. + */ +// Implementing methods conflicting with auto-generated getter/setter -- https://stackoverflow.com/a/50871196/1814086 +open class LazyFileJavadocOptionFileOption @Inject constructor( + private val option: String, + private var value: Any, + val project: Project +) : + JavadocOptionFileOption { + + override fun getOption(): String = option + + override fun getValue(): Any = value + + override fun setValue(value: Any) { + this.value = value + } + + override fun write(writerContext: JavadocOptionFileWriterContext) { + writerContext.writeValueOption(option, project.file(value).absolutePath) + } +} + +/** + * The Gradle task invoking plantuml over the configured source files. + */ +abstract class PlantUmlTask : SourceTask() { + + /** + * PlantUML output directory corresponding to the '-output' option. + * Default is "${buildDirectory}/generated/plantuml/${soruceSet.name}". + */ + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + /** + * Charset used for PlantUML output corresponding to the '-charset' option. + * Defaults to "UTF-8". + */ + @get:Input + abstract val charset: Property + + /** + * Generate diagnostic output corresponding to the '-verbose' option. + * Default to true if Gradle logging level is [LogLevel.DEBUG]; false otherwise. + */ + @get:Internal + abstract val verbose: Property + + /** + * Collection of file exclusion patterns corresponding to the PlantUML '-exclude' option. + * Defaults to an empty list. + */ + @get:Input + abstract val plantUmlExcludes: ListProperty + + @TaskAction + fun generate() { + val outputDir = outputDirectory.get().asFile + outputDir.mkdirs() + + val faults = mutableListOf() + source.visit { + if (!this.isDirectory) { + val oDir = File(outputDir, this.relativePath.parent.pathString) + + val fileGroup = FileGroup(file.absolutePath, plantUmlExcludes.get(), Option()) + fileGroup.files.forEach { file -> + val reader = SourceFileReader(file, oDir, charset.get()) + val images = reader.generatedImages + images.forEach { + if (it.lineErrorRaw() == -1) { + logger.info("PlantUML generated ${it.pngFile} from $it") + } else { + val message = "PlantUML generation failed for $it at line ${it.lineErrorRaw()}" + faults.add(GradleException(message)) + } + } + } + } + } + if (faults.isNotEmpty()) { + throw MultipleBuildFailures(faults) + } + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..a75ae3a --- /dev/null +++ b/gradle.properties @@ -0,0 +1,16 @@ + +defaultVersion=0.0.18-SNAPSHOT + +spotbugsAnnotationsVersion=4.0.2 + +slf4jBaseVersion=1.7.32 +slf4jUpperVersion=1.7.9999 +logbackBaseVersion=1.2.11 +logbackUpperVersion=1.2.9999 + +hamcrestVersion=2.2 +mockitoVersion=3.3.3 +junitVersion=4.12 + +sonatypeUser = OVERRIDE_ME +sonatypePwd = OVERRIDE_ME diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7f93135 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3fa8f86 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..0adc8e1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original 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 POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# 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 ;; #( + MSYS* | 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 + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pom.xml b/pom.xml index 6c22dd1..5a1de0e 100644 --- a/pom.xml +++ b/pom.xml @@ -224,12 +224,21 @@ .mvn/wrapper/maven-wrapper.properties + .mvn/.gradle-enterprise/** + .mvn/extensions.xml + gradle/wrapper/gradle-wrapper.properties + buildSrc/.gradle/** + buildSrc/**/org.terracotta.java-conventions.gradle.kts README.md LICENSE-* NOTICE-* src/main/java/org/terracotta/org/junit/** src/test/java/org/terracotta/org/junit/** .github/** + gradle.properties + settings.gradle.kts + **/build.gradle.kts + build/** diff --git a/port-chooser/build.gradle.kts b/port-chooser/build.gradle.kts new file mode 100644 index 0000000..e852fa8 --- /dev/null +++ b/port-chooser/build.gradle.kts @@ -0,0 +1,73 @@ +plugins { + id("org.terracotta.java-conventions") + id("org.terracotta.utilities.plugins.plantuml") +} + +val logbackRangeVersion: String by project + +description = "Terracotta Utilities Port Chooser" + +dependencies { + api(project(":terracotta-utilities-tools")) { + exclude(mapOf("group" to "org.slf4j", "module" to "slf4j-api")) + } + testImplementation(project(":terracotta-utilities-test-tools")) + testImplementation("ch.qos.logback:logback-classic:${logbackRangeVersion}") +} + +testing { + suites { + named(JvmTestSuitePlugin.DEFAULT_TEST_SUITE_NAME) { + targets { + + val testIPv4_base by creating { + // Run test using IPv4 preference + testTask { + filter { + setIncludePatterns("org.terracotta.utilities.test.net.*Test") + // Exclude single test run below + setExcludePatterns("org.terracotta.utilities.test.net.PortManagerTest") + } + systemProperty("java.net.preferIPv4Stack", "true") + systemProperty("org.terracotta.disablePortReleaseCheck", "false") + environment("DISABLE_PORT_RELEASE_CHECK" to "") + } + } + + val testIPv4_A by creating { + // Repeat tests using IPv4 preference - single test of long-running suite + testTask { + filter { + includeTest("org.terracotta.utilities.test.net.PortManagerTest", "testReleaseCheckEnabled") + } + systemProperty("java.net.preferIPv4Stack", "true") + systemProperty("org.terracotta.disablePortReleaseCheck", "false") + environment("DISABLE_PORT_RELEASE_CHECK" to "") + } + } + + val testPortCheckDisabled by creating { + // Single test for DISABLE_PORT_RELEASE_CHECK=true + testTask { + filter { + includeTest("org.terracotta.utilities.test.net.PortManagerTest", "testReleaseCheckDisabledEnvironment") + } + environment("DISABLE_PORT_RELEASE_CHECK" to "true") + } + } + + named(this@named.name) { + testTask { + dependsOn( + testIPv4_base, + testIPv4_A, + testPortCheckDisabled + ) + // Standard tests run with DISABLE_PORT_RELEASE_CHECK false or unset + environment("DISABLE_PORT_RELEASE_CHECK" to "") + } + } + } + } + } +} diff --git a/port-chooser/src/main/java/org/terracotta/utilities/test/net/PortManager.java b/port-chooser/src/main/java/org/terracotta/utilities/test/net/PortManager.java index 453f055..bcc2017 100644 --- a/port-chooser/src/main/java/org/terracotta/utilities/test/net/PortManager.java +++ b/port-chooser/src/main/java/org/terracotta/utilities/test/net/PortManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 Terracotta, Inc., a Software AG company. + * Copyright 2020-2023 Terracotta, Inc., a Software AG company. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..ab93136 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,9 @@ +rootProject.name = "terracotta-utilities-parent" + +include(":terracotta-utilities-tools") +include(":terracotta-utilities-port-chooser") +include(":terracotta-utilities-test-tools") + +project(":terracotta-utilities-tools").projectDir = file("tools") +project(":terracotta-utilities-port-chooser").projectDir = file("port-chooser") +project(":terracotta-utilities-test-tools").projectDir = file("test-tools") diff --git a/test-tools/README.md b/test-tools/README.md index 805e948..ddb74e9 100644 --- a/test-tools/README.md +++ b/test-tools/README.md @@ -24,3 +24,136 @@ when tests are run in a Docker container (during CI) running as root. **NOTE:** To limit changes to the files obtained from the JUnit code base to the absolute minimum, the Terracotta copyright header is intentionally omitted and SpotBugs/FindBugs is intentionally suppressed. + + +## Dependency Declarations for `terracotta-utilities-test-tools` + +The dependency declarations needed for the `terracotta-utilities-test-tools` module depends on: + +1. the build system, e.g. Maven vs Gradle +2. the tools needed + +The `terracotta-utilities-test-tools` module contains the following general tool categories: + +1. tools used for writing JUnit4/Hamcrest tests +2. tools used to verify Logback event output +3. tools not requiring either JUnit4/Hamcrest or Logback + +The dependency declarations are described below. + +### Maven Declarations + +For Maven builds, the `terracotta-utilities-test-tools` module has several _optional_ dependencies. +The dependency declarations needed vary with the tools you wish to use. + +#### Minimal Declaration + +The following Maven dependency declaration is required for all uses of `terracotta-utilties-test-tools`: + + + org.terracotta + terracotta-utilities-test-tools + ${toolVersion} + test + + +The above declaration supports the use of the tooling not requiring JUnit 4, Hamcrest, or Logback, +for example, `Diagnostics`. To use the JUnit4/Hamcrest and/or Logback tools, additional dependencies +must be added. + +#### JUnit4/Hamcrest Tooling + +To use the JUnit4/Hamcrest tools, the following declarations must be added: + + + junit + junit + 4.12 + test + + + org.hamcrest + hamcrest-core + + + + + org.hamcrest + hamcrest + 2.2 + test + + +#### Logback Tooling + +To use the Logback tools (`ConsoleAppenderCapture`), the following declarations must +be added: + + + ch.qos.logback + logback-classic + [1.2.11,1.2.9999) + + +You may _pin_ the version to any version within the specified range. + + +### Gradle Declarations + +For Gradle builds, this module defines three (3) feature variants each with a uniquely-defined capability: + + "main" / capability "org.terracotta:terracotta-utilities-test-tools:" + : For use when JUnit 4 and Hamcrest tools are needed; this is considered the typical use and + is the variant chosen when `org.terracotta.terracotta-test-tools` is declared as the + dependency. + + "logback" / capability "org.terracotta:terracotta-utilities-test-tools-logback:" + : For use when the logging test helpers are needed (`ConsoleAppenderCapture`). + + "default" / capability "org.terracotta:terracotta-utilities-test-tools-base:" + : For use when neither JUnit4/Hamcrest nor logging tools are needed (`Diagnostics`). + +The variants differ in the transitive dependencies each declares; multiple variants may be +combined, as described below, in order to use multiple capabilities. + +The dependency declarations shown in the sections that follow are for Kotlin build scripts. + +#### JUnit4/Hamcrest Tooling + +To use JUnit4/Hamcrest tools, the following dependency declaration should be used: + + testImplementation("org.terracotta:terracotta-utilities-test-tools:${toolVersion}") + +#### Logback Tooling + +To use the Logback tools, the following dependency declaration should be used: + + testImplementation("org.terracotta:terracotta-utilities-test-tools:${toolVersion}") { + capabilities { + requireCapability("org.terracotta:terracotta-utilities-test-tools-logback") + } + } + +#### Basic Tooling + +If neither the JUnit4/Hamcrest nor Logback capabilities are needed (for example, to +use only the `Diagnostics` tool), the following dependency declaration can be used: + + testImplementation("org.terracotta:terracotta-utilities-test-tools:${toolVersion}") { + capabilities { + requireCapability("org.terracotta:terracotta-utilities-test-tools-base") + } + } + +#### Combined Tooling + +The `Diagnostics` tool is available with each of the dependency declarations above. To use both +the JUnit4/Hamcrest tools and Logback tools, use _both_ the JUnit4/Hamcrest and Logback declarations +as follows: + + testImplementation("org.terracotta:terracotta-utilities-test-tools:${toolVersion}") + testImplementation("org.terracotta:terracotta-utilities-test-tools:${toolVersion}") { + capabilities { + requireCapability("org.terracotta:terracotta-utilities-test-tools-logback") + } + } diff --git a/test-tools/build.gradle.kts b/test-tools/build.gradle.kts new file mode 100644 index 0000000..c03706f --- /dev/null +++ b/test-tools/build.gradle.kts @@ -0,0 +1,157 @@ +plugins { + id("org.terracotta.java-conventions") +} + +val logbackRangeVersion: String by project +val hamcrestVersion: String by project +val junitVersion: String by project +val slf4jRangeVersion: String by project + +description = "Terracotta Utilities Test Tools" + +/* + * See README.md for a description of the use of the feature/capability and + * the Maven and Gradle declarations needed for each feature/capability. + */ + +java { + /* + * The ("main") configuration carries the JUnit/Hamcrest dependencies and + * has the "normal" or "default" capability for this module. This is done + * because this is the typical usage of this module. (The api and + * implementation configurations for the "normal"/"default" feature + * are changed to a "base" capability below.) + */ + registerFeature("main") { + usingSourceSet(sourceSets.main.get()) + capability(group as String, name, version as String) + } + + /* + * 'logback' configuration is needed when 'ConsoleAppenderCapture' + * is used. + */ + registerFeature("logback") { + usingSourceSet(sourceSets.main.get()) + } +} + +configurations { + apiElements { + outgoing { + // "Default" api configuration moves to "base" capability + capability("${project.group}:${project.name}-base:${project.version}") + } + } + runtimeElements { + outgoing { + // "Default" runtime configuration moves to "base" capability + capability("${project.group}:${project.name}-base:${project.version}") + } + } + + /* + * Anchor each feature's apiElements and runtimeElements configurations in the + * primary/default compileOnlyApi/api and implementation/runtimeOnly configurations. + * See https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_configurations_graph. + */ + "mainApiElements" { + extendsFrom(api.get(), compileOnlyApi.get()) + } + "mainRuntimeElements" { + extendsFrom(implementation.get(), runtimeOnly.get()) + } + "logbackApiElements" { + extendsFrom(api.get(), compileOnlyApi.get()) + } + "logbackRuntimeElements" { + extendsFrom(implementation.get(), runtimeOnly.get()) + } +} + +dependencies { + "mainApi"("org.hamcrest:hamcrest:${hamcrestVersion}") + "mainApi"("junit:junit:${junitVersion}") { + exclude(mapOf("group" to "org.hamcrest")) + } + + "logbackImplementation"("ch.qos.logback:logback-core:${logbackRangeVersion}") + "logbackApi"("ch.qos.logback:logback-classic:${logbackRangeVersion}") +} + +tasks.javadoc { + (options as StandardJavadocDocletOptions).apply { + if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { + // Suppress junk from imported TemporaryFolder package + addBooleanOption("Xdoclint/package:-org.terracotta.org.junit.*", true) // Java 9+ + } + } +} + +tasks.test { + filter { + // Don't look for nested classes + excludeTestsMatching("*$*") + } +} + +testing { + suites { + named(JvmTestSuitePlugin.DEFAULT_TEST_SUITE_NAME) { + targets { + + val withoutConsole by creating { + testTask { + filter { + includeTestsMatching("org.terracotta.utilities.test.logging.ConsoleAppenderCaptureNoConsoleTest") + } + systemProperty("logback.configurationFile", "noConsoleAppender.xml") + } + } + + named(this@named.name) { + testTask { + dependsOn(withoutConsole) + } + } + } + } + } +} + +spotbugs { + excludeFilter.value(layout.projectDirectory.file("config/spotbugs/excludeFilter.xml")) +} + +tasks.sourcesJar { + into("META-INF/licenses/junit/junit") { + from(layout.projectDirectory) { + include("*-junit*") + } + } + into("") { + from(layout.projectDirectory.file("README.md")) + } +} + +publishing { + publications.withType { + pom { + licenses { + license { + // License for JUnit TemporaryFolder + name = "Eclipse Public License - v 1.0" + url = "https://www.eclipse.org/legal/epl-v10.html" + } + } + } + } +} + +tasks.rat { + exclude("README.md") + exclude("LICENSE-*") + exclude("NOTICE-*") + exclude("src/main/java/org/terracotta/org/junit/**") + exclude("src/test/java/org/terracotta/org/junit/**") +} diff --git a/test-tools/pom.xml b/test-tools/pom.xml index f0000b4..11a696a 100644 --- a/test-tools/pom.xml +++ b/test-tools/pom.xml @@ -48,6 +48,10 @@ org.slf4j slf4j-api + + com.github.spotbugs + spotbugs-annotations + ch.qos.logback @@ -110,6 +114,8 @@ apache-rat-plugin + build/** + build.gradle.kts README.md LICENSE-* diff --git a/test-tools/src/main/java/org/terracotta/utilities/test/ByteCodeVersion.java b/test-tools/src/main/java/org/terracotta/utilities/test/ByteCodeVersion.java new file mode 100644 index 0000000..762f9f2 --- /dev/null +++ b/test-tools/src/main/java/org/terracotta/utilities/test/ByteCodeVersion.java @@ -0,0 +1,147 @@ +/* + * Copyright 2023 Terracotta, Inc., a Software AG company. + * + * 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 + * + * http://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. + */ +package org.terracotta.utilities.test; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; + +import static java.util.Objects.requireNonNull; + +/** + * Represents the byte code version of a class. + */ +@SuppressWarnings("unused") +public class ByteCodeVersion { + + /** + * Java byte code version to language level mappings. + * + * @see + * Java Virtual Machine Specification / The class File Format / Table 4.1-A. class file format major versions + */ + private static final SortedMap VERSION_MAP; + static { + TreeMap versionMap = new TreeMap<>(); + versionMap.put(45, "1.1"); + versionMap.put(46, "1.2"); + versionMap.put(47, "1.3"); + versionMap.put(48, "1.4"); + versionMap.put(49, "5.0"); + versionMap.put(50, "6"); + versionMap.put(51, "7"); + versionMap.put(52, "8"); + versionMap.put(53, "9"); + versionMap.put(54, "10"); + versionMap.put(55, "11"); + versionMap.put(56, "12"); + versionMap.put(57, "13"); + versionMap.put(58, "14"); + versionMap.put(59, "15"); + versionMap.put(60, "16"); + versionMap.put(61, "17"); + versionMap.put(62, "18"); + versionMap.put(63, "19"); + versionMap.put(64, "20"); + versionMap.put(65, "21"); + VERSION_MAP = Collections.unmodifiableSortedMap(versionMap); + } + + private final int majorVersion; + private final int minorVersion; + + private ByteCodeVersion(short majorVersion, short minorVersion) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + } + + /** + * Determines the byte code version of the indicated class. + * + * @param clazz the class for which the byte code version is determined + * @return new {@code ByteCodeVersion} instance; {@code major} is -1 if the byte code version cannot be determined + */ + @SuppressFBWarnings(value = "DCN_NULLPOINTER_EXCEPTION", justification = "NPE from getResourceAsStream is handled quietly") + public static ByteCodeVersion fromClass(Class clazz) { + requireNonNull(clazz, "clazz"); + try (InputStream in = clazz.getClassLoader() + .getResourceAsStream(clazz.getName().replace('.', '/') + ".class"); + DataInputStream dataStream = new DataInputStream(requireNonNull(in, "in"))) { + int magic = dataStream.readInt(); + if (magic != 0xCAFEBABE) { + return new ByteCodeVersion((short)-1, (short)-1); + } + short minor_version = dataStream.readShort(); + short major_version = dataStream.readShort(); + return new ByteCodeVersion(major_version, minor_version); + } catch (NullPointerException e) { + return new ByteCodeVersion((short)-1, (short)-2); + } catch (IOException e) { + return new ByteCodeVersion((short)-1, (short)-3); + } + } + + /** + * Gets the major version number. + * @return the major version number + */ + public int majorVersion() { + return majorVersion; + } + + /** + * Gets the minor version number. + * @return the minor version number + */ + public int minorVersion() { + return minorVersion; + } + + /** + * Gets the Java language level at which the major.minor version was introduced. + *

+ * If the major version is outside the range recognized by this routine, the value returned + * is {@code "-"} for a major below the recognized values or {@code "+"} for above. + * @return the Java language level for this {@code ByteCodeVersion} + */ + public String languageLevel() { + String languageVersion = VERSION_MAP.get(majorVersion); + if (languageVersion == null) { + if (majorVersion > VERSION_MAP.lastKey()) { + languageVersion = VERSION_MAP.lastKey() + "+"; + } else if (majorVersion < VERSION_MAP.firstKey()) { + languageVersion = VERSION_MAP.firstKey() + "-"; + } else { + throw new AssertionError("major_version " + majorVersion + " not found in table"); + } + } + return languageVersion; + } + + @Override + public String toString() { + return "ByteCodeVersion{" + + "majorVersion=" + majorVersion + + ", minorVersion=" + minorVersion + + ", languageLevel='" + languageLevel() + '\'' + + '}'; + } +} diff --git a/tools/build.gradle.kts b/tools/build.gradle.kts new file mode 100644 index 0000000..55d2de8 --- /dev/null +++ b/tools/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + id("org.terracotta.java-conventions") +} + +val logbackRangeVersion: String by project + +description = "Terracotta Utilities Tools" + +dependencies { + testImplementation(project(":terracotta-utilities-test-tools")) + testImplementation("ch.qos.logback:logback-classic:${logbackRangeVersion}") +} + +tasks.rat { + exclude("src/test/resources/De_finibus_bonorum_et_malorum_Liber_Primus.txt") +} + +tasks.test { + systemProperties.put("dumputility.diagnostics.enable", System.getProperty("dumputility.diagnostics.enable")) + if (JavaVersion.current() >= JavaVersion.VERSION_17) { + // DumpUtilityTest needs add-opens option for complete testing under Java 17+. + jvmArgs("--add-opens=java.base/java.nio=ALL-UNNAMED") + } +} diff --git a/tools/pom.xml b/tools/pom.xml index 16cfee4..4798411 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -105,6 +105,8 @@ apache-rat-plugin + build/** + build.gradle.kts src/test/resources/De_finibus_bonorum_et_malorum_Liber_Primus.txt diff --git a/tools/src/test/java/org/terracotta/utilities/io/FilesCreateTest.java b/tools/src/test/java/org/terracotta/utilities/io/FilesCreateTest.java index 9a787f3..152c897 100644 --- a/tools/src/test/java/org/terracotta/utilities/io/FilesCreateTest.java +++ b/tools/src/test/java/org/terracotta/utilities/io/FilesCreateTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 Terracotta, Inc., a Software AG company. + * Copyright 2022-2023 Terracotta, Inc., a Software AG company. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,6 @@ import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; import static org.terracotta.utilities.test.matchers.ThrowsMatcher.threw; @@ -286,7 +285,10 @@ private Process spawn(String methodName, String... methodArguments) throws IOExc compile("-((cp)|(classpath))=.*"), compile(quote("-Dvisualvm.id=") + ".*"), compile(quote("-Didea.test.cyclic.buffer.size=") + ".*"), - compile("-javaagent:.*[/\\\\]idea_rt\\.jar=.*")) + compile("-javaagent:.*[/\\\\]idea_rt\\.jar=.*"), + compile("-Djava\\.security\\.manager=.*"), + compile("-Dorg\\.gradle\\.*") + ) .map(Pattern::asPredicate).collect(toSet()); List jvmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments().stream() .filter(a -> exclusions.stream().noneMatch(p -> p.test(a))) diff --git a/tools/src/test/java/org/terracotta/utilities/io/buffer/AbstractDumpUtilityTest.java b/tools/src/test/java/org/terracotta/utilities/io/buffer/AbstractDumpUtilityTest.java index f3fdc67..b41606e 100644 --- a/tools/src/test/java/org/terracotta/utilities/io/buffer/AbstractDumpUtilityTest.java +++ b/tools/src/test/java/org/terracotta/utilities/io/buffer/AbstractDumpUtilityTest.java @@ -18,10 +18,12 @@ import org.junit.Before; import org.junit.Rule; import org.junit.rules.TestName; +import org.terracotta.utilities.test.ByteCodeVersion; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UncheckedIOException; +import java.lang.management.ManagementFactory; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.List; @@ -67,6 +69,9 @@ public class AbstractDumpUtilityTest { public void prepare() { if (DIAGNOSTICS_ENABLED) { System.out.format("%n%s%n", testName.getMethodName()); + System.out.format("DumpUtility.class compiled with %s%n", ByteCodeVersion.fromClass(DumpUtility.class)); + System.out.format("Current JVM = %s%n", System.getProperty("java.runtime.version", System.getProperty("java.version"))); + System.out.format("JVM options: %s%n", ManagementFactory.getRuntimeMXBean().getInputArguments()); } }