001/*
002 * Units of Measurement TCK
003 * Copyright © 2005-2023, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385 nor the names of its contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.units.tck;
031
032import static tech.units.tck.util.TestUtils.SYS_PROPERTY_OUTPUT_DIR;
033import static tech.units.tck.util.TestUtils.SYS_PROPERTY_PROFILE;
034import static tech.units.tck.util.TestUtils.SYS_PROPERTY_REPORT_FILE;
035import static tech.units.tck.util.TestUtils.SYS_PROPERTY_VERBOSE;
036
037import java.io.File;
038import java.io.FileWriter;
039import java.io.IOException;
040import java.io.InputStream;
041import java.io.OutputStream;
042import java.io.PrintWriter;
043import java.io.StringWriter;
044import java.lang.reflect.Method;
045import java.util.ArrayList;
046import java.util.Arrays;
047import java.util.Collections;
048import java.util.HashSet;
049import java.util.List;
050import java.util.Set;
051
052import javax.lang.model.SourceVersion;
053import javax.tools.Tool;
054
055import org.testng.ITestResult;
056import org.testng.TestListenerAdapter;
057import org.testng.TestNG;
058import org.testng.annotations.Test;
059import org.testng.reporters.VerboseReporter;
060import org.testng.xml.XmlClass;
061import org.testng.xml.XmlSuite;
062import org.testng.xml.XmlTest;
063
064import tech.units.tck.tests.FundamentalTypesTest;
065import tech.units.tck.tests.format.QuantityFormatTest;
066import tech.units.tck.tests.format.UnitFormatTest;
067import tech.units.tck.tests.quantity.QuantityInterfaceTest;
068import tech.units.tck.tests.quantity.QuantityTypesTest;
069import tech.units.tck.tests.spi.ObtainingQuantiesTest;
070import tech.units.tck.tests.spi.ObtainingUnitsTest;
071import tech.units.tck.tests.spi.QuantityFactoryTest;
072import tech.units.tck.tests.spi.ServiceProviderTest;
073import tech.units.tck.tests.spi.ServicesTest;
074import tech.units.tck.tests.spi.SystemOfUnitsTest;
075import tech.units.tck.tests.unit.PrefixInterfaceTest;
076import tech.units.tck.tests.unit.UnitConversionTest;
077import tech.units.tck.tests.unit.UnitDimensionTest;
078import tech.units.tck.tests.unit.UnitInterfaceTest;
079import tech.units.tck.util.TestGroups.Profile;
080import tech.uom.lib.common.function.Versioned;
081
082/**
083 * Main class for executing the JSR 385 TCK.
084 *
085 * @author <a href="mailto:[email protected]">Werner Keil</a>
086 * @version 2.5, October 31, 2023
087 * @since 1.0
088 */
089public class TCKRunner extends XmlSuite implements Tool, Versioned<String> {
090
091    /**
092     *
093     */
094    //private static final long serialVersionUID = 3189431432291353154L;
095
096        // General String Constants
097        /** Section prefix */ public static final String SECTION_PREFIX = "Section ";
098        /** JSR Base package */ public static final String MEASURE_PACKAGE = "javax.measure";
099
100        // TCK Constants
101        /** The Spec ID */ public static final String SPEC_ID = "JSR 385";
102    /** The Spec Version */ public static final String SPEC_VERSION = "2.2";
103    /** The TCK Version */ private static final String TCK_VERSION = "2.2";
104
105    private static final String MSG_FAILED = "[FAILED]  ";
106    
107    private final Profile profile;
108
109    /** Default constructor */
110    public TCKRunner() {
111        setName(SPEC_ID + " - TCK " + TCK_VERSION);
112        final XmlTest test = new XmlTest(this);
113        profile = Profile.valueOf((System.getProperty(SYS_PROPERTY_PROFILE,
114                        Profile.FULL.name()).toUpperCase()));
115        for (String group : profile.getGroups()) {
116            test.addIncludedGroup(group);
117        }
118        test.setName("TCK/Test Setup");
119        final List<XmlClass> classes = new ArrayList<>();
120        classes.add(new XmlClass(TCKSetup.class));
121        classes.add(new XmlClass(FundamentalTypesTest.class));
122        classes.add(new XmlClass(UnitInterfaceTest.class));
123        classes.add(new XmlClass(UnitConversionTest.class));
124        classes.add(new XmlClass(PrefixInterfaceTest.class));
125        classes.add(new XmlClass(UnitDimensionTest.class));
126        classes.add(new XmlClass(QuantityInterfaceTest.class));
127        classes.add(new XmlClass(QuantityTypesTest.class));
128        classes.add(new XmlClass(UnitFormatTest.class));
129        classes.add(new XmlClass(QuantityFormatTest.class));
130        classes.add(new XmlClass(QuantityFactoryTest.class));
131        classes.add(new XmlClass(SystemOfUnitsTest.class));
132        classes.add(new XmlClass(ServiceProviderTest.class));
133        classes.add(new XmlClass(ServicesTest.class));
134        classes.add(new XmlClass(ObtainingUnitsTest.class));
135        classes.add(new XmlClass(ObtainingQuantiesTest.class));
136        test.setXmlClasses(classes);
137    }
138
139    /**
140     * Main method to start the TCK. Optional arguments are:
141     * <ul>
142     * <li>-Dtech.units.tck.profile for defining the profile for TestNG groups (default: full).</li>
143     * <li>-Dtech.units.tck.outputDir for defining the output directory TestNG uses (default:
144     * ./target/tck-output).</li>
145     * <li>-Dtech.units.tck.verbose=true to enable TestNG verbose mode.</li>
146     * <li>-Dtech.units.tck.reportFile=targetFile.txt for defining the TCK result summary report
147     * target file (default: ./target/tck-results.txt).</li>
148     * </ul>
149     *
150     * @param args Optional arguments to control TCK execution
151     */
152    @Override
153    public int run(InputStream in, OutputStream out, OutputStream err, String... args) {
154        System.out.println("-- " + SPEC_ID + " TCK started --");
155        System.out.println("Profile: " + profile.getDescription());
156        final List<XmlSuite> suites = new ArrayList<>();
157        suites.add(new TCKRunner());
158        final TestNG tng = new TestNG();
159        tng.setXmlSuites(suites);
160        String outDir = System.getProperty(SYS_PROPERTY_OUTPUT_DIR, "./target/tck-output");
161        tng.setOutputDirectory(outDir);
162        String verbose = System.getProperty(SYS_PROPERTY_VERBOSE);
163        if ("true".equalsIgnoreCase(verbose)) {
164            tng.addListener(new VerboseReporter("[VerboseUoM] "));
165        }
166        String reportFile = System.getProperty(SYS_PROPERTY_REPORT_FILE, "./target/tck-results.txt");
167        final File file = new File(reportFile);
168        final Reporter rep = new Reporter(profile, file);
169        System.out.println("Writing to file " + file.getAbsolutePath() + " ...");
170        tng.addListener(rep);
171        tng.run();
172        rep.writeSummary();
173        System.out.println("-- " + SPEC_ID + " TCK finished --");
174        return 0;
175    }
176
177    @Override
178    public String getVersion() {
179        return TCK_VERSION;
180    }
181
182    @SuppressWarnings("exports")
183        @Override
184    public final Set<SourceVersion> getSourceVersions() {
185        return Collections.unmodifiableSet(new HashSet<>(Arrays.asList(new SourceVersion[] { 
186                        SourceVersion.RELEASE_8})));
187    }
188
189    /** Main method
190     * @param args the arguments 
191     */
192    public static final void main(String... args) {
193        if (args.length > 0 && "-version".equalsIgnoreCase(args[0])) {
194            showVersion();
195        } else {
196            showHelp();
197        }
198    }
199
200    private static void showHelp() {
201        final StringWriter consoleWriter = new StringWriter(1000);
202        consoleWriter.write("*****************************************************************************************\n");
203        consoleWriter.write("**** " + SPEC_ID + " - Units of Measurement, Technical Compatibility Kit, version " + TCK_VERSION + "\n");
204        consoleWriter.write("*****************************************************************************************\n\n");
205        consoleWriter.write("Usage:\n");
206        consoleWriter.write("To run the TCK, execute TestNG with Maven or a similar build tool.\n\n");
207        consoleWriter.write("E.g. by running \"mvn test\" with this POM.\n\n");
208        consoleWriter.write("You may use the following system properties to override the default behavior:\n");
209        consoleWriter.write("-D" + SYS_PROPERTY_PROFILE + "=<profile>" + " to select the desired profile from these available " + SPEC_ID + " profiles:\n");
210        for (Profile p : Profile.values()) {
211            consoleWriter.write("   " + p.name() + " - " + p.getDescription() + (p.isDefault() ? " (the default profile)\n" : "\n"));
212        }
213        consoleWriter.write("-D" + SYS_PROPERTY_OUTPUT_DIR + "=<directory> to set the output directory of your choice.\n");
214        consoleWriter.write("-D" + SYS_PROPERTY_REPORT_FILE + "=<file> to set the TCK result file directory of your choice.\n");
215        consoleWriter.write("-D" + SYS_PROPERTY_VERBOSE + "=true/false to toggle the TCK verbose option for additional test output. The default is \"false\"\n");
216        System.out.println(consoleWriter);
217    }
218
219    private static void showVersion() {
220        System.out.println(SPEC_ID + " - Units of Measurement, Technical Compatibility Kit, version \"" + TCK_VERSION + "\"\n");
221    }
222
223    /** The Test Reporter */
224    private static final class Reporter extends TestListenerAdapter {
225        private int count = 0;
226        private int skipped = 0;
227        private int failed = 0;
228        private int success = 0;
229        private final StringWriter consoleWriter = new StringWriter(3000);
230        private FileWriter fileWriter;
231
232        public Reporter(Profile profile, File file) {
233            try {
234                if (!file.exists()) {
235                    file.createNewFile();
236                }
237                fileWriter = new FileWriter(file);
238                fileWriter.write("*****************************************************************************************\n");
239                fileWriter.write("**** " + SPEC_ID + " - Units of Measurement, Technical Compatibility Kit, version " + TCK_VERSION + "\n");
240                fileWriter.write("*****************************************************************************************\n\n");
241                fileWriter.write("Executed on " + new java.util.Date() + "\n");
242                fileWriter.write("Operating System "
243                  + System.getProperty("os.name") + " ("
244                  + System.getProperty("os.version") + ", "
245                  + System.getProperty("os.arch") + ") \n");
246                fileWriter.write("Java "
247                        + System.getProperty("java.version") + " ("
248                        + System.getProperty("java.vendor") + ") \n");
249                fileWriter.write("Using " + profile.getDescription() + " profile\n\n");
250                // System.out:
251                consoleWriter.write("*****************************************************************************************\n");
252                consoleWriter.write("**** " + SPEC_ID + " - Units of Measurement, Technical Compatibility Kit, version " + TCK_VERSION + "\n");
253                consoleWriter.write("*****************************************************************************************\n\n");
254                consoleWriter.write("Executed on " + new java.util.Date() + "\n");
255                consoleWriter.write("Using " + profile.getDescription() + " profile\n\n");
256            } catch (IOException e) {
257                e.printStackTrace();
258                System.exit(-1);
259            }
260        }
261
262        @Override
263        public void onTestFailure(ITestResult tr) {
264            failed++;
265            count++;
266            String location = tr.getTestClass().getRealClass().getSimpleName() + '#' + tr.getMethod().getMethodName();
267            try {
268                Method realTestMethod = tr.getMethod().getConstructorOrMethod().getMethod();
269                Test testAnnot = realTestMethod.getAnnotation(Test.class);
270                if (testAnnot != null && testAnnot.description() != null && !testAnnot.description().isEmpty()) {
271                    if (tr.getThrowable() != null) {
272                        StringWriter sw = new StringWriter();
273                        PrintWriter w = new PrintWriter(sw);
274                        tr.getThrowable().printStackTrace(w);
275                        w.flush();
276                        log(MSG_FAILED + testAnnot.description() + "(" + location + "):\n" + sw.toString());
277                    } else {
278                        log(MSG_FAILED + testAnnot.description() + "(" + location + ")");
279                    }
280                } else {
281                    if (tr.getThrowable() != null) {
282                        StringWriter sw = new StringWriter();
283                        PrintWriter w = new PrintWriter(sw);
284                        tr.getThrowable().printStackTrace(w);
285                        w.flush();
286                        log(MSG_FAILED + location + ":\n" + sw.toString());
287                    } else {
288                        log(MSG_FAILED + location);
289                    }
290                }
291            } catch (IOException e) {
292                throw new IllegalStateException("IO Error", e);
293            }
294        }
295
296        @Override
297        public void onTestSkipped(ITestResult tr) {
298            skipped++;
299            count++;
300            String location = tr.getTestClass().getRealClass().getSimpleName() + '#' + tr.getMethod().getMethodName();
301            try {
302                Method realTestMethod = tr.getMethod().getConstructorOrMethod().getMethod();
303                Test specAssert = realTestMethod.getAnnotation(Test.class);
304                if (specAssert != null && specAssert.description() != null && !specAssert.description().isEmpty()) {
305                    log("[SKIPPED] " + specAssert.description() + "(" + location + ")");
306                } else {
307                    log("[SKIPPED] " + location);
308                }
309            } catch (IOException e) {
310                throw new IllegalStateException("IO Error", e);
311            }
312        }
313
314        @Override
315        public void onTestSuccess(ITestResult tr) {
316            success++;
317            count++;
318            String location = tr.getTestClass().getRealClass().getSimpleName() + '#' + tr.getMethod().getMethodName();
319            try {
320                Method realTestMethod = tr.getMethod().getConstructorOrMethod().getMethod();
321                Test specAssert = realTestMethod.getAnnotation(Test.class);
322                if (specAssert != null && specAssert.description() != null && !specAssert.description().isEmpty()) {
323                    log("[SUCCESS] " + specAssert.description() + "(" + location + ")");
324                } else {
325                    log("[SUCCESS] " + location);
326                }
327            } catch (IOException e) {
328                throw new IllegalStateException("IO Error", e);
329            }
330        }
331
332        private void log(String text) throws IOException {
333            fileWriter.write(text);
334            fileWriter.write('\n');
335            consoleWriter.write(text);
336            consoleWriter.write('\n');
337        }
338
339        public void writeSummary() {
340            try {
341                log("\n" + SPEC_ID + " TCK version " + TCK_VERSION + " Summary");
342                log("-------------------------------------------------------");
343                log("\nTOTAL TESTS EXECUTED : " + count);
344                log("TOTAL TESTS SKIPPED  : " + skipped);
345                log("TOTAL TESTS SUCCESS  : " + success);
346                log("TOTAL TESTS FAILED   : " + failed);
347                fileWriter.flush();
348                fileWriter.close();
349                consoleWriter.flush();
350                System.out.println();
351                System.out.println(consoleWriter);
352            } catch (IOException e) {
353                throw new IllegalStateException("IO Error", e);
354            }
355        }
356    }
357}