1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
#pragma once
#ifndef MESSMER_CRYFS_TEST_CLI_TESTUTILS_CLITEST_H
#define MESSMER_CRYFS_TEST_CLI_TESTUTILS_CLITEST_H
#if defined(_MSC_VER)
#include <codecvt>
#include <dokan/dokan.h>
#endif
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cpp-utils/tempfile/TempDir.h>
#include <cpp-utils/tempfile/TempFile.h>
#include <cryfs-cli/Cli.h>
#include <cryfs-cli/VersionChecker.h>
#include <cpp-utils/logging/logging.h>
#include <cpp-utils/process/subprocess.h>
#include <cpp-utils/network/FakeHttpClient.h>
#include <cpp-utils/lock/ConditionBarrier.h>
#include "../../cryfs/impl/testutils/MockConsole.h"
#include "../../cryfs/impl/testutils/TestWithFakeHomeDirectory.h"
#include <fspp/fuse/Fuse.h>
#include <cryfs/impl/ErrorCodes.h>
#include <cpp-utils/testutils/CaptureStderrRAII.h>
#include <regex>
#include <string>
class CliTest : public ::testing::Test, TestWithFakeHomeDirectory {
public:
CliTest(): _basedir(), _mountdir(), basedir(_basedir.path()), mountdir(_mountdir.path()), logfile(), configfile(false), console(std::make_shared<MockConsole>()) {}
cpputils::TempDir _basedir;
cpputils::TempDir _mountdir;
boost::filesystem::path basedir;
boost::filesystem::path mountdir;
cpputils::TempFile logfile;
cpputils::TempFile configfile;
std::shared_ptr<MockConsole> console;
cpputils::unique_ref<cpputils::HttpClient> _httpClient() {
cpputils::unique_ref<cpputils::FakeHttpClient> httpClient = cpputils::make_unique_ref<cpputils::FakeHttpClient>();
httpClient->addWebsite("https://www.cryfs.org/version_info.json", "{\"version_info\":{\"current\":\"0.8.5\"}}");
return httpClient;
}
int run(const std::vector<std::string>& args, std::function<void()> onMounted) {
std::vector<const char*> _args;
_args.reserve(args.size() + 1);
_args.emplace_back("cryfs");
for (const std::string& arg : args) {
_args.emplace_back(arg.c_str());
}
auto *keyGenerator = cpputils::Random::PseudoRandom();
ON_CALL(*console, askPassword(testing::StrEq("Password: "))).WillByDefault(testing::Return("pass"));
ON_CALL(*console, askPassword(testing::StrEq("Confirm Password: "))).WillByDefault(testing::Return("pass"));
// Run Cryfs
return cryfs_cli::Cli(keyGenerator, cpputils::SCrypt::TestSettings, console).main(
_args.size(),
_args.data(),
#ifdef CRYFS_UPDATE_CHECKS
_httpClient(),
#endif
std::move(onMounted)
);
}
void EXPECT_EXIT_WITH_HELP_MESSAGE(const std::vector<std::string>& args, const std::string &message, cryfs::ErrorCode errorCode) {
EXPECT_RUN_ERROR(args, "Usage:[^\\x00]*"+message, errorCode);
}
void EXPECT_RUN_ERROR(const std::vector<std::string>& args, const std::string& message, cryfs::ErrorCode errorCode, std::function<void ()> onMounted = [] {}) {
const FilesystemOutput filesystem_output = run_filesystem(args, boost::none, std::move(onMounted));
EXPECT_EQ(exitCode(errorCode), filesystem_output.exit_code);
if (!std::regex_search(filesystem_output.stderr_, std::regex(message))) {
std::cerr << filesystem_output.stderr_ << std::endl;
EXPECT_TRUE(false);
}
}
void EXPECT_RUN_SUCCESS(const std::vector<std::string>& args, const boost::optional<boost::filesystem::path> &mountDir, std::function<void ()> onMounted = [] {}) {
//TODO Make this work when run in background
ASSERT(std::find(args.begin(), args.end(), string("-f")) != args.end(), "Currently only works if run in foreground");
bool successfully_mounted = false;
const FilesystemOutput filesystem_output = run_filesystem(args, mountDir, [&] {
successfully_mounted = true;
onMounted();
});
EXPECT_EQ(0, filesystem_output.exit_code);
if (!std::regex_search(filesystem_output.stdout_, std::regex("Mounting filesystem"))) {
std::cerr << "STDOUT:\n" << filesystem_output.stdout_ << "STDERR:\n" << filesystem_output.stderr_ << std::endl;
EXPECT_TRUE(false) << "Filesystem did not output the 'Mounting filesystem' message, probably wasn't successfully mounted.";
}
if (!successfully_mounted) {
EXPECT_TRUE(false) << "Filesystem did not call onMounted callback, probably wasn't successfully mounted.";
}
}
struct FilesystemOutput final {
int exit_code;
std::string stdout_;
std::string stderr_;
};
static void _unmount(const boost::filesystem::path &mountDir) {
fspp::fuse::Fuse::unmount(mountDir, true);
}
FilesystemOutput run_filesystem(const std::vector<std::string>& args, boost::optional<boost::filesystem::path> mountDirForUnmounting, std::function<void()> onMounted) {
testing::internal::CaptureStdout();
testing::internal::CaptureStderr();
bool exited = false;
cpputils::ConditionBarrier isMountedOrFailedBarrier;
std::future<int> exit_code = std::async(std::launch::async, [&] {
const int exit_code = run(args, [&] { isMountedOrFailedBarrier.release(); });
// just in case it fails, we also want to release the barrier.
// if it succeeds, this will release it a second time, which doesn't hurt.
exited = true;
isMountedOrFailedBarrier.release();
return exit_code;
});
std::future<bool> on_mounted_success = std::async(std::launch::async, [&] {
isMountedOrFailedBarrier.wait();
if (exited) {
// file system already exited on its own, this indicates an error. It should have stayed mounted.
// while the exit_code from run() will signal an error in this case, we didn't encounter another
// error in the onMounted future, so return true here.
return true;
}
// now we know the filesystem stayed online, so we can call the onMounted callback
onMounted();
// and unmount it afterwards
if (mountDirForUnmounting.is_initialized()) {
_unmount(*mountDirForUnmounting);
}
return true;
});
if(std::future_status::ready != on_mounted_success.wait_for(std::chrono::seconds(1000))) {
testing::internal::GetCapturedStdout(); // stop capturing stdout
testing::internal::GetCapturedStderr(); // stop capturing stderr
std::cerr << "onMounted thread (e.g. used for unmount) didn't finish" << std::endl;
// The std::future destructor of a future created with std::async blocks until the future is ready.
// so, instead of causing a deadlock, rather abort
exit(EXIT_FAILURE);
}
EXPECT_TRUE(on_mounted_success.get()); // this also re-throws any potential exceptions
if(std::future_status::ready != exit_code.wait_for(std::chrono::seconds(1000))) {
testing::internal::GetCapturedStdout(); // stop capturing stdout
testing::internal::GetCapturedStderr(); // stop capturing stderr
std::cerr << "Filesystem thread didn't finish" << std::endl;
// The std::future destructor of a future created with std::async blocks until the future is ready.
// so, instead of causing a deadlock, rather abort
exit(EXIT_FAILURE);
}
return {
exit_code.get(), // this also re-throws any potential exceptions
testing::internal::GetCapturedStdout(),
testing::internal::GetCapturedStderr()
};
}
};
#endif
|