package autotest;
// $Id$
// Author: Jean-Guilhem Rouel
// (c) COPYRIGHT MIT, ERCIM and Keio, 2003.
// Please first read the full copyright statement in file COPYRIGHT.html
import org.w3c.www.http.HTTP;
import org.w3c.www.protocol.http.HttpException;
import org.w3c.www.protocol.http.HttpManager;
import org.w3c.www.protocol.http.Reply;
import org.w3c.www.protocol.http.Request;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
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;
/*
* TODO: add support for different profiles, Sender/Receiver errors
*/
/**
* @author smeric
*
* Exemple d'implementation extremement simplifiee d'un SAX XML ContentHandler.
* Le but de cet exemple est purement pedagogique. Very simple implementation
* sample for XML SAX ContentHandler.
*/
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();
public static final int TEST = "test".hashCode();
public static final int TYPE = "type".hashCode();
public static final int TITLE = "title".hashCode();
public static final int URL = "url".hashCode();
public static final int FILE = "file".hashCode();
public static final int DESCRIPTION = "description".hashCode();
public static final int RESULT = "result".hashCode();
public static final int VALIDITY = "valid".hashCode();
public static final int ERRORS = "errors".hashCode();
public static final int WARNINGS = "warnings".hashCode();
// file writer
private String s = System.getProperty("file.separator");
private String ret = System.getProperty("line.separator");
private String OutputFile = "autotest" + s + "results" + s + "results.html";
private BufferedWriter bw;
// private Locator locator;
boolean inUrl = false;
boolean isFile = false;
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 = "";
Result awaitedResult = new Result();
Result result = new Result();
String profile;
String warning;
String medium;
String testInstance = "servlet";
StringBuilder errorSb;
/**
* Default Constructor.
*/
public AutoTestContentHandler(String testInstance) {
super();
// On definit le locator par defaut.
// locator = new LocatorImpl();
if (testInstance != null) {
this.testInstance = testInstance;
}
}
/**
* @see org.xml.sax.ContentHandler#setDocumentLocator(org.xml.sax.Locator)
*/
public void setDocumentLocator(Locator value) {
// locator = value;
}
/**
* @see org.xml.sax.ContentHandler#startDocument()
*/
public void startDocument() throws SAXException {
try {
File f = new File(OutputFile);
if (!f.exists())
f.createNewFile();
bw = new BufferedWriter(new FileWriter(OutputFile));
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
/**
* @see org.xml.sax.ContentHandler#endDocument()
*/
public void endDocument() throws SAXException {
}
/**
* @see org.xml.sax.ContentHandler#startPrefixMapping(java.lang.String,
* java.lang.String)
*/
public void startPrefixMapping(String prefix, String URI)
throws SAXException {
}
/**
* @see org.xml.sax.ContentHandler#endPrefixMapping(java.lang.String)
*/
public void endPrefixMapping(String prefix) throws SAXException {
}
public void print (String str) {
try {
bw.write(str + ret);
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
public void print () {
try {
bw.append(ret);
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
/**
* @see org.xml.sax.ContentHandler#startElement(java.lang.String,
* java.lang.String, java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(String nameSpaceURI, String localName,
String rawName, Attributes attributs) throws SAXException {
int element = localName.hashCode();
if (element == TESTSUITE) {
print("");
print("");
print("");
print("
");
print(" ");
print(" Tests Results");
print(" ");
print(" ");
print();
print(" ");
print(" Test Suite
");
} else if (element == TEST) {
awaitedResult = new Result();
urlString = "";
file = "";
desc = "";
result = new Result();
// 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")) {
warning = attributs.getValue(i);
} else if (currentAttr.equals("profile")) {
profile = attributs.getValue(i);
} else if (currentAttr.equals("medium")) {
medium = attributs.getValue(i);
}
}
} else if (element == TYPE) {
if (attributs.getLength() >= 1
&& attributs.getLocalName(0).hashCode() == TITLE) {
print(" " + attributs.getValue(0) + "
");
}
} else if (element == URL) {
inUrl = true;
isFile = false;
} else if (element == FILE) {
inUrl = true;
isFile = true;
} else if (element == DESCRIPTION) {
inDesc = true;
} else if (element == RESULT) {
boolean valid = false;
if (attributs.getLength() >= 1
&& attributs.getLocalName(0).hashCode() == VALIDITY) {
valid = attributs.getValue(0).equals("true");
}
awaitedResult.setValid(valid);
} else if (element == ERRORS) {
inErrors = true;
} else if (element == WARNINGS) {
inWarnings = true;
}
}
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)
*/
public void endElement(String nameSpaceURI, String localName, String rawName)
throws SAXException {
int element = localName.hashCode();
if (element == TESTSUITE) {
print(" ");
print(" ");
print("
");
print(" ");
print(" ");
print("
");
print(" ");
print("
");
print("");
print();
print("");
try {
bw.close();
} catch (IOException e) {
System.err.println(e.getMessage());
}
} else if (element == TEST) {
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 = "";
try {
content = //
AutoTestContentHandler.class //
.getClassLoader()
.getResourceAsStream(urlString);
byte[] textBytes = new byte[content.available()];
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());
}
val = VALIDATOR + "text=" + text;
} else {
val = VALIDATOR + "uri=" + validURL;
}
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 {
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();
while ((currentChar = res.read()) != -1) {
buf.append((char) currentChar);
}
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("");
int end;
if (begin != -1) {
end = buf.indexOf("");
if (end != -1) {
String v = buf.substring(begin + 12, end).trim();
result.setValid(v.equals("true"));
}
}
begin = buf.indexOf("");
end = buf.indexOf("");
if (begin != -1 && end != -1) {
String err = buf.substring(begin + 14, end).trim();
result.setErrors(Integer.parseInt(err));
}
begin = buf.indexOf("");
end = buf.indexOf("");
if (begin != -1 && end != -1) {
String warn = buf.substring(begin + 16, end).trim();
result.setWarnings(Integer.parseInt(warn));
}
printResult(val.substring(0, val.length() - 14));
printResultToConsole(urlString);
}
} catch (MalformedURLException e) {
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) {
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) {
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;
} else if (element == URL) {
inUrl = false;
} else if (element == FILE) {
inUrl = false;
} else if (element == DESCRIPTION) {
inDesc = false;
} else if (element == ERRORS) {
inErrors = false;
} else if (element == WARNINGS) {
inWarnings = false;
}
}
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)
*/
public void characters(char[] ch, int start, int end) throws SAXException {
if (inUrl) {
urlString += new String(ch, start, end).trim();
} else if (inDesc) {
desc += new String(ch, start, end).trim();
} else if (inErrors) {
int errors;
try {
errors = Integer.parseInt(new String(ch, start, end));
} catch (NumberFormatException e) {
errors = 0;
}
awaitedResult.setErrors(errors);
} else if (inWarnings) {
int warnings;
try {
warnings= Integer.parseInt(new String(ch, start, end));
} catch (NumberFormatException e) {
warnings = 0;
}
awaitedResult.setWarnings(warnings);
}
}
/**
* @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
*/
public void ignorableWhitespace(char[] ch, int start, int end)
throws SAXException {
}
/**
* @see org.xml.sax.ContentHandler#processingInstruction(java.lang.String,
* java.lang.String)
*/
public void processingInstruction(String target, String data)
throws SAXException {
}
/**
* @see org.xml.sax.ContentHandler#skippedEntity(java.lang.String)
*/
public void skippedEntity(String arg0) throws SAXException {
System.err.println("Malformed entity: " + arg0);
}
/**
* Prints an HTML result of a validation
*
* @param validatorPage
* the validator page result
*/
private void printResult(String validatorPage) {
validatorPage = validatorPage.replaceAll("&", "&");
urlString = urlString.replaceAll("&", "&");
print(" ");
print("
");
print("
Go to the Validator page
");
print("
" + desc + "
");
print("
");
print(" - Awaited result
");
print(" - "
+ (awaitedResult.isValid() ? "Valid" : "Not valid") + "
");
print(" - Errors: " + awaitedResult.getErrors()
+ "
");
print(" - Warnings: " + awaitedResult.getWarnings()
+ "
");
print(" - Result
");
print(" - " + (result.isValid() ? "Valid" : "Not valid")
+ "
");
print(" - Errors: " + result.getErrors() + "
");
print(" - Warnings: " + result.getWarnings() + "
");
print("
");
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
*
* @param validatorPage
* the validator page result
* @param message
* the message to be displayed
*/
private void printError(String validatorPage, String message) {
validatorPage = validatorPage.replaceAll("&", "&");
String urlString2 = urlString.replaceAll("&", "&");
print(" ");
print("
");
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("
");
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
*
* @param url
* the url to transform
* @return the valid URL
*/
public String createValidURL(String url) {
String res = url;
res = res.replaceAll("%", "%25");
res = res.replaceAll("\"", "%22");
res = res.replaceAll("\\{", "%7B");
res = res.replaceAll("\\}", "%7D");
res = res.replaceAll("\\\t", "%09");
res = res.replaceAll(" ", "+");
res = res.replaceAll("#", "%23");
res = res.replaceAll("&", "%26");
res = res.replaceAll("\\(", "%28");
res = res.replaceAll("\\)", "%29");
res = res.replaceAll(",", "%2C");
res = res.replaceAll("\\.", "%2E");
res = res.replaceAll("/", "%2F");
res = res.replaceAll(":", "%3A");
res = res.replaceAll(";", "%3B");
res = res.replaceAll("<", "%3C");
res = res.replaceAll("=", "%3D");
res = res.replaceAll(">", "%3E");
res = res.replaceAll("\\?", "%3F");
res = res.replaceAll("@", "%40");
res = res.replaceAll("\\[", "%5B");
res = res.replaceAll("\\\\", "%5C");
res = res.replaceAll("\\]", "%5D");
res = res.replaceAll("\\^", "%5E");
res = res.replaceAll("'", "%27");
res = res.replaceAll("\\|", "%7C");
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;
}
}