プロセス制御 API

1 概要

testfw/include/processController.h が提供するプロセス制御 API です。
テスト対象バイナリをサブプロセスとして起動し、
stdin / stdout / stderr をパイプ経由で制御できます。

2 つのモードがあります。

モード API 用途
同期 startProcess() 短命プロセス (起動 → 終了まで待機)
非同期 startProcessAsync() + 各操作関数 常駐プロセス・対話プロセスの制御

startProcess()startProcessAsync() のラッパーとして実装されており、
実質的な処理はすべて非同期 API 側に集約されています。

2 型・構造体

2.1 AsyncProcessHandle

struct AsyncProcess;
using AsyncProcessHandle = shared_ptr<AsyncProcess>;

非同期プロセスのハンドルです。内部実装は非公開です。
shared_ptr であるため、スコープアウト時にデストラクタで自動的にリソースが解放されます。

2.2 ProcessOptions

struct ProcessOptions {
    map<string, string> env_set;

#ifndef _WIN32
    string preload_lib;
#endif

#ifdef _WIN32
    bool capture_debug_output = true;
#endif
};
フィールド 説明
env_set 追加または上書きする環境変数
preload_lib LD_PRELOAD に追加するライブラリの絶対パス (Linux のみ)testfw/lib/$(TARGET_ARCH)/libmock_syslog.so を指定すると syslog 出力を getDebugLog() でキャプチャできる
capture_debug_output OutputDebugString 出力をキャプチャする (Windows のみ)true にすると getDebugLog() でキャプチャできる。Linux の preload_lib に相当する。デフォルト true

2.3 ProcessResult

startProcess() の返値です。

struct ProcessResult {
    int    exit_code;     // 終了コード (-1 = 起動失敗またはタイムアウト)
    string stdout_out;    // 標準出力
    string stderr_out;    // 標準エラー出力
    string debug_log;     // デバッグログ出力 (preload_lib / capture_debug_output 指定時のみ)
};

debug_log は以下の場合に内容が入ります。

  • Linux: preload_lib を指定した場合 (syslog モック出力)
  • Windows: capture_debug_output = true を指定した場合 (OutputDebugString 出力)

3 API リファレンス

3.1 同期 API

3.1.1 startProcess

inline ProcessResult startProcess(
    const string& binary,
    const vector<string>& args        = {},
    const ProcessOptions& opts        = ProcessOptions{},
    const vector<string>& stdin_lines = {},
    int timeout_ms                    = 30000)

プロセスを起動し、終了まで待機して ProcessResult を返します。

引数 説明
binary 実行するバイナリの絶対パス
args コマンドライン引数 (argv[1] 以降)
opts 実行オプション
stdin_lines stdin に渡す行リスト。各要素末尾に \n を付加して順次書き込む
timeout_ms タイムアウト (ms)。デフォルト 30000

処理フロー:

  1. startProcessAsync() でプロセスを起動
  2. stdin_lineswriteLineStdin() で順次書き込み
  3. closeStdin() で EOF を通知
  4. waitForExit(handle, timeout_ms) で終了待機
  5. getStdout() / getStderr() / getDebugLog() で結果を収集して返す

3.2 非同期 API

3.2.1 startProcessAsync

extern AsyncProcessHandle startProcessAsync(
    const string& path,
    const vector<string>& args = {},
    const ProcessOptions& opts = ProcessOptions{})

プロセスを非同期で起動します。
stdin / stdout / stderr はすべてパイプ経由で制御されます。
起動失敗時は nullptr を返します。

3.2.2 writeStdin / writeLineStdin

extern bool writeStdin    (AsyncProcessHandle& handle, const string& data)
extern bool writeLineStdin(AsyncProcessHandle& handle, const string& line)

プロセスの stdin に書き込みます。
writeStdin は改行を付加しません。writeLineStdin は末尾に \n を付加します。
プロセスが終了済みの場合は false を返します。

3.2.3 waitForOutput

extern string waitForOutput(AsyncProcessHandle& handle,
                             const string& pattern,
                             int timeout_ms = 5000)

stdout に指定パターン (部分一致) が出現するまで待機します。
パターン出現位置までの stdout を返します。
タイムアウトした場合、またはプロセスが予期せず終了した場合は std::runtime_error を送出します。

stdout の読み取りはバックグラウンドスレッドが常時行っているため、
writeLineStdin() と同時に呼び出してもデッドロックは発生しません。

Linux / Windows いずれも reader_thread がリアルタイムで収集するため、waitForOutput() 完了後にはその時点までの全ログが利用可能です。

テスト対象側の要件: stdout / stderr はパイプ経由で受け渡されるため、
テストが待機するパターンを出力した直後にテスト対象側で fflush(stdout) を呼ぶ必要があります。
呼ばれていない場合、出力が stdio の内部バッファに滞留し、タイムアウトまでパターンが届きません。

3.2.4 closeStdin

extern void closeStdin(AsyncProcessHandle& handle)

stdin パイプを閉じます。fgets() でブロックしているプロセスに EOF を通知します。

3.2.5 interruptProcess

extern void interruptProcess(AsyncProcessHandle& handle)

プロセスに Ctrl+C 相当のシグナルを送ります。

  • Linux: kill(pid, SIGINT)
  • Windows: GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid)

3.2.6 killProcess

extern void killProcess(AsyncProcessHandle& handle)

