blob: 354cb16ade9865b171055ebdb421a2d4f78d6ae4 [file] [log] [blame]
// Copyright (C) 2015 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "android/base/files/PathUtils.h"
#include "android/base/Log.h"
#include "android/base/system/System.h"
#include "android/base/testing/TestTempDir.h"
#include "android/base/threads/Thread.h"
namespace android {
namespace base {
class TestSystem : public System {
public:
TestSystem(StringView launcherDir,
int hostBitness,
StringView homeDir = "/home",
StringView appDataDir = "")
: mProgramDir(launcherDir),
mProgramSubdir(""),
mLauncherDir(launcherDir),
mHomeDir(homeDir),
mAppDataDir(appDataDir),
mCurrentDir(homeDir),
mHostBitness(hostBitness),
mIsRemoteSession(false),
mRemoteSessionType(),
mTempDir(NULL),
mTempRootPrefix(),
mEnvPairs(),
mPrevSystem(System::setForTesting(this)),
mTimes(),
mShellFunc(NULL),
mShellOpaque(NULL),
mUnixTime(),
mPid() {}
virtual ~TestSystem() {
System::setForTesting(mPrevSystem);
delete mTempDir;
}
virtual const std::string& getProgramDirectory() const { return mProgramDir; }
// Set directory of currently executing binary. This must be a subdirectory
// of mLauncherDir and specified relative to mLauncherDir
void setProgramSubDir(StringView programSubDir) {
mProgramSubdir = programSubDir;
if (programSubDir.empty()) {
mProgramDir = getLauncherDirectory();
} else {
mProgramDir = PathUtils::join(getLauncherDirectory(),
programSubDir);
}
}
virtual const std::string& getLauncherDirectory() const {
if (mLauncherDir.size()) {
return mLauncherDir;
} else {
return getTempRoot()->pathString();
}
}
void setLauncherDirectory(StringView launcherDir) {
mLauncherDir = launcherDir;
// Update directories that are suffixes of |mLauncherDir|.
setProgramSubDir(mProgramSubdir);
}
virtual const std::string& getHomeDirectory() const {
return mHomeDir;
}
void setHomeDirectory(StringView homeDir) { mHomeDir = homeDir; }
virtual const std::string& getAppDataDirectory() const {
return mAppDataDir;
}
void setAppDataDirectory(StringView appDataDir) {
mAppDataDir = appDataDir;
}
virtual std::string getCurrentDirectory() const { return mCurrentDir; }
// Set current directory during unit-testing.
void setCurrentDirectoryForTesting(StringView path) { mCurrentDir = path; }
virtual int getHostBitness() const {
return mHostBitness;
}
virtual OsType getOsType() const override {
return mOsType;
}
virtual bool isRunningUnderWine() const override {
return mUnderWine;
}
void setRunningUnderWine(bool underWine) {
mUnderWine = underWine;
}
virtual Pid getCurrentProcessId() const override {
return mPid;
}
void setCurrentProcessId(Pid pid) {
mPid = pid;
}
void setOsType(OsType type) {
mOsType = type;
}
virtual std::string envGet(StringView varname) const {
for (size_t n = 0; n < mEnvPairs.size(); n += 2) {
const std::string& name = mEnvPairs[n];
if (name == varname) {
return mEnvPairs[n + 1];
}
}
return std::string();
}
virtual std::vector<std::string> envGetAll() const override {
std::vector<std::string> res;
for (size_t i = 0; i < mEnvPairs.size(); i += 2) {
const std::string& name = mEnvPairs[i];
const std::string& val = mEnvPairs[i + 1];
res.push_back(std::string(name.c_str(), name.size())
+ '=' + val.c_str());
}
return res;
}
virtual void envSet(StringView varname, StringView varvalue) {
// First, find if the name is in the array.
int index = -1;
for (size_t n = 0; n < mEnvPairs.size(); n += 2) {
if (mEnvPairs[n] == varname) {
index = static_cast<int>(n);
break;
}
}
if (varvalue.empty()) {
// Remove definition, if any.
if (index >= 0) {
mEnvPairs.erase(mEnvPairs.begin() + index,
mEnvPairs.begin() + index + 2);
}
} else {
if (index >= 0) {
// Replacement.
mEnvPairs[index + 1] = varvalue;
} else {
// Addition.
mEnvPairs.push_back(varname);
mEnvPairs.push_back(varvalue);
}
}
}
virtual bool envTest(StringView varname) const {
for (size_t n = 0; n < mEnvPairs.size(); n += 2) {
const std::string& name = mEnvPairs[n];
if (name == varname) {
return true;
}
}
return false;
}
virtual bool pathExists(StringView path) const {
return pathExistsInternal(toTempRoot(path));
}
virtual bool pathIsFile(StringView path) const {
return pathIsFileInternal(toTempRoot(path));
}
virtual bool pathIsDir(StringView path) const {
return pathIsDirInternal(toTempRoot(path));
}
virtual bool pathCanRead(StringView path) const override {
return pathCanReadInternal(toTempRoot(path));
}
virtual bool pathCanWrite(StringView path) const override {
return pathCanWriteInternal(toTempRoot(path));
}
virtual bool pathCanExec(StringView path) const override {
return pathCanExecInternal(toTempRoot(path));
}
virtual bool deleteFile(StringView path) const override {
return deleteFileInternal(toTempRoot(path));
}
virtual bool pathFileSize(StringView path,
FileSize* outFileSize) const override {
return pathFileSizeInternal(toTempRoot(path), outFileSize);
}
virtual std::vector<std::string> scanDirEntries(
StringView dirPath,
bool fullPath = false) const {
getTempRoot(); // make sure we have a temp root;
std::string newPath = toTempRoot(dirPath);
std::vector<std::string> result = scanDirInternal(newPath);
if (fullPath) {
std::string prefix = PathUtils::addTrailingDirSeparator(dirPath);
for (size_t n = 0; n < result.size(); ++n) {
result[n] = prefix + result[n];
}
}
return result;
}
virtual TestTempDir* getTempRoot() const {
if (!mTempDir) {
mTempDir = new TestTempDir("TestSystem");
mTempRootPrefix = PathUtils::addTrailingDirSeparator(
std::string(mTempDir->path()));
}
return mTempDir;
}
virtual bool isRemoteSession(std::string* sessionType) const {
if (!mIsRemoteSession) {
return false;
}
*sessionType = mRemoteSessionType;
return true;
}
// Force the remote session type. If |sessionType| is NULL or empty,
// this sets the session as local. Otherwise, |*sessionType| must be
// a session type.
void setRemoteSessionType(StringView sessionType) {
mIsRemoteSession = !sessionType.empty();
if (mIsRemoteSession) {
mRemoteSessionType = sessionType;
}
}
virtual Times getProcessTimes() const {
return mTimes;
}
void setProcessTimes(const Times& times) {
mTimes = times;
}
// Type of a helper function that can be used during unit-testing to
// receive the parameters of a runCommand() call. Register it
// with setShellCommand().
typedef bool(ShellCommand)(void* opaque,
const std::vector<std::string>& commandLine,
System::Duration timeoutMs,
System::ProcessExitCode* outExitCode,
System::Pid* outChildPid,
const std::string& outputFile);
// Register a silent shell function. |shell| is the function callback,
// and |shellOpaque| a user-provided pointer passed as its first parameter.
void setShellCommand(ShellCommand* shell, void* shellOpaque) {
mShellFunc = shell;
mShellOpaque = shellOpaque;
}
bool runCommand(const std::vector<std::string>& commandLine,
RunOptions options,
System::Duration timeoutMs,
System::ProcessExitCode* outExitCode,
System::Pid* outChildPid,
const std::string& outputFile) override {
if (!commandLine.size()) {
return false;
}
// If a silent shell function was registered, invoke it, otherwise
// ignore the command completely.
bool result = true;
if (mShellFunc) {
result = (*mShellFunc)(mShellOpaque, commandLine, timeoutMs,
outExitCode, outChildPid, outputFile);
}
return result;
}
virtual std::string getTempDir() const { return std::string("/tmp"); }
virtual time_t getUnixTime() const override {
return mUnixTime / 1000000;
}
virtual Duration getUnixTimeUs() const override {
return mUnixTime;
}
virtual WallDuration getHighResTimeUs() const override {
return mUnixTime;
}
void setUnixTime(time_t time) {
setUnixTimeUs(time * 1000000LL);
}
void setUnixTimeUs(Duration time) {
mUnixTime = time;
}
virtual void sleepMs(unsigned n) const override {
// Don't sleep in tests, use the static functions from Thread class
// if you need a delay (you don't!).
Thread::yield(); // Add a small delay to mimic the intended behavior.
}
virtual void yield() const override {
Thread::yield();
}
private:
std::string toTempRoot(StringView path) const {
// mTempRootPrefix ends with a dir separator, ignore it for comparison.
StringView prefix(mTempRootPrefix.c_str(), mTempRootPrefix.size() - 1);
if (prefix.size() <= path.size() &&
prefix == StringView(path.c_str(), prefix.size()) &&
(prefix.size() == path.size() ||
PathUtils::isDirSeparator(path[prefix.size()]))) {
// Avoid prepending prefix if it's already there.
return path;
} else {
return mTempRootPrefix + path.c_str();
}
}
std::string mProgramDir;
std::string mProgramSubdir;
std::string mLauncherDir;
std::string mHomeDir;
std::string mAppDataDir;
std::string mCurrentDir;
int mHostBitness;
bool mIsRemoteSession;
std::string mRemoteSessionType;
mutable TestTempDir* mTempDir;
mutable std::string mTempRootPrefix;
std::vector<std::string> mEnvPairs;
System* mPrevSystem;
Times mTimes;
ShellCommand* mShellFunc;
void* mShellOpaque;
Duration mUnixTime;
OsType mOsType = OsType::Windows;
bool mUnderWine = false;
Pid mPid = 0;
};
} // namespace base
} // namespace android