AndroidTestRunner: don't fail waiting for the app to start/finish

Currently, under waitToFinish(), we wait for the app to start and if
it doesn't start or starts and exits too quickly for the test runner
to catch it, it fails. The test runner tries to get the pid of the test
after it detects the app has started. However, we don't really need to
fail, the test runner could simply continue the execution and assume
the test was run and finished, and proceed to fetching the test results.
Since, the results fetching don't anyway rely on the pid, the test
runner can let that operation decide whether to fail or not (if not
output is found).

Also, along the way, instead of issuing a different command to get the
pid (i.e. adb shell pidof), we can use the same "adb shell ps" command
that is used to check if the app is running, to obtain the pid.

Fixes: QTQAINFRA-5928
Fixes: QTBUG-88508
Pick-to: 6.6 6.5
Change-Id: Ice945fcb686c4ef21b5f1c143aa22922ae928333
Reviewed-by: Axel Spoerl <axel.spoerl@qt.io>
This commit is contained in:
Assam Boudjelthia 2023-11-23 20:13:08 +02:00
parent 518ac20dcb
commit 4bc3f700ff

View File

@ -10,9 +10,9 @@
#include <QXmlStreamReader> #include <QXmlStreamReader>
#include <algorithm> #include <algorithm>
#include <chrono>
#include <functional> #include <functional>
#include <thread> #include <QtCore/QDeadlineTimer>
#include <QtCore/QThread>
#include <shellquote_shared.h> #include <shellquote_shared.h>
@ -115,7 +115,7 @@ struct Options
bool helpRequested = false; bool helpRequested = false;
bool verbose = false; bool verbose = false;
bool skipAddInstallRoot = false; bool skipAddInstallRoot = false;
std::chrono::seconds timeout{480}; // 8 minutes int timeoutSecs = 480; // 8 minutes
QString buildPath; QString buildPath;
QString adbCommand{QStringLiteral("adb")}; QString adbCommand{QStringLiteral("adb")};
QString makeCommand; QString makeCommand;
@ -205,7 +205,7 @@ static bool parseOptions()
if (i + 1 == arguments.size()) if (i + 1 == arguments.size())
g_options.helpRequested = true; g_options.helpRequested = true;
else else
g_options.timeout = std::chrono::seconds{arguments.at(++i).toInt()}; g_options.timeoutSecs = arguments.at(++i).toInt();
} else if (argument.compare(QStringLiteral("--help"), Qt::CaseInsensitive) == 0) { } else if (argument.compare(QStringLiteral("--help"), Qt::CaseInsensitive) == 0) {
g_options.helpRequested = true; g_options.helpRequested = true;
} else if (argument.compare(QStringLiteral("--verbose"), Qt::CaseInsensitive) == 0) { } else if (argument.compare(QStringLiteral("--verbose"), Qt::CaseInsensitive) == 0) {
@ -355,53 +355,58 @@ static bool parseTestArgs()
return true; return true;
} }
static bool obtainPid() {
QByteArray output;
const auto psCmd = "%1 shell \"ps | grep ' %2'\""_L1.arg(g_options.adbCommand,
shellQuote(g_options.package));
if (!execCommand(psCmd, &output))
return false;
const QList<QByteArray> lines = output.split(u'\n');
if (lines.size() < 1)
return false;
QList<QByteArray> columns = lines.first().simplified().replace(u'\t', u' ').split(u' ');
if (columns.size() < 3)
return false;
if (g_options.pid == -1) {
bool ok = false;
int pid = columns.at(1).toInt(&ok);
if (ok)
g_options.pid = pid;
}
return true;
}
static bool isRunning() { static bool isRunning() {
QByteArray output; QByteArray output;
if (!execCommand(QStringLiteral("%1 shell \"ps | grep ' %2'\"").arg(g_options.adbCommand, const auto psCmd = "%1 shell \"ps | grep ' %2'\""_L1.arg(g_options.adbCommand,
shellQuote(g_options.package)), &output)) { shellQuote(g_options.package));
if (!execCommand(psCmd, &output))
return false; return false;
}
return output.indexOf(QLatin1StringView(" " + g_options.package.toUtf8())) > -1; return output.indexOf(QLatin1StringView(" " + g_options.package.toUtf8())) > -1;
} }
static bool waitToFinish() static void waitForFinished()
{ {
using clock = std::chrono::system_clock; // wait to start and set PID
auto start = clock::now(); QDeadlineTimer startDeadline(10000);
// wait to start do {
while (!isRunning()) { if (obtainPid())
std::this_thread::sleep_for(std::chrono::milliseconds(100)); break;
if ((clock::now() - start) > std::chrono::seconds{10}) QThread::msleep(100);
return false; } while (!startDeadline.hasExpired());
}
if (g_options.sdkVersion > 23) { // pidof is broken in SDK 23, non-existent before
QByteArray output;
const QString command(QStringLiteral("%1 shell pidof -s %2")
.arg(g_options.adbCommand, shellQuote(g_options.package)));
execCommand(command, &output, g_options.verbose);
bool ok = false;
int pid = output.toInt(&ok); // If we got more than one pid, fail.
if (ok) {
g_options.pid = pid;
} else {
fprintf(stderr,
"Unable to obtain the PID of the running unit test. Command \"%s\" "
"returned \"%s\"\n",
command.toUtf8().constData(), output.constData());
fflush(stderr);
}
}
// Wait to finish // Wait to finish
while (isRunning()) { QDeadlineTimer finishedDeadline(g_options.timeoutSecs * 1000);
std::this_thread::sleep_for(std::chrono::milliseconds(250)); do {
if (g_options.timeout >= std::chrono::seconds::zero() if (!isRunning())
&& (clock::now() - start) > g_options.timeout) break;
return false; QThread::msleep(250);
} } while (!finishedDeadline.hasExpired());
return true;
} }
static void obtainSDKVersion() static void obtainSDKVersion()
@ -604,9 +609,10 @@ int main(int argc, char *argv[])
const QString formattedTime = getCurrentTimeString(); const QString formattedTime = getCurrentTimeString();
// start the tests // start the tests
bool res = execCommand("%1 %2"_L1.arg(g_options.adbCommand, g_options.testArgs), const auto startCmd = "%1 %2"_L1.arg(g_options.adbCommand, g_options.testArgs);
nullptr, g_options.verbose) bool res = execCommand(startCmd, nullptr, g_options.verbose);
&& waitToFinish();
waitForFinished();
if (res) if (res)
res &= pullFiles(); res &= pullFiles();