001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.scxml2.w3c;
018
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.FileOutputStream;
022import java.io.FileReader;
023import java.net.URL;
024import java.util.ArrayList;
025import java.util.Collections;
026import java.util.LinkedHashMap;
027import java.util.List;
028
029import javax.xml.bind.JAXBContext;
030import javax.xml.bind.Unmarshaller;
031import javax.xml.bind.annotation.XmlAccessType;
032import javax.xml.bind.annotation.XmlAccessorType;
033import javax.xml.bind.annotation.XmlAttribute;
034import javax.xml.bind.annotation.XmlElement;
035import javax.xml.bind.annotation.XmlRootElement;
036import javax.xml.bind.annotation.XmlValue;
037import javax.xml.transform.Transformer;
038import javax.xml.transform.TransformerFactory;
039import javax.xml.transform.stream.StreamResult;
040import javax.xml.transform.stream.StreamSource;
041
042import org.apache.commons.io.FileUtils;
043import org.apache.commons.scxml2.PathResolver;
044import org.apache.commons.scxml2.SCXMLExecutor;
045import org.apache.commons.scxml2.env.Tracer;
046import org.apache.commons.scxml2.env.URLResolver;
047import org.apache.commons.scxml2.invoke.SimpleSCXMLInvoker;
048import org.apache.commons.scxml2.io.SCXMLReader;
049import org.apache.commons.scxml2.model.Final;
050import org.apache.commons.scxml2.model.SCXML;
051
052/**
053 * W3C SCXML 1.0 IRP tests: <a href="http://www.w3.org/Voice/2013/scxml-irp/">http://www.w3.org/Voice/2013/scxml-irp/</a>.
054 * <p>
055 * The <b>W3CTests</b> class is standalone and can download and transform the IRP tests locally using respectively
056 * commandline parameter <b>get</b> or <b>make</b>.
057 * </p>
058 * <p>
059 * To execute one or multiple IRP tests the commandline parameter <b>run</b> must be specified.
060 * </p>
061 * <p>
062 * Optional environment parameter <b>-Ddatamodel=&lt;minimal|ecma|xpath&gt;</b> can be specified to limit the
063 * execution of the tests for and using only the specified datamodel language.
064 * </p>
065 * <p>
066 * Optional environment parameter <b>-Dtest=&lt;testId&gt;</b> can be specified to only execute a single test, which
067 * also can be combined with the <b>-Ddatamodel</b> parameter.
068 * </p>
069 * <p>
070 * The W3CTests also uses a separate <b><code>tests.xml</code></b> configuration file, located in the
071 * <b><code>src/test/resources/w3c</code></b> directory, which is manually maintained to enable|disable execution
072 * of tests (when <em>not</em> using the <b>-Dtest</b> parameter, which will always execute the specified test).<br/>
073 * Furthermore, in this configuration file the current <em>success</em> or <em>failure</em> status, and even more
074 * meta data per test is maintained.
075 * </p>
076 */
077@SuppressWarnings("unused")
078public class W3CTests {
079
080    private static final String SCXML_IRP_BASE_URL = "http://www.w3.org/Voice/2013/scxml-irp/";
081    private static final String SCXML_IRP_MANIFEST_URI = "manifest.xml";
082    private static final String SCXML_IRP_ECMA_XSL_URI = "confEcma.xsl";
083    private static final String SCXML_IRP_XPATH_XSL_URI = "confXpath.xsl";
084
085    private static final String TESTS_SRC_DIR = "src/w3c/scxml-irp/";
086    private static final String TXML_TESTS_DIR = TESTS_SRC_DIR + "txml/";
087    private static final String MINIMAL_TESTS_DIR = TESTS_SRC_DIR + "minimal/";
088    private static final String ECMA_TESTS_DIR = TESTS_SRC_DIR + "ecma/";
089    private static final String XPATH_TESTS_DIR = TESTS_SRC_DIR + "xpath/";
090    private static final String PACKAGE_PATH = "/"+W3CTests.class.getPackage().getName().replace('.','/');
091    private static final String TESTS_FILENAME = PACKAGE_PATH + "/tests.xml";
092    private static final String SCXML_IRP_MINIMAL_XSL_FILENAME = PACKAGE_PATH + "/confMinimal.xsl";
093
094    /**
095     * Tests model class used for loading the <b>tests.xml</b> configuration file
096     */
097    @XmlRootElement(name="tests")
098    @XmlAccessorType(XmlAccessType.FIELD)
099    protected static class Tests {
100
101        @XmlAccessorType(XmlAccessType.FIELD)
102        protected static class Test {
103
104            @XmlAttribute(required=true)
105            private String id;
106            @XmlAttribute(required=true)
107            private Boolean mandatory;
108            @XmlAttribute(required=true)
109            private Boolean manual;
110            @XmlAttribute(required=true)
111            private boolean enabled;
112            @XmlAttribute
113            private String finalId;
114            @XmlAttribute
115            private Boolean implemented;
116            @XmlAttribute(name="minimal")
117            String minimalStatus;
118            @XmlAttribute(name="ecma")
119            String ecmaStatus;
120            @XmlAttribute(name="xpath")
121            String xpathStatus;
122            @XmlAttribute
123            Boolean xpathEnabled;
124            @XmlValue
125            private String comment;
126
127            public String getId() {
128                return id;
129            }
130
131            public boolean isMandatory() {
132                return mandatory;
133            }
134
135            public boolean isManual() {
136                return manual == null || manual;
137            }
138
139            public boolean isEnabled() {
140                return enabled;
141            }
142
143            public String getFinalState() {
144                return finalId;
145            }
146
147            public boolean isImplemented() {
148                return implemented == null || implemented;
149            }
150
151            public String getMinimalStatus() {
152                return minimalStatus;
153            }
154
155            public String getEcmaStatus() {
156                return ecmaStatus;
157            }
158
159            public String getXpathStatus() {
160                return xpathStatus;
161            }
162
163            public boolean isXPathEnabled() {
164                return xpathEnabled == null || xpathEnabled;
165            }
166
167            public String getComment() {
168                return comment;
169            }
170
171            public String toString() {
172                return id;
173            }
174        }
175
176        @XmlElement(name="test")
177        private ArrayList<Test> tests;
178
179        private LinkedHashMap<String, Test> testsMap;
180
181        public LinkedHashMap<String, Test> getTests() {
182            if (testsMap == null) {
183                testsMap = new LinkedHashMap<String, Test>();
184                if (tests != null) {
185                    for (Test t : tests) {
186                        testsMap.put(t.getId(), t);
187                    }
188                }
189            }
190            return testsMap;
191        }
192    }
193
194    /**
195     * Datamodel enum representing the minimal, ecma and xpath datamodel types used and tested by the W3C IRP tests.
196     */
197    protected enum Datamodel {
198
199        MINIMAL("minimal"),
200        ECMA("ecma"),
201        XPATH("xpath");
202
203        private final String value;
204
205        private Datamodel(final String value) {
206            this.value = value;
207        }
208
209        public String value() {
210            return value;
211        }
212
213        public static Datamodel fromValue(final String value) {
214            for (Datamodel datamodel : Datamodel.values()) {
215                if (datamodel.value().equals(value)) {
216                    return datamodel;
217                }
218            }
219            return null;
220        }
221    }
222
223    /**
224     * Assertions model class used for loading the W3C IRP tests manifest.xml file, defining the meta data and
225     * source URIs for all the W3C IRP tests.
226     */
227    @XmlRootElement(name="assertions")
228    @XmlAccessorType(XmlAccessType.FIELD)
229    protected static class Assertions {
230
231        @XmlAccessorType(XmlAccessType.FIELD)
232        protected static class Assertion {
233
234            @XmlAttribute
235            private String id;
236            @XmlAttribute(name="specnum")
237            private String specnum;
238            @XmlAttribute(name="specid")
239            private String specid;
240            @XmlElement(name="test")
241            private ArrayList<TestCase> testCases;
242
243            public String getId() {
244                return id;
245            }
246
247            public String getSpecNum() {
248                return specnum;
249            }
250
251            public String getSpecId() {
252                return specid;
253            }
254
255            public List<TestCase> getTestCases() {
256                return testCases != null ? testCases : Collections.<TestCase>emptyList();
257            }
258
259            public Datamodel getDatamodel() {
260                if ("#minimal-profile".equals(specid)) {
261                    return Datamodel.MINIMAL;
262                }
263                else if ("#ecma-profile".equals(specid)) {
264                    return Datamodel.ECMA;
265                }
266                else if ("#xpath-profile".equals(specid)) {
267                    return Datamodel.XPATH;
268                }
269                return null;
270            }
271
272            public String toString() {
273                return id;
274            }
275        }
276
277        @XmlAccessorType(XmlAccessType.FIELD)
278        protected static class TestCase {
279
280            @XmlAttribute
281            private String id;
282            @XmlAttribute
283            private String manual;
284            @XmlAttribute
285            private String conformance;
286            @XmlElement(name="start")
287            private ArrayList<Resource> scxmlResources;
288            @XmlElement(name="dep")
289            private ArrayList<Resource> depResources;
290
291            private ArrayList<Resource> resources;
292
293            public String getId() {
294                return id;
295            }
296
297            public boolean isManual() {
298                return Boolean.parseBoolean(manual);
299            }
300
301            public boolean isOptional() {
302                return "mandatory".equals(conformance);
303            }
304
305            public List<Resource> getScxmlResources() {
306                return scxmlResources != null ? scxmlResources : Collections.<Resource>emptyList();
307            }
308
309            public List<Resource> getResources() {
310                if (resources == null) {
311                    resources = new ArrayList<Resource>();
312                    if (scxmlResources != null) {
313                        resources.addAll(scxmlResources);
314                    }
315                    if (depResources != null) {
316                        resources.addAll(depResources);
317                        // no longer needed
318                        depResources = null;
319                    }
320                }
321                return resources;
322            }
323        }
324
325        @XmlAccessorType(XmlAccessType.FIELD)
326        protected static class Resource {
327
328            @XmlAttribute
329            private String uri;
330
331            public String getUri() {
332                return uri;
333            }
334
335            public String getName() {
336                return uri.substring(uri.indexOf("/")+1, uri.indexOf("."));
337            }
338
339            public String getFilename() {
340                return uri.substring(uri.indexOf("/")+1);
341            }
342        }
343
344        @XmlElement(name="assert")
345        private ArrayList<Assertion> assertions;
346
347        private LinkedHashMap<String, Assertion> assertionsMap;
348
349        public LinkedHashMap<String, Assertion> getAssertions() {
350            if (assertionsMap == null) {
351                assertionsMap = new LinkedHashMap<String, Assertion>();
352                if (assertions != null) {
353                    for (Assertion a : assertions) {
354                        assertionsMap.put(a.getId(), a);
355                    }
356                }
357            }
358            return assertionsMap;
359        }
360    }
361
362    /**
363     * Simple TestResult data struct for tracking test results
364     */
365    protected static class TestResults {
366        int testsSkipped;
367        int testsPassed;
368        int testsFailed;
369        int minimalPassed;
370        int minimalFailed;
371        int ecmaPassed;
372        int ecmaFailed;
373        int xpathPassed;
374        int xpathFailed;
375        ArrayList<String> failedTests = new ArrayList<String>();
376    }
377
378    /**
379     * W3CTests main function, see {@link #usage()} how to use.
380     * @param args
381     * @throws Exception
382     */
383    public static void main(final String[] args) throws Exception {
384        if (args.length > 0) {
385            if ("get".equals(args[0])) {
386                new W3CTests().getTests();
387                return;
388            }
389            else if ("make".equals(args[0])) {
390                new W3CTests().makeTests();
391                return;
392            }
393            else if ("run".equals(args[0])) {
394                Datamodel datamodel = Datamodel.fromValue(System.getProperty("datamodel"));
395                String testId = System.getProperty("test");
396                new W3CTests().runTests(testId, datamodel);
397                return;
398            }
399        }
400        usage();
401    }
402
403    /**
404     * Usage prints the 'commandline' usage options.
405     */
406    protected static void usage() {
407        System.out.println("Usage: W3CTests <get|run>\n" +
408                "  get  - downloads the W3C IRP tests\n" +
409                "  make - make previously downloaded  W3C IRP tests by transforming the .txml templates\n" +
410                "  run  - runs test(s), optionally only for a specific datamodel (default: all)\n\n" +
411                "To run a single test, specify -Dtest=<testId>, otherwise all enabled tests will be run.\n" +
412                "To only run test(s) for a specific datamodel, specify -Ddatamodel=<minimal|ecma|xpath>.\n");
413    }
414
415    /**
416     * Downloads the W3C IRP manifest.xml, the IRP ecma and xpath stylesheets to transform the tests, and the
417     * actual test templates (.txml) as defined in the manifest.xml
418     * @throws Exception
419     */
420    protected void getTests() throws Exception {
421        final File testsSrcDir = new File(TESTS_SRC_DIR);
422        if (!testsSrcDir.mkdirs()) {
423            FileUtils.cleanDirectory(testsSrcDir);
424        }
425        new File(TXML_TESTS_DIR).mkdirs();
426        new File(MINIMAL_TESTS_DIR).mkdirs();
427        new File(ECMA_TESTS_DIR).mkdirs();
428        new File(XPATH_TESTS_DIR).mkdirs();
429        System.out.println("Downloading IRP manifest: " + SCXML_IRP_BASE_URL + SCXML_IRP_MANIFEST_URI);
430        FileUtils.copyURLToFile(new URL(SCXML_IRP_BASE_URL + SCXML_IRP_MANIFEST_URI), new File(testsSrcDir, SCXML_IRP_MANIFEST_URI));
431        System.out.println("Downloading ecma stylesheet: " + SCXML_IRP_BASE_URL + SCXML_IRP_ECMA_XSL_URI);
432        FileUtils.copyURLToFile(new URL(SCXML_IRP_BASE_URL + SCXML_IRP_ECMA_XSL_URI), new File(testsSrcDir, SCXML_IRP_ECMA_XSL_URI));
433        System.out.println("Downloading xpath stylesheet: " + SCXML_IRP_BASE_URL + SCXML_IRP_XPATH_XSL_URI);
434        FileUtils.copyURLToFile(new URL(SCXML_IRP_BASE_URL + SCXML_IRP_XPATH_XSL_URI), new File(testsSrcDir, SCXML_IRP_XPATH_XSL_URI));
435        Assertions assertions = loadAssertions();
436        for (Assertions.Assertion entry : assertions.getAssertions().values()) {
437            for (Assertions.TestCase test : entry.getTestCases()) {
438                for (Assertions.Resource resource : test.getResources()) {
439                    System.out.println("Downloading IRP test file: " + SCXML_IRP_BASE_URL + resource.getUri());
440                    FileUtils.copyURLToFile(new URL(SCXML_IRP_BASE_URL + resource.getUri()), new File(TXML_TESTS_DIR + resource.getFilename()));
441                }
442            }
443        }
444    }
445
446    /**
447     * Transforms the W3C IRP tests.
448     * <p>
449     * Note: for transforming the IRP .txml test files XPath 2.0 is required, for which the Saxon library is used.
450     * </p>
451     * @throws Exception
452     */
453    protected void makeTests() throws Exception {
454        final File testsSrcDir = new File(TESTS_SRC_DIR);
455
456        TransformerFactory factory = TransformerFactory.newInstance("net.sf.saxon.TransformerFactoryImpl",null);
457        factory.setFeature("http://saxon.sf.net/feature/suppressXsltNamespaceCheck", true);
458        Transformer ecmaTransformer = factory.newTransformer(new StreamSource(new FileInputStream(new File(testsSrcDir, SCXML_IRP_ECMA_XSL_URI))));
459        Transformer xpathTransformer = factory.newTransformer(new StreamSource(new FileInputStream(new File(testsSrcDir, SCXML_IRP_XPATH_XSL_URI))));
460        Transformer minimalTransformer = factory.newTransformer(new StreamSource(getClass().getResourceAsStream(SCXML_IRP_MINIMAL_XSL_FILENAME)));
461        Assertions assertions = loadAssertions();
462        for (Assertions.Assertion entry : assertions.getAssertions().values()) {
463            for (Assertions.TestCase test : entry.getTestCases()) {
464                for (Assertions.Resource resource : test.getResources()) {
465                    processResource(entry.getSpecId(), resource, minimalTransformer, ecmaTransformer, xpathTransformer);
466                }
467            }
468        }
469    }
470
471    /**
472     * Unmarshall and return the W3C IRP tests manifest.xml
473     * @return an Assertions instance reprenting the W3C IRP tests manifest.xml
474     * @throws Exception
475     */
476    protected Assertions loadAssertions() throws Exception {
477        final JAXBContext jaxbContext = JAXBContext.newInstance(Assertions.class);
478        final Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
479        return (Assertions)jaxbUnmarshaller.unmarshal(new File(TESTS_SRC_DIR, SCXML_IRP_MANIFEST_URI));
480    }
481
482    /**
483     * Download and transform a W3C IRP test resource file
484     * @param specid the SCXML 1.0 spec id (anchor) for the current assertion,
485     *               which is used to determine if, how and where the resource should be transformed.
486     * @param resource The test resource definition
487     * @param minimalTransformer transformer to produce an minimal datamodel SCXML document from the txml resource
488     * @param ecmaTransformer transformer to produce an ecmascript datamodel SCXML document from the txml resource
489     * @param xpathTransformer transformer to produce a xpath datamodel based SCXML document from the txml resource
490     * @throws Exception
491     */
492    protected void processResource(final String specid, final Assertions.Resource resource,
493                                   final Transformer minimalTransformer, final Transformer ecmaTransformer,
494                                   final Transformer xpathTransformer)
495            throws Exception {
496        System.out.println("processing IRP test file " + resource.getFilename());
497        FileUtils.copyURLToFile(new URL(SCXML_IRP_BASE_URL + resource.getUri()), new File(TXML_TESTS_DIR + resource.getFilename()));
498        if (specid.equals("#minimal-profile")) {
499            transformResource(resource, minimalTransformer, MINIMAL_TESTS_DIR);
500        }
501        else if (specid.equals("#ecma-profile")) {
502            transformResource(resource, ecmaTransformer, ECMA_TESTS_DIR);
503        }
504        else if (specid.equals("#xpath-profile")) {
505            transformResource(resource, xpathTransformer, XPATH_TESTS_DIR);
506        }
507        else {
508            transformResource(resource, ecmaTransformer, ECMA_TESTS_DIR);
509            transformResource(resource, xpathTransformer, XPATH_TESTS_DIR);
510        }
511    }
512
513    /**
514     * XSL transform a W3C IRP test SCXML resource to a datamodel specific location and format,
515     * or simply copy a non SCXML resource to that location.
516     * @param resource the test resource definition
517     * @param transformer the XSL transformer to use
518     * @param targetDir the target location for the transformed SCXML document, or the non-SCXML resource
519     * @throws Exception
520     */
521    protected void transformResource(final Assertions.Resource resource, final Transformer transformer,
522                                     final String targetDir) throws Exception {
523        if (resource.getFilename().endsWith(".txml")) {
524            StreamSource txmlSource = new StreamSource(new FileInputStream(new File(TXML_TESTS_DIR, resource.getFilename())));
525            transformer.transform(txmlSource, new StreamResult(new FileOutputStream(new File(targetDir, resource.getName() + ".scxml"))));
526        }
527        else {
528            FileUtils.copyFile(new File(TXML_TESTS_DIR, resource.getFilename()), new File(targetDir, resource.getFilename()));
529        }
530    }
531
532    protected void createCleanDirectory(final String path) throws Exception {
533        final File dir = new File(path);
534        if (!dir.mkdirs()) {
535            FileUtils.cleanDirectory(dir);
536        }
537    }
538
539    /**
540     * Run one or multiple W3C IRP tests
541     * @param testId a W3C IRP test id, or null to specify all tests to run
542     * @param datamodel only tests available for or executable with the specified datamodel will be run (or all if null)
543     * @throws Exception
544     */
545    protected void runTests(final String testId, final Datamodel datamodel) throws Exception {
546        final Assertions assertions = loadAssertions();
547        final Tests tests = loadTests();
548        final TestResults results = new TestResults();
549        if (testId != null) {
550            final Assertions.Assertion assertion = assertions.getAssertions().get(testId);
551            if (assertion != null) {
552                runTest(assertion, tests, datamodel, true, results);
553            }
554            else {
555                throw new IllegalArgumentException("Unknown test with id: "+testId);
556            }
557        }
558        else {
559            for (Assertions.Assertion entry : assertions.getAssertions().values()) {
560                runTest(entry, tests, datamodel, false, results);
561            }
562        }
563        System.out.println(
564                "\nTest results running " +
565                (testId == null ? "all enabled tests" : "test "+testId) +
566                (datamodel != null ? " for the "+datamodel.value+" datamodel" : "") +
567                ":\n" +
568                "  number of tests    : "+(results.testsSkipped+results.testsPassed+results.testsFailed) +
569                   " ("+results.testsPassed+" passed,  "+results.testsFailed +" failed,  "+results.testsSkipped+" skipped)");
570        if (results.minimalPassed+results.minimalFailed > 0) {
571            System.out.println(
572                    "    mimimal datamodel: "+results.minimalPassed+" passed,  "+results.minimalFailed+" failed");
573        }
574        if (results.ecmaPassed+results.ecmaFailed > 0) {
575            System.out.println(
576                    "    ecma    datamodel: "+results.ecmaPassed+" passed,  "+results.ecmaFailed+" failed");
577        }
578        if (results.xpathPassed+results.xpathFailed > 0) {
579            System.out.println(
580                    "    xpath   datamodel: "+results.xpathPassed+" passed,  "+results.xpathFailed+" failed");
581        }
582        System.out.print("\n");
583        if (!results.failedTests.isEmpty()) {
584            System.out.println("  failed tests: ");
585            for (String filename : results.failedTests) {
586                System.out.println("    "+filename);
587            }
588            System.out.print("\n");
589        }
590    }
591
592    /**
593     * Loads the tests.xml configuration file into a Tests class configuration model instance.
594     * @return a Tests instance for the tests.xml configuration file.
595     * @throws Exception
596     */
597    protected Tests loadTests() throws Exception {
598        final JAXBContext jaxbContext = JAXBContext.newInstance(Tests.class);
599        final Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
600        return (Tests)jaxbUnmarshaller.unmarshal(getClass().getResource(TESTS_FILENAME));
601    }
602
603    /**
604     * Run a single W3C IRP test (assert)
605     * @param assertion The W3C IRP assert, defining one or more {@link Assertions.TestCase}s
606     * @param tests the tests configurations
607     * @param datamodel the datamodel to limit and restrict the execution of the test
608     * @param singleTest if true a single test id was specified which will be executed even if disabled in the configuration.
609     * @throws Exception
610     */
611    protected void runTest(final Assertions.Assertion assertion, final Tests tests, final Datamodel datamodel,
612                           final boolean singleTest, TestResults results) throws Exception {
613        final Tests.Test test = tests.getTests().get(assertion.getId());
614        if (test == null) {
615            throw new IllegalStateException("No test configuration found for W3C IRP test with id: "+assertion.getId());
616        }
617        boolean skipped = true;
618        boolean passed = true;
619        if (singleTest || test.isEnabled()) {
620            if (datamodel != Datamodel.MINIMAL || datamodel.equals(assertion.getDatamodel())) {
621                if (datamodel == null || assertion.getDatamodel() == null || datamodel.equals(assertion.getDatamodel())) {
622                    final Datamodel effectiveDM = datamodel != null ? datamodel : assertion.getDatamodel();
623                    for (Assertions.TestCase testCase : assertion.getTestCases()) {
624                        if (effectiveDM != null) {
625                            switch (effectiveDM) {
626                                case MINIMAL:
627                                    skipped = false;
628                                    if (runTests(assertion, testCase, test, MINIMAL_TESTS_DIR, results.failedTests)) {
629                                        results.minimalPassed++;
630                                    }
631                                    else {
632                                        passed = false;
633                                        results.minimalFailed++;
634                                    }
635                                    break;
636                                case ECMA:
637                                    skipped = false;
638                                    if (runTests(assertion, testCase, test, ECMA_TESTS_DIR, results.failedTests)) {
639                                        results.ecmaPassed++;
640                                    }
641                                    else {
642                                        passed = false;
643                                        results.ecmaFailed++;
644                                    }
645                                    break;
646                                case XPATH:
647                                    if (test.isXPathEnabled()) {
648                                        skipped = false;
649                                        if (runTests(assertion, testCase, test, XPATH_TESTS_DIR, results.failedTests)) {
650                                            results.xpathPassed++;
651                                        }
652                                        else {
653                                            passed = false;
654                                            results.xpathFailed++;
655                                        }
656                                    }
657                                    break;
658                            }
659                        }
660                        else {
661                            skipped = false;
662                            if (runTests(assertion, testCase, test, ECMA_TESTS_DIR, results.failedTests)) {
663                                results.ecmaPassed++;
664                            }
665                            else {
666                                passed = false;
667                                results.ecmaFailed++;
668                            }
669                            if (test.isXPathEnabled()) {
670                                if (runTests(assertion, testCase, test, XPATH_TESTS_DIR, results.failedTests)) {
671                                    results.xpathPassed++;
672                                }
673                                else {
674                                    passed = false;
675                                    results.xpathFailed++;
676                                }
677                            }
678                        }
679                    }
680                }
681            }
682        }
683        if (skipped) {
684            results.testsSkipped++;
685        }
686        else if (passed) {
687            results.testsPassed++;
688        }
689        else {
690            results.testsFailed++;
691        }
692    }
693
694    /**
695     * Execute all W3C IRP SCXML tests for a specific {@link Assertions.TestCase}
696     * @param assertion the W3C IRP test assert definition
697     * @param testCase the W3C IRP test definition
698     * @param test the test configuration
699     * @param scxmlDir the datamodel specific directory path containing the SCXML document(s)
700     * @throws Exception
701     */
702    protected boolean runTests(final Assertions.Assertion assertion, final Assertions.TestCase testCase,
703                               final Tests.Test test, final String scxmlDir, ArrayList<String> failedTests)
704            throws Exception {
705        boolean passed = true;
706        for (Assertions.Resource scxmlResource : testCase.getScxmlResources()) {
707            File scxmlFile = new File(scxmlDir, scxmlResource.getName()+".scxml");
708            if (!runTest(testCase, test, scxmlFile)) {
709                passed = false;
710                failedTests.add(scxmlFile.getParentFile().getName()+"/"+scxmlFile.getName());
711            }
712        }
713        return passed;
714    }
715
716    /**
717     * Run a single W3C IRP SCXML test
718     * @param testCase the W3C IRP test definition
719     * @param test the test configuration
720     * @param scxmlFile the file handle for the SCXML document
721     */
722    protected boolean runTest(final Assertions.TestCase testCase, final Tests.Test test, final File scxmlFile) {
723        try {
724            System.out.println("Executing test: "+scxmlFile.getParentFile().getName()+"/"+scxmlFile.getName());
725            final Tracer trc = new Tracer();
726            final PathResolver pathResolver = new URLResolver(scxmlFile.getParentFile().toURI().toURL());
727            final SCXMLReader.Configuration configuration = new SCXMLReader.Configuration(null, pathResolver);
728            final SCXML doc = SCXMLReader.read(new FileReader(scxmlFile), configuration);
729            if (doc == null) {
730                System.out.println("                FAIL: the SCXML file " +
731                        scxmlFile.getCanonicalPath() + " can not be parsed!");
732                return false;
733            }
734            final SCXMLExecutor exec = new SCXMLExecutor(null, null, trc);
735            exec.setSingleContext(true);
736            exec.setStateMachine(doc);
737            exec.addListener(doc, trc);
738            exec.registerInvokerClass("scxml", SimpleSCXMLInvoker.class);
739            exec.registerInvokerClass("http://www.w3.org/TR/scxml/", SimpleSCXMLInvoker.class);
740            exec.go();
741            Final end;
742            while ((end = exec.getStatus().getFinalState()) == null) {
743                Thread.sleep(100);
744                exec.triggerEvents();
745            }
746            System.out.println("                final state: "+end.getId());
747            if (!testCase.isManual()) {
748                return end.getId().equals("pass");
749            }
750            else if (test.getFinalState() != null) {
751                return end.getId().equals(test.getFinalState());
752            }
753            else {
754                // todo: manual verification for specific tests
755                return false;
756            }
757        }
758        catch (Exception e) {
759            System.out.println("                FAIL: "+e.getMessage());
760            return false;
761        }
762    }
763}