プロセスを強制終了します。

  • Linux: kill(pid, SIGKILL)
  • Windows: TerminateProcess

3.2.7 waitForExit

extern int waitForExit(AsyncProcessHandle& handle, int timeout_ms = 10000)

プロセス終了を待機し、終了コードを返します。
タイムアウト時は -1 を返します。

Linux / Windows いずれも reader_thread がリアルタイムで収集するため、waitForExit() 完了後には全ログが利用可能です。

3.2.8 getStdout / getStderr

extern string getStdout(AsyncProcessHandle& handle)
extern string getStderr(AsyncProcessHandle& handle)

これまでに受信した stdout / stderr 全体を返します (非破壊)。

3.2.9 getDebugLogCount / getDebugLog

extern size_t          getDebugLogCount(AsyncProcessHandle& handle)
extern vector<string>  getDebugLog     (AsyncProcessHandle& handle, size_t from_index = 0)

デバッグログを行単位で返します (非破壊)。

  • Linux: preload_lib を指定した場合に有効。syslog モック出力が対象。
  • Windows: capture_debug_output = true を指定した場合に有効。OutputDebugString 出力が対象。
  • from_indexgetDebugLogCount() で記録したインデックスを渡すと、
    その時点以降のログのみを取り出せます。

4 使い方

4.1 同期 (startProcess)

// 最小呼び出し
ProcessResult res = startProcess(binary_path);

// 引数・環境変数・入力行を指定
ProcessOptions opts;
opts.env_set["LD_LIBRARY_PATH"] = lib_path;

ProcessResult res = startProcess(
    binary_path,
    {"config.conf", "10"},  // args
    opts,
    {"Hello", "N", "N"},    // stdin_lines
    5000);                  // timeout_ms

EXPECT_EQ(0, res.exit_code);
EXPECT_NE(string::npos, res.stdout_out.find("送信完了"));

4.2 非同期 (startProcessAsync)

// 常駐プロセスを起動して対話する例
ProcessOptions opts;
opts.env_set["LD_LIBRARY_PATH"] = lib_path;

AsyncProcessHandle recv_h = startProcessAsync(recv_path, {config_path, "10"}, opts);
ASSERT_NE(nullptr, recv_h);

// リスニング開始を確認してから次のステップへ
ASSERT_NO_THROW(waitForOutput(recv_h, "受信待機中", 5000));

// 別プロセスを起動して対話する
AsyncProcessHandle send_h = startProcessAsync(send_path, {config_path, "10"}, opts);
ASSERT_NO_THROW(waitForOutput(send_h, "メッセージ>", 5000));

ASSERT_TRUE(writeLineStdin(send_h, "Hello Porter"));
ASSERT_NO_THROW(waitForOutput(send_h, "圧縮送信しますか", 3000));
ASSERT_TRUE(writeLineStdin(send_h, "N"));
ASSERT_NO_THROW(waitForOutput(send_h, "続けて送信しますか", 3000));
ASSERT_TRUE(writeLineStdin(send_h, "N"));
EXPECT_EQ(0, waitForExit(send_h, 5000));

// 常駐プロセスを SIGINT で停止する
interruptProcess(recv_h);
waitForExit(recv_h, 3000);

EXPECT_NE(string::npos, getStdout(recv_h).find("Hello Porter"));

4.3 テスト失敗時のプロセスリーク防止

ASSERT_* マクロでテストが中断された場合でもプロセスを確実に終了させるため、
TearDown() に終了処理を実装します。

void TearDown() override {
    if (send_h_) { killProcess(send_h_); waitForExit(send_h_, 1000); }
    if (recv_h_) { killProcess(recv_h_); waitForExit(recv_h_, 1000); }
}

4.4 デバッグログのキャプチャ

ProcessOptions opts = makeOpts();
#ifndef _WIN32
opts.preload_lib = ws + "/testfw/lib/" + TARGET_ARCH + "/libmock_syslog.so";
#endif
/* Windows は capture_debug_output がデフォルト true のため追加設定不要 */

AsyncProcessHandle h = startProcessAsync(binary, args, opts);
ASSERT_NO_THROW(waitForOutput(h, "起動完了", 5000));

interruptProcess(h);
waitForExit(h, 3000); // ← Linux はここでログが一括収集される

// waitForExit() 後にログを検証
auto logs = getDebugLog(h);
EXPECT_TRUE(any_of(logs.begin(), logs.end(),
    [](const string& l) { return l.find("received message") != string::npos; }));

注意: Linux の getDebugLogCount()waitForExit() 後にのみ意味のある値を返します。
ステップ別のログ分割は waitForExit() 後に from_index で行ってください。

5 旧 API (runProcess) からの移行

runProcess() startProcess()

ProcessOptions から argstimeout_msstdin_lines フィールドが削除されました。
それぞれ startProcess() の独立した引数として渡します。

// 旧 API
ProcessOptions opts;
opts.args         = {"config.conf", "10"};
opts.stdin_lines  = {"Hello", "N"};
opts.timeout_ms   = 5000;
opts.env_set["LD_LIBRARY_PATH"] = lib_path;
auto res = runProcess(binary, opts);

// 新 API
ProcessOptions opts;
opts.env_set["LD_LIBRARY_PATH"] = lib_path;
auto res = startProcess(binary, {"config.conf", "10"}, opts, {"Hello", "N"}, 5000);