summaryrefslogtreecommitdiffstats
path: root/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp')
-rw-r--r--tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp278
1 files changed, 278 insertions, 0 deletions
diff --git a/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp b/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp
new file mode 100644
index 000000000..6809f81a8
--- /dev/null
+++ b/tests/auto/integration/qwindowcapturebackend/tst_qwindowcapturebackend.cpp
@@ -0,0 +1,278 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+// TESTED_COMPONENT=src/multimedia
+
+#include "fixture.h"
+#include "widget.h"
+
+#include <qmediarecorder.h>
+#include <qpainter.h>
+#include <qsignalspy.h>
+#include <qtest.h>
+#include <qwindowcapture.h>
+#include <qcommandlineparser.h>
+
+#include <chrono>
+#include <vector>
+
+using std::chrono::duration_cast;
+using std::chrono::high_resolution_clock;
+using std::chrono::microseconds;
+
+QT_USE_NAMESPACE
+
+class tst_QWindowCaptureBackend : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ static void initTestCase()
+ {
+#ifdef Q_OS_ANDROID
+ QSKIP("Feature does not work on Android");
+#endif
+#if defined(Q_OS_LINUX)
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci"
+ && qEnvironmentVariable("XDG_SESSION_TYPE").toLower() != "x11")
+ QSKIP("Skip on wayland; to be fixed");
+#elif defined(Q_OS_MACOS)
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116285: Skip on macOS CI because of permissions issues");
+#endif
+
+ const QWindowCapture capture;
+ if (capture.error() == QWindowCapture::CapturingNotSupported)
+ QSKIP("Screen capturing not supported");
+ }
+
+ void isActive_returnsFalse_whenNotStarted()
+ {
+ const WindowCaptureFixture fixture;
+ QVERIFY(!fixture.m_capture.isActive());
+ }
+
+ void setActive_failsAndEmitEerrorOccurred_whenNoWindowSelected()
+ {
+ WindowCaptureFixture fixture;
+
+ fixture.m_capture.setActive(true);
+
+ QVERIFY(!fixture.m_capture.isActive());
+ QVERIFY(!fixture.m_errors.empty());
+ }
+
+ void setActive_startsWindowCapture_whenCalledWithTrue()
+ {
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start());
+
+ // Ensure that we have received a frame
+ QVERIFY(fixture.waitForFrame().isValid());
+
+ QCOMPARE(fixture.m_activations.size(), 1);
+ QVERIFY(fixture.m_errors.empty());
+ }
+
+ void capturedImage_equals_imageFromGrab_data()
+ {
+ QTest::addColumn<QSize>("windowSize");
+ QTest::newRow("single-pixel-window") << QSize{1, 1};
+ QTest::newRow("small-window") << QSize{60, 40};
+ QTest::newRow("odd-width-window") << QSize{ 61, 40 };
+ QTest::newRow("odd-height-window") << QSize{ 60, 41 };
+ QTest::newRow("big-window") << QApplication::primaryScreen()->size();
+ }
+
+ void capturedImage_equals_imageFromGrab()
+ {
+ QFETCH(QSize, windowSize);
+
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start(windowSize));
+
+ const QImage expected = fixture.m_widget.grabImage();
+ const QImage actual = fixture.waitForFrame().toImage();
+
+ QVERIFY(fixture.compareImages(actual, expected));
+ }
+
+ void capturedImage_changes_whenWindowContentChanges()
+ {
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start());
+
+ const auto startTime = high_resolution_clock::now();
+
+ const QVideoFrame colorFrame = fixture.waitForFrame();
+ QVERIFY(colorFrame.isValid());
+
+ fixture.m_widget.setDisplayPattern(TestWidget::Grid);
+
+ // Ignore all frames that were grabbed since the colored frame,
+ // to ensure that we get a frame after we changed display pattern
+ const high_resolution_clock::duration delay = high_resolution_clock::now() - startTime;
+ const QVideoFrame gridFrame = fixture.waitForFrame(
+ colorFrame.endTime() + duration_cast<microseconds>(delay).count());
+
+ QVERIFY(gridFrame.isValid());
+
+ // Make sure that the gridFrame has a different content than the colorFrame
+ QCOMPARE(gridFrame.size(), colorFrame.size());
+ QCOMPARE_NE(gridFrame.toImage(), colorFrame.toImage());
+
+ const QImage actualGridImage = fixture.m_widget.grabImage();
+ QVERIFY(fixture.compareImages(gridFrame.toImage(), actualGridImage));
+ }
+
+ void sequenceOfCapturedImages_compareEqual_whenWindowContentIsUnchanged()
+ {
+ WindowCaptureWithWidgetFixture fixture;
+ QVERIFY(fixture.start());
+
+ const std::vector<QVideoFrame> frames = fixture.m_grabber.waitAndTakeFrames(10);
+ QVERIFY(!frames.empty());
+
+ QImage firstFrame = frames.front().toImage();
+ QVERIFY(!firstFrame.isNull());
+
+ qsizetype index = 0;
+ for (const auto &frame : std::as_const(frames)){
+ QVERIFY(fixture.compareImages(frame.toImage(), firstFrame, QString::number(index)));
+ ++index;
+ }
+ }
+
+ void recorder_encodesFrames_toValidMediaFile_data()
+ {
+ QTest::addColumn<QSize>("windowSize");
+ //QTest::newRow("empty-window") << QSize{ 0, 0 }; TODO: Crash
+ //QTest::newRow("single-pixel-window") << QSize{ 1, 1 }; TODO: Crash
+ QTest::newRow("small-window") << QSize{ 60, 40 };
+ QTest::newRow("odd-width-window") << QSize{ 61, 40 };
+ QTest::newRow("odd-height-window") << QSize{ 60, 41 };
+ QTest::newRow("big-window") << QSize{ 800, 600 };
+ }
+
+ void recorder_encodesFrames_toValidMediaFile()
+ {
+#ifdef Q_OS_LINUX
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116671: SKIP on linux CI to avoid crashes in ffmpeg. To be fixed.");
+#endif
+ QFETCH(QSize, windowSize);
+
+ WindowCaptureWithWidgetAndRecorderFixture fixture;
+ fixture.start(windowSize);
+
+ // Wait on grabber to ensure that video recorder also get some frames
+ fixture.m_grabber.waitAndTakeFrames(60);
+
+ // Wait for recorder finalization
+ fixture.stop();
+
+ QVERIFY(fixture.m_recorderErrors.empty());
+ QVERIFY(QFile{ fixture.m_mediaFile }.exists());
+ QVERIFY(fixture.testVideoFilePlayback(fixture.m_mediaFile));
+ }
+
+ void recorder_encodesFrames_toValidMediaFile_whenWindowResizes_data()
+ {
+ QTest::addColumn<int>("increment");
+ QTest::newRow("shrink") << -1;
+ QTest::newRow("grow") << 1;
+ }
+
+ void recorder_encodesFrames_toValidMediaFile_whenWindowResizes()
+ {
+#ifdef Q_OS_LINUX
+ if (qEnvironmentVariable("QTEST_ENVIRONMENT").toLower() == "ci")
+ QSKIP("QTBUG-116671: SKIP on linux CI to avoid crashes in ffmpeg. To be fixed.");
+#endif
+ QFETCH(int, increment);
+
+ QSize windowSize = { 200, 150 };
+ WindowCaptureWithWidgetAndRecorderFixture fixture;
+ fixture.start(windowSize, /*toggle pattern*/ false);
+
+ for (qsizetype i = 0; i < 20; ++i) {
+ windowSize.setWidth(windowSize.width() + increment);
+ windowSize.setHeight(windowSize.height() + increment);
+ fixture.m_widget.setSize(windowSize);
+
+ // Wait on grabber to ensure that video recorder also get some frames
+ fixture.m_grabber.waitAndTakeFrames(1);
+ }
+
+ // Wait for recorder finalization
+ fixture.stop();
+
+ QVERIFY(fixture.m_recorderErrors.empty());
+ QVERIFY(QFile{ fixture.m_mediaFile }.exists());
+ QVERIFY(fixture.testVideoFilePlayback(fixture.m_mediaFile));
+ }
+
+ void windowCapture_capturesWindowsInOtherProcesses()
+ {
+ WindowCaptureWithWidgetInOtherProcessFixture fixture;
+ QVERIFY(fixture.start());
+
+ // Get reference image from our in-process widget
+ const QImage expected = fixture.m_widget.grabImage();
+
+ // Get actual image grabbed from out-of-process widget
+ const QImage actual = fixture.waitForFrame().toImage();
+
+ QVERIFY(fixture.compareImages(actual, expected));
+ }
+
+ /*
+ This test is not a requirement per se, but we want all platforms
+ to behave the same. A reasonable alternative could have been to
+ treat closed window as a regular 'Stop' capture (not an error).
+ */
+ void windowCapture_stopsWithError_whenProcessCloses()
+ {
+ WindowCaptureWithWidgetInOtherProcessFixture fixture;
+ QVERIFY(fixture.start());
+
+ // Get capturing started
+ fixture.m_grabber.waitAndTakeFrames(3);
+
+ // Closing the process waits for it to exit
+ fixture.m_windowProcess.close();
+
+ const bool captureFailed =
+ QTest::qWaitFor([&] { return !fixture.m_errors.empty(); }, s_testTimeout);
+
+ QVERIFY(captureFailed);
+ }
+};
+
+int main(int argc, char *argv[])
+{
+ QCommandLineParser cmd;
+ const QCommandLineOption showTestWidget{ QStringList{ "show" },
+ "Creates a test widget with given title",
+ "windowTitle" };
+ cmd.addOption(showTestWidget);
+ cmd.parse({ argv, argv + argc });
+
+ if (cmd.isSet(showTestWidget)) {
+ QApplication app{ argc, argv };
+ const QString windowTitle = cmd.value(showTestWidget);
+ const bool result = showCaptureWindow(windowTitle);
+ return result ? 0 : 1;
+ }
+
+ // If no special arguments are set, enter the regular QTest main routine
+ TESTLIB_SELFCOVERAGE_START("tst_QWindowCaptureatioBackend")
+ QT_PREPEND_NAMESPACE(QTest::Internal::callInitMain)<tst_QWindowCaptureBackend>();
+ QApplication app(argc, argv);
+ app.setAttribute(Qt::AA_Use96Dpi, true);
+ tst_QWindowCaptureBackend tc;
+ QTEST_SET_MAIN_SOURCE_PATH return QTest::qExec(&tc, argc, argv);
+
+}
+
+#include "tst_qwindowcapturebackend.moc"