Merge branch 'master' of git://scm.dev.nokia.troll.no/qt/qtdeclarative
[profile/ivi/qtdeclarative.git] / src / imports / testlib / TestCase.qml
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** No Commercial Usage
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU Lesser
18 ** General Public License version 2.1 as published by the Free Software
19 ** Foundation and appearing in the file LICENSE.LGPL included in the
20 ** packaging of this file.  Please review the following information to
21 ** ensure the GNU Lesser General Public License version 2.1 requirements
22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23 **
24 ** In addition, as a special exception, Nokia gives you certain additional
25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27 **
28 ** If you have questions regarding the use of this file, please contact
29 ** Nokia at qt-info@nokia.com.
30 **
31 **
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 import QtQuick 2.0
43 import QtTest 1.0
44 import "testlogger.js" as TestLogger
45
46 Item {
47     id: testCase
48     visible: false
49
50     // Name of the test case to prefix the function name in messages.
51     property string name
52
53     // Set to true to start the test running.
54     property bool when: true
55
56     // Set to true once the test has completed.
57     property bool completed: false
58
59     // Set to true when the test is running but not yet complete.
60     property bool running: false
61
62     // Set to true if the test doesn't have to run (because some
63     // other test failed which this one depends on).
64     property bool optional: false
65
66     // Property that is set to true when the main window is shown.
67     // We need to set the property value in an odd way to handle
68     // both qmlviewer and the QtQuickTest module test wrapper.
69     property bool windowShown: Qt.qtest_wrapper ? qtest.windowShown : false
70
71     // Internal private state.  Identifiers prefixed with qtest are reserved.
72     property bool qtest_prevWhen: true
73     property int qtest_testId: -1
74     property variant qtest_testCaseResult
75     property variant qtest_results: qtest_results_normal
76     TestResult { id: qtest_results_normal }
77     property variant qtest_events: qtest_events_normal
78     TestEvent { id: qtest_events_normal }
79
80     function fail(msg) {
81         if (msg === undefined)
82             msg = "";
83         qtest_results.fail(msg, Qt.qtest_caller_file(), Qt.qtest_caller_line())
84         throw new Error("QtQuickTest::fail")
85     }
86
87     function qtest_fail(msg, frame) {
88         if (msg === undefined)
89             msg = "";
90         qtest_results.fail(msg, Qt.qtest_caller_file(frame), Qt.qtest_caller_line(frame))
91         throw new Error("QtQuickTest::fail")
92     }
93
94     function verify(cond, msg) {
95         if (msg === undefined)
96             msg = "";
97         if (!qtest_results.verify(cond, msg, Qt.qtest_caller_file(), Qt.qtest_caller_line()))
98             throw new Error("QtQuickTest::fail")
99     }
100
101     // Determine what is o.
102     // Discussions and reference: http://philrathe.com/articles/equiv
103     // Test suites: http://philrathe.com/tests/equiv
104     // Author: Philippe Rathé <prathe@gmail.com>
105     function qtest_typeof(o) {
106         if (typeof o === "undefined") {
107             return "undefined";
108
109         // consider: typeof null === object
110         } else if (o === null) {
111             return "null";
112
113         } else if (o.constructor === String) {
114             return "string";
115
116         } else if (o.constructor === Boolean) {
117             return "boolean";
118
119         } else if (o.constructor === Number) {
120
121             if (isNaN(o)) {
122                 return "nan";
123             } else {
124                 return "number";
125             }
126         // consider: typeof [] === object
127         } else if (o instanceof Array) {
128             return "array";
129
130         // consider: typeof new Date() === object
131         } else if (o instanceof Date) {
132             return "date";
133
134         // consider: /./ instanceof Object;
135         //           /./ instanceof RegExp;
136         //          typeof /./ === "function"; // => false in IE and Opera,
137         //                                          true in FF and Safari
138         } else if (o instanceof RegExp) {
139             return "regexp";
140
141         } else if (typeof o === "object") {
142             if ("mapFromItem" in o && "mapToItem" in o) {
143                 return "declarativeitem";  // @todo improve detection of declarative items
144             } else if ("x" in o && "y" in o && "z" in o) {
145                 return "vector3d"; // Qt3D vector
146             }
147             return "object";
148         } else if (o instanceof Function) {
149             return "function";
150         } else {
151             return undefined;
152         }
153     }
154
155     // Test for equality
156     // Large parts contain sources from QUnit or http://philrathe.com
157     // Discussions and reference: http://philrathe.com/articles/equiv
158     // Test suites: http://philrathe.com/tests/equiv
159     // Author: Philippe Rathé <prathe@gmail.com>
160     function qtest_compareInternal(act, exp) {
161         var success = false;
162
163         if (act === exp) {
164             success = true; // catch the most you can
165         } else if (act === null || exp === null || typeof act === "undefined" || typeof exp === "undefined") {
166             success = false; // don't lose time with error prone cases
167         } else {
168             var typeExp = qtest_typeof(exp), typeAct = qtest_typeof(act)
169
170             if (typeExp !== typeAct) {
171                 // allow object vs string comparison (e.g. for colors)
172                 // else break on different types
173                 if ((typeExp === "string" && typeAct === "object") || (typeExp === "object" && typeAct === "string")) {
174                     success = (act == exp)
175                 }
176             } else if (typeExp === "string" || typeExp === "boolean" || typeExp === "number" ||
177                        typeExp === "null" || typeExp === "undefined") {
178                 if (exp instanceof act.constructor || act instanceof exp.constructor) {
179                     // to catch short annotaion VS 'new' annotation of act declaration
180                     // e.g. var i = 1;
181                     //      var j = new Number(1);
182                     success = (act == exp)
183                 } else {
184                     success = (act === exp)
185                 }
186             } else if (typeExp === "nan") {
187                 success = isNaN(act);
188             } else if (typeExp == "number") {
189                 // Use act fuzzy compare if the two values are floats
190                 if (Math.abs(act - exp) <= 0.00001) {
191                     success = true
192                 }
193             } else if (typeExp === "array") {
194                 success = qtest_compareInternalArrays(act, exp)
195             } else if (typeExp === "object") {
196                 success = qtest_compareInternalObjects(act, exp)
197             } else if (typeExp === "declarativeitem") {
198                 success = qtest_compareInternalObjects(act, exp) // @todo improve comparison of declarative items
199             } else if (typeExp === "vector3d") {
200                 success = (Math.abs(act.x - exp.x) <= 0.00001 &&
201                            Math.abs(act.y - exp.y) <= 0.00001 &&
202                            Math.abs(act.z - exp.z) <= 0.00001)
203             } else if (typeExp === "date") {
204                 success = (act.valueOf() === exp.valueOf())
205             } else if (typeExp === "regexp") {
206                 success = (act.source === exp.source && // the regex itself
207                            act.global === exp.global && // and its modifers (gmi) ...
208                            act.ignoreCase === exp.ignoreCase &&
209                            act.multiline === exp.multiline)
210             }
211         }
212         return success
213     }
214
215     function qtest_compareInternalObjects(act, exp) {
216         var i;
217         var eq = true; // unless we can proove it
218         var aProperties = [], bProperties = []; // collection of strings
219
220         // comparing constructors is more strict than using instanceof
221         if (act.constructor !== exp.constructor) {
222             return false;
223         }
224
225         for (i in act) { // be strict: don't ensures hasOwnProperty and go deep
226             aProperties.push(i); // collect act's properties
227
228             if (!qtest_compareInternal(act[i], exp[i])) {
229                 eq = false;
230                 break;
231             }
232         }
233
234         for (i in exp) {
235             bProperties.push(i); // collect exp's properties
236         }
237
238         // Ensures identical properties name
239         return eq && qtest_compareInternal(aProperties.sort(), bProperties.sort());
240
241     }
242
243     function qtest_compareInternalArrays(actual, expected) {
244         if (actual.length != expected.length) {
245             return false
246         }
247
248         for (var i = 0, len = actual.length; i < len; i++) {
249             if (!qtest_compareInternal(actual[i], expected[i])) {
250                 return false
251             }
252         }
253
254         return true
255     }
256
257     function qtest_formatValue(value) {
258         if (typeof value == "object") {
259             if ("x" in value && "y" in value && "z" in value) {
260                 return "Qt.vector3d(" + value.x + ", " +
261                        value.y + ", " + value.z + ")"
262             }
263             try {
264                 return JSON.stringify(value)
265             } catch (ex) {
266                 // stringify might fail (e.g. due to circular references)
267             }
268         }
269         return value
270     }
271
272     function compare(actual, expected, msg) {
273         var act = qtest_formatValue(actual)
274         var exp = qtest_formatValue(expected)
275         var success = qtest_compareInternal(actual, expected)
276         if (msg === undefined) {
277             if (success)
278                 msg = "COMPARE()"
279             else
280                 msg = "Compared values are not the same"
281         }
282         if (!qtest_results.compare(success, msg, act, exp, Qt.qtest_caller_file(), Qt.qtest_caller_line()))
283             throw new Error("QtQuickTest::fail")
284     }
285
286     function tryCompare(obj, prop, value, timeout) {
287         if (!timeout)
288             timeout = 5000
289         if (!qtest_compareInternal(obj[prop], value))
290             wait(0)
291         var i = 0
292         while (i < timeout && !qtest_compareInternal(obj[prop], value)) {
293             wait(50)
294             i += 50
295         }
296         var actual = obj[prop]
297         var act = qtest_formatValue(actual)
298         var exp = qtest_formatValue(value)
299         var success = qtest_compareInternal(actual, value)
300         if (!qtest_results.compare(success, "property " + prop, act, exp, Qt.qtest_caller_file(), Qt.qtest_caller_line()))
301             throw new Error("QtQuickTest::fail")
302     }
303
304     function skip(msg) {
305         if (msg === undefined)
306             msg = ""
307         qtest_results.skipSingle(msg, Qt.qtest_caller_file(), Qt.qtest_caller_line())
308         throw new Error("QtQuickTest::skip")
309     }
310
311     function skipAll(msg) {
312         if (msg === undefined)
313             msg = ""
314         qtest_results.skipAll(msg, Qt.qtest_caller_file(), Qt.qtest_caller_line())
315         throw new Error("QtQuickTest::skip")
316     }
317
318     function expectFail(tag, msg) {
319         if (tag === undefined) {
320             warn("tag argument missing from expectFail()")
321             tag = ""
322         }
323         if (msg === undefined) {
324             warn("message argument missing from expectFail()")
325             msg = ""
326         }
327         if (!qtest_results.expectFail(tag, msg, Qt.qtest_caller_file(), Qt.qtest_caller_line()))
328             throw new Error("QtQuickTest::expectFail")
329     }
330
331     function expectFailContinue(tag, msg) {
332         if (tag === undefined) {
333             warn("tag argument missing from expectFailContinue()")
334             tag = ""
335         }
336         if (msg === undefined) {
337             warn("message argument missing from expectFailContinue()")
338             msg = ""
339         }
340         if (!qtest_results.expectFailContinue(tag, msg, Qt.qtest_caller_file(), Qt.qtest_caller_line()))
341             throw new Error("QtQuickTest::expectFail")
342     }
343
344     function warn(msg) {
345         if (msg === undefined)
346             msg = ""
347         qtest_results.warn(msg);
348     }
349
350     function ignoreWarning(msg) {
351         if (msg === undefined)
352             msg = ""
353         qtest_results.ignoreWarning(msg)
354     }
355
356     function wait(ms) {
357         qtest_results.wait(ms)
358     }
359
360     function sleep(ms) {
361         qtest_results.sleep(ms)
362     }
363
364     function keyPress(key, modifiers, delay) {
365         if (modifiers === undefined)
366             modifiers = Qt.NoModifier
367         if (delay == undefined)
368             delay = -1
369         if (!qtest_events.keyPress(key, modifiers, delay))
370             qtest_fail("window not shown", 2)
371     }
372
373     function keyRelease(key, modifiers, delay) {
374         if (modifiers === undefined)
375             modifiers = Qt.NoModifier
376         if (delay == undefined)
377             delay = -1
378         if (!qtest_events.keyRelease(key, modifiers, delay))
379             qtest_fail("window not shown", 2)
380     }
381
382     function keyClick(key, modifiers, delay) {
383         if (modifiers === undefined)
384             modifiers = Qt.NoModifier
385         if (delay == undefined)
386             delay = -1
387         if (!qtest_events.keyClick(key, modifiers, delay))
388             qtest_fail("window not shown", 2)
389     }
390
391     function mousePress(item, x, y, button, modifiers, delay) {
392         if (button === undefined)
393             button = Qt.LeftButton
394         if (modifiers === undefined)
395             modifiers = Qt.NoModifier
396         if (delay == undefined)
397             delay = -1
398         if (!qtest_events.mousePress(item, x, y, button, modifiers, delay))
399             qtest_fail("window not shown", 2)
400     }
401
402     function mouseRelease(item, x, y, button, modifiers, delay) {
403         if (button === undefined)
404             button = Qt.LeftButton
405         if (modifiers === undefined)
406             modifiers = Qt.NoModifier
407         if (delay == undefined)
408             delay = -1
409         if (!qtest_events.mouseRelease(item, x, y, button, modifiers, delay))
410             qtest_fail("window not shown", 2)
411     }
412
413     function mouseClick(item, x, y, button, modifiers, delay) {
414         if (button === undefined)
415             button = Qt.LeftButton
416         if (modifiers === undefined)
417             modifiers = Qt.NoModifier
418         if (delay == undefined)
419             delay = -1
420         if (!qtest_events.mouseClick(item, x, y, button, modifiers, delay))
421             qtest_fail("window not shown", 2)
422     }
423
424     function mouseDoubleClick(item, x, y, button, modifiers, delay) {
425         if (button === undefined)
426             button = Qt.LeftButton
427         if (modifiers === undefined)
428             modifiers = Qt.NoModifier
429         if (delay == undefined)
430             delay = -1
431         if (!qtest_events.mouseDoubleClick(item, x, y, button, modifiers, delay))
432             qtest_fail("window not shown", 2)
433     }
434
435     function mouseMove(item, x, y, delay) {
436         if (delay == undefined)
437             delay = -1
438         if (!qtest_events.mouseMove(item, x, y, delay))
439             qtest_fail("window not shown", 2)
440     }
441
442     // Functions that can be overridden in subclasses for init/cleanup duties.
443     function initTestCase() {}
444     function cleanupTestCase() {}
445     function init() {}
446     function cleanup() {}
447
448     function qtest_runInternal(prop, arg) {
449         try {
450             qtest_testCaseResult = testCase[prop](arg)
451         } catch (e) {
452             qtest_testCaseResult = []
453             if (e.message.indexOf("QtQuickTest::") != 0) {
454                 // Test threw an unrecognized exception - fail.
455                 qtest_results.fail("Uncaught exception: " + e.message,
456                              e.fileName, e.lineNumber)
457             }
458         }
459         return !qtest_results.dataFailed
460     }
461
462     function qtest_runFunction(prop, arg) {
463         qtest_results.functionType = TestResult.InitFunc
464         qtest_runInternal("init")
465         if (!qtest_results.skipped) {
466             qtest_results.functionType = TestResult.Func
467             qtest_runInternal(prop, arg)
468             qtest_results.functionType = TestResult.CleanupFunc
469             qtest_runInternal("cleanup")
470         }
471         qtest_results.functionType = TestResult.NoWhere
472     }
473
474     function qtest_runBenchmarkFunction(prop, arg) {
475         qtest_results.startMeasurement()
476         do {
477             qtest_results.beginDataRun()
478             do {
479                 // Run the initialization function.
480                 qtest_results.functionType = TestResult.InitFunc
481                 qtest_runInternal("init")
482                 if (qtest_results.skipped)
483                     break
484
485                 // Execute the benchmark function.
486                 qtest_results.functionType = TestResult.Func
487                 if (prop.indexOf("benchmark_once_") != 0)
488                     qtest_results.startBenchmark(TestResult.RepeatUntilValidMeasurement, qtest_results.dataTag)
489                 else
490                     qtest_results.startBenchmark(TestResult.RunOnce, qtest_results.dataTag)
491                 while (!qtest_results.isBenchmarkDone()) {
492                     if (!qtest_runInternal(prop, arg))
493                         break
494                     qtest_results.nextBenchmark()
495                 }
496                 qtest_results.stopBenchmark()
497
498                 // Run the cleanup function.
499                 qtest_results.functionType = TestResult.CleanupFunc
500                 qtest_runInternal("cleanup")
501                 qtest_results.functionType = TestResult.NoWhere
502             } while (!qtest_results.measurementAccepted())
503             qtest_results.endDataRun()
504         } while (qtest_results.needsMoreMeasurements())
505     }
506
507     function qtest_run() {
508         if (Qt.qtest_printAvailableFunctions) {
509             completed = true
510             return
511         }
512
513         if (TestLogger.log_start_test()) {
514             qtest_results.reset()
515             qtest_results.testCaseName = name
516             qtest_results.startLogging()
517         } else {
518             qtest_results.testCaseName = name
519         }
520         running = true
521
522         // Check the run list to see if this class is mentioned.
523         var functionsToRun = qtest_results.functionsToRun
524         if (functionsToRun.length > 0) {
525             var found = false
526             var list = []
527             if (name.length > 0) {
528                 var prefix = name + "::"
529                 for (var index in functionsToRun) {
530                     if (functionsToRun[index].indexOf(prefix) == 0) {
531                         list.push(functionsToRun[index])
532                         found = true
533                     }
534                 }
535             }
536             if (!found) {
537                 completed = true
538                 if (!TestLogger.log_complete_test(qtest_testId)) {
539                     qtest_results.stopLogging()
540                     Qt.quit()
541                 }
542                 qtest_results.testCaseName = ""
543                 return
544             }
545             functionsToRun = list
546         }
547
548         // Run the initTestCase function.
549         qtest_results.functionName = "initTestCase"
550         qtest_results.functionType = TestResult.InitFunc
551         var runTests = true
552         if (!qtest_runInternal("initTestCase"))
553             runTests = false
554         qtest_results.finishTestFunction()
555
556         // Run the test methods.
557         var testList = []
558         if (runTests) {
559             for (var prop in testCase) {
560                 if (prop.indexOf("test_") != 0 && prop.indexOf("benchmark_") != 0)
561                     continue
562                 var tail = prop.lastIndexOf("_data");
563                 if (tail != -1 && tail == (prop.length - 5))
564                     continue
565                 testList.push(prop)
566             }
567             testList.sort()
568         }
569         var checkNames = (functionsToRun.length > 0)
570         for (var index in testList) {
571             var prop = testList[index]
572             var datafunc = prop + "_data"
573             var isBenchmark = (prop.indexOf("benchmark_") == 0)
574             if (checkNames) {
575                 var index = functionsToRun.indexOf(name + "::" + prop)
576                 if (index < 0)
577                     continue
578                 functionsToRun.splice(index, 1)
579             }
580             qtest_results.functionName = prop
581             if (datafunc in testCase) {
582                 qtest_results.functionType = TestResult.DataFunc
583                 if (qtest_runInternal(datafunc)) {
584                     var table = qtest_testCaseResult
585                     var haveData = false
586                     qtest_results.initTestTable()
587                     for (var index in table) {
588                         haveData = true
589                         var row = table[index]
590                         if (!row.tag)
591                             row.tag = "row " + index    // Must have something
592                         qtest_results.dataTag = row.tag
593                         if (isBenchmark)
594                             qtest_runBenchmarkFunction(prop, row)
595                         else
596                             qtest_runFunction(prop, row)
597                         qtest_results.dataTag = ""
598                     }
599                     if (!haveData)
600                         qtest_results.warn("no data supplied for " + prop + "() by " + datafunc + "()")
601                     qtest_results.clearTestTable()
602                 }
603             } else if (isBenchmark) {
604                 qtest_runBenchmarkFunction(prop, null, isBenchmark)
605             } else {
606                 qtest_runFunction(prop, null, isBenchmark)
607             }
608             qtest_results.finishTestFunction()
609             qtest_results.skipped = false
610         }
611
612         // Run the cleanupTestCase function.
613         qtest_results.skipped = false
614         qtest_results.functionName = "cleanupTestCase"
615         qtest_results.functionType = TestResult.CleanupFunc
616         qtest_runInternal("cleanupTestCase")
617
618         // Complain about missing functions that we were supposed to run.
619         if (functionsToRun.length > 0)
620             qtest_results.fail("Could not find functions: " + functionsToRun, "", 0)
621
622         // Clean up and exit.
623         running = false
624         completed = true
625         qtest_results.finishTestFunction()
626         qtest_results.functionName = ""
627
628         // Stop if there are no more tests to be run.
629         if (!TestLogger.log_complete_test(qtest_testId)) {
630             qtest_results.stopLogging()
631             Qt.quit()
632         }
633         qtest_results.testCaseName = ""
634     }
635
636     onWhenChanged: {
637         if (when != qtest_prevWhen) {
638             qtest_prevWhen = when
639             if (when && !completed && !running)
640                 qtest_run()
641         }
642     }
643
644     onOptionalChanged: {
645         if (!completed) {
646             if (optional)
647                 TestLogger.log_optional_test(qtest_testId)
648             else
649                 TestLogger.log_mandatory_test(qtest_testId)
650         }
651     }
652
653     // The test framework will set qtest.windowShown when the
654     // window is actually shown.  If we are running with qmlviewer,
655     // then this won't happen.  So we use a timer instead.
656     Timer {
657         id: qtest_windowShowTimer
658         interval: 100
659         repeat: false
660         onTriggered: { windowShown = true }
661     }
662
663     Component.onCompleted: {
664         if (Qt.qtest_printAvailableFunctions) {
665             var testList = []
666             for (var prop in testCase) {
667                 if (prop.indexOf("test_") != 0 && prop.indexOf("benchmark_") != 0)
668                     continue
669                 var tail = prop.lastIndexOf("_data");
670                 if (tail != -1 && tail == (prop.length - 5))
671                     continue
672                 // Note: cannot run functions in TestCase elements
673                 // that lack a name.
674                 if (name.length > 0)
675                     testList.push(name + "::" + prop + "()")
676             }
677             testList.sort()
678             for (var index in testList)
679                 console.log(testList[index])
680             return
681         }
682         qtest_testId = TestLogger.log_register_test(name)
683         if (optional)
684             TestLogger.log_optional_test(qtest_testId)
685         qtest_prevWhen = when
686         var isQmlViewer = Qt.qtest_wrapper ? false : true
687         if (isQmlViewer)
688             qtest_windowShowTimer.running = true
689         if (when && !completed && !running)
690             qtest_run()
691     }
692 }