Capture full logcat output in Android unit tests

Previously, when running unit tests on Android, the log presented
to the user was the log as known to the Qt logging system. This
does not include log messages generated by Java code using e.g.
Log.d("message"). Neither does it include log messages by system
libs. This patch changes androidtestrunner to capture the full
logcat output for the unit test. This improves the developer
experience when working with unit tests.

Fixes: QTBUG-93438
Change-Id: I580f728349041eb8a84a32d187754b7b5448f512
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Andreas Buhr 2021-09-27 08:50:23 +02:00
parent 81c92aec66
commit 0098dd8120

View File

@ -74,6 +74,9 @@ struct Options
QHash<QString, QString> outFiles; QHash<QString, QString> outFiles;
QString testArgs; QString testArgs;
QString apkPath; QString apkPath;
int sdkVersion = -1;
int pid = -1;
bool showLogcatOutput = false;
QHash<QString, std::function<bool(const QByteArray &)>> checkFiles = { QHash<QString, std::function<bool(const QByteArray &)>> checkFiles = {
{QStringLiteral("txt"), [](const QByteArray &data) -> bool { {QStringLiteral("txt"), [](const QByteArray &data) -> bool {
return data.indexOf("\nFAIL! : ") < 0; return data.indexOf("\nFAIL! : ") < 0;
@ -126,6 +129,10 @@ static bool execCommand(const QString &command, QByteArray *output = nullptr, bo
if (verbose) if (verbose)
fprintf(stdout, "%s", buffer); fprintf(stdout, "%s", buffer);
} }
fflush(stdout);
fflush(stderr);
return pclose(process) == 0; return pclose(process) == 0;
} }
@ -233,6 +240,8 @@ static bool parseOptions()
g_options.activity = arguments.at(++i); g_options.activity = arguments.at(++i);
} else if (argument.compare(QStringLiteral("--skip-install-root"), Qt::CaseInsensitive) == 0) { } else if (argument.compare(QStringLiteral("--skip-install-root"), Qt::CaseInsensitive) == 0) {
g_options.skipAddInstallRoot = true; g_options.skipAddInstallRoot = true;
} else if (argument.compare(QStringLiteral("--show-logcat"), Qt::CaseInsensitive) == 0) {
g_options.showLogcatOutput = true;
} else if (argument.compare(QStringLiteral("--timeout"), Qt::CaseInsensitive) == 0) { } else if (argument.compare(QStringLiteral("--timeout"), Qt::CaseInsensitive) == 0) {
if (i + 1 == arguments.size()) if (i + 1 == arguments.size())
g_options.helpRequested = true; g_options.helpRequested = true;
@ -289,6 +298,8 @@ static void printHelp()
"\n" "\n"
" --skip-install-root: Do not append INSTALL_ROOT=... to the make command.\n" " --skip-install-root: Do not append INSTALL_ROOT=... to the make command.\n"
"\n" "\n"
" --show-logcat: Print Logcat output to stdout.\n"
"\n"
" -- Arguments that will be passed to the test application.\n" " -- Arguments that will be passed to the test application.\n"
"\n" "\n"
" --verbose: Prints out information during processing.\n" " --verbose: Prints out information during processing.\n"
@ -403,6 +414,24 @@ static bool waitToFinish()
return false; return false;
} }
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()) { while (isRunning()) {
std::this_thread::sleep_for(std::chrono::milliseconds(250)); std::this_thread::sleep_for(std::chrono::milliseconds(250));
@ -412,6 +441,26 @@ static bool waitToFinish()
return true; return true;
} }
static void obtainSDKVersion()
{
// SDK version is necessary, as in SDK 23 pidof is broken, so we cannot obtain the pid.
// Also, Logcat cannot filter by pid in SDK 23, so we don't offer the --show-logcat option.
QByteArray output;
const QString command(
QStringLiteral("%1 shell getprop ro.build.version.sdk").arg(g_options.adbCommand));
execCommand(command, &output, g_options.verbose);
bool ok = false;
int sdkVersion = output.toInt(&ok);
if (ok) {
g_options.sdkVersion = sdkVersion;
} else {
fprintf(stderr,
"Unable to obtain the SDK version of the target. Command \"%s\" "
"returned \"%s\"\n",
command.toUtf8().constData(), output.constData());
fflush(stderr);
}
}
static bool pullFiles() static bool pullFiles()
{ {
@ -496,6 +545,8 @@ int main(int argc, char *argv[])
return 1; return 1;
} }
obtainSDKVersion();
RunnerLocker lock; // do not install or run packages while another test is running RunnerLocker lock; // do not install or run packages while another test is running
if (!execCommand(QStringLiteral("%1 install -r -g %2") if (!execCommand(QStringLiteral("%1 install -r -g %2")
.arg(g_options.adbCommand, g_options.apkPath), nullptr, g_options.verbose)) { .arg(g_options.adbCommand, g_options.apkPath), nullptr, g_options.verbose)) {
@ -513,7 +564,24 @@ int main(int argc, char *argv[])
// start the tests // start the tests
bool res = execCommand(QStringLiteral("%1 %2").arg(g_options.adbCommand, g_options.testArgs), bool res = execCommand(QStringLiteral("%1 %2").arg(g_options.adbCommand, g_options.testArgs),
nullptr, g_options.verbose) && waitToFinish(); nullptr, g_options.verbose)
&& waitToFinish();
// get logcat output
if (res && g_options.showLogcatOutput) {
if (g_options.sdkVersion <= 23) {
fprintf(stderr, "Cannot show logcat output on Android 23 and below.\n");
fflush(stderr);
} else if (g_options.pid > 0) {
fprintf(stdout, "Logcat output:\n");
res &= execCommand(QStringLiteral("%1 logcat -d --pid=%2")
.arg(g_options.adbCommand)
.arg(g_options.pid),
nullptr, true);
fprintf(stdout, "End Logcat output.\n");
}
}
if (res) if (res)
res &= pullFiles(); res &= pullFiles();
res &= execCommand(QStringLiteral("%1 uninstall %2").arg(g_options.adbCommand, g_options.package), res &= execCommand(QStringLiteral("%1 uninstall %2").arg(g_options.adbCommand, g_options.package),