1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 /* 19 * $Id$ 20 */ 21 22 /* 23 * 24 * XHTFileCheckService.java 25 * 26 */ 27 package org.apache.qetest.xsl; 28 29 import java.io.File; 30 import java.io.PrintWriter; 31 import java.io.StringWriter; 32 import java.util.Enumeration; 33 import java.util.Hashtable; 34 import java.util.Properties; 35 36 import org.apache.qetest.CheckService; 37 import org.apache.qetest.ConsoleLogger; 38 import org.apache.qetest.Logger; 39 import org.apache.qetest.XMLFileLogger; 40 41 /** 42 * Uses an XML/HTML/Text diff comparator to check or diff two files. 43 * @see #check(Logger logger, Object actual, Object reference, String msg, String id) 44 * @author Shane_Curcuru@lotus.com 45 * @version $Id$ 46 */ 47 public class XHTFileCheckService implements CheckService 48 { 49 50 /** XHTComparator tool to diff two files. */ 51 protected XHTComparator comparator = new XHTComparator(); 52 53 /** Stores last checkFile calls printed output. */ 54 private StringWriter sw = null; 55 56 /** 57 * Compare two objects for equivalence, and return appropriate result. 58 * Note that the order of actual, reference is important 59 * important in determining the result. 60 * <li>Typically: 61 * <ul>any unexpected Exceptions thrown -> ERRR_RESULT</ul> 62 * <ul>actual does not exist -> FAIL_RESULT</ul> 63 * <ul>reference does not exist -> AMBG_RESULT</ul> 64 * <ul>actual is equivalent to reference -> PASS_RESULT</ul> 65 * <ul>actual is not equivalent to reference -> FAIL_RESULT</ul> 66 * </li> 67 * Equvalence is first checked by parsing both files as XML to 68 * a DOM; if that has problems, we parse as HTML to a DOM; if 69 * that has problems, we create a DOM with a single text node 70 * after reading the file as text. We then compare the two DOM 71 * trees for equivalence. Note that in XML mode differences in 72 * the XML header itself (i.e. standalone=no/yes) are not caught, 73 * and will still report a pass (this is a defect in our 74 * comparison method). 75 * Side effect: every call to check() fills some additional 76 * info about how the check() call was processed which is then 77 * returned from getExtendedInfo() - this happens no matter what 78 * the result of the check() call was. 79 * 80 * @param logger to dump any output messages to 81 * @param actual (current) Object to check 82 * @param reference (gold, or expected) Object to check against 83 * @param description of what you're checking 84 * @param msg comment to log out with this test point 85 * @param id ID tag to log out with this test point 86 * @return Logger.*_RESULT code denoting status; each method may 87 * define it's own meanings for pass, fail, ambiguous, etc. 88 */ XHTFileCheckService()89 public XHTFileCheckService() 90 { 91 //No-op 92 } 93 check(Logger logger, Object actual, Object reference, String msg, String id)94 public int check(Logger logger, Object actual, Object reference, 95 String msg, String id) 96 { 97 // Create our 'extended info' stuff now, so it will 98 // always reflect the most recent call to this method 99 sw = new StringWriter(); 100 PrintWriter pw = new PrintWriter(sw); 101 102 if (((null == actual) || (null == reference ))) 103 { 104 pw.println("XHTFileCheckService actual or reference was null!"); 105 pw.flush(); 106 logFileCheckElem(logger, "null", "null", msg, id, sw.toString()); 107 logger.checkErr(msg, id); 108 return logger.ERRR_RESULT; 109 } 110 if (!((actual instanceof File) & (reference instanceof File))) 111 { 112 // Must have File objects to continue 113 pw.println("XHTFileCheckService only takes File objects!"); 114 pw.flush(); 115 logFileCheckElem(logger, actual.toString(), reference.toString(), msg, id, sw.toString()); 116 logger.checkErr(msg, id); 117 return logger.ERRR_RESULT; 118 } 119 120 File actualFile = (File) actual; 121 File referenceFile = (File) reference; 122 123 // Fail if Actual file doesn't exist or is 0 len 124 if ((!actualFile.exists()) || (actualFile.length() == 0)) 125 { 126 pw.println("actual(" + actualFile.toString() + ") did not exist or was 0 len"); 127 pw.flush(); 128 logFileCheckElem(logger, actualFile.toString(), referenceFile.toString(), msg, id, sw.toString()); 129 logger.checkFail(msg, id); 130 return logger.FAIL_RESULT; 131 } 132 133 // Ambiguous if gold file doesn't exist or is 0 len 134 if ((!referenceFile.exists()) || (referenceFile.length() == 0)) 135 { 136 pw.println("reference(" + referenceFile.toString() + ") did not exist or was 0 len"); 137 pw.flush(); 138 logFileCheckElem(logger, actualFile.toString(), referenceFile.toString(), msg, id, sw.toString()); 139 logger.checkAmbiguous(msg, id); 140 return Logger.AMBG_RESULT; 141 } 142 143 boolean warning[] = new boolean[1]; 144 warning[0] = false; 145 boolean isEqual = false; 146 147 // Inefficient code to get around very rare spurious exception: 148 // java.io.IOException: The process cannot access the file because it is being used by another process 149 // at java.io.Win32FileSystem.canonicalize(Native Method) 150 // at java.io.File.getCanonicalPath(File.java:442) 151 // at org.apache.qetest.xsl.XHTFileCheckService.check(XHTFileCheckService.java:181) 152 // So get filenames first separately, then call comparator 153 String referenceFileName = referenceFile.getAbsolutePath(); 154 String actualFileName = actualFile.getAbsolutePath(); 155 try 156 { 157 referenceFileName = referenceFile.getCanonicalPath(); 158 // Occasional spurious exception happens here, perhaps 159 // because sometimes the previous transform or whatever 160 // hasn't quite closed the actualFile yet 161 actualFileName = actualFile.getCanonicalPath(); 162 } 163 catch (Exception e) { /* no-op, ignore */ } 164 165 try 166 { 167 // Note calling order (gold, act) is different than checkFiles() 168 isEqual = comparator.compare(referenceFileName, 169 actualFileName, pw, 170 warning, attributes); 171 // Side effect: fills in pw/sw with info about the comparison 172 } 173 catch (Throwable t) 174 { 175 // Add any exception info to pw/sw; this will automatically 176 // get logged out later on via logFileCheckElem 177 pw.println("XHTFileCheckService threw: " + t.toString()); 178 t.printStackTrace(pw); 179 isEqual = false; 180 } 181 182 // If not equal at all, fail 183 if (!isEqual) 184 { 185 pw.println("XHTFileCheckService files were not equal"); 186 pw.flush(); 187 logFileCheckElem(logger, actualFile.toString(), referenceFile.toString(), msg, id, sw.toString()); 188 logger.checkFail(msg, id); 189 return Logger.FAIL_RESULT; 190 } 191 // If whitespace-only diffs, then pass/fail based on allowWhitespaceDiff 192 else if (warning[0]) 193 { 194 pw.println("XHTFileCheckService whitespace diff warning!"); 195 pw.flush(); 196 if (allowWhitespaceDiff) 197 { 198 logger.logMsg(Logger.STATUSMSG, "XHTFileCheckService whitespace diff warning, passing!"); 199 logger.checkPass(msg, id); 200 return Logger.PASS_RESULT; 201 } 202 else 203 { 204 logFileCheckElem(logger, actualFile.toString(), referenceFile.toString(), msg, id, 205 "XHTFileCheckService whitespace diff warning, failing!\n" + sw.toString()); 206 logger.checkFail(msg, id); 207 return Logger.FAIL_RESULT; 208 } 209 } 210 // Otherwise we were completely equal, so pass 211 else 212 { 213 pw.println("XHTFileCheckService files were equal"); 214 pw.flush(); 215 // For pass case, we *dont* call logFileCheckElem 216 logger.checkPass(msg, id); 217 return Logger.PASS_RESULT; 218 } 219 } 220 221 /** 222 * Logs a custom element about the current check() call. 223 * <pre> 224 * <fileCheck level="40" 225 * reference="tests\conf-gold\match\match16.out" 226 * reportedBy="XHTFileCheckService" 227 * actual="results-alltest\dom\match\match16.out" 228 * > 229 * StylesheetTestlet match16.xsl(null) 230 * XHTFileCheckService threw: java.io.IOException: The process cannot access the file because it is being used by another process 231 * java.io.IOException: The process cannot access the file because it is being used by another process 232 * at java.io.Win32FileSystem.canonicalize(Native Method) 233 * etc... 234 * XHTFileCheckService files were not equal 235 * 236 * </fileCheck> 237 * </pre> 238 * @param logger to dump any output messages to 239 * @param name of actual (current) File to check 240 * @param name of reference (gold, or expected) File to check against 241 * @param msg comment to log out with this test point 242 * @param id to log out with this test point 243 * @param additional log info from PrintWriter/StringWriter 244 */ logFileCheckElem(Logger logger, String actualFile, String referenceFile, String msg, String id, String logs)245 protected void logFileCheckElem(Logger logger, 246 String actualFile, String referenceFile, 247 String msg, String id, String logs) 248 { 249 Hashtable attrs = new Hashtable(); 250 attrs.put("actual", actualFile); 251 attrs.put("reference", referenceFile); 252 attrs.put("reportedBy", "XHTFileCheckService"); 253 try 254 { 255 attrs.put("baseref", System.getProperty("user.dir")); 256 } 257 catch (Exception e) { /* no-op, ignore */ } 258 String elementBody = msg + "(" + id + ") \n" + logs; 259 // HACK: escapeString(elementBody) so that it's legal XML 260 // for cases where we have XML output. This isn't 261 // necessarily a 'hack', I'm just not sure what the 262 // cleanest place to put this is (here or some sort 263 // of intelligent logic in XMLFileLogger) 264 elementBody = XMLFileLogger.escapeString(elementBody); 265 logger.logElement(Logger.STATUSMSG, "fileCheck", attrs, elementBody); 266 } 267 268 /** 269 * Compare two objects for equivalence, and return appropriate result. 270 * 271 * @see #check(Logger logger, Object actual, Object reference, String msg, String id) 272 * @param logger to dump any output messages to 273 * @param actual (current) File to check 274 * @param reference (gold, or expected) File to check against 275 * @param description of what you're checking 276 * @param msg comment to log out with this test point 277 * @return Logger.*_RESULT code denoting status; each method may 278 * define it's own meanings for pass, fail, ambiguous, etc. 279 */ check(Logger logger, Object actual, Object reference, String msg)280 public int check(Logger logger, Object actual, Object reference, 281 String msg) 282 { 283 return check(logger, actual, reference, msg, null); 284 } 285 286 /** 287 * Prefix to all attrs we understand. 288 * Note: design-wise, it would be better to have these constants 289 * in the XHTComparator class, since we know we're tightly bound 290 * to them anyways, and they shouldn't really be bound to us. 291 * But for my current purposes, it's simpler to put them here 292 * for documentation purposes. 293 */ 294 public static final String URN_XHTFILECHECKSERVICE = "urn:XHTFileCheckService:"; 295 296 /** Whether whitespace differences will cause a fail or not. */ 297 public static final String ALLOW_WHITESPACE_DIFF = URN_XHTFILECHECKSERVICE + "allowWhitespaceDiff"; 298 299 /** If we should call parser.setValidating(). */ 300 public static final String SETVALIDATING = URN_XHTFILECHECKSERVICE + "setValidating"; 301 302 /** If we should call parser.setIgnoringElementContentWhitespace(). */ 303 public static final String SETIGNORINGELEMENTCONTENTWHITESPACE = URN_XHTFILECHECKSERVICE + "setIgnoringElementContentWhitespace"; 304 305 /** If we should call parser.setExpandEntityReferences(). */ 306 public static final String SETEXPANDENTITYREFERENCES = URN_XHTFILECHECKSERVICE + "setExpandEntityReferences"; 307 308 /** If we should call parser.setIgnoringComments(). */ 309 public static final String SETIGNORINGCOMMENTS = URN_XHTFILECHECKSERVICE + "setIgnoringComments"; 310 311 /** If we should call parser.setCoalescing(). */ 312 public static final String SETCOALESCING = URN_XHTFILECHECKSERVICE + "setCoalescing"; 313 314 /** 315 * Whether whitespace differences will cause a fail or not. 316 * setAttribute("allow-whitespace-diff", true|false) 317 * true=whitespace-only diff will pass; 318 * false, whitespace-only diff will fail 319 */ 320 protected boolean allowWhitespaceDiff = false; // default; backwards compatible 321 322 /** 323 * Properties/Hash of parser-like attributes that have been set. 324 */ 325 protected Properties attributes = null; 326 327 /** 328 * Allows the user to set specific attributes on the testing 329 * utility or it's underlying product object under test. 330 * 331 * Supports basic JAXP DocumentBuilder attributes, plus our own 332 * ALLOW_WHITESPACE_DIFF attribute. 333 * 334 * @param name The name of the attribute. 335 * @param value The value of the attribute. 336 * @throws IllegalArgumentException thrown if the underlying 337 * implementation doesn't recognize the attribute and wants to 338 * inform the user of this fact. 339 */ setAttribute(String name, Object value)340 public void setAttribute(String name, Object value) 341 throws IllegalArgumentException 342 { 343 // Check for our own attributes first 344 if (ALLOW_WHITESPACE_DIFF.equals(name)) 345 { 346 try 347 { 348 allowWhitespaceDiff = (new Boolean((String)value)).booleanValue(); 349 } 350 catch (Throwable t) 351 { 352 // If it's an illegal value or type, ignore it 353 } 354 } 355 else 356 { 357 if (null == attributes) 358 { 359 attributes = new Properties(); 360 } 361 attributes.put(name, value); 362 } 363 } 364 365 /** 366 * Allows the user to set specific attributes on the testing 367 * utility or it's underlying product object under test. 368 * 369 * This method should attempt to set any applicable attributes 370 * found in the given attrs onto itself, and will ignore any and 371 * all attributes it does not recognize. It should never 372 * throw exceptions. This method will overwrite any previous 373 * attributes that were set. 374 * This method will only set values that are Strings findable 375 * by the Properties.getProperty() method. 376 * 377 * Future Work: this could be optimized by simply setting our 378 * Properties block to default from the passed-in one, but for 379 * now instead it only copies over the explicit values that 380 * we think are applicable. 381 * 382 * @param attrs Props of various name, value attrs. 383 */ applyAttributes(Properties attrs)384 public void applyAttributes(Properties attrs) 385 { 386 attributes = null; 387 for (Enumeration names = attrs.propertyNames(); 388 names.hasMoreElements(); /* no increment portion */ ) 389 { 390 String key = (String)names.nextElement(); 391 if (key.startsWith(URN_XHTFILECHECKSERVICE)) 392 { 393 setAttribute(key, attrs.getProperty(key)); 394 } 395 } 396 } 397 398 /** 399 * Allows the user to retrieve specific attributes on the testing 400 * utility or it's underlying product object under test. 401 * 402 * See applyAttributes for some limitations. 403 * 404 * @param name The name of the attribute. 405 * @return value of supported attributes or null if not recognized. 406 * @throws IllegalArgumentException thrown if the underlying 407 * implementation doesn't recognize the attribute and wants to 408 * inform the user of this fact. 409 */ getAttribute(String name)410 public Object getAttribute(String name) 411 throws IllegalArgumentException 412 { 413 // Check for our own attributes first 414 if (ALLOW_WHITESPACE_DIFF.equals(name)) 415 { 416 return new Boolean(allowWhitespaceDiff); 417 } 418 else if (null != attributes) 419 { 420 return attributes.get(name); 421 } 422 else 423 return null; 424 } 425 426 /** 427 * Description of what this testing utility does. 428 * 429 * @return String description of extension 430 */ getDescription()431 public String getDescription() 432 { 433 return ("Uses an XML/HTML/Text diff comparator to check or diff two files."); 434 } 435 436 /** 437 * Gets extended information about the last check call. 438 * This info is filled in for every call to check() with brief 439 * descriptions of what happened; will return 440 * <code>XHTFileCheckService-no-info-available</code> if 441 * check() has never been called. 442 * @return String describing any additional info about the 443 * last two files that were checked 444 */ getExtendedInfo()445 public String getExtendedInfo() 446 { 447 448 if (sw != null) 449 return sw.toString(); 450 else 451 return "XHTFileCheckService-no-info-available"; 452 } 453 454 /** 455 * Main method to run test from the command line - can be left alone. 456 * @param args command line argument array 457 */ main(String[] args)458 public static void main(String[] args) 459 { 460 if (args.length < 2) 461 { 462 System.out.println(" Please provide two files to compare"); 463 } 464 else 465 { 466 ConsoleLogger log = new ConsoleLogger(); 467 XHTFileCheckService app = new XHTFileCheckService(); 468 System.out.println("\nThank you for using XHTFileCheckService"); 469 System.out.println( app.getDescription() ); 470 System.out.println("We hope your results are satisfactory"); 471 System.out.println("\n" + args[0] + " " + args[1]); 472 473 File fAct = new File(args[0]); 474 File fExp = new File(args[1]); 475 476 try 477 { 478 app.check(log, fAct, fExp, "Check"); 479 } 480 catch (Exception e) 481 { 482 System.out.println ("main() caught unexpected Exception"); 483 } 484 } 485 } 486 } // end of class XHTFileCheckService 487 488