summaryrefslogtreecommitdiffstats
path: root/src/lib/util/tests/process_spawn_unittest.cc
blob: 406a5f05fa8d47a18ac88cb6316622b657464330 (plain)
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>

#include <util/process_spawn.h>
#include <gtest/gtest.h>
#include <signal.h>
#include <stdint.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

namespace {

using namespace isc;
using namespace isc::util;

/// @brief Returns a location of the test script.
///
/// The test script is no-op and it returns the exit code equal to
/// the argument passed to it.
///
/// @return Absolute location of the test script.
std::string getApp() {
    std::ostringstream s;
    s << TEST_DATA_TOPBUILDDIR << "/src/lib/util/tests/process_spawn_app.sh";
    return (s.str());
}

/// @brief Waits for the specified process to finish (using sleep)
///
/// @param process An object which started the process.
/// @param pid ID of the spawned process.
/// @param timeout Timeout in seconds.
/// @param sleep specifies whether usleep(1ms) should be used or not.
///
/// This method uses usleep() to wait between checks. For an alternative,
/// see waitForProcessFast(). Note: If the signal comes in when the
/// loop calls usleep(), usleep() will be aborted and errno will be set
/// to EINTR to indicate that interruption. Therefore this method is not
/// suitable for tests that want to observe errno value. See
/// @ref waitForProcessFast as an alternative.
///
/// @return true if the process ended, false otherwise
bool waitForProcess(const ProcessSpawn& process, const pid_t pid,
                    const uint8_t timeout) {
    uint32_t iterations = 0;
    const uint32_t iterations_max = timeout * 1000;

    int errno_save = errno;
    while (process.isRunning(pid) && (iterations < iterations_max)) {
        usleep(1000);
        ++iterations;
    }
    errno = errno_save;
    return (iterations < iterations_max);
}

/// @brief Waits for the specified process to finish (using fast loop)
///
/// @param process An object which started the process.
/// @param pid ID of the spawned process.
/// @param timeout Timeout in seconds.
/// @param sleep specifies whether usleep(1ms) should be used or not.
///
/// This method does not use any sleep() calls, but rather iterates through
/// the loop very fast. This is not recommended in general, but is necessary
/// to avoid updating errno by sleep() after receving a signal.
///
/// Note: the timeout is only loosely accurate. Depending on the fraction
/// of second it was started on, it may terminate later by up to almost
/// whole second.
///
/// @return true if the process ended, false otherwise
bool waitForProcessFast(const ProcessSpawn& process, const pid_t pid,
                        const uint8_t timeout) {
    time_t before = time(NULL);
    while (process.isRunning(pid)) {
        time_t now = time(NULL);

        // The difference before we started and now is greater than
        // the timeout, we should give up.
        if (now - before > timeout) {
            return (false);
        }
    }

    return (true);
}

// This test verifies that the external application can be ran with
// arguments and that the exit code is gathered.
TEST(ProcessSpawn, spawnWithArgs) {
    std::vector<std::string> args;
    args.push_back("-e");
    args.push_back("64");
    ProcessSpawn process(getApp(), args);
    pid_t pid = 0;
    ASSERT_NO_THROW(pid = process.spawn());

    ASSERT_TRUE(waitForProcess(process, pid, 2));

    EXPECT_EQ(64, process.getExitStatus(pid));
}

// This test verifies that the single ProcessSpawn object can be used
// to start two processes and that their status codes can be gathered.
// It also checks that it is possible to clear the status of the
// process.
TEST(ProcessSpawn, spawnTwoProcesses) {
    std::vector<std::string> args;
    args.push_back("-p");
    ProcessSpawn process(getApp(), args);
    pid_t pid1 = 0;
    ASSERT_NO_THROW(pid1 = process.spawn());
    ASSERT_TRUE(waitForProcess(process, pid1, 2));

    pid_t pid2 = 0;
    ASSERT_NO_THROW(pid2 = process.spawn());
    ASSERT_TRUE(waitForProcess(process, pid2, 2));

    EXPECT_NE(process.getExitStatus(pid1), process.getExitStatus(pid2));

    // Clear the status of the first process. An attempt to get the status
    // for the cleared process should result in exception. But, there should
    // be no exception for the second process.
    process.clearState(pid1);
    EXPECT_THROW(process.getExitStatus(pid1), InvalidOperation);
    EXPECT_NO_THROW(process.getExitStatus(pid2));

    process.clearState(pid2);
    EXPECT_THROW(process.getExitStatus(pid2), InvalidOperation);
}

// This test verifies that the external application can be ran without
// arguments and that the exit code is gathered.
TEST(ProcessSpawn, spawnNoArgs) {
    std::vector<std::string> args;
    ProcessSpawn process(getApp());
    pid_t pid = 0;
    ASSERT_NO_THROW(pid = process.spawn());

    ASSERT_TRUE(waitForProcess(process, pid, 2));

    EXPECT_EQ(32, process.getExitStatus(pid));
}


// This test verifies that the EXIT_FAILURE code is returned when
// application can't be executed.
TEST(ProcessSpawn, invalidExecutable) {
    ProcessSpawn process("foo");
    pid_t pid = 0;
    ASSERT_NO_THROW(pid = process.spawn());

    ASSERT_TRUE(waitForProcess(process, pid, 2));

    EXPECT_EQ(EXIT_FAILURE, process.getExitStatus(pid));
}

// This test verifies that the full command line for the process is
// returned.
TEST(ProcessSpawn, getCommandLine) {
    // Note that cases below are enclosed in separate scopes to make
    // sure that the ProcessSpawn object is destroyed before a new
    // object is created. Current implementation doesn't allow for
    // having two ProcessSpawn objects simultaneously as they will
    // both try to allocate a signal handler for SIGCHLD.
    {
        // Case 1: arguments present.
        ProcessArgs args;
        args.push_back("-x");
        args.push_back("-y");
        args.push_back("foo");
        args.push_back("bar");
        ProcessSpawn process("myapp", args);
        EXPECT_EQ("myapp -x -y foo bar", process.getCommandLine());
    }

    {
        // Case 2: no arguments.
        ProcessSpawn process("myapp");
        EXPECT_EQ("myapp", process.getCommandLine());
    }
}

// This test verifies that it is possible to check if the process is
// running.
TEST(ProcessSpawn, isRunning) {
    // Run the process which sleeps for 10 seconds, so as we have
    // enough time to check if it is running.
    std::vector<std::string> args;
    args.push_back("-s");
    args.push_back("10");
    ProcessSpawn process(getApp(), args);
    pid_t pid = 0;
    ASSERT_NO_THROW(pid = process.spawn());
    EXPECT_TRUE(process.isRunning(pid));

    // Kill the process.
    ASSERT_EQ(0, kill(pid, SIGKILL));
    // And make sure if died.
    ASSERT_TRUE(waitForProcess(process, pid, 2));
}

// This test verifies that the signal handler does not modify value of
// errno.
TEST(ProcessSpawn, errnoInvariance) {

    // Set errno to an arbitrary value. We'll later check that it was not
    // stumped on.
    errno = 123;

    std::vector<std::string> args;
    args.push_back("-e");
    args.push_back("64");
    ProcessSpawn process(getApp(), args);
    pid_t pid = 0;
    ASSERT_NO_THROW(pid = process.spawn());

    ASSERT_TRUE(waitForProcessFast(process, pid, 2));

    EXPECT_EQ(64, process.getExitStatus(pid));

    // errno value should be set to be preserved, despite the SIGCHILD
    // handler (ProcessSpawnImpl::waitForProcess) calling waitpid(), which
    // will likely set errno to ECHILD. See trac4000.
    EXPECT_EQ(123, errno);
}


} // end of anonymous namespace