-
-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Verify if tests communicate with just spawned dev server #479
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self comment
+ (options.verbose ? ["--verbose"] : []) | ||
+ extractor.remainingArguments | ||
) | ||
var args: [String] = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここ、巨大なリテラルと演算子が混ざっていて型推論が壊れやすいです。
正しいコードを書いていればいいですが、
作業中に間違ったコードを書いたりした時に IDEサポートがなくなってしまうのでやりづらいです。
式を分割して安定させます。
"--build-request", buildRequestPipe, | ||
"--build-response", buildResponsePipe | ||
] | ||
args += (options.pid.map { ["--pid", $0] } ?? []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
パッチとしての挙動の変更は --pid
オプションを扱うこの部分です。
"--environment", options.environment.rawValue, | ||
"--plugin-work-directory", context.pluginWorkDirectory.string | ||
] | ||
args += (options.pid.map { ["--pid", $0] } ?? []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここで --pid
の転送を追加しました。
また、式を分割しています。
@@ -64,6 +66,7 @@ func derivePackageCommandArguments( | |||
cartonPluginArguments = ["--output", "Bundle"] + cartonPluginArguments | |||
case "dev": | |||
packageArguments += ["--disable-sandbox"] | |||
cartonPluginArguments += ["--pid", pid.description] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
driverがpluginを起動するときに、
devとtestだったらdriverのpidを渡します。
|
||
public static let serverName = "carton dev server" | ||
|
||
public struct ServerNameField: CustomStringConvertible { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Server:
ヘッダーフィールドの型です。
テスト側で受け取って読み取りたいのでパーサ付き。
@@ -82,6 +82,7 @@ final class DevCommandTests: XCTestCase { | |||
|
|||
let (response, data) = try await fetchDevServerWithRetry(at: try URL(string: url).unwrap("url")) | |||
XCTAssertEqual(response.statusCode, 200, "Response was not ok") | |||
try checkServerNameField(response: response, expectedPID: process.process.processID) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
通信相手はちゃんとテスト対象のプロセスだったのか検証します。
@@ -5,6 +5,70 @@ import CartonKit | |||
import SwiftToolchain | |||
import WebDriver | |||
|
|||
struct DevServerClient { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
元々ここに書いてあったロジックをまとめました。
主な目的は fetchString
などの関数に暗黙のパラメータとして起動したプロセスを参照することです。
これによりテストコードのシンプルさを保ったまま、
フェッチ関数の内部で通信相手を検証できます。
at url: URL, | ||
file: StaticString = #file, line: UInt = #line | ||
) async throws -> Data { | ||
let (response, body) = try await withRetry( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
これ元々は外側でretryしていましたが、
これはサーバの起動待ちが目的であって、
通信相手の検証処理などはretryしてもしょうがないため、
通信処理だけをリトライするようにここに移しました。
}, stderr: { (_) in }, | ||
redirectStderr: true | ||
) | ||
let cl = try DevServerClient( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
まとめたものを使っています。
見かけがスッキリしつつ、このテストケースのロジックは変わっていません。
@@ -5,5 +5,5 @@ let package = Package( | |||
name: "Foo", | |||
products: [.executable(name: "my-echo", targets: ["my-echo"])], | |||
dependencies: [.package(path: "../../..")], | |||
targets: [.target(name: "my-echo")] | |||
targets: [.executableTarget(name: "my-echo")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.target
は古い書き方のため警告が出るので新しい書き方にしました。
@kateinoigakukun こちら確認をお願いします。 |
@@ -113,6 +113,8 @@ struct CartonFrontendDevCommand: AsyncParsableCommand { | |||
) | |||
var mainWasmPath: String | |||
|
|||
@Option(name: .long) var pid: Int32? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you hide the internal option from the help by help: .hidden
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
そういうのがあるんですね。対応しました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
背景・課題
DevCommandTests と FrontendDevServerTests では、テストケースの中で dev server を起動して、
それと通信して挙動を検証しています。
しかし、テスト実行中に別のターミナルで dev server が起動していたり、
全く別のプロセスがポートを使用していたり、
開発中のバグの影響で、以前起動したテスト用のプロセスがゾンビ化して生き残っていたりすると、
テストしようとした dev server が待ち受けポートを利用できずに起動に失敗してしまいます。
これは実際に、
SIGTERM
をSIGINT
に意図せず変換してしまうバグを修正した時に、SIGTERM
のハンドルがされずにゾンビプロセスが生まれることによって、CIがわかりづらい壊れ方をするなどの現象を引き起こしました。
起動に失敗したことによりテストが停止すれば良いのですが、
この起動の失敗を検出するのは困難です。
carton dev
コマンドは swift ツールチェーンのインストールなども行うため、プロセスが正常に実行されているのか、
将来的にサーバの起動に失敗する状況にあるかわからないからです。
起動時間が読めないこともあって、テストケースはHTTPリクエストをしばらくリトライします。
そして、別の理由で先に存在していた dev server がいた場合は、
テストプロセスのHTTP通信が成功してしまうため、
テストが誤って成功したり、予期し得ないレスポンスが返って意味不明な失敗をしたりします。
また、ゾンビプロセスが残っていることがわかった時、
それを特定して kill するまでの手順も多少面倒です。
macOS は
$ lsof -i -P
の実行に何故か結構待ち時間があります。提案
テストを安定させるために、
HTTPレスポンスを返してきたサーバが期待したプロセスかどうか検証できるようにします。
そのため、HTTPレスポンスヘッダーの
Server:
フィールドを利用して、のような情報を返すようにします。
テストケースではこれを検証することによって、
そもそもそのテストケースのために起動したサーバプロセスと通信しているのかを検証し、
そうでなければエラーを出して停止するようにします。
また、これは開発中にゾンビプロセスが生じた場合にも便利です。
$ curl -i localhost:8080
でヘッダを見れば pid がわかるからです。pid の指定
frontend を起動した場合はそれが直接サーバプロセスになりますが、
driver を起動した場合は、
carton driver → swiftpm plugin runner → carton plugin → carton frontend
と4階層のプロセスツリーができます。
この時、テストケースが知っているのは自分が起動した手元のdriverのpidなので、
サーバはその driver の pid を通知して欲しいです。
そこで、carton plugin と carton frontend に
--pid
オプションを追加して、dev server が広告する carton のプロセス番号を指定できるようにします。
結果
以下のようにテストがうまく停止するようになりました。
FrontendDevServerTestsで検出成功した例
DevCommandTestsで検出成功した例