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