• 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

85.71
/exist-core/src/main/java/org/exist/test/runner/XMLTestRunner.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.Namespaces;
54
import org.exist.dom.memtree.SAXAdapter;
55
import org.exist.security.PermissionDeniedException;
56
import org.exist.source.ClassLoaderSource;
57
import org.exist.source.Source;
58
import org.exist.storage.BrokerPool;
59
import org.exist.util.DatabaseConfigurationException;
60
import org.exist.util.ExistSAXParserFactory;
61
import org.exist.xquery.Expression;
62
import org.exist.xquery.FunctionCall;
63
import org.exist.xquery.XPathException;
64
import org.exist.xquery.XQueryContext;
65
import org.exist.xquery.value.FunctionReference;
66
import org.exist.xquery.value.Sequence;
67
import org.junit.runner.Description;
68
import org.junit.runner.notification.RunNotifier;
69
import org.junit.runners.model.InitializationError;
70
import org.w3c.dom.Document;
71
import org.w3c.dom.Element;
72
import org.w3c.dom.Node;
73
import org.w3c.dom.NodeList;
74
import org.xml.sax.InputSource;
75
import org.xml.sax.SAXException;
76
import org.xml.sax.XMLReader;
77

78
import javax.annotation.Nullable;
79
import javax.xml.parsers.ParserConfigurationException;
80
import javax.xml.parsers.SAXParser;
81
import javax.xml.parsers.SAXParserFactory;
82
import java.io.IOException;
83
import java.nio.file.Path;
84
import java.util.ArrayList;
85
import java.util.Arrays;
86
import java.util.List;
87

88
import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
89
import static org.exist.util.StringUtil.notNullOrEmptyOrWs;
90

91
/**
92
 * A JUnit test runner which can run the XML formatter XQuery tests
93
 * using $EXIST_HOME/src/org/exist/xquery/lib/test.xq.
94
 *
95
 * @author Adam Retter
96
 */
