QtcProcess: Introduce TextChannelMode and textOnChannel signals

This is alternative to setStd[Out/Err](Line)Callback()
methods. In this way there may be many clients connected
to textOnStandard[Output/Error]() signals. This should
also simplify handling the lifetime of user callback.

Change-Id: If82baa1f3f9c432ed431926619b9bbf11d770a84
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2022-10-07 12:05:37 +02:00
parent 4bbbee1e63
commit 77a140c581
4 changed files with 134 additions and 9 deletions

View File

@@ -37,6 +37,21 @@ enum class EventLoopMode {
On // Avoid On // Avoid
}; };
enum class Channel {
Output,
Error
};
enum class TextChannelMode {
// Keep | Emit | Emit
// raw | text | content
// data | sig |
// -----+------+--------
Off, // yes | no | -
SingleLine, // no | yes | Single lines
MultiLine // yes | yes | All the available data
};
enum class ProcessResult { enum class ProcessResult {
// Finished successfully. Unless an ExitCodeInterpreter is set // Finished successfully. Unless an ExitCodeInterpreter is set
// this corresponds to a return code 0. // this corresponds to a return code 0.
@@ -53,6 +68,7 @@ enum class ProcessResult {
}; };
using ExitCodeInterpreter = std::function<ProcessResult(int /*exitCode*/)>; using ExitCodeInterpreter = std::function<ProcessResult(int /*exitCode*/)>;
using TextChannelCallback = std::function<void(const QString & /*text*/)>;
} // namespace Utils } // namespace Utils

View File

