summaryrefslogtreecommitdiffstats
path: root/chromeos/process_proxy/process_proxy_unittest.cc
blob: 3605dc323d8f33ffa71f62eba3529d2d47cc8c83 (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
237
238
239
240
241
242
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <gtest/gtest.h>
#include <stddef.h>

#include <string>

#include "base/bind.h"
#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/process/kill.h"
#include "base/process/process.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread.h"
#include "chromeos/process_proxy/process_proxy_registry.h"

namespace chromeos {

namespace {

// The test line must have all distinct characters.
const char kTestLineToSend[] = "abcdefgh\n";
const char kTestLineExpected[] = "abcdefgh\r\n";

const char kCatCommand[] = "cat";
const char kStdoutType[] = "stdout";
const int kTestLineNum = 100;

class TestRunner {
 public:
  virtual ~TestRunner() {}
  virtual void SetupExpectations(int terminal_id) = 0;
  virtual void OnSomeRead(int terminal_id,
                          const std::string& type,
                          const std::string& output) = 0;
  virtual void StartRegistryTest(ProcessProxyRegistry* registry) = 0;

 protected:
  int terminal_id_;
};

class RegistryTestRunner : public TestRunner {
 public:
  ~RegistryTestRunner() override {}

  void SetupExpectations(int terminal_id) override {
    terminal_id_ = terminal_id;
    left_to_check_index_[0] = 0;
    left_to_check_index_[1] = 0;
    // We consider that a line processing has started if a value in
    // left_to_check__[index] is set to 0, thus -2.
    lines_left_ = 2 * kTestLineNum - 2;
    expected_line_ = kTestLineExpected;
  }

  // Method to test validity of received input. We will receive two streams of
  // the same data. (input will be echoed twice by the testing process). Each
  // stream will contain the same string repeated |kTestLineNum| times. So we
  // have to match 2 * |kTestLineNum| lines. The problem is the received lines
  // from different streams may be interleaved (e.g. we may receive
  // abc|abcdef|defgh|gh). To deal with that, we allow to test received text
  // against two lines. The lines MUST NOT have two same characters for this
  // algorithm to work.
  void OnSomeRead(int terminal_id,
                  const std::string& type,
                  const std::string& output) override {
    EXPECT_EQ(type, kStdoutType);
    EXPECT_EQ(terminal_id_, terminal_id);

    bool valid = true;
    for (size_t i = 0; i < output.length(); i++) {
      // The character output[i] should be next in at least one of the lines we
      // are testing.
      valid = (ProcessReceivedCharacter(output[i], 0) ||
               ProcessReceivedCharacter(output[i], 1));
      EXPECT_TRUE(valid) << "Received: " << output;
    }

    if (!valid || TestSucceeded()) {
      base::ThreadTaskRunnerHandle::Get()->PostTask(
          FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
    }
  }

  void StartRegistryTest(ProcessProxyRegistry* registry) override {
    for (int i = 0; i < kTestLineNum; i++) {
      EXPECT_TRUE(registry->SendInput(terminal_id_, kTestLineToSend));
    }
  }

 private:
  bool ProcessReceivedCharacter(char received, size_t stream) {
    if (stream >= arraysize(left_to_check_index_))
      return false;
    bool success = left_to_check_index_[stream] < expected_line_.length() &&
        expected_line_[left_to_check_index_[stream]] == received;
    if (success)
      left_to_check_index_[stream]++;
    if (left_to_check_index_[stream] == expected_line_.length() &&
        lines_left_ > 0) {
      // Take another line to test for this stream, if there are any lines left.
      // If not, this stream is done.
      left_to_check_index_[stream] = 0;
      lines_left_--;
    }
    return success;
  }

  bool TestSucceeded() {
    return left_to_check_index_[0] == expected_line_.length() &&
        left_to_check_index_[1] == expected_line_.length() &&
        lines_left_ == 0;
  }

  size_t left_to_check_index_[2];
  size_t lines_left_;
  std::string expected_line_;
};

class RegistryNotifiedOnProcessExitTestRunner : public TestRunner {
 public:
  ~RegistryNotifiedOnProcessExitTestRunner() override {}

  void SetupExpectations(int terminal_id) override {
    output_received_ = false;
    terminal_id_ = terminal_id;
  }

  void OnSomeRead(int terminal_id,
                  const std::string& type,
                  const std::string& output) override {
    EXPECT_EQ(terminal_id_, terminal_id);
    if (!output_received_) {
      output_received_ = true;
      EXPECT_EQ(type, "stdout");
      EXPECT_EQ(output, "p");
      base::Process process =
          base::Process::DeprecatedGetProcessFromHandle(terminal_id_);
      process.Terminate(0, true);
      return;
    }
    EXPECT_EQ("exit", type);
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
  }

  void StartRegistryTest(ProcessProxyRegistry* registry) override {
    EXPECT_TRUE(registry->SendInput(terminal_id_, "p"));
  }

 private:
  bool output_received_;
};

}  // namespace

class ProcessProxyTest : public testing::Test {
 public:
  ProcessProxyTest() {}
  ~ProcessProxyTest() override {}

 protected:
  void InitRegistryTest() {
    registry_ = ProcessProxyRegistry::Get();

    terminal_id_ = registry_->OpenProcess(
        kCatCommand,
        base::Bind(&ProcessProxyTest::HandleRead, base::Unretained(this)));

    EXPECT_GE(terminal_id_, 0);
    test_runner_->SetupExpectations(terminal_id_);
    test_runner_->StartRegistryTest(registry_);
  }

  void HandleRead(int terminal_id,
                  const std::string& output_type,
                  const std::string& output) {
    test_runner_->OnSomeRead(terminal_id, output_type, output);
    registry_->AckOutput(terminal_id);
  }

  void EndRegistryTest() {
    registry_->CloseProcess(terminal_id_);

    base::TerminationStatus status =
        base::GetTerminationStatus(terminal_id_, NULL);
    EXPECT_NE(base::TERMINATION_STATUS_STILL_RUNNING, status);
    if (status == base::TERMINATION_STATUS_STILL_RUNNING) {
      base::Process process =
          base::Process::DeprecatedGetProcessFromHandle(terminal_id_);
      process.Terminate(0, true);
    }

    registry_->ShutDown();

    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
  }

  void RunTest() {
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&ProcessProxyTest::InitRegistryTest,
                              base::Unretained(this)));

    // Wait until all data from output watcher is received (QuitTask will be
    // fired on watcher thread).
    base::MessageLoop::current()->Run();

    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::Bind(&ProcessProxyTest::EndRegistryTest, base::Unretained(this)));

    // Wait until we clean up the process proxy.
    base::MessageLoop::current()->Run();
  }

  scoped_ptr<TestRunner> test_runner_;

 private:
  ProcessProxyRegistry* registry_;
  int terminal_id_;

  base::MessageLoop message_loop_;
};

// Test will open new process that will run cat command, and verify data we
// write to process gets echoed back.
TEST_F(ProcessProxyTest, RegistryTest) {
  test_runner_.reset(new RegistryTestRunner());
  RunTest();
}

// Open new process, then kill it. Verifiy that we detect when the process dies.
TEST_F(ProcessProxyTest, RegistryNotifiedOnProcessExit) {
  test_runner_.reset(new RegistryNotifiedOnProcessExitTestRunner());
  RunTest();
}

}  // namespace chromeos