tizen 2.3 release
[framework/web/wearable/wrt-security.git] / tests / geolocation_security / geolocationSecurityTest / js / TestEngine.js
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 /**
17  * This file contains the implementation of test engine class.
18  *
19  * @author      Wojciech Bielawski(w.bielawski@samsung.com)
20  * @author      Pawel Misiak (p.misiak@samsung.com)
21  * @version     0.1
22  */
23
24 var TestEngine = {
25     logText: "",
26     testCaseTimeout: 5 * 1000, //in miliseconds
27     currentCaseTimeout: 5 * 1000,
28     timer: null,
29     countOK: 0,
30     countErr: 0,
31     countException: 0,
32     countIgnored: 0,
33     currentFailings: [],
34     currentTestSuiteName: null,
35     callbackMutex: 0,
36     callbackMethodName: "",
37     currentTestCase: 0,
38     countAllPassed: 0,
39     countAllFailed: 0,
40     testCasesFailedCount: 0,
41     testCasesPassedCount: 0,
42     testCasesFailed: [],
43     testList: [],
44     finalLog: "\n",
45     testSuccessCallback: null,
46     testErrorCallback: null,
47     testSuiteName: null,
48     testSuiteStats: [],
49     resultLogger: new HTMLTestResultLogger('log'),
50     summaryRenderer: new HTMLTestSummaryRenderer('summary'),
51     finalCallback: null,
52
53     stepsArray: null,
54     stepTimeout: null,
55     currentStep: null,
56     errorType: null,
57     errorField: null,
58
59     /*
60      * Values used only as types representations.
61      */
62     STRING: '',
63     NUMBER: 0,
64     OBJECT: {},
65     ARRAY: [],
66     DATE: new Date(),
67     BOOL: false,
68     FUNCTION: function() {},
69
70     /*
71      * Error test possible results.
72      */
73     ERROR_TEST_RESULT: {
74       NOT_RUN: -4,
75       NOT_THROWN: -3,
76       BAD_TYPE: -2,
77       BAD_VALUE: -1,
78       OK: 0
79     },
80
81     /**
82      * Prints specified object in a TreeView like structure.
83      * @param obj Object to print.
84      * @param indent Must be undefined (don't pass anything).
85      */
86     dumpObject: function(obj, indent) {
87         if (indent === undefined) indent = '';
88         else indent += '   ';
89         var prefix = (indent.length == 0 ? indent : indent + '|--');
90         for (var i in obj) {
91             if (typeof(obj[i]) == "object") {
92                 TestEngine.log(prefix + i + ":");
93                 TestEngine.dumpObject(obj[i], indent);
94             }
95             else
96                 TestEngine.log(prefix + i + ": " + obj[i]);
97         }
98     },
99
100     addTest: function(enabled, testFunc, testName, testPrereq)
101     {
102         if (null==testName) {
103             testName="unnamed test"
104         }
105         jsPrint("Add test: " + testName)
106         var data = new Object();
107         data.enabled = enabled;
108         data.testFunc = testFunc;
109         data.testName = testName;
110         data.testPrereq = testPrereq;
111         data.testSuite = TestEngine.testSuiteName;
112         // this.testList.push(testFunc)
113         this.testList.push(data);
114     },
115
116     setTestSuiteName: function(name, timeout)
117     {
118         this.testSuiteName = name;
119         this.testSuiteStats[name] = new Object();
120         this.testSuiteStats[name].passed = 0;
121         this.testSuiteStats[name].failed = 0;
122         this.testSuiteStats[name].assertsOK = 0;
123         this.testSuiteStats[name].assertsErr = 0;
124         TestEngine.currentCaseTimeout =
125             (timeout === undefined) ? TestEngine.testCaseTimeout : timeout;
126     },
127
128     setFinalCallback: function(finalCallbackParam)
129     {
130         this.finalCallback = finalCallbackParam;
131     },
132
133     log: function(text)
134     {
135         try
136         {
137             jsPrint(text);
138             this.logText += text + "<br/>";
139             this.finalLog += text + "\n";
140             // document.getElementById(TestEngine.currentTestSuite).innerHTML += text + "<br/>";
141             //document.getElementById('log').innerHTML += text + "<br/>";
142         }
143         catch(err)
144         {
145             this.countException++;
146             jsPrint("   TestEngine.log failure: " + err.message);
147         }
148     },
149
150     logException: function(text)
151     {
152         try
153         {
154             TestEngine.countException++;
155             TestEngine.log("[EXCEPTION] " + text);
156             TestEngine.currentFailings.push(text);
157         }
158         catch(err)
159         {
160             TestEngine.countException++;
161             jsPrint("   TestEngine.logErr failure: " + err.message);
162         }
163     },
164
165     logErr: function(text)
166     {
167         try
168         {
169             TestEngine.countErr++;
170             TestEngine.log("[FAILED] " + text);
171             TestEngine.currentFailings.push(text);
172         }
173         catch(err)
174         {
175             TestEngine.countException++;
176             jsPrint("   TestEngine.logErr failure: " + err.message);
177         }
178     },
179
180     logIgnored: function(text)
181     {
182         try
183         {
184             TestEngine.countIgnored++;
185             TestEngine.log("[IGNORED] " + text);
186         }
187         catch(err)
188         {
189             TestEngine.countException++;
190             jsPrint("   TestEngine.logErr failure: " + err.message);
191         }
192     },
193
194     logOK: function(text)
195     {
196         try
197         {
198             TestEngine.countOK++;
199             TestEngine.log("[OK] " + text);
200         }
201         catch(err)
202         {
203             TestEngine.countException++;
204             jsPrint("   TestEngine.logOK failure: " + err.message);
205         }
206     },
207
208     test: function(text, value)
209     {
210         try
211         {
212             if(typeof(value) == "undefined")
213             {
214                 TestEngine.logErr("value not defined for test: '" + text + "'");
215             }
216             else if(!value)
217             {
218                 TestEngine.logErr(text);
219             }
220             else
221             {
222                 TestEngine.logOK(text);
223                 return true;
224             }
225         }
226         catch(err)
227         {
228             TestEngine.countException++;
229             jsPrint("   TestEngine.test failure: " + err.message);
230         }
231         return false;
232     },
233
234     /**
235      * Sets error type used in every typePresetError check.
236      * @param type Type of an error/exception.
237      */
238     setErrorType: function(type)
239     {
240         TestEngine.errorType = type;
241     },
242
243     /**
244      * Sets error field used in every typePresetError check.
245      * @param field Name of the field in error structure to check its value.
246      */
247     setErrorField: function(field)
248     {
249         TestEngine.errorField = field;
250     },
251
252     /**
253      * Checks if specified expression throws a specified error.
254      * Expression must be enclosed in a function. Use setErrorType and
255      * setErrorField to set what error to look for.
256      * Error type must be set but if error field is left unset (i.e. null)
257      * then whole exception object is compared to specified value.
258      * @param msg Text to display for this test.
259      * @param fn Function eclosing the expression one wants to verify.
260      * @param value Value of an error/exception one looks for.
261      */
262     testPresetError: function(msg, fn, value)
263     {
264         if (TestEngine.errorType === null) {
265             TestEngine.logException("testPresetError skipped. Set error type first.");
266             return;
267         }
268
269         return TestEngine.testError(msg, fn, TestEngine.errorType,
270             TestEngine.errorField, value);
271     },
272
273     /**
274      * Checks if specified expression throws a specified error.
275      * This is a more general version of testPresetError function.
276      * Expression must be enclosed in a function.
277      * Error type must be set but if error field is left unset (i.e. null)
278      * then whole exception object is compared to specified value.
279      * @param msg Text to display for this test.
280      * @param fn Function eclosing the expression one wants to verify.
281      * @param errType Type of desired error/exception.
282      * @param errField Property from exception structure to look for exception
283      * value.
284      * @param errValue Value of an error/exception one looks for.
285      */
286     testError: function(msg, fn, errType, errField, errValue)
287     {
288         if (errType === null) {
289             TestEngine.logException("testError skipped. Error type can't be null.");
290             return TestEngine.ERROR_TEST_RESULT.NOT_RUN;
291         }
292
293         try {
294             fn();
295             TestEngine.logErr(msg + ' Exception has not been thrown.');
296             return TestEngine.ERROR_TEST_RESULT.NOT_THROWN;
297         }
298         catch (ex) {
299             if (ex instanceof errType) {
300                 var exValue = (errField !== null ? ex[errField] : ex);
301                 if (exValue === errValue) {
302                     TestEngine.logOK(msg + ' [' + errValue + ']');
303                     return TestEngine.ERROR_TEST_RESULT.OK;
304                 }
305                 else {
306                     TestEngine.logErr(msg + ' Exception is not of value ' + errValue);
307                     return TestEngine.ERROR_TEST_RESULT.BAD_VALUE;
308                 }
309             }
310             else {
311                 TestEngine.logErr(msg + ' Exception is of wrong type.');
312                 return TestEngine.ERROR_TEST_RESULT.BAD_TYPE;
313             }
314         }
315     },
316
317     testPresence: function(text, object)
318     {
319         try
320         {
321             if(object === undefined)
322             {
323                 TestEngine.logErr("value not defined. Name: " + text);
324             }
325             else
326             {
327                 TestEngine.logOK("object " + text + " present");
328             }
329         }
330         catch(err)
331         {
332             TestEngine.countException++;
333             jsPrint("   TestEngine.testPresence failure: " + err.message);
334         }
335     },
336
337     /**
338      * Checks whether object implements given property.
339      * In addition it also checks whether any exception (e.g. "Not Supported")
340      * is thrown.
341      * @param object Object to check property for.
342      * @param property Property to look for.
343      * @return True if object implements such property, false otherwise.
344      */
345     testPresence2: function(object, property)
346     {
347         var result = property in object;
348         if (result)
349         {
350             TestEngine.logOK("property " + property + " present");
351         }
352         else
353         {
354             TestEngine.logErr("property " + property + " absent");
355         }
356         return result;
357     },
358
359
360     /**
361      * Checks whether mainObj object equals templateObj object, property by
362      * property.
363      * Runs recursively through all the properties of templateObj object and
364      * checks if they exist and are equal to those in mainObj object.
365      * mainObj has to implement no less properties than templateObj.
366      * @param mainObj Object to check for properties implementation.
367      * @param templateObj Object to verify properties against.
368      * @return True if mainObj has at least the same properties as templateObj,
369      *         false otherwise.
370      */
371     checkObjectsEqual: function(mainObj, templateObj)
372     {
373         try
374         {
375             if ((!mainObj && templateObj) || (typeof(mainObj) != typeof(templateObj))) {
376                 return false;
377             }
378             else if (isNumber(templateObj) || isString(templateObj) || isBoolean(templateObj)) {
379                 return (mainObj === templateObj);
380             }
381             else if (isDate(templateObj)) {
382                 return (mainObj.valueOf() === templateObj.valueOf());
383             }
384             else {
385                 for (var i in templateObj) {
386                     if (!TestEngine.checkObjectsEqual(mainObj[i], templateObj[i])) {
387                         return false;
388                     }
389                 }
390             }
391         }
392         catch(err)
393         {
394             TestEngine.logException("TestEngine.checkObjectsEqual failure: " + err.message);
395             return false;
396         }
397         return true;
398     },
399
400     // test properties of given object. Steps:
401     // - check name presence
402     // - check default value (if not null value passed)
403     // - check if name is writable
404     //
405     // description of properties array:
406     // [0] - property name
407     // [1] - default value - check if property equals given value
408     //          undefined or null - disable check
409     // [2] - value to do writability test - try to write given value
410     //          undefined or null - don't check writability
411     // [3] - indicates if property should be read-only
412     // [4] - assumed type, undefined value skips this check
413     testProperties: function(object, props)
414     {
415         var result = new Object();
416         try
417         {
418             for(var i in props)
419             {
420                 var name = props[i][0];
421                 var defaultVal = props[i][1];
422                 var setVal = props[i][2];
423                 var isReadonly = props[i][3];
424                 var type = props[i][4];
425                 var errors = TestEngine.countErr + TestEngine.countException;
426
427                 if ((typeof(name) != "string") || (name == ""))
428                 {
429                     TestEngine.logException("Property name not defined, skipping it.");
430                     continue;
431                 }
432
433                 result[name] = false;
434                 if (TestEngine.testPresence2(object, name)) {
435                     if ((defaultVal != null) && (defaultVal !== undefined))
436                     {
437                         var isObjectEqual = TestEngine.checkObjectsEqual(object[name], defaultVal);
438                         TestEngine.test(name + " default value", isObjectEqual);
439                     }
440
441                     if ((setVal != null) && (setVal !== undefined))
442                     {
443                         // try-catch is needed when SetProperty returns 'false'
444                         if(setVal === defaultVal)
445                         {
446                             TestEngine.logException("Default value and set value are equal");
447                             continue;
448                         }
449                         try { object[name] = setVal; }
450                         catch (e) { }
451                         if (typeof(isReadonly) == "undefined")
452                         {
453                             TestEngine.test(name + " writability, reason: isReadonly not specified", false);
454                         }
455                         if (isReadonly)
456                         {
457                             TestEngine.test(name + " writability", object[name] != setVal);
458                         }
459                         else
460                         {
461                             var isObjectEqual = TestEngine.checkObjectsEqual(object[name], setVal);
462                             TestEngine.test(name + " writability", isObjectEqual);
463                         }
464                     }
465
466                     if (type !== undefined) {
467                         var isType = (typeof(object[name]) == typeof(type));
468                         if (typeof(type) == 'object') {
469                             if (isArray(type)) {
470                                 isType = isArray(object[name]);
471                             }
472                             else if (isDate(type)) {
473                                 isType = isDate(object[name]);
474                             }
475                         }
476                         TestEngine.test(name + " type check.", isType);
477                     }
478                 }
479                 if (errors == TestEngine.countErr + TestEngine.countException) {
480                     result[name] = true;
481                 }
482             }
483         }
484         catch(err)
485         {
486             TestEngine.countException++;
487             jsPrint("   TestEngine.testProperties failure: " + err.message);
488         }
489         return result;
490     },
491
492     startTestCase: function()
493     {
494         try
495         {
496             TestEngine.countOK = 0;
497             TestEngine.countErr = 0;
498             TestEngine.countException = 0;
499             TestEngine.countIgnored = 0;
500             TestEngine.currentFailings = [];
501             TestEngine.timer = setTimeout(TestEngine.timeout, TestEngine.currentCaseTimeout);
502         }
503         catch(err)
504         {
505             TestEngine.countException++;
506             jsPrint("   TestEngine.startTestCase failure: " + err.message);
507         }
508
509     },
510
511     endTestCase: function(testCase)
512     {
513         try
514         {
515             if(this.timer === null)
516             {
517                 return;
518             }
519
520             clearTimeout(this.timer);
521             this.log("");
522             var ignored = this.countIgnored > 0;
523             var failed = this.countErr || ((this.countOK+this.countErr)<1) || this.countException || ignored;
524
525             if (widget.__test) {
526                 if (ignored) {
527                     widget.__test.collectIgnored(testCase.testName);
528                 } else if (failed) {
529                     widget.__test.collectFail(testCase.testName, TestEngine.currentFailings.join('; '));
530                 } else {
531                     widget.__test.collectPass(testCase.testName);
532                 }
533             }
534             this.log("Test case " + (failed ? "FAILED" : "PASSED"));
535             this.log("Passed: " + this.countOK);
536             this.log("Failed: " + this.countErr);
537             if(this.countException)
538             {
539                 this.log("Exception occured!");
540             }
541
542             this.countAllPassed += this.countOK;
543             this.countAllFailed += this.countErr;
544             this.testSuiteStats[testCase.testSuite].assertsOK += this.countOK;
545             this.testSuiteStats[testCase.testSuite].assertsErr += this.countErr;
546
547             if(failed)
548             {
549                 TestEngine.testCasesFailedCount++;
550                 this.testSuiteStats[testCase.testSuite].failed++;
551                 if (isVerbose()) {
552                     TestEngine.testCasesFailed.push(testCase.testName);
553                 }
554                 TestEngine.resultLogger.logFail(testCase.testName);
555             }
556             else
557             {
558                 TestEngine.testCasesPassedCount++;
559                 this.testSuiteStats[testCase.testSuite].passed++;
560                 TestEngine.resultLogger.logPass(testCase.testName);
561             }
562             TestEngine.summaryRenderer.render(TestEngine);
563         }
564         catch(err)
565         {
566             this.countException++;
567             jsPrint("   TestEngine.endTestCase failure:" + err.message);
568         }
569     },
570
571     timeout: function()
572     {
573         try
574         {
575             TestEngine.callbackMutex = 0;
576             TestEngine.logErr("Widget run timeout.", false);
577         }
578         catch(err)
579         {
580             TestEngine.countException++;
581             jsPrint("   TestEngine.timeout failure:" + err.message);
582         }
583     },
584
585     /**
586      * Registers callbacks for asynchronous function.
587      *
588      * To avoid finish test case before callbacks will execute it's necessary
589      * to register callbacks in the engine.
590      *
591      * @param methodName Testcase name, suggested asynchronous function name.
592      * @param testSuccessCallback Callback that will be executed on success.
593      * @param testErrorCallback Callback that will be executed on failure.
594      * @param callbacksCount number of callbacks to register.
595      * @return An object with defined functions "successCallback" and "errorCallback" you
596      *          need to pass as arguments to asynchronous function  e.g.
597      *
598      * function success() {  }
599      * function failure() {  }
600      *
601      * {
602          *      var obj = TestEngine.registerCallback("myAsyncFunc", success, failure);
603          *      myAsyncFunc(obj.successCallback, obj.errorCallback);
604          * }
605      */
606     registerCallback: function(methodName, testSuccessCallback, testErrorCallback, callbacksCount)
607     {
608         try
609         {
610             if(callbacksCount !== undefined && callbacksCount > 0){
611                 TestEngine.callbackMutex += callbacksCount;
612             }
613             else {
614                 TestEngine.callbackMutex++;
615             }
616             TestEngine.callbackMethodName = methodName;
617             TestEngine.testSuccessCallback = testSuccessCallback;
618             TestEngine.testErrorCallback = testErrorCallback;
619
620             var retObj = new Object();
621             retObj.callbackMethodName = methodName;
622             retObj.testSuccessCallback = testSuccessCallback;
623             retObj.successCallback = function(param){
624                 try
625                 {
626                     if((typeof retObj.testSuccessCallback != "undefined") && (retObj.testSuccessCallback !== null))
627                     {
628                         retObj.testSuccessCallback(param);
629                     }
630                     else
631                     {
632                         TestEngine.logOK(retObj.callbackMethodName + " succeed");
633                     }
634                 }
635                 catch(err)
636                 {
637                     TestEngine.countException++;
638                     jsPrint("   TestEngine.this.successCallback failure:" + err.message);
639                 }
640                 TestEngine.callbackMutex--;
641             };
642
643             retObj.testErrorCallback = testErrorCallback;
644             retObj.errorCallback = function(param){
645                 try
646                 {
647                     if((typeof retObj.testErrorCallback != "undefined") && (retObj.testErrorCallback !== null))
648                     {
649                         retObj.testErrorCallback(param);
650                     }
651                     else
652                     {
653                         TestEngine.logErr(retObj.callbackMethodName + " failed");
654                     }
655                 }
656                 catch(err)
657                 {
658                     TestEngine.countException++;
659                     jsPrint("   TestEngine.retObj.errorCallback failure:" + err.message);
660                 }
661                 TestEngine.callbackMutex--;
662             };
663
664             return retObj;
665         }
666         catch(err)
667         {
668             TestEngine.countException++;
669             jsPrint("   TestEngine.registerCallback failure:" + err.message);
670         }
671     },
672
673     successCallback: function(params)
674     {
675         TestEngine.log("[Warning] Function TestEngine.successCallback deprecated");
676         try
677         {
678             TestEngine.callbackMutex--;
679             if(typeof TestEngine.testSuccessCallback != "undefined")
680             {
681                 TestEngine.testSuccessCallback(params);
682             }
683             else
684             {
685                 TestEngine.logOK(TestEngine.callbackMethodName + " succeed");
686             }
687         }
688         catch(err)
689         {
690             TestEngine.countException++;
691             jsPrint("   TestEngine.successCallback failure:" + err.message);
692         }
693     },
694
695     errorCallback: function(params)
696     {
697         TestEngine.log("[Warning] Function TestEngine.errorCallback deprecated");
698         try
699         {
700             TestEngine.callbackMutex--;
701             if(typeof TestEngine.testErrorCallback != "undefined")
702             {
703                 TestEngine.testErrorCallback(params);
704             }
705             else
706             {
707                 TestEngine.logErr(TestEngine.callbackMethodName + " failed");
708             }
709         }
710         catch(err)
711         {
712             TestEngine.countException++;
713             jsPrint("   TestEngine.errorCallback failure:" + err.message);
714         }
715     },
716
717     waitForCallback: function()
718     {
719         try
720         {
721             //    while( TestEngine.callbackMutex )
722             {
723             }
724         }
725         catch(err)
726         {
727             TestEngine.countException++;
728             jsPrint("   TestEngine.waitForCallback failure:" + err.message);
729         }
730     },
731
732     /*
733      * code - error code which is expected
734      * object - object which will be used to call method
735      * functionName - method name to call
736      * restArguments - rest arguments which will be passed to callback
737      *
738      * example:
739      * TestEngine.catchError(10001, bondi.messaging, findSMSs, succCallback, null, filter)
740      */
741     catchError: function(code, object, functionName, restArguments /* , ... */ )
742     {
743         try
744         {
745             TestEngine.log("TestEngine.catchError is DEPRECATED. Please use TestEngine.catchErrorType.");
746             var error;
747             try
748             {
749                 var newArgs = []
750                 for (var i=3;i<arguments.length;i++) {
751                     newArgs.push(arguments[i])
752                 }
753                 var retVal = null;
754                 retVal = object[functionName].apply(object, newArgs);
755                 TestEngine.logErr(functionName + " no error thrown");
756                 return retVal;
757             }
758             catch(error)
759             {
760                 TestEngine.testPresence("<error code from: " + functionName + ">", error.code);
761                 TestEngine.test("Error number", error.code == code);
762                 return;
763             }
764             TestEngine.logErr("Function " + functionName + " desn't throw");
765         }
766         catch(err)
767         {
768             TestEngine.countException++;
769             jsPrint("   TestEngine.testError failure:" + err.message);
770         }
771     },
772
773     /*
774      * errorTypeName - attribute name of catched exception to compare with code
775      * code - error code which is expected
776      * object - object which will be used to call method
777      * functionName - method name to call
778      * restArguments - rest arguments which will be passed to callback
779      *
780      * example:
781      * TestEngine.catchErrorType("code", 10001, bondi.messaging, findSMSs, succCallback, null, filter)
782      */
783     catchErrorType: function(errorTypeName, code, object, functionName, restArguments /* , ... */ )
784     {
785         try
786         {
787             var error;
788             try
789             {
790                 var newArgs = []
791                 for (var i=4;i<arguments.length;i++) {
792                     newArgs.push(arguments[i])
793                 }
794                 var retVal = null;
795                 if (arguments.length < 4) {
796                     TestEngine.logErr("Wrong catchErrorType usage.");
797                     return retVal;
798                 }
799                 retVal = object[functionName].apply(object, newArgs);
800                 TestEngine.logErr(functionName + " no error thrown");
801                 return retVal;
802             }
803             catch(error)
804             {
805                 TestEngine.testPresence("<error code from: " + functionName + ">", error[errorTypeName]);
806                 TestEngine.test("Error number", error[errorTypeName] == code);
807                 return;
808             }
809             TestEngine.logErr("Function " + functionName + " desn't throw");
810         }
811         catch(err)
812         {
813             TestEngine.countException++;
814             jsPrint("   TestEngine.testError failure:" + err.message);
815         }
816     },
817
818     // Executes step by step functions passed in steps array
819     // and waits after every execution time defined in timeInterval
820     executeSteps: function(steps, timeInterval)
821     {
822         try
823         {
824             if(typeof(timeInterval) == "undefined")
825             {
826                 timeInterval = 100; //default value
827             }
828
829             TestEngine.stepsArray = steps;
830             TestEngine.stepTimeout = timeInterval;
831             TestEngine.currentStep = 0;
832             TestEngine.executeNextStep();
833         }
834         catch(err)
835         {
836             TestEngine.countException++;
837             jsPrint("   TestEngine.executeSteps failure:" + err.message);
838         }
839     },
840
841     executeNextStep: function()
842     {
843         try
844         {
845             if( TestEngine.stepsArray && (TestEngine.currentStep < TestEngine.stepsArray.length) )
846             {
847                 if( isArray( TestEngine.stepsArray[ TestEngine.currentStep ] ) )
848                 {
849                     TestEngine.stepsArray[ TestEngine.currentStep ][0]();
850                     setTimeout( TestEngine.executeNextStep, TestEngine.stepsArray[ TestEngine.currentStep ][1] );
851
852                 }
853                 else
854                 {
855                     TestEngine.stepsArray[ TestEngine.currentStep ]();
856                     setTimeout( TestEngine.executeNextStep, TestEngine.stepTimeout );
857                 }
858                 TestEngine.currentStep++;
859             }
860             else
861             {
862                 TestEngine.currentStep = null;
863                 TestEngine.stepTimeout = null;
864                 TestEngine.stepsArray = null;
865             }
866         }
867         catch(err)
868         {
869             TestEngine.countException++;
870             jsPrint("   TestEngine.executeNextStep failure:" + err.message);
871             jsPrint("   Current step:" + TestEngine.currentStep);
872
873             TestEngine.currentStep = null;
874             TestEngine.stepTimeout = null;
875             TestEngine.stepsArray = null;
876         }
877     },
878
879     enumerate: function(obj, level)
880     {
881         try
882         {
883             if(typeof level == "undefined")
884             {
885                 TestEngine.log(obj + ":");
886                 level = "";
887             }
888             for(i in obj)
889             {
890                 if(!(typeof obj[i] == "object" || typeof obj[i] == "array"))
891                 {
892                     TestEngine.log(level + i + " =  " + obj[i]);
893                 }
894                 else
895                 {
896                     TestEngine.log(level + i + " =  ");
897                     TestEngine.enumerate(obj[i], level + "----");
898                 }
899             }
900         }
901         catch(err)
902         {
903             TestEngine.countException++;
904             jsPrint("   TestEngine.enumerate failure:" + err.message);
905         }
906     },
907
908     doTests: function()
909     {
910         try
911         {
912             TestEngine.testCasesFailed = [];
913             TestEngine.test("jsPrint presence", jsPrint);
914             TestEngine.test("Widget presence", window.widget);
915             TestEngine.doNextTestCase();
916         }
917         catch(err)
918         {
919             TestEngine.countException++;
920             jsPrint("   TestEngine.doTests failure:" + err.message);
921         }
922     },
923
924     showSuitesStats: function()
925     {
926         try
927         {
928             jsPrint("============ Test suites:");
929             for(var i in this.testSuiteStats)
930             {
931                 jsPrint(i + " - " + this.testSuiteStats[i].passed + " passed, " + this.testSuiteStats[i].failed + " failed," + "   asserts: " + this.testSuiteStats[i].assertsOK + " passed, " + this.testSuiteStats[i].assertsErr + " failed"); }
932         }
933         catch(err)
934         {
935             jsPrint("   TestEngine.showSuitesStats failure:" + err.message);
936         }
937     },
938
939     doNextTestCase: function()
940     {
941         try
942         {
943             if( TestEngine.stepsArray !== null || (TestEngine.callbackMutex > 0))
944             {
945                 setTimeout( TestEngine.doNextTestCase, 100 );
946                 return;
947             }
948
949             if(TestEngine.currentTestCase)
950             {
951                 TestEngine.endTestCase(TestEngine.testList[TestEngine.currentTestCase-1]);
952             }
953
954             if( TestEngine.testList.length == TestEngine.currentTestCase )
955             {
956                 if (widget.__test) {
957                     widget.__test.outputResults();
958                 }
959                 jsPrint("============");
960                 jsPrint(TestEngine.finalLog);
961                 TestEngine.showSuitesStats();
962                 jsPrint("============ Summary:");
963                 jsPrint("Test cases all: " + TestEngine.testList.length);
964                 jsPrint("Test cases passed: " + TestEngine.testCasesPassedCount);
965                 jsPrint("Test cases failed: " + TestEngine.testCasesFailedCount);
966                 jsPrint("Asserts passed: " + TestEngine.countAllPassed);
967                 jsPrint("Asserts failed: " + TestEngine.countAllFailed);
968                 if (isVerbose()) {
969                     jsPrint("============ Failing test cases:");
970                     for (i = 0; i < TestEngine.testCasesFailed.length; ++i) {
971                         jsPrint(TestEngine.testCasesFailed[i]);
972                     }
973                 }
974                 TestEngine.summaryRenderer.render(TestEngine);
975
976                 if(typeof TestEngine.finalCallback != "undefined")
977                 {
978                     jsPrint("Registering final callback");
979                     TestEngine.summaryRenderer.render(TestEngine);
980                     setTimeout(TestEngine.finalCallback, 4000);
981                 } else {
982                     jsPrint("Final callback was not registered.");
983                 }
984                 return;
985             }
986
987             var i = TestEngine.currentTestCase++;
988             try
989             {
990                 if (widget.__test) {
991                     if (TestEngine.currentTestSuiteName != TestEngine.testList[i].testSuite) {
992                         TestEngine.currentTestSuiteName = TestEngine.testList[i].testSuite;
993                         widget.__test.collectGroup(TestEngine.testList[i].testSuite);
994                     }
995                 }
996                 TestEngine.log("");
997                 TestEngine.log("==== Test case: " + TestEngine.testList[i].testName);
998                 TestEngine.startTestCase();
999                 var testPrereq = true;
1000                 if (TestEngine.testList[i].testPrereq !== undefined)
1001                 {
1002                     testPrereq = TestEngine.testList[i].testPrereq();
1003                 }
1004                 if (testPrereq)
1005                 {
1006                     if(TestEngine.testList[i].enabled)
1007                     {
1008                         TestEngine.testList[i].testFunc();
1009                     }
1010                     else
1011                     {
1012                         TestEngine.logIgnored("Test disabled");
1013                     }
1014
1015                 }
1016                 else {
1017                     TestEngine.logException
1018                     ("Test case prerequisites unfulfilled.  Skipping it.");
1019                 }
1020             }
1021             catch(err)
1022             {
1023                 TestEngine.countException++;
1024                 TestEngine.log("   Test case '" + TestEngine.testList[i].testName + "' failed:" + err.message);
1025             }
1026             setTimeout( TestEngine.doNextTestCase, 100 );
1027         }
1028         catch(err)
1029         {
1030             jsPrint("   TestEngine.doNextTestCase failure:" + err.message);
1031         }
1032     }
1033 };
1034
1035 function isUndefined(val)
1036 {
1037     if (typeof val == "undefined") {
1038         return true;
1039     }
1040     return false;
1041 }
1042 function isNull(val)
1043 {
1044     return val === null;
1045 }
1046 function isString(val) {
1047     if (typeof val == typeof "") {
1048         return true;
1049     }
1050     return false;
1051 }
1052 function isNumber(val) {
1053     if (typeof val == typeof 0) {
1054         return true
1055     }
1056     return false;
1057 }
1058 function isDate(val) {
1059     return (val instanceof Date);
1060 }
1061 function isFunction(val) {
1062     return (typeof(val) == 'function');
1063 }
1064 function isBoolean(val) {
1065     if (typeof val == typeof true) {
1066         return true
1067     }
1068     return false;
1069 }
1070 function isArray(val) {
1071     return (val instanceof Array);
1072 }
1073 function isObject(val) {
1074     return (val instanceof Object);
1075 }
1076
1077 function isVerbose() {
1078     return ((typeof(VERBOSE) != "undefined") && (VERBOSE === 1));
1079 }
1080
1081 /**
1082  * Tests results logger.
1083  */
1084 function HTMLTestResultLogger(sinkId) {
1085
1086   /**
1087    * Logs a message.
1088    * @param message Message to log.
1089    * @param status Status of the message (PASSED, FAILED, EXCEPTION).
1090    *               By default status is set to PASSED.
1091    */
1092   this.log = function(message, status) {
1093     if (arguments.length < 2) throw "Not enough number of arguments.";
1094     $(sink).append(createLogEntry(message, status));
1095   }
1096
1097   /**
1098    * Helper functions.
1099    */
1100   this.logPass = function(message) {
1101     if (arguments.length < 1) throw "Not enough number of arguments.";
1102     this.log(message, HTMLTestResultLogger.PASSED);
1103   }
1104
1105   this.logFail = function(message) {
1106     if (arguments.length < 1) throw "Not enough number of arguments.";
1107     this.log(message, HTMLTestResultLogger.FAILED);
1108   }
1109
1110   $(document).ready(function() {
1111     sink = document.getElementById(sinkId);
1112     if (null === sink) throw "Summary element unavailable.";
1113   });
1114
1115   var createLogEntry = function(message, status) {
1116     var entry = '<div class="entry ' + status + '">';
1117     entry +=    message.toString();
1118     entry +=    '</div>';
1119     return entry;
1120   }
1121
1122   var id = sinkId;
1123   var sink = null;
1124 }
1125
1126 HTMLTestResultLogger.PASSED = "passed";
1127 HTMLTestResultLogger.FAILED = "failed";
1128 HTMLTestResultLogger.EXCEPTION = "exception";
1129
1130
1131 /**
1132  * Tests summary renderer.
1133  */
1134 function HTMLTestSummaryRenderer(summaryId) {
1135
1136   this.render = function(engine) {
1137     if (arguments.length < 1) throw "Not enough arguments.";
1138
1139     $('#_summary_numberOfRunTests').text(engine.currentTestCase);
1140     $('#_summary_numberOfAllTests').text(engine.testList.length);
1141     $('#_summary_numberOfPassedTests').text(engine.testCasesPassedCount);
1142     $('#_summary_numberOfPassedAsserts').text(engine.countAllPassed);
1143     $('#_summary_numberOfFailedTests').text(engine.testCasesFailedCount);
1144     $('#_summary_numberOfFailedAsserts').text(engine.countAllFailed);
1145     for(var suiteName in engine.testSuiteStats) {
1146       if (!isSuiteStarted(engine.testSuiteStats[suiteName])) continue;
1147       renderSuite(suiteName, engine.testSuiteStats[suiteName]);
1148     }
1149   }
1150
1151   $(document).ready(function() {
1152     summary = document.getElementById(summaryId);
1153     if (null === summary) {
1154       throw "Summary element unavailable.";
1155     }
1156     setupSummary(summary);
1157   });
1158
1159   var isSuiteStarted = function(stats) {
1160     return (stats.passed + stats.failed != 0);
1161   }
1162
1163   var renderSuite = function(name, stats) {
1164     var elementId = '_summary_suite_' + name;
1165     var element = document.getElementById(elementId);
1166     if (null === element) {
1167       element = $('<div id="' + elementId + '">' + name + ': '+ '<span name="stats"></span></div>');
1168       $(summary).append(element);
1169     }
1170     var elementStats = $(element).children('span[name="stats"]');
1171     elementStats.text(stats.passed + '(' + stats.assertsOK + ')' + ' passed, ' +
1172                       stats.failed + '(' + stats.assertsErr + ')' + ' failed');
1173   }
1174
1175   var setupSummary = function(summary) {
1176     var run  = '<div>Run: <span id="_summary_numberOfRunTests">0</span>';
1177         run += ' of <span id="_summary_numberOfAllTests">0</span></div>';
1178     var current = '<div>Current: <span id="_summary_currentTest">0</span></div>';
1179     var passed  = '<div>Passed: <span id="_summary_numberOfPassedTests">0</span>';
1180         passed += '(<span id="_summary_numberOfPassedAsserts">0</span>)</div>';
1181     var failed  = '<div>Failed: <span id="_summary_numberOfFailedTests">0</span>';
1182         failed += '(<span id="_summary_numberOfFailedAsserts">0</span>)</div>';
1183     $(summary).append(run).append(passed).append(failed);
1184   }
1185
1186   var id = summaryId;
1187   var summary = null;
1188 }
1189