test: Use QSignalSpy instead of QEventLoop #335

pull jarolrod wants to merge 1 commits into bitcoin-core:master from jarolrod:apptest-spy changing 1 files +5 −4
  1. jarolrod commented at 8:38 pm on May 16, 2021: member

    This PR refactors our GUI apptests to use QSignalSpy instead of QEventLoop.

    QSignalSpy is more appropriate for our GUI test’s as it is purpose-built for testing emission of signals and sets up its own QEventLoop when the wait function is called.

  2. jarolrod renamed this:
    Use QSignalSpy instead of QEventLoop
    test: Use QSignalSpy instead of QEventLoop
    on May 16, 2021
  3. hebasto added the label Tests on May 16, 2021
  4. in src/qt/test/apptests.cpp:42 in bb460141d8 outdated
    39     QLineEdit* lineEdit = console->findChild<QLineEdit*>("lineEdit");
    40+    QSignalSpy mw_spy(messagesWidget, &QTextEdit::textChanged);
    41     QTest::keyClicks(lineEdit, "getblockchaininfo");
    42     QTest::keyClick(lineEdit, Qt::Key_Return);
    43-    loop.exec();
    44+    QVERIFY(mw_spy.wait(1000));
    


    hebasto commented at 9:12 pm on May 16, 2021:
    Why 1000?

    jarolrod commented at 9:13 pm on May 16, 2021:
    No specific reason, just an arbitrary number. Would you suggest leaving it as the default value of 5000 milliseconds?

    hebasto commented at 9:15 pm on May 16, 2021:
    I’m ok if CI passes.

    jarolrod commented at 9:42 pm on May 16, 2021:
  5. in src/qt/test/apptests.cpp:38 in bb460141d8 outdated
    32@@ -33,13 +33,12 @@ namespace {
    33 //! Call getblockchaininfo RPC and check first field of JSON output.
    34 void TestRpcCommand(RPCConsole* console)
    35 {
    36-    QEventLoop loop;
    37     QTextEdit* messagesWidget = console->findChild<QTextEdit*>("messagesWidget");
    38-    QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, &QEventLoop::quit);
    39     QLineEdit* lineEdit = console->findChild<QLineEdit*>("lineEdit");
    40+    QSignalSpy mw_spy(messagesWidget, &QTextEdit::textChanged);
    



    jarolrod commented at 4:34 pm on May 17, 2021:
    done in c519b41
  6. hebasto commented at 10:48 am on May 17, 2021: member
    Concept ACK on moving to the QSignalSpy class.
  7. jarolrod force-pushed on May 17, 2021
  8. jarolrod commented at 4:36 pm on May 17, 2021: member

    updated from bb46014 -> c519b41

    addressed @hebasto’s comment:

    • add a check to ensure the spy’s validity after construction. This mainly checks that the signal we passed to be spied on exists.
  9. qt, test: use qsignalspy instead of qeventloop 7eea659fc9
  10. jarolrod force-pushed on May 17, 2021
  11. jarolrod commented at 7:54 pm on May 17, 2021: member

    updated from c519b41 -> 7eea659

    Changes:

    • add a check for the number of times the spy received the textChanged signal (it is received twice because the console outputs the command which was entered, then outputs the response to that command)
  12. hebasto approved
  13. hebasto commented at 9:30 pm on May 17, 2021: member
    ACK 7eea659fc908e5edfc90c185a6958ed07ecf5cd4, tested on Linux Mint 20.1 (Qt 5.12.8).
  14. promag commented at 9:03 am on May 18, 2021: contributor
    Code review ACK 7eea659fc908e5edfc90c185a6958ed07ecf5cd4.
  15. hebasto merged this on May 20, 2021
  16. hebasto closed this on May 20, 2021

  17. jarolrod deleted the branch on May 20, 2021
  18. in src/qt/test/apptests.cpp:42 in 7eea659fc9
    40+    QSignalSpy mw_spy(messagesWidget, &QTextEdit::textChanged);
    41+    QVERIFY(mw_spy.isValid());
    42     QTest::keyClicks(lineEdit, "getblockchaininfo");
    43     QTest::keyClick(lineEdit, Qt::Key_Return);
    44-    loop.exec();
    45+    QVERIFY(mw_spy.wait(1000));
    


    ryanofsky commented at 10:53 pm on May 20, 2021:
    Doesn’t switching from exec to wait(1000) make the test non-deterministic? The previous exec call would process all events while the new signal spy wait will only wait for the text changed event, which could potentially trigger after the getblockchaininfo event and before the Key_Return event, or time out before either event. The PR description says using signal spy is more appropriate without explaining why, but it seems to make the test more verbose and less robust.

    jarolrod commented at 11:15 pm on May 20, 2021:

    The wait call will process all events just like the exec call does. The wait call sets up a QEventLoop which calls exec, but it’s bounded by a timer which is the value provided to wait

    Looking at the source code for the qsignalspy wait function, we can see the following line:

    0m_loop.enterLoopMSecs(timeout);
    

    Going to the source for the enterLoopMSecs function, we can observe that

    • a QEventLoop is being setup
    • The exec function is being called
    • the QEventLoop will end after a certain amount of ms, which is our provided value

    As I see it, if there is a downside, it is that this part of the test will always wait for 1000ms. So if the signals are received in 1ms, the loop will continue waiting for 999 more ms. At the same time, it is possible that the test fails because the signals weren’t received within 1000ms because the CI is slow.


    promag commented at 11:18 pm on May 20, 2021:
    I find QSignalSpy more intuitive than to use an event loop and connect the signal to QEventLoop::quit.

    ryanofsky commented at 11:24 pm on May 20, 2021:

    Sounds good if test isn’t failing. Just if the test turns out to be unreliable, I’d recommend reverting.

    I see only downsides and no upsides to using signal spy in this instance. It makes the test more fragile, more verbose, and less straightforward, waiting indirectly though a hidden event loop in an inflexible and poorly documented test class, instead of just running the code we need to run directly in a deterministic way.


    jarolrod commented at 11:29 pm on May 20, 2021:
    Thanks for taking the time to review! I think if this turns out to be unreliable on the part of wait it doesn’t diminish the use of QSignalSpy to test the proper emission of signals and count. So a hybrid approach of both setting up a QEventLoop to process events and a QSignalSpy as a window into what is happening signal-wise can be adapted.

    ryanofsky commented at 11:42 pm on May 20, 2021:

    Not sure why a “signal spy” would be more intuitive than an “event loop”. The previous code is telling the event loop to quit when the textChanged signal happens. The new code is telling the signal spy to wait until the textChanged signal happens.

    I’d think both versions should be about as intuitive at the test behavior level. Only an event loop is a well known construct that I think you need to understand anyway to use Qt, while a signal spy is a specialized test class (with a hidden event loop inside) that makes you worry about timeouts and leaves you “going to the source for the enterLoopMSecs function” to find out how they work.


    ryanofsky commented at 11:47 pm on May 20, 2021:
    I don’t see any benefit to the test counting its own signal emissions either. The goal of the test is to trigger an rpc, wait for the rpc, and check the rpc result.

    ryanofsky commented at 11:51 pm on May 20, 2021:
    Sorry I’m probably sounding too cranky about this. I definitely appreciate any work on these tests, and think this change is basically fine, but just wanted to point out some drawbacks I see and point to a resolution just in case there do turn out to be reliability problems.

    jarolrod commented at 0:01 am on May 21, 2021:

    Sorry I’m probably sounding too cranky about this.

    😀 no worries. Please always be as vocal as possible, we all just want to ensure good changes occur.

    There is one hidden bug case that the original code wouldn’t detect. And another that this version does a (very slightly) better job of diagnosing.

    Scenario A: No Actual console response The QEventLoop will quit when the textChanged signal is received. But, this is supposed to be received twice. When you enter a command, the textChanged signal will be emitted because the command you entered will now be present. Then the console will present the output to your command and another textChanged signal will be emitted.

    Now, let’s say we introduce a bug where the console fails to present output to your command. The old version will pass on the QEventLoop but detect this when it searches for the current chain value and compares it with “regtest”. The new version will fail earlier because the signal wasn’t received twice. This makes it (very slightly) easier to diagnose what we did wrong because we are counting the signal emissions.

    Scenario B: Double console response If we introduce a bug where the console starts to output responses twice, the old version cannot detect this. The chain value will be found and the old test will say everything looks good. The new version will know something is wrong because the signal would have been received three times instead of 2 and the test will fail.

    Furthermore, let’s say we introduce a bug where a textChanged signal is never emitted. The old test will hang indeterminately. The new test will time out and the CI can continue to test other things.

  19. sidhujag referenced this in commit daba9b472d on May 21, 2021
  20. gwillen referenced this in commit cf23962d62 on Jun 1, 2022
  21. bitcoin-core locked this on Aug 16, 2022

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin-core/gui. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-12-22 02:20 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me