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 * StylesheetTestlet.java 25 * 26 */ 27 package org.apache.qetest.xsl; 28 29 import java.io.File; 30 import java.io.FileInputStream; 31 import java.util.Hashtable; 32 import java.util.Iterator; 33 import java.util.Map; 34 import java.util.Properties; 35 36 import org.apache.qetest.CheckService; 37 import org.apache.qetest.Datalet; 38 import org.apache.qetest.Logger; 39 import org.apache.qetest.QetestFactory; 40 import org.apache.qetest.QetestUtils; 41 import org.apache.qetest.TestletImpl; 42 import org.apache.qetest.xslwrapper.TransformWrapper; 43 import org.apache.qetest.xslwrapper.TransformWrapperFactory; 44 45 /** 46 * Testlet for conformance testing of xsl stylesheet files. 47 * 48 * This class provides the default algorithm used for verifying 49 * Xalan's conformance to the XSLT spec. It works in conjunction 50 * with StylesheetTestletDriver, which supplies the logic for 51 * choosing the testfiles to iterate over, and with 52 * TransformWrapper, which provides an XSL processor- and 53 * method-independent way to process files (i.e. different 54 * flavors of TransformWrapper may be different products, as well 55 * as different processing models, like SAX, DOM or Streams). 56 * 57 * This class is broken up into common worker methods to make 58 * subclassing easier for alternate testing algorithm. 59 * 60 * @author Shane_Curcuru@lotus.com 61 * @version $Id$ 62 */ 63 public class StylesheetTestlet extends TestletImpl 64 { 65 // Initialize our classname for TestletImpl's main() method 66 static { thisClassName = "org.apache.qetest.xsl.StylesheetTestlet"; } 67 68 // Initialize our defaultDatalet 69 { defaultDatalet = (Datalet)new StylesheetDatalet(); } 70 71 /** 72 * Accesor method for a brief description of this test. 73 * 74 * @return String describing what this StylesheetTestlet does. 75 */ getDescription()76 public String getDescription() 77 { 78 return "StylesheetTestlet"; 79 } 80 81 82 /** 83 * Run this StylesheetTestlet: execute it's test and return. 84 * 85 * @param Datalet to use as data point for the test. 86 */ execute(Datalet d)87 public void execute(Datalet d) 88 { 89 // Ensure we have the correct kind of datalet 90 StylesheetDatalet datalet = null; 91 try 92 { 93 datalet = (StylesheetDatalet)d; 94 } 95 catch (ClassCastException e) 96 { 97 logger.checkErr("Datalet provided is not a StylesheetDatalet; cannot continue with " + d); 98 return; 99 } 100 101 // Perform any other general setup needed 102 testletInit(datalet); 103 try 104 { 105 // Get a TransformWrapper of the appropriate flavor 106 TransformWrapper transformWrapper = getTransformWrapper(datalet); 107 // Transform our supplied input file... 108 testDatalet(datalet, transformWrapper); 109 transformWrapper = null; 110 // ...and compare with gold data 111 checkDatalet(datalet); 112 } 113 // Handle any exceptions from the testing 114 catch (Throwable t) 115 { 116 handleException(datalet, t); 117 return; 118 } 119 } 120 121 122 /** 123 * Worker method to perform any pre-processing needed. 124 * 125 * @param datalet to test with 126 */ testletInit(StylesheetDatalet datalet)127 protected void testletInit(StylesheetDatalet datalet) 128 { 129 //@todo validate our Datalet - ensure it has valid 130 // and/or existing files available. 131 132 // Cleanup outName only if asked to - delete the file on disk 133 // Optimization: this takes extra time and often is not 134 // needed, so only do this if the option is set 135 if ("true".equalsIgnoreCase(datalet.options.getProperty("deleteOutFile"))) 136 { 137 try 138 { 139 boolean btmp = (new File(datalet.outputName)).delete(); 140 logger.logMsg(Logger.TRACEMSG, "Deleting OutFile of::" + datalet.outputName 141 + " status: " + btmp); 142 } 143 catch (SecurityException se) 144 { 145 logger.logMsg(Logger.WARNINGMSG, "Deleting OutFile of::" + datalet.outputName 146 + " threw: " + se.toString()); 147 } 148 } 149 } 150 151 152 /** 153 * Worker method to get a TransformWrapper. 154 * 155 * @param datalet to test with 156 * @return TransformWrapper to use with this datalet 157 */ getTransformWrapper(StylesheetDatalet datalet)158 protected TransformWrapper getTransformWrapper(StylesheetDatalet datalet) 159 { 160 TransformWrapper transformWrapper = null; 161 try 162 { 163 transformWrapper = TransformWrapperFactory.newWrapper(datalet.flavor); 164 // Set our datalet's options as options in the wrapper 165 //@todo this is inefficient, since our datalet may 166 // have many options that don't pertain to the wrapper, 167 // but it does allow users to simply pass new options 168 // without us having to change code 169 transformWrapper.newProcessor(datalet.options); 170 } 171 catch (Throwable t) 172 { 173 logger.logThrowable(Logger.ERRORMSG, t, getDescription() + " newWrapper/newProcessor threw"); 174 logger.checkErr(getCheckDescription(datalet) + " newWrapper/newProcessor threw: " + t.toString()); 175 return null; 176 } 177 return transformWrapper; 178 } 179 180 181 /** 182 * Worker method to actually perform the transform. 183 * 184 * Logs out applicable info; attempts to perform transformation. 185 * 186 * @param datalet to test with 187 * @param transformWrapper to have perform the transform 188 * @throws allows any underlying exception to be thrown 189 */ testDatalet(StylesheetDatalet datalet, TransformWrapper transformWrapper)190 protected void testDatalet(StylesheetDatalet datalet, TransformWrapper transformWrapper) 191 throws Exception 192 { 193 //@todo Should we log a custom logElement here instead? 194 logger.logMsg(Logger.TRACEMSG, "executing with: inputName=" + datalet.inputName 195 + " xmlName=" + datalet.xmlName + " outputName=" + datalet.outputName 196 + " goldName=" + datalet.goldName + " flavor=" + datalet.flavor 197 + " paramName=" + datalet.paramName); 198 199 // Optional: configure a test with XSLT parameters. 200 final File paramFile = new File(datalet.paramName); 201 if (paramFile.exists()) { 202 Properties params = new Properties(); 203 final FileInputStream inStream = new FileInputStream(paramFile); 204 try { 205 params.load(inStream); 206 final Iterator iter = params.entrySet().iterator(); 207 while (iter.hasNext()) { 208 Map.Entry entry = (Map.Entry) iter.next(); 209 transformWrapper.setParameter(null, entry.getKey().toString(), entry.getValue()); 210 } 211 } finally { 212 inStream.close(); 213 } 214 } 215 216 // Simply have the wrapper do all the transforming 217 // or processing for us - we handle either normal .xsl 218 // stylesheet tests or just .xml embedded tests 219 long retVal = 0L; 220 if (null == datalet.inputName) 221 { 222 // presume it's an embedded test 223 long [] times = transformWrapper.transformEmbedded(datalet.xmlName, datalet.outputName); 224 retVal = times[TransformWrapper.IDX_OVERALL]; 225 } 226 else 227 { 228 // presume it's a normal stylesheet test 229 long[] times = transformWrapper.transform(datalet.xmlName, datalet.inputName, datalet.outputName); 230 retVal = times[TransformWrapper.IDX_OVERALL]; 231 } 232 } 233 234 235 /** 236 * Worker method to validate output file with gold. 237 * 238 * Logs out applicable info while validating output file. 239 * 240 * @param datalet to test with 241 * @throws allows any underlying exception to be thrown 242 */ checkDatalet(StylesheetDatalet datalet)243 protected void checkDatalet(StylesheetDatalet datalet) 244 throws Exception 245 { 246 // See if the datalet already has a fileChecker to use... 247 CheckService fileChecker = (CheckService)datalet.options.get("fileCheckerImpl"); 248 249 // ...if not, construct a default one with attributes 250 if (null == fileChecker) { 251 String fcName = datalet.options.getProperty("fileChecker"); 252 Class fcClazz = QetestUtils.testClassForName(fcName, 253 QetestUtils.defaultPackages, 254 null); 255 if (null != fcClazz) { 256 fileChecker = (CheckService) fcClazz.newInstance(); 257 fileChecker.applyAttributes(datalet.options); 258 } 259 } 260 261 if (null == fileChecker) 262 { 263 fileChecker = QetestFactory.newCheckService(logger, QetestFactory.TYPE_FILES); 264 // Apply any testing options to the fileChecker 265 fileChecker.applyAttributes(datalet.options); 266 } 267 268 // Validate the file 269 if (Logger.PASS_RESULT 270 != fileChecker.check(logger, 271 new File(datalet.outputName), 272 new File(datalet.goldName), 273 getCheckDescription(datalet)) 274 ) 275 { 276 // Log a custom element with all the file refs 277 // Closely related to viewResults.xsl select='fileref" 278 //@todo check that these links are valid when base 279 // paths are either relative or absolute! 280 Hashtable attrs = new Hashtable(); 281 attrs.put("idref", (new File(datalet.inputName)).getName()); 282 try 283 { 284 attrs.put("baseref", System.getProperty("user.dir")); 285 } 286 catch (Exception e) { /* no-op, ignore */ } 287 288 attrs.put("inputName", datalet.inputName); 289 attrs.put("xmlName", datalet.xmlName); 290 attrs.put("outputName", datalet.outputName); 291 attrs.put("goldName", datalet.goldName); 292 logger.logElement(Logger.STATUSMSG, "fileref", attrs, "Conformance test file references"); 293 } 294 } 295 296 297 /** 298 * Worker method to validate or log exceptions thrown by testDatalet. 299 * 300 * Provided so subclassing is simpler; our implementation merely 301 * calls checkErr and logs the exception. 302 * 303 * @param datalet to test with 304 * @param e Throwable that was thrown 305 */ handleException(StylesheetDatalet datalet, Throwable t)306 protected void handleException(StylesheetDatalet datalet, Throwable t) 307 { 308 // Put the logThrowable first, so it appears before 309 // the Fail record, and gets color-coded 310 logger.logThrowable(Logger.ERRORMSG, t, getDescription() + " " + datalet.getDescription()); 311 logger.checkErr(getCheckDescription(datalet) 312 + " threw: " + t.toString()); 313 } 314 315 316 /** 317 * Worker method to construct a description. 318 * 319 * Simply concatenates useful info to override getDescription(). 320 * 321 * @param datalet to test with 322 * @param e Throwable that was thrown 323 */ getCheckDescription(StylesheetDatalet datalet)324 protected String getCheckDescription(StylesheetDatalet datalet) 325 { 326 return getDescription() 327 + "{" + datalet.flavor + "} " 328 + datalet.getDescription(); 329 } 330 } // end of class StylesheetTestlet 331 332