1 /************************************************************************* 2 * 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * Copyright 2000, 2010 Oracle and/or its affiliates. 6 * 7 * OpenOffice.org - a multi-platform office productivity suite 8 * 9 * This file is part of OpenOffice.org. 10 * 11 * OpenOffice.org is free software: you can redistribute it and/or modify 12 * it under the terms of the GNU Lesser General Public License version 3 13 * only, as published by the Free Software Foundation. 14 * 15 * OpenOffice.org is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU Lesser General Public License version 3 for more details 19 * (a copy is included in the LICENSE file that accompanied this code). 20 * 21 * You should have received a copy of the GNU Lesser General Public License 22 * version 3 along with OpenOffice.org. If not, see 23 * <http://www.openoffice.org/license.html> 24 * for a copy of the LGPLv3 License. 25 * 26 ************************************************************************/ 27 package lib; 28 29 import java.io.PrintWriter; 30 import java.lang.reflect.Field; 31 import java.lang.reflect.InvocationTargetException; 32 import java.lang.reflect.Method; 33 import java.util.Vector; 34 35 import com.sun.star.uno.UnoRuntime; 36 import com.sun.star.uno.XInterface; 37 38 import share.DescEntry; 39 import lib.TestParameters; 40 import stats.Summarizer; 41 42 /** 43 * The class supports method based interface tests development. 44 * 45 * <p>There are some points that should be fulfilled in a subclass to work 46 * correctly in the multi-method framework: 47 * 48 * 1. each subclass schould define a public field named oObj of type tested 49 * by the subclass, e.g. 'public XText oObj;'. That field will be initialized 50 * by the MultiMethodTest code with the instance of the interface to test. 51 * In a case of service testing the field type should be XPropertySet. 52 * 53 * 2. for the test of each method of the tested interface(or a property in the 54 * case of service testing) should be method with the following signature 55 * provided: 'public void _<method name>()', e.g. 'public void _getText()'. 56 * The methods will be called by MultiMethodText code using reflection API 57 * for each method in the interface description. 58 * 59 * 3. to set status for a call 'tRes.tested(String method, 60 * boolean result)' should be used. For example 'tRes.tested("getText()", 61 * true)'. Also 'tRes.assert(String assertion, boolean result)' call can 62 * be used. Note, that one can call the methods not neccesarily from the 63 * test for the tested method, but from other method tests too (in the 64 * MultiMethodTest subclass). See also TestResult and MultiMethodTest.tRes 65 * documentation. 66 * 67 * 4. the before() and after() methods can be overriden to perform some 68 * actions, accordingly, before and after calling the test methods. 69 * 70 * 5. besides tRes, there are some fields initialized in the MultiMethodTest, 71 * that can be used for implementing tests: 72 * 73 * - tEnv contains the environment tested 74 * - tParam contains parameters of the test 75 * - log a writer to log information about the test 76 * 77 * @see TestResult 78 */ 79 public class MultiMethodTest 80 { 81 82 /** 83 * Contains the TestEnvironment being tested, to allow for tests to access 84 * it. 85 */ 86 protected TestEnvironment tEnv; 87 /** 88 * Contains the TestParameters for the tests, to allow for tests to access 89 * it. 90 */ 91 protected TestParameters tParam; 92 /** 93 * Contains the Description for the test 94 * it. 95 */ 96 protected DescEntry entry; 97 /** 98 * Contains a writer to log an information about the interface testing, to 99 * allows for tests to access it. 100 */ 101 protected PrintWriter log; 102 /** 103 * Contains the TestResult instance for the interface test to collect 104 * information about methods test. 105 */ 106 protected TestResult tRes; 107 /** 108 * Contains names of the methods have been alreadycalled 109 */ 110 private Vector methCalled = new Vector(10); 111 112 /** 113 * Disposes the test environment, which was corrupted by the test. 114 * 115 * @param tEnv the environment to dispose 116 */ 117 public void disposeEnvironment(TestEnvironment tEnv) 118 { 119 disposeEnvironment(); 120 } 121 122 /** 123 * Disposes the current test environment, which was corrupted by the test. 124 * 125 * @see #disposeEnvironment(TestEnvironment) 126 */ 127 public void disposeEnvironment() 128 { 129 tEnv.dispose(); 130 TestCase tCase = tEnv.getTestCase(); 131 tCase.disposeTestEnvironment(tEnv, tParam); 132 } 133 134 /** 135 * Runs the interface test: its method tests. First, it initializes some 136 * of MultiMethodTest fields, like tRes, log, tEnv, etc. Then, it queries 137 * the tested interface and initializes 'oObj' field (defined in a 138 * subclass). Before calling method tests, before() method calles to allow 139 * initialization of s stuff before testing. Then, the method tests are 140 * called. After them, after() method is called, to allow cleaning up the 141 * stuff initialized in before() and test methods. 142 * 143 * @param entry the interface test state 144 * @param tEnv the environment to test 145 * @param tParam the parameters of the test 146 * 147 * @see #before 148 * @see #after 149 */ 150 public TestResult run(DescEntry entry, TestEnvironment tEnv, TestParameters tParam) 151 { 152 153 log = (PrintWriter) entry.Logger; 154 155 this.tEnv = tEnv; 156 this.tParam = tParam; 157 // this.log = log; 158 this.entry = entry; 159 this.tRes = new TestResult(); 160 Class testedClass; 161 162 // Some fake code for a self test. 163 // For normal test we must not be a "ifc.qadevooo._SelfTest" 164 if (! entry.entryName.equals("ifc.qadevooo._SelfTest")) 165 { 166 String ifcName = getInterfaceName(); 167 // System.out.println("checking : " + ifcName); 168 System.out.print("checking: [" + entry.longName + "]"); 169 170 // defining a name of the class corresponding to the tested interface 171 // or service 172 String testedClassName; 173 174 testedClassName = getTestedClassName(); 175 176 if (entry.EntryType.equals("service")) 177 { 178 testedClassName = "com.sun.star.beans.XPropertySet"; 179 } 180 181 try 182 { 183 testedClass = Class.forName(testedClassName); 184 } 185 catch (ClassNotFoundException cnfE) 186 { 187 System.out.println(); 188 cnfE.printStackTrace(log); 189 log.println("could not find a class : " + getTestedClassName()); 190 return null; 191 } 192 System.out.println(" is iface: [" + testedClassName + "] testcode: [" + entry.entryName + "]"); 193 194 // quering the tested interface from the tested object 195 XInterface tCase = tEnv.getTestObject(); 196 Object oObj = UnoRuntime.queryInterface(testedClass, tEnv.getTestObject()); 197 198 if (oObj == null) 199 { 200 if (entry.isOptional) 201 { 202 Summarizer.summarizeDown(entry, "Not supported but optional.OK"); 203 } 204 else 205 { 206 Summarizer.summarizeDown(entry, "queryInterface returned null.FAILED"); 207 entry.ErrorMsg = "queryInterface returned null"; 208 entry.hasErrorMsg = true; 209 } 210 211 return null; 212 } 213 214 //setting the field oObj 215 setField("oObj", oObj); 216 } 217 218 // to perform some stuff before all method tests 219 try 220 { 221 before(); 222 } 223 catch (Exception e) 224 { 225 setSubStates(e.toString()); 226 return tRes; 227 } 228 229 // executing methods tests 230 for (int i = 0; i < entry.SubEntryCount; i++) 231 { 232 DescEntry aSubEntry = entry.SubEntries[i]; 233 try 234 { 235 final String sEntryName = aSubEntry.entryName; 236 executeMethod(sEntryName); 237 } 238 catch (Exception e) 239 { 240 log.println("Exception while checking: " + aSubEntry.entryName + " : " + e.getMessage()); 241 } 242 } 243 244 // to perform some stuff after all method tests 245 try 246 { 247 after(); 248 } 249 catch (Exception e) 250 { 251 } 252 253 return tRes; 254 } 255 256 /** 257 * Is called before calling method tests, but after initialization. 258 * Subclasses may override to perform actions before method tests. 259 */ 260 protected void before() 261 { 262 } 263 264 /** 265 * Is called after calling method tests. Subclasses may override 266 * to perform actions after method tests. 267 */ 268 protected void after() 269 { 270 } 271 272 /** 273 * @return the name of the interface or the service tested. 274 */ 275 protected String getTestedClassName() 276 { 277 String clsName = this.getClass().getName(); 278 279 int firstDot = clsName.indexOf("."); 280 int lastDot = clsName.lastIndexOf("."); 281 282 String append = "com.sun.star."; 283 284 if (entry.longName.indexOf("::drafts::com::") > -1) 285 { 286 append = "drafts.com.sun.star."; 287 } 288 289 return append + clsName.substring(firstDot + 1, lastDot + 1) + clsName.substring(lastDot + 2); 290 } 291 292 /** 293 * Sets a method status. 294 * 295 * @param methName the method name to set status 296 * @param methStatus the status to set to the method 297 */ 298 protected void setStatus(String methName, Status methStatus) 299 { 300 tRes.tested(methName, methStatus); 301 } 302 303 /** 304 * sets the substates 305 */ 306 protected void setSubStates(String msg) 307 { 308 for (int k = 0; k < entry.SubEntryCount; k++) 309 { 310 entry.SubEntries[k].hasErrorMsg = true; 311 entry.SubEntries[k].ErrorMsg = msg; 312 if (entry.SubEntries[k].State.equals("UNKNOWN")) 313 { 314 entry.SubEntries[k].State = msg; 315 } 316 } 317 318 } 319 320 /** 321 * Checks if the <code>method</code> is optional in the service. 322 */ 323 protected boolean isOptional(String _method) 324 { 325 for (int k = 0; k < entry.SubEntryCount; k++) 326 { 327 final String sName = entry.SubEntries[k].entryName; 328 if (sName.equals(_method)) 329 { 330 final boolean bIsOptional = entry.SubEntries[k].isOptional; 331 return bIsOptional; 332 } 333 } 334 return false; 335 } 336 337 /** 338 * Checks if the <code>method</code> test has been already called. 339 */ 340 protected boolean isCalled(String method) 341 { 342 return methCalled.contains(method); 343 } 344 345 /** 346 * Calling of the method indicates that the <code>method</code> test should 347 * be called. The method checks this and if it is not called, calls it. 348 * If the method is failed or skipped, it throws StatusException. 349 */ 350 protected void requiredMethod(String method) 351 { 352 log.println("starting required method: " + method); 353 executeMethod(method); 354 Status mtStatus = tRes.getStatusFor(method); 355 356 if (mtStatus != null && (!mtStatus.isPassed() || mtStatus.isFailed())) 357 { 358 log.println("! Required method " + method + " failed"); 359 throw new StatusException(mtStatus); 360 } 361 } 362 363 /** 364 * Checks if the <code>method</code> was called, and if not, call it. 365 * On contrary to requiredMethod(), he method doesn't check its status. 366 */ 367 protected void executeMethod(String method) 368 { 369 if (!isCalled(method)) 370 { 371 log.println("Execute: " + method); 372 callMethod(method); 373 log.println(method + ": " + tRes.getStatusFor(method)); 374 log.println(); 375 } 376 } 377 378 /** 379 * Just calls the <code>method</code> test. 380 */ 381 protected void callMethod(String method) 382 { 383 methCalled.add(method); 384 invokeTestMethod(getMethodFor(method), method); 385 } 386 387 /** 388 * Invokes a test method of the subclass using reflection API. Handles 389 * the method results and sets its status. 390 * 391 * @param meth the subclass' method to invoke 392 * @param methName the name of the method 393 */ 394 protected void invokeTestMethod(Method meth, String methName) 395 { 396 if (meth == null) 397 { 398 setStatus(methName, Status.skipped(false)); 399 } 400 else 401 { 402 Status stat; 403 404 try 405 { 406 meth.invoke(this, new Object[0]); 407 return; 408 } 409 catch (InvocationTargetException itE) 410 { 411 Throwable t = itE.getTargetException(); 412 413 if (t instanceof StatusException) 414 { 415 stat = ((StatusException) t).getStatus(); 416 } 417 else 418 { 419 t.printStackTrace(log); 420 stat = Status.exception(t); 421 } 422 } 423 catch (IllegalAccessException iaE) 424 { 425 iaE.printStackTrace(log); 426 stat = Status.exception(iaE); 427 } 428 catch (IllegalArgumentException iaE) 429 { 430 iaE.printStackTrace(log); 431 stat = Status.exception(iaE); 432 } 433 catch (ClassCastException ccE) 434 { 435 ccE.printStackTrace(log); 436 stat = Status.exception(ccE); 437 } 438 439 setStatus(methName, stat); 440 } 441 } 442 443 /** 444 * Finds a testing method for the <code>method</code> of the interface. 445 * 446 * @return the testing method, if found, <tt>null</tt> otherwise 447 */ 448 protected Method getMethodFor(String method) 449 { 450 String mName = "_" + method; 451 452 if (mName.endsWith("()")) 453 { 454 mName = mName.substring(0, mName.length() - 2); 455 } 456 457 final Class[] paramTypes = new Class[0]; 458 459 try 460 { 461 return this.getClass().getDeclaredMethod(mName, paramTypes); 462 } 463 catch (NoSuchMethodException nsmE) 464 { 465 return null; 466 } 467 } 468 469 /** 470 * @return the name of the interface tested 471 */ 472 public String getInterfaceName() 473 { 474 String clName = this.getClass().getName(); 475 return clName.substring(clName.lastIndexOf('.') + 1); 476 } 477 478 /** 479 * Initializes <code>fieldName</code> of the subclass with 480 * <code>value</code>. 481 * 482 * @return Status describing the result of the operation. 483 */ 484 protected Status setField(String fieldName, Object value) 485 { 486 Field objField; 487 488 try 489 { 490 objField = this.getClass().getField(fieldName); 491 } 492 catch (NoSuchFieldException nsfE) 493 { 494 return Status.exception(nsfE); 495 } 496 497 try 498 { 499 objField.set(this, value); 500 return Status.passed(true); 501 } 502 catch (IllegalArgumentException iaE) 503 { 504 return Status.exception(iaE); 505 } 506 catch (IllegalAccessException iaE) 507 { 508 return Status.exception(iaE); 509 } 510 } 511 } 512