diff --git a/autotest/AutoTest.java b/autotest/AutoTest.java index 11b967cfc..9d74156dd 100644 --- a/autotest/AutoTest.java +++ b/autotest/AutoTest.java @@ -32,15 +32,17 @@ public class AutoTest { XMLReader saxReader; + AutoTestContentHandler autoTestContentHandler; /** * Constructor. * * @throws SAXException */ - public AutoTest() throws SAXException { + public AutoTest(String testInstance) throws SAXException { saxReader = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser"); - saxReader.setContentHandler(new AutoTestContentHandler()); + autoTestContentHandler = new AutoTestContentHandler(testInstance); + saxReader.setContentHandler(autoTestContentHandler); } /** @@ -62,18 +64,54 @@ public void parse(String uri) throws IOException, SAXException { * list of arguments of the program: uri [options] */ public static void main(String[] args) { - if (args.length != 1) { - System.out.println("Usage : AutoTest uri"); + if (args.length == 0) { + System.out.println("Usage : AutoTest uri [instance]"); System.exit(1); } - String uri = args[0]; + String uri = null; + String instance = null; + if (args.length >= 1) { + uri = args[0]; + } + + if (args.length >= 2) { + instance = args[1]; + } + + System.out.println("Processing: " + uri); try { - AutoTest parser = new AutoTest(); + AutoTest parser = new AutoTest(instance); parser.parse(uri); + if (parser.autoTestContentHandler.hasErrors()) { + System.err.println("\tTests outcome:" + + " " + + parser.autoTestContentHandler.getTestSuccessCount() + + " success(es)." + + " " + + parser.autoTestContentHandler.getTestFailCount() + + " failure(s)." + + " " + + parser.autoTestContentHandler.getTestErrorCount() + + " error(s)." + ); + System.exit(1); + } else { + System.out.println("\tTests outcome:" + + " " + + parser.autoTestContentHandler.getTestSuccessCount() + + " success(es)." + + " " + + parser.autoTestContentHandler.getTestFailCount() + + " failure(s)." + + " " + + parser.autoTestContentHandler.getTestErrorCount() + + " error(s)." + ); + } } catch (Throwable t) { t.printStackTrace(); } } -} \ No newline at end of file +} diff --git a/autotest/AutoTestContentHandler.java b/autotest/AutoTestContentHandler.java index e97efbd4d..e037e0da0 100644 --- a/autotest/AutoTestContentHandler.java +++ b/autotest/AutoTestContentHandler.java @@ -16,12 +16,19 @@ import org.xml.sax.SAXException; import java.io.BufferedWriter; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.StringBuilder; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; //import org.xml.sax.helpers.LocatorImpl; @@ -37,6 +44,7 @@ */ public class AutoTestContentHandler implements ContentHandler { + public static final String CLI_PARAMS = "--output=soap12"; public static final String VALIDATOR = "http://jigsaw.w3.org/css-validator/validator?"; public static final String PARAMS = "&output=soap12"; public static final int TESTSUITE = "testsuite".hashCode(); @@ -63,6 +71,10 @@ public class AutoTestContentHandler implements ContentHandler { boolean inDesc = false; boolean inErrors = false; boolean inWarnings = false; + int testFailCount = 0; + int testSuccessCount = 0; + int testErrorCount = 0; // Errors while trying to run the test + boolean hasError = false; String urlString = ""; String file = ""; String desc = ""; @@ -71,14 +83,19 @@ public class AutoTestContentHandler implements ContentHandler { String profile; String warning; String medium; + String testInstance = "servlet"; + StringBuilder errorSb; /** * Default Constructor. */ - public AutoTestContentHandler() { + public AutoTestContentHandler(String testInstance) { super(); // On definit le locator par defaut. // locator = new LocatorImpl(); + if (testInstance != null) { + this.testInstance = testInstance; + } } /** @@ -188,9 +205,17 @@ public void startElement(String nameSpaceURI, String localName, desc = ""; result = new Result(); - warning = null; - profile = null; - medium = null; + // Set default value of warning to 1, because + // - The test suite ran by upstream uses the javasript at autotest/client/buildtest.js, + // and the default value of warning is 1. + // - if the GET request to the servlet doesn't define a warning, it will be 0 (as per the + // ApplContext class default values). + // - on the contrary, the default value for the warning of the CLI is 2. + // So we have to set the default value to 1 here, so that when the warning is not defined + // it means 1. This is required to harmonize test result between call to jar and call to servlet. + warning = "1"; // Set to same default value as in autotest/client/buildtest.js + profile = "css21"; // Set to same default value as in autotest/client/buildtest.js + medium = "all"; // Set to same default value as in autotest/client/buildtest.js for (int i = 0; i < attributs.getLength(); i++) { String currentAttr = attributs.getLocalName(i); if (currentAttr.equals("warning")) { @@ -228,6 +253,61 @@ public void startElement(String nameSpaceURI, String localName, } } + private void waitProcess(Process p, List command) { + boolean waitForValue = false; + try { + waitForValue = p.waitFor(20,java.util.concurrent.TimeUnit.SECONDS); + } catch (InterruptedException e) { + hasError = true; + errorSb.append("Request: "); + errorSb.append(command.toString()); + errorSb.append(System.getProperty("line.separator")); + errorSb.append("Timeout reached. Subprocess stopped."); + errorSb.append(System.getProperty("line.separator")); + errorSb.append(e.getStackTrace().toString()); + printError(command, "Timeout reached. Subprocess stopped."); + printErrorToConsole(); + return; + } + if (waitForValue == true && p.exitValue() == 1) { + hasError = true; + errorSb.append("Request: "); + errorSb.append(command.toString()); + errorSb.append(System.getProperty("line.separator")); + errorSb.append("Command failed with exit code: " + p.exitValue()); + errorSb.append(System.getProperty("line.separator")); + + StringBuilder cmdOutput = new StringBuilder(); + try { + String line; + BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream())); + while ((line = input.readLine()) != null) { + cmdOutput.append(line); + cmdOutput.append(System.getProperty("line.separator")); + } + input.close(); + BufferedReader error = new BufferedReader(new InputStreamReader(p.getErrorStream())); + while ((line = error.readLine()) != null) { + cmdOutput.append(line); + cmdOutput.append(System.getProperty("line.separator")); + } + error.close(); + errorSb.append(cmdOutput); + } + catch (IOException e) { + errorSb.append(e.getMessage()); + errorSb.append(System.getProperty("line.separator")); + errorSb.append(e.getStackTrace().toString()); + printError(command, e.getMessage()); + printErrorToConsole(); + return; + } + printError(command, "Command failed with exit code: " + p.exitValue() + "
" + cmdOutput + "
"); + printErrorToConsole(); + //System.exit(p.exitValue()); + } + } + /** * @see org.xml.sax.ContentHandler#endElement(java.lang.String, * java.lang.String, java.lang.String) @@ -259,9 +339,12 @@ public void endElement(String nameSpaceURI, String localName, String rawName) System.err.println(e.getMessage()); } } else if (element == TEST) { - String val; - System.err.println(urlString); + hasError = false; + errorSb = new StringBuilder(""); + System.out.print(urlString + "... "); String validURL = createValidURL(urlString); + String val; + List command = new ArrayList<>(); if (isFile) { InputStream content; String text = ""; @@ -271,8 +354,13 @@ public void endElement(String nameSpaceURI, String localName, String rawName) .getClassLoader() .getResourceAsStream(urlString); byte[] textBytes = new byte[content.available()]; - content.read(textBytes, 0, textBytes.length); - text = createValidURL(new String(textBytes)); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + for (int length; (length = content.read(textBytes)) != -1; ) { + result.write(textBytes, 0, length); + } + // Files are encoded in ISO-8859-1 + // ie. "testsuite/properties/positive/content/css3/001.css" + text = createValidURL(result.toString("ISO-8859-1")); } catch (IOException e) { System.err.println(e.getMessage()); } @@ -283,24 +371,61 @@ public void endElement(String nameSpaceURI, String localName, String rawName) if (warning != null) { val += "&warning=" + warning; + if (warning.equals("no")) { + command.add("--warning=-1"); + } else { + command.add("--warning=" + warning); + } } if (profile != null) { val += "&profile=" + profile; + command.add("--profile=" + profile); } if (medium != null) { val += "&medium=" + medium; + command.add("--medium=" + medium); } val += PARAMS; + command.add(CLI_PARAMS); + + if (isFile) { + command.add("file:" + urlString); + } else { + command.add(urlString); + } try { - HttpManager manager = HttpManager.getManager(); - Request request = manager.createRequest(); - request.setMethod(HTTP.GET); - System.err.println(val); - request.setURL(new URL(val)); - Reply reply = manager.runRequest(request); - // Get the reply input stream that contains the actual data: - InputStream res = reply.getInputStream(); + InputStream res = null; + Reply reply = null; + if (testInstance.equals("servlet")) { + HttpManager manager = HttpManager.getManager(); + Request request = manager.createRequest(); + request.setMethod(HTTP.GET); + // System.out.println(val); + request.setURL(new URL(val)); + reply = manager.runRequest(request); + res = reply.getInputStream(); + } else if (testInstance.equals("jar")) { + Runtime r = Runtime.getRuntime(); + command.add(0, "java"); + command.add(1, "org.w3c.css.css.CssValidator"); + ProcessBuilder pb = new ProcessBuilder(command); + Map env = pb.environment(); + env.put("CLASSPATH", env.get("CLASSPATH") + ":css-validator.jar"); + Process p = pb.start(); + waitProcess(p, command); + res = p.getInputStream(); + } else if (testInstance.equals("cli")) { + Runtime r = Runtime.getRuntime(); + command.add(0, "css-validator"); + ProcessBuilder pb = new ProcessBuilder(command); + Process p = pb.start(); + waitProcess(p, command); + res = p.getInputStream(); + } else { + System.err.println("Unsupported operation. Invalid instance or instance not set: " + testInstance); + System.exit(2); + } int currentChar; StringBuffer buf = new StringBuffer(); @@ -308,16 +433,27 @@ public void endElement(String nameSpaceURI, String localName, String rawName) buf.append((char) currentChar); } - if (reply.getStatus() == 500) { // Internal Server Error + if (testInstance.equals("servlet") && reply.getStatus() == 500) { // Internal Server Error + hasError = true; if (buf.indexOf("env:Sender") != -1) { printError(val, "Reply status code: 500
" + "Invalid URL: Sender error"); + errorSb.append(val); + errorSb.append(System.getProperty("line.separator")); + errorSb.append("Reply status code: 500. Invalid URL: Sender error"); } else if (buf.indexOf("env:Receiver") != -1) { printError(val, "Reply status code: 500
" + "Unreachable URL: Receiver error"); + errorSb.append(val); + errorSb.append(System.getProperty("line.separator")); + errorSb.append("Reply status code: 500. Unreachable URL: Receiver error"); } else { printError(val, "Reply status code: 500"); + errorSb.append(val); + errorSb.append(System.getProperty("line.separator")); + errorSb.append("Reply status code: 500"); } + printErrorToConsole(); } else { result = new Result(); int begin = buf.indexOf(""); @@ -342,14 +478,46 @@ public void endElement(String nameSpaceURI, String localName, String rawName) result.setWarnings(Integer.parseInt(warn)); } printResult(val.substring(0, val.length() - 14)); + printResultToConsole(urlString); } } catch (MalformedURLException e) { - printError(val, e.getMessage()); + if (hasError == false) { + hasError = true; + errorSb.append("Request: "); + errorSb.append(testInstance.equals("servlet") ? truncateString(val) : command.toString()); + errorSb.append(System.getProperty("line.separator")); + errorSb.append(truncateString(e.getMessage())); + errorSb.append(System.getProperty("line.separator")); + printError(val, e.getMessage()); + printErrorToConsole(); + } } catch (IOException e) { - printError(val, e.getMessage()); + if (hasError == false) { + hasError = true; + errorSb.append("Request: "); + errorSb.append(testInstance.equals("servlet") ? truncateString(val) : command.toString()); + errorSb.append(System.getProperty("line.separator")); + errorSb.append(truncateString(e.getMessage())); + errorSb.append(System.getProperty("line.separator")); + printError(val, e.getMessage()); + printErrorToConsole(); + } } catch (HttpException e) { - printError(val, e.getMessage()); + if (hasError == false) { + hasError = true; + errorSb.append("Request: "); + errorSb.append(testInstance.equals("servlet") ? truncateString(val) : command.toString()); + errorSb.append(System.getProperty("line.separator")); + errorSb.append(truncateString(e.getMessage())); + errorSb.append(System.getProperty("line.separator")); + printError(val, e.getMessage()); + printErrorToConsole(); + } + } + + if (hasError == true) { + testErrorCount++; } isFile = false; @@ -366,6 +534,16 @@ public void endElement(String nameSpaceURI, String localName, String rawName) } } + private String truncateString(String str) { + int maxLength = 512; + int tailLength = 100; + if (str.length() <= maxLength) { + return str; + } else { + return str.substring(0, maxLength) + "...[TRUNCATED]..." + str.substring(str.length()-tailLength, str.length()) + "[TRUNCATED]"; + } + } + /** * @see org.xml.sax.ContentHandler#characters(char[], int, int) */ @@ -449,6 +627,55 @@ private void printResult(String validatorPage) { print(" "); } + /** + * Return whether "Valid" status is equal + * + */ + private boolean isValidEqual() { + return (awaitedResult.isValid() == result.isValid()); + } + + /** + * Return whether "Warnings" status is equal + * + */ + private boolean isWarningsEqual() { + return (awaitedResult.getWarnings() == result.getWarnings()); + } + + /** + * Return whether "Errors" status is equal + * + */ + private boolean isErrorsEqual() { + return (awaitedResult.getErrors() == result.getErrors()); + } + + /** + * Prints an HTML result of a validation to StdOut + * + * @param validatorPage + * the validator page result + */ + private void printResultToConsole(String urlString) { + if (isValidEqual() && isWarningsEqual() && isErrorsEqual()) { + testSuccessCount++; + System.out.println(" Success"); + } else { + testFailCount++; + System.out.println(" \u001B[31mFailure\u001B[0m"); + System.err.println("\t" + urlString); + System.err.print("\tExpected:"); + System.err.print("\tV:"+awaitedResult.isValid()); + System.err.print("\tE:"+awaitedResult.getErrors()); + System.err.println("\tW:"+awaitedResult.getWarnings()); + System.err.print("\tResult:\t"); + System.err.print("\tV:"+result.isValid()); + System.err.print("\tE:"+result.getErrors()); + System.err.println("\tW:"+result.getWarnings()); + } + } + /** * Used when an error occurs * @@ -460,18 +687,49 @@ private void printResult(String validatorPage) { private void printError(String validatorPage, String message) { validatorPage = validatorPage.replaceAll("&", "&"); - urlString = urlString.replaceAll("&", "&"); + String urlString2 = urlString.replaceAll("&", "&"); print("
"); - print("

" - + urlString + "

"); + print("

" + + urlString2 + "

"); print("

Go to the Validator page

"); print("

" + desc + "

"); + print("

" + truncateString(message) + "

"); + print("
"); + } + + /** + * Used when an error occurs + * + * @param validatorPage + * the validator page result + * @param message + * the message to be displayed + */ + private void printError(List command, String message) { + + String urlString2 = urlString.replaceAll("&", "&"); + + print("
"); + print("

" + + urlString2 + "

"); + print("

Command: " + command + "

"); + print("

" + desc + "

"); print("

" + message + "

"); print("
"); } + /** + * Used when an error occurs. Prints to console. + * + */ + private void printErrorToConsole() { + System.out.println(" \u001B[31mError\u001B[0m"); + System.err.println(urlString.indent(4)); + System.err.println(errorSb.toString().indent(4)); // String.indent() requires java >= 12. + } + /** * Replaces all URL special chars in a String with their matching URL * entities @@ -511,7 +769,28 @@ public String createValidURL(String url) { res = res.replaceAll("~'", "%7E"); res = res.replaceAll("\\\n", ""); res = res.replaceAll("\\\r", ""); + // 'à' character is present in 'testsuite/properties/positive/content/css2/001.css' + res = res.replaceAll("à", "%C3%A0"); return res; } + public boolean hasErrors() { + if (testFailCount > 0 || testErrorCount > 0) { + return true; + } + return false; + } + + public int getTestFailCount() { + return testFailCount; + } + + public int getTestSuccessCount() { + return testSuccessCount; + } + + public int getTestErrorCount() { + return testErrorCount; + } + } diff --git a/autotest/autotest.sh b/autotest/autotest.sh index 1ebfa06ed..4c0af83c1 100755 --- a/autotest/autotest.sh +++ b/autotest/autotest.sh @@ -17,4 +17,4 @@ javac -classpath css-validator.jar \ echo "Usage: autotest.sh ./testsuite/xml/FILENAME.xml" && \ exit 1 -java -cp css-validator.jar autotest.AutoTest "$1" +java -cp css-validator.jar autotest.AutoTest "$1" "${ENGINE:-servlet}" diff --git a/build.xml b/build.xml index f6833ff6d..62d1079ff 100644 --- a/build.xml +++ b/build.xml @@ -159,7 +159,16 @@ - + + + + + @@ -168,10 +177,10 @@ - - + + + @@ -199,6 +208,10 @@ + + + + diff --git a/org/w3c/css/css/CssValidator.java b/org/w3c/css/css/CssValidator.java index 55ee70156..064e53364 100644 --- a/org/w3c/css/css/CssValidator.java +++ b/org/w3c/css/css/CssValidator.java @@ -10,6 +10,7 @@ package org.w3c.css.css; import org.w3c.css.util.ApplContext; +import org.w3c.css.util.CssVersion; import org.w3c.css.util.HTTPURL; import org.w3c.css.util.Messages; import org.w3c.css.util.Util; @@ -79,13 +80,21 @@ public void setOptionsFromParams() { String profile = params.get("profile"); ac.setCssVersionAndProfile(profile); - // medium to use - ac.setMedium(params.get("medium")); + // media, only if we are not using CSS1 + if (ac.getCssVersion() != CssVersion.CSS1) { + String usermedium = params.get("medium"); + if (usermedium == null || usermedium.isEmpty()) { + usermedium = "all"; + } + ac.setMedium(usermedium); + } String vextwarn = params.get("vextwarning"); ac.setTreatVendorExtensionsAsWarnings("true".equalsIgnoreCase(vextwarn)); // TODO for now we use the same parameter for both vendor extensions and CSS Hacks. ac.setTreatCssHacksAsWarnings("true".equalsIgnoreCase(vextwarn)); + + ac.setWarningLevel(Integer.parseInt(params.get("warning"))); } public static void main(String args[])