File: process_proxy_unittest.cc

package info (click to toggle)
chromium 120.0.6099.224-1~deb11u1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 6,112,112 kB
  • sloc: cpp: 32,907,025; ansic: 8,148,123; javascript: 3,679,536; python: 2,031,248; asm: 959,718; java: 804,675; xml: 617,256; sh: 111,417; objc: 100,835; perl: 88,443; cs: 53,032; makefile: 29,579; fortran: 24,137; php: 21,162; tcl: 21,147; sql: 20,809; ruby: 17,735; pascal: 12,864; yacc: 8,045; lisp: 3,388; lex: 1,323; ada: 727; awk: 329; jsp: 267; csh: 117; exp: 43; sed: 37
file content (286 lines) | stat: -rw-r--r-- 9,426 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
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
// Copyright 2012 The Chromium Authors
// 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 <memory>
#include <string>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/process/kill.h"
#include "base/process/process.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.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 kFakeUserHash[] = "0123456789abcdef";
const char kStdoutType[] = "stdout";
const int kTestLineNum = 100;

void RunOnTaskRunner(
    base::OnceClosure closure,
    const scoped_refptr<base::SequencedTaskRunner>& task_runner) {
  task_runner->PostTask(FROM_HERE, std::move(closure));
}

class TestRunner {
 public:
  TestRunner() = default;
  virtual ~TestRunner() = default;
  virtual void SetupExpectations(const std::string& id,
                                 const base::Process* process) = 0;
  virtual void OnSomeRead(const std::string& id,
                          const std::string& type,
                          const std::string& output) = 0;
  virtual void StartRegistryTest(ProcessProxyRegistry* registry) = 0;

  void set_done_read_closure(base::OnceClosure done_closure) {
    done_read_closure_ = std::move(done_closure);
  }

 protected:
  std::string id_;
  raw_ptr<const base::Process, AcrossTasksDanglingUntriaged> process_;

  base::OnceClosure done_read_closure_;
};

class RegistryTestRunner : public TestRunner {
 public:
  ~RegistryTestRunner() override = default;

  void SetupExpectations(const std::string& id,
                         const base::Process* process) override {
    id_ = id;
    process_ = process;
    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(const std::string& id,
                  const std::string& type,
                  const std::string& output) override {
    EXPECT_EQ(type, kStdoutType);
    EXPECT_EQ(id_, 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()) {
      ASSERT_FALSE(done_read_closure_.is_null());
      std::move(done_read_closure_).Run();
    }
  }

  void StartRegistryTest(ProcessProxyRegistry* registry) override {
    for (int i = 0; i < kTestLineNum; i++) {
      registry->SendInput(id_, kTestLineToSend, base::BindOnce([](bool result) {
                            EXPECT_TRUE(result);
                          }));
    }
  }

 private:
  bool ProcessReceivedCharacter(char received, size_t stream) {
    if (stream >= std::size(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 = default;

  void SetupExpectations(const std::string& id,
                         const base::Process* process) override {
    output_received_ = false;
    id_ = id;
    process_ = process;
  }

  void OnSomeRead(const std::string& id,
                  const std::string& type,
                  const std::string& output) override {
    EXPECT_EQ(id_, id);
    if (!output_received_) {
      base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;
      output_received_ = true;
      EXPECT_EQ(type, "stdout");
      EXPECT_EQ(output, "p");
      process_->Terminate(0, true);
      return;
    }
    EXPECT_EQ("exit", type);
    ASSERT_FALSE(done_read_closure_.is_null());
    std::move(done_read_closure_).Run();
  }

  void StartRegistryTest(ProcessProxyRegistry* registry) override {
    registry->SendInput(
        id_, "p", base::BindOnce([](bool result) { EXPECT_TRUE(result); }));
  }

 private:
  bool output_received_;
};

}  // namespace

class ProcessProxyTest : public testing::Test {
 public:
  ProcessProxyTest() = default;
  ~ProcessProxyTest() override = default;

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

    base::CommandLine cmdline{{kCatCommand}};
    bool success = registry_->OpenProcess(
        cmdline, kFakeUserHash,
        base::BindRepeating(&ProcessProxyTest::HandleRead,
                            base::Unretained(this)),
        &id_);
    process_ = registry_->GetProcessForTesting(id_);

    EXPECT_TRUE(success);
    test_runner_->set_done_read_closure(std::move(done_closure));
    test_runner_->SetupExpectations(id_, process_);
    test_runner_->StartRegistryTest(registry_);
  }

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

  void EndRegistryTest(base::OnceClosure done_closure) {
    base::ScopedAllowBaseSyncPrimitivesForTesting allow_sync_primitives;

    registry_->CloseProcess(id_);

    int unused_exit_code = 0;
    base::TerminationStatus status =
        base::GetTerminationStatus(process_->Handle(), &unused_exit_code);
    EXPECT_NE(base::TERMINATION_STATUS_STILL_RUNNING, status);
    if (status == base::TERMINATION_STATUS_STILL_RUNNING) {
      process_->Terminate(0, true);
    }

    registry_->ShutDown();

    std::move(done_closure).Run();
  }

  void RunTest() {
    base::RunLoop init_registry_waiter;
    ProcessProxyRegistry::GetTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &ProcessProxyTest::InitRegistryTest, base::Unretained(this),
            base::BindOnce(&RunOnTaskRunner, init_registry_waiter.QuitClosure(),
                           base::SequencedTaskRunner::GetCurrentDefault())));
    // Wait until all data from output watcher is received (QuitTask will be
    // fired on watcher thread).
    init_registry_waiter.Run();

    base::RunLoop end_registry_waiter;
    ProcessProxyRegistry::GetTaskRunner()->PostTask(
        FROM_HERE,
        base::BindOnce(
            &ProcessProxyTest::EndRegistryTest, base::Unretained(this),
            base::BindOnce(&RunOnTaskRunner, end_registry_waiter.QuitClosure(),
                           base::SequencedTaskRunner::GetCurrentDefault())));
    // Wait until we clean up the process proxy.
    end_registry_waiter.Run();
  }

  std::unique_ptr<TestRunner> test_runner_;

 private:
  // Destroys ProcessProxyRegistry LazyInstance after each test.
  base::ShadowingAtExitManager shadowing_at_exit_manager_;

  raw_ptr<ProcessProxyRegistry> registry_;
  std::string id_;
  raw_ptr<const base::Process, AcrossTasksDanglingUntriaged> process_ = nullptr;

  base::test::TaskEnvironment task_environment_;
};

// 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_ = std::make_unique<RegistryTestRunner>();
  RunTest();
}

// Open new process, then kill it. Verifiy that we detect when the process dies.
//
// Disabled due to flakiness: https://crbug.com/1151205
TEST_F(ProcessProxyTest, DISABLED_RegistryNotifiedOnProcessExit) {
  test_runner_ = std::make_unique<RegistryNotifiedOnProcessExitTestRunner>();
  RunTest();
}

}  // namespace chromeos