Skip to content
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

Merged
merged 3 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ let package = Package(
"WebDriver",
"WasmTransformer",
],
exclude: ["Utilities/README.md"]
exclude: ["Utilities/README.md"],
swiftSettings: [
.enableUpcomingFeature("BareSlashRegexLiterals")
]
),
.target(
name: "SwiftToolchain",
Expand Down
33 changes: 19 additions & 14 deletions Plugins/CartonDevPlugin/CartonDevPluginCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@ struct CartonDevPluginCommand: CommandPlugin {
var product: String?
var release: Bool
var verbose: Bool
var pid: String?

static func parse(from extractor: inout ArgumentExtractor) throws -> Options {
let product = extractor.extractOption(named: "product").last
let release = extractor.extractFlag(named: "release")
let verbose = extractor.extractFlag(named: "verbose")
return Options(product: product, release: release != 0, verbose: verbose != 0)
let pid = extractor.extractOption(named: "pid").last
return Options(
product: product, release: release != 0, verbose: verbose != 0,
pid: pid
)
}
}

Expand Down Expand Up @@ -82,19 +87,19 @@ struct CartonDevPluginCommand: CommandPlugin {
let buildRequestPipe = try createFifo(hint: "build-request", directory: tempDirectory)
let buildResponsePipe = try createFifo(hint: "build-response", directory: tempDirectory)

let frontend = try makeCartonFrontendProcess(
context: context,
arguments: [
"dev",
"--main-wasm-path", productArtifact.path.string,
"--build-request", buildRequestPipe,
"--build-response", buildResponsePipe,
]
+ resourcesPaths.flatMap { ["--resources", $0.string] }
+ pathsToWatch.flatMap { ["--watch-path", $0] }
+ (options.verbose ? ["--verbose"] : [])
+ extractor.remainingArguments
)
var args: [String] = [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここ、巨大なリテラルと演算子が混ざっていて型推論が壊れやすいです。
正しいコードを書いていればいいですが、
作業中に間違ったコードを書いたりした時に IDEサポートがなくなってしまうのでやりづらいです。
式を分割して安定させます。

"dev",
"--main-wasm-path", productArtifact.path.string,
"--build-request", buildRequestPipe,
"--build-response", buildResponsePipe
]
args += (options.pid.map { ["--pid", $0] } ?? [])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

パッチとしての挙動の変更は --pid オプションを扱うこの部分です。

args += resourcesPaths.flatMap { ["--resources", $0.string] }
args += pathsToWatch.flatMap { ["--watch-path", $0] }
args += (options.verbose ? ["--verbose"] : [])
args += extractor.remainingArguments

let frontend = try makeCartonFrontendProcess(context: context, arguments: args)
frontend.forwardTerminationSignals()

try frontend.run()
Expand Down
30 changes: 18 additions & 12 deletions Plugins/CartonTestPlugin/CartonTestPluginCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,16 @@ struct CartonTestPluginCommand: CommandPlugin {
struct Options {
var environment: Environment
var prebuiltTestBundlePath: String?
var pid: String?

static func parse(from extractor: inout ArgumentExtractor) throws -> Options {
let environment = try Environment.parse(from: &extractor)
let prebuiltTestBundlePath = extractor.extractOption(named: "prebuilt-test-bundle-path").first
return Options(environment: environment, prebuiltTestBundlePath: prebuiltTestBundlePath)
let pid = extractor.extractOption(named: "pid").last
return Options(
environment: environment, prebuiltTestBundlePath: prebuiltTestBundlePath,
pid: pid
)
}
}

Expand Down Expand Up @@ -98,17 +103,18 @@ struct CartonTestPluginCommand: CommandPlugin {
package: context.package
)

let frontendArguments =
[
"test",
"--prebuilt-test-bundle-path", testProductArtifactPath,
"--environment", options.environment.rawValue,
"--plugin-work-directory", context.pluginWorkDirectory.string
]
+ resourcesPaths.flatMap {
["--resources", $0.string]
} + extractor.remainingArguments
let frontend = try makeCartonFrontendProcess(context: context, arguments: frontendArguments)
var args: [String] = [
"test",
"--prebuilt-test-bundle-path", testProductArtifactPath,
"--environment", options.environment.rawValue,
"--plugin-work-directory", context.pluginWorkDirectory.string
]
args += (options.pid.map { ["--pid", $0] } ?? [])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここで --pid の転送を追加しました。
また、式を分割しています。

args += resourcesPaths.flatMap {
["--resources", $0.string]
}
args += extractor.remainingArguments
let frontend = try makeCartonFrontendProcess(context: context, arguments: args)
try frontend.checkRun(printsLoadingMessage: false, forwardExit: true)
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/CartonCore/CartonCoreError.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
struct CartonCoreError: Error & CustomStringConvertible {
init(_ description: String) {
public struct CartonCoreError: Error & CustomStringConvertible {
public init(_ description: String) {
self.description = description
}
var description: String
public var description: String
}
4 changes: 4 additions & 0 deletions Sources/CartonDriver/CartonDriverCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ func derivePackageCommandArguments(
let pluginArguments: [String] = ["plugin"]
var cartonPluginArguments: [String] = extraArguments

let pid = ProcessInfo.processInfo.processIdentifier

switch subcommand {
case "bundle":
packageArguments += ["--disable-sandbox"]
Expand All @@ -64,6 +66,7 @@ func derivePackageCommandArguments(
cartonPluginArguments = ["--output", "Bundle"] + cartonPluginArguments
case "dev":
packageArguments += ["--disable-sandbox"]
cartonPluginArguments += ["--pid", pid.description]
Copy link
Contributor Author

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を渡します。

case "test":
// 1. Ask the plugin process to generate the build command based on the given options
let commandFile = try makeTemporaryFile(prefix: "test-build")
Expand Down Expand Up @@ -92,6 +95,7 @@ func derivePackageCommandArguments(

// "--environment browser" launches a http server
packageArguments += ["--disable-sandbox"]
cartonPluginArguments += ["--pid", pid.description]
default: break
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ struct CartonFrontendDevCommand: AsyncParsableCommand {
)
var mainWasmPath: String

@Option(name: .long, help: .hidden) var pid: Int32?

static let configuration = CommandConfiguration(
commandName: "dev",
abstract: "Watch the current directory, host the app, rebuild on change."
Expand Down Expand Up @@ -169,6 +171,7 @@ struct CartonFrontendDevCommand: AsyncParsableCommand {
},
resourcesPaths: resources,
entrypoint: Self.entrypoint,
pid: pid,
terminal: terminal
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ struct CartonFrontendTestCommand: AsyncParsableCommand {
))
var pluginWorkDirectory: String = "./"

@Option(name: .long, help: .hidden) var pid: Int32?

func validate() throws {
if headless && environment != .browser {
throw TestError(
Expand Down Expand Up @@ -133,6 +135,7 @@ struct CartonFrontendTestCommand: AsyncParsableCommand {
port: port,
headless: headless,
resourcesPaths: resources,
pid: pid,
terminal: terminal
).run()
case .node:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct BrowserTestRunner: TestRunner {
let port: Int
let headless: Bool
let resourcesPaths: [String]
let pid: Int32?
let terminal: InteractiveWriter
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)

Expand All @@ -63,6 +64,7 @@ struct BrowserTestRunner: TestRunner {
port: Int,
headless: Bool,
resourcesPaths: [String],
pid: Int32?,
terminal: InteractiveWriter
) {
self.testFilePath = testFilePath
Expand All @@ -71,6 +73,7 @@ struct BrowserTestRunner: TestRunner {
self.port = port
self.headless = headless
self.resourcesPaths = resourcesPaths
self.pid = pid
self.terminal = terminal
}

Expand All @@ -86,6 +89,7 @@ struct BrowserTestRunner: TestRunner {
customIndexPath: nil,
resourcesPaths: resourcesPaths,
entrypoint: Constants.entrypoint,
pid: pid,
terminal: terminal
)
)
Expand Down
48 changes: 47 additions & 1 deletion Sources/CartonKit/Server/Server.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import CartonCore
import CartonHelpers
import Foundation
import Logging
Expand Down Expand Up @@ -106,6 +107,42 @@ public actor Server {
hasher.combine(ObjectIdentifier(self))
}
}

public static let serverName = "carton dev server"

public struct ServerNameField: CustomStringConvertible {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Server: ヘッダーフィールドの型です。
テスト側で受け取って読み取りたいのでパーサ付き。

public init(
name: String = serverName,
version: String = cartonVersion,
pid: Int32
) {
self.name = name
self.version = version
self.pid = pid
}

public var name: String
public var version: String
public var pid: Int32

public var description: String {
"\(name)/\(version) (PID \(pid))"
}

private static let regex = #/([\w ]+)/([\w\.]+) \(PID (\d+)\)/#

public static func parse(_ string: String) throws -> ServerNameField {
guard let m = try regex.wholeMatch(in: string),
let pid = Int32(m.output.3) else {
throw CartonCoreError("invalid server name: \(string)")
}

let name = String(m.output.1)
let version = String(m.output.2)
return ServerNameField(name: name, version: version, pid: pid)
}
}

/// Used for decoding `Event` values sent from the WebSocket client.
private let decoder = JSONDecoder()

Expand All @@ -131,6 +168,8 @@ public actor Server {

private let configuration: Configuration

private let serverName: ServerNameField

public struct Configuration {
let builder: BuilderProtocol?
let mainWasmPath: AbsolutePath
Expand All @@ -141,6 +180,7 @@ public actor Server {
let customIndexPath: AbsolutePath?
let resourcesPaths: [String]
let entrypoint: Entrypoint
let pid: Int32?
let terminal: InteractiveWriter

public init(
Expand All @@ -153,6 +193,7 @@ public actor Server {
customIndexPath: AbsolutePath?,
resourcesPaths: [String],
entrypoint: Entrypoint,
pid: Int32?,
terminal: InteractiveWriter
) {
self.builder = builder
Expand All @@ -164,6 +205,7 @@ public actor Server {
self.customIndexPath = customIndexPath
self.resourcesPaths = resourcesPaths
self.entrypoint = entrypoint
self.pid = pid
self.terminal = terminal
}

Expand All @@ -184,6 +226,9 @@ public actor Server {
self.localURL = localURL
watcher = nil
self.configuration = configuration
self.serverName = ServerNameField(
pid: configuration.pid ?? ProcessInfo.processInfo.processIdentifier
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

指定がなければ自身のプロセス番号を広告します。
frontendを直接起動する場合にこの挙動になります。

)

guard let builder = configuration.builder else {
return
Expand Down Expand Up @@ -293,7 +338,8 @@ public actor Server {
mainWasmPath: configuration.mainWasmPath,
customIndexPath: configuration.customIndexPath,
resourcesPaths: configuration.resourcesPaths,
entrypoint: configuration.entrypoint
entrypoint: configuration.entrypoint,
serverName: serverName.description
)
let channel = try await ServerBootstrap(group: group)
// Specify backlog and enable SO_REUSEADDR for the server itself
Expand Down
2 changes: 2 additions & 0 deletions Sources/CartonKit/Server/ServerHTTPHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ final class ServerHTTPHandler: ChannelInboundHandler, RemovableChannelHandler {
let customIndexPath: AbsolutePath?
let resourcesPaths: [String]
let entrypoint: Entrypoint
let serverName: String
}

let configuration: Configuration
Expand Down Expand Up @@ -93,6 +94,7 @@ final class ServerHTTPHandler: ChannelInboundHandler, RemovableChannelHandler {
self.responseBody = response.body

var headers = HTTPHeaders()
headers.add(name: "Server", value: configuration.serverName)
headers.add(name: "Content-Type", value: response.contentType)
headers.add(name: "Content-Length", value: String(response.body.readableBytes))
headers.add(name: "Connection", value: "close")
Expand Down
18 changes: 18 additions & 0 deletions Tests/CartonCommandTests/CommandTestHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import ArgumentParser
import XCTest
import CartonHelpers
import CartonKit

#if canImport(FoundationNetworking)
import FoundationNetworking
Expand Down Expand Up @@ -91,8 +92,10 @@ func swiftRunProcess(
stdout: { (chunk) in
outputBuffer += chunk
stdoutStream.write(sequence: chunk)
stdoutStream.flush()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここxctest の実行時に全然出力が渡ってきませんでした。
flushしたらちゃんと流れてくるようになって、
テスト実行中にリアルタイムな出力が見えるようになりました。

}, stderr: { (chunk) in
stderrStream.write(sequence: chunk)
stderrStream.flush()
},
redirectStderr: false
)
Expand Down Expand Up @@ -134,3 +137,18 @@ func fetchWebContent(at url: URL, timeout: Duration) async throws -> (response:

return (response: response, body: body)
}

func checkServerNameField(response: HTTPURLResponse, expectedPID: Int32) throws {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

レスポンスヘッダの中のPIDを検証するユーティリティ関数です。

guard let string = response.value(forHTTPHeaderField: "Server") else {
throw CommandTestError("no Server header")
}
let field = try Server.ServerNameField.parse(string)

guard field.name == Server.serverName else {
throw CommandTestError("invalid server name: \(field)")
}

guard field.pid == expectedPID else {
throw CommandTestError("Expected PID \(expectedPID) but got PID \(field.pid).")
}
}
1 change: 1 addition & 0 deletions Tests/CartonCommandTests/DevCommandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

通信相手はちゃんとテスト対象のプロセスだったのか検証します。


let expectedHtml = """
<!DOCTYPE html>
Expand Down
Loading