@@ -187,6 +187,7 @@ public:
QTextCodec *codec = nullptr; // Not owner QTextCodec *codec = nullptr; // Not owner
std::unique_ptr<QTextCodec::ConverterState> codecState; std::unique_ptr<QTextCodec::ConverterState> codecState;
std::function<void(const QString &lines)> outputCallback; std::function<void(const QString &lines)> outputCallback;
TextChannelMode m_textChannelMode = TextChannelMode::Off;
bool emitSingleLines = true; bool emitSingleLines = true;
bool keepRawData = true; bool keepRawData = true;
@@ -1831,32 +1832,74 @@ void QtcProcess::runBlocking(EventLoopMode eventLoopMode)
} }
} }
void QtcProcess::setStdOutCallback(const std::function<void (const QString &)> &callback) void QtcProcess::setStdOutCallback(const TextChannelCallback &callback)
{ {
d->m_stdOut.outputCallback = callback; d->m_stdOut.outputCallback = callback;
d->m_stdOut.emitSingleLines = false; d->m_stdOut.emitSingleLines = false;
} }
void QtcProcess::setStdOutLineCallback(const std::function<void (const QString &)> &callback) void QtcProcess::setStdOutLineCallback(const TextChannelCallback &callback)
{ {
d->m_stdOut.outputCallback = callback; d->m_stdOut.outputCallback = callback;
d->m_stdOut.emitSingleLines = true; d->m_stdOut.emitSingleLines = true;
d->m_stdOut.keepRawData = false; d->m_stdOut.keepRawData = false;
} }
void QtcProcess::setStdErrCallback(const std::function<void (const QString &)> &callback) void QtcProcess::setStdErrCallback(const TextChannelCallback &callback)
{ {
d->m_stdErr.outputCallback = callback; d->m_stdErr.outputCallback = callback;
d->m_stdErr.emitSingleLines = false; d->m_stdErr.emitSingleLines = false;
} }
void QtcProcess::setStdErrLineCallback(const std::function<void (const QString &)> &callback) void QtcProcess::setStdErrLineCallback(const TextChannelCallback &callback)
{ {
d->m_stdErr.outputCallback = callback; d->m_stdErr.outputCallback = callback;
d->m_stdErr.emitSingleLines = true; d->m_stdErr.emitSingleLines = true;
d->m_stdErr.keepRawData = false; d->m_stdErr.keepRawData = false;
} }
void QtcProcess::setTextChannelMode(Channel channel, TextChannelMode mode)
{
const TextChannelCallback outputCb = [this](const QString &text) {
GuardLocker locker(d->m_guard);
emit textOnStandardOutput(text);
};
const TextChannelCallback errorCb = [this](const QString &text) {
GuardLocker locker(d->m_guard);
emit textOnStandardError(text);
};
const TextChannelCallback callback = (channel == Channel::Output) ? outputCb : errorCb;
ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr;
QTC_ASSERT(buffer->m_textChannelMode == TextChannelMode::Off, qWarning()
<< "QtcProcess::setTextChannelMode(): Changing text channel mode for"
<< (channel == Channel::Output ? "Output": "Error")
<< "channel while it was previously set for this channel.");
buffer->m_textChannelMode = mode;
switch (mode) {
case TextChannelMode::Off:
buffer->outputCallback = {};
buffer->emitSingleLines = true;
buffer->keepRawData = true;
break;
case TextChannelMode::SingleLine:
buffer->outputCallback = callback;
buffer->emitSingleLines = true;
buffer->keepRawData = false;
break;
case TextChannelMode::MultiLine:
buffer->outputCallback = callback;
buffer->emitSingleLines = false;
buffer->keepRawData = true;
break;
}
}
TextChannelMode QtcProcess::textChannelMode(Channel channel) const
{
ChannelBuffer *buffer = channel == Channel::Output ? &d->m_stdOut : &d->m_stdErr;
return buffer->m_textChannelMode;
}
void QtcProcessPrivate::slotTimeout() void QtcProcessPrivate::slotTimeout()
{ {
if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) { if (!m_waitingForUser && (++m_hangTimerCount > m_maxHangTimerCount)) {

View File

@@ -148,10 +148,13 @@ public:
void setTimeOutMessageBoxEnabled(bool); void setTimeOutMessageBoxEnabled(bool);
void setExitCodeInterpreter(const ExitCodeInterpreter &interpreter); void setExitCodeInterpreter(const ExitCodeInterpreter &interpreter);
void setStdOutCallback(const std::function<void(const QString &)> &callback); void setStdOutCallback(const TextChannelCallback &callback);
void setStdOutLineCallback(const std::function<void(const QString &)> &callback); void setStdOutLineCallback(const TextChannelCallback &callback);
void setStdErrCallback(const std::function<void(const QString &)> &callback); void setStdErrCallback(const TextChannelCallback &callback);
void setStdErrLineCallback(const std::function<void(const QString &)> &callback); void setStdErrLineCallback(const TextChannelCallback &callback);
void setTextChannelMode(Channel channel, TextChannelMode mode);
TextChannelMode textChannelMode(Channel channel) const;
bool readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS = 30); bool readDataFromProcess(QByteArray *stdOut, QByteArray *stdErr, int timeoutS = 30);
@@ -182,6 +185,8 @@ signals:
void done(); // On Starting | Running -> NotRunning state transition void done(); // On Starting | Running -> NotRunning state transition
void readyReadStandardOutput(); void readyReadStandardOutput();
void readyReadStandardError(); void readyReadStandardError();
void textOnStandardOutput(const QString &text);
void textOnStandardError(const QString &text);
private: private:
friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r); friend QTCREATOR_UTILS_EXPORT QDebug operator<<(QDebug str, const QtcProcess &r);

View File

@@ -112,7 +112,10 @@ private slots:
void exitCode(); void exitCode();
void runBlockingStdOut_data(); void runBlockingStdOut_data();
void runBlockingStdOut(); void runBlockingStdOut();
void runBlockingSignal_data();
void runBlockingSignal();
void lineCallback(); void lineCallback();
void lineSignal();
void waitForStartedAfterStarted(); void waitForStartedAfterStarted();
void waitForStartedAfterStarted2(); void waitForStartedAfterStarted2();
void waitForStartedAndFinished(); void waitForStartedAndFinished();
@@ -937,13 +940,46 @@ void tst_QtcProcess::runBlockingStdOut()
QVERIFY2(readLastLine, "Last line was read."); QVERIFY2(readLastLine, "Last line was read.");
} }
void tst_QtcProcess::runBlockingSignal_data()
{
runBlockingStdOut_data();
}
void tst_QtcProcess::runBlockingSignal()
{
QFETCH(bool, withEndl);
QFETCH(int, timeOutS);
QFETCH(ProcessResult, expectedResult);
SubProcessConfig subConfig(ProcessTestApp::RunBlockingStdOut::envVar(), withEndl ? "true" : "false");
QtcProcess process;
subConfig.setupSubProcess(&process);
process.setTimeoutS(timeOutS);
bool readLastLine = false;
process.setTextChannelMode(Channel::Output, TextChannelMode::MultiLine);
connect(&process, &QtcProcess::textOnStandardOutput,
this, [&readLastLine, &process](const QString &out) {
if (out.startsWith(s_runBlockingStdOutSubProcessMagicWord)) {
readLastLine = true;
process.kill();
}
});
process.runBlocking();
// See also QTCREATORBUG-25667 for why it is a bad idea to use QtcProcess::runBlocking
// with interactive cli tools.
QCOMPARE(process.result(), expectedResult);
QVERIFY2(readLastLine, "Last line was read.");
}
void tst_QtcProcess::lineCallback() void tst_QtcProcess::lineCallback()
{ {
SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {}); SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {});
QtcProcess process; QtcProcess process;
subConfig.setupSubProcess(&process); subConfig.setupSubProcess(&process);
QStringList lines = QString(s_lineCallbackData).split('|'); const QStringList lines = QString(s_lineCallbackData).split('|');
int lineNumber = 0; int lineNumber = 0;
process.setStdErrLineCallback([lines, &lineNumber](const QString &actual) { process.setStdErrLineCallback([lines, &lineNumber](const QString &actual) {
QString expected = lines.at(lineNumber); QString expected = lines.at(lineNumber);
@@ -960,6 +996,31 @@ void tst_QtcProcess::lineCallback()
QCOMPARE(lineNumber, lines.size()); QCOMPARE(lineNumber, lines.size());
} }
void tst_QtcProcess::lineSignal()
{
SubProcessConfig subConfig(ProcessTestApp::LineCallback::envVar(), {});
QtcProcess process;
subConfig.setupSubProcess(&process);
const QStringList lines = QString(s_lineCallbackData).split('|');
int lineNumber = 0;
process.setTextChannelMode(Channel::Error, TextChannelMode::SingleLine);
connect(&process, &QtcProcess::textOnStandardError,
this, [lines, &lineNumber](const QString &actual) {
QString expected = lines.at(lineNumber);
expected.replace("\r\n", "\n");
// Omit some initial lines generated by Qt, e.g.
// Warning: Ignoring WAYLAND_DISPLAY on Gnome. Use QT_QPA_PLATFORM=wayland to run on Wayland anyway.
if (lineNumber == 0 && actual != expected)
return;
++lineNumber;
QCOMPARE(actual, expected);
});
process.start();
process.waitForFinished();
QCOMPARE(lineNumber, lines.size());
}
void tst_QtcProcess::waitForStartedAfterStarted() void tst_QtcProcess::waitForStartedAfterStarted()
{ {
SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {}); SubProcessConfig subConfig(ProcessTestApp::SimpleTest::envVar(), {});