forked from qt-creator/qt-creator
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:
@@ -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
|
||||||
|
|
||||||
|
@@ -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)) {
|
||||||
|
@@ -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);
|
||||||
|
@@ -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(), {});
|
||||||
|
Reference in New Issue
Block a user