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