From 6f2ba042c186019bd3f0792a49b33d20a7a10b52 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 16 Mar 2025 04:58:19 +0000 Subject: [PATCH 1/2] Add `--enable-code-coverage` --- Examples/Testing/.gitignore | 8 -- Examples/Testing/Package.swift | 4 - Examples/Testing/README.md | 33 +++++ Makefile | 10 +- Plugins/PackageToJS/Sources/PackageToJS.swift | 116 +++++++++++++++--- .../Sources/PackageToJSPlugin.swift | 71 +++++------ Plugins/PackageToJS/Templates/bin/test.js | 19 +++ .../PackageToJS/Templates/instantiate.d.ts | 7 ++ .../PackageToJS/Templates/platforms/node.d.ts | 1 + .../PackageToJS/Templates/platforms/node.js | 44 ++++++- Plugins/PackageToJS/Tests/ExampleTests.swift | 30 ++++- 11 files changed, 271 insertions(+), 72 deletions(-) delete mode 100644 Examples/Testing/.gitignore create mode 100644 Examples/Testing/README.md diff --git a/Examples/Testing/.gitignore b/Examples/Testing/.gitignore deleted file mode 100644 index 0023a5340..000000000 --- a/Examples/Testing/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/Examples/Testing/Package.swift b/Examples/Testing/Package.swift index 2e997652f..6dd492cd1 100644 --- a/Examples/Testing/Package.swift +++ b/Examples/Testing/Package.swift @@ -1,20 +1,16 @@ // swift-tools-version: 6.0 -// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Counter", products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "Counter", targets: ["Counter"]), ], dependencies: [.package(name: "JavaScriptKit", path: "../../")], targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. .target( name: "Counter", dependencies: [ diff --git a/Examples/Testing/README.md b/Examples/Testing/README.md new file mode 100644 index 000000000..2f28357a6 --- /dev/null +++ b/Examples/Testing/README.md @@ -0,0 +1,33 @@ +# Testing example + +This example demonstrates how to write and run tests for Swift code compiled to WebAssembly using JavaScriptKit. + +## Running Tests + +To run the tests, use the following command: + +```console +swift package --disable-sandbox --swift-sdk wasm32-unknown-wasi js test +``` + +## Code Coverage + +To generate and view code coverage reports: + +1. Run tests with code coverage enabled: + +```console +swift package --disable-sandbox --swift-sdk wasm32-unknown-wasi js test --enable-code-coverage +``` + +2. Generate HTML coverage report: + +```console +llvm-cov show -instr-profile=.build/plugins/PackageToJS/outputs/PackageTests/default.profdata --format=html .build/plugins/PackageToJS/outputs/PackageTests/main.wasm -o .build/coverage/html Sources +``` + +3. Serve and view the coverage report: + +```console +npx serve .build/coverage/html +``` diff --git a/Makefile b/Makefile index a2ad1526a..3764ed06a 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,11 @@ unittest: @echo Running unit tests swift package --swift-sdk "$(SWIFT_SDK_ID)" \ --disable-sandbox \ - -Xlinker --stack-first \ - -Xlinker --global-base=524288 \ - -Xlinker -z \ - -Xlinker stack-size=524288 \ - js test --prelude ./Tests/prelude.mjs + -Xlinker --stack-first \ + -Xlinker --global-base=524288 \ + -Xlinker -z \ + -Xlinker stack-size=524288 \ + js test --prelude ./Tests/prelude.mjs .PHONY: benchmark_setup benchmark_setup: diff --git a/Plugins/PackageToJS/Sources/PackageToJS.swift b/Plugins/PackageToJS/Sources/PackageToJS.swift index e54a2a910..1949527dc 100644 --- a/Plugins/PackageToJS/Sources/PackageToJS.swift +++ b/Plugins/PackageToJS/Sources/PackageToJS.swift @@ -6,10 +6,12 @@ struct PackageToJS { var outputPath: String? /// Name of the package (default: lowercased Package.swift name) var packageName: String? - /// Whether to explain the build plan + /// Whether to explain the build plan (default: false) var explain: Bool = false - /// Whether to use CDN for dependency packages + /// Whether to use CDN for dependency packages (default: false) var useCDN: Bool = false + /// Whether to enable code coverage collection (default: false) + var enableCodeCoverage: Bool = false } struct BuildOptions { @@ -51,7 +53,69 @@ struct PackageToJS { return (buildConfiguration, triple) } - static func runTest(testRunner: URL, currentDirectoryURL: URL, extraArguments: [String]) throws { + static func runTest(testRunner: URL, currentDirectoryURL: URL, outputDir: URL, testOptions: TestOptions) throws { + var testJsArguments: [String] = [] + var testLibraryArguments: [String] = [] + if testOptions.listTests { + testLibraryArguments += ["--list-tests"] + } + if let prelude = testOptions.prelude { + let preludeURL = URL(fileURLWithPath: prelude, relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath)) + testJsArguments += ["--prelude", preludeURL.path] + } + if let environment = testOptions.environment { + testJsArguments += ["--environment", environment] + } + if testOptions.inspect { + testJsArguments += ["--inspect"] + } + + let xctestCoverageFile = outputDir.appending(path: "XCTest.profraw") + do { + var extraArguments = testJsArguments + if testOptions.packageOptions.enableCodeCoverage { + extraArguments += ["--coverage-file", xctestCoverageFile.path] + } + extraArguments += ["--"] + extraArguments += testLibraryArguments + extraArguments += testOptions.filter + + try PackageToJS.runSingleTestingLibrary( + testRunner: testRunner, currentDirectoryURL: currentDirectoryURL, + extraArguments: extraArguments + ) + } + let swiftTestingCoverageFile = outputDir.appending(path: "SwiftTesting.profraw") + do { + var extraArguments = testJsArguments + if testOptions.packageOptions.enableCodeCoverage { + extraArguments += ["--coverage-file", swiftTestingCoverageFile.path] + } + extraArguments += ["--", "--testing-library", "swift-testing"] + extraArguments += testLibraryArguments + extraArguments += testOptions.filter.flatMap { ["--filter", $0] } + + try PackageToJS.runSingleTestingLibrary( + testRunner: testRunner, currentDirectoryURL: currentDirectoryURL, + extraArguments: extraArguments + ) + } + + if testOptions.packageOptions.enableCodeCoverage { + let profrawFiles = [xctestCoverageFile, swiftTestingCoverageFile].filter { FileManager.default.fileExists(atPath: $0.path) } + do { + try PackageToJS.postProcessCoverageFiles(outputDir: outputDir, profrawFiles: profrawFiles) + } catch { + print("Warning: Failed to merge coverage files: \(error)") + } + } + } + + static func runSingleTestingLibrary( + testRunner: URL, + currentDirectoryURL: URL, + extraArguments: [String] + ) throws { let node = try which("node") let arguments = ["--experimental-wasi-unstable-preview1", testRunner.path] + extraArguments print("Running test...") @@ -70,6 +134,18 @@ struct PackageToJS { throw PackageToJSError("Test failed with status \(task.terminationStatus)") } } + + static func postProcessCoverageFiles(outputDir: URL, profrawFiles: [URL]) throws { + let mergedCoverageFile = outputDir.appending(path: "default.profdata") + do { + // Merge the coverage files by llvm-profdata + let arguments = ["merge", "-sparse", "-output", mergedCoverageFile.path] + profrawFiles.map { $0.path } + let llvmProfdata = try which("llvm-profdata") + logCommandExecution(llvmProfdata.path, arguments) + try runCommand(llvmProfdata, arguments) + print("Saved profile data to \(mergedCoverageFile.path)") + } + } } struct PackageToJSError: Swift.Error, CustomStringConvertible { @@ -140,21 +216,19 @@ final class DefaultPackagingSystem: PackagingSystem { } try runCommand(wasmOpt, arguments + ["-o", output, input]) } - - private func runCommand(_ command: URL, _ arguments: [String]) throws { - let task = Process() - task.executableURL = command - task.arguments = arguments - task.currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) - try task.run() - task.waitUntilExit() - guard task.terminationStatus == 0 else { - throw PackageToJSError("Command failed with status \(task.terminationStatus)") - } - } } internal func which(_ executable: String) throws -> URL { + do { + // Check overriding environment variable + let envVariable = executable.uppercased().replacingOccurrences(of: "-", with: "_") + "_PATH" + if let path = ProcessInfo.processInfo.environment[envVariable] { + let url = URL(fileURLWithPath: path).appendingPathComponent(executable) + if FileManager.default.isExecutableFile(atPath: url.path) { + return url + } + } + } let pathSeparator: Character #if os(Windows) pathSeparator = ";" @@ -171,6 +245,18 @@ internal func which(_ executable: String) throws -> URL { throw PackageToJSError("Executable \(executable) not found in PATH") } +private func runCommand(_ command: URL, _ arguments: [String]) throws { + let task = Process() + task.executableURL = command + task.arguments = arguments + task.currentDirectoryURL = URL(fileURLWithPath: FileManager.default.currentDirectoryPath) + try task.run() + task.waitUntilExit() + guard task.terminationStatus == 0 else { + throw PackageToJSError("Command failed with status \(task.terminationStatus)") + } +} + /// Plans the build for packaging. struct PackagingPlanner { /// The options for packaging diff --git a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift index 4074a8218..853ea5020 100644 --- a/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift +++ b/Plugins/PackageToJS/Sources/PackageToJSPlugin.swift @@ -93,7 +93,9 @@ struct PackageToJSPlugin: CommandPlugin { // Build products let productName = try buildOptions.product ?? deriveDefaultProduct(package: context.package) let build = try buildWasm( - productName: productName, context: context) + productName: productName, context: context, + enableCodeCoverage: buildOptions.packageOptions.enableCodeCoverage + ) guard build.succeeded else { reportBuildFailure(build, arguments) exit(1) @@ -145,7 +147,9 @@ struct PackageToJSPlugin: CommandPlugin { let productName = "\(context.package.displayName)PackageTests" let build = try buildWasm( - productName: productName, context: context) + productName: productName, context: context, + enableCodeCoverage: testOptions.packageOptions.enableCodeCoverage + ) guard build.succeeded else { reportBuildFailure(build, arguments) exit(1) @@ -198,36 +202,18 @@ struct PackageToJSPlugin: CommandPlugin { try make.build(output: rootTask, scope: scope) print("Packaging tests finished") - let testRunner = scope.resolve(path: binDir.appending(path: "test.js")) if !testOptions.buildOnly { - var testJsArguments: [String] = [] - var testFrameworkArguments: [String] = [] - if testOptions.listTests { - testFrameworkArguments += ["--list-tests"] - } - if let prelude = testOptions.prelude { - let preludeURL = URL(fileURLWithPath: prelude, relativeTo: URL(fileURLWithPath: FileManager.default.currentDirectoryPath)) - testJsArguments += ["--prelude", preludeURL.path] - } - if let environment = testOptions.environment { - testJsArguments += ["--environment", environment] - } - if testOptions.inspect { - testJsArguments += ["--inspect"] - } - try PackageToJS.runTest( - testRunner: testRunner, currentDirectoryURL: context.pluginWorkDirectoryURL, - extraArguments: testJsArguments + ["--"] + testFrameworkArguments + testOptions.filter - ) + let testRunner = scope.resolve(path: binDir.appending(path: "test.js")) try PackageToJS.runTest( - testRunner: testRunner, currentDirectoryURL: context.pluginWorkDirectoryURL, - extraArguments: testJsArguments + ["--", "--testing-library", "swift-testing"] + testFrameworkArguments - + testOptions.filter.flatMap { ["--filter", $0] } + testRunner: testRunner, + currentDirectoryURL: context.pluginWorkDirectoryURL, + outputDir: outputDir, + testOptions: testOptions ) } } - private func buildWasm(productName: String, context: PluginContext) throws + private func buildWasm(productName: String, context: PluginContext, enableCodeCoverage: Bool) throws -> PackageManager.BuildResult { var parameters = PackageManager.BuildParameters( @@ -248,6 +234,12 @@ struct PackageToJSPlugin: CommandPlugin { parameters.otherLinkerFlags = [ "--export-if-defined=__main_argc_argv" ] + + // Enable code coverage options if requested + if enableCodeCoverage { + parameters.otherSwiftcFlags += ["-profile-coverage-mapping", "-profile-generate"] + parameters.otherCFlags += ["-fprofile-instr-generate", "-fcoverage-mapping"] + } } return try self.packageManager.build(.product(productName), parameters: parameters) } @@ -292,8 +284,9 @@ extension PackageToJS.PackageOptions { let packageName = extractor.extractOption(named: "package-name").last let explain = extractor.extractFlag(named: "explain") let useCDN = extractor.extractFlag(named: "use-cdn") + let enableCodeCoverage = extractor.extractFlag(named: "enable-code-coverage") return PackageToJS.PackageOptions( - outputPath: outputPath, packageName: packageName, explain: explain != 0, useCDN: useCDN != 0 + outputPath: outputPath, packageName: packageName, explain: explain != 0, useCDN: useCDN != 0, enableCodeCoverage: enableCodeCoverage != 0 ) } } @@ -314,12 +307,14 @@ extension PackageToJS.BuildOptions { USAGE: swift package --swift-sdk [SwiftPM options] PackageToJS [options] [subcommand] OPTIONS: - --product Product to build (default: executable target if there's only one) - --output Path to the output directory (default: .build/plugins/PackageToJS/outputs/Package) - --package-name Name of the package (default: lowercased Package.swift name) - --explain Whether to explain the build plan - --split-debug Whether to split debug information into a separate .wasm.debug file (default: false) - --no-optimize Whether to disable wasm-opt optimization (default: false) + --product Product to build (default: executable target if there's only one) + --output Path to the output directory (default: .build/plugins/PackageToJS/outputs/Package) + --package-name Name of the package (default: lowercased Package.swift name) + --explain Whether to explain the build plan (default: false) + --split-debug Whether to split debug information into a separate .wasm.debug file (default: false) + --no-optimize Whether to disable wasm-opt optimization (default: false) + --use-cdn Whether to use CDN for dependency packages (default: false) + --enable-code-coverage Whether to enable code coverage collection (default: false) SUBCOMMANDS: test Builds and runs tests @@ -365,10 +360,12 @@ extension PackageToJS.TestOptions { USAGE: swift package --swift-sdk [SwiftPM options] PackageToJS test [options] OPTIONS: - --build-only Whether to build only (default: false) - --prelude Path to the prelude script - --environment The environment to use for the tests - --inspect Whether to run tests in the browser with inspector enabled + --build-only Whether to build only (default: false) + --prelude Path to the prelude script + --environment The environment to use for the tests + --inspect Whether to run tests in the browser with inspector enabled + --use-cdn Whether to use CDN for dependency packages (default: false) + --enable-code-coverage Whether to enable code coverage collection (default: false) EXAMPLES: $ swift package --swift-sdk wasm32-unknown-wasi plugin js test diff --git a/Plugins/PackageToJS/Templates/bin/test.js b/Plugins/PackageToJS/Templates/bin/test.js index 5fed17359..b31d82086 100644 --- a/Plugins/PackageToJS/Templates/bin/test.js +++ b/Plugins/PackageToJS/Templates/bin/test.js @@ -3,6 +3,7 @@ import { instantiate } from "../instantiate.js" import { testBrowser } from "../test.js" import { parseArgs } from "node:util" import path from "node:path" +import { writeFileSync } from "node:fs" function splitArgs(args) { // Split arguments into two parts by "--" @@ -31,6 +32,7 @@ const args = parseArgs({ prelude: { type: "string" }, environment: { type: "string" }, inspect: { type: "boolean" }, + "coverage-file": { type: "string" }, }, }) @@ -38,6 +40,17 @@ const harnesses = { node: async ({ preludeScript }) => { let options = await nodePlatform.defaultNodeSetup({ args: testFrameworkArgs, + onExit: (code) => { + if (code !== 0) { return } + // Extract the coverage file from the wasm module + const filePath = "default.profraw" + const destinationPath = args.values["coverage-file"] ?? filePath + const profraw = options.wasi.extractFile?.(filePath) + if (profraw) { + console.log(`Saved ${filePath} to ${destinationPath}`); + writeFileSync(destinationPath, profraw); + } + }, /* #if USE_SHARED_MEMORY */ spawnWorker: nodePlatform.createDefaultWorkerFactory(preludeScript) /* #endif */ @@ -52,6 +65,12 @@ const harnesses = { await instantiate(options) } catch (e) { if (e instanceof WebAssembly.CompileError) { + // Check Node.js major version + const nodeVersion = process.version.split(".")[0] + const minNodeVersion = 20 + if (nodeVersion < minNodeVersion) { + console.error(`Hint: Node.js version ${nodeVersion} is not supported, please use version ${minNodeVersion} or later.`) + } } throw e } diff --git a/Plugins/PackageToJS/Templates/instantiate.d.ts b/Plugins/PackageToJS/Templates/instantiate.d.ts index f813b5489..424d35175 100644 --- a/Plugins/PackageToJS/Templates/instantiate.d.ts +++ b/Plugins/PackageToJS/Templates/instantiate.d.ts @@ -42,6 +42,13 @@ export interface WASI { * @param instance - The instance of the WebAssembly module */ setInstance(instance: WebAssembly.Instance): void + /** + * Extract a file from the WASI filesystem + * + * @param path - The path to the file to extract + * @returns The data of the file if it was extracted, undefined otherwise + */ + extractFile?(path: string): Uint8Array | undefined } export type ModuleSource = WebAssembly.Module | ArrayBufferView | ArrayBuffer | Response | PromiseLike diff --git a/Plugins/PackageToJS/Templates/platforms/node.d.ts b/Plugins/PackageToJS/Templates/platforms/node.d.ts index 433f97ad6..636ad0eea 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.d.ts +++ b/Plugins/PackageToJS/Templates/platforms/node.d.ts @@ -5,6 +5,7 @@ export async function defaultNodeSetup(options: { /* #if IS_WASI */ args?: string[], /* #endif */ + onExit?: (code: number) => void, /* #if USE_SHARED_MEMORY */ spawnWorker: (module: WebAssembly.Module, memory: WebAssembly.Memory, startArg: any) => Worker, /* #endif */ diff --git a/Plugins/PackageToJS/Templates/platforms/node.js b/Plugins/PackageToJS/Templates/platforms/node.js index a8bb638bc..c45bdf354 100644 --- a/Plugins/PackageToJS/Templates/platforms/node.js +++ b/Plugins/PackageToJS/Templates/platforms/node.js @@ -3,7 +3,7 @@ import { fileURLToPath } from "node:url"; import { Worker, parentPort } from "node:worker_threads"; import { MODULE_PATH /* #if USE_SHARED_MEMORY */, MEMORY_TYPE /* #endif */} from "../instantiate.js" /* #if IS_WASI */ -import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory } from '@bjorn3/browser_wasi_shim'; +import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory, Directory, Inode } from '@bjorn3/browser_wasi_shim'; /* #endif */ /* #if USE_SHARED_MEMORY */ @@ -119,6 +119,7 @@ export async function defaultNodeSetup(options) { const { readFile } = await import("node:fs/promises") const args = options.args ?? process.argv.slice(2) + const rootFs = new Map(); const wasi = new WASI(/* args */[MODULE_PATH, ...args], /* env */[], /* fd */[ new OpenFile(new File([])), // stdin ConsoleStdout.lineBuffered((stdout) => { @@ -127,7 +128,7 @@ export async function defaultNodeSetup(options) { ConsoleStdout.lineBuffered((stderr) => { console.error(stderr); }), - new PreopenDirectory("/", new Map()), + new PreopenDirectory("/", rootFs), ], { debug: false }) const pkgDir = path.dirname(path.dirname(fileURLToPath(import.meta.url))) const module = await WebAssembly.compile(await readFile(path.join(pkgDir, MODULE_PATH))) @@ -143,10 +144,49 @@ export async function defaultNodeSetup(options) { wasi: Object.assign(wasi, { setInstance(instance) { wasi.inst = instance; + }, + /** + * @param {string} path + * @returns {Uint8Array | undefined} + */ + extractFile(path) { + /** + * @param {Map} parent + * @param {string[]} components + * @param {number} index + * @returns {Inode | undefined} + */ + const getFile = (parent, components, index) => { + const name = components[index]; + const entry = parent.get(name); + if (entry === undefined) { + return undefined; + } + if (index === components.length - 1) { + return entry; + } + if (entry instanceof Directory) { + return getFile(entry.contents, components, index + 1); + } + throw new Error(`Expected directory at ${components.slice(0, index).join("/")}`); + } + + const components = path.split("/"); + const file = getFile(rootFs, components, 0); + if (file === undefined) { + return undefined; + } + if (file instanceof File) { + return file.data; + } + return undefined; } }), addToCoreImports(importObject) { importObject["wasi_snapshot_preview1"]["proc_exit"] = (code) => { + if (options.onExit) { + options.onExit(code); + } process.exit(code); } }, diff --git a/Plugins/PackageToJS/Tests/ExampleTests.swift b/Plugins/PackageToJS/Tests/ExampleTests.swift index be5d8e60b..743504e3e 100644 --- a/Plugins/PackageToJS/Tests/ExampleTests.swift +++ b/Plugins/PackageToJS/Tests/ExampleTests.swift @@ -42,6 +42,10 @@ extension Trait where Self == ConditionTrait { ProcessInfo.processInfo.environment["SWIFT_SDK_ID"] } + static func getSwiftPath() -> String? { + ProcessInfo.processInfo.environment["SWIFT_PATH"] + } + static let repoPath = URL(fileURLWithPath: #filePath) .deletingLastPathComponent() .deletingLastPathComponent() @@ -97,7 +101,7 @@ extension Trait where Self == ConditionTrait { process.executableURL = URL( fileURLWithPath: "swift", relativeTo: URL( - fileURLWithPath: ProcessInfo.processInfo.environment["SWIFT_PATH"]!)) + fileURLWithPath: try #require(Self.getSwiftPath()))) process.arguments = args process.currentDirectoryURL = destination.appending(path: path) process.environment = ProcessInfo.processInfo.environment.merging(env) { _, new in @@ -148,6 +152,30 @@ extension Trait where Self == ConditionTrait { } } + #if compiler(>=6.1) + @Test(.requireSwiftSDK) + func testingWithCoverage() throws { + let swiftSDKID = try #require(Self.getSwiftSDKID()) + let swiftPath = try #require(Self.getSwiftPath()) + try withPackage(at: "Examples/Testing") { packageDir, runSwift in + try runSwift(["package", "--swift-sdk", swiftSDKID, "js", "test", "--enable-code-coverage"], [ + "LLVM_PROFDATA_PATH": URL(fileURLWithPath: swiftPath).appending(path: "llvm-profdata").path + ]) + do { + let llvmCov = try which("llvm-cov") + let process = Process() + process.executableURL = llvmCov + let profdata = packageDir.appending(path: ".build/plugins/PackageToJS/outputs/PackageTests/default.profdata") + let wasm = packageDir.appending(path: ".build/plugins/PackageToJS/outputs/PackageTests/main.wasm") + process.arguments = ["report", "-instr-profile", profdata.path, wasm.path] + process.standardOutput = FileHandle.nullDevice + try process.run() + process.waitUntilExit() + } + } + } + #endif + @Test(.requireSwiftSDK(triple: "wasm32-unknown-wasip1-threads")) func multithreading() throws { let swiftSDKID = try #require(Self.getSwiftSDKID()) From 2ce221bd9daa19382e0882568841dd1a2663ddd3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Sun, 16 Mar 2025 06:04:11 +0000 Subject: [PATCH 2/2] test: Relax sleep duration checks --- Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift b/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift index 5d610aa48..0609232a0 100644 --- a/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift +++ b/Tests/JavaScriptEventLoopTests/JavaScriptEventLoopTests.swift @@ -68,13 +68,13 @@ final class JavaScriptEventLoopTests: XCTestCase { let sleepDiff = try await measureTime { try await Task.sleep(nanoseconds: 200_000_000) } - XCTAssertGreaterThanOrEqual(sleepDiff, 200) + XCTAssertGreaterThanOrEqual(sleepDiff, 150) // Test shorter sleep duration let shortSleepDiff = try await measureTime { try await Task.sleep(nanoseconds: 100_000_000) } - XCTAssertGreaterThanOrEqual(shortSleepDiff, 100) + XCTAssertGreaterThanOrEqual(shortSleepDiff, 50) } func testTaskPriority() async throws {