From 47490648b14938049ddf84c3f665619c1117241c Mon Sep 17 00:00:00 2001 From: Ulf Hermann Date: Tue, 23 Aug 2022 20:44:13 +0200 Subject: [PATCH] QmlTest: Prevent interleaved execution of TestCases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run all TestCase instances on a timer in a singleton. This way, even if one TestCase manages to trigger a different one while it's still running, the new TestCase won't be executed until the old one is finished. Fixes: QTBUG-98350 Change-Id: I1797b5487f2c70fd2edfdbf8cf4c43a6353b12c8 Reviewed-by: Tor Arne Vestbø --- src/qmltest/CMakeLists.txt | 7 +- src/qmltest/TestCase.qml | 14 ++- src/qmltest/TestSchedule.qml | 29 +++++ src/qmltest/testlogger.js | 16 ++- .../qmltest/selftests/tst_multiTestCase.qml | 117 ++++++++++++++++++ 5 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 src/qmltest/TestSchedule.qml create mode 100644 tests/auto/qmltest/selftests/tst_multiTestCase.qml diff --git a/src/qmltest/CMakeLists.txt b/src/qmltest/CMakeLists.txt index 69698ad72d..8fa3ebbc54 100644 --- a/src/qmltest/CMakeLists.txt +++ b/src/qmltest/CMakeLists.txt @@ -9,6 +9,10 @@ set_source_files_properties(testlogger.js PROPERTIES QT_QML_SKIP_QMLDIR_ENTRY TRUE ) +set_source_files_properties(TestSchedule.qml PROPERTIES + QT_QML_SINGLETON_TYPE TRUE +) + qt_internal_add_qml_module(QuickTest URI "QtTest" VERSION "${PROJECT_VERSION}" @@ -25,8 +29,9 @@ qt_internal_add_qml_module(QuickTest quicktestresult.cpp quicktestresult_p.h quicktestutil.cpp quicktestutil_p.h QML_FILES - TestCase.qml SignalSpy.qml + TestCase.qml + TestSchedule.qml testlogger.js DEFINES QT_NO_FOREACH diff --git a/src/qmltest/TestCase.qml b/src/qmltest/TestCase.qml index 2fcf9611c0..d86478eaf9 100644 --- a/src/qmltest/TestCase.qml +++ b/src/qmltest/TestCase.qml @@ -1849,7 +1849,11 @@ Item { /*! \internal */ function qtest_run() { - if (TestLogger.log_start_test()) { + if (!when || completed || running || !qtest_componentCompleted) + return; + + verify(TestLogger.log_can_start_test(qtest_testId)) + if (TestLogger.log_start_test(qtest_testId)) { qtest_results.reset() qtest_results.testCaseName = name qtest_results.startLogging() @@ -2020,8 +2024,8 @@ Item { onWhenChanged: { if (when != qtest_prevWhen) { qtest_prevWhen = when - if (when && !completed && !running && qtest_componentCompleted) - qtest_run() + if (when) + TestSchedule.testCases.push(testCase) } } @@ -2041,7 +2045,7 @@ Item { if (optional) TestLogger.log_optional_test(qtest_testId) qtest_prevWhen = when - if (when && !completed && !running) - qtest_run() + if (when) + TestSchedule.testCases.push(testCase) } } diff --git a/src/qmltest/TestSchedule.qml b/src/qmltest/TestSchedule.qml new file mode 100644 index 0000000000..a2d291f80d --- /dev/null +++ b/src/qmltest/TestSchedule.qml @@ -0,0 +1,29 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +pragma Singleton +import QtQml + +Timer { + property list testCases + property QtObject currentTest: null + + running: testCases.length > 0 && !currentTest + interval: 1 + repeat: true + + onTriggered: { + if (currentTest) { + console.error("Interleaved test execution detected. This shouldn't happen") + return; + } + + try { + currentTest = testCases.shift() + currentTest.qtest_run() + } finally { + currentTest = null + } + } + +} diff --git a/src/qmltest/testlogger.js b/src/qmltest/testlogger.js index b001df3c58..dc28bf6c73 100644 --- a/src/qmltest/testlogger.js +++ b/src/qmltest/testlogger.js @@ -9,7 +9,7 @@ function log_init_results() { if (!testResults) { testResults = { - reportedStart: false, + runningTest: -1, nextId: 0, testCases: [] } @@ -36,16 +36,21 @@ function log_mandatory_test(testId) { log_init_results() var index = testResults.testCases.indexOf(testId) - if (index == -1) + if (index === -1) testResults.testCases.push(testId) } -function log_start_test() +function log_can_start_test(testId) +{ + return !testResults || testResults.runningTest === -1 || testResults.runningTest === testId; +} + +function log_start_test(testId) { log_init_results() - if (testResults.reportedStart) + if (testResults.runningTest === testId) return false - testResults.reportedStart = true + testResults.runningTest = testId return true } @@ -54,5 +59,6 @@ function log_complete_test(testId) var index = testResults.testCases.indexOf(testId) if (index >= 0) testResults.testCases.splice(index, 1) + testResults.runningTest = -1 return testResults.testCases.length > 0 } diff --git a/tests/auto/qmltest/selftests/tst_multiTestCase.qml b/tests/auto/qmltest/selftests/tst_multiTestCase.qml new file mode 100644 index 0000000000..220888a5da --- /dev/null +++ b/tests/auto/qmltest/selftests/tst_multiTestCase.qml @@ -0,0 +1,117 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import QtTest +import QtQuick + +Item { + id: root + property int finished: 0 + property TestCase currentTest + + Repeater { + model: 100 + TestCase { + id: test + + name: "A" + + required property int index + when: root.children.length > 50 && ((root.finished + index) % 17 == 0 || cutoff.completed) + + function initTestCase() { + compare(root.currentTest, null) + root.currentTest = test + } + + function init() { + compare(root.currentTest, test) + } + + function test_do() { + compare(root.currentTest, test) + ++root.finished + wait(index / 10) + compare(root.currentTest, test) + } + + function cleanup() { + compare(root.currentTest, test) + } + + function cleanupTestCase() { + compare(root.currentTest, test) + root.currentTest = null + } + } + } + + TestCase { + id: cutoff + name: "C" + when: root.finished > 80 + + function initTestCase() { + compare(root.currentTest, null) + root.currentTest = cutoff + } + + function init() { + compare(root.currentTest, cutoff) + } + + function test_cutoff() { + compare(root.currentTest, cutoff) + } + + function cleanup() { + compare(root.currentTest, cutoff) + } + + function cleanupTestCase() { + compare(root.currentTest, cutoff) + root.currentTest = null + } + } + + TestCase { + id: sum + name: "B" + when: root.finished === 100 + + function initTestCase() { + compare(root.currentTest, null) + root.currentTest = sum + } + + function init() { + compare(root.currentTest, sum) + } + + + function test_sum() { + compare(root.currentTest, sum) + var numTests = 0; + for (var i in root.children) { + var test = root.children[i]; + if (test.name === "A") { + verify(test.completed) + ++numTests + } + } + + compare(numTests, 100) + compare(root.currentTest, sum) + } + + + function cleanup() { + compare(root.currentTest, sum) + } + + function cleanupTestCase() { + compare(root.currentTest, sum) + root.currentTest = null + } + } +}