• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.85
/exist-core/src/main/java/org/exist/test/runner/XQueryTestRunner.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.test.runner;
50

51
import com.evolvedbinary.j8fu.tuple.Tuple2;
52
import org.exist.EXistException;
53
import org.exist.dom.QName;
54
import org.exist.repo.ExistRepository;
55
import org.exist.security.PermissionDeniedException;
56
import org.exist.source.ClassLoaderSource;
57
import org.exist.source.FileSource;
58
import org.exist.source.Source;
59
import org.exist.storage.BrokerPool;
60
import org.exist.storage.BrokerPoolServiceException;
61
import org.exist.util.Configuration;
62
import org.exist.util.ConfigurationHelper;
63
import org.exist.util.DatabaseConfigurationException;
64
import org.exist.util.FileUtils;
65
import org.exist.xquery.*;
66
import org.exist.xquery.value.AnyURIValue;
67
import org.exist.xquery.value.FunctionReference;
68
import org.junit.runner.Description;
69
import org.junit.runner.notification.RunNotifier;
70
import org.junit.runners.model.InitializationError;
71

72
import java.io.IOException;
73
import java.net.URI;
74
import java.net.URISyntaxException;
75
import java.nio.file.Files;
76
import java.nio.file.Path;
77
import java.nio.file.Paths;
78
import java.util.*;
79

80
import static java.nio.charset.StandardCharsets.UTF_8;
81

82
/**
83
 * A JUnit test runner which can run the XQuery tests (XQSuite)
84
 * using $EXIST_HOME/src/org/exist/xquery/lib/xqsuite/xqsuite.xql.
85
 *
86
 * @author Adam Retter
87
 */
