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 package org.apache.qetest; 23 24 import org.apache.test.android.AndroidFileUtils; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.lang.reflect.Method; 29 import java.util.Hashtable; 30 31 /** 32 * Static utility class for both general-purpose testing methods 33 * and a few XML-specific methods. 34 * Also provides a simplistic Test/Testlet launching helper 35 * functionality. Simply execute this class from the command 36 * line with a full or partial classname (in the org.apache.qetest 37 * area, obviously) and we'll load and execute that class instead. 38 * @author shane_curcuru@lotus.com 39 * @version $Id$ 40 */ 41 public abstract class QetestUtils 42 { 43 // abstract class cannot be instantiated 44 45 /** 46 * Utility method to translate a String filename to URL. 47 * 48 * Note: This method is not necessarily proven to get the 49 * correct URL for every possible kind of filename; it should 50 * be improved. It handles the most common cases that we've 51 * encountered when running Conformance tests on Xalan. 52 * Also note, this method does not handle other non-file: 53 * flavors of URLs at all. 54 * 55 * If the name is null, return null. 56 * If the name starts with a common URI scheme (namely the ones 57 * found in the examples of RFC2396), then simply return the 58 * name as-is (the assumption is that it's already a URL) 59 * Otherwise we attempt (cheaply) to convert to a file:/// URL. 60 * 61 * @param String local path\filename of a file 62 * @return a file:/// URL, the same string if it appears to 63 * already be a URL, or null if error 64 */ filenameToURL(String filename)65 public static String filenameToURL(String filename) 66 { 67 // null begets null - something like the commutative property 68 if (null == filename) 69 return null; 70 71 // Don't translate a string that already looks like a URL 72 if (isCommonURL(filename)) 73 return filename; 74 75 // Android-added: Look up the file in the java resources. 76 String androidUrl = AndroidFileUtils.getInputFileUrlString(filename); 77 if (androidUrl != null) { 78 return androidUrl; 79 } 80 81 File f = new File(filename); 82 String tmp = null; 83 try 84 { 85 // This normally gives a better path 86 tmp = f.getCanonicalPath(); 87 } 88 catch (IOException ioe) 89 { 90 // But this can be used as a backup, for cases 91 // where the file does not exist, etc. 92 tmp = f.getAbsolutePath(); 93 } 94 95 // URLs must explicitly use only forward slashes 96 if (File.separatorChar == '\\') { 97 tmp = tmp.replace('\\', '/'); 98 } 99 // Note the presumption that it's a file reference 100 // Ensure we have the correct number of slashes at the 101 // start: we always want 3 /// if it's absolute 102 // (which we should have forced above) 103 if (tmp.startsWith("/")) 104 return "file://" + tmp; 105 else 106 return "file:///" + tmp; 107 108 } 109 110 111 /** 112 * Utility method to find a relative path. 113 * 114 * <p>Attempt to find a relative path based from the current 115 * directory (usually user.dir property).</p> 116 * 117 * <p>If the name is null, return null. If the name starts 118 * with a common URI scheme (namely the ones 119 * found in the examples of RFC2396), then simply return 120 * the name itself (future work could attempt to detect 121 * file: protocols if needed).</p> 122 * 123 * @param String local path\filename of a file 124 * @return a local path\file that is relative; if we can't 125 * find one, we return the original name 126 */ filenameToRelative(String filename)127 public static String filenameToRelative(String filename) 128 { 129 // null begets null - something like the commutative property 130 if (null == filename) 131 return null; 132 133 // Don't translate a string that already looks like a URL 134 if (isCommonURL(filename)) 135 return filename; 136 137 String base = null; 138 try 139 { 140 File userdir = new File(System.getProperty("user.dir")); 141 // Note: use CanonicalPath, since this ensures casing 142 // will be identical between the two files 143 base = userdir.getCanonicalPath(); 144 } 145 catch (Exception e) 146 { 147 // If we can't detect this, we can't determine 148 // relativeness, so just return the name 149 return filename; 150 } 151 File f = new File(filename); 152 String tmp = null; 153 try 154 { 155 tmp = f.getCanonicalPath(); 156 } 157 catch (IOException ioe) 158 { 159 tmp = f.getAbsolutePath(); 160 } 161 162 // If it's not relative to the base, just return as-is 163 // (note: this may not be the answer you expect) 164 if (!tmp.startsWith(base)) 165 return tmp; 166 167 // Strip off the base 168 tmp = tmp.substring(base.length()); 169 // Also strip off any beginning file separator, since we 170 // don't want it to be mistaken for an absolute path 171 if (tmp.startsWith(File.separator)) 172 return tmp.substring(1); 173 else 174 return tmp; 175 } 176 177 178 /** 179 * Worker method to detect common absolute URLs. 180 * 181 * @param s String path\filename or URL (or any, really) 182 * @return true if s starts with a common URI scheme (namely 183 * the ones found in the examples of RFC2396); false otherwise 184 */ isCommonURL(String s)185 protected static boolean isCommonURL(String s) 186 { 187 if (null == s) 188 return false; 189 190 if (s.startsWith("file:") 191 || s.startsWith("http:") 192 || s.startsWith("ftp:") 193 || s.startsWith("gopher:") 194 || s.startsWith("mailto:") 195 || s.startsWith("news:") 196 || s.startsWith("telnet:") 197 ) 198 return true; 199 else 200 return false; 201 } 202 203 204 /** 205 * Utility method to get a testing Class object. 206 * This is mainly a bit of syntactic sugar to allow users 207 * to specify only the end parts of a package.classname 208 * and still have it loaded. It basically does a 209 * Class.forName() search, starting with the provided 210 * classname, and if not found, searching through a list 211 * of root packages to try to find the class. 212 * 213 * Note the inherent danger when there are same-named 214 * classes in different packages, where the behavior will 215 * depend on the order of searchPackages. 216 * 217 * Commonly called like: 218 * <code>testClassForName("PerformanceTestlet", 219 * new String[] {"org.apache.qetest", "org.apache.qetest.xsl" }, 220 * "org.apache.qetest.StylesheetTestlet");</code> 221 * 222 * @param String classname FQCN or partially specified classname 223 * that you wish to load 224 * @param String[] rootPackages a list of packages to search 225 * for the classname specified in array order; if null then 226 * we don't search any additional packages 227 * @param String defaultClassname a default known-good FQCN to 228 * return if the classname was not found 229 * 230 * @return Class object asked for if one found by combining 231 * clazz with one of the rootPackages; if none, a Class of 232 * defaultClassname; or null if an error occoured 233 */ testClassForName(String classname, String[] rootPackages, String defaultClassname)234 public static Class testClassForName(String classname, 235 String[] rootPackages, 236 String defaultClassname) 237 { 238 // Ensure we have a valid classname, and try it 239 if ((null != classname) && (classname.length() > 0)) 240 { 241 // Try just the specified classname, in case it's a FQCN 242 try 243 { 244 return Class.forName(classname); 245 } 246 catch (Exception e) 247 { 248 /* no-op, fall through */ 249 } 250 251 // Now combine each of the rootPackages with the classname 252 // and see if one of them gets loaded 253 if (null != rootPackages) 254 { 255 for (int i = 0; i < rootPackages.length; i++) 256 { 257 try 258 { 259 return Class.forName(rootPackages[i] + "." + classname); 260 } 261 catch (Exception e) 262 { 263 /* no-op, continue */ 264 } 265 } // end for 266 } // end if rootPackages... 267 } // end if classname... 268 269 // If we fell out here, try the defaultClassname 270 try 271 { 272 return Class.forName(defaultClassname); 273 } 274 catch (Exception e) 275 { 276 // You can't always get you what you want 277 return null; 278 } 279 } 280 281 282 /** 283 * Utility method to get a class name of a test. 284 * This is mainly a bit of syntactic sugar built on 285 * top of testClassForName. 286 * 287 * @param String classname FQCN or partially specified classname 288 * that you wish to load 289 * @param String[] rootPackages a list of packages to search 290 * for the classname specified in array order; if null then 291 * we don't search any additional packages 292 * @param String defaultClassname a default known-good FQCN to 293 * return if the classname was not found 294 * 295 * @return name of class that testClassForName returns; 296 * or null if an error occoured 297 */ testClassnameForName(String classname, String[] rootPackages, String defaultClassname)298 public static String testClassnameForName(String classname, 299 String[] rootPackages, 300 String defaultClassname) 301 { 302 Class clazz = testClassForName(classname, rootPackages, defaultClassname); 303 if (null == clazz) 304 return null; 305 else 306 return clazz.getName(); 307 } 308 309 310 /** 311 * Utility method to create a unique runId. 312 * 313 * This is used to construct a theoretically unique Id for 314 * each run of a test script. It is used later in some results 315 * analysis stylesheets to create comparative charts showing 316 * differences in results and timing data from one run of 317 * a test to another. 318 * 319 * Current format: MMddHHmm[;baseId] 320 * where baseId is not used if null. 321 * 322 * @param String Id base to start with 323 * 324 * @return String Id to use; will include a timestamp 325 */ createRunId(String baseId)326 public static String createRunId(String baseId) 327 { 328 java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat ("MMddHHmm"); 329 if (null != baseId) 330 //return formatter.format(new java.util.Date())+ ";" + baseId; 331 return baseId + ":" + formatter.format(new java.util.Date()); 332 else 333 return formatter.format(new java.util.Date()); 334 } 335 336 337 /** 338 * Utility method to get info about the environment. 339 * 340 * This is a simple way to get a Hashtable about the current 341 * JVM's environment from either Xalan's EnvironmentCheck 342 * utility or from org.apache.env.Which. 343 * 344 * @return Hashtable with info about the environment 345 */ getEnvironmentHash()346 public static Hashtable getEnvironmentHash() 347 { 348 Hashtable hash = new Hashtable(); 349 // Attempt to use Which, which will be better supported 350 Class clazz = testClassForName("org.apache.env.Which", null, null); 351 352 try 353 { 354 if (null != clazz) 355 { 356 // Call Which's method to fill hash 357 final Class whichSignature[] = 358 { Hashtable.class, String.class, String.class }; 359 Method which = clazz.getMethod("which", whichSignature); 360 String projects = ""; 361 String options = ""; 362 Object whichArgs[] = { hash, projects, options }; 363 which.invoke(null, whichArgs); 364 } 365 else 366 { 367 // Use Xalan's EnvironmentCheck 368 clazz = testClassForName("org.apache.xalan.xslt.EnvironmentCheck", null, null); 369 if (null != clazz) 370 { 371 Object envCheck = clazz.newInstance(); 372 final Class getSignature[] = { }; 373 Method getHash = clazz.getMethod("getEnvironmentHash", getSignature); 374 375 Object getArgs[] = { }; // empty 376 hash = (Hashtable)getHash.invoke(envCheck, getArgs); 377 } 378 } 379 } 380 catch (Throwable t) 381 { 382 hash.put("FATAL-ERROR", "QetestUtils.getEnvironmentHash no services available; " + t.toString()); 383 t.printStackTrace(); 384 } 385 return hash; 386 } 387 388 389 /** 390 * Main method to run from the command line - this acts 391 * as a cheap launching mechanisim for Xalan tests. 392 * 393 * Simply finds the class specified in the first argument, 394 * instantiates one, and passes it any remaining command 395 * line arguments we were given. 396 * The primary motivation here is to provide a simpler 397 * command line for inexperienced users. You can either 398 * pass the FQCN, or just the classname and it will still 399 * get run. Note the one danger is the order of package 400 * lookups and the potential for the wrong class to run 401 * when we have identically named classes in different 402 * packages - but this will usually work! 403 * 404 * @param args command line argument array 405 */ main(String[] args)406 public static void main(String[] args) 407 { 408 if (args.length < 1) 409 { 410 System.err.println("QetestUtils.main() ERROR in usage: must have at least one arg: classname [options]"); 411 return; 412 } 413 414 // Get the class specified by the first arg... 415 Class clazz = QetestUtils.testClassForName( 416 args[0], defaultPackages, null); // null = no default class 417 if (null == clazz) 418 { 419 System.err.println("QetestUtils.main() ERROR: Could not find class:" + args[0]); 420 return; 421 } 422 423 try 424 { 425 // ...find the main() method... 426 Class[] parameterTypes = new Class[1]; 427 parameterTypes[0] = java.lang.String[].class; 428 java.lang.reflect.Method main = clazz.getMethod("main", parameterTypes); 429 430 // ...copy over our remaining cmdline args... 431 final String[] appArgs = new String[(args.length) == 1 ? 0 : args.length - 1]; 432 if (args.length > 1) 433 { 434 System.arraycopy(args, 1, 435 appArgs, 0, 436 args.length - 1); 437 } 438 439 // ...and execute the method! 440 Object[] mainArgs = new Object[1]; 441 mainArgs[0] = appArgs; 442 main.invoke(null, mainArgs); 443 } 444 catch (Throwable t) 445 { 446 System.err.println("QetestUtils.main() ERROR: running " + args[0] 447 + ".main() threw: " + t.toString()); 448 t.printStackTrace(); 449 } 450 } 451 452 453 /** 454 * Default list of packages for xml-xalan tests. 455 * Technically this is Xalan-specific and should really be 456 * in some other directory, but I'm being lazy tonight. 457 * This looks for Xalan-related tests in the following 458 * packages in <b>this order</b>: 459 * <ul> 460 * <li>org.apache.qetest.xsl</li> 461 * <li>org.apache.qetest.xalanj2</li> 462 * <li>org.apache.qetest.trax</li> 463 * <li>org.apache.qetest.trax.dom</li> 464 * <li>org.apache.qetest.trax.sax</li> 465 * <li>org.apache.qetest.trax.stream</li> 466 * <li>org.apache.qetest.xslwrapper</li> 467 * <li>org.apache.qetest.xalanj1</li> 468 * <li>org.apache.qetest</li> 469 * <li>org.apache.qetest.qetesttest</li> 470 * </ul> 471 * Note the normal naming convention for automated tests 472 * is either *Test.java or *Testlet.java; although this is 473 * not required, it will make it easier to write simple 474 * test discovery mechanisims. 475 */ 476 public static final String[] defaultPackages = 477 { 478 "org.apache.qetest.xsl", 479 "org.apache.qetest.xalanj2", 480 "org.apache.qetest.trax", 481 "org.apache.qetest.trax.dom", 482 "org.apache.qetest.trax.sax", 483 "org.apache.qetest.trax.stream", 484 "org.apache.qetest.xslwrapper", 485 "org.apache.qetest.dtm", 486 "org.apache.qetest.xalanj1", 487 "org.apache.qetest", 488 "org.apache.qetest.qetesttest" 489 }; 490 491 } 492