Unit testing paint event (QWidget::paintEngine: Should no longer be called)



  • I've run into a very frustrating problem. I have unit tests (QtTest) that use a pattern of doing widget.grab() to draw a buffered image and save it as a QPixmap and then compare that image to a previously stored image from disk. These tests all work with Qt 5.6.1 on rhel 7.2 (Maipo). However if I update to Qt 5.11.3 or rhel 7.6 (Maipo) then the tests stop drawing to the buffer and I get lots of warnings about QPainter. My organization is trying to spin up a new build environment and eventually transition to rhel 7.6, and we are planning to step up the QT version soon as well to fix a few bugs in our system, so I need to solve this issue or lose these tests entirely. FYI I've only tried running the tests with either QT 5.11.3 or rhel 7.6, not together, but I can't imagine that would magically fix my problems.

    Here's an example of the paintEvent that I am trying to test. Note that I am NOT calling paintEvent directly from any where within my code, all the help I could find on this issue are about people who have been doing that and I want to state clearly that this is not what I am doing.

    This is the paintEvent of a canvas widget for a Notepad app. The Notepad allows drawn images on top of typed text. I've tried running without calling the super's paintEvent, but I get the same result. SheetCanvas is a public QWidget and paintEvent is a protected override. I can provide more code but my organization prefers that I post as little code as possible initially.

    void SheetCanvas::paintEvent(QPaintEvent *event)
    {
        QWidget::paintEvent(event);
        QPainter painter(this);
        QRect dirtyRect = event->rect();
        painter.drawImage(dirtyRect, m_image, dirtyRect);
    }
    

    Here is the Qtest that is failing.

    void SheetCanvasTest::paint()
    {
        SheetCanvas canvas;
        canvas.resize(100, 100);
        canvas.show();
        canvas.setMode(SheetCanvas::DRAW);
        canvas.setLineColor(Qt::black);
        canvas.setLineWidth(10);
    
        QTest::mousePress(&canvas, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(50,0));
        QMouseEvent event(QEvent::MouseMove, QPointF(50.0, 50.0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
        QApplication::sendEvent(&canvas, &event);
        QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(50,100));
    
        QPixmap actual = canvas.grab();
        QPixmap expected("SheetCanvas.png");
    
        QCOMPARE(ImageCompare::pixelImageCompare(actual, expected), true);
    
        canvas.setMode(SheetCanvas::ERASE);
        canvas.setEraserWidth(10);
    
        QTest::mousePress(&canvas, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(50,0));
        QMouseEvent event2(QEvent::MouseMove, QPointF(50.0, 50.0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
        QApplication::sendEvent(&canvas, &event2);
        QTest::mouseRelease(&canvas, Qt::LeftButton, Qt::KeyboardModifiers(), QPoint(50,51));
    
        QPixmap actual2 = canvas.grab();
        QPixmap expected2("SheetCanvasErased.png");
    
        QCOMPARE(ImageCompare::pixelImageCompare(actual2, expected2), true);
    }
    

    I think this is pretty self-explanatory. It draws to the buffer, then compares the image. I also test an erase feature. This test, the exact same executable, passes on the rhel 7.2 box but fails on the rhel 7.6 box. The major difference is the OpenGL libraries and the graphics drivers are different between the boxes, but I also get the same result if I compile this test against QT 5.11.3 on the rhel 7.2 box. So something is common between these two environments and I can't figure out what the issue is.

    Here are the actual warnings I see when I run on the rhel 7.6 box

    QWARN  : SheetCanvasTest::paint() No XVisualInfo for format QSurfaceFormat(version 2.0, options QFlags<QSurfaceFormat::FormatOption>(), depthBufferSize -1, redBufferSize 0, greenBufferSize 0, blueBufferSize 0, alphaBufferSize -1, stencilBufferSize -1, samples -1, swapBehavior QSurfaceFormat::SwapBehavior(SingleBuffer), swapInterval 1, profile  QSurfaceFormat::OpenGLContextProfile(NoProfile))
    QWARN  : SheetCanvasTest::paint() Falling back to using screens root_visual.
    QWARN  : SheetCanvasTest::paint() Unsupported screen format: depth: 8, red_mask: 0, blue_mask: 0
    
    ... //a bunch of log output from the test, this is normal
    
    QWARN  : SheetCanvasTest::paint() QWidget::paintEngine: Should no longer be called
    QWARN  : SheetCanvasTest::paint() QPainter::begin: Paint device returned engine == 0, type: 1
    FAIL!  : SheetCanvasTest::paint() Compared values are not the same
       Actual   (ImageCompare::pixelImageCompare(actual, expected)): 0
       Expected (true)                                             : 1
       Loc: [tst_sheetcanvas.cpp(126)]
    

    When it passes I do get a few warnings

    QWARN  : SheetCanvasTest::paint() Could not initialize OpenGL for RasterGLSurface, reverting to RasterSurface.
    QWARN  : SheetCanvasTest::paint() Unsupported screen format: depth: 8, red_mask: 0, blue_mask: 0
    
    ... // Log output
    
    QWARN  : SheetCanvasTest::paint() QCursor: Cannot create bitmap cursor; invalid bitmap(s)
    
    ... // More logs
    
    PASS   : SheetCanvasTest::paint()
    

    Finally I'll note that if I try to save off the "actual" buffered image to a png it does not save anything, this is how I know for sure that it does not draw anything. How I initially produced the "expected" image was to do actual.save("blahblah.png"). Doing that on the rhel 7.6 box produces its own set of errors.

    QWARN  : SheetCanvasTest::paint() libpng warning: Image width is zero in IHDR
    QWARN  : SheetCanvasTest::paint() libpng warning: Image height is zero in IHDR
    libpng error: Invalid IHDR data
    

    So my questions are can anyone explain this? Is there any other way to draw to a buffer that I simply have not found yet? I've tried multiple different things but I decided to focus on the original code here hoping that someone understands why my original code works in one environment and not the other. If anyone has a better way of testing paintEvent I'm open to anything.

    Thanks for your time.



  • I finally figured out my issue and how to fix it.

    Our tests are run with xvfb-run because we run the tests either off the RHEL sde and the QApplication needs some place to display to. While we have XVMs to help develop on there is no good way for our Bamboo builds to use this. So what finally tipped me off to the issue was this QWARN:

    No XVisualInfo for format QSurfaceFormat(version 2.0, options QFlags<QSurfaceFormat::FormatOption>(), depthBufferSize -1, redBufferSize 0, greenBufferSize 0, blueBufferSize 0, alphaBufferSize -1, stencilBufferSize -1, samples -1, swapBehavior QSurfaceFormat::SwapBehavior(SingleBuffer), swapInterval 1, colorSpace QSurfaceFormat::ColorSpace(DefaultColorSpace), profile  QSurfaceFormat::OpenGLContextProfile(NoProfile))
    

    When I went looking into this there were indications that this had to do with Qt not finding graphics drivers. So I decided to try and run the test without xvfb and the test finally produced an image. The best idea I can come up with is that xvfb doesn't have OpenGL and Qt 5.11.3 needs it and cannot run with the CPU graphics engine.

    So after some digging and experimenting I changed the command from this

    xvfb-run -a -s '-dpi 96'
    

    to this:

    xvfb-run -a -s '-screen 0 1280x1024x24 +extension GLX -dpi 96'
    

    And now my tests are passing!

    I wonder if anyone has any insight as to my theory on why the step up from 5.6.1 to 5.11.3 would have issues with the graphics driver.


  • Lifetime Qt Champion

    Hi and welcome to devnet,

    Glad you found a solution and thanks for sharing !

    One thing you can do is to take a look at the release logs to see what has changed.

    However from your description you may have found a regression.

    Did you already check the bug report system ?