Make bit able to run gtest native tests.

The output parsing isn't ideal, so these are a bit more spammy
than I'd like, but at least they build, install and run without
the manual glop.

Test: bit incidentd_test
Change-Id: I3c34a4ebbf661f612b4b0f8b4e05cade8669b5a6
This commit is contained in:
Joe Onorato
2019-02-27 20:42:37 -05:00
parent b953577b27
commit 6c97f4908c
5 changed files with 190 additions and 3 deletions

View File

@@ -33,6 +33,8 @@
using namespace std;
#define NATIVE_TESTS "NATIVE_TESTS"
/**
* An entry from the command line for something that will be built, installed,
* and/or tested.
@@ -132,6 +134,32 @@ InstallApk::InstallApk(const string& filename, bool always)
{
}
struct PushedFile
{
TrackedFile file;
string dest;
PushedFile();
PushedFile(const PushedFile& that);
PushedFile(const string& filename, const string& dest);
~PushedFile() {};
};
PushedFile::PushedFile()
{
}
PushedFile::PushedFile(const PushedFile& that)
:file(that.file),
dest(that.dest)
{
}
PushedFile::PushedFile(const string& f, const string& d)
:file(f),
dest(d)
{
}
/**
* Record for an test that is going to be launched.
@@ -658,12 +686,14 @@ run_phases(vector<Target*> targets, const Options& options)
}
// Figure out whether we need to sync the system and which apks to install
string systemPath = buildOut + "/target/product/" + buildDevice + "/system/";
string dataPath = buildOut + "/target/product/" + buildDevice + "/data/";
string deviceTargetPath = buildOut + "/target/product/" + buildDevice;
string systemPath = deviceTargetPath + "/system/";
string dataPath = deviceTargetPath + "/data/";
bool syncSystem = false;
bool alwaysSyncSystem = false;
vector<string> systemFiles;
vector<InstallApk> installApks;
vector<PushedFile> pushedFiles;
for (size_t i=0; i<targets.size(); i++) {
Target* target = targets[i];
if (target->install) {
@@ -687,6 +717,11 @@ run_phases(vector<Target*> targets, const Options& options)
installApks.push_back(InstallApk(file, !target->build));
continue;
}
// If it's a native test module, push it.
if (target->module.HasClass(NATIVE_TESTS) && starts_with(file, dataPath)) {
string installedPath(file.c_str() + deviceTargetPath.length());
pushedFiles.push_back(PushedFile(file, installedPath));
}
}
}
}
@@ -701,6 +736,13 @@ run_phases(vector<Target*> targets, const Options& options)
printf(" %s\n", systemFiles[i].c_str());
}
}
if (pushedFiles.size() > 0){
print_info("Files to push:");
for (size_t i=0; i<pushedFiles.size(); i++) {
printf(" %s\n", pushedFiles[i].file.filename.c_str());
printf(" --> %s\n", pushedFiles[i].dest.c_str());
}
}
if (installApks.size() > 0){
print_info("APKs to install:");
for (size_t i=0; i<installApks.size(); i++) {
@@ -784,6 +826,25 @@ run_phases(vector<Target*> targets, const Options& options)
}
}
// Push files
if (pushedFiles.size() > 0) {
print_status("Pushing files");
for (size_t i=0; i<pushedFiles.size(); i++) {
const PushedFile& pushed = pushedFiles[i];
string dir = dirname(pushed.dest);
if (dir.length() == 0 || dir == "/") {
// This isn't really a file inside the data directory. Just skip it.
continue;
}
// TODO: if (!apk.file.fileInfo.exists || apk.file.HasChanged())
err = run_adb("shell", "mkdir", "-p", dir.c_str(), NULL);
check_error(err);
err = run_adb("push", pushed.file.filename.c_str(), pushed.dest.c_str());
check_error(err);
// pushed.installed = true;
}
}
// Install APKs
if (installApks.size() > 0) {
print_status("Installing APKs");
@@ -804,6 +865,74 @@ run_phases(vector<Target*> targets, const Options& options)
// Actions
//
// Whether there have been any tests run, so we can print a summary.
bool testsRun = false;
// Run the native tests.
// TODO: We don't have a good way of running these and capturing the output of
// them live. It'll take some work. On the other hand, if they're gtest tests,
// the output of gtest is not completely insane like the text output of the
// instrumentation tests. So for now, we'll just live with that.
for (size_t i=0; i<targets.size(); i++) {
Target* target = targets[i];
if (target->test && target->module.HasClass(NATIVE_TESTS)) {
// We don't have a clear signal from the build system which of the installed
// files is actually the test, so we guess by looking for one with the same
// leaf name as the module that is executable.
for (size_t j=0; j<target->module.installed.size(); j++) {
string filename = target->module.installed[j];
if (!starts_with(filename, dataPath)) {
// Native tests go into the data directory.
continue;
}
if (leafname(filename) != target->module.name) {
// This isn't the test executable.
continue;
}
if (!is_executable(filename)) {
continue;
}
string installedPath(filename.c_str() + deviceTargetPath.length());
printf("the magic one is: %s\n", filename.c_str());
printf(" and it's installed at: %s\n", installedPath.c_str());
// Convert bit-style actions to gtest test filter arguments
if (target->actions.size() > 0) {
testsRun = true;
target->testActionCount++;
bool runAll = false;
string filterArg("--gtest_filter=");
for (size_t k=0; k<target->actions.size(); k++) {
string actionString = target->actions[k];
if (actionString == "*") {
runAll = true;
} else {
filterArg += actionString;
if (k != target->actions.size()-1) {
// We would otherwise have to worry about this condition
// being true, and appending an extra ':', but we know that
// if the extra action is "*", then we'll just run all and
// won't use filterArg anyway, so just keep this condition
// simple.
filterArg += ':';
}
}
}
if (runAll) {
err = run_adb("shell", installedPath.c_str(), NULL);
} else {
err = run_adb("shell", installedPath.c_str(), filterArg.c_str(), NULL);
}
if (err == 0) {
target->testPassCount++;
} else {
target->testFailCount++;
}
}
}
}
}
// Inspect the apks, and figure out what is an activity and what needs a test runner
bool printedInspecting = false;
vector<TestAction> testActions;
@@ -872,6 +1001,7 @@ run_phases(vector<Target*> targets, const Options& options)
TestResults testResults;
if (testActions.size() > 0) {
print_status("Running tests");
testsRun = true;
for (size_t i=0; i<testActions.size(); i++) {
TestAction& action = testActions[i];
testResults.SetCurrentAction(&action);
@@ -969,7 +1099,7 @@ run_phases(vector<Target*> targets, const Options& options)
// Tests
bool hasErrors = false;
if (testActions.size() > 0) {
if (testsRun) {
printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor);
size_t maxNameLength = 0;
for (size_t i=0; i<targets.size(); i++) {

View File

@@ -51,6 +51,18 @@ make_cache_filename(const string& outDir)
return filename + "/.bit_cache";
}
bool
Module::HasClass(const string& cl)
{
for (vector<string>::const_iterator c = classes.begin(); c != classes.end(); c++) {
if (*c == cl) {
return true;
}
}
return false;
}
BuildVars::BuildVars(const string& outDir, const string& buildProduct,
const string& buildVariant, const string& buildType)
:m_filename(),

View File

@@ -29,6 +29,8 @@ struct Module
vector<string> classes;
vector<string> paths;
vector<string> installed;
bool HasClass(const string& cl);
};
/**

View File

@@ -254,4 +254,42 @@ read_file(const string& filename)
return result;
}
bool
is_executable(const string& filename)
{
int err;
struct stat st;
err = stat(filename.c_str(), &st);
if (err != 0) {
return false;
}
return (st.st_mode & S_IXUSR) != 0;
}
string
dirname(const string& filename)
{
size_t slash = filename.rfind('/');
if (slash == string::npos) {
return "";
} else if (slash == 0) {
return "/";
} else {
return string(filename, 0, slash);
}
}
string
leafname(const string& filename)
{
size_t slash = filename.rfind('/');
if (slash == string::npos) {
return filename;
} else if (slash == filename.length() - 1) {
return "";
} else {
return string(filename, slash + 1);
}
}

View File

@@ -79,5 +79,10 @@ void split_lines(vector<string>* result, const string& str);
string read_file(const string& filename);
bool is_executable(const string& filename);
string dirname(const string& filename);
string leafname(const string& filename);
#endif // UTIL_H