File: CliTest.h

package info (click to toggle)
cryfs 1.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 28,404 kB
  • sloc: cpp: 150,188; asm: 10,493; python: 1,455; javascript: 65; sh: 50; makefile: 17; xml: 7
file content (175 lines) | stat: -rw-r--r-- 7,723 bytes parent folder | download
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