97
public class XMLTestRunner extends AbstractTestRunner {
98

99
    private static final SAXParserFactory SAX_PARSER_FACTORY = ExistSAXParserFactory.getSAXParserFactory();
1✔
100
    static {
101
        SAX_PARSER_FACTORY.setNamespaceAware(true);
1✔
102
    }
1✔
103

104
    private final Document doc;
105
    private final XMLTestInfo info;
106

107
    /**
108
     * @param path The path to the XML file containing the tests.
109
     * @param parallel whether the tests should be run in parallel.
110
     * @throws InitializationError if the test runner could not be constructed.
111
     */
112

113
    XMLTestRunner(final Path path, final boolean parallel) throws InitializationError {
114
        super(path, parallel);
1✔
115
        try {
116
            this.doc = parse(path);
1✔
117
        } catch (final ParserConfigurationException | IOException | SAXException e) {
1✔
118
            throw new InitializationError(e);
×
119
        }
120
        this.info = extractTestInfo(path, doc);
1✔
121
    }
1✔
122

123
    private static XMLTestInfo extractTestInfo(final Path path, final Document doc) throws InitializationError {
124
        String testSetName = null;
1✔
125
        String description = null;
1✔
126
        final List<String> testNames = new ArrayList<>();
1✔
127

128
        final Element docElement = doc.getDocumentElement();
1✔
129
        if(docElement == null) {
1!
130
            throw new InitializationError("Invalid XML test document: " + path.toAbsolutePath());
×
131
        }
132

133
        final NodeList children = docElement.getChildNodes();
1✔
134
        for (int i = 0; i < children.getLength(); i++) {
1✔
135
            final Node child = children.item(i);
1✔
136
            if (child.getNodeType() == Node.ELEMENT_NODE && child.getNamespaceURI() == null) {
1!
137
                switch (child.getLocalName()) {
1✔
138
                    case "testName":
139
                        testSetName = child.getTextContent().trim();
1✔
140
                        break;
1✔
141
                    case "description":
142
                        description = child.getTextContent().trim();
1✔
143
                        break;
1✔
144
                    case "test":
145
                        // prefer @id over <task> for the test name
146
                        String testName = getIdValue(child);
1✔
147
                        if (testName == null) {
1✔
148
                            testName = getTaskText(child);
1✔
149
                        }
150
                        if (testName == null) {
1!
151
                            throw new InitializationError("Could not find @id or <task> within <test> of XML <TestSet> document:" + path.toAbsolutePath());
×
152
                        }
153
                        testNames.add(testName);
1✔
154
                        break;
1✔
155

156
                    default:
157
                        // ignored
158
                        break;
159
                }
160
            }
161
        }
162

163
        if (testSetName == null) {
1!
164
            throw new InitializationError("Could not find <testName> in XML <TestSet> document: " + path.toAbsolutePath());
×
165
        }
166

167
        return new XMLTestInfo(testSetName, description, testNames);
1✔
168
    }
169

170
    private static @Nullable String getIdValue(final Node test) {
171
        return notNullOrEmptyOrWs(((Element) test).getAttribute("id"), null);
1✔
172
    }
173

174
    private static @Nullable String getTaskText(final Node test) {
175
        final NodeList testChildren = test.getChildNodes();
1✔
176
        for (int j = 0; j < testChildren.getLength(); j++) {
1!
177
            final Node testChild = testChildren.item(j);
1✔
178
            if (testChild.getNodeType() == Node.ELEMENT_NODE && testChild.getNamespaceURI() == null && testChild.getLocalName().equals("task")) {
1!
179
                String textContent = testChild.getTextContent();
1✔
180
                if (textContent != null) {
1!
181
                    textContent = textContent.trim();
1✔
182
                    if (!textContent.isEmpty()) {
1!
183
                        return textContent;
1✔
184
                    }
185
                }
186
                return null;
×
187
            }
188
        }
189
        return null;
×
190
    }
191

192
    private String getSuiteName() {
193
        return "xmlts." + info.getName();
1✔
194
    }
195

196
    @Override
197
    public Description getDescription() {
198
        final String suiteName = checkDescription(info, getSuiteName());
1✔
199
        final Description description = Description.createSuiteDescription(suiteName);
1✔
200
        for (final String childName : info.getChildNames()) {
1✔
201
            description.addChild(Description.createTestDescription(suiteName, checkDescription(info, childName)));
1✔
202
        }
203
        return description;
1✔
204
    }
205

206
    @Override
207
    public void run(final RunNotifier notifier) {
208
        try {
209
            final String pkgName = getClass().getPackage().getName().replace('.', '/');
1✔
210
            final Source query = new ClassLoaderSource(pkgName + "/xml-test-runner.xq");
1✔
211

212
            final List<java.util.function.Function<XQueryContext, Tuple2<String, Object>>> externalVariableDeclarations = Arrays.asList(
1✔
213
                context -> new Tuple2<>("doc", doc),
1✔
214
                context -> new Tuple2<>("id", Sequence.EMPTY_SEQUENCE),
1✔
215

216
                // set callback functions for notifying junit!
217
                context -> new Tuple2<>("test-ignored-function", new FunctionReference(new FunctionCall(context, new ExtTestIgnoredFunction(context, getSuiteName(), notifier)))),
1✔
218
                context -> new Tuple2<>("test-started-function", new FunctionReference(new FunctionCall(context, new ExtTestStartedFunction(context, getSuiteName(), notifier)))),
1✔
219
                context -> new Tuple2<>("test-failure-function", new FunctionReference(new FunctionCall(context, new ExtTestFailureFunction(context, getSuiteName(), notifier)))),
1✔
220
                context -> new Tuple2<>("test-assumption-failed-function", new FunctionReference(new FunctionCall(context, new ExtTestAssumptionFailedFunction(context, getSuiteName(), notifier)))),
1✔
221
                context -> new Tuple2<>("test-error-function", new FunctionReference(new FunctionCall(context, new ExtTestErrorFunction(context, getSuiteName(), notifier)))),
1✔
222
                context -> new Tuple2<>("test-finished-function", new FunctionReference(new FunctionCall(context, new ExtTestFinishedFunction(context, getSuiteName(), notifier))))
1✔
223
            );
224

225
            // NOTE: at this stage EXIST_EMBEDDED_SERVER_CLASS_INSTANCE in XSuite will be usable
226
            final BrokerPool brokerPool = XSuite.EXIST_EMBEDDED_SERVER_CLASS_INSTANCE.getBrokerPool();
1✔
227
            executeQuery(brokerPool, query, externalVariableDeclarations);
1✔
228

229

230
        } catch(final DatabaseConfigurationException | IOException | EXistException | PermissionDeniedException | XPathException e) {
1✔
231
            //TODO(AR) what to do here?
232
            throw new RuntimeException(e);
×
233
        }
234
    }
1✔
235

236
    private static Document parse(final Path path) throws ParserConfigurationException, IOException, SAXException {
237
        final InputSource src = new InputSource(path.toUri().toASCIIString());
1✔
238
        final SAXParser parser = SAX_PARSER_FACTORY.newSAXParser();
1✔
239
        final XMLReader xr = parser.getXMLReader();
1✔
240

241
        xr.setFeature("http://xml.org/sax/features/external-general-entities", false);
1✔
242
        xr.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
1✔
243
        xr.setFeature(FEATURE_SECURE_PROCESSING, true);
1✔
244

245
        // we have to use SAXAdapter, otherwise un-referenced namespaces as used by xpath assertions may be stripped by Xerces.
246
        final SAXAdapter adapter = new SAXAdapter((Expression) null);
1✔
247
        xr.setContentHandler(adapter);
1✔
248
        xr.setProperty(Namespaces.SAX_LEXICAL_HANDLER, adapter);
1✔
249
        xr.parse(src);
1✔
250

251
        return adapter.getDocument();
1✔
252
    }
253

254
    private static class XMLTestInfo {
255
        @Nullable private final String name;
256
        @Nullable private final String description;
257
        private final List<String> childNames;
258

259
        private XMLTestInfo(@Nullable final String name, @Nullable final String description, final List<String> childNames) {
1✔
260
            this.name = name;
1✔
261
            this.description = description;
1✔
262
            this.childNames = childNames;
1✔
263
        }
1✔
264

265
        @Nullable
266
        public String getName() {
267
            return name;
1✔
268
        }
269

270
        @Nullable
271
        public String getDescription() {
272
            return description;
×
273
        }
274

275
        public List<String> getChildNames() {
276
            return childNames;
1✔
277
        }
278
    }
279
}
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