getWebAppStartupFailureDetail() {
diff --git a/exist-core/src/main/java/org/exist/test/runner/XSuite.java b/exist-core/src/main/java/org/exist/test/runner/XSuite.java
deleted file mode 100644
index 46f1145bd1c..00000000000
--- a/exist-core/src/main/java/org/exist/test/runner/XSuite.java
+++ /dev/null
@@ -1,667 +0,0 @@
-/*
- * eXist-db Open Source Native XML Database
- * Copyright (C) 2001 The eXist-db Authors
- *
- * info@exist-db.org
- * http://www.exist-db.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-package org.exist.test.runner;
-
-import org.exist.storage.BrokerPool;
-import org.exist.test.ExistEmbeddedServer;
-import org.exist.util.XMLFilenameFilter;
-import org.exist.util.XQueryFilenameFilter;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.runner.Description;
-import org.junit.runner.Runner;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runners.ParentRunner;
-import org.junit.runners.model.*;
-
-import javax.annotation.Nullable;
-import java.io.IOException;
-import java.lang.annotation.*;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Using XSuite as a runner allows you to manually
- * build a suite containing tests from both:
- *
- * 1. XQSuite - as defined in $EXIST_HOME/src/org/exist/xquery/lib/xqsuite/xqsuite.xql
- * 2. XML Test - as defined in $EXIST_HOME/src/org/exist/xquery/lib/test.xq
- *
- * To use it, annotate a class
- * with @RunWith(XSuite.class) and @XSuiteClasses({"extensions/my-extension/src/test/xquery", ...}).
- * When you run this class, it will run all the tests in all the suite classes.
- *
- * @author Adam Retter
- */
-public class XSuite extends ParentRunner {
-
- private final List runners;
- @Nullable
- private final ParallelFileScheduler parallelScheduler;
-
- /**
- * Returns an empty suite.
- *
- * @return a runner for an empty suite.
- */
- public static Runner emptySuite() {
- try {
- return new XSuite((Class>) null, new String[0]);
- } catch (final InitializationError e) {
- throw new RuntimeException("This shouldn't be possible");
- }
- }
-
- /**
- * The XSuiteFiles annotation specifies the directory/file containing the tests to be run when a class
- * annotated with @RunWith(XSuite.class) is run.
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- @Inherited
- public @interface XSuiteFiles {
- /**
- * @return the classes to be run
- */
- String[] value();
- }
-
- /**
- * The XSuiteParallel annotation specifies that the tests will be run in parallel when a class
- * annotated with @RunWith(XSuite.class) is run.
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.TYPE)
- @Inherited
- public @interface XSuiteParallel {
- }
-
- private static String[] getAnnotatedDirectories(final Class> klass) throws InitializationError {
- final XSuite.XSuiteFiles annotation = klass.getAnnotation(XSuite.XSuiteFiles.class);
- if (annotation == null) {
- throw new InitializationError("class '%s' must have a XSuiteFiles annotation".formatted(klass.getName()));
- }
- return annotation.value();
- }
-
- private static boolean hasParallelAnnotation(@Nullable final Class> klass) {
- if (klass == null) {
- return false;
- }
- final XSuite.XSuiteParallel annotation = klass.getAnnotation(XSuite.XSuiteParallel.class);
- return annotation != null;
- }
-
- /**
- * Called reflectively on classes annotated with @RunWith(XSuite.class)
- *
- * @param klass the root class
- * @param builder builds runners for classes in the suite
- *
- * @throws InitializationError if the XSuite cannot be constructed
- */
- public XSuite(final Class> klass, final RunnerBuilder builder) throws InitializationError {
- this(builder, klass, getAnnotatedDirectories(klass));
- }
-
- /**
- * Call this when there is no single root class (for example, multiple class names
- * passed on the command line to {@link org.junit.runner.JUnitCore}
- *
- * @param builder builds runners for classes in the suite
- * @param suites the directories/files in the suite
- *
- * @throws InitializationError if the XSuite cannot be constructed
- */
- public XSuite(final RunnerBuilder builder, final String[] suites) throws InitializationError {
- this(builder, null, suites);
- }
-
- /**
- * Call this when the default builder is good enough. Left in for compatibility with JUnit 4.4.
- *
- * @param klass the root of the suite
- * @param suites the directories/files in the suite
- *
- * @throws InitializationError if the XSuite cannot be constructed
- */
- protected XSuite(final Class> klass, final String[] suites) throws InitializationError {
- this(null, klass, suites);
- }
-
- /**
- * Called by this class and subclasses once the classes making up the suite have been determined.
- *
- * @param builder builds runners for classes in the suite
- * @param klass the root of the suite
- * @param suites the directories/files in the suite
- *
- * @throws InitializationError if the XSuite cannot be constructed
- */
- protected XSuite(final RunnerBuilder builder, final Class> klass, final String[] suites) throws InitializationError {
- this(klass, getRunners(suites, hasParallelAnnotation(klass)));
- }
-
- /**
- * Called by this class and subclasses once the runners making up the suite have been determined.
- *
- * @param klass root of the suite
- * @param runners for each class in the suite, a {@link Runner}
- *
- * @throws InitializationError if the XSuite cannot be constructed
- */
- protected XSuite(final Class> klass, final List runners) throws InitializationError {
- super(klass);
- this.runners = Collections.unmodifiableList(runners);
- if (hasParallelAnnotation(klass) && !runners.isEmpty()) {
- final ParallelFileScheduler scheduler = new ParallelFileScheduler();
- setScheduler(scheduler);
- this.parallelScheduler = scheduler;
- } else {
- this.parallelScheduler = null;
- }
- }
-
- /**
- * Returns true if any of the suite paths is an XQuery file or a directory containing at least one XQuery file.
- * Used to decide whether to start the DB before getRunners (so discovery can run for XQueryTestRunner).
- */
- private static boolean hasAnyXQueryFiles(final String[] suites) throws IOException {
- if (suites == null) {
- return false;
- }
- final java.util.function.Predicate isXQueryFile = XQueryFilenameFilter.asPredicate();
- for (final String suite : suites) {
- final Path path = Path.of(suite);
- if (!Files.exists(path)) {
- continue;
- }
- if (Files.isDirectory(path)) {
- try (final Stream children = Files.list(path)) {
- final boolean hasXq = children.anyMatch(p -> !Files.isDirectory(p) && isXQueryFile.test(p) && !"runTests.xql".equals(p.getFileName().toString()));
- if (hasXq) {
- return true;
- }
- }
- } else if (isXQueryFile.test(path) && !"runTests.xql".equals(path.getFileName().toString())) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Starts the embedded DB if not already started. Idempotent.
- * Called before getRunners when the suite contains XQuery files so discovery can use the DB.
- */
- private static void ensureExistDbStarted() throws Throwable {
- if (EXIST_EMBEDDED_SERVER_CLASS_INSTANCE == null) {
- EXIST_EMBEDDED_SERVER_CLASS_INSTANCE = newExistDbServer();
- EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.startDb();
- }
- }
-
- /**
- * Get the runners for the suiteDirectories.
- *
- * @param suites/files the directories in the suite
- * @param parallel should a runner execute tests in parallel
- *
- * @throws InitializationError if the runners cannot be retrieved
- */
- private static List getRunners(final String[] suites, final boolean parallel) throws InitializationError {
- if(suites == null) {
- return Collections.emptyList();
- }
-
- try {
- if (hasAnyXQueryFiles(suites)) {
- try {
- ensureExistDbStarted();
- } catch (final Throwable t) {
- throw new InitializationError(t);
- }
- }
-
- final List runners = new ArrayList<>();
- for (final String suite : suites) {
-
- // if directory/file does not exist - throw an exception
- final Path path = Path.of(suite);
- if (!Files.exists(path)) {
- throw new InitializationError("XSuite does not exist: " + suite + ". path=" + path.toAbsolutePath());
- }
-
- if (Files.isDirectory(path)) {
- // directory of files of test(s)
- try (final Stream children = Files.list(path)) {
- final List sorted = children.collect(Collectors.toList());
- sorted.sort(Comparator.comparing(Path::toString));
- for (final Path child : sorted) {
- if (!Files.isDirectory(child)) {
- final Runner runner = getRunner(child, parallel);
- if (runner != null) {
- runners.add(runner);
- }
- }
- }
- }
- } else {
- // just a file of test(s)
- runners.add(getRunner(path, parallel));
- }
- }
-
- return runners;
- } catch (final IOException e) {
- throw new InitializationError(e);
- }
- }
-
- private static @Nullable Runner getRunner(final Path path, final boolean parallel) throws InitializationError {
- if(XMLFilenameFilter.asPredicate().test(path)) {
- return new XMLTestRunner(path, parallel);
- } else if(XQueryFilenameFilter.asPredicate().test(path) && !"runTests.xql".equals(path.getFileName().toString())) {
- return new XQueryTestRunner(path, parallel);
- } else {
- return null;
- }
- }
-
- @Override
- protected List getChildren() {
- return runners;
- }
-
- @Override
- protected Description describeChild(final Runner child) {
- return child.getDescription();
- }
-
- @Override
- protected void runChild(final Runner runner, final RunNotifier notifier) {
- if (parallelScheduler != null) {
- final ParallelFileScheduler parallel = parallelScheduler;
- parallel.prepareForRun(notifier);
- final Description runnerDesc = runner.getDescription();
- parallel.setCurrentRunner(runnerDesc);
- parallel.runnerStarted(runnerDesc, runner);
- try {
- runner.run(notifier);
- } finally {
- parallel.runnerFinished(runnerDesc);
- parallel.setCurrentRunner(null);
- }
- } else {
- runner.run(notifier);
- }
- }
-
- @Override
- protected Statement withBeforeClasses(final Statement statement) {
- // get @BeforeClass methods
- final List befores = getTestClass().getAnnotatedMethods(BeforeClass.class);
-
- // inject an eXist-db Server startup as though it were an @BeforeClass
- final Statement startExistDb = new StartExistDbStatement();
-
- return new RunXSuiteBefores(statement, startExistDb, befores, null);
- }
-
- @Override
- protected Statement withAfterClasses(final Statement statement) {
- // get @AfterClass methods
- final List afters = getTestClass().getAnnotatedMethods(AfterClass.class);
-
- // inject an eXist-db Server shutdown as though it were an @AfterClass
- final Statement stopExist = new StopExistDbStatement();
-
- return new RunXSuiteAfters(statement, stopExist, afters, null);
- }
-
- private static class RunXSuiteBefores extends Statement {
- private final Statement next;
- private final Object target;
- private final Statement beforeXSuite;
- private final List befores;
-
- public RunXSuiteBefores(final Statement next, final Statement beforeXSuite, final List befores, final Object target) {
- this.next = next;
- this.beforeXSuite = beforeXSuite;
- this.befores = befores;
- this.target = target;
- }
-
- @Override
- public void evaluate() throws Throwable {
- beforeXSuite.evaluate();
- for (final FrameworkMethod before : befores) {
- before.invokeExplosively(target);
- }
- next.evaluate();
- }
- }
-
- private static class RunXSuiteAfters extends Statement {
- private final Statement next;
- private final Object target;
- private final Statement afterXSuite;
- private final List afters;
-
- public RunXSuiteAfters(final Statement next, final Statement afterXSuite, final List afters, final Object target) {
- this.next = next;
- this.afterXSuite = afterXSuite;
- this.afters = afters;
- this.target = target;
- }
-
- @Override
- public void evaluate() throws Throwable {
- final List errors = new ArrayList<>();
- try {
- next.evaluate();
- } catch (final Throwable e) {
- errors.add(e);
- } finally {
- try {
- afterXSuite.evaluate();
- } catch (final Throwable e) {
- errors.add(e);
- }
- for (final FrameworkMethod after : afters) {
- try {
- after.invokeExplosively(target);
- } catch (final Throwable e) {
- errors.add(e);
- }
- }
- }
- MultipleFailureException.assertEmpty(errors);
- }
- }
-
- private static class StartExistDbStatement extends Statement {
- @Override
- public void evaluate() throws Throwable {
- if (EXIST_EMBEDDED_SERVER_CLASS_INSTANCE != null) {
- return;
- }
- EXIST_EMBEDDED_SERVER_CLASS_INSTANCE = newExistDbServer();
- EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.startDb();
- }
- }
-
- private static class StopExistDbStatement extends Statement {
- @Override
- public void evaluate() {
- if (EXIST_EMBEDDED_SERVER_CLASS_INSTANCE == null) {
- throw new IllegalStateException("EXIST_EMBEDDED_SERVER_CLASS_INSTANCE already stopped");
- }
- EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.stopDb();
- EXIST_EMBEDDED_SERVER_CLASS_INSTANCE = null;
- }
- }
-
- static ExistEmbeddedServer EXIST_EMBEDDED_SERVER_CLASS_INSTANCE = null;
-
- static ExistEmbeddedServer newExistDbServer() {
- return new ExistEmbeddedServer(true, true);
- }
-
- /**
- * Runs child runners (test files) in parallel using a fixed thread pool.
- * Pool size is dynamic (processors and eXist broker count) with optional override.
- * Progress-based hang detection reports which file hung when no activity is seen.
- * All children share the same single eXist instance; tests must not rely on execution order.
- */
- private static final class ParallelFileScheduler implements RunnerScheduler {
-
- private static final String PARALLEL_THREADS_PROPERTY = "xsuite.parallel.threads";
- private static final String HANG_THRESHOLD_PROPERTY = "xsuite.hang.threshold.minutes";
- private static final String HANG_WATCHER_INTERVAL_PROPERTY = "xsuite.hang.watcher.interval.seconds";
- private static final int POOL_FLOOR = 2;
- private static final int POOL_CAP = 32;
- private static final double DEFAULT_HANG_THRESHOLD_MINUTES = 5.0;
- private static final int DEFAULT_WATCHER_INTERVAL_SECONDS = 30;
-
- private volatile ExecutorService executor;
- private final Object executorLock = new Object();
-
- private final ConcurrentHashMap lastActivityByRunner = new ConcurrentHashMap<>();
- private final ConcurrentHashMap runnerLabelByDescription = new ConcurrentHashMap<>();
- private final ThreadLocal currentRunnerId = new ThreadLocal<>();
- private volatile RunNotifier notifier;
- private final AtomicBoolean notifierListenerAdded = new AtomicBoolean(false);
- private final AtomicBoolean hungReported = new AtomicBoolean(false);
- private volatile Thread watcherThread;
-
- private ExecutorService getOrCreateExecutor() {
- if (executor != null) {
- return executor;
- }
- synchronized (executorLock) {
- if (executor != null) {
- return executor;
- }
- final int size = computePoolSize();
- executor = Executors.newFixedThreadPool(size);
- startWatcher();
- return executor;
- }
- }
-
- private static int computePoolSize() {
- final String override = System.getProperty(PARALLEL_THREADS_PROPERTY);
- if (override != null && !override.isEmpty()) {
- try {
- return Math.max(POOL_FLOOR, Math.min(POOL_CAP, Integer.parseInt(override.trim())));
- } catch (final NumberFormatException ignored) {
- // fall through to dynamic
- }
- }
- final int processors = Runtime.getRuntime().availableProcessors();
- int brokerMax = processors;
- if (EXIST_EMBEDDED_SERVER_CLASS_INSTANCE != null) {
- try {
- final BrokerPool pool = EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.getBrokerPool();
- if (pool != null) {
- brokerMax = pool.getMax();
- }
- } catch (final Exception ignored) {
- // use processors only
- }
- }
- return Math.max(POOL_FLOOR, Math.min(POOL_CAP, Math.min(processors, brokerMax)));
- }
-
- void prepareForRun(final RunNotifier notifier) {
- this.notifier = notifier;
- if (notifierListenerAdded.compareAndSet(false, true)) {
- notifier.addListener(new RunListener() {
- @Override
- public void testStarted(final Description description) {
- recordActivity();
- }
-
- @Override
- public void testFinished(final Description description) {
- recordActivity();
- }
-
- @Override
- public void testFailure(final Failure failure) {
- recordActivity();
- }
-
- @Override
- public void testAssumptionFailure(final Failure failure) {
- recordActivity();
- }
-
- @Override
- public void testIgnored(final Description description) {
- recordActivity();
- }
-
- private void recordActivity() {
- final Description runnerDesc = currentRunnerId.get();
- if (runnerDesc != null) {
- lastActivityByRunner.put(runnerDesc, System.currentTimeMillis());
- }
- }
- });
- }
- }
-
- void setCurrentRunner(final Description description) {
- if (description == null) {
- currentRunnerId.remove();
- } else {
- currentRunnerId.set(description);
- }
- }
-
- void runnerStarted(final Description runnerDesc, final Runner runner) {
- lastActivityByRunner.put(runnerDesc, System.currentTimeMillis());
- if (runner instanceof AbstractTestRunner testRunner) {
- runnerLabelByDescription.put(runnerDesc, testRunner.getSourcePath().toAbsolutePath().toString());
- } else {
- runnerLabelByDescription.put(runnerDesc, runnerDesc.getDisplayName());
- }
- }
-
- void runnerFinished(final Description runnerDesc) {
- lastActivityByRunner.remove(runnerDesc);
- runnerLabelByDescription.remove(runnerDesc);
- }
-
- private void startWatcher() {
- watcherThread = new Thread(this::runWatcher, "xsuite-hang-watcher");
- watcherThread.setDaemon(false);
- watcherThread.start();
- }
-
- private static double hangThresholdMinutes() {
- final String v = System.getProperty(HANG_THRESHOLD_PROPERTY);
- if (v == null || v.isEmpty()) {
- return DEFAULT_HANG_THRESHOLD_MINUTES;
- }
- try {
- return Math.max(0.001, Double.parseDouble(v.trim()));
- } catch (final NumberFormatException e) {
- return DEFAULT_HANG_THRESHOLD_MINUTES;
- }
- }
-
- private static int watcherIntervalSeconds() {
- final String v = System.getProperty(HANG_WATCHER_INTERVAL_PROPERTY);
- if (v == null || v.isEmpty()) {
- return DEFAULT_WATCHER_INTERVAL_SECONDS;
- }
- try {
- return Math.max(1, Integer.parseInt(v.trim()));
- } catch (final NumberFormatException e) {
- return DEFAULT_WATCHER_INTERVAL_SECONDS;
- }
- }
-
- private void runWatcher() {
- final long thresholdMs = (long) (hangThresholdMinutes() * 60 * 1000);
- final long intervalMs = watcherIntervalSeconds() * 1000L;
- while (!Thread.currentThread().isInterrupted() && executor != null && !executor.isShutdown()) {
- try {
- Thread.sleep(intervalMs);
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- break;
- }
- if (hungReported.get()) {
- break;
- }
- final long now = System.currentTimeMillis();
- for (final var e : lastActivityByRunner.entrySet()) {
- if (now - e.getValue() > thresholdMs) {
- if (hungReported.compareAndSet(false, true)) {
- reportHung(e.getKey());
- final ExecutorService exec = executor;
- if (exec != null) {
- exec.shutdownNow();
- }
- }
- break;
- }
- }
- }
- }
-
- private void reportHung(final Description runnerDesc) {
- final RunNotifier n = notifier;
- if (n == null) {
- return;
- }
- final String label = runnerLabelByDescription.getOrDefault(runnerDesc, runnerDesc.getDisplayName());
- final AssertionError err = new AssertionError(
- "Test file appears hung (no activity for " + hangThresholdMinutes() + " minutes): " + label);
- n.fireTestFailure(new Failure(runnerDesc, err));
- }
-
- @Override
- public void schedule(final Runnable childStatement) {
- getOrCreateExecutor().submit(childStatement);
- }
-
- @Override
- public void finished() {
- final ExecutorService exec = executor;
- if (exec == null) {
- return;
- }
- exec.shutdown();
- try {
- if (hungReported.get()) {
- exec.awaitTermination(1, TimeUnit.MINUTES);
- } else {
- exec.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
- }
- } catch (final InterruptedException e) {
- exec.shutdownNow();
- Thread.currentThread().interrupt();
- }
- if (watcherThread != null) {
- watcherThread.interrupt();
- }
- }
- }
-}
diff --git a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java
index 66bfe297372..72ee3c63a5d 100644
--- a/exist-core/src/main/java/org/exist/xquery/XQueryContext.java
+++ b/exist-core/src/main/java/org/exist/xquery/XQueryContext.java
@@ -444,7 +444,7 @@ public class XQueryContext implements BinaryValueManager, Context {
private final Map staticDecimalFormats = hashMap(Tuple(UNNAMED_DECIMAL_FORMAT, DecimalFormat.UNNAMED));
- // Only used for testing, e.g. {@link org.exist.test.runner.XQueryTestRunner}.
+ // Only used for testing, e.g. {@code org.exist.test.runner.XQueryTestRunner}.
private Optional testRepository = Optional.empty();
/**
@@ -562,7 +562,7 @@ public void setHttpContext(@Nullable final HttpContext httpContext) {
/**
* Set the EXPath repository used for testing,
- * only should be called from {@link org.exist.test.runner.XQueryTestRunner}.
+ * only should be called from {@code org.exist.test.runner.XQueryTestRunner}.
*
* @param testRepository the EXPath repository to use for test execution.
*/
diff --git a/exist-core/src/test/java/org/exist/backup/SystemExportFiltersTest.java b/exist-core/src/test/java/org/exist/backup/SystemExportFiltersTest.java
index 61bfd0da7be..94d3df758b5 100644
--- a/exist-core/src/test/java/org/exist/backup/SystemExportFiltersTest.java
+++ b/exist-core/src/test/java/org/exist/backup/SystemExportFiltersTest.java
@@ -60,8 +60,9 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.exist.test.TestConstants.TEST_COLLECTION_URI;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+
/**
* @author Dmitriy Shabanov
@@ -79,7 +80,7 @@ public class SystemExportFiltersTest {
private static final XmldbURI doc02uri = TEST_COLLECTION_URI.append("test2.xml");
private static final XmldbURI doc03uri = TEST_COLLECTION_URI.append("test3.xml");
private static final XmldbURI doc11uri = TEST_COLLECTION_URI.append("test.binary");
-
+
private static final String XML1 = "";
private static final String XML1_BACKUP = "test";
private static final String XML2 =
@@ -172,7 +173,7 @@ public void exportImport() throws Exception {
assertEquals(XML3_PROPER, serializer(broker, doc));
doc = getDoc(broker, test, doc11uri.lastSegment());
- assertTrue(doc instanceof BinaryDocument);
+ assertInstanceOf(BinaryDocument.class, doc);
try (final InputStream is = broker.getBinaryResource(transaction, ((BinaryDocument)doc))) {
assertEquals(BINARY, InputStreamUtil.readString(is, UTF_8));
}
diff --git a/exist-core/src/test/java/org/exist/backup/SystemExportImportTest.java b/exist-core/src/test/java/org/exist/backup/SystemExportImportTest.java
index 87e545eeb4b..f235349c55b 100644
--- a/exist-core/src/test/java/org/exist/backup/SystemExportImportTest.java
+++ b/exist-core/src/test/java/org/exist/backup/SystemExportImportTest.java
@@ -54,8 +54,8 @@
import org.exist.storage.txn.Txn;
import static org.exist.test.TestConstants.TEST_COLLECTION_URI;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import org.exist.test.ExistEmbeddedServer;
import org.exist.util.LockException;
@@ -98,14 +98,14 @@ public class SystemExportImportTest {
private static XmldbURI doc03uri = TEST_COLLECTION_URI.append("test3.xml");
private static XmldbURI doc04uri = TEST_COLLECTION_URI.append("test4.xml");
private static XmldbURI doc11uri = TEST_COLLECTION_URI.append("test.binary");
-
+
private static String XML1 = "";
- private static String XML2 =
+ private static String XML2 =
"""
""";
- private static String XML2_PROPER =
+ private static String XML2_PROPER =
"""
@@ -177,7 +177,7 @@ public void exportImport(String apiName, boolean direct, boolean zip) throws EXi
assertEquals(XML4, serializer(broker, doc, withXmlDeclOutputProps));
doc = getDoc(broker, test, doc11uri.lastSegment());
- assertTrue(doc instanceof BinaryDocument);
+ assertInstanceOf(BinaryDocument.class, doc);
try (final InputStream is = broker.getBinaryResource(transaction, ((BinaryDocument)doc))) {
assertEquals(BINARY, InputStreamUtil.readString(is, UTF_8));
}
@@ -189,7 +189,7 @@ public void exportImport(String apiName, boolean direct, boolean zip) throws EXi
private DocumentImpl getDoc(final DBBroker broker, final Collection col, final XmldbURI uri) throws PermissionDeniedException {
final DocumentImpl doc = col.getDocument(broker, uri);
assertNotNull(doc);
-
+
return doc;
}
@@ -199,7 +199,7 @@ private DocumentImpl getDoc(final DBBroker broker, final Collection col, final X
contentsOutputProps.setProperty( OutputKeys.INDENT, "yes" );
contentsOutputProps.setProperty( EXistOutputKeys.OUTPUT_DOCTYPE, "yes" );
}
-
+
private String serializer(final DBBroker broker, final DocumentImpl document) throws SAXException {
return serializer(broker, document, contentsOutputProps);
}
diff --git a/exist-core/src/test/java/org/exist/backup/XMLDBRestoreTest.java b/exist-core/src/test/java/org/exist/backup/XMLDBRestoreTest.java
index 2f39b353479..afcdd3f2d6f 100644
--- a/exist-core/src/test/java/org/exist/backup/XMLDBRestoreTest.java
+++ b/exist-core/src/test/java/org/exist/backup/XMLDBRestoreTest.java
@@ -59,8 +59,8 @@
import static org.exist.util.FileUtils.withUnixSep;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
public class XMLDBRestoreTest {
@RegisterExtension
@@ -316,9 +316,9 @@ private void checkMediaType(final XmldbURI collectionUri, final DocInfo backupDo
final Collection collection = DatabaseManager.getCollection(XmldbURI.create(getBaseUri()).append(collectionUri).toString(), TestUtils.ADMIN_DB_USER, TestUtils.ADMIN_DB_PWD);
final Resource resource = collection.getResource(backupDocInfo.name);
if (backupDocInfo.type == MimeType.XML) {
- assertTrue(resource instanceof XMLResource);
+ assertInstanceOf(XMLResource.class, resource);
} else {
- assertTrue(resource instanceof BinaryResource);
+ assertInstanceOf(BinaryResource.class, resource);
}
if (backupDocInfo.mediaType != null) {
assertEquals(backupDocInfo.mediaType, ((EXistResource) resource).getMimeType());
diff --git a/exist-core/src/test/java/org/exist/collections/CollectionStoreTest.java b/exist-core/src/test/java/org/exist/collections/CollectionStoreTest.java
index c7609c33c8c..fa684c1730c 100644
--- a/exist-core/src/test/java/org/exist/collections/CollectionStoreTest.java
+++ b/exist-core/src/test/java/org/exist/collections/CollectionStoreTest.java
@@ -56,8 +56,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
public class CollectionStoreTest {
@RegisterExtension
@@ -133,7 +133,7 @@ private void storeBinary(final PreserveType preserveOnCopy) throws EXistExceptio
col.close();
if (lockedDoc != null) {
- assertTrue(lockedDoc.getDocument() instanceof BinaryDocument);
+ assertInstanceOf(BinaryDocument.class, lockedDoc.getDocument());
final BinaryDocument doc = (BinaryDocument)lockedDoc.getDocument();
final Try docContent = broker.withBinaryFile(transaction, doc, is ->
diff --git a/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java b/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java
index 4cfcbd136ef..a49ea6e7d24 100644
--- a/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java
+++ b/exist-core/src/test/java/org/exist/dom/memtree/DocumentImplTest.java
@@ -38,8 +38,9 @@
import java.io.InputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
@@ -69,7 +70,7 @@ public void checkNamespaces_xerces() throws IOException, ParserConfigurationExce
final Attr attr1 = (Attr)attrs.item(index++);
assertEquals(Node.ATTRIBUTE_NODE, attr1.getNodeType());
- assertTrue(attr1 instanceof AttrNSImpl);
+ assertInstanceOf(AttrNSImpl.class, attr1);
assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, attr1.getNamespaceURI());
assertNull(attr1.getPrefix());
assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr1.getLocalName());
@@ -78,7 +79,7 @@ public void checkNamespaces_xerces() throws IOException, ParserConfigurationExce
final Attr attr2 = (Attr)attrs.item(index++);
assertEquals(Node.ATTRIBUTE_NODE, attr2.getNodeType());
- assertTrue(attr2 instanceof AttrNSImpl);
+ assertInstanceOf(AttrNSImpl.class, attr2);
assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, attr2.getNamespaceURI());
assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr2.getPrefix());
assertEquals("repo", attr2.getLocalName());
@@ -134,7 +135,7 @@ public void checkNamespaces_exist() throws IOException, SAXException, ParserConf
final Attr attr1 = (Attr)attrs.item(index++);
assertEquals(NodeImpl.NAMESPACE_NODE, attr1.getNodeType());
- assertTrue(attr1 instanceof NamespaceNode);
+ assertInstanceOf(NamespaceNode.class, attr1);
assertEquals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, attr1.getNamespaceURI());
assertNull(attr1.getPrefix());
assertEquals(XMLConstants.XMLNS_ATTRIBUTE, attr1.getLocalName());
diff --git a/exist-core/src/test/java/org/exist/dom/memtree/MemtreeTest.java b/exist-core/src/test/java/org/exist/dom/memtree/MemtreeTest.java
index 247846def23..1b9282f7884 100644
--- a/exist-core/src/test/java/org/exist/dom/memtree/MemtreeTest.java
+++ b/exist-core/src/test/java/org/exist/dom/memtree/MemtreeTest.java
@@ -41,6 +41,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -388,33 +389,33 @@ public void getNode() throws IOException, ParserConfigurationException, SAXExcep
doc = parse(is);
}
- assertTrue(doc.getNode(0) instanceof DocumentImpl); // the document node
- assertTrue(doc.getNode(1) instanceof CommentImpl); //
- assertTrue(doc.getNode(2) instanceof ProcessingInstructionImpl); //
- assertTrue(doc.getNode(3) instanceof CommentImpl); //
- assertTrue(doc.getNode(4) instanceof ProcessingInstructionImpl); //
- assertTrue(doc.getNode(5) instanceof ElementImpl); // doc-element
- assertTrue(doc.getNode(6) instanceof TextImpl); // doc-element/text()[1]
- assertTrue(doc.getNode(7) instanceof CommentImpl); //
- assertTrue(doc.getNode(8) instanceof TextImpl); // doc-element/text()[2]
- assertTrue(doc.getNode(9) instanceof ElementImpl); // e1
- assertTrue(doc.getNode(10) instanceof TextImpl); // e1/text()[1]
- assertTrue(doc.getNode(11) instanceof ProcessingInstructionImpl); //
- assertTrue(doc.getNode(12) instanceof TextImpl); // e1/text()[2]
- assertTrue(doc.getNode(13) instanceof ElementImpl); // e1_1
- assertTrue(doc.getNode(14) instanceof TextImpl); // e1_1/text()[1]
- assertTrue(doc.getNode(15) instanceof TextImpl); // e1/text()[3]
- assertTrue(doc.getNode(16) instanceof ElementImpl); // e1_2
- assertTrue(doc.getNode(17) instanceof TextImpl); // e1_2/text()[1]
- assertTrue(doc.getNode(18) instanceof TextImpl); // e1/text()[4]
- assertTrue(doc.getNode(19) instanceof TextImpl); // doc-element/text()[3]
- assertTrue(doc.getNode(20) instanceof CommentImpl); //
- assertTrue(doc.getNode(21) instanceof TextImpl); // doc-element/text()[4]
- assertTrue(doc.getNode(22) instanceof ProcessingInstructionImpl); //
- assertTrue(doc.getNode(23) instanceof CommentImpl); //
- assertTrue(doc.getNode(24) instanceof ProcessingInstructionImpl); //
- assertTrue(doc.getNode(25) instanceof CommentImpl); //
- assertTrue(doc.getNode(26) instanceof ProcessingInstructionImpl); //
+ assertInstanceOf(DocumentImpl.class, doc.getNode(0)); // the document node
+ assertInstanceOf(CommentImpl.class, doc.getNode(1)); //
+ assertInstanceOf(ProcessingInstructionImpl.class, doc.getNode(2)); //
+ assertInstanceOf(CommentImpl.class, doc.getNode(3)); //
+ assertInstanceOf(ProcessingInstructionImpl.class, doc.getNode(4)); //
+ assertInstanceOf(ElementImpl.class, doc.getNode(5)); // doc-element
+ assertInstanceOf(TextImpl.class, doc.getNode(6)); // doc-element/text()[1]
+ assertInstanceOf(CommentImpl.class, doc.getNode(7)); //
+ assertInstanceOf(TextImpl.class, doc.getNode(8)); // doc-element/text()[2]
+ assertInstanceOf(ElementImpl.class, doc.getNode(9)); // e1
+ assertInstanceOf(TextImpl.class, doc.getNode(10)); // e1/text()[1]
+ assertInstanceOf(ProcessingInstructionImpl.class, doc.getNode(11)); //
+ assertInstanceOf(TextImpl.class, doc.getNode(12)); // e1/text()[2]
+ assertInstanceOf(ElementImpl.class, doc.getNode(13)); // e1_1
+ assertInstanceOf(TextImpl.class, doc.getNode(14)); // e1_1/text()[1]
+ assertInstanceOf(TextImpl.class, doc.getNode(15)); // e1/text()[3]
+ assertInstanceOf(ElementImpl.class, doc.getNode(16)); // e1_2
+ assertInstanceOf(TextImpl.class, doc.getNode(17)); // e1_2/text()[1]
+ assertInstanceOf(TextImpl.class, doc.getNode(18)); // e1/text()[4]
+ assertInstanceOf(TextImpl.class, doc.getNode(19)); // doc-element/text()[3]
+ assertInstanceOf(CommentImpl.class, doc.getNode(20)); //
+ assertInstanceOf(TextImpl.class, doc.getNode(21)); // doc-element/text()[4]
+ assertInstanceOf(ProcessingInstructionImpl.class, doc.getNode(22)); //
+ assertInstanceOf(CommentImpl.class, doc.getNode(23)); //
+ assertInstanceOf(ProcessingInstructionImpl.class, doc.getNode(24)); //
+ assertInstanceOf(CommentImpl.class, doc.getNode(25)); //
+ assertInstanceOf(ProcessingInstructionImpl.class, doc.getNode(26)); //
}
@Test
@@ -440,140 +441,140 @@ public void getChildNodes() throws IOException, ParserConfigurationException, SA
// children of
final Node commentBeforeDoc1 = docChildren.item(0);
- assertTrue(commentBeforeDoc1 instanceof CommentImpl);
+ assertInstanceOf(CommentImpl.class, commentBeforeDoc1);
assertEquals(0, commentBeforeDoc1.getChildNodes().getLength());
// children of
final Node piBeforeDoc1 = docChildren.item(1);
- assertTrue(piBeforeDoc1 instanceof ProcessingInstructionImpl);
+ assertInstanceOf(ProcessingInstructionImpl.class, piBeforeDoc1);
assertEquals(0, piBeforeDoc1.getChildNodes().getLength());
// children of
final Node commentBeforeDoc2 = docChildren.item(2);
- assertTrue(commentBeforeDoc2 instanceof CommentImpl);
+ assertInstanceOf(CommentImpl.class, commentBeforeDoc2);
assertEquals(0, commentBeforeDoc2.getChildNodes().getLength());
// children of
final Node piBeforeDoc2 = docChildren.item(3);
- assertTrue(piBeforeDoc2 instanceof ProcessingInstructionImpl);
+ assertInstanceOf(ProcessingInstructionImpl.class, piBeforeDoc2);
assertEquals(0, piBeforeDoc2.getChildNodes().getLength());
// children of doc-element
final Node docElement = docChildren.item(4);
- assertTrue(docElement instanceof ElementImpl);
+ assertInstanceOf(ElementImpl.class, docElement);
assertEquals("doc-element", docElement.getLocalName());
final NodeList docElementChildren = docElement.getChildNodes();
assertEquals(7, docElementChildren.getLength());
// children of doc-element/text()[1]
final Node docElementText1 = docElementChildren.item(0);
- assertTrue(docElementText1 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, docElementText1);
assertEquals(0, docElementText1.getChildNodes().getLength());
// children of
final Node commentBeforeE1 = docElementChildren.item(1);
- assertTrue(commentBeforeE1 instanceof CommentImpl);
+ assertInstanceOf(CommentImpl.class, commentBeforeE1);
assertEquals(0, commentBeforeE1.getChildNodes().getLength());
// children of doc-element/text()[2]
final Node docElementText2 = docElementChildren.item(2);
- assertTrue(docElementText2 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, docElementText2);
assertEquals(0, docElementText2.getChildNodes().getLength());
// children of e1
final Node e1 = docElementChildren.item(3);
- assertTrue(e1 instanceof ElementImpl);
+ assertInstanceOf(ElementImpl.class, e1);
assertEquals("e1", e1.getLocalName());
final NodeList e1Children = e1.getChildNodes();
assertEquals(7, e1Children.getLength());
// children of e1/text()[1]
final Node e1Text1 = e1Children.item(0);
- assertTrue(e1Text1 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, e1Text1);
assertEquals(0, e1Text1.getChildNodes().getLength());
// children of
final Node piBeforeE11 = e1Children.item(1);
- assertTrue(piBeforeE11 instanceof ProcessingInstructionImpl);
+ assertInstanceOf(ProcessingInstructionImpl.class, piBeforeE11);
assertEquals(0, piBeforeE11.getChildNodes().getLength());
// children of e1/text()[2]
final Node e1Text2 = e1Children.item(2);
- assertTrue(e1Text2 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, e1Text2);
assertEquals(0, e1Text2.getChildNodes().getLength());
// children of e1_1
final Node e11 = e1Children.item(3);
- assertTrue(e11 instanceof ElementImpl);
+ assertInstanceOf(ElementImpl.class, e11);
assertEquals("e1_1", e11.getLocalName());
final NodeList e11Children = e11.getChildNodes();
assertEquals(1, e11Children.getLength());
// children of e1_1/text()[1]
final Node e11Text1 = e11Children.item(0);
- assertTrue(e11Text1 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, e11Text1);
assertEquals(0, e11Text1.getChildNodes().getLength());
// children of e1/text()[2]
final Node e1Text3 = e1Children.item(4);
- assertTrue(e1Text3 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, e1Text3);
assertEquals(0, e1Text3.getChildNodes().getLength());
// children of e1_2
final Node e12 = e1Children.item(5);
- assertTrue(e12 instanceof ElementImpl);
+ assertInstanceOf(ElementImpl.class, e12);
assertEquals("e1_2", e12.getLocalName());
final NodeList e12Children = e12.getChildNodes();
assertEquals(1, e12Children.getLength());
// children of e1_2/text()[1]
final Node e12Text1 = e12Children.item(0);
- assertTrue(e12Text1 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, e12Text1);
assertEquals(0, e12Text1.getChildNodes().getLength());
// children of e1/text()[4]
final Node e1Text4 = e1Children.item(6);
- assertTrue(e1Text4 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, e1Text4);
assertEquals(0, e1Text4.getChildNodes().getLength());
// children of doc-element/text()[3]
final Node docElementText3 = docElementChildren.item(4);
- assertTrue(docElementText3 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, docElementText3);
assertEquals(0, docElementText3.getChildNodes().getLength());
// children of
final Node commentAfterE1 = docElementChildren.item(5);
- assertTrue(commentAfterE1 instanceof CommentImpl);
+ assertInstanceOf(CommentImpl.class, commentAfterE1);
assertEquals(0, commentAfterE1.getChildNodes().getLength());
// children of doc-element/text()[4]
final Node docElementText4 = docElementChildren.item(6);
- assertTrue(docElementText4 instanceof TextImpl);
+ assertInstanceOf(TextImpl.class, docElementText4);
assertEquals(0, docElementText4.getChildNodes().getLength());
// children of
final Node piAfterDoc1 = docChildren.item(5);
- assertTrue(piAfterDoc1 instanceof ProcessingInstructionImpl);
+ assertInstanceOf(ProcessingInstructionImpl.class, piAfterDoc1);
assertEquals(0, piAfterDoc1.getChildNodes().getLength());
// children of
final Node commentAfterDoc1 = docChildren.item(6);
- assertTrue(commentAfterDoc1 instanceof CommentImpl);
+ assertInstanceOf(CommentImpl.class, commentAfterDoc1);
assertEquals(0, commentAfterDoc1.getChildNodes().getLength());
// children of
final Node piAfterDoc2 = docChildren.item(7);
- assertTrue(piAfterDoc2 instanceof ProcessingInstructionImpl);
+ assertInstanceOf(ProcessingInstructionImpl.class, piAfterDoc2);
assertEquals(0, piAfterDoc2.getChildNodes().getLength());
// children of
final Node commentAfterDoc2 = docChildren.item(8);
- assertTrue(commentAfterDoc2 instanceof CommentImpl);
+ assertInstanceOf(CommentImpl.class, commentAfterDoc2);
assertEquals(0, commentAfterDoc2.getChildNodes().getLength());
// children of
final Node piAfterDoc3 = docChildren.item(9);
- assertTrue(piAfterDoc3 instanceof ProcessingInstructionImpl);
+ assertInstanceOf(ProcessingInstructionImpl.class, piAfterDoc3);
assertEquals(0, piAfterDoc3.getChildNodes().getLength());
}
diff --git a/exist-core/src/test/java/org/exist/source/SourceFactoryTest.java b/exist-core/src/test/java/org/exist/source/SourceFactoryTest.java
index 51b1c7d1d17..be3ddabe08f 100644
--- a/exist-core/src/test/java/org/exist/source/SourceFactoryTest.java
+++ b/exist-core/src/test/java/org/exist/source/SourceFactoryTest.java
@@ -38,9 +38,10 @@
import java.nio.file.Path;
import static org.easymock.EasyMock.*;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
@Execution(ExecutionMode.CONCURRENT)
public class SourceFactoryTest {
@@ -54,7 +55,7 @@ public void getSourceFromFile_contextAbsoluteFileUrl_locationAbsoluteUrl() throw
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof FileSource);
+ assertInstanceOf(FileSource.class, source);
assertEquals(new java.io.File(libraryUrl.toURI()).getAbsolutePath(), source.path());
}
@@ -67,7 +68,7 @@ public void getSourceFromFile_contextAbsoluteFile_locationAbsoluteFile() throws
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof FileSource);
+ assertInstanceOf(FileSource.class, source);
assertEquals(new java.io.File(libraryUrl.toURI()).getAbsolutePath(), source.path());
}
@@ -79,7 +80,7 @@ public void getSourceFromFile_contextAbsoluteFileUrl_locationRelative() throws I
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof FileSource);
+ assertInstanceOf(FileSource.class, source);
assertEquals(new java.io.File(getClass().getResource("library.xqm").toURI()).getAbsolutePath(), source.path());
}
@@ -91,7 +92,7 @@ public void getSourceFromFile_contextAbsoluteFile_locationRelative() throws IOEx
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof FileSource);
+ assertInstanceOf(FileSource.class, source);
assertEquals(new java.io.File(getClass().getResource("library.xqm").toURI()).getAbsolutePath(), source.path());
}
@@ -104,7 +105,7 @@ public void getSourceFromFile_contextAbsoluteDir_locationRelative() throws IOExc
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof FileSource);
+ assertInstanceOf(FileSource.class, source);
assertEquals(Path.of(getClass().getResource("library.xqm").toURI()).toString(), source.path());
}
@@ -115,7 +116,7 @@ public void getSourceFromResource_contextAbsoluteFileUrl_locationRelative() thro
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, source);
assertEquals(getClass().getResource("library.xqm").getFile(), source.path());
}
@@ -126,7 +127,7 @@ public void getSourceFromResource_contextAbsoluteFileUrl_locationAbsoluteUrl() t
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, source);
assertEquals(getClass().getResource("library.xqm").getFile(), source.path());
}
@@ -137,7 +138,7 @@ public void getSourceFromResource_contextAbsoluteFileUrl_locationRelativeUrl() t
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, source);
assertEquals(getClass().getResource("library.xqm").getFile(), source.path());
}
@@ -147,11 +148,11 @@ public void getSourceFromResource_contextAbsoluteFileUrl_locationRelativeUrl_bas
final String location = "library.xqm";
final Source mainSource = SourceFactory.getSource(null, "", contextPath, false);
- assertTrue(mainSource instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, mainSource);
final Source relativeSource = SourceFactory.getSource(null, ((ClassLoaderSource)mainSource).getSource(), location, false);
- assertTrue(relativeSource instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, relativeSource);
assertEquals(getClass().getResource(location).getFile(), relativeSource.path());
}
@@ -162,7 +163,7 @@ public void getSourceFromResource_contextFolderUrl_locationRelative() throws IOE
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, source);
assertEquals(getClass().getResource("library.xqm").getFile(), source.path());
}
@@ -173,7 +174,7 @@ public void getSourceFromResource_contextFolderUrl_locationAbsoluteUrl() throws
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, source);
assertEquals(getClass().getResource("library.xqm").getFile(), source.path());
}
@@ -184,7 +185,7 @@ public void getSourceFromResource_contextFolderUrl_locationRelativeUrl() throws
final Source source = SourceFactory.getSource(null, contextPath, location, false);
- assertTrue(source instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, source);
assertEquals(getClass().getResource("library.xqm").getFile(), source.path());
}
@@ -194,11 +195,11 @@ public void getSourceFromResource_contextFolderUrl_locationRelativeUrl_basedOnSo
final String location = "library.xqm";
final Source mainSource = SourceFactory.getSource(null, "", contextPath, false);
- assertTrue(mainSource instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, mainSource);
final Source relativeSource = SourceFactory.getSource(null, ((ClassLoaderSource)mainSource).getSource(), location, false);
- assertTrue(relativeSource instanceof ClassLoaderSource);
+ assertInstanceOf(ClassLoaderSource.class, relativeSource);
assertEquals(getClass().getResource(location).getFile(), relativeSource.path());
}
@@ -222,7 +223,7 @@ public void getSourceFromXmldb_noContext() throws IOException, PermissionDeniedE
replay(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
final Source libSource = SourceFactory.getSource(mockBroker, contextPath, location, false);
- assertTrue(libSource instanceof DBSource);
+ assertInstanceOf(DBSource.class, libSource);
assertEquals(XmldbURI.create(location), ((DBSource)libSource).getDocumentPath());
verify(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
@@ -248,7 +249,7 @@ public void getSourceFromXmldb() throws IOException, PermissionDeniedException {
replay(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
final Source libSource = SourceFactory.getSource(mockBroker, contextPath, location, false);
- assertTrue(libSource instanceof DBSource);
+ assertInstanceOf(DBSource.class, libSource);
assertEquals(XmldbURI.create(contextPath).append(location), ((DBSource)libSource).getDocumentPath());
verify(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
@@ -306,7 +307,7 @@ public void getSourceFromXmldbEmbedded_noContext() throws IOException, Permissio
replay(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
final Source libSource = SourceFactory.getSource(mockBroker, contextPath, location, false);
- assertTrue(libSource instanceof DBSource);
+ assertInstanceOf(DBSource.class, libSource);
assertEquals(XmldbURI.create(location), ((DBSource)libSource).getDocumentPath());
verify(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
@@ -332,7 +333,7 @@ public void getSourceFromXmldbEmbedded() throws IOException, PermissionDeniedExc
replay(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
final Source libSource = SourceFactory.getSource(mockBroker, contextPath, location, false);
- assertTrue(libSource instanceof DBSource);
+ assertInstanceOf(DBSource.class, libSource);
assertEquals(XmldbURI.create(contextPath).append(location), ((DBSource)libSource).getDocumentPath());
verify(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
@@ -390,7 +391,7 @@ public void getSourceFromDb() throws IOException, PermissionDeniedException {
replay(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
final Source libSource = SourceFactory.getSource(mockBroker, contextPath, location, false);
- assertTrue(libSource instanceof DBSource);
+ assertInstanceOf(DBSource.class, libSource);
assertEquals(XmldbURI.create(contextPath).append(location), ((DBSource)libSource).getDocumentPath());
verify(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
@@ -416,7 +417,7 @@ public void getSourceFromDb_noContext() throws IOException, PermissionDeniedExce
replay(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
final Source libSource = SourceFactory.getSource(mockBroker, contextPath, location, false);
- assertTrue(libSource instanceof DBSource);
+ assertInstanceOf(DBSource.class, libSource);
assertEquals(XmldbURI.create(location), ((DBSource)libSource).getDocumentPath());
verify(mockBrokerPool, mockBroker, mockLockedDoc, mockBinDoc);
diff --git a/exist-core/src/test/java/org/exist/storage/BrokerPoolsTest.java b/exist-core/src/test/java/org/exist/storage/BrokerPoolsTest.java
index 5a999932320..c987dfd5f03 100644
--- a/exist-core/src/test/java/org/exist/storage/BrokerPoolsTest.java
+++ b/exist-core/src/test/java/org/exist/storage/BrokerPoolsTest.java
@@ -35,7 +35,8 @@
import java.util.concurrent.*;
import java.nio.file.Files;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Patrick Reinhart
diff --git a/exist-core/src/test/java/org/exist/storage/ValueIndexFactoryTest.java b/exist-core/src/test/java/org/exist/storage/ValueIndexFactoryTest.java
index 91e82e611a6..6b7db8d961c 100644
--- a/exist-core/src/test/java/org/exist/storage/ValueIndexFactoryTest.java
+++ b/exist-core/src/test/java/org/exist/storage/ValueIndexFactoryTest.java
@@ -26,8 +26,10 @@
import org.exist.EXistException;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
+
import org.exist.storage.btree.Value;
import org.exist.xquery.value.DecimalValue;
import org.exist.xquery.value.DoubleValue;
@@ -82,7 +84,7 @@ public void roundTripDecimal() throws EXistException {
byte data[] = ValueIndexFactory.serialize(new DecimalValue(dec), 0);
Indexable value = ValueIndexFactory.deserialize(data, 0, data.length);
- assertTrue(value instanceof DecimalValue);
+ assertInstanceOf(DecimalValue.class, value);
assertEquals(dec, ((DecimalValue)value).getValue());
}
@@ -93,7 +95,7 @@ public void roundTripDouble() throws EXistException {
for (final double d : values) {
final byte[] data = ValueIndexFactory.serialize(new DoubleValue(d), 0);
final Indexable value = ValueIndexFactory.deserialize(data, 0, data.length);
- assertTrue(value instanceof DoubleValue);
+ assertInstanceOf(DoubleValue.class, value);
assertEquals(d, ((DoubleValue) value).getValue(), 0.0);
}
}
diff --git a/exist-core/src/test/java/org/exist/storage/journal/JournalBinaryTest.java b/exist-core/src/test/java/org/exist/storage/journal/JournalBinaryTest.java
index bfb46c1e8b2..7c8ae8c0e9a 100644
--- a/exist-core/src/test/java/org/exist/storage/journal/JournalBinaryTest.java
+++ b/exist-core/src/test/java/org/exist/storage/journal/JournalBinaryTest.java
@@ -65,8 +65,8 @@
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Test expectations to check that the correct entries
@@ -446,7 +446,7 @@ protected BinaryDocLocator storeAndVerify(final DBBroker broker, final Txn trans
final InputSource data, final String dbFilename) throws EXistException, PermissionDeniedException, IOException,
SAXException, LockException {
- assertTrue(data instanceof FileInputSource);
+ assertInstanceOf(FileInputSource.class, data);
broker.storeDocument(transaction, XmldbURI.create(dbFilename), data, MimeType.BINARY_TYPE, collection);
final BinaryDocument doc = (BinaryDocument) collection.getDocument(broker, XmldbURI.create(dbFilename));
diff --git a/exist-core/src/test/java/org/exist/storage/lock/GetXMLResourceNoLockTest.java b/exist-core/src/test/java/org/exist/storage/lock/GetXMLResourceNoLockTest.java
index 0709936e720..b4527b51d2f 100644
--- a/exist-core/src/test/java/org/exist/storage/lock/GetXMLResourceNoLockTest.java
+++ b/exist-core/src/test/java/org/exist/storage/lock/GetXMLResourceNoLockTest.java
@@ -45,7 +45,8 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
/**
*
diff --git a/exist-core/src/test/java/org/exist/storage/vector/VectorStoreImplTest.java b/exist-core/src/test/java/org/exist/storage/vector/VectorStoreImplTest.java
index 0901b278a69..2517cfb01db 100644
--- a/exist-core/src/test/java/org/exist/storage/vector/VectorStoreImplTest.java
+++ b/exist-core/src/test/java/org/exist/storage/vector/VectorStoreImplTest.java
@@ -31,6 +31,7 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class VectorStoreImplTest {
@@ -45,7 +46,7 @@ public void setUp() {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
final VectorStore vectorStore = pool.getVectorStore();
assertNotNull(vectorStore);
- assertTrue(vectorStore instanceof VectorStoreImpl);
+ assertInstanceOf(VectorStoreImpl.class, vectorStore);
store = (VectorStoreImpl) vectorStore;
store.resetEntryCountCache();
}
diff --git a/exist-core/src/main/java/org/exist/test/DiffMatcher.java b/exist-core/src/test/java/org/exist/test/DiffMatcher.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/DiffMatcher.java
rename to exist-core/src/test/java/org/exist/test/DiffMatcher.java
diff --git a/exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java b/exist-core/src/test/java/org/exist/test/ExistEmbeddedServer.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/ExistEmbeddedServer.java
rename to exist-core/src/test/java/org/exist/test/ExistEmbeddedServer.java
diff --git a/exist-core/src/main/java/org/exist/test/ExistWebServer.java b/exist-core/src/test/java/org/exist/test/ExistWebServer.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/ExistWebServer.java
rename to exist-core/src/test/java/org/exist/test/ExistWebServer.java
diff --git a/exist-core/src/main/java/org/exist/test/ExistXmldbEmbeddedServer.java b/exist-core/src/test/java/org/exist/test/ExistXmldbEmbeddedServer.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/ExistXmldbEmbeddedServer.java
rename to exist-core/src/test/java/org/exist/test/ExistXmldbEmbeddedServer.java
diff --git a/exist-core/src/main/java/org/exist/test/TestConstants.java b/exist-core/src/test/java/org/exist/test/TestConstants.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/TestConstants.java
rename to exist-core/src/test/java/org/exist/test/TestConstants.java
diff --git a/exist-core/src/main/java/org/exist/test/TransactionTestDSL.java b/exist-core/src/test/java/org/exist/test/TransactionTestDSL.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/TransactionTestDSL.java
rename to exist-core/src/test/java/org/exist/test/TransactionTestDSL.java
diff --git a/exist-core/src/main/java/org/exist/test/XQueryAssertions.java b/exist-core/src/test/java/org/exist/test/XQueryAssertions.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/XQueryAssertions.java
rename to exist-core/src/test/java/org/exist/test/XQueryAssertions.java
diff --git a/exist-core/src/main/java/org/exist/test/XQueryCompilationTest.java b/exist-core/src/test/java/org/exist/test/XQueryCompilationTest.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/XQueryCompilationTest.java
rename to exist-core/src/test/java/org/exist/test/XQueryCompilationTest.java
diff --git a/exist-core/src/main/java/org/exist/test/XmlStringDiffMatcher.java b/exist-core/src/test/java/org/exist/test/XmlStringDiffMatcher.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/XmlStringDiffMatcher.java
rename to exist-core/src/test/java/org/exist/test/XmlStringDiffMatcher.java
diff --git a/exist-core/src/main/java/org/exist/test/runner/AbstractTestRunner.java b/exist-core/src/test/java/org/exist/test/runner/AbstractTestRunner.java
similarity index 86%
rename from exist-core/src/main/java/org/exist/test/runner/AbstractTestRunner.java
rename to exist-core/src/test/java/org/exist/test/runner/AbstractTestRunner.java
index a79a1cb3ce0..33b5da54b69 100644
--- a/exist-core/src/main/java/org/exist/test/runner/AbstractTestRunner.java
+++ b/exist-core/src/test/java/org/exist/test/runner/AbstractTestRunner.java
@@ -38,7 +38,6 @@
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.AnyURIValue;
import org.exist.xquery.value.Sequence;
-import org.junit.runner.Runner;
import java.io.IOException;
import java.nio.file.Files;
@@ -50,18 +49,16 @@
import static java.util.Objects.requireNonNull;
/**
- * Base class for XSuite test runners.
+ * Base class for the runners of a file of XQSuite or XML tests.
*
* @author Adam Retter
*/
-public abstract class AbstractTestRunner extends Runner {
+public abstract class AbstractTestRunner {
protected final Path path;
- protected final boolean parallel;
- protected AbstractTestRunner(final Path path, final boolean parallel) {
+ protected AbstractTestRunner(final Path path) {
this.path = path;
- this.parallel = parallel;
}
/**
@@ -73,6 +70,24 @@ public Path getSourcePath() {
return path;
}
+ /**
+ * @return the name that groups the tests of this file
+ */
+ public abstract String getSuiteName();
+
+ /**
+ * @return the names of the tests that are expected to be reported when this file is run
+ */
+ public abstract List getTestNames();
+
+ /**
+ * Runs the tests of this file, reporting each outcome to {@code events}.
+ *
+ * @param events receives the outcome of each test
+ * @param brokerPool the running database to execute the tests against
+ */
+ public abstract void run(TestEvents events, BrokerPool brokerPool);
+
protected static Sequence executeQuery(final BrokerPool brokerPool, final Source query, final List>> externalVariableBindings) throws EXistException, PermissionDeniedException, XPathException, IOException, DatabaseConfigurationException {
return executeQuery(brokerPool, query, externalVariableBindings, null);
}
@@ -132,17 +147,4 @@ protected static Sequence executeQuery(final BrokerPool brokerPool, final Source
}
}
}
-
- protected static String checkDescription(final Object source, final String description) {
- if (description == null) {
- throw new IllegalArgumentException(source + " description is null");
- }
- if (description.isEmpty()) {
- throw new IllegalArgumentException(source + " description is empty");
- }
- if (description.startsWith("(")) {
- throw new IllegalArgumentException(source + " description '" + description + "' starts with '('");
- }
- return description;
- }
}
diff --git a/exist-core/src/test/java/org/exist/test/runner/AbstractTestRunnerTest.java b/exist-core/src/test/java/org/exist/test/runner/AbstractTestRunnerTest.java
deleted file mode 100644
index 38c47094abc..00000000000
--- a/exist-core/src/test/java/org/exist/test/runner/AbstractTestRunnerTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * eXist-db Open Source Native XML Database
- * Copyright (C) 2001 The eXist-db Authors
- *
- * info@exist-db.org
- * http://www.exist-db.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-package org.exist.test.runner;
-
-import org.junit.jupiter.api.Test;
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunNotifier;
-
-import static org.exist.test.runner.AbstractTestRunner.checkDescription;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Tests the global methods of the {@link AbstractTestRunner}.
- */
-class AbstractTestRunnerTest {
- @Test
- void testCheckDescriptionNull() {
- final IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> checkDescription(this, null));
- assertEquals(this + " description is null", iae.getMessage());
- }
-
- @Test
- void testCheckDescriptionEmpty() {
- final IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> checkDescription(this, ""));
- assertEquals(this + " description is empty", iae.getMessage());
- }
-
- @Test
- void testCheckDescriptionStartsWithBrace() {
- final IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> checkDescription(this, "(bla)"));
- assertEquals(this + " description '(bla)' starts with '('", iae.getMessage());
- }
-
- @Test
- void testCheckDescription() {
- assertEquals("testMethod", checkDescription(this, "testMethod"));
- }
-
- @Test
- void testRun() {
- final AbstractTestRunner runner = new AbstractTestRunner(null, false) {
- @Override
- public void run(final RunNotifier notifier) {
- // no action
- }
-
- @Override
- public Description getDescription() {
- return Description.createTestDescription(getClass(), "testRun");
- }
- };
-
- final RunNotifier notifier = new RunNotifier();
- assertDoesNotThrow(() -> runner.run(notifier));
- }
-}
diff --git a/exist-core/src/test/java/org/exist/test/runner/DebuggabilityNavigabilitySuite.java b/exist-core/src/test/java/org/exist/test/runner/DebuggabilityNavigabilitySuite.java
deleted file mode 100644
index 446621de572..00000000000
--- a/exist-core/src/test/java/org/exist/test/runner/DebuggabilityNavigabilitySuite.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * eXist-db Open Source Native XML Database
- * Copyright (C) 2001 The eXist-db Authors
- *
- * info@exist-db.org
- * http://www.exist-db.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-package org.exist.test.runner;
-
-import org.junit.runner.RunWith;
-
-/**
- * XSuite that runs the debuggability test resource (failing-both.xqm: one assertion failure,
- * one unexpected error) so we can assert on failure stack traces (navigate to XQuery / navigate to Java).
- */
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
- "src/test/resources/org/exist/test/runner/failing-both.xqm"
-})
-public class DebuggabilityNavigabilitySuite {
-}
diff --git a/exist-core/src/main/java/org/exist/test/runner/ExtTestAssumptionFailedFunction.java b/exist-core/src/test/java/org/exist/test/runner/ExtTestAssumptionFailedFunction.java
similarity index 72%
rename from exist-core/src/main/java/org/exist/test/runner/ExtTestAssumptionFailedFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/ExtTestAssumptionFailedFunction.java
index b107a37005f..4b5cb759b36 100644
--- a/exist-core/src/main/java/org/exist/test/runner/ExtTestAssumptionFailedFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/ExtTestAssumptionFailedFunction.java
@@ -29,22 +29,18 @@
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;
-import org.junit.AssumptionViolatedException;
-import org.junit.runner.Description;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
import static org.exist.xquery.FunctionDSL.optParam;
import static org.exist.xquery.FunctionDSL.param;
import static org.exist.xquery.FunctionDSL.params;
public class ExtTestAssumptionFailedFunction extends JUnitIntegrationFunction {
- public ExtTestAssumptionFailedFunction(final XQueryContext context, final String parentName, final RunNotifier notifier) {
+ public ExtTestAssumptionFailedFunction(final XQueryContext context, final String parentName, final TestEvents events) {
super("ext-test-assumption-failed-function",
params(
param("name", Type.STRING, "name of the test"),
optParam("error", Type.MAP_ITEM, "error detail of the test")
- ), context, parentName, notifier);
+ ), context, parentName, events);
}
@Override
@@ -55,25 +51,19 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
final Sequence arg2 = getCurrentArguments().length == 2 ? getCurrentArguments()[1] : null;
final MapType assumption = arg2 != null ? (MapType)arg2.itemAt(0) : null;
- final Description description = createTestDescription(name);
// notify JUnit
try {
- final AssumptionViolatedException assumptionFailureReason = assumptionMapAsAssumptionViolationException(assumption);
-
- // NOTE: We remove the StackTrace, because it is not useful to have a Java Stack Trace pointing into the XML XQuery Test Suite code
- assumptionFailureReason.setStackTrace(new StackTraceElement[0]);
-
- notifier.fireTestAssumptionFailed(new Failure(description, assumptionFailureReason));
+ events.assumptionFailed(name, assumptionMessage(assumption));
} catch (final XPathException e) {
//signal internal failure
- notifier.fireTestFailure(new Failure(description, e));
+ events.failed(name, e);
}
return Sequence.EMPTY_SEQUENCE;
}
- public AssumptionViolatedException assumptionMapAsAssumptionViolationException(final MapType assumptionMap) throws XPathException {
+ String assumptionMessage(final MapType assumptionMap) throws XPathException {
final Sequence seqName = assumptionMap.get(new StringValue(this, "name"));
final String name;
if(seqName != null && !seqName.isEmpty()) {
@@ -90,6 +80,6 @@ public AssumptionViolatedException assumptionMapAsAssumptionViolationException(f
value = "";
}
- return new AssumptionViolatedException("Assumption %" + name + " does not hold for: " + value);
+ return "Assumption %" + name + " does not hold for: " + value;
}
}
diff --git a/exist-core/src/main/java/org/exist/test/runner/ExtTestErrorFunction.java b/exist-core/src/test/java/org/exist/test/runner/ExtTestErrorFunction.java
similarity index 95%
rename from exist-core/src/main/java/org/exist/test/runner/ExtTestErrorFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/ExtTestErrorFunction.java
index c6c0b18a92a..9b0fcd040d0 100644
--- a/exist-core/src/main/java/org/exist/test/runner/ExtTestErrorFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/ExtTestErrorFunction.java
@@ -27,9 +27,6 @@
import org.exist.xquery.XQueryContext;
import org.exist.xquery.functions.map.MapType;
import org.exist.xquery.value.*;
-import org.junit.runner.Description;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
import javax.annotation.Nullable;
import java.util.Arrays;
@@ -40,12 +37,12 @@
public class ExtTestErrorFunction extends JUnitIntegrationFunction {
- public ExtTestErrorFunction(final XQueryContext context, final String parentName, final RunNotifier notifier) {
+ public ExtTestErrorFunction(final XQueryContext context, final String parentName, final TestEvents events) {
super("ext-test-error-function",
params(
param("name", Type.STRING, "name of the test"),
optParam("error", Type.MAP_ITEM, "error detail of the test. e.g. map { \"code\": $err:code, \"description\": $err:description, \"value\": $err:value, \"module\": $err:module, \"line-number\": $err:line-number, \"column-number\": $err:column-number, \"additional\": $err:additional, \"xquery-stack-trace\": $exerr:xquery-stack-trace, \"java-stack-trace\": $exerr:java-stack-trace}")
- ), context, parentName, notifier);
+ ), context, parentName, events);
}
@Override
@@ -56,16 +53,15 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
final Sequence arg2 = getCurrentArguments().length == 2 ? getCurrentArguments()[1] : null;
final MapType error = arg2 != null ? (MapType)arg2.itemAt(0) : null;
- final Description description = createTestDescription(name);
// notify JUnit
try {
final XPathException errorReason = errorMapAsXPathException(name, error);
logOneLineIfXQueryError(name, error);
- notifier.fireTestFailure(new Failure(description, errorReason));
+ events.failed(name, errorReason);
} catch (final XPathException e) {
//signal internal failure
- notifier.fireTestFailure(new Failure(description, e));
+ events.failed(name, e);
}
return Sequence.EMPTY_SEQUENCE;
diff --git a/exist-core/src/main/java/org/exist/test/runner/ExtTestFailureFunction.java b/exist-core/src/test/java/org/exist/test/runner/ExtTestFailureFunction.java
similarity index 93%
rename from exist-core/src/main/java/org/exist/test/runner/ExtTestFailureFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/ExtTestFailureFunction.java
index 92147fc3b3a..57ca4e30c3a 100644
--- a/exist-core/src/main/java/org/exist/test/runner/ExtTestFailureFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/ExtTestFailureFunction.java
@@ -32,9 +32,6 @@
import org.exist.xquery.value.SequenceIterator;
import org.exist.xquery.value.StringValue;
import org.exist.xquery.value.Type;
-import org.junit.runner.Description;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
import org.opentest4j.AssertionFailedError;
import org.xml.sax.SAXException;
@@ -53,17 +50,17 @@ public class ExtTestFailureFunction extends JUnitIntegrationFunction {
@Nullable
private final Path sourcePath;
- public ExtTestFailureFunction(final XQueryContext context, final String parentName, final RunNotifier notifier) {
- this(context, parentName, notifier, null);
+ public ExtTestFailureFunction(final XQueryContext context, final String parentName, final TestEvents events) {
+ this(context, parentName, events, null);
}
- public ExtTestFailureFunction(final XQueryContext context, final String parentName, final RunNotifier notifier, @Nullable final Path sourcePath) {
+ public ExtTestFailureFunction(final XQueryContext context, final String parentName, final TestEvents events, @Nullable final Path sourcePath) {
super("ext-test-failure-function",
params(
param("name", Type.STRING, "name of the test"),
param("expected", Type.MAP_ITEM, "expected result of the test"),
param("actual", Type.MAP_ITEM, "actual result of the test")
- ), context, parentName, notifier);
+ ), context, parentName, events);
this.sourcePath = sourcePath;
}
@@ -78,7 +75,6 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
final Sequence arg3 = getCurrentArguments()[2];
final MapType actual = (MapType)arg3.itemAt(0);
- final Description description = createTestDescription(name);
// notify JUnit
try {
@@ -104,10 +100,10 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
failureReason.setStackTrace(new StackTraceElement[0]);
}
- notifier.fireTestFailure(new Failure(description, failureReason));
+ events.failed(name, failureReason);
} catch (final XPathException | SAXException | IOException | IllegalStateException e) {
//signal internal failure
- notifier.fireTestFailure(new Failure(description, e));
+ events.failed(name, e);
}
return Sequence.EMPTY_SEQUENCE;
diff --git a/exist-core/src/main/java/org/exist/test/runner/ExtTestFinishedFunction.java b/exist-core/src/test/java/org/exist/test/runner/ExtTestFinishedFunction.java
similarity index 82%
rename from exist-core/src/main/java/org/exist/test/runner/ExtTestFinishedFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/ExtTestFinishedFunction.java
index b3018742bb8..83510fda17f 100644
--- a/exist-core/src/main/java/org/exist/test/runner/ExtTestFinishedFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/ExtTestFinishedFunction.java
@@ -27,16 +27,14 @@
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunNotifier;
import static org.exist.xquery.FunctionDSL.param;
import static org.exist.xquery.FunctionDSL.params;
public class ExtTestFinishedFunction extends JUnitIntegrationFunction {
- public ExtTestFinishedFunction(final XQueryContext context, final String parentName, final RunNotifier notifier) {
- super("ext-test-finished-function", params(param("name", Type.STRING, "name of the test")), context, parentName, notifier);
+ public ExtTestFinishedFunction(final XQueryContext context, final String parentName, final TestEvents events) {
+ super("ext-test-finished-function", params(param("name", Type.STRING, "name of the test")), context, parentName, events);
}
@Override
@@ -44,9 +42,7 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
final Sequence arg1 = getCurrentArguments()[0];
final String name = arg1.itemAt(0).getStringValue();
- // notify JUnit
- final Description description = createTestDescription(name);
- notifier.fireTestFinished(description);
+ events.finished(name);
return Sequence.EMPTY_SEQUENCE;
}
diff --git a/exist-core/src/main/java/org/exist/test/runner/ExtTestIgnoredFunction.java b/exist-core/src/test/java/org/exist/test/runner/ExtTestIgnoredFunction.java
similarity index 83%
rename from exist-core/src/main/java/org/exist/test/runner/ExtTestIgnoredFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/ExtTestIgnoredFunction.java
index fe9c9525cb6..ee5b0d1dd96 100644
--- a/exist-core/src/main/java/org/exist/test/runner/ExtTestIgnoredFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/ExtTestIgnoredFunction.java
@@ -27,16 +27,14 @@
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunNotifier;
import static org.exist.xquery.FunctionDSL.param;
import static org.exist.xquery.FunctionDSL.params;
public class ExtTestIgnoredFunction extends JUnitIntegrationFunction {
- public ExtTestIgnoredFunction(final XQueryContext context, final String parentName, final RunNotifier notifier) {
- super("ext-test-ignored-function", params(param("name", Type.STRING, "name of the test")), context, parentName, notifier);
+ public ExtTestIgnoredFunction(final XQueryContext context, final String parentName, final TestEvents events) {
+ super("ext-test-ignored-function", params(param("name", Type.STRING, "name of the test")), context, parentName, events);
}
@Override
@@ -44,9 +42,7 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
final Sequence arg1 = getCurrentArguments()[0];
final String name = arg1.itemAt(0).getStringValue();
- // notify JUnit
- final Description description = createTestDescription(name);
- notifier.fireTestIgnored(description);
+ events.ignored(name);
return Sequence.EMPTY_SEQUENCE;
}
diff --git a/exist-core/src/main/java/org/exist/test/runner/ExtTestStartedFunction.java b/exist-core/src/test/java/org/exist/test/runner/ExtTestStartedFunction.java
similarity index 83%
rename from exist-core/src/main/java/org/exist/test/runner/ExtTestStartedFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/ExtTestStartedFunction.java
index c962b35ed6d..eca994b0c28 100644
--- a/exist-core/src/main/java/org/exist/test/runner/ExtTestStartedFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/ExtTestStartedFunction.java
@@ -27,16 +27,14 @@
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunNotifier;
import static org.exist.xquery.FunctionDSL.param;
import static org.exist.xquery.FunctionDSL.params;
public class ExtTestStartedFunction extends JUnitIntegrationFunction {
- public ExtTestStartedFunction(final XQueryContext context, final String parentName, final RunNotifier notifier) {
- super("ext-test-started-function", params(param("name", Type.STRING, "name of the test")), context, parentName, notifier);
+ public ExtTestStartedFunction(final XQueryContext context, final String parentName, final TestEvents events) {
+ super("ext-test-started-function", params(param("name", Type.STRING, "name of the test")), context, parentName, events);
}
@Override
@@ -44,9 +42,7 @@ public Sequence eval(final Sequence contextSequence, final Item contextItem) thr
final Sequence arg1 = getCurrentArguments()[0];
final String name = arg1.itemAt(0).getStringValue();
- // notify JUnit
- final Description description = createTestDescription(name);
- notifier.fireTestStarted(description);
+ events.started(name);
return Sequence.EMPTY_SEQUENCE;
}
diff --git a/exist-core/src/main/java/org/exist/test/runner/JUnitIntegrationFunction.java b/exist-core/src/test/java/org/exist/test/runner/JUnitIntegrationFunction.java
similarity index 75%
rename from exist-core/src/main/java/org/exist/test/runner/JUnitIntegrationFunction.java
rename to exist-core/src/test/java/org/exist/test/runner/JUnitIntegrationFunction.java
index fa24c4bc9d2..0b59a50e8f0 100644
--- a/exist-core/src/main/java/org/exist/test/runner/JUnitIntegrationFunction.java
+++ b/exist-core/src/test/java/org/exist/test/runner/JUnitIntegrationFunction.java
@@ -27,8 +27,6 @@
import org.exist.xquery.UserDefinedFunction;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.FunctionParameterSequenceType;
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunNotifier;
import javax.xml.XMLConstants;
@@ -36,25 +34,25 @@
import static org.exist.xquery.FunctionDSL.returnsNothing;
/**
- * Base class for XQuery functions that integrate with JUnit.
+ * Base class for XQuery functions that report test outcomes to a {@link TestEvents} sink.
*
* @author Adam Retter
*/
public abstract class JUnitIntegrationFunction extends UserDefinedFunction {
protected final String suiteName;
- protected final RunNotifier notifier;
+ protected final TestEvents events;
- public JUnitIntegrationFunction(final String functionName, final FunctionParameterSequenceType[] paramTypes, final XQueryContext context, final String suiteName, final RunNotifier notifier) {
+ public JUnitIntegrationFunction(final String functionName, final FunctionParameterSequenceType[] paramTypes, final XQueryContext context, final String suiteName, final TestEvents events) {
super(context,
functionSignature(
new QName(functionName, XMLConstants.NULL_NS_URI),
- "External JUnit integration function",
+ "External test integration function",
returnsNothing(),
paramTypes
));
this.suiteName = suiteName;
- this.notifier = notifier;
+ this.events = events;
}
@Override
@@ -65,14 +63,4 @@ public void accept(final ExpressionVisitor visitor) {
visited = true;
}
- /**
- * Create a JUnit description of the test.
- *
- * @param name the name of the test.
- *
- * @return the test description.
- */
- protected Description createTestDescription(final String name) {
- return Description.createTestDescription(suiteName, name);
- }
}
diff --git a/exist-core/src/test/java/org/exist/test/runner/XSuiteDiscoveryTest.java b/exist-core/src/test/java/org/exist/test/runner/TestDiscoveryTest.java
similarity index 50%
rename from exist-core/src/test/java/org/exist/test/runner/XSuiteDiscoveryTest.java
rename to exist-core/src/test/java/org/exist/test/runner/TestDiscoveryTest.java
index 1a7d4261902..4a455a29860 100644
--- a/exist-core/src/test/java/org/exist/test/runner/XSuiteDiscoveryTest.java
+++ b/exist-core/src/test/java/org/exist/test/runner/TestDiscoveryTest.java
@@ -19,29 +19,25 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-
package org.exist.test.runner;
import org.exist.storage.BrokerPool;
import org.exist.test.ExistEmbeddedServer;
import org.junit.jupiter.api.Test;
-import org.junit.runner.Description;
-import org.junit.runners.model.InitializationError;
+import org.junit.jupiter.api.extension.RegisterExtension;
import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.List;
-import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
- * Tests discovery via a single XQuery per file.
- * Asserts that the discovery XQuery returns the expected test list for known files,
- * and that XQueryTestRunner uses discovery when the DB is up and runs the same tests.
+ * Tests discovery of XQSuite tests, either by one discovery XQuery on a running database or by compiling the module.
*/
-public class XSuiteDiscoveryTest {
+public class TestDiscoveryTest {
+
+ private static final String RESOURCES = "src/test/resources/org/exist/test/runner/";
@RegisterExtension
public ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true);
@@ -49,8 +45,9 @@ public class XSuiteDiscoveryTest {
@Test
public void discoveryReturnsTestListForSingleTestFile() {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
- final Path path = Path.of("src/test/resources/org/exist/test/runner/single-test.xqm").toAbsolutePath();
+ final Path path = Path.of(RESOURCES + "single-test.xqm").toAbsolutePath();
final XQueryTestRunner.XQueryTestInfo info = XQueryTestRunner.runDiscovery(pool, path);
+
assertNotNull(info, "discovery XQuery should return test info");
assertEquals("http://exist-db.org/xquery/single-test-module", info.namespace(), "namespace");
assertEquals(1, info.testFunctions().size(), "one test function");
@@ -59,32 +56,17 @@ public void discoveryReturnsTestListForSingleTestFile() {
}
/**
- * When the DB is up (XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE set), XQueryTestRunner should use
- * discovery and the runner's description should match the discovery result.
+ * Both ways of discovering tests must name them as the XQSuite runtime reports them, or a test
+ * would be discovered under one name and reported under another.
*/
@Test
- public void runnerUsesDiscoveryWhenDbIsUp() throws InitializationError {
- final Path path = Path.of("src/test/resources/org/exist/test/runner/single-test.xqm").toAbsolutePath();
+ public void discoveringWithTheDatabaseAndByCompilingGiveTheSameNames() throws TestInitializationException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
- final XQueryTestRunner.XQueryTestInfo discoveryInfo = XQueryTestRunner.runDiscovery(pool, path);
- assertNotNull(discoveryInfo, "discovery must succeed in this test");
-
- final ExistEmbeddedServer previous = XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE;
- try {
- XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE = existEmbeddedServer;
- final XQueryTestRunner runner = new XQueryTestRunner(path, false);
- final Description description = runner.getDescription();
- final List children = description.getChildren();
- assertEquals(discoveryInfo.testFunctions().size(), children.size(), "runner should have same number of tests as discovery");
- final List childNames = new ArrayList<>();
- for (final Description d : children) {
- childNames.add(d.getMethodName());
- }
- for (int i = 0; i < discoveryInfo.testFunctions().size(); i++) {
- assertEquals(discoveryInfo.testFunctions().get(i).localName(), childNames.get(i), "test name from runner should match discovery");
- }
- } finally {
- XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE = previous;
+ for (final String file : List.of("single-test.xqm", "hyphenated-prefix.xqm")) {
+ final Path path = Path.of(RESOURCES + file).toAbsolutePath();
+ final List viaDatabase = new XQueryTestRunner(path, pool).getTestNames();
+ final List viaCompiling = new XQueryTestRunner(path, null).getTestNames();
+ assertEquals(viaCompiling, viaDatabase, "test names discovered for " + file);
}
}
}
diff --git a/exist-core/src/test/java/org/exist/test/runner/ParallelWithHungSuite.java b/exist-core/src/test/java/org/exist/test/runner/TestEvents.java
similarity index 58%
rename from exist-core/src/test/java/org/exist/test/runner/ParallelWithHungSuite.java
rename to exist-core/src/test/java/org/exist/test/runner/TestEvents.java
index 531d35ad85c..6a85f856ac8 100644
--- a/exist-core/src/test/java/org/exist/test/runner/ParallelWithHungSuite.java
+++ b/exist-core/src/test/java/org/exist/test/runner/TestEvents.java
@@ -19,20 +19,28 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-
package org.exist.test.runner;
-import org.junit.runner.RunWith;
-
/**
- * XSuite that runs one passing file and one file that never emits activity (hanging.xqm).
- * Used by XSuiteParallelTest to verify progress-based hang detection reports the hung file.
+ * Receives the outcome of each test as the XQuery side of an XQSuite or XML test
+ * run reports it. Decouples the XQuery callback functions from any particular
+ * test framework, such as the JUnit Platform engine for XQSuite tests.
*/
-@RunWith(XSuite.class)
-@XSuite.XSuiteParallel
-@XSuite.XSuiteFiles({
- "src/test/resources/org/exist/test/runner/single-test.xqm",
- "src/test/resources/org/exist/test/runner/hanging.xqm"
-})
-public class ParallelWithHungSuite {
+public interface TestEvents {
+
+ void started(String testName);
+
+ void finished(String testName);
+
+ void ignored(String testName);
+
+ /**
+ * An assertion failure or an error raised while running the test.
+ */
+ void failed(String testName, Throwable reason);
+
+ /**
+ * The test's assumptions did not hold, so it is aborted rather than failed.
+ */
+ void assumptionFailed(String testName, String message);
}
diff --git a/exist-core/src/test/java/org/exist/test/runner/SerializationFailureMessageSuite.java b/exist-core/src/test/java/org/exist/test/runner/TestInitializationException.java
similarity index 70%
rename from exist-core/src/test/java/org/exist/test/runner/SerializationFailureMessageSuite.java
rename to exist-core/src/test/java/org/exist/test/runner/TestInitializationException.java
index fd91a39846d..ce8c0702712 100644
--- a/exist-core/src/test/java/org/exist/test/runner/SerializationFailureMessageSuite.java
+++ b/exist-core/src/test/java/org/exist/test/runner/TestInitializationException.java
@@ -19,18 +19,18 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-
package org.exist.test.runner;
-import org.junit.runner.RunWith;
-
/**
- * XSuite that runs {@code failing-serialization.xqm} (a single assertion failure whose result is a
- * node) so we can assert on how the actual value is rendered into the failure message.
+ * A file of tests could not be read or compiled, so its tests cannot be discovered.
*/
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
- "src/test/resources/org/exist/test/runner/failing-serialization.xqm"
-})
-public class SerializationFailureMessageSuite {
+public class TestInitializationException extends Exception {
+
+ public TestInitializationException(final String message) {
+ super(message);
+ }
+
+ public TestInitializationException(final Throwable cause) {
+ super(cause);
+ }
}
diff --git a/exist-core/src/test/java/org/exist/test/runner/TestRunners.java b/exist-core/src/test/java/org/exist/test/runner/TestRunners.java
new file mode 100644
index 00000000000..511b3ba3323
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/runner/TestRunners.java
@@ -0,0 +1,67 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.runner;
+
+import org.exist.storage.BrokerPool;
+import org.exist.util.XMLFilenameFilter;
+import org.exist.util.XQueryFilenameFilter;
+
+import javax.annotation.Nullable;
+import java.nio.file.Path;
+
+/**
+ * Creates the runner for a file of XQSuite or XML tests.
+ */
+public final class TestRunners {
+
+ private TestRunners() {
+ }
+
+ /**
+ * @param path a file in a suite
+ *
+ * @return true if the file is an XQuery file of tests
+ */
+ public static boolean isXQueryTestFile(final Path path) {
+ return XQueryFilenameFilter.asPredicate().test(path) && !"runTests.xql".equals(path.getFileName().toString());
+ }
+
+ /**
+ * Creates the test runner for a file of tests.
+ *
+ * @param path the XQuery or XML test file
+ * @param discoveryPool a running database to discover XQuery tests with, or null to compile the module instead
+ *
+ * @return the runner, or null if the file is not a test file
+ *
+ * @throws TestInitializationException if the file cannot be read as a test file
+ */
+ public static @Nullable AbstractTestRunner newTestRunner(final Path path, @Nullable final BrokerPool discoveryPool) throws TestInitializationException {
+ if (XMLFilenameFilter.asPredicate().test(path)) {
+ return new XMLTestRunner(path);
+ } else if (isXQueryTestFile(path)) {
+ return new XQueryTestRunner(path, discoveryPool);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/exist-core/src/main/java/org/exist/test/runner/XMLTestRunner.java b/exist-core/src/test/java/org/exist/test/runner/XMLTestRunner.java
similarity index 78%
rename from exist-core/src/main/java/org/exist/test/runner/XMLTestRunner.java
rename to exist-core/src/test/java/org/exist/test/runner/XMLTestRunner.java
index 5496faba086..c07988c5f18 100644
--- a/exist-core/src/main/java/org/exist/test/runner/XMLTestRunner.java
+++ b/exist-core/src/test/java/org/exist/test/runner/XMLTestRunner.java
@@ -38,9 +38,6 @@
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.FunctionReference;
import org.exist.xquery.value.Sequence;
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runners.model.InitializationError;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@@ -80,27 +77,27 @@ public class XMLTestRunner extends AbstractTestRunner {
/**
* @param path The path to the XML file containing the tests.
* @param parallel whether the tests should be run in parallel.
- * @throws InitializationError if the test runner could not be constructed.
+ * @throws TestInitializationException if the test runner could not be constructed.
*/
- XMLTestRunner(final Path path, final boolean parallel) throws InitializationError {
- super(path, parallel);
+ XMLTestRunner(final Path path) throws TestInitializationException {
+ super(path);
try {
this.doc = parse(path);
} catch (final ParserConfigurationException | IOException | SAXException e) {
- throw new InitializationError(e);
+ throw new TestInitializationException(e);
}
this.info = extractTestInfo(path, doc);
}
- private static XMLTestInfo extractTestInfo(final Path path, final Document doc) throws InitializationError {
+ private static XMLTestInfo extractTestInfo(final Path path, final Document doc) throws TestInitializationException {
String testSetName = null;
String description = null;
final List testNames = new ArrayList<>();
final Element docElement = doc.getDocumentElement();
if(docElement == null) {
- throw new InitializationError("Invalid XML test document: " + path.toAbsolutePath());
+ throw new TestInitializationException("Invalid XML test document: " + path.toAbsolutePath());
}
final NodeList children = docElement.getChildNodes();
@@ -121,7 +118,7 @@ private static XMLTestInfo extractTestInfo(final Path path, final Document doc)
testName = getTaskText(child);
}
if (testName == null) {
- throw new InitializationError("Could not find @id or within of XML document:" + path.toAbsolutePath());
+ throw new TestInitializationException("Could not find @id or within of XML document:" + path.toAbsolutePath());
}
testNames.add(testName);
break;
@@ -134,15 +131,16 @@ private static XMLTestInfo extractTestInfo(final Path path, final Document doc)
}
if (testSetName == null) {
- throw new InitializationError("Could not find in XML document: " + path.toAbsolutePath());
+ throw new TestInitializationException("Could not find in XML document: " + path.toAbsolutePath());
}
return new XMLTestInfo(testSetName, description, testNames);
}
private static @Nullable String getIdValue(final Node test) {
+ // as the runtime does: ($test/@id[. ne ""], $test/task)[1]
final String id = ((Element)test).getAttribute("id");
- return id.isBlank() ? null : id;
+ return id.isEmpty() ? null : id;
}
private static @Nullable String getTaskText(final Node test) {
@@ -150,12 +148,11 @@ private static XMLTestInfo extractTestInfo(final Path path, final Document doc)
for (int j = 0; j < testChildren.getLength(); j++) {
final Node testChild = testChildren.item(j);
if (testChild.getNodeType() == Node.ELEMENT_NODE && testChild.getNamespaceURI() == null && "task".equals(testChild.getLocalName())) {
- String textContent = testChild.getTextContent();
- if (textContent != null) {
- textContent = textContent.trim();
- if (!textContent.isEmpty()) {
- return textContent;
- }
+ // the runtime reports the raw text of , including any surrounding whitespace,
+ // so the name must not be trimmed or it would not match the reported test
+ final String textContent = testChild.getTextContent();
+ if (textContent != null && !textContent.isBlank()) {
+ return textContent;
}
return null;
}
@@ -163,22 +160,18 @@ private static XMLTestInfo extractTestInfo(final Path path, final Document doc)
return null;
}
- private String getSuiteName() {
+ @Override
+ public String getSuiteName() {
return "xmlts." + info.name();
}
@Override
- public Description getDescription() {
- final String suiteName = checkDescription(info, getSuiteName());
- final Description description = Description.createSuiteDescription(suiteName);
- for (final String childName : info.childNames()) {
- description.addChild(Description.createTestDescription(suiteName, checkDescription(info, childName)));
- }
- return description;
+ public List getTestNames() {
+ return List.copyOf(info.childNames());
}
@Override
- public void run(final RunNotifier notifier) {
+ public void run(final TestEvents events, final BrokerPool brokerPool) {
try {
final String pkgName = getClass().getPackage().getName().replace('.', '/');
final Source query = new ClassLoaderSource(pkgName + "/xml-test-runner.xq");
@@ -188,19 +181,15 @@ public void run(final RunNotifier notifier) {
context -> new Tuple2<>("id", Sequence.EMPTY_SEQUENCE),
// set callback functions for notifying junit!
- context -> new Tuple2<>("test-ignored-function", new FunctionReference(new FunctionCall(context, new ExtTestIgnoredFunction(context, getSuiteName(), notifier)))),
- context -> new Tuple2<>("test-started-function", new FunctionReference(new FunctionCall(context, new ExtTestStartedFunction(context, getSuiteName(), notifier)))),
- context -> new Tuple2<>("test-failure-function", new FunctionReference(new FunctionCall(context, new ExtTestFailureFunction(context, getSuiteName(), notifier, path)))),
- context -> new Tuple2<>("test-assumption-failed-function", new FunctionReference(new FunctionCall(context, new ExtTestAssumptionFailedFunction(context, getSuiteName(), notifier)))),
- context -> new Tuple2<>("test-error-function", new FunctionReference(new FunctionCall(context, new ExtTestErrorFunction(context, getSuiteName(), notifier)))),
- context -> new Tuple2<>("test-finished-function", new FunctionReference(new FunctionCall(context, new ExtTestFinishedFunction(context, getSuiteName(), notifier))))
+ context -> new Tuple2<>("test-ignored-function", new FunctionReference(new FunctionCall(context, new ExtTestIgnoredFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-started-function", new FunctionReference(new FunctionCall(context, new ExtTestStartedFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-failure-function", new FunctionReference(new FunctionCall(context, new ExtTestFailureFunction(context, getSuiteName(), events, path)))),
+ context -> new Tuple2<>("test-assumption-failed-function", new FunctionReference(new FunctionCall(context, new ExtTestAssumptionFailedFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-error-function", new FunctionReference(new FunctionCall(context, new ExtTestErrorFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-finished-function", new FunctionReference(new FunctionCall(context, new ExtTestFinishedFunction(context, getSuiteName(), events))))
);
- // NOTE: at this stage EXIST_EMBEDDED_SERVER_CLASS_INSTANCE in XSuite will be usable
- final BrokerPool brokerPool = XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.getBrokerPool();
executeQuery(brokerPool, query, externalVariableDeclarations);
-
-
} catch(final DatabaseConfigurationException | IOException | EXistException | PermissionDeniedException | XPathException e) {
//TODO(AR) what to do here?
throw new RuntimeException(e);
diff --git a/exist-core/src/test/java/org/exist/test/runner/XMLTestRunnerTest.java b/exist-core/src/test/java/org/exist/test/runner/XMLTestRunnerTest.java
index 70970ebcb79..c85beb3e1df 100644
--- a/exist-core/src/test/java/org/exist/test/runner/XMLTestRunnerTest.java
+++ b/exist-core/src/test/java/org/exist/test/runner/XMLTestRunnerTest.java
@@ -19,67 +19,57 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-
package org.exist.test.runner;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
-import org.junit.runner.Description;
-import org.junit.runners.model.InitializationError;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.ArrayList;
import java.util.List;
import static java.nio.file.Files.write;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests the global methods of the {@link XMLTestRunner}.
*/
class XMLTestRunnerTest {
+
@TempDir
Path tempPath;
- XMLTestRunner runner;
- @BeforeEach
- void prepare() throws InitializationError, IOException {
+ private XMLTestRunner runnerFor(final String... lines) throws TestInitializationException, IOException {
final Path xmlTestFile = tempPath.resolve("test.xml");
- final List lines = new ArrayList<>();
- lines.add("");
- lines.add(" demoTest");
- lines.add(" description for the demo test");
- lines.add(" ");
- lines.add(" ");
- lines.add(" taskName");
- lines.add(" ");
- lines.add("");
- write(xmlTestFile, lines);
- runner = new XMLTestRunner(xmlTestFile, false);
+ write(xmlTestFile, List.of(lines));
+ return new XMLTestRunner(xmlTestFile);
}
@Test
- void testGetDescription() {
- final Description description = runner.getDescription();
- assertNotNull(description);
- assertTrue(description.isSuite());
- assertTrue(description.getAnnotations().isEmpty());
- assertEquals("xmlts.demoTest", description.getDisplayName());
- assertEquals(2, description.testCount());
- final ArrayList children = description.getChildren();
- assertChild(children.getFirst(), "testId(xmlts.demoTest)");
- assertChild(children.get(1), "taskName(xmlts.demoTest)");
+ void suiteAndTestNamesComeFromTheTestSet() throws TestInitializationException, IOException {
+ final XMLTestRunner runner = runnerFor(
+ "",
+ " demoTest",
+ " description for the demo test",
+ " ",
+ " ",
+ " taskName",
+ " ",
+ "");
+
+ assertEquals("xmlts.demoTest", runner.getSuiteName());
+ assertEquals(List.of("testId", "taskName"), runner.getTestNames());
}
- private void assertChild(final Description description, final String expectedDisplayName) {
- assertFalse(description.isSuite());
- assertTrue(description.getAnnotations().isEmpty());
- assertEquals(expectedDisplayName, description.getDisplayName());
- assertEquals(1, description.testCount());
+ @Test
+ void taskNameIsTheRawTextBecauseThatIsWhatTheRuntimeReports() throws TestInitializationException, IOException {
+ final XMLTestRunner runner = runnerFor(
+ "",
+ " demoTest",
+ " trailing space ",
+ "");
+
+ assertEquals(List.of("trailing space "), runner.getTestNames());
}
}
diff --git a/exist-core/src/main/java/org/exist/test/runner/XQueryFailureLog.java b/exist-core/src/test/java/org/exist/test/runner/XQueryFailureLog.java
similarity index 100%
rename from exist-core/src/main/java/org/exist/test/runner/XQueryFailureLog.java
rename to exist-core/src/test/java/org/exist/test/runner/XQueryFailureLog.java
diff --git a/exist-core/src/main/java/org/exist/test/runner/XQueryTestRunner.java b/exist-core/src/test/java/org/exist/test/runner/XQueryTestRunner.java
similarity index 79%
rename from exist-core/src/main/java/org/exist/test/runner/XQueryTestRunner.java
rename to exist-core/src/test/java/org/exist/test/runner/XQueryTestRunner.java
index 1044aebfb7f..f764389bd8a 100644
--- a/exist-core/src/main/java/org/exist/test/runner/XQueryTestRunner.java
+++ b/exist-core/src/test/java/org/exist/test/runner/XQueryTestRunner.java
@@ -42,12 +42,9 @@
import org.exist.xquery.value.FunctionReference;
import org.exist.xquery.value.NodeValue;
import org.exist.xquery.value.Sequence;
-import org.junit.runner.Description;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
-import org.junit.runner.notification.RunNotifier;
-import org.junit.runners.model.InitializationError;
import javax.annotation.Nullable;
import java.io.IOException;
@@ -60,8 +57,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;
/**
- * A JUnit test runner which can run the XQuery tests (XQSuite)
- * of eXist-db using $EXIST_HOME/src/org/exist/xquery/lib/xqsuite/xqsuite.xql.
+ * Runs the XQuery tests (XQSuite) of a file using $EXIST_HOME/src/org/exist/xquery/lib/xqsuite/xqsuite.xql,
+ * reporting each outcome to a {@link TestEvents}.
*
* @author Adam Retter
*/
@@ -73,39 +70,60 @@ public class XQueryTestRunner extends AbstractTestRunner {
/**
* @param path The path to the XQuery file containing the XQSuite tests
- * @param parallel whether the tests should be run in parallel.
+ * @param discoveryPool a running database to discover the tests with, or null to discover by compiling the module
*
- * @throws InitializationError if the test runner could not be constructed.
+ * @throws TestInitializationException if the test runner could not be constructed.
*/
- public XQueryTestRunner(final Path path, final boolean parallel) throws InitializationError {
- super(path, parallel);
- this.info = discoverOrExtractTestInfo(path);
+ public XQueryTestRunner(final Path path, @Nullable final BrokerPool discoveryPool) throws TestInitializationException {
+ super(path);
+ this.info = discoverOrExtractTestInfo(path, discoveryPool);
}
/**
* Obtain test metadata by discovery when possible, otherwise by compiling the module.
- * When the DB is already started (e.g. by XSuite), try runDiscovery first so we run a single
+ * When a database is available, try runDiscovery first so we run a single
* discovery XQuery instead of compiling the module twice. Fall back to extractTestInfo in two
* cases: (1) the DB is not started, or (2) the DB is started but runDiscovery returns null
* (e.g. discovery failed, empty result, or wrong XML shape).
*
* @param path the path to the XQuery file containing the XQSuite tests
* @return test info (from discovery or from compiling the module)
- * @throws InitializationError if the runner could not be constructed
+ * @throws TestInitializationException if the runner could not be constructed
*/
- private static XQueryTestInfo discoverOrExtractTestInfo(final Path path) throws InitializationError {
- if (XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE != null) {
- final BrokerPool pool = XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.getBrokerPool();
- if (pool != null) {
- final XQueryTestInfo discovered = runDiscovery(pool, path);
- if (discovered != null) {
- return discovered;
- }
+ private static XQueryTestInfo discoverOrExtractTestInfo(final Path path, @Nullable final BrokerPool pool) throws TestInitializationException {
+ if (pool != null) {
+ final XQueryTestInfo discovered = runDiscovery(pool, path);
+ if (discovered != null) {
+ return discovered;
}
}
return extractTestInfo(path);
}
+ /**
+ * The name under which the XQSuite runtime reports a test that has no explicit {@code %test:name}
+ * (see {@code test:get-test-name} in xqsuite.xql): the lexical name of the function, with a prefix
+ * dropped only if it consists solely of word characters. XPath's {@code \w} excludes punctuation,
+ * so a prefix such as {@code my-tests} is kept.
+ */
+ static String runtimeTestName(final String prefix, final String localName) {
+ if (prefix == null || prefix.isEmpty() || prefix.codePoints().allMatch(XQueryTestRunner::isXPathWordChar)) {
+ return localName;
+ }
+ return prefix + ":" + localName;
+ }
+
+ private static boolean isXPathWordChar(final int codePoint) {
+ return switch (Character.getType(codePoint)) {
+ case Character.CONNECTOR_PUNCTUATION, Character.DASH_PUNCTUATION, Character.START_PUNCTUATION,
+ Character.END_PUNCTUATION, Character.INITIAL_QUOTE_PUNCTUATION, Character.FINAL_QUOTE_PUNCTUATION,
+ Character.OTHER_PUNCTUATION, Character.SPACE_SEPARATOR, Character.LINE_SEPARATOR,
+ Character.PARAGRAPH_SEPARATOR, Character.CONTROL, Character.FORMAT, Character.SURROGATE,
+ Character.PRIVATE_USE, Character.UNASSIGNED -> false;
+ default -> true;
+ };
+ }
+
private static Configuration getConfiguration() throws DatabaseConfigurationException {
final Optional home = Optional.ofNullable(System.getProperty("exist.home", System.getProperty("user.dir"))).map(Path::of);
final Path confFile = ConfigurationHelper.lookup("conf.xml", home);
@@ -117,7 +135,7 @@ private static Configuration getConfiguration() throws DatabaseConfigurationExce
}
}
- private static XQueryTestInfo extractTestInfo(final Path path) throws InitializationError {
+ private static XQueryTestInfo extractTestInfo(final Path path) throws TestInitializationException {
try {
final Configuration config = getConfiguration();
@@ -126,7 +144,7 @@ private static XQueryTestInfo extractTestInfo(final Path path) throws Initializa
expathRepo.configure(config);
expathRepo.prepare(null);
} catch (final BrokerPoolServiceException e) {
- throw new InitializationError(e);
+ throw new TestInitializationException(e);
}
final XQueryContext xqueryContext = new XQueryContext(config);
@@ -176,7 +194,7 @@ private static XQueryTestInfo extractTestInfo(final Path path) throws Initializa
if (isTest) {
if (testName == null) {
- testName = localFunctionSignature.getName().getLocalPart();
+ testName = runtimeTestName(localFunctionSignature.getName().getPrefix(), localFunctionSignature.getName().getLocalPart());
testArity = localFunctionSignature.getArgumentCount();
}
@@ -198,7 +216,7 @@ private static XQueryTestInfo extractTestInfo(final Path path) throws Initializa
}
} catch (final DatabaseConfigurationException | IOException | PermissionDeniedException | XPathException e) {
- throw new InitializationError(e);
+ throw new TestInitializationException(e);
}
}
@@ -247,7 +265,8 @@ static XQueryTestInfo runDiscovery(final BrokerPool brokerPool, final Path path)
}
}
- private String getSuiteName() {
+ @Override
+ public String getSuiteName() {
if (info.namespace() == null) {
return path.getFileName().toString();
}
@@ -292,40 +311,30 @@ private void pathToPackageName(String path, final StringBuilder buffer) {
}
@Override
- public Description getDescription() {
- final String suiteName = checkDescription(this, getSuiteName());
- final Description description = Description.createSuiteDescription(suiteName);
- for (final XQueryTestInfo.TestFunctionDef testFunctionDef : info.testFunctions()) {
- description.addChild(Description.createTestDescription(suiteName, checkDescription(testFunctionDef, testFunctionDef.localName())));
- }
- return description;
+ public List getTestNames() {
+ return info.testFunctions().stream().map(XQueryTestInfo.TestFunctionDef::localName).toList();
}
@Override
- public void run(final RunNotifier notifier) {
+ public void run(final TestEvents events, final BrokerPool brokerPool) {
try {
final String pkgName = getClass().getPackage().getName().replace('.', '/');
final Source query = new ClassLoaderSource(pkgName + "/xquery-test-runner.xq");
final URI testModuleUri = path.toAbsolutePath().toUri();
- final String suiteName = getSuiteName();
-
final List>> externalVariableDeclarations = Arrays.asList(
context -> new Tuple2<>("test-module-uri", new AnyURIValue(testModuleUri)),
- // set callback functions for notifying junit!
- context -> new Tuple2<>("test-ignored-function", new FunctionReference(new FunctionCall(context, new ExtTestIgnoredFunction(context, suiteName, notifier)))),
- context -> new Tuple2<>("test-started-function", new FunctionReference(new FunctionCall(context, new ExtTestStartedFunction(context, suiteName, notifier)))),
- context -> new Tuple2<>("test-failure-function", new FunctionReference(new FunctionCall(context, new ExtTestFailureFunction(context, suiteName, notifier, path)))),
- context -> new Tuple2<>("test-assumption-failed-function", new FunctionReference(new FunctionCall(context, new ExtTestAssumptionFailedFunction(context, suiteName, notifier)))),
- context -> new Tuple2<>("test-error-function", new FunctionReference(new FunctionCall(context, new ExtTestErrorFunction(context, suiteName, notifier)))),
- context -> new Tuple2<>("test-finished-function", new FunctionReference(new FunctionCall(context, new ExtTestFinishedFunction(context, suiteName, notifier))))
+ // set callback functions for reporting test outcomes!
+ context -> new Tuple2<>("test-ignored-function", new FunctionReference(new FunctionCall(context, new ExtTestIgnoredFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-started-function", new FunctionReference(new FunctionCall(context, new ExtTestStartedFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-failure-function", new FunctionReference(new FunctionCall(context, new ExtTestFailureFunction(context, getSuiteName(), events, path)))),
+ context -> new Tuple2<>("test-assumption-failed-function", new FunctionReference(new FunctionCall(context, new ExtTestAssumptionFailedFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-error-function", new FunctionReference(new FunctionCall(context, new ExtTestErrorFunction(context, getSuiteName(), events)))),
+ context -> new Tuple2<>("test-finished-function", new FunctionReference(new FunctionCall(context, new ExtTestFinishedFunction(context, getSuiteName(), events))))
);
- // NOTE: at this stage EXIST_EMBEDDED_SERVER_CLASS_INSTANCE in XSuite will be usable
- final BrokerPool brokerPool = XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.getBrokerPool();
executeQuery(brokerPool, query, externalVariableDeclarations);
-
} catch(final DatabaseConfigurationException | IOException | EXistException | PermissionDeniedException | XPathException e) {
//TODO(AR) what to do here?
throw new RuntimeException(e);
diff --git a/exist-core/src/test/java/org/exist/test/runner/XQueryTestRunnerTest.java b/exist-core/src/test/java/org/exist/test/runner/XQueryTestRunnerTest.java
index 4ab90071ab3..759ffdb0e1c 100644
--- a/exist-core/src/test/java/org/exist/test/runner/XQueryTestRunnerTest.java
+++ b/exist-core/src/test/java/org/exist/test/runner/XQueryTestRunnerTest.java
@@ -19,47 +19,55 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-
package org.exist.test.runner;
import org.junit.jupiter.api.Test;
-import org.junit.runner.Description;
-import org.junit.runners.model.InitializationError;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Tests the global methods of the {@link XQueryTestRunner}.
*/
class XQueryTestRunnerTest {
+
+ private XQueryTestRunner runnerFor(final String resource) throws URISyntaxException, TestInitializationException {
+ final URL queryUrl = getClass().getResource(resource);
+ return new XQueryTestRunner(Path.of(queryUrl.toURI()), null);
+ }
+
+ @Test
+ void suiteNameIsDerivedFromTheModuleNamespace() throws URISyntaxException, TestInitializationException {
+ final XQueryTestRunner runner = runnerFor("single-test.xqm");
+ assertEquals("xqts.org.exist-db.xquery.single-test-module", runner.getSuiteName());
+ assertEquals(List.of("f1"), runner.getTestNames());
+ }
+
+ @Test
+ void fileWithoutTestsIsNamedAfterTheFileAndHasNoTests() throws URISyntaxException, TestInitializationException {
+ final XQueryTestRunner runner = runnerFor("no-tests.xqm");
+ assertEquals("no-tests.xqm", runner.getSuiteName());
+ assertTrue(runner.getTestNames().isEmpty());
+ }
+
@Test
- void testGetDescription() throws URISyntaxException, InitializationError {
- final URL queryUrl = getClass().getResource("single-test.xqm");
- final XQueryTestRunner runner = new XQueryTestRunner(Path.of(queryUrl.toURI()), false);
- final Description description = runner.getDescription();
- assertNotNull(description);
- assertTrue(description.isSuite());
- assertTrue(description.getAnnotations().isEmpty());
- assertEquals("xqts.org.exist-db.xquery.single-test-module", description.getDisplayName());
- assertEquals(1, description.testCount());
+ void testNamesFollowTheNamesTheXQSuiteRuntimeReports() throws URISyntaxException, TestInitializationException {
+ // a prefix with a hyphen is kept, an explicit %test:name is used as is
+ assertEquals(List.of("my-tests:f1", "explicitly named"), runnerFor("hyphenated-prefix.xqm").getTestNames());
}
@Test
- void testGetDescriptionWhenNoTests() throws URISyntaxException, InitializationError {
- final URL queryUrl = getClass().getResource("no-tests.xqm");
- final XQueryTestRunner runner = new XQueryTestRunner(Path.of(queryUrl.toURI()), false);
- final Description description = runner.getDescription();
- assertNotNull(description);
- assertFalse(description.isSuite());
- assertTrue(description.getAnnotations().isEmpty());
- assertEquals("no-tests.xqm", description.getDisplayName());
- assertEquals(1, description.testCount());
+ void runtimeTestNameDropsOnlyAPrefixOfWordCharacters() {
+ assertEquals("f1", XQueryTestRunner.runtimeTestName("single", "f1"));
+ assertEquals("f1", XQueryTestRunner.runtimeTestName("t1", "f1"));
+ assertEquals("f1", XQueryTestRunner.runtimeTestName("", "f1"));
+ assertEquals("my-tests:f1", XQueryTestRunner.runtimeTestName("my-tests", "f1"));
+ // underscore is punctuation in XPath regular expressions, so it is not a word character
+ assertEquals("my_tests:f1", XQueryTestRunner.runtimeTestName("my_tests", "f1"));
}
}
diff --git a/exist-core/src/test/java/org/exist/test/runner/XSuiteDebuggabilityTest.java b/exist-core/src/test/java/org/exist/test/runner/XSuiteDebuggabilityTest.java
deleted file mode 100644
index 6b7062197d5..00000000000
--- a/exist-core/src/test/java/org/exist/test/runner/XSuiteDebuggabilityTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * eXist-db Open Source Native XML Database
- * Copyright (C) 2001 The eXist-db Authors
- *
- * info@exist-db.org
- * http://www.exist-db.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-package org.exist.test.runner;
-
-import org.junit.jupiter.api.Test;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Result;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.opentest4j.AssertionFailedError;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * TDD tests for XSuite debuggability: failure stack traces should support
- * "navigate to XQuery" (test file:line) and "navigate to Java" (underlying Java code).
- */
-class XSuiteDebuggabilityTest {
-
- @Test
- void failureFromAssertionIncludesTestFileInStackTraceOrMessage() {
- final List failures = runSuiteAndCollectFailures();
- final Failure assertionFailure = failures.stream()
- .filter(f -> f.getException() instanceof AssertionFailedError)
- .findFirst()
- .orElse(null);
- assertNotNull(assertionFailure, "expected one assertion failure (ComparisonFailure) from failing-both.xqm; collected failures: " + failures.size()
- + " types: " + failures.stream().map(f -> f.getException() == null ? "null" : f.getException().getClass().getName()).toList());
-
- final Throwable t = assertionFailure.getException();
- final String message = t.getMessage();
- final StackTraceElement[] stack = t.getStackTrace();
-
- boolean hasLocation = false;
- if (stack != null && stack.length > 0) {
- for (final StackTraceElement e : stack) {
- final String file = e.getFileName();
- if (file != null && file.contains("failing-both")) {
- hasLocation = true;
- break;
- }
- }
- }
- if (!hasLocation && message != null) {
- hasLocation = message.contains("failing-both");
- }
- assertTrue(hasLocation,
- "assertion failure should include test file (failing-both.xqm) in stack trace or message for IDE navigation (navigate to XQuery); stack.length=" + (stack != null ? stack.length : 0) + " message=" + message);
- }
-
- @Test
- void failureFromErrorIncludesOrgExistInStackTrace() {
- final List failures = runSuiteAndCollectFailures();
- final Failure errorFailure = failures.stream()
- .filter(f -> f.getException() instanceof org.exist.xquery.XPathException)
- .findFirst()
- .orElse(null);
- assertNotNull(errorFailure, "expected one error failure (XPathException) from failing-both.xqm throwsUnexpectedError; xqsuite runs only functions with assert* annotations");
- final Throwable t = errorFailure.getException();
- final StackTraceElement[] stack = t.getStackTrace();
-
- boolean hasOrgExist = false;
- if (stack != null) {
- for (final StackTraceElement e : stack) {
- if (e.getClassName() != null && e.getClassName().startsWith("org.exist")) {
- hasOrgExist = true;
- break;
- }
- }
- }
- assertTrue(hasOrgExist,
- "error failure should include org.exist in stack trace for IDE navigation (navigate to Java); stack.length=" + (stack != null ? stack.length : 0));
- }
-
- private static List runSuiteAndCollectFailures() {
- final List collected = new ArrayList<>();
- final JUnitCore core = new JUnitCore();
- core.addListener(new RunListener() {
- @Override
- public void testFailure(final Failure failure) {
- collected.add(failure);
- }
- });
- final Result result = core.run(DebuggabilityNavigabilitySuite.class);
- assertFalse(result.wasSuccessful(), "suite is expected to have failures (failing-both.xqm)");
- assertTrue(collected.size() >= 2,
- "expected at least 2 failures (assertion + error) from failing-both.xqm; got " + collected.size()
- + " types: " + collected.stream().map(f -> f.getException() == null ? "null" : f.getException().getClass().getName()).toList());
- return collected;
- }
-}
diff --git a/exist-core/src/test/java/org/exist/test/runner/XSuiteFailureMessageTest.java b/exist-core/src/test/java/org/exist/test/runner/XSuiteFailureMessageTest.java
deleted file mode 100644
index e3f4808a7b2..00000000000
--- a/exist-core/src/test/java/org/exist/test/runner/XSuiteFailureMessageTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * eXist-db Open Source Native XML Database
- * Copyright (C) 2001 The eXist-db Authors
- *
- * info@exist-db.org
- * http://www.exist-db.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-package org.exist.test.runner;
-
-import org.junit.jupiter.api.Test;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Result;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.opentest4j.AssertionFailedError;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-/**
- * The XQSuite layer hands {@link ExtTestFailureFunction} values it has already serialized to
- * strings. Serializing them a second time on the Java side escaped their markup, so a test whose
- * result was {@code text} reported it as {@code <doc a="1">text</doc>}
- * and a string result containing markup was escaped twice over.
- */
-class XSuiteFailureMessageTest {
-
- @Test
- void nodeResultIsNotEscapedIntoTheFailureMessage() {
- final AssertionFailedError failure = runSuiteAndGetComparisonFailure();
-
- assertEquals("text", failure.getActual().getValue(),
- "a node-valued result should reach the failure message as markup, not XML-escaped");
- }
-
- private static AssertionFailedError runSuiteAndGetComparisonFailure() {
- final List collected = new ArrayList<>();
- final JUnitCore core = new JUnitCore();
- core.addListener(new RunListener() {
- @Override
- public void testFailure(final Failure failure) {
- collected.add(failure);
- }
- });
-
- final Result result = core.run(SerializationFailureMessageSuite.class);
- assertFalse(result.wasSuccessful(), "suite is expected to fail (failing-serialization.xqm)");
-
- final AssertionFailedError comparisonFailure = collected.stream()
- .map(Failure::getException)
- .filter(AssertionFailedError.class::isInstance)
- .map(AssertionFailedError.class::cast)
- .findFirst()
- .orElse(null);
- assertNotNull(comparisonFailure,
- "expected a ComparisonFailure from failing-serialization.xqm; collected: " + collected.size());
-
- return comparisonFailure;
- }
-}
diff --git a/exist-core/src/test/java/org/exist/test/runner/XSuiteParallelTest.java b/exist-core/src/test/java/org/exist/test/runner/XSuiteParallelTest.java
deleted file mode 100644
index d378641e864..00000000000
--- a/exist-core/src/test/java/org/exist/test/runner/XSuiteParallelTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * eXist-db Open Source Native XML Database
- * Copyright (C) 2001 The eXist-db Authors
- *
- * info@exist-db.org
- * http://www.exist-db.org
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-package org.exist.test.runner;
-
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.Test;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Result;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * Tests for XSuite parallel scheduler: dynamic pool size and progress-based hang detection.
- */
-class XSuiteParallelTest {
-
- @AfterEach
- void clearSystemProperties() {
- System.clearProperty("xsuite.parallel.threads");
- System.clearProperty("xsuite.hang.threshold.minutes");
- System.clearProperty("xsuite.hang.watcher.interval.seconds");
- }
-
- @Test
- void parallelSuiteRunsAndCompletesSuccessfully() {
- final Result result = runSuite(ParallelPassingSuite.class);
- assertTrue(result.wasSuccessful(),
- "parallel suite with passing files should succeed; failures: " + result.getFailureCount());
- }
-
- @Test
- void parallelSuiteWithThreadOverrideCompletes() {
- System.setProperty("xsuite.parallel.threads", "2");
- final Result result = runSuite(ParallelPassingSuite.class);
- assertTrue(result.wasSuccessful(),
- "parallel suite with xsuite.parallel.threads=2 should succeed; failures: " + result.getFailureCount());
- }
-
- /**
- * Parallel suite with two files (one passing, one that would hang if module vars were evaluated at load).
- * Runs to verify the parallel path with multiple files and that hang-detection properties are read.
- * Full "appears hung" behaviour is not asserted here because eXist may not evaluate library module
- * variables when inspect:module-functions loads the module, so hanging.xqm can complete without hanging.
- */
- @Test
- void parallelSuiteWithTwoFilesCompletes() {
- System.setProperty("xsuite.hang.threshold.minutes", "0.05");
- System.setProperty("xsuite.hang.watcher.interval.seconds", "1");
- final Result result = runSuite(ParallelWithHungSuite.class);
- assertTrue(result.getRunCount() > 0, "suite should run and complete");
- }
-
- private static Result runSuite(final Class> suiteClass) {
- return new JUnitCore().run(suiteClass);
- }
-}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/EngineTestEvents.java b/exist-core/src/test/java/org/exist/test/xqsuite/EngineTestEvents.java
new file mode 100644
index 00000000000..4f6874633c0
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/EngineTestEvents.java
@@ -0,0 +1,191 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.exist.test.runner.TestEvents;
+import org.opentest4j.TestAbortedException;
+import org.junit.platform.engine.EngineExecutionListener;
+import org.junit.platform.engine.TestExecutionResult;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Translates the outcome events of one test file into JUnit Platform execution events.
+ *
+ * The Platform allows exactly one result per test, whereas the XQuery side may report
+ * a failure before it reports that the test finished, so the result is held back
+ * until {@link #finished(String)}.
+ */
+final class EngineTestEvents implements TestEvents {
+ private final FileDescriptor file;
+ private final EngineExecutionListener listener;
+ /** called whenever the XQuery side reports something, to show that the file is still making progress */
+ private final Runnable activity;
+ /** once the file is complete or abandoned, nothing more is reported, even if its thread is still running */
+ private boolean closed = false;
+ /** how many tests of each name have been started, to match repeated names to occurrences in order */
+ private final Map startedCount = new HashMap<>();
+ /** the test of each name that is currently running */
+ private final Map current = new HashMap<>();
+ private final Map pending = new HashMap<>();
+ private final Set started = new HashSet<>();
+ private final Set done = new HashSet<>();
+
+ EngineTestEvents(final FileDescriptor file, final EngineExecutionListener listener, final Runnable activity) {
+ this.file = file;
+ this.listener = listener;
+ this.activity = activity;
+ }
+
+ /**
+ * @return the names of the tests that have started but not finished
+ */
+ synchronized List runningTests() {
+ return started.stream().filter(t -> !done.contains(t)).map(XQTestDescriptor::getDisplayName).sorted().toList();
+ }
+
+ /**
+ * Starts the next not yet started test of the given name.
+ */
+ private XQTestDescriptor start(final String testName) {
+ final int index = startedCount.merge(testName, 1, Integer::sum) - 1;
+ final List occurrences = file.findTests(testName);
+ final XQTestDescriptor test;
+ if (index < occurrences.size()) {
+ test = occurrences.get(index);
+ } else {
+ // reported by the XQuery side but not found by discovery
+ test = file.addTest(testName);
+ listener.dynamicTestRegistered(test);
+ }
+ current.put(testName, test);
+ started.add(test);
+ listener.executionStarted(test);
+ return test;
+ }
+
+ /**
+ * @return the running test of the given name, starting one if the XQuery side reports on a test it never started
+ */
+ private XQTestDescriptor running(final String testName) {
+ final XQTestDescriptor test = current.get(testName);
+ return test != null && !done.contains(test) ? test : start(testName);
+ }
+
+ @Override
+ public synchronized void started(final String testName) {
+ if (closed) {
+ return;
+ }
+ activity.run();
+ start(testName);
+ }
+
+ @Override
+ public synchronized void finished(final String testName) {
+ if (closed) {
+ return;
+ }
+ activity.run();
+ final XQTestDescriptor test = running(testName);
+ done.add(test);
+ final TestExecutionResult result = pending.remove(test);
+ listener.executionFinished(test, result != null ? result : TestExecutionResult.successful());
+ }
+
+ @Override
+ public synchronized void ignored(final String testName) {
+ if (closed) {
+ return;
+ }
+ activity.run();
+ final int index = startedCount.merge(testName, 1, Integer::sum) - 1;
+ final List occurrences = file.findTests(testName);
+ final XQTestDescriptor test = index < occurrences.size() ? occurrences.get(index) : file.addTest(testName);
+ if (index >= occurrences.size()) {
+ listener.dynamicTestRegistered(test);
+ }
+ started.add(test);
+ done.add(test);
+ listener.executionSkipped(test, "Ignored");
+ }
+
+ @Override
+ public synchronized void failed(final String testName, final Throwable reason) {
+ if (closed) {
+ return;
+ }
+ activity.run();
+ final XQTestDescriptor test = running(testName);
+ final TestExecutionResult previous = pending.get(test);
+ if (previous != null && previous.getStatus() == TestExecutionResult.Status.FAILED && previous.getThrowable().isPresent()) {
+ previous.getThrowable().get().addSuppressed(reason);
+ } else {
+ pending.put(test, TestExecutionResult.failed(reason));
+ }
+ }
+
+ @Override
+ public synchronized void assumptionFailed(final String testName, final String message) {
+ if (closed) {
+ return;
+ }
+ activity.run();
+ pending.putIfAbsent(running(testName), TestExecutionResult.aborted(new TestAbortedException(message)));
+ }
+
+ /**
+ * Completes every test of the file that the XQuery side did not finish, so that none is silently lost.
+ *
+ * @param fileFailure the reason the whole file failed to run, or null
+ */
+ private static String reasonSuffix(final Throwable fileFailure) {
+ return fileFailure != null && fileFailure.getMessage() != null ? ": " + fileFailure.getMessage() : "";
+ }
+
+ synchronized void completeOutstanding(final Throwable fileFailure) {
+ if (closed) {
+ return;
+ }
+ closed = true;
+ for (final XQTestDescriptor test : file.tests()) {
+ if (done.contains(test)) {
+ continue;
+ }
+ done.add(test);
+ if (started.contains(test)) {
+ final TestExecutionResult result = pending.remove(test);
+ listener.executionFinished(test, result != null ? result : TestExecutionResult.failed(
+ new AssertionError("Test started but did not finish" + reasonSuffix(fileFailure), fileFailure)));
+ } else {
+ started.add(test);
+ listener.executionStarted(test);
+ listener.executionFinished(test, TestExecutionResult.failed(new AssertionError(
+ "Test was discovered but never reported by the XQuery test runner" + reasonSuffix(fileFailure), fileFailure)));
+ }
+ }
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/FileDescriptor.java b/exist-core/src/test/java/org/exist/test/xqsuite/FileDescriptor.java
new file mode 100644
index 00000000000..c1d51d8334e
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/FileDescriptor.java
@@ -0,0 +1,81 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.exist.test.runner.AbstractTestRunner;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
+import org.junit.platform.engine.support.descriptor.FileSource;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A file of XQSuite or XML tests.
+ */
+final class FileDescriptor extends AbstractTestDescriptor {
+ private final AbstractTestRunner runner;
+ private final Map> tests = new LinkedHashMap<>();
+
+ FileDescriptor(final UniqueId id, final AbstractTestRunner runner) {
+ super(id, runner.getSourcePath().getFileName().toString(), FileSource.from(runner.getSourcePath().toFile()));
+ this.runner = runner;
+ }
+
+ AbstractTestRunner runner() {
+ return runner;
+ }
+
+ /**
+ * Adds a test. Several test functions in one file may share a name (for example overloads),
+ * and each is a test in its own right, so a repeated name adds a further occurrence.
+ */
+ synchronized XQTestDescriptor addTest(final String name) {
+ final List occurrences = tests.computeIfAbsent(name, n -> new ArrayList<>());
+ final String segment = occurrences.isEmpty() ? name : name + "#" + (occurrences.size() + 1);
+ final XQTestDescriptor test = new XQTestDescriptor(getUniqueId().append("test", segment), name, getSource().orElseThrow());
+ occurrences.add(test);
+ addChild(test);
+ return test;
+ }
+
+ /**
+ * @return the tests with the given name, in the order they were added
+ */
+ synchronized List findTests(final String name) {
+ final List occurrences = tests.get(name);
+ return occurrences == null ? List.of() : new ArrayList<>(occurrences);
+ }
+
+ synchronized List tests() {
+ final List all = new ArrayList<>();
+ tests.values().forEach(all::addAll);
+ return all;
+ }
+
+ @Override
+ public Type getType() {
+ return Type.CONTAINER;
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/SuiteDescriptor.java b/exist-core/src/test/java/org/exist/test/xqsuite/SuiteDescriptor.java
new file mode 100644
index 00000000000..7d7bb12fd55
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/SuiteDescriptor.java
@@ -0,0 +1,69 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
+import org.junit.platform.engine.support.descriptor.ClassSource;
+
+/**
+ * A class annotated with {@link XQSuite}: one embedded database instance serves all of its files.
+ */
+final class SuiteDescriptor extends AbstractTestDescriptor {
+ private final Class> suiteClass;
+ private final Throwable discoveryFailure;
+ private final boolean parallel;
+
+ SuiteDescriptor(final UniqueId id, final Class> suiteClass, final boolean parallel, final Throwable discoveryFailure) {
+ super(id, suiteClass.getSimpleName(), ClassSource.from(suiteClass));
+ this.suiteClass = suiteClass;
+ this.parallel = parallel;
+ this.discoveryFailure = discoveryFailure;
+ }
+
+ Class> suiteClass() {
+ return suiteClass;
+ }
+
+ /**
+ * @return true if the files of the suite run concurrently
+ */
+ boolean parallel() {
+ return parallel;
+ }
+
+ /**
+ * @return why the suite's files could not be discovered, or null
+ */
+ Throwable discoveryFailure() {
+ return discoveryFailure;
+ }
+
+ /**
+ * A suite whose files could not be discovered is itself a test, which fails when run,
+ * so that the problem is visible instead of the suite silently disappearing.
+ */
+ @Override
+ public Type getType() {
+ return discoveryFailure != null ? Type.CONTAINER_AND_TEST : Type.CONTAINER;
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/SuiteRun.java b/exist-core/src/test/java/org/exist/test/xqsuite/SuiteRun.java
new file mode 100644
index 00000000000..ff7f06eb96b
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/SuiteRun.java
@@ -0,0 +1,218 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.exist.storage.BrokerPool;
+import org.exist.xquery.XQueryWatchDog;
+import org.junit.platform.engine.EngineExecutionListener;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestExecutionResult;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Runs the test files of one {@link XQSuite} against a running database, and fails any file that hangs.
+ *
+ * Every file runs on a thread of its own, so that a file which stops making progress can be given up on
+ * without taking the whole run down with it. A sequential suite runs one file at a time; a parallel suite
+ * runs up to {@link XQSuiteSettings#parallelism(int)} files at a time. The tests within a file always run
+ * one after the other.
+ *
+ * A watcher thread checks the running files. A file that has reported nothing for longer than the hang
+ * threshold is abandoned: its running and not yet run tests are failed, the file is finished as
+ * failed, its XQuery is killed and its thread interrupted, and the remaining files carry on. Whatever the
+ * abandoned thread reports afterwards is ignored.
+ *
+ * Giving up on a file is reliable, stopping its thread is not. A thread that is sleeping or blocked ends when it
+ * is interrupted, and eXist can stop a query at the points where it checks for being killed, but a query that is
+ * spinning in code that does neither keeps running (which expressions check differs, and queries nested inside the
+ * test runner do not always see the kill). Such a thread is left behind, and since it can stop the database from
+ * shutting down the engine bounds that shutdown, see {@link XQSuiteTestEngine}.
+ */
+final class SuiteRun {
+ private final SuiteDescriptor suite;
+ private final EngineExecutionListener listener;
+ private final BrokerPool brokerPool;
+ private final XQSuiteSettings settings;
+
+ private final List runs = new CopyOnWriteArrayList<>();
+ private final Semaphore permits;
+ private final Duration hangThreshold;
+
+ SuiteRun(final SuiteDescriptor suite, final EngineExecutionListener listener, final BrokerPool brokerPool, final XQSuiteSettings settings) {
+ this.suite = suite;
+ this.listener = listener;
+ this.brokerPool = brokerPool;
+ this.settings = settings;
+ this.permits = new Semaphore(suite.parallel() ? settings.parallelism(brokerPool.getMax()) : 1);
+ this.hangThreshold = settings.hangThreshold();
+ }
+
+ void run() throws InterruptedException {
+ final AtomicInteger threadNumber = new AtomicInteger();
+ final ThreadFactory factory = runnable -> {
+ final Thread thread = new Thread(runnable, "xqsuite-" + suite.suiteClass().getSimpleName() + "-" + threadNumber.incrementAndGet());
+ // a hung file's thread must not keep the JVM alive
+ thread.setDaemon(true);
+ return thread;
+ };
+ final ExecutorService threads = Executors.newCachedThreadPool(factory);
+ final Thread watcher = new Thread(this::watch, "xqsuite-hang-watcher-" + suite.suiteClass().getSimpleName());
+ watcher.setDaemon(true);
+ watcher.start();
+ try {
+ for (final TestDescriptor child : new ArrayList<>(suite.getChildren())) {
+ permits.acquire();
+ final FileRun run = new FileRun((FileDescriptor) child);
+ runs.add(run);
+ threads.execute(run);
+ }
+ for (final FileRun run : runs) {
+ run.awaitSettled();
+ }
+ } finally {
+ watcher.interrupt();
+ threads.shutdown();
+ awaitAbandonedThreads();
+ }
+ }
+
+ /**
+ * Give the threads of abandoned files a chance to stop before the database is shut down under them.
+ */
+ private void awaitAbandonedThreads() throws InterruptedException {
+ final long deadline = System.nanoTime() + settings.hangGrace().toNanos();
+ for (final FileRun run : runs) {
+ if (run.state.get() == State.ABANDONED && run.thread != null) {
+ final long remainingMillis = Math.max(0, (deadline - System.nanoTime()) / 1_000_000);
+ run.thread.join(remainingMillis);
+ }
+ }
+ }
+
+ private void watch() {
+ final long intervalMillis = settings.hangWatcherInterval().toMillis();
+ try {
+ while (!Thread.currentThread().isInterrupted()) {
+ Thread.sleep(intervalMillis);
+ final long now = System.nanoTime();
+ for (final FileRun run : runs) {
+ if (run.state.get() == State.RUNNING && now - run.lastActivityNanos > hangThreshold.toNanos()) {
+ run.abandon();
+ }
+ }
+ }
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private enum State { PENDING, RUNNING, DONE, ABANDONED }
+
+ private final class FileRun implements Runnable {
+ private final FileDescriptor file;
+ private final EngineTestEvents events;
+ private final AtomicReference state = new AtomicReference<>(State.PENDING);
+ private final CountDownLatch settled = new CountDownLatch(1);
+ private volatile long lastActivityNanos = System.nanoTime();
+ private volatile Thread thread;
+
+ FileRun(final FileDescriptor file) {
+ this.file = file;
+ this.events = new EngineTestEvents(file, listener, this::touch);
+ }
+
+ private void touch() {
+ lastActivityNanos = System.nanoTime();
+ }
+
+ void awaitSettled() throws InterruptedException {
+ settled.await();
+ }
+
+ @Override
+ public void run() {
+ thread = Thread.currentThread();
+ touch();
+ state.set(State.RUNNING);
+ listener.executionStarted(file);
+
+ Throwable failure = null;
+ try {
+ file.runner().run(events, brokerPool);
+ } catch (final Throwable t) {
+ failure = t;
+ }
+
+ // the watcher may have abandoned the file meanwhile, in which case everything has been reported
+ if (state.compareAndSet(State.RUNNING, State.DONE)) {
+ events.completeOutstanding(failure);
+ listener.executionFinished(file, failure == null ? TestExecutionResult.successful() : TestExecutionResult.failed(failure));
+ settle();
+ }
+ }
+
+ void abandon() {
+ if (!state.compareAndSet(State.RUNNING, State.ABANDONED)) {
+ return;
+ }
+ final List running = events.runningTests();
+ final AssertionError hung = new AssertionError("Test file appears hung (no activity for "
+ + format(hangThreshold) + "): " + file.runner().getSourcePath().toAbsolutePath()
+ + (running.isEmpty() ? "" : " while running " + running));
+ events.completeOutstanding(hung);
+ listener.executionFinished(file, TestExecutionResult.failed(hung));
+
+ // stop the query if it can be stopped, then the thread, so that a stuck file does not keep using the database
+ final Thread stuck = thread;
+ for (final XQueryWatchDog watchDog : brokerPool.getProcessMonitor().getRunningXQueries()) {
+ if (stuck != null && stuck.getName().equals(watchDog.getRunningThread())) {
+ watchDog.kill(0);
+ }
+ }
+ if (stuck != null) {
+ stuck.interrupt();
+ }
+ settle();
+ }
+
+ private void settle() {
+ permits.release();
+ settled.countDown();
+ }
+ }
+
+ private static String format(final Duration duration) {
+ final long seconds = duration.toSeconds();
+ return seconds >= 120 ? (seconds / 60) + " minutes" : seconds + " seconds";
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/XQSuite.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuite.java
new file mode 100644
index 00000000000..eac717f377a
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuite.java
@@ -0,0 +1,64 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a class as a suite of XQSuite and XML tests, to be run by the {@link XQSuiteTestEngine}.
+ *
+ * The class itself is just a marker, for example:
+ *
{@code
+ * @XQSuite("src/test/xquery/maps")
+ * public class MapTests {
+ * }
+ * }
+ * Static methods of the class annotated with {@code @BeforeAll} and {@code @AfterAll}
+ * run around the whole suite, after the embedded database has started and before it stops.
+ *
+ * The test files of a suite run one after the other unless {@link #parallel()} is set. In either
+ * case a test file that stops reporting for too long is failed instead of hanging the build, see
+ * {@link XQSuiteSettings#HANG_THRESHOLD_MINUTES}.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Inherited
+public @interface XQSuite {
+
+ /**
+ * @return the directories or files containing the tests, relative to the working directory
+ */
+ String[] value();
+
+ /**
+ * Run the test files of the suite concurrently, against the one embedded database, with at most
+ * {@link XQSuiteSettings#PARALLELISM} files at a time. The tests within a file always run one
+ * after the other. Only set this if the files do not depend on or disturb each other's data.
+ *
+ * @return true to run the files of the suite concurrently
+ */
+ boolean parallel() default false;
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSchedulingTest.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSchedulingTest.java
new file mode 100644
index 00000000000..51670f97ae5
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSchedulingTest.java
@@ -0,0 +1,203 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.testkit.engine.EngineExecutionResults;
+import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.Event;
+import org.junit.platform.testkit.engine.EventType;
+import org.opentest4j.AssertionFailedError;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+
+/**
+ * Tests how the {@link XQSuiteTestEngine} schedules the files of a suite, and that it fails a file that hangs.
+ *
+ * Whether files overlap is read off the timeline of the engine's events. The fixtures each sleep for
+ * a few seconds, so files overlap if, and only if, they were run concurrently.
+ */
+class XQSuiteSchedulingTest {
+
+ private static final String WAITS = "src/test/resources/org/exist/test/xqsuite/";
+ private static final String RUNNER = "src/test/resources/org/exist/test/runner/";
+
+ @XQSuite({WAITS + "wait-a.xqm", WAITS + "wait-b.xqm", WAITS + "wait-c.xqm", WAITS + "wait-d.xqm"})
+ static class SequentialWaits {
+ }
+
+ @XQSuite(value = {WAITS + "wait-a.xqm", WAITS + "wait-b.xqm", WAITS + "wait-c.xqm", WAITS + "wait-d.xqm"}, parallel = true)
+ static class ParallelWaits {
+ }
+
+ /** the hanging file comes first, so a sequential suite only gets to the second file if the first is given up on */
+ @XQSuite({WAITS + "hang.xqm", RUNNER + "single-test.xqm"})
+ static class SequentialHang {
+ }
+
+ @XQSuite(value = {WAITS + "hang.xqm", RUNNER + "single-test.xqm"}, parallel = true)
+ static class ParallelHang {
+ }
+
+ private static EngineExecutionResults run(final Class> suite, final Map parameters) {
+ final EngineTestKit.Builder builder = EngineTestKit.engine(XQSuiteTestEngine.ENGINE_ID).selectors(selectClass(suite));
+ parameters.forEach(builder::configurationParameter);
+ return builder.execute();
+ }
+
+ /** a hang is detected after a couple of seconds instead of minutes */
+ private static Map quickHangDetection() {
+ return Map.of(
+ XQSuiteSettings.HANG_THRESHOLD_MINUTES, "0.04",
+ XQSuiteSettings.HANG_WATCHER_INTERVAL_SECONDS, "1",
+ XQSuiteSettings.HANG_GRACE_SECONDS, "20");
+ }
+
+ /**
+ * @return the most tests that were running at the same moment
+ */
+ private static int maxConcurrentTests(final EngineExecutionResults results) {
+ final Map starts = new HashMap<>();
+ final Map ends = new HashMap<>();
+ for (final Event event : results.testEvents().list()) {
+ final UniqueId id = event.getTestDescriptor().getUniqueId();
+ if (event.getType() == EventType.STARTED) {
+ starts.put(id, event.getTimestamp());
+ } else if (event.getType() == EventType.FINISHED) {
+ ends.put(id, event.getTimestamp());
+ }
+ }
+ // sweep over the start and end points, handling an end before a start at the same instant
+ record Point(Instant at, int change) { }
+ final List points = new ArrayList<>();
+ starts.forEach((id, start) -> {
+ points.add(new Point(start, 1));
+ points.add(new Point(ends.get(id), -1));
+ });
+ points.sort(Comparator.comparing(Point::at).thenComparingInt(Point::change));
+ int running = 0;
+ int max = 0;
+ for (final Point point : points) {
+ running += point.change();
+ max = Math.max(max, running);
+ }
+ return max;
+ }
+
+ private static List failures(final EngineExecutionResults results) {
+ return results.testEvents().failed().list().stream()
+ .map(event -> event.getRequiredPayload(TestExecutionResult.class).getThrowable().orElseThrow())
+ .toList();
+ }
+
+ @Test
+ void sequentialSuiteRunsOneFileAtATime() {
+ final EngineExecutionResults results = run(SequentialWaits.class, Map.of());
+ results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4));
+ assertEquals(1, maxConcurrentTests(results), "files of a sequential suite must not overlap");
+ }
+
+ @Test
+ void parallelSuiteRunsFilesAtTheSameTime() {
+ final EngineExecutionResults results = run(ParallelWaits.class, Map.of(XQSuiteSettings.PARALLELISM, "4"));
+ results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4));
+ assertEquals(4, maxConcurrentTests(results), "all four files should have been running together");
+ }
+
+ @Test
+ void parallelismSettingLimitsHowManyFilesRunAtOnce() {
+ final EngineExecutionResults results = run(ParallelWaits.class, Map.of(XQSuiteSettings.PARALLELISM, "2"));
+ results.testEvents().assertStatistics(stats -> stats.started(4).succeeded(4));
+ assertEquals(2, maxConcurrentTests(results), "no more than two files may run together");
+ }
+
+ @ParameterizedTest(name = "parallel suite: {0}")
+ @ValueSource(booleans = {false, true})
+ void hungFileIsFailedAndTheOtherFilesStillRun(final boolean parallel) {
+ final Class> suite = parallel ? ParallelHang.class : SequentialHang.class;
+ final EngineExecutionResults results = assertTimeoutPreemptively(Duration.ofSeconds(60), () -> run(suite, quickHangDetection()));
+
+ results.testEvents().assertStatistics(stats -> stats.started(2).succeeded(1).failed(1));
+
+ final Throwable failure = failures(results).getFirst();
+ assertTrue(failure instanceof AssertionError && !(failure instanceof AssertionFailedError), "the hang is reported as an assertion error: " + failure);
+ assertTrue(failure.getMessage().contains("appears hung"), failure.getMessage());
+ assertTrue(failure.getMessage().contains("hang.xqm"), "names the file: " + failure.getMessage());
+ assertTrue(failure.getMessage().contains("hangs"), "names the test that was running: " + failure.getMessage());
+
+ // the abandoned thread is woken up and finishes the test normally; that must not be reported as well
+ final Map finishes = new HashMap<>();
+ results.allEvents().list().stream()
+ .filter(event -> event.getType() == EventType.FINISHED)
+ .forEach(event -> finishes.merge(event.getTestDescriptor().getUniqueId(), 1L, Long::sum));
+ finishes.forEach((id, count) -> assertEquals(1L, count, id + " must finish exactly once"));
+ assertEquals(1, results.containerEvents().failed().count(), "the hung file, and only it, is a failed container");
+ }
+
+ @Test
+ void stopThatFinishesInTimeIsReportedAsStopped() throws InterruptedException {
+ assertTrue(XQSuiteTestEngine.stopWithin(() -> { }, Duration.ofSeconds(5)));
+ }
+
+ /**
+ * A runaway query that ignores both interruption and the kill flag keeps the database from shutting down.
+ * That cannot be reproduced for real in a shared test JVM, so the bounded wait is tested on its own.
+ */
+ @Test
+ void stopThatNeverReturnsIsGivenUpOn() throws InterruptedException {
+ final java.util.concurrent.CountDownLatch release = new java.util.concurrent.CountDownLatch(1);
+ try {
+ assertFalse(XQSuiteTestEngine.stopWithin(() -> {
+ try {
+ release.await();
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }, Duration.ofMillis(300)));
+ } finally {
+ release.countDown();
+ }
+ }
+
+ @Test
+ void stopThatFailsIsReportedAsAFailure() {
+ final IllegalStateException e = org.junit.jupiter.api.Assertions.assertThrows(IllegalStateException.class,
+ () -> XQSuiteTestEngine.stopWithin(() -> { throw new IllegalStateException("boom"); }, Duration.ofSeconds(5)));
+ assertEquals("boom", e.getCause().getMessage());
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSettings.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSettings.java
new file mode 100644
index 00000000000..ab86dd9d57b
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSettings.java
@@ -0,0 +1,119 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.engine.ConfigurationParameters;
+
+import java.time.Duration;
+import java.util.function.Function;
+
+/**
+ * Settings of the {@link XQSuiteTestEngine}, read from the JUnit configuration parameters
+ * (so they can be set with a system property, in junit-platform.properties, or by the launcher).
+ */
+final class XQSuiteSettings {
+
+ /** how many test files of a parallel suite run at once */
+ static final String PARALLELISM = "exist.xqsuite.parallelism";
+
+ /** how long a running test file may report nothing before it is considered hung, in minutes (may be fractional) */
+ static final String HANG_THRESHOLD_MINUTES = "exist.xqsuite.hang.threshold.minutes";
+
+ /** how often running test files are checked for being hung, in seconds */
+ static final String HANG_WATCHER_INTERVAL_SECONDS = "exist.xqsuite.hang.watcher.interval.seconds";
+
+ /** how long to wait for the thread of a hung test file to stop once it has been interrupted, in seconds */
+ static final String HANG_GRACE_SECONDS = "exist.xqsuite.hang.grace.seconds";
+
+ private static final int MIN_PARALLELISM = 2;
+ private static final int MAX_PARALLELISM = 32;
+ private static final double DEFAULT_HANG_THRESHOLD_MINUTES = 5.0;
+ private static final long DEFAULT_HANG_GRACE_SECONDS = 60;
+
+ private final ConfigurationParameters parameters;
+
+ XQSuiteSettings(final ConfigurationParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ /**
+ * @param brokerPoolMax the most brokers (concurrent database users) the database allows
+ */
+ int parallelism(final int brokerPoolMax) {
+ final Integer configured = parse(PARALLELISM, Integer::parseInt);
+ if (configured != null) {
+ if (configured < 1) {
+ throw invalid(PARALLELISM, String.valueOf(configured), "must be at least 1");
+ }
+ return configured;
+ }
+ final int processors = Runtime.getRuntime().availableProcessors();
+ return Math.max(MIN_PARALLELISM, Math.min(MAX_PARALLELISM, Math.min(processors, brokerPoolMax)));
+ }
+
+ Duration hangThreshold() {
+ final Double minutes = parse(HANG_THRESHOLD_MINUTES, Double::parseDouble);
+ final double value = minutes != null ? minutes : DEFAULT_HANG_THRESHOLD_MINUTES;
+ if (Double.isNaN(value) || value <= 0) {
+ throw invalid(HANG_THRESHOLD_MINUTES, String.valueOf(value), "must be greater than 0");
+ }
+ return Duration.ofMillis((long) (value * 60_000));
+ }
+
+ /**
+ * Unless set, a tenth of the hang threshold, but at least a second and at most 30 seconds.
+ */
+ Duration hangWatcherInterval() {
+ final Long seconds = parse(HANG_WATCHER_INTERVAL_SECONDS, Long::parseLong);
+ if (seconds != null) {
+ if (seconds < 1) {
+ throw invalid(HANG_WATCHER_INTERVAL_SECONDS, String.valueOf(seconds), "must be at least 1");
+ }
+ return Duration.ofSeconds(seconds);
+ }
+ final Duration tenth = hangThreshold().dividedBy(10);
+ return tenth.compareTo(Duration.ofSeconds(1)) < 0 ? Duration.ofSeconds(1)
+ : tenth.compareTo(Duration.ofSeconds(30)) > 0 ? Duration.ofSeconds(30) : tenth;
+ }
+
+ Duration hangGrace() {
+ final Long seconds = parse(HANG_GRACE_SECONDS, Long::parseLong);
+ return Duration.ofSeconds(seconds != null ? Math.max(0, seconds) : DEFAULT_HANG_GRACE_SECONDS);
+ }
+
+ private T parse(final String key, final Function parser) {
+ final String value = parameters.get(key).map(String::trim).filter(v -> !v.isEmpty()).orElse(null);
+ if (value == null) {
+ return null;
+ }
+ try {
+ return parser.apply(value);
+ } catch (final NumberFormatException e) {
+ throw invalid(key, value, "is not a number");
+ }
+ }
+
+ private static JUnitException invalid(final String key, final String value, final String problem) {
+ return new JUnitException("Invalid value '" + value + "' for configuration parameter " + key + ": " + problem);
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSettingsTest.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSettingsTest.java
new file mode 100644
index 00000000000..6424e6a781a
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteSettingsTest.java
@@ -0,0 +1,102 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.junit.jupiter.api.Test;
+import org.junit.platform.commons.JUnitException;
+import org.junit.platform.engine.ConfigurationParameters;
+
+import java.time.Duration;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class XQSuiteSettingsTest {
+
+ private static XQSuiteSettings settings(final Map values) {
+ return new XQSuiteSettings(new ConfigurationParameters() {
+ @Override
+ public Optional get(final String key) {
+ return Optional.ofNullable(values.get(key));
+ }
+
+ @Override
+ public Optional getBoolean(final String key) {
+ return get(key).map(Boolean::parseBoolean);
+ }
+
+ @Override
+ public Set keySet() {
+ return values.keySet();
+ }
+ });
+ }
+
+ @Test
+ void defaults() {
+ final XQSuiteSettings settings = settings(Map.of());
+ assertEquals(Duration.ofMinutes(5), settings.hangThreshold());
+ assertEquals(Duration.ofSeconds(30), settings.hangWatcherInterval());
+ assertEquals(Duration.ofSeconds(60), settings.hangGrace());
+ }
+
+ @Test
+ void defaultParallelismFollowsTheProcessorsAndTheBrokerPoolWithinBounds() {
+ final XQSuiteSettings settings = settings(Map.of());
+ assertEquals(2, settings.parallelism(1), "never fewer than two");
+ assertEquals(Math.max(2, Math.min(32, Math.min(Runtime.getRuntime().availableProcessors(), 8))), settings.parallelism(8));
+ assertTrue(settings.parallelism(1000) <= 32, "never more than 32");
+ }
+
+ @Test
+ void settingsOverrideTheDefaults() {
+ final XQSuiteSettings settings = settings(Map.of(
+ XQSuiteSettings.PARALLELISM, "3",
+ XQSuiteSettings.HANG_THRESHOLD_MINUTES, "0.5",
+ XQSuiteSettings.HANG_WATCHER_INTERVAL_SECONDS, "7",
+ XQSuiteSettings.HANG_GRACE_SECONDS, "2"));
+ assertEquals(3, settings.parallelism(1), "an explicit value is used as given");
+ assertEquals(Duration.ofSeconds(30), settings.hangThreshold());
+ assertEquals(Duration.ofSeconds(7), settings.hangWatcherInterval());
+ assertEquals(Duration.ofSeconds(2), settings.hangGrace());
+ }
+
+ @Test
+ void watcherIntervalIsATenthOfTheThresholdWithinBounds() {
+ assertEquals(Duration.ofSeconds(1), settings(Map.of(XQSuiteSettings.HANG_THRESHOLD_MINUTES, "0.04")).hangWatcherInterval());
+ assertEquals(Duration.ofSeconds(6), settings(Map.of(XQSuiteSettings.HANG_THRESHOLD_MINUTES, "1")).hangWatcherInterval());
+ assertEquals(Duration.ofSeconds(30), settings(Map.of(XQSuiteSettings.HANG_THRESHOLD_MINUTES, "60")).hangWatcherInterval());
+ }
+
+ @Test
+ void invalidValuesAreRejectedAndNamed() {
+ assertEquals(true, assertThrows(JUnitException.class, () -> settings(Map.of(XQSuiteSettings.PARALLELISM, "many")).parallelism(8))
+ .getMessage().contains(XQSuiteSettings.PARALLELISM));
+ assertThrows(JUnitException.class, () -> settings(Map.of(XQSuiteSettings.PARALLELISM, "0")).parallelism(8));
+ assertThrows(JUnitException.class, () -> settings(Map.of(XQSuiteSettings.HANG_THRESHOLD_MINUTES, "0")).hangThreshold());
+ assertThrows(JUnitException.class, () -> settings(Map.of(XQSuiteSettings.HANG_WATCHER_INTERVAL_SECONDS, "0")).hangWatcherInterval());
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteTestEngine.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteTestEngine.java
new file mode 100644
index 00000000000..aa90eeaf64d
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteTestEngine.java
@@ -0,0 +1,280 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.exist.storage.BrokerPool;
+import org.exist.test.ExistEmbeddedServer;
+import org.exist.test.runner.AbstractTestRunner;
+import org.exist.test.runner.TestRunners;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.platform.commons.support.HierarchyTraversalMode;
+import org.junit.platform.commons.support.ReflectionSupport;
+import org.junit.platform.engine.EngineDiscoveryRequest;
+import org.junit.platform.engine.EngineExecutionListener;
+import org.junit.platform.engine.ExecutionRequest;
+import org.junit.platform.engine.TestDescriptor;
+import org.junit.platform.engine.TestEngine;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.discovery.ClassSelector;
+import org.junit.platform.engine.discovery.ClasspathRootSelector;
+import org.junit.platform.engine.discovery.PackageSelector;
+import org.junit.platform.engine.support.descriptor.EngineDescriptor;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+/**
+ * A JUnit Platform engine for XQSuite and XML tests.
+ *
+ * Classes annotated with {@link XQSuite} are discovered; each is a container whose children are
+ * the test files (containers) and the tests within them. The tests themselves are run by the
+ * same XQuery runner as before, which reports outcomes through {@link org.exist.test.runner.TestEvents}.
+ */
+public final class XQSuiteTestEngine implements TestEngine {
+ public static final String ENGINE_ID = "exist-xqsuite";
+
+ /**
+ * Set when a database could not be stopped: a test file that was given up on as hung is still running
+ * and keeps the database alive, so no further suite can be run safely in this JVM.
+ */
+ private static volatile String unusableBecause = null;
+
+ /**
+ * Test files are discovered once per file for the life of the JVM: the launcher discovers each
+ * class several times (for example once to decide whether to run it and once to run it).
+ */
+ private static final Map RUNNERS = new ConcurrentHashMap<>();
+
+ @Override
+ public String getId() {
+ return ENGINE_ID;
+ }
+
+ @Override
+ public TestDescriptor discover(final EngineDiscoveryRequest request, final UniqueId uniqueId) {
+ final EngineDescriptor engine = new EngineDescriptor(uniqueId, "eXist-db XQSuite");
+
+ final Set> classes = new LinkedHashSet<>();
+ for (final ClassSelector selector : request.getSelectorsByType(ClassSelector.class)) {
+ classes.add(selector.getJavaClass());
+ }
+ for (final ClasspathRootSelector selector : request.getSelectorsByType(ClasspathRootSelector.class)) {
+ classes.addAll(ReflectionSupport.findAllClassesInClasspathRoot(selector.getClasspathRoot(), c -> true, n -> true));
+ }
+ for (final PackageSelector selector : request.getSelectorsByType(PackageSelector.class)) {
+ classes.addAll(ReflectionSupport.findAllClassesInPackage(selector.getPackageName(), c -> true, n -> true));
+ }
+
+ try (final DiscoveryDatabase discoveryDb = new DiscoveryDatabase()) {
+ for (final Class> clazz : classes) {
+ final XQSuite annotation = clazz.getAnnotation(XQSuite.class);
+ if (annotation != null) {
+ engine.addChild(discoverSuite(engine.getUniqueId(), clazz, annotation, discoveryDb));
+ }
+ }
+ }
+ return engine;
+ }
+
+ /**
+ * An embedded database that is only started if some file needs it for discovery.
+ * XQuery modules may import modules that need the database to compile, and the database is
+ * how tests have always been discovered, so it is used rather than compiling on its own.
+ */
+ private static final class DiscoveryDatabase implements AutoCloseable {
+ private ExistEmbeddedServer server;
+
+ BrokerPool pool() throws Exception {
+ if (server == null) {
+ server = new ExistEmbeddedServer(true, true);
+ server.startDb();
+ }
+ return server.getBrokerPool();
+ }
+
+ @Override
+ public void close() {
+ if (server != null) {
+ server.stopDb();
+ server = null;
+ }
+ }
+ }
+
+ private static SuiteDescriptor discoverSuite(final UniqueId engineId, final Class> clazz, final XQSuite annotation, final DiscoveryDatabase discoveryDb) {
+ final UniqueId suiteId = engineId.append("suite", clazz.getName());
+ final List runners = new ArrayList<>();
+ Throwable failure = null;
+ try {
+ for (final String suite : annotation.value()) {
+ final Path path = Path.of(suite);
+ if (!Files.exists(path)) {
+ throw new IOException("XQSuite does not exist: " + suite + ". path=" + path.toAbsolutePath());
+ }
+ if (Files.isDirectory(path)) {
+ try (final Stream children = Files.list(path)) {
+ final List sorted = children.filter(p -> !Files.isDirectory(p)).sorted(Comparator.comparing(Path::toString)).toList();
+ for (final Path child : sorted) {
+ addRunner(runners, child, discoveryDb);
+ }
+ }
+ } else {
+ addRunner(runners, path, discoveryDb);
+ }
+ }
+ } catch (final Throwable t) {
+ failure = t;
+ }
+
+ final SuiteDescriptor suite = new SuiteDescriptor(suiteId, clazz, annotation.parallel(), failure);
+ for (final AbstractTestRunner runner : runners) {
+ final FileDescriptor file = new FileDescriptor(suiteId.append("file", runner.getSourcePath().toString()), runner);
+ for (final String testName : runner.getTestNames()) {
+ file.addTest(testName);
+ }
+ suite.addChild(file);
+ }
+ return suite;
+ }
+
+ private static void addRunner(final List runners, final Path path, final DiscoveryDatabase discoveryDb) throws Exception {
+ final Path key = path.toAbsolutePath().normalize();
+ AbstractTestRunner runner = RUNNERS.get(key);
+ if (runner == null) {
+ runner = TestRunners.newTestRunner(path, TestRunners.isXQueryTestFile(path) ? discoveryDb.pool() : null);
+ if (runner != null) {
+ RUNNERS.put(key, runner);
+ }
+ }
+ if (runner != null) {
+ runners.add(runner);
+ }
+ }
+
+ @Override
+ public void execute(final ExecutionRequest request) {
+ final EngineExecutionListener listener = request.getEngineExecutionListener();
+ final TestDescriptor root = request.getRootTestDescriptor();
+ listener.executionStarted(root);
+ final XQSuiteSettings settings = new XQSuiteSettings(request.getConfigurationParameters());
+ for (final TestDescriptor suite : new ArrayList<>(root.getChildren())) {
+ runSuite((SuiteDescriptor) suite, listener, settings);
+ }
+ listener.executionFinished(root, TestExecutionResult.successful());
+ }
+
+ private static void runSuite(final SuiteDescriptor suite, final EngineExecutionListener listener, final XQSuiteSettings settings) {
+ final String unusable = unusableBecause;
+ if (unusable != null) {
+ listener.executionSkipped(suite, "Not run: " + unusable);
+ return;
+ }
+ listener.executionStarted(suite);
+ Throwable failure = suite.discoveryFailure();
+ ExistEmbeddedServer server = null;
+ boolean started = false;
+ try {
+ if (failure == null) {
+ server = new ExistEmbeddedServer(true, true);
+ server.startDb();
+ started = true;
+ invokeStatic(suite.suiteClass(), BeforeAll.class);
+ new SuiteRun(suite, listener, server.getBrokerPool(), settings).run();
+ }
+ } catch (final Throwable t) {
+ failure = t;
+ } finally {
+ if (started) {
+ try {
+ invokeStatic(suite.suiteClass(), AfterAll.class);
+ } catch (final Throwable t) {
+ failure = add(failure, t);
+ }
+ try {
+ if (!stopWithin(server::stopDb, settings.hangGrace())) {
+ final String reason = "the embedded database of " + suite.suiteClass().getName() + " could not be stopped within "
+ + settings.hangGrace().toSeconds() + " seconds; a test file that was given up on as hung is probably still running";
+ unusableBecause = reason;
+ failure = add(failure, new IllegalStateException(reason));
+ }
+ } catch (final Throwable t) {
+ failure = add(failure, t);
+ }
+ }
+ }
+ listener.executionFinished(suite, failure == null ? TestExecutionResult.successful() : TestExecutionResult.failed(failure));
+ }
+
+ private static void invokeStatic(final Class> clazz, final Class extends java.lang.annotation.Annotation> annotation) {
+ for (final Method method : ReflectionSupport.findMethods(clazz,
+ m -> Modifier.isStatic(m.getModifiers()) && m.isAnnotationPresent(annotation), HierarchyTraversalMode.TOP_DOWN)) {
+ ReflectionSupport.invokeMethod(method, null);
+ }
+ }
+
+ /**
+ * Runs {@code stop} on a thread of its own and waits for it for at most {@code timeout}.
+ *
+ * @return false if it was still running when the time was up
+ */
+ static boolean stopWithin(final Runnable stop, final java.time.Duration timeout) throws InterruptedException {
+ final java.util.concurrent.atomic.AtomicReference thrown = new java.util.concurrent.atomic.AtomicReference<>();
+ final Thread thread = new Thread(() -> {
+ try {
+ stop.run();
+ } catch (final Throwable t) {
+ thrown.set(t);
+ }
+ }, "xqsuite-stop");
+ thread.setDaemon(true);
+ thread.start();
+ thread.join(timeout.toMillis());
+ if (thread.isAlive()) {
+ return false;
+ }
+ if (thrown.get() != null) {
+ throw new IllegalStateException("The embedded database failed to stop", thrown.get());
+ }
+ return true;
+ }
+
+ private static Throwable add(final Throwable existing, final Throwable additional) {
+ if (existing == null) {
+ return additional;
+ }
+ existing.addSuppressed(additional);
+ return existing;
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteTestEngineTest.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteTestEngineTest.java
new file mode 100644
index 00000000000..5bdf83ba7c9
--- /dev/null
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQSuiteTestEngineTest.java
@@ -0,0 +1,155 @@
+/*
+ * eXist-db Open Source Native XML Database
+ * Copyright (C) 2001 The eXist-db Authors
+ *
+ * info@exist-db.org
+ * http://www.exist-db.org
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+package org.exist.test.xqsuite;
+
+import org.junit.jupiter.api.Test;
+import org.junit.platform.engine.TestExecutionResult;
+import org.junit.platform.testkit.engine.EngineExecutionResults;
+import org.junit.platform.testkit.engine.EngineTestKit;
+import org.junit.platform.testkit.engine.Event;
+import org.opentest4j.AssertionFailedError;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
+
+class XQSuiteTestEngineTest {
+
+ private static final String FIXTURES = "src/test/resources/org/exist/test/runner/";
+
+ @XQSuite(FIXTURES + "single-test.xqm")
+ static class SingleTest {
+ }
+
+ @XQSuite(FIXTURES + "failing-assertion.xqm")
+ static class FailingAssertion {
+ }
+
+ @XQSuite(FIXTURES + "failing-both.xqm")
+ static class FailingBoth {
+ }
+
+ @XQSuite(FIXTURES + "failing-serialization.xqm")
+ static class FailingSerialization {
+ }
+
+ @XQSuite(FIXTURES + "no-tests.xqm")
+ static class NoTests {
+ }
+
+ @XQSuite(FIXTURES + "hyphenated-prefix.xqm")
+ static class HyphenatedPrefix {
+ }
+
+ @XQSuite("src/test/resources/does/not/exist.xqm")
+ static class MissingFile {
+ }
+
+ private static EngineExecutionResults run(final Class> suite) {
+ return EngineTestKit.engine(XQSuiteTestEngine.ENGINE_ID).selectors(selectClass(suite)).execute();
+ }
+
+ private static List failures(final EngineExecutionResults results) {
+ return results.testEvents().failed().list().stream()
+ .map(event -> event.getRequiredPayload(TestExecutionResult.class).getThrowable().orElseThrow())
+ .toList();
+ }
+
+ @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") // assertion is delegated to assertStatistics, which asserts internally
+ @Test
+ void passingTestIsReportedAsSucceeded() {
+ run(SingleTest.class).testEvents().assertStatistics(stats -> stats.started(1).succeeded(1).failed(0).skipped(0));
+ }
+
+ @Test
+ void failingAssertionIsReportedWithExpectedAndActual() {
+ final EngineExecutionResults results = run(FailingAssertion.class);
+ results.testEvents().assertStatistics(stats -> stats.started(1).failed(1));
+
+ final Throwable failure = failures(results).get(0);
+ final AssertionFailedError assertionFailure = assertInstanceOf(AssertionFailedError.class, failure);
+ assertTrue(assertionFailure.getExpected().getStringRepresentation().contains("expected"));
+ assertEquals("actual", assertionFailure.getActual().getValue());
+ }
+
+ @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") // assertion is delegated to assertStatistics, which asserts internally
+ @Test
+ void assertionFailureAndUnexpectedErrorAreBothReported() {
+ run(FailingBoth.class).testEvents().assertStatistics(stats -> stats.started(2).failed(2));
+ }
+
+ @Test
+ void nodeResultIsNotEscapedIntoTheFailureMessage() {
+ final AssertionFailedError failure = assertInstanceOf(AssertionFailedError.class, failures(run(FailingSerialization.class)).get(0));
+ assertEquals("text", failure.getActual().getValue(),
+ "a node-valued result should reach the failure message as markup, not XML-escaped");
+ }
+
+ @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") // assertion is delegated to assertStatistics, which asserts internally
+ @Test
+ void testsAreReportedUnderTheNamesTheyWereDiscoveredWith() {
+ // if discovery and the XQSuite runtime disagreed on a name, a test would be reported twice:
+ // once as passed under the runtime's name and once as failed because it was never reported
+ run(HyphenatedPrefix.class).testEvents().assertStatistics(stats -> stats.started(2).succeeded(2).failed(0));
+ }
+
+ @Test
+ void assertionFailureLeadsToTheXQueryTestFile() {
+ final Throwable failure = failures(run(FailingBoth.class)).stream()
+ .filter(AssertionFailedError.class::isInstance)
+ .findFirst()
+ .orElseThrow();
+
+ final boolean inStackTrace = java.util.Arrays.stream(failure.getStackTrace())
+ .anyMatch(e -> e.getFileName() != null && e.getFileName().contains("failing-both"));
+ final boolean inMessage = failure.getMessage() != null && failure.getMessage().contains("failing-both");
+ assertTrue(inStackTrace || inMessage, "the failure should name the XQuery test file so an IDE can navigate to it");
+ }
+
+ @Test
+ void unexpectedErrorLeadsToTheJavaCode() {
+ final Throwable failure = failures(run(FailingBoth.class)).stream()
+ .filter(org.exist.xquery.XPathException.class::isInstance)
+ .findFirst()
+ .orElseThrow();
+
+ assertTrue(java.util.Arrays.stream(failure.getStackTrace()).anyMatch(e -> e.getClassName().startsWith("org.exist")),
+ "the error should keep its Java stack trace so an IDE can navigate to the code");
+ }
+
+ @SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert") // assertion is delegated to assertStatistics, which asserts internally
+ @Test
+ void fileWithoutTestsRunsNothing() {
+ run(NoTests.class).testEvents().assertStatistics(stats -> stats.started(0));
+ }
+
+ @Test
+ void suiteThatCannotBeDiscoveredFailsInsteadOfDisappearing() {
+ final EngineExecutionResults results = run(MissingFile.class);
+ results.testEvents().assertStatistics(stats -> stats.started(1).failed(1));
+ final List failed = results.testEvents().failed().list();
+ assertEquals(1, failed.size());
+ }
+}
diff --git a/exist-core/src/test/java/org/exist/test/runner/ParallelPassingSuite.java b/exist-core/src/test/java/org/exist/test/xqsuite/XQTestDescriptor.java
similarity index 64%
rename from exist-core/src/test/java/org/exist/test/runner/ParallelPassingSuite.java
rename to exist-core/src/test/java/org/exist/test/xqsuite/XQTestDescriptor.java
index fd6920e50f9..517ef0a4dd0 100644
--- a/exist-core/src/test/java/org/exist/test/runner/ParallelPassingSuite.java
+++ b/exist-core/src/test/java/org/exist/test/xqsuite/XQTestDescriptor.java
@@ -19,20 +19,23 @@
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+package org.exist.test.xqsuite;
-package org.exist.test.runner;
-
-import org.junit.runner.RunWith;
+import org.junit.platform.engine.TestSource;
+import org.junit.platform.engine.UniqueId;
+import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor;
/**
- * XSuite that runs passing XQuery tests in parallel.
- * Used by XSuiteParallelTest to verify dynamic pool and parallel execution.
+ * A single XQSuite test function or XML test.
*/
-@RunWith(XSuite.class)
-@XSuite.XSuiteParallel
-@XSuite.XSuiteFiles({
- "src/test/resources/org/exist/test/runner/single-test.xqm",
- "src/test/resources/org/exist/test/runner/no-tests.xqm"
-})
-public class ParallelPassingSuite {
+final class XQTestDescriptor extends AbstractTestDescriptor {
+
+ XQTestDescriptor(final UniqueId id, final String name, final TestSource source) {
+ super(id, name, source);
+ }
+
+ @Override
+ public Type getType() {
+ return Type.TEST;
+ }
}
diff --git a/exist-core/src/test/java/org/exist/util/io/FilterInputStreamCacheMonitorTest.java b/exist-core/src/test/java/org/exist/util/io/FilterInputStreamCacheMonitorTest.java
index 447d8ebadad..ba04eaf91ed 100644
--- a/exist-core/src/test/java/org/exist/util/io/FilterInputStreamCacheMonitorTest.java
+++ b/exist-core/src/test/java/org/exist/util/io/FilterInputStreamCacheMonitorTest.java
@@ -42,7 +42,10 @@
import java.nio.file.Path;
import org.junit.jupiter.api.extension.RegisterExtension;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.fail;
public class FilterInputStreamCacheMonitorTest {
@RegisterExtension
@@ -95,8 +98,8 @@ public void binaryResult() throws XMLDBException {
assertEquals(1, resourceSet.getSize());
try (final EXistResource resource = (EXistResource)resourceSet.getResource(0)) {
- assertTrue(resource instanceof LocalBinaryResource);
- assertTrue(((ExtendedResource)resource).getExtendedContent() instanceof BinaryValue);
+ assertInstanceOf(LocalBinaryResource.class, resource);
+ assertInstanceOf(BinaryValue.class, ((ExtendedResource) resource).getExtendedContent());
// one active binary (as it is in the result set)
assertEquals(1, monitor.getActive().size());
diff --git a/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java b/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java
index a2fa1a9d290..eee2c158844 100644
--- a/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/CountExpressionTest.java
@@ -33,7 +33,7 @@
import java.io.StringReader;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.fail;
/**
@@ -79,8 +79,8 @@ public void countTest() throws RecognitionException, XPathException, TokenStream
assertEquals(XQueryParser.LITERAL_count, ast.getNextSibling().getFirstChild().getNextSibling().getNextSibling().getType());
// rank variable binding
assertEquals(XQueryParser.VARIABLE_BINDING, ast.getNextSibling().getFirstChild().getNextSibling().getNextSibling().getFirstChild().getType());
- assertTrue(((ForExpr)expr.getFirst()).returnExpr instanceof OrderByClause);
- assertTrue(((OrderByClause)(((ForExpr)expr.getFirst()).returnExpr)).returnExpr instanceof CountClause);
+ assertInstanceOf(OrderByClause.class, ((ForExpr) expr.getFirst()).returnExpr);
+ assertInstanceOf(CountClause.class, ((OrderByClause) (((ForExpr) expr.getFirst()).returnExpr)).returnExpr);
assertEquals(new QName("rank"), ((CountClause)((OrderByClause)(((ForExpr)expr.getFirst()).returnExpr)).returnExpr).varName);
}
}
diff --git a/exist-core/src/test/java/org/exist/xquery/ExpressionVisitorTraversalTest.java b/exist-core/src/test/java/org/exist/xquery/ExpressionVisitorTraversalTest.java
index 9e0c848beef..288f15b3c92 100644
--- a/exist-core/src/test/java/org/exist/xquery/ExpressionVisitorTraversalTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/ExpressionVisitorTraversalTest.java
@@ -30,7 +30,8 @@
import org.junit.jupiter.api.Test;
import antlr.collections.AST;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.StringReader;
import java.util.ArrayList;
diff --git a/exist-core/src/test/java/org/exist/xquery/InternalModuleTest.java b/exist-core/src/test/java/org/exist/xquery/InternalModuleTest.java
index 5c8c6819e61..4631b9a065a 100644
--- a/exist-core/src/test/java/org/exist/xquery/InternalModuleTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/InternalModuleTest.java
@@ -42,7 +42,11 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.exist.xquery.InternalModuleTest.TestModuleWithVariables.*;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Adam Retter
@@ -102,7 +106,7 @@ public void requestResponseSessionVariables_4_x_x_Api() throws XMLDBException {
requestResponseSessionVariablesQuery_4_x_X_Api(queryService, querySource);
fail("Expected XQuery error XPST0008");
} catch (final XMLDBException e) {
- assertTrue(e.getCause() instanceof XPathException);
+ assertInstanceOf(XPathException.class, e.getCause());
final XPathException xpe = (XPathException)e.getCause();
assertEquals(ErrorCodes.XPST0008, xpe.getErrorCode());
}
diff --git a/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java
index 9cd465e0f1a..dc1657d3f74 100644
--- a/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/WindowClauseTest.java
@@ -35,10 +35,11 @@
import java.io.StringReader;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* @author Adam Retter
@@ -74,7 +75,7 @@ void simpleWindowConditions() throws RecognitionException, XPathException, Token
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
}
@Test
@@ -104,7 +105,7 @@ void complexWindowCondition() throws RecognitionException, XPathException, Token
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
}
@Test
@@ -133,7 +134,7 @@ void noEndWindowCondition() throws RecognitionException, XPathException, TokenSt
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
}
@Test
@@ -164,7 +165,7 @@ void slidingWindowClause() throws RecognitionException, XPathException, TokenStr
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType());
}
@@ -198,7 +199,7 @@ void allWindowsVars() throws RecognitionException, XPathException, TokenStreamEx
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals(new QName("first"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertEquals(new QName("s"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
assertEquals(new QName("start-previous"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPreviousItem());
@@ -240,7 +241,7 @@ void tumblingWindowAllWindowVarsNoOnly() throws RecognitionException, XPathExcep
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals(WindowExpr.WindowType.TUMBLING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType());
assertEquals(new QName("s"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertEquals(new QName("spos"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
@@ -281,7 +282,7 @@ void tumblingWindowAvgReturn() throws RecognitionException, XPathException, Toke
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertEquals(new QName("s"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getPreviousItem());
@@ -319,7 +320,7 @@ void tumblingWindowNoEndWindowConditionPositional() throws RecognitionException,
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertEquals(new QName("s"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getPreviousItem());
@@ -353,7 +354,7 @@ void tumblingWindowNoEndWindowConditionCurrentItem() throws RecognitionException
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals("first", ((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem().getStringValue());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getPreviousItem());
@@ -389,7 +390,7 @@ void slidingWindowAvgReturn() throws RecognitionException, XPathException, Token
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertEquals(new QName("s"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
@@ -429,7 +430,7 @@ void slidingWindowEndWithoutOnly() throws RecognitionException, XPathException,
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals(WindowExpr.WindowType.SLIDING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertEquals(new QName("s"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
@@ -475,7 +476,7 @@ void tumblingWindowRunUp() throws RecognitionException, XPathException, TokenStr
fail(treeParser.getErrorMessage());
}
- assertTrue(expr.getFirst() instanceof WindowExpr, "Expression should be of type WindowExpr");
+ assertInstanceOf(WindowExpr.class, expr.getFirst(), "Expression should be of type WindowExpr");
assertEquals(WindowExpr.WindowType.TUMBLING_WINDOW, ((WindowExpr) expr.getFirst()).getWindowType());
assertEquals(new QName("first"), ((WindowExpr) expr.getFirst()).getWindowStartCondition().getCurrentItem());
assertNull(((WindowExpr) expr.getFirst()).getWindowStartCondition().getPosVar());
diff --git a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java
index 9b6aaacb02b..4064a30a715 100644
--- a/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/XPathQueryTest.java
@@ -57,6 +57,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class XPathQueryTest {
@@ -84,7 +85,7 @@ private final String getBaseUri() {
private final static String nested =
""
+ "";
-
+
private final static String numbers =
""
+ "- 5.622
"
@@ -92,7 +93,7 @@ private final String getBaseUri() {
+ "- 18.45
"
+ "- 65.5416
"
+ "";
-
+
private final static String numbers2 =
""
+ "- 5.622
"
@@ -100,7 +101,7 @@ private final String getBaseUri() {
+ "- 18.45
"
+ "- 65.5416
"
+ "";
-
+
private final static String namespaces =
""
+ " "
@@ -108,21 +109,21 @@ private final String getBaseUri() {
+ " This is my comment"
+ " "
+ "";
-
+
private final static String strings =
""
+ "Hello World!"
+ ""
+ "Hello"
+ "";
-
+
private final static String nested2 =
"" +
"" +
"" +
"" +
"";
-
+
private final static String nested3 =
"" +
" " +
@@ -135,7 +136,7 @@ private final String getBaseUri() {
" " +
" " +
"";
-
+
private final static String siblings =
"" +
"" +
@@ -189,16 +190,16 @@ private final String getBaseUri() {
"" +
"]>" +
ids_content;
-
+
private final static String date =
"";
-
+
private final static String quotes =
""Hello"";
-
+
private final static String ws =
" ";
-
+
private final static String self =
"HelloWorld!";
@@ -215,16 +216,16 @@ private final String getBaseUri() {
val2
""";
-
+
// Added by Geoff Shuetrim (geoff@galexy.net) to highlight problems with XPath queries of elements called 'xpointer'.
private final static String xpointerElementName =
"";
private final static String cdata_content = "Hello there \"Bob?\"";
private final static String cdata_xml = "";
-
+
private Collection testCollection;
-
+
private void setUp() throws ReflectiveOperationException, XMLDBException {
// initialize driver
Class> cl = Class.forName("org.exist.xmldb.DatabaseImpl");
@@ -740,7 +741,7 @@ public void followingSiblingAxisPersistent(String apiName, String baseUri) throw
result = queryResource(service, "siblings.xml", "/node()[1]/following-sibling::node()", 3);
assertEquals("", result.getResource(0).getContent().toString());
final Node testElem = ((XMLResource)result.getResource(1)).getContentAsDOM();
- assertTrue(testElem instanceof Element);
+ assertInstanceOf(Element.class, testElem);
assertEquals("test", testElem.getNodeName());
assertEquals("", result.getResource(2).getContent().toString());
@@ -1271,10 +1272,10 @@ public void predicatesBug1460610(String apiName, String baseUri) throws XMLDBExc
initXPathQueryTest(apiName, baseUri);
setUp();
final String xQuery = "(1, 2, 3)[ . lt 3]";
-
+
final XQueryService service = getQueryService();
final ResourceSet rs = service.query(xQuery);
-
+
assertEquals(2, rs.getSize(), "SFBUG 1460610 nr of results");
assertEquals("1", rs.getResource(0).getContent().toString(),
"SFBUG 1460610 1st result");
@@ -1290,10 +1291,10 @@ public void predicatesBug1537355(String apiName, String baseUri) throws XMLDBExc
initXPathQueryTest(apiName, baseUri);
setUp();
final String xQuery = "let $one := 1 return (1, 2, 3)[$one + 1]";
-
+
final XQueryService service = getQueryService();
final ResourceSet rs = service.query(xQuery);
-
+
assertEquals(1, rs.getSize(), "SFBUG 1537355 nr of results");
assertEquals("2", rs.getResource(0).getContent().toString(),
"SFBUG 1537355 result");
@@ -1312,10 +1313,10 @@ public void nestedPredicatesBug1533053(String apiName, String baseUri) throws XM
" " +
"let $matches := $doc/detail[source[dynamic='false'] or class] " +
"return count($matches) eq 2";
-
+
XQueryService service = getQueryService();
ResourceSet rs = service.query(xQuery);
-
+
assertEquals(1, rs.getSize());
assertEquals("true", rs.getResource(0).getContent().toString());
@@ -1326,11 +1327,11 @@ public void nestedPredicatesBug1533053(String apiName, String baseUri) throws XM
service = getQueryService();
service.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
- service.setProperty(OutputKeys.INDENT, "no");
+ service.setProperty(OutputKeys.INDENT, "no");
rs = service.query(xQuery);
assertEquals(1, rs.getSize());
- assertXMLEqual("",
+ assertXMLEqual("",
rs.getResource(0).getContent().toString());
}
@@ -1345,21 +1346,21 @@ public void predicateBug1488303(String apiName, String baseUri) throws XMLDBExce
setUp();
XQueryService service = getQueryService();
ResourceSet rs=null;
-
+
// test one
final String xQuery1 = "let $q := eXist
return $q//t";
rs = service.query(xQuery1);
assertEquals(1, rs.getSize(), "nr of results");
assertEquals("eXist", rs.getResource(0).getContent().toString(),
"result");
-
+
// test two
final String xQuery2 = "let $q := eXist
return ($q//t)[1]";
rs = service.query(xQuery2);
assertEquals(1, rs.getSize(), "nr of results");
assertEquals("eXist", rs.getResource(0).getContent().toString(),
"result");
-
+
// This one fails http://sourceforge.net/tracker/index.php?func=detail&aid=1488303&group_id=17691&atid=117691
final String xQuery3 = "let $q := eXist
return $q//t[1]";
rs = service.query(xQuery3);
@@ -1378,18 +1379,18 @@ public void descendantOrSelfBug1460791(String apiName, String baseUri) throws XM
setUp();
final String xQuery = "declare option exist:serialize \"method=xml indent=no\"; let $test:=aaazzz "
+"return ( {$test//z}, {$test/descendant-or-self::node()/child::z} )";
-
+
final XQueryService service = getQueryService();
final ResourceSet rs = service.query(xQuery);
-
+
// System.out.println("BUG1460791/1" + rs.getResource(0).getContent().toString() );
// System.out.println("BUG1460791/2" + rs.getResource(1).getContent().toString() );
-
+
assertEquals(2, rs.getSize(), "SFBUG 1460791 nr of results");
-
+
assertEquals("zzz", rs.getResource(0).getContent().toString(),
"SFBUG 1460791 result part 1");
-
+
assertEquals("zzz", rs.getResource(1).getContent().toString(),
"SFBUG 1460791 result part 2");
}
@@ -1408,18 +1409,18 @@ public void xpathBug1462120(String apiName, String baseUri) throws XMLDBExceptio
+"let $one:=$list[1] return ( "
+"$m/Unit[string(data(@name)) eq string(data($list[1]/@aaa))],"
+"
,$m/Unit[string(data(@name)) eq string(data($one/@aaa))] )";
-
+
final XQueryService service = getQueryService();
final ResourceSet rs = service.query(xQuery);
-
+
assertEquals(3, rs.getSize(), "SFBUG 1462120 nr of results");
-
+
assertEquals("", rs.getResource(0).getContent().toString(),
"SFBUG 1462120 result part 1");
-
+
assertEquals("
", rs.getResource(1).getContent().toString(),
"SFBUG 1462120 result part 2");
-
+
assertEquals("", rs.getResource(2).getContent().toString(),
"SFBUG 1462120 result part 3");
}
@@ -1436,10 +1437,10 @@ public void predicateBugWiki1(String apiName, String baseUri) throws XMLDBExcept
initXPathQueryTest(apiName, baseUri);
setUp();
final String xQuery = "let $dum := 12 return $dum/el[2]";
-
+
final XQueryService service = getQueryService();
final ResourceSet rs = service.query(xQuery);
-
+
assertEquals(1, rs.getSize(), "Predicate bug wiki_1");
assertEquals("2", rs.getResource(0).getContent().toString(),
"Predicate bug wiki_1");
@@ -1475,11 +1476,11 @@ public void cardinalitySelfBugWiki2(String apiName, String baseUri) throws XMLDB
assertEquals(1, rs.getSize(), "CardinalitySelfBUG bug wiki_2");
assertEquals("employee", rs.getResource(0).getContent().toString(),
"CardinalitySelfBUG bug wiki_2");
-
+
}
/**
- * Problem in VirtualNodeSet which return 2 attributes because it
+ * Problem in VirtualNodeSet which return 2 attributes because it
* computes every level
* @see http://wiki.exist-db.org/space/XQueryBugs
*/
@@ -1493,7 +1494,7 @@ public void virtualNodesetBugWiki3(String apiName, String baseUri) throws XMLDBE
final XQueryService service = getQueryService();
final ResourceSet rs = service.query(xQuery);
-
+
assertEquals(1, rs.getSize(), "VirtualNodesetBUG_wiki_3");
assertEquals("", rs.getResource(0).getContent().toString(),
"VirtualNodesetBUG_wiki_3");
@@ -1575,7 +1576,7 @@ public void castInPredicateBugWiki7(String apiName, String baseUri) throws XMLDB
}
/**
- * Miscomputation of the expression context in where clause when no
+ * Miscomputation of the expression context in where clause when no
* wrapper expression is used. Using, e.g. where data($x/@id) eq "id" works !
*/
@MethodSource("data") @ParameterizedTest(name = "{0}")
@@ -1732,7 +1733,7 @@ public void logicalOr(String apiName, String baseUri) throws XMLDBException, IOE
setUp();
final XQueryService service =
storeXMLStringAndGetQueryService("strings.xml", strings);
-
+
ResourceSet result = queryResource(service, "strings.xml", "{() or ()}", 1);
Resource r = result.getResource(0);
assertXMLEqual("false", r.getContent().toString());
@@ -1828,7 +1829,7 @@ public void idRefsPersistent(String apiName, String baseUri) throws XMLDBExcepti
setUp();
final XQueryService service =
storeXMLStringAndGetQueryService("ids.xml", ids);
-
+
queryResource(service, "ids.xml", "/idref('id2')", 1);
queryResource(service, "ids.xml", "/idref('id1')", 2);
queryResource(service, "ids.xml", "/idref(('id2', 'id1'))", 3);
@@ -1953,7 +1954,7 @@ public void queryResource(String apiName, String baseUri) throws XMLDBException,
XMLResource doc = testCollection.createResource("strings.xml", XMLResource.class);
doc.setContent(strings);
testCollection.storeResource(doc);
-
+
doc = testCollection.createResource("strings2.xml", XMLResource.class);
doc.setContent(strings);
testCollection.storeResource(doc);
@@ -2250,7 +2251,7 @@ public void convertToBoolean(String apiName, String baseUri) throws XMLDBExcepti
.getResource(i).getContent().toString(), "false " + (i + 1));
}
}
-
+
boolean exceptionThrown = false;
String message = "";
try {
@@ -2278,7 +2279,7 @@ public void compile(String apiName, String baseUri) throws XMLDBException, Refle
module namespace foo="urn:foo";
declare function foo:test() { "Hello World! };\
""";
-
+
final EXistXQueryService service = (EXistXQueryService) getQueryService();
boolean exceptionOccurred = false;
try {
@@ -2288,7 +2289,7 @@ public void compile(String apiName, String baseUri) throws XMLDBException, Refle
exceptionOccurred = true;
}
assertTrue(exceptionOccurred, "Expected an exception");
-
+
exceptionOccurred = false;
try {
service.compileAndCheck(invalidModule);
@@ -2350,7 +2351,7 @@ public void substring(String apiName, String baseUri) throws XMLDBException, Ref
initXPathQueryTest(apiName, baseUri);
setUp();
final XQueryService service = getQueryService();
-
+
// Test cases by MIKA
final String validQuery = "substring(\"MK-1234\", 4,1)";
ResourceSet result = queryAndAssert( service, validQuery, 1, validQuery);
diff --git a/exist-core/src/test/java/org/exist/xquery/XQueryContextAttributesTest.java b/exist-core/src/test/java/org/exist/xquery/XQueryContextAttributesTest.java
index 3c1cbd86eb4..d2fecb25445 100644
--- a/exist-core/src/test/java/org/exist/xquery/XQueryContextAttributesTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/XQueryContextAttributesTest.java
@@ -61,10 +61,11 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
/**
@@ -135,10 +136,10 @@ public void attributesOfLibraryModuleContextCleared() throws EXistException, Loc
// get the context of the library module
final Module[] libraryModules = mainQueryContext.getModules("http://mod1");
assertEquals(1, libraryModules.length);
- assertTrue(libraryModules[0] instanceof ExternalModule);
+ assertInstanceOf(ExternalModule.class, libraryModules[0]);
final ExternalModule libraryModule = (ExternalModule) libraryModules[0];
final XQueryContext libraryQueryContext = libraryModule.getContext();
- assertTrue(libraryQueryContext instanceof ModuleContext);
+ assertInstanceOf(ModuleContext.class, libraryQueryContext);
libraryQueryContext.setAttribute("attr1", "value1");
libraryQueryContext.setAttribute("attr2", "value2");
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/CollectionTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/CollectionTest.java
index cefaea0228c..357e5d94a11 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/fn/CollectionTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/CollectionTest.java
@@ -56,7 +56,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+
public class CollectionTest {
@RegisterExtension
@@ -85,7 +86,7 @@ public void doc_dynamicallyAvailableCollection_absoluteUri() throws XPathExcepti
assertFalse(result.isEmpty());
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof Node);
+ assertInstanceOf(Node.class, result.itemAt(0));
final Source expectedSource = Input.fromString(doc).build();
final Source actualSource = Input.fromNode((Node)result.itemAt(0)).build();
@@ -119,7 +120,7 @@ public void doc_dynamicallyAvailableCollection_relativeUri() throws XPathExcepti
assertFalse(result.isEmpty());
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof Node);
+ assertInstanceOf(Node.class, result.itemAt(0));
final Source expectedSource = Input.fromString(doc).build();
final Source actualSource = Input.fromNode((Node)result.itemAt(0)).build();
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/fn/DocTest.java b/exist-core/src/test/java/org/exist/xquery/functions/fn/DocTest.java
index e599ae8935c..d1dc9c5bb9d 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/fn/DocTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/fn/DocTest.java
@@ -40,9 +40,11 @@
import static com.evolvedbinary.j8fu.Either.Left;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+
import org.exist.xmldb.EXistResource;
import org.exist.xmldb.LocalXMLResource;
import org.w3c.dom.Node;
@@ -115,15 +117,15 @@ public void tearDown() throws XMLDBException {
existEmbeddedServer.getRoot().removeResource(existEmbeddedServer.getRoot().getResource("test.xml"));
}
-
+
private void storeResource(final Collection col, final String fileName, final Class extends Resource> type, final String mimeType, final String content) throws XMLDBException {
Resource res = col.createResource(fileName, type);
res.setContent(content);
-
+
if (mimeType != null) {
((EXistResource) res).setMimeType(mimeType);
}
-
+
col.storeResource(res);
}
@@ -172,7 +174,7 @@ public void doc_dynamicallyAvailableDocument_absoluteUri() throws XPathException
assertFalse(result.isEmpty());
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof Node);
+ assertInstanceOf(Node.class, result.itemAt(0));
final Source expectedSource = Input.fromString(doc).build();
final Source actualSource = Input.fromNode((Node)result.itemAt(0)).build();
@@ -206,7 +208,7 @@ public void doc_dynamicallyAvailableDocument_relativeUri() throws XPathException
assertFalse(result.isEmpty());
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof Node);
+ assertInstanceOf(Node.class, result.itemAt(0));
final Source expectedSource = Input.fromString(doc).build();
final Source actualSource = Input.fromNode((Node)result.itemAt(0)).build();
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/inspect/InspectModuleTest.java b/exist-core/src/test/java/org/exist/xquery/functions/inspect/InspectModuleTest.java
index 11b332f83e0..dbf706845f4 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/inspect/InspectModuleTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/inspect/InspectModuleTest.java
@@ -53,9 +53,10 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+
public class InspectModuleTest {
@RegisterExtension
@@ -75,7 +76,7 @@ public class InspectModuleTest {
declare function x:fun1() as xs:string {
"hello from fun1"
};
-
+
(:~
: Some other description.
:
@@ -87,7 +88,7 @@ public class InspectModuleTest {
declare function x:fun2($one as xs:int, $two as xs:float) as xs:string {
"hello from fun2"
};
-
+
(:~
: This is a multiline description and therefore
: spans multiple
@@ -98,7 +99,7 @@ public class InspectModuleTest {
declare function x:fun3() {
"hello from fun3"
};
-
+
(:~
: An annotated function.
:
@@ -154,7 +155,7 @@ public class InspectModuleTest {
""";
private static final String MAIN_MODULE = """
import module namespace inspect = "http://exist-db.org/xquery/inspection";
-
+
inspect:inspect-module(xs:anyURI("xmldb:exist://%s"))/function[@name eq "%s"]
""";
@@ -292,7 +293,7 @@ private static void assertInspection(
assertNotNull(result);
assertEquals(1, result.getItemCount());
final Item item1 = result.itemAt(0);
- assertTrue(item1 instanceof ElementImpl);
+ assertInstanceOf(ElementImpl.class, item1);
final Element function = (Element)item1;
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/request/GetData2Test.java b/exist-core/src/test/java/org/exist/xquery/functions/request/GetData2Test.java
index c672ce71acb..21070fb64c1 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/request/GetData2Test.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/request/GetData2Test.java
@@ -40,7 +40,10 @@
import java.nio.charset.StandardCharsets;
import static org.easymock.EasyMock.*;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
* Unlike {@link GetDataTest} this test tries to test the code of
@@ -88,7 +91,7 @@ public void xmlChunkedTransferNonBlockingAvailable() throws XPathException, IOEx
assertNotNull(result);
assertFalse(result.isEmpty());
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof Document);
+ assertInstanceOf(Document.class, result.itemAt(0));
assertEquals("hello", ((Document) result.itemAt(0)).getDocumentElement().getLocalName());
assertEquals("world", ((Document) result.itemAt(0)).getDocumentElement().getTextContent());
@@ -134,7 +137,7 @@ public void xmlChunkedTransferNonBlockingNoneAvailable() throws XPathException,
assertNotNull(result);
assertFalse(result.isEmpty());
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof Document);
+ assertInstanceOf(Document.class, result.itemAt(0));
assertEquals("hello", ((Document) result.itemAt(0)).getDocumentElement().getLocalName());
assertEquals("world", ((Document) result.itemAt(0)).getDocumentElement().getTextContent());
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/transform/TransformTest.java b/exist-core/src/test/java/org/exist/xquery/functions/transform/TransformTest.java
index 7cb2d336a29..95231b3428b 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/transform/TransformTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/transform/TransformTest.java
@@ -61,9 +61,11 @@
import static com.evolvedbinary.j8fu.tuple.Tuple.Tuple;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+
/**
* @author Adam Retter
*/
@@ -105,18 +107,18 @@ public class TransformTest {
-
+
-
+
-
+
Could not find listOpsFileUri document
-
+
@@ -231,7 +233,7 @@ public void keys() throws EXistException, PermissionDeniedException, XPathExcept
assertEquals(1, sequence.getItemCount());
final Item item = sequence.itemAt(0);
- assertTrue(item instanceof Element);
+ assertInstanceOf(Element.class, item);
final Element dsn_flat = ((Element)item);
assertEquals("DSN_FLAT", dsn_flat.getNodeName());
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/util/EvalTest.java b/exist-core/src/test/java/org/exist/xquery/functions/util/EvalTest.java
index d64d0342e17..47cc04625de 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/util/EvalTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/util/EvalTest.java
@@ -39,11 +39,14 @@
import org.junit.jupiter.api.extension.RegisterExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+
+
/**
*
* @author jim.fuller@webcomposite.com
@@ -164,7 +167,7 @@ public void testEvalSupplyingContextAndVariable() throws XPathException, XMLDBEx
final String r = (String) result.getResource(0).getContent();
assertEquals("3", r);
}
-
+
@org.junit.jupiter.api.Test
public void testEvalSupplyingContextItem() throws XPathException, XMLDBException {
final String query = """
@@ -176,7 +179,7 @@ public void testEvalSupplyingContextItem() throws XPathException, XMLDBException
final String r = (String) result.getResource(0).getContent();
assertEquals("London", r);
}
-
+
@org.junit.jupiter.api.Test
public void evalInContextWithPreDeclaredNamespace() throws XMLDBException {
createCollection("testEvalInContextWithPreDeclaredNamespace");
@@ -188,7 +191,7 @@ public void evalInContextWithPreDeclaredNamespace() throws XMLDBException {
"util:eval($q)";
existEmbeddedServer.executeQuery(query);
}
-
+
@org.junit.jupiter.api.Test
public void evalInContextWithPreDeclaredNamespaceAcrossLocalFunctionBoundary() throws XMLDBException {
createCollection("testEvalInContextWithPreDeclaredNamespace");
@@ -203,7 +206,7 @@ public void evalInContextWithPreDeclaredNamespaceAcrossLocalFunctionBoundary() t
"local:process($q)";
existEmbeddedServer.executeQuery(query);
}
-
+
//should fail with - Error while evaluating expression: /db:article. XPST0081: No namespace defined for prefix db [at line 5, column 9]
@org.junit.jupiter.api.Test
public void evalInContextWithPreDeclaredNamespaceAcrossModuleBoundary() {
@@ -267,7 +270,7 @@ public void evalWithMissingVariableReferenceShouldReportTheSameErrorEachTime() t
executeModule(testHome, testModuleName);
} catch(final XMLDBException e) {
final Throwable cause = e.getCause();
- assertTrue(cause instanceof XPathException);
+ assertInstanceOf(XPathException.class, cause);
assertEquals(ErrorCodes.XPDY0002, ((XPathException) cause).getErrorCode());
}
@@ -276,11 +279,11 @@ public void evalWithMissingVariableReferenceShouldReportTheSameErrorEachTime() t
executeModule(testHome, testModuleName);
} catch(final XMLDBException e) {
final Throwable cause = e.getCause();
- assertTrue(cause instanceof XPathException);
+ assertInstanceOf(XPathException.class, cause);
assertEquals(ErrorCodes.XPDY0002, ((XPathException)cause).getErrorCode());
}
}
-
+
private Collection createCollection(String collectionName) throws XMLDBException {
Collection collection = existEmbeddedServer.getRoot().getChildCollection(collectionName);
if (collection == null) {
@@ -399,7 +402,7 @@ public void evalPassErrorInfo() {
assertTrue(e.getMessage().contains("column 5"));
}
}
-
+
private void writeModule(Collection collection, String modulename, String module) throws XMLDBException {
BinaryResource res = collection.createResource(modulename, BinaryResource.class);
((EXistResource) res).setMimeType("application/xquery");
diff --git a/exist-core/src/test/java/org/exist/xquery/functions/util/ParseHtmlTest.java b/exist-core/src/test/java/org/exist/xquery/functions/util/ParseHtmlTest.java
index 9e7fbeefa14..b552eb2dd8f 100644
--- a/exist-core/src/test/java/org/exist/xquery/functions/util/ParseHtmlTest.java
+++ b/exist-core/src/test/java/org/exist/xquery/functions/util/ParseHtmlTest.java
@@ -37,7 +37,9 @@
import javax.xml.transform.Source;
import org.junit.jupiter.api.extension.RegisterExtension;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
public class ParseHtmlTest {
@RegisterExtension
@@ -51,7 +53,7 @@ public void parseHtml() throws EXistException, PermissionDeniedException, XPathE
try (final DBBroker broker = server.getBrokerPool().getBroker()) {
final Sequence result = xquery.execute(broker, query, null);
assertEquals(1, result.getItemCount());
- assertTrue(result.itemAt(0) instanceof DocumentImpl);
+ assertInstanceOf(DocumentImpl.class, result.itemAt(0));
final Source expected = Input.fromString("hello 
").build();
final Source actual = Input.fromDocument((DocumentImpl) result.itemAt(0)).build();
diff --git a/exist-core/src/test/java/xquery/CoreTests.java b/exist-core/src/test/java/xquery/CoreTests.java
index 345ffcd61be..8163db386de 100644
--- a/exist-core/src/test/java/xquery/CoreTests.java
+++ b/exist-core/src/test/java/xquery/CoreTests.java
@@ -21,11 +21,9 @@
*/
package xquery;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery"
})
public class CoreTests {
diff --git a/exist-core/src/test/java/xquery/arrays/ArrayTests.java b/exist-core/src/test/java/xquery/arrays/ArrayTests.java
index df277f81aa6..162ef518c53 100644
--- a/exist-core/src/test/java/xquery/arrays/ArrayTests.java
+++ b/exist-core/src/test/java/xquery/arrays/ArrayTests.java
@@ -21,11 +21,9 @@
*/
package xquery.arrays;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/arrays"
})
public class ArrayTests {
diff --git a/exist-core/src/test/java/xquery/dates/DateTests.java b/exist-core/src/test/java/xquery/dates/DateTests.java
index 6b63f055642..12b44d0d2ac 100644
--- a/exist-core/src/test/java/xquery/dates/DateTests.java
+++ b/exist-core/src/test/java/xquery/dates/DateTests.java
@@ -22,11 +22,9 @@
package xquery.dates;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/dates"
})
public class DateTests {
diff --git a/exist-core/src/test/java/xquery/indexing/IndexingTests.java b/exist-core/src/test/java/xquery/indexing/IndexingTests.java
index 662739bd79f..1facc44457a 100644
--- a/exist-core/src/test/java/xquery/indexing/IndexingTests.java
+++ b/exist-core/src/test/java/xquery/indexing/IndexingTests.java
@@ -21,11 +21,9 @@
*/
package xquery.indexing;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/indexing"
})
public class IndexingTests {
diff --git a/exist-core/src/test/java/xquery/inspect/InspectTests.java b/exist-core/src/test/java/xquery/inspect/InspectTests.java
index 7ee25954377..4937937ff5e 100644
--- a/exist-core/src/test/java/xquery/inspect/InspectTests.java
+++ b/exist-core/src/test/java/xquery/inspect/InspectTests.java
@@ -22,11 +22,9 @@
package xquery.inspect;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/inspect"
})
public class InspectTests {
diff --git a/exist-core/src/test/java/xquery/logicalOp/LogicalOpTests.java b/exist-core/src/test/java/xquery/logicalOp/LogicalOpTests.java
index f4901afb113..1af0d0ce388 100644
--- a/exist-core/src/test/java/xquery/logicalOp/LogicalOpTests.java
+++ b/exist-core/src/test/java/xquery/logicalOp/LogicalOpTests.java
@@ -21,11 +21,9 @@
*/
package xquery.logicalOp;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/logicalOp"
})
public class LogicalOpTests {
diff --git a/exist-core/src/test/java/xquery/maps/MapTests.java b/exist-core/src/test/java/xquery/maps/MapTests.java
index bd5f02f9588..02446f4b77d 100644
--- a/exist-core/src/test/java/xquery/maps/MapTests.java
+++ b/exist-core/src/test/java/xquery/maps/MapTests.java
@@ -21,11 +21,9 @@
*/
package xquery.maps;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/maps"
})
public class MapTests {
diff --git a/exist-core/src/test/java/xquery/numbers/NumberTests.java b/exist-core/src/test/java/xquery/numbers/NumberTests.java
index f970e999e71..6bcc5b0a62e 100644
--- a/exist-core/src/test/java/xquery/numbers/NumberTests.java
+++ b/exist-core/src/test/java/xquery/numbers/NumberTests.java
@@ -21,11 +21,9 @@
*/
package xquery.numbers;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/numbers"
})
public class NumberTests {
diff --git a/exist-core/src/test/java/xquery/numericOp/NumericOpTests.java b/exist-core/src/test/java/xquery/numericOp/NumericOpTests.java
index 6e952eb779f..86714e63e95 100644
--- a/exist-core/src/test/java/xquery/numericOp/NumericOpTests.java
+++ b/exist-core/src/test/java/xquery/numericOp/NumericOpTests.java
@@ -21,12 +21,14 @@
*/
package xquery.numericOp;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+/**
+ * The files of this suite only evaluate expressions on numbers: none reads or writes the database, so they
+ * do not depend on each other and can run concurrently.
+ */
+@XQSuite(value = {
"src/test/xquery/numericOp"
-})
+}, parallel = true)
public class NumericOpTests {
}
diff --git a/exist-core/src/test/java/xquery/optimizer/OptimizerTests.java b/exist-core/src/test/java/xquery/optimizer/OptimizerTests.java
index 9094fd5427c..96c98cc4ecd 100644
--- a/exist-core/src/test/java/xquery/optimizer/OptimizerTests.java
+++ b/exist-core/src/test/java/xquery/optimizer/OptimizerTests.java
@@ -21,14 +21,12 @@
*/
package xquery.optimizer;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
/**
* XQuery optimizer tests
*/
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/optimizer"
})
public class OptimizerTests {
diff --git a/exist-core/src/test/java/xquery/securitymanager/SecurityManagerTests.java b/exist-core/src/test/java/xquery/securitymanager/SecurityManagerTests.java
index b571c2cb95d..43db642e4df 100644
--- a/exist-core/src/test/java/xquery/securitymanager/SecurityManagerTests.java
+++ b/exist-core/src/test/java/xquery/securitymanager/SecurityManagerTests.java
@@ -21,11 +21,9 @@
*/
package xquery.securitymanager;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/securitymanager"
})
public class SecurityManagerTests {
diff --git a/exist-core/src/test/java/xquery/system/SystemTests.java b/exist-core/src/test/java/xquery/system/SystemTests.java
index 10459f0d641..4c716177404 100644
--- a/exist-core/src/test/java/xquery/system/SystemTests.java
+++ b/exist-core/src/test/java/xquery/system/SystemTests.java
@@ -21,11 +21,9 @@
*/
package xquery.system;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/system"
})
public class SystemTests {
diff --git a/exist-core/src/test/java/xquery/util/UtilTests.java b/exist-core/src/test/java/xquery/util/UtilTests.java
index fa52381a400..a80994b511c 100644
--- a/exist-core/src/test/java/xquery/util/UtilTests.java
+++ b/exist-core/src/test/java/xquery/util/UtilTests.java
@@ -21,11 +21,9 @@
*/
package xquery.util;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/util"
})
public class UtilTests {
diff --git a/exist-core/src/test/java/xquery/validation/ValidationTests.java b/exist-core/src/test/java/xquery/validation/ValidationTests.java
index d63e78d19e4..4f415b391d6 100644
--- a/exist-core/src/test/java/xquery/validation/ValidationTests.java
+++ b/exist-core/src/test/java/xquery/validation/ValidationTests.java
@@ -22,11 +22,9 @@
package xquery.validation;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/validation"
})
public class ValidationTests {
diff --git a/exist-core/src/test/java/xquery/xinclude/XIncludeTests.java b/exist-core/src/test/java/xquery/xinclude/XIncludeTests.java
index a51244e1e0c..2c038061904 100644
--- a/exist-core/src/test/java/xquery/xinclude/XIncludeTests.java
+++ b/exist-core/src/test/java/xquery/xinclude/XIncludeTests.java
@@ -21,11 +21,9 @@
*/
package xquery.xinclude;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/xinclude"
})
public class XIncludeTests {
diff --git a/exist-core/src/test/java/xquery/xmldb/XMLDBTests.java b/exist-core/src/test/java/xquery/xmldb/XMLDBTests.java
index 166ae6fcace..b3a45ce08eb 100644
--- a/exist-core/src/test/java/xquery/xmldb/XMLDBTests.java
+++ b/exist-core/src/test/java/xquery/xmldb/XMLDBTests.java
@@ -21,11 +21,9 @@
*/
package xquery.xmldb;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/xmldb"
})
public class XMLDBTests {
diff --git a/exist-core/src/test/java/xquery/xqsuite/XQSuiteTests.java b/exist-core/src/test/java/xquery/xqsuite/XQSuiteTests.java
index ae84b31e7c1..fd49de251d3 100644
--- a/exist-core/src/test/java/xquery/xqsuite/XQSuiteTests.java
+++ b/exist-core/src/test/java/xquery/xqsuite/XQSuiteTests.java
@@ -22,11 +22,9 @@
package xquery.xqsuite;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/xqsuite"
})
public class XQSuiteTests {
diff --git a/exist-core/src/test/java/xquery/xquery3/XQuery3Tests.java b/exist-core/src/test/java/xquery/xquery3/XQuery3Tests.java
index 1363a2bd93a..0e12851ec6d 100644
--- a/exist-core/src/test/java/xquery/xquery3/XQuery3Tests.java
+++ b/exist-core/src/test/java/xquery/xquery3/XQuery3Tests.java
@@ -21,11 +21,9 @@
*/
package xquery.xquery3;
-import org.exist.test.runner.XSuite;
-import org.junit.runner.RunWith;
+import org.exist.test.xqsuite.XQSuite;
-@RunWith(XSuite.class)
-@XSuite.XSuiteFiles({
+@XQSuite({
"src/test/xquery/xquery3",
"src/test/xquery/xquery3/transform",
"src/test/xquery/transform",
diff --git a/exist-core/src/test/resources/META-INF/services/org.junit.platform.engine.TestEngine b/exist-core/src/test/resources/META-INF/services/org.junit.platform.engine.TestEngine
new file mode 100644
index 00000000000..d35f1554a4e
--- /dev/null
+++ b/exist-core/src/test/resources/META-INF/services/org.junit.platform.engine.TestEngine
@@ -0,0 +1 @@
+org.exist.test.xqsuite.XQSuiteTestEngine
diff --git a/exist-core/src/test/resources/org/exist/test/runner/hyphenated-prefix.xqm b/exist-core/src/test/resources/org/exist/test/runner/hyphenated-prefix.xqm
new file mode 100644
index 00000000000..7b66d66dec4
--- /dev/null
+++ b/exist-core/src/test/resources/org/exist/test/runner/hyphenated-prefix.xqm
@@ -0,0 +1,41 @@
+(:
+ : eXist-db Open Source Native XML Database
+ : Copyright (C) 2001 The eXist-db Authors
+ :
+ : info@exist-db.org
+ : http://www.exist-db.org
+ :
+ : This library is free software; you can redistribute it and/or
+ : modify it under the terms of the GNU Lesser General Public
+ : License as published by the Free Software Foundation; either
+ : version 2.1 of the License, or (at your option) any later version.
+ :
+ : This library is distributed in the hope that it will be useful,
+ : but WITHOUT ANY WARRANTY; without even the implied warranty of
+ : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ : Lesser General Public License for more details.
+ :
+ : You should have received a copy of the GNU Lesser General Public
+ : License along with this library; if not, write to the Free Software
+ : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ :)
+xquery version "3.1";
+
+(: A prefix containing a hyphen is not made only of word characters, so the XQSuite runtime
+ : reports the unannotated test as "my-tests:f1" rather than "f1". :)
+module namespace my-tests = "http://exist-db.org/xquery/hyphenated-prefix-module";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+declare
+ %test:assertEquals("hello")
+function my-tests:f1() {
+ "hello"
+};
+
+declare
+ %test:name("explicitly named")
+ %test:assertEquals("hello")
+function my-tests:f2() {
+ "hello"
+};
diff --git a/exist-core/src/main/resources/org/exist/test/runner/xml-test-runner.xq b/exist-core/src/test/resources/org/exist/test/runner/xml-test-runner.xq
similarity index 100%
rename from exist-core/src/main/resources/org/exist/test/runner/xml-test-runner.xq
rename to exist-core/src/test/resources/org/exist/test/runner/xml-test-runner.xq
diff --git a/exist-core/src/main/resources/org/exist/test/runner/xquery-discovery.xq b/exist-core/src/test/resources/org/exist/test/runner/xquery-discovery.xq
similarity index 78%
rename from exist-core/src/main/resources/org/exist/test/runner/xquery-discovery.xq
rename to exist-core/src/test/resources/org/exist/test/runner/xquery-discovery.xq
index 7ddc929e8ee..5b68207e9d6 100644
--- a/exist-core/src/main/resources/org/exist/test/runner/xquery-discovery.xq
+++ b/exist-core/src/test/resources/org/exist/test/runner/xquery-discovery.xq
@@ -43,8 +43,16 @@ return
attribute namespace { namespace-uri-from-QName($name) },
attribute prefix { prefix-from-QName($name) },
for $f in $test-functions
+ let $meta := util:inspect-function($f)
+ let $name-annotation := $meta/annotation[matches(@name, ":name")]
return element f {
- attribute name { local-name-from-QName(function-name($f)) },
+ (: must match test:get-test-name in xqsuite.xql, which names the test in the reports :)
+ attribute name {
+ if ($name-annotation) then
+ string(($name-annotation/value)[1])
+ else
+ replace($meta/@name, "^\w+:([^:]+)$", "$1")
+ },
attribute arity { function-arity($f) }
}
)
diff --git a/exist-core/src/main/resources/org/exist/test/runner/xquery-test-runner.xq b/exist-core/src/test/resources/org/exist/test/runner/xquery-test-runner.xq
similarity index 100%
rename from exist-core/src/main/resources/org/exist/test/runner/xquery-test-runner.xq
rename to exist-core/src/test/resources/org/exist/test/runner/xquery-test-runner.xq
diff --git a/exist-core/src/test/resources/org/exist/test/runner/hanging.xqm b/exist-core/src/test/resources/org/exist/test/xqsuite/hang.xqm
similarity index 76%
rename from exist-core/src/test/resources/org/exist/test/runner/hanging.xqm
rename to exist-core/src/test/resources/org/exist/test/xqsuite/hang.xqm
index 851c0941822..ac7e99f30e0 100644
--- a/exist-core/src/test/resources/org/exist/test/runner/hanging.xqm
+++ b/exist-core/src/test/resources/org/exist/test/xqsuite/hang.xqm
@@ -21,19 +21,14 @@
:)
xquery version "3.1";
-module namespace hang = "http://exist-db.org/xquery/hang";
+module namespace hang = "http://exist-db.org/xquery/xqsuite-fixture/hang";
declare namespace test = "http://exist-db.org/xquery/xqsuite";
-declare function hang:loop() {
- hang:loop()
-};
-
+(: sleeps for ten minutes, unless it is interrupted, in which case util:wait just returns :)
declare
- %test:assertEquals(1)
-function hang:neverReached() {
- 1
+ %test:assertEquals("woke")
+function hang:hangs() {
+ util:wait(600000),
+ "woke"
};
-
-(: Evaluated when module is used; never returns so no ext: callback is ever fired :)
-declare variable $hang:blocker := hang:loop();
diff --git a/exist-core/src/test/resources/org/exist/test/xqsuite/wait-a.xqm b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-a.xqm
new file mode 100644
index 00000000000..60afb75be64
--- /dev/null
+++ b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-a.xqm
@@ -0,0 +1,34 @@
+(:
+ : eXist-db Open Source Native XML Database
+ : Copyright (C) 2001 The eXist-db Authors
+ :
+ : info@exist-db.org
+ : http://www.exist-db.org
+ :
+ : This library is free software; you can redistribute it and/or
+ : modify it under the terms of the GNU Lesser General Public
+ : License as published by the Free Software Foundation; either
+ : version 2.1 of the License, or (at your option) any later version.
+ :
+ : This library is distributed in the hope that it will be useful,
+ : but WITHOUT ANY WARRANTY; without even the implied warranty of
+ : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ : Lesser General Public License for more details.
+ :
+ : You should have received a copy of the GNU Lesser General Public
+ : License along with this library; if not, write to the Free Software
+ : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ :)
+xquery version "3.1";
+
+module namespace wa = "http://exist-db.org/xquery/xqsuite-fixture/wait-a";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+declare
+ %test:assertEquals("done")
+function wa:waits() {
+ (: long enough that files run side by side if, and only if, they are run concurrently :)
+ util:wait(2500),
+ "done"
+};
diff --git a/exist-core/src/test/resources/org/exist/test/xqsuite/wait-b.xqm b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-b.xqm
new file mode 100644
index 00000000000..af270b18280
--- /dev/null
+++ b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-b.xqm
@@ -0,0 +1,34 @@
+(:
+ : eXist-db Open Source Native XML Database
+ : Copyright (C) 2001 The eXist-db Authors
+ :
+ : info@exist-db.org
+ : http://www.exist-db.org
+ :
+ : This library is free software; you can redistribute it and/or
+ : modify it under the terms of the GNU Lesser General Public
+ : License as published by the Free Software Foundation; either
+ : version 2.1 of the License, or (at your option) any later version.
+ :
+ : This library is distributed in the hope that it will be useful,
+ : but WITHOUT ANY WARRANTY; without even the implied warranty of
+ : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ : Lesser General Public License for more details.
+ :
+ : You should have received a copy of the GNU Lesser General Public
+ : License along with this library; if not, write to the Free Software
+ : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ :)
+xquery version "3.1";
+
+module namespace wb = "http://exist-db.org/xquery/xqsuite-fixture/wait-b";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+declare
+ %test:assertEquals("done")
+function wb:waits() {
+ (: long enough that files run side by side if, and only if, they are run concurrently :)
+ util:wait(2500),
+ "done"
+};
diff --git a/exist-core/src/test/resources/org/exist/test/xqsuite/wait-c.xqm b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-c.xqm
new file mode 100644
index 00000000000..4ec41601604
--- /dev/null
+++ b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-c.xqm
@@ -0,0 +1,34 @@
+(:
+ : eXist-db Open Source Native XML Database
+ : Copyright (C) 2001 The eXist-db Authors
+ :
+ : info@exist-db.org
+ : http://www.exist-db.org
+ :
+ : This library is free software; you can redistribute it and/or
+ : modify it under the terms of the GNU Lesser General Public
+ : License as published by the Free Software Foundation; either
+ : version 2.1 of the License, or (at your option) any later version.
+ :
+ : This library is distributed in the hope that it will be useful,
+ : but WITHOUT ANY WARRANTY; without even the implied warranty of
+ : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ : Lesser General Public License for more details.
+ :
+ : You should have received a copy of the GNU Lesser General Public
+ : License along with this library; if not, write to the Free Software
+ : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ :)
+xquery version "3.1";
+
+module namespace wc = "http://exist-db.org/xquery/xqsuite-fixture/wait-c";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+declare
+ %test:assertEquals("done")
+function wc:waits() {
+ (: long enough that files run side by side if, and only if, they are run concurrently :)
+ util:wait(2500),
+ "done"
+};
diff --git a/exist-core/src/test/resources/org/exist/test/xqsuite/wait-d.xqm b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-d.xqm
new file mode 100644
index 00000000000..26af65031d0
--- /dev/null
+++ b/exist-core/src/test/resources/org/exist/test/xqsuite/wait-d.xqm
@@ -0,0 +1,34 @@
+(:
+ : eXist-db Open Source Native XML Database
+ : Copyright (C) 2001 The eXist-db Authors
+ :
+ : info@exist-db.org
+ : http://www.exist-db.org
+ :
+ : This library is free software; you can redistribute it and/or
+ : modify it under the terms of the GNU Lesser General Public
+ : License as published by the Free Software Foundation; either
+ : version 2.1 of the License, or (at your option) any later version.
+ :
+ : This library is distributed in the hope that it will be useful,
+ : but WITHOUT ANY WARRANTY; without even the implied warranty of
+ : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ : Lesser General Public License for more details.
+ :
+ : You should have received a copy of the GNU Lesser General Public
+ : License along with this library; if not, write to the Free Software
+ : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ :)
+xquery version "3.1";
+
+module namespace wd = "http://exist-db.org/xquery/xqsuite-fixture/wait-d";
+
+declare namespace test = "http://exist-db.org/xquery/xqsuite";
+
+declare
+ %test:assertEquals("done")
+function wd:waits() {
+ (: long enough that files run side by side if, and only if, they are run concurrently :)
+ util:wait(2500),
+ "done"
+};
diff --git a/exist-distribution/pom.xml b/exist-distribution/pom.xml
index 3161c396fff..5ab44383fb7 100644
--- a/exist-distribution/pom.xml
+++ b/exist-distribution/pom.xml
@@ -69,12 +69,6 @@
exist-core
${project.version}
runtime
-
-
- junit
- junit
-
-