88
public class XQueryTestRunner extends AbstractTestRunner {
89

90
    private static final String XQSUITE_NAMESPACE = "http://exist-db.org/xquery/xqsuite";
91

92
    private final XQueryTestInfo info;
93

94
    /**
95
     * @param path The path to the XQuery file containing the XQSuite tests
96
     * @param parallel whether the tests should be run in parallel.
97
     *
98
     * @throws InitializationError if the test runner could not be constructed.
99
     */
100
    public XQueryTestRunner(final Path path, final boolean parallel) throws InitializationError {
101
        super(path, parallel);
1✔
102
        this.info = extractTestInfo(path);
1✔
103
    }
1✔
104

105
    private static Configuration getConfiguration() throws DatabaseConfigurationException {
106
        final Optional<Path> home = Optional.ofNullable(System.getProperty("exist.home", System.getProperty("user.dir"))).map(Paths::get);
1✔
107
        final Path confFile = ConfigurationHelper.lookup("conf.xml", home);
1✔
108

109
        if (confFile.isAbsolute() && Files.exists(confFile)) {
1!
110
            return new Configuration(confFile.toAbsolutePath().toString());
×
111
        } else {
112
            return new Configuration(FileUtils.fileName(confFile), home);
1✔
113
        }
114
    }
115

116
    private static XQueryTestInfo extractTestInfo(final Path path) throws InitializationError {
117
        try {
118
            final Configuration config = getConfiguration();
1✔
119

120
            final ExistRepository expathRepo = new ExistRepository();
1✔
121
            try {
122
                expathRepo.configure(config);
1✔
123
                expathRepo.prepare(null);
1✔
124
            } catch (final BrokerPoolServiceException e) {
1✔
125
                throw new InitializationError(e);
×
126
            }
127

128
            final XQueryContext xqueryContext = new XQueryContext(config);
1✔
129
            try {
130
                xqueryContext.setTestRepository(Optional.of(expathRepo));
1✔
131

132
                final Source xquerySource = new FileSource(path, UTF_8, false);
1✔
133
                final XQuery xquery = new XQuery();
1✔
134

135
                final CompiledXQuery compiledXQuery = xquery.compile(xqueryContext, xquerySource);
1✔
136

137
                String moduleNsPrefix = null;
1✔
138
                String moduleNsUri = null;
1✔
139
                final List<XQueryTestInfo.TestFunctionDef> testFunctions = new ArrayList<>();
1✔
140

141
                final Iterator<UserDefinedFunction> localFunctions = compiledXQuery.getContext().localFunctions();
1✔
142
                while (localFunctions.hasNext()) {
1✔
143
                    final UserDefinedFunction localFunction = localFunctions.next();
1✔
144
                    final FunctionSignature localFunctionSignature = localFunction.getSignature();
1✔
145

146
                    String testName = null;
1✔
147
                    int testArity = 0;
1✔
148
                    boolean isTest = false;
1✔
149

150
                    final Annotation[] annotations = localFunctionSignature.getAnnotations();
1✔
151
                    if (annotations != null) {
1!
152
                        for (final Annotation annotation : annotations) {
1✔
153
                            final QName annotationName = annotation.getName();
1✔
154
                            if (annotationName.getNamespaceURI().equals(XQSUITE_NAMESPACE)) {
1✔
155
                                if (annotationName.getLocalPart().startsWith("assert")) {
1✔
156
                                    isTest = true;
1✔
157
                                    if (testName != null) {
1✔
158
                                        break;
1✔
159
                                    }
160
                                } else if (annotationName.getLocalPart().equals("name")) {
1✔
161
                                    final LiteralValue[] annotationValues = annotation.getValue();
1✔
162
                                    if (annotationValues != null && annotationValues.length > 0) {
1!
163
                                        testName = annotationValues[0].getValue().getStringValue();
1✔
164
                                        if (isTest) {
1✔
165
                                            break;
1✔
166
                                        }
167
                                    }
168
                                }
169
                            }
170
                        }
171
                    }
172

173
                    if (isTest) {
1✔
174
                        if (testName == null) {
1✔
175
                            testName = localFunctionSignature.getName().getLocalPart();
1✔
176
                            testArity = localFunctionSignature.getArgumentCount();
1✔
177
                        }
178

179
                        if (moduleNsPrefix == null) {
1✔
180
                            moduleNsPrefix = localFunctionSignature.getName().getPrefix();
1✔
181
                        }
182
                        if (moduleNsUri == null) {
1✔
183
                            moduleNsUri = localFunctionSignature.getName().getNamespaceURI();
1✔
184
                        }
185

186
                        testFunctions.add(new XQueryTestInfo.TestFunctionDef(testName, testArity));
1✔
187
                    }
188
                } // end while
189

190
                return new XQueryTestInfo(moduleNsPrefix, moduleNsUri, testFunctions);
1✔
191
            } finally {
192
                xqueryContext.runCleanupTasks();
1✔
193
                xqueryContext.reset();
1✔
194
            }
195

196
        } catch (final DatabaseConfigurationException | IOException | PermissionDeniedException | XPathException e) {
×
197
            throw new InitializationError(e);
×
198
        }
199
    }
200

201
    private String getSuiteName() {
202
        if (info.getNamespace() == null) {
1✔
203
            return path.getFileName().toString();
1✔
204
        }
205

206
        return namespaceToPackageName(info.getNamespace());
1✔
207
    }
208

209
    private String namespaceToPackageName(final String namespace) {
210
        try {
211
            final URI uri = new URI(namespace);
1✔
212
            final StringBuilder packageName = new StringBuilder();
1✔
213
            hostNameToPackageName(uri.getHost(), packageName);
1✔
214
            pathToPackageName(uri.getPath(), packageName);
1✔
215
            packageName.insert(0, "xqts.");  // add "xqts." prefix
1✔
216
            return packageName.toString();
1✔
217
        } catch (final URISyntaxException e) {
×
218
            throw new RuntimeException(e);
×
219
        }
220
    }
221

222
    private void hostNameToPackageName(String host, final StringBuilder buffer) {
223
        while (host != null && !host.isEmpty()) {
1!
224
            if (buffer.length() > 0) {
1✔
225
                buffer.append('.');
1✔
226
            }
227

228
            final int idx = host.lastIndexOf('.');
1✔
229
            if (idx > -1) {
1✔
230
                buffer.append(host.substring(idx + 1));
1✔
231
                host = host.substring(0, idx);
1✔
232
            } else {
1✔
233
                buffer.append(host);
1✔
234
                host = null;
1✔
235
            }
236
        }
237
    }
1✔
238

239
    private void pathToPackageName(String path, final StringBuilder buffer) {
240
        path = path.replace('.', '_');
1✔
241
        path = path.replace('/', '.');
1✔
242
        buffer.append(path);
1✔
243
    }
1✔
244

245
    @Override
246
    public Description getDescription() {
247
        final String suiteName = checkDescription(this, getSuiteName());
1✔
248
        final Description description = Description.createSuiteDescription(suiteName);
1✔
249
        for (final XQueryTestInfo.TestFunctionDef testFunctionDef : info.getTestFunctions()) {
1✔
250
            description.addChild(Description.createTestDescription(suiteName, checkDescription(testFunctionDef, testFunctionDef.getLocalName())));
1✔
251
        }
252
        return description;
1✔
253
    }
254

255
    @Override
256
    public void run(final RunNotifier notifier) {
257
        try {
258
            final String pkgName = getClass().getPackage().getName().replace('.', '/');
1✔
259
            final Source query = new ClassLoaderSource(pkgName + "/xquery-test-runner.xq");
1✔
260
            final URI testModuleUri = path.toAbsolutePath().toUri();
1✔
261

262
            final String suiteName = getSuiteName();
1✔
263

264
            final List<java.util.function.Function<XQueryContext, Tuple2<String, Object>>> externalVariableDeclarations = Arrays.asList(
1✔
265
                    context -> new Tuple2<>("test-module-uri", new AnyURIValue(testModuleUri)),
1✔
266

267
                    // set callback functions for notifying junit!
268
                    context -> new Tuple2<>("test-ignored-function", new FunctionReference(new FunctionCall(context, new ExtTestIgnoredFunction(context, suiteName, notifier)))),
1✔
269
                    context -> new Tuple2<>("test-started-function", new FunctionReference(new FunctionCall(context, new ExtTestStartedFunction(context, suiteName, notifier)))),
1✔
270
                    context -> new Tuple2<>("test-failure-function", new FunctionReference(new FunctionCall(context, new ExtTestFailureFunction(context, suiteName, notifier)))),
1✔
271
                    context -> new Tuple2<>("test-assumption-failed-function", new FunctionReference(new FunctionCall(context, new ExtTestAssumptionFailedFunction(context, suiteName, notifier)))),
1✔
272
                    context -> new Tuple2<>("test-error-function", new FunctionReference(new FunctionCall(context, new ExtTestErrorFunction(context, suiteName, notifier)))),
1✔
273
                    context -> new Tuple2<>("test-finished-function", new FunctionReference(new FunctionCall(context, new ExtTestFinishedFunction(context, suiteName, notifier))))
1✔
274
            );
275

276
            // NOTE: at this stage EXIST_EMBEDDED_SERVER_CLASS_INSTANCE in XSuite will be usable
277
            final BrokerPool brokerPool = XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.getBrokerPool();
1✔
278
            executeQuery(brokerPool, query, externalVariableDeclarations);
1✔
279

280
        } catch(final DatabaseConfigurationException | IOException | EXistException | PermissionDeniedException | XPathException e) {
1✔
281
            //TODO(AR) what to do here?
282
            throw new RuntimeException(e);
×
283
        }
284
    }
1✔
285

286
    private static class XQueryTestInfo {
287
        private final String prefix;
288
        private final String namespace;
289
        private final List<TestFunctionDef> testFunctions;
290

291
        private XQueryTestInfo(final String prefix, final String namespace, final List<TestFunctionDef> testFunctions) {
1✔
292
            this.prefix = prefix;
1✔
293
            this.namespace = namespace;
1✔
294
            this.testFunctions = testFunctions;
1✔
295
        }
1✔
296

297
        public String getPrefix() {
298
            return prefix;
×
299
        }
300

301
        public String getNamespace() {
302
            return namespace;
1✔
303
        }
304

305
        public List<TestFunctionDef> getTestFunctions() {
306
            return testFunctions;
1✔
307
        }
308

309
        private static class TestFunctionDef {
310
            private final String localName;
311
            private final int arity;
312

313
            private TestFunctionDef(final String localName, final int arity) {
1✔
314
                this.localName = localName;
1✔
315
                this.arity = arity;
1✔
316
            }
1✔
317

318
            public String getLocalName() {
319
                return localName;
1✔
320
            }
321

322
            public int getArity() {
323
                return arity;
×
324
            }
325
        }
326
    }
327
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc