From c4a8cceb5b09be6edf8ba1aac905092cdbcd55d5 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Fri, 27 Sep 2024 15:16:28 +0000 Subject: [PATCH 01/10] some modifications for easier debugging --- .../Fuzzilli/Compiler/JavaScriptParser.swift | 1 + Sources/Fuzzilli/Compiler/Parser/parser.js | 3 +- Sources/FuzzilliCli/Profiles/Profile.swift | 1 + Sources/REPRLRun/main.swift | 64 +++++++++++-------- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/JavaScriptParser.swift b/Sources/Fuzzilli/Compiler/JavaScriptParser.swift index f6a12f15d..b4874afc6 100644 --- a/Sources/Fuzzilli/Compiler/JavaScriptParser.swift +++ b/Sources/Fuzzilli/Compiler/JavaScriptParser.swift @@ -43,6 +43,7 @@ public class JavaScriptParser { do { try runParserScript(withArguments: []) } catch { + return nil } } diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index f42fe00dd..c5229ff63 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -34,7 +34,8 @@ function tryReadFile(path) { // Parse the given JavaScript script and return an AST compatible with Fuzzilli's protobuf-based AST format. function parse(script, proto) { - let ast = Parser.parse(script, { plugins: ["v8intrinsic"] }); + let ast = Parser.parse(script, { plugins: ["v8intrinsic"], allowImportExportEverywhere: true + }); function assertNoError(err) { if (err) throw err; diff --git a/Sources/FuzzilliCli/Profiles/Profile.swift b/Sources/FuzzilliCli/Profiles/Profile.swift index d3a0038c4..4a777832f 100644 --- a/Sources/FuzzilliCli/Profiles/Profile.swift +++ b/Sources/FuzzilliCli/Profiles/Profile.swift @@ -53,4 +53,5 @@ let profiles = [ "v8holefuzzing": v8HoleFuzzingProfile, "serenity": serenityProfile, "njs": njsProfile, + "workerd": workerdProfile, ] diff --git a/Sources/REPRLRun/main.swift b/Sources/REPRLRun/main.swift index 41fa352f6..f8ec75bf0 100644 --- a/Sources/REPRLRun/main.swift +++ b/Sources/REPRLRun/main.swift @@ -1,21 +1,8 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - import Foundation import libreprl func convertToCArray(_ array: [String]) -> UnsafeMutablePointer?> { + print("Converting array to C array: \(array)") let buffer = UnsafeMutablePointer?>.allocate(capacity: array.count + 1) for (i, str) in array.enumerated() { buffer[i] = UnsafePointer(str.withCString(strdup)) @@ -24,12 +11,25 @@ func convertToCArray(_ array: [String]) -> UnsafeMutablePointer (status: Int32, exec_time: UInt64) { var exec_time: UInt64 = 0 var status: Int32 = 0 + print("Executing script: \(script)") script.withCString { ptr in status = reprl_execute(ctx, ptr, UInt64(script.utf8.count), 1_000_000, &exec_time, 0) } + print("Execution result: status = \(status), exec_time = \(exec_time)") + printREPRLOutput(ctx) return (status, exec_time) } @@ -56,29 +65,32 @@ func runREPRLTests() { var numFailures = 0 func expect_success(_ code: String) { + print("Expecting success for code: \(code)") if execute(code).status != 0 { print("Execution of \"\(code)\" failed") numFailures += 1 + } else { + print("Success for code: \(code)") } } func expect_failure(_ code: String) { + print("Expecting failure for code: \(code)") if execute(code).status == 0 { print("Execution of \"\(code)\" unexpectedly succeeded") numFailures += 1 + } else { + print("Failure as expected for code: \(code)") } } expect_success("42") expect_failure("throw 42") - // Verify that existing state is property reset between executions expect_success("globalProp = 42; Object.prototype.foo = \"bar\";") expect_success("if (typeof(globalProp) !== 'undefined') throw 'failure'") expect_success("if (typeof(({}).foo) !== 'undefined') throw 'failure'") - // Verify that rejected promises are properly reset between executions - // Only if async functions are available if execute("async function foo() {}").status == 0 { expect_failure("async function fail() { throw 42; }; fail()") expect_success("42") @@ -89,17 +101,19 @@ func runREPRLTests() { if numFailures == 0 { print("All tests passed!") } else { - print("Not all tests passed. That means REPRL support likely isn't properly implemented in the target engine") + print("Not all tests passed. REPRL support may not be properly implemented.") } } -// Check whether REPRL works at all +print("Checking if REPRL works...") if execute("").status != 0 { - print("Script execution failed, REPRL support does not appear to be working") + print("Initial script execution failed, REPRL support does not appear to be working") + printREPRLOutput(ctx) exit(1) +} else { + print("Initial REPRL check passed.") } -// Run a couple of tests now runREPRLTests() print("Enter code to run, then hit enter to execute it") @@ -110,15 +124,15 @@ while true { break } + print("Executing user input code...") let (status, exec_time) = execute(code) if status < 0 { print("Error during script execution: \(String(cString: reprl_get_last_error(ctx))). REPRL support in the target probably isn't working correctly...") + printREPRLOutput(ctx) continue } print("Execution finished with status \(status) (signaled: \(RIFSIGNALED(status) != 0), timed out: \(RIFTIMEDOUT(status) != 0)) and took \(exec_time / 1000)ms") - print("========== Fuzzout ==========\n\(String(cString: reprl_fetch_fuzzout(ctx)))") - print("========== Stdout ==========\n\(String(cString: reprl_fetch_stdout(ctx)))") - print("========== Stderr ==========\n\(String(cString: reprl_fetch_stderr(ctx)))") } + From a11554c7a1d20381d101d4e27139efb89328be39 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Tue, 10 Jun 2025 11:41:22 +0000 Subject: [PATCH 02/10] profile --- .../FuzzilliCli/Profiles/WorkerdProfile.swift | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 Sources/FuzzilliCli/Profiles/WorkerdProfile.swift diff --git a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift new file mode 100644 index 000000000..6db61243f --- /dev/null +++ b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift @@ -0,0 +1,57 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Fuzzilli + +let workerdProfile = Profile( + processArgs: { randomize in + ["--reprl-fuzzilli"] + }, + + processEnv: [:], + + maxExecsBeforeRespawn: 1000, + + timeout: 250, + + codePrefix: """ + """, + + codeSuffix: """ + """, + + ecmaVersion: ECMAScriptVersion.es5, + + startupTests: [ + // Check that the fuzzilli integration is available. + ("fuzzilli('FUZZILLI_PRINT', 'test')", .shouldSucceed), + + // Check that common crash types are detected. + ("fuzzilli('FUZZILLI_CRASH', 0)", .shouldCrash), + ], + + additionalCodeGenerators: [], + + additionalProgramTemplates: WeightedList([]), + + disabledCodeGenerators: [], + + disabledMutators: [], + + additionalBuiltins: [:], + + additionalObjectGroups: [], + + optionalPostProcessor: nil +) From ae99c0e20793b171e01f4e9b36be2f1f5889c0d7 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Tue, 5 Aug 2025 13:28:25 +0000 Subject: [PATCH 03/10] add parser support for imports/exports in code --- Sources/Fuzzilli/Compiler/Parser/parser.js | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index c5229ff63..52f2b16ac 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -364,6 +364,63 @@ function parse(script, proto) { switchCase.consequent = node.consequent.map(visitStatement); return switchCase; } + case 'ImportDeclaration': { + if (node.specifiers?.length > 0) { + const declarations = node.specifiers.map(spec => make('VariableDeclarator',{ + name: spec.local.name, + })); + return makeStatement('VariableDeclaration', { + kind: 2, // make this const for now + declarations, + }); + } + //fallback + return makeStatement('EmptyStatement', {}); + } + case 'ExportNamedDeclaration': { + if (node.declaration) { + return visitStatement(node.declaration); + } + + if (node.specifiers?.length > 0) { + const declarations = node.specifiers.map(spec => make('VariableDeclarator',{ + name: spec.exported.name, + })); + return makeStatement('VariableDeclaration', { + kind: 2, + declarations, + }); + } + //fallback + return makeStatement('EmptyStatement', {}); + } + case 'ExportDefaultDeclaration': { + const decl = node.declaration; + if (decl) { + if (decl.type === 'FunctionDeclaration' || + decl.type === 'ClassDeclaration' || + decl.type === 'VariableDeclaration') { + return visitStatement(node.declaration); + } + // treat as Expression + return makeStatement('ExpressionStatement', { + expression: visitExpression(decl), + }); + } + return makeStatement('EmptyStatement', {}); + } + case 'ExportAllDeclaration': { + if (node.exported) { + const declarations = [make('VariableDeclarator', { + name: node.exported.name + })]; + return makeStatement('VariableDeclaration', { + kind: 2, + declarations, + }) + } + return makeStatement('EmptyStatement', {}); + } default: { throw "Unhandled node type " + node.type; } From 20850d4c1abe12aaa578782f9bc2913fca06af63 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Wed, 13 Aug 2025 14:09:48 +0000 Subject: [PATCH 04/10] allow await outside functions --- Sources/Fuzzilli/Compiler/Parser/parser.js | 60 +--------------------- Sources/Fuzzilli/Fuzzer.swift | 10 +++- 2 files changed, 10 insertions(+), 60 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index 52f2b16ac..211054bee 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -34,8 +34,7 @@ function tryReadFile(path) { // Parse the given JavaScript script and return an AST compatible with Fuzzilli's protobuf-based AST format. function parse(script, proto) { - let ast = Parser.parse(script, { plugins: ["v8intrinsic"], allowImportExportEverywhere: true - }); + let ast = Parser.parse(script, { allowAwaitOutsideFunction: true, plugins: ["topLevelAwait","v8intrinsic"] }); function assertNoError(err) { if (err) throw err; @@ -364,63 +363,6 @@ function parse(script, proto) { switchCase.consequent = node.consequent.map(visitStatement); return switchCase; } - case 'ImportDeclaration': { - if (node.specifiers?.length > 0) { - const declarations = node.specifiers.map(spec => make('VariableDeclarator',{ - name: spec.local.name, - })); - return makeStatement('VariableDeclaration', { - kind: 2, // make this const for now - declarations, - }); - } - //fallback - return makeStatement('EmptyStatement', {}); - } - case 'ExportNamedDeclaration': { - if (node.declaration) { - return visitStatement(node.declaration); - } - - if (node.specifiers?.length > 0) { - const declarations = node.specifiers.map(spec => make('VariableDeclarator',{ - name: spec.exported.name, - })); - return makeStatement('VariableDeclaration', { - kind: 2, - declarations, - }); - } - //fallback - return makeStatement('EmptyStatement', {}); - } - case 'ExportDefaultDeclaration': { - const decl = node.declaration; - if (decl) { - if (decl.type === 'FunctionDeclaration' || - decl.type === 'ClassDeclaration' || - decl.type === 'VariableDeclaration') { - return visitStatement(node.declaration); - } - // treat as Expression - return makeStatement('ExpressionStatement', { - expression: visitExpression(decl), - }); - } - return makeStatement('EmptyStatement', {}); - } - case 'ExportAllDeclaration': { - if (node.exported) { - const declarations = [make('VariableDeclarator', { - name: node.exported.name - })]; - return makeStatement('VariableDeclaration', { - kind: 2, - declarations, - }) - } - return makeStatement('EmptyStatement', {}); - } default: { throw "Unhandled node type " + node.type; } diff --git a/Sources/Fuzzilli/Fuzzer.swift b/Sources/Fuzzilli/Fuzzer.swift index 58dddc1ee..8752b725d 100644 --- a/Sources/Fuzzilli/Fuzzer.swift +++ b/Sources/Fuzzilli/Fuzzer.swift @@ -431,7 +431,6 @@ public class Fuzzer { } let execution = execute(program, purpose: .programImport) - var wasImported = false switch execution.outcome { case .crashed(let termsig): @@ -674,6 +673,15 @@ public class Fuzzer { let execution = runner.run(script, withTimeout: timeout ?? config.timeout) dispatchEvent(events.PostExecute, data: execution) + //Stdout + // if !execution.stdout.isEmpty { + // print(execution.stdout) + // } + + // if !execution.stderr.isEmpty { + // print(execution.stderr) + // } + return execution } From 8a3db8352c30cb9790d8ed14c86f6e5e7fe79813 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Wed, 13 Aug 2025 14:39:02 +0000 Subject: [PATCH 05/10] support for top-level awaits --- Sources/Fuzzilli/Compiler/Compiler.swift | 3 ++- Sources/Fuzzilli/FuzzIL/Code.swift | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index 43e8e58ff..4d010159b 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -1161,9 +1161,10 @@ public class JavaScriptCompiler { case .awaitExpression(let awaitExpression): // TODO await is also allowed at the top level of a module - if !contextAnalyzer.context.contains(.asyncFunction) { + /*if !contextAnalyzer.context.contains(.asyncFunction) { throw CompilerError.invalidNodeError("`await` is currently only supported in async functions") } + */ let argument = try compileExpression(awaitExpression.argument) return emit(Await(), withInputs: [argument]).output diff --git a/Sources/Fuzzilli/FuzzIL/Code.swift b/Sources/Fuzzilli/FuzzIL/Code.swift index 1e63b0ff9..66d1cae8b 100644 --- a/Sources/Fuzzilli/FuzzIL/Code.swift +++ b/Sources/Fuzzilli/FuzzIL/Code.swift @@ -239,9 +239,21 @@ public struct Code: Collection { throw FuzzilliError.codeVerificationError("variable \(input) is not visible anymore") } } - - guard instr.op.requiredContext.isSubset(of: contextAnalyzer.context) else { - throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") + + if instr.op is Await { + if !contextAnalyzer.context.contains(.asyncFunction) + { + if contextAnalyzer.context.contains(.subroutine) { + if !contextAnalyzer.context.contains(.method) && !contextAnalyzer.context.contains(.classMethod) { + throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") + } + } + } + // fall-through allow top-level await + } else { + guard instr.op.requiredContext.isSubset(of: contextAnalyzer.context) else { + throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") + } } // Ensure that the instruction exists in the right context From b974cfb532443805d888c12cf008469f733cdfc4 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Thu, 14 Aug 2025 15:14:24 +0000 Subject: [PATCH 06/10] corpus --- Sources/Fuzzilli/FuzzIL/Code.swift | 16 +- .../FuzzilliCli/Profiles/WorkerdProfile.swift | 2 + workerd-corpus-fs/append-operations.js | 14 + workerd-corpus-fs/async-callbacks.js | 17 + workerd-corpus-fs/basic-file-ops.js | 11 + workerd-corpus-fs/boundary-conditions.js | 10 + workerd-corpus-fs/buffer-edge-cases.js | 19 + workerd-corpus-fs/buffer-io.js | 9 + workerd-corpus-fs/computed-properties.js | 9 + workerd-corpus-fs/concurrent-operations.js | 14 + workerd-corpus-fs/constants-usage.js | 12 + workerd-corpus-fs/copy-rename.js | 10 + workerd-corpus-fs/dynamic-calls.js | 10 + workerd-corpus-fs/encoding-variations.js | 9 + workerd-corpus-fs/fd-operations.js | 9 + workerd-corpus-fs/fs-chown-chmod-test.js | 573 +++++++++ workerd-corpus-fs/fs-dir-test.js | 839 +++++++++++++ workerd-corpus-fs/fs-promises.js | 10 + workerd-corpus-fs/fs-readstream-test.js | 1038 +++++++++++++++++ workerd-corpus-fs/fs-utimes-test.js | 116 ++ workerd-corpus-fs/fs-webfs-api.js | 118 ++ workerd-corpus-fs/fs-writestream-test.js | 491 ++++++++ workerd-corpus-fs/fs_basic_sync_ops.js | 50 + workerd-corpus-fs/fs_buffer_operations.js | 173 +++ workerd-corpus-fs/fs_bundle_boundary_tests.js | 66 ++ workerd-corpus-fs/fs_promises_api.js | 88 ++ workerd-corpus-fs/fs_webfs_api.js | 118 ++ workerd-corpus-fs/function-references.js | 12 + workerd-corpus-fs/invalid-args.js | 6 + workerd-corpus-fs/large-buffers.js | 9 + workerd-corpus-fs/mixed-sync-async.js | 12 + workerd-corpus-fs/multiple-fd-ops.js | 15 + workerd-corpus-fs/open-modes.js | 14 + workerd-corpus-fs/position-seeking.js | 10 + workerd-corpus-fs/simple.js | 1 + workerd-corpus-fs/stat-operations.js | 10 + workerd-corpus-fs/truncate-operations.js | 11 + workerd-corpus-fs/unicode-paths.js | 2 + workerd-corpus-fs/vector-io.js | 11 + 39 files changed, 3956 insertions(+), 8 deletions(-) create mode 100644 workerd-corpus-fs/append-operations.js create mode 100644 workerd-corpus-fs/async-callbacks.js create mode 100644 workerd-corpus-fs/basic-file-ops.js create mode 100644 workerd-corpus-fs/boundary-conditions.js create mode 100644 workerd-corpus-fs/buffer-edge-cases.js create mode 100644 workerd-corpus-fs/buffer-io.js create mode 100644 workerd-corpus-fs/computed-properties.js create mode 100644 workerd-corpus-fs/concurrent-operations.js create mode 100644 workerd-corpus-fs/constants-usage.js create mode 100644 workerd-corpus-fs/copy-rename.js create mode 100644 workerd-corpus-fs/dynamic-calls.js create mode 100644 workerd-corpus-fs/encoding-variations.js create mode 100644 workerd-corpus-fs/fd-operations.js create mode 100644 workerd-corpus-fs/fs-chown-chmod-test.js create mode 100644 workerd-corpus-fs/fs-dir-test.js create mode 100644 workerd-corpus-fs/fs-promises.js create mode 100644 workerd-corpus-fs/fs-readstream-test.js create mode 100644 workerd-corpus-fs/fs-utimes-test.js create mode 100644 workerd-corpus-fs/fs-webfs-api.js create mode 100644 workerd-corpus-fs/fs-writestream-test.js create mode 100644 workerd-corpus-fs/fs_basic_sync_ops.js create mode 100644 workerd-corpus-fs/fs_buffer_operations.js create mode 100644 workerd-corpus-fs/fs_bundle_boundary_tests.js create mode 100644 workerd-corpus-fs/fs_promises_api.js create mode 100644 workerd-corpus-fs/fs_webfs_api.js create mode 100644 workerd-corpus-fs/function-references.js create mode 100644 workerd-corpus-fs/invalid-args.js create mode 100644 workerd-corpus-fs/large-buffers.js create mode 100644 workerd-corpus-fs/mixed-sync-async.js create mode 100644 workerd-corpus-fs/multiple-fd-ops.js create mode 100644 workerd-corpus-fs/open-modes.js create mode 100644 workerd-corpus-fs/position-seeking.js create mode 100644 workerd-corpus-fs/simple.js create mode 100644 workerd-corpus-fs/stat-operations.js create mode 100644 workerd-corpus-fs/truncate-operations.js create mode 100644 workerd-corpus-fs/unicode-paths.js create mode 100644 workerd-corpus-fs/vector-io.js diff --git a/Sources/Fuzzilli/FuzzIL/Code.swift b/Sources/Fuzzilli/FuzzIL/Code.swift index 66d1cae8b..f37d8c175 100644 --- a/Sources/Fuzzilli/FuzzIL/Code.swift +++ b/Sources/Fuzzilli/FuzzIL/Code.swift @@ -241,14 +241,14 @@ public struct Code: Collection { } if instr.op is Await { - if !contextAnalyzer.context.contains(.asyncFunction) - { - if contextAnalyzer.context.contains(.subroutine) { - if !contextAnalyzer.context.contains(.method) && !contextAnalyzer.context.contains(.classMethod) { - throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") - } - } - } + // if !contextAnalyzer.context.contains(.asyncFunction) + // { + // if contextAnalyzer.context.contains(.subroutine) { + // if !contextAnalyzer.context.contains(.method) && !contextAnalyzer.context.contains(.classMethod) && !contextAnalyzer.context.contains(.javascript) { + // throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") + // } + // } + // } // fall-through allow top-level await } else { guard instr.op.requiredContext.isSubset(of: contextAnalyzer.context) else { diff --git a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift index 6db61243f..21ad1e4c5 100644 --- a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift +++ b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift @@ -39,6 +39,8 @@ let workerdProfile = Profile( // Check that common crash types are detected. ("fuzzilli('FUZZILLI_CRASH', 0)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 2)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 3)", .shouldCrash), ], additionalCodeGenerators: [], diff --git a/workerd-corpus-fs/append-operations.js b/workerd-corpus-fs/append-operations.js new file mode 100644 index 000000000..3ba6780ed --- /dev/null +++ b/workerd-corpus-fs/append-operations.js @@ -0,0 +1,14 @@ + +function testAppend() { + // Append operations + let data = "This is a file containing a collection"; + var path2 = '/tmp/append.txt'; + fs.writeFileSync(path2, data); + fs.appendFileSync(path2, ' appended'); + fs.readFileSync(path2); + var readData = fs.readFileSync(path2, { encoding: 'utf8', flag: 'r' }); + console.log(readData); + fs.unlinkSync(path2); +} + +testAppend(); diff --git a/workerd-corpus-fs/async-callbacks.js b/workerd-corpus-fs/async-callbacks.js new file mode 100644 index 000000000..1f42f9d44 --- /dev/null +++ b/workerd-corpus-fs/async-callbacks.js @@ -0,0 +1,17 @@ + + +// Async callback operations +function asyncCB() { + let path = '/tmp/async.txt'; + fs.writeFile(path, 'async data', (err) => { + if (!err) { + fs.readFile(path, (err, data) => { + if (!err) { + fs.unlink(path, () => { }); + } + }); + } + }); +} + +asyncCB(); diff --git a/workerd-corpus-fs/basic-file-ops.js b/workerd-corpus-fs/basic-file-ops.js new file mode 100644 index 000000000..6909696c6 --- /dev/null +++ b/workerd-corpus-fs/basic-file-ops.js @@ -0,0 +1,11 @@ +// Basic file operations +function basicOps() { + let path = '/tmp/test.txt'; + let content = 'Hello World'; + fs.writeFileSync(path, content); + fs.readFileSync(path); + fs.existsSync(path); + fs.unlinkSync(path); +} + +basicOps(); \ No newline at end of file diff --git a/workerd-corpus-fs/boundary-conditions.js b/workerd-corpus-fs/boundary-conditions.js new file mode 100644 index 000000000..874fd690d --- /dev/null +++ b/workerd-corpus-fs/boundary-conditions.js @@ -0,0 +1,10 @@ +// Boundary conditions +function boundaryCond() { + let path = '/tmp/boundary.txt'; + fs.writeFileSync(path, ''); // Empty file + let fd = fs.openSync(path, 'r+'); + fs.ftruncateSync(fd, 0); + fs.closeSync(fd); + fs.unlinkSync(path); +} +boundaryCond(); diff --git a/workerd-corpus-fs/buffer-edge-cases.js b/workerd-corpus-fs/buffer-edge-cases.js new file mode 100644 index 000000000..26e09fe63 --- /dev/null +++ b/workerd-corpus-fs/buffer-edge-cases.js @@ -0,0 +1,19 @@ +// Buffer edge cases +function bufferTest() { + let path = '/tmp/buf-edge.txt'; + let fd = fs.openSync(path, 'w+'); + + try { + fs.writeSync(fd, Buffer.alloc(0)); + fs.writeSync(fd, Buffer.alloc(1, 255)); + } catch (e) { } + + fs.closeSync(fd); + fs.unlinkSync(path); + fs.unlinkSync(path); + fs.unlinkSync(path); + fs.unlinkSync(path); +} + +bufferTest(); + diff --git a/workerd-corpus-fs/buffer-io.js b/workerd-corpus-fs/buffer-io.js new file mode 100644 index 000000000..563068236 --- /dev/null +++ b/workerd-corpus-fs/buffer-io.js @@ -0,0 +1,9 @@ +function bufferIO() { + // Buffer-based I/O operations + let path = '/tmp/buffer.txt'; + let buffer = Buffer.from('binary data', 'utf8'); + fs.writeFileSync(path, buffer); + let result = fs.readFileSync(path); + fs.unlinkSync(path); +} +bufferIO(); \ No newline at end of file diff --git a/workerd-corpus-fs/computed-properties.js b/workerd-corpus-fs/computed-properties.js new file mode 100644 index 000000000..e8ea940c6 --- /dev/null +++ b/workerd-corpus-fs/computed-properties.js @@ -0,0 +1,9 @@ +function props() { + // Computed property access + let prop = 'Sync'; + let path = '/tmp/computed.txt'; + + fs['writeFile' + prop](path, 'computed'); + fs['readFile' + prop](path); + fs['unlink' + prop](path); +} \ No newline at end of file diff --git a/workerd-corpus-fs/concurrent-operations.js b/workerd-corpus-fs/concurrent-operations.js new file mode 100644 index 000000000..b26425457 --- /dev/null +++ b/workerd-corpus-fs/concurrent-operations.js @@ -0,0 +1,14 @@ +function timeout() { + + // Concurrent file operations + let path1 = '/tmp/concurrent1.txt'; + let path2 = '/tmp/concurrent2.txt'; + + fs.writeFile(path1, 'data1', () => { }); + fs.writeFile(path2, 'data2', () => { }); + + setTimeout(() => { + try { fs.unlinkSync(path1); } catch (e) { } + try { fs.unlinkSync(path2); } catch (e) { } + }, 100); +} diff --git a/workerd-corpus-fs/constants-usage.js b/workerd-corpus-fs/constants-usage.js new file mode 100644 index 000000000..bad3ff289 --- /dev/null +++ b/workerd-corpus-fs/constants-usage.js @@ -0,0 +1,12 @@ +function letAntsTest() { + // Using fs letants + let { letants } = fs; + let path = '/tmp/let.txt'; + fs.writeFileSync(path, 'data'); + try { + fs.copyFileSync(path, '/tmp/let2.txt', letants.COPYFILE_EXCL); + } catch (e) { } + fs.unlinkSync(path); +} + +letAntsTest(); \ No newline at end of file diff --git a/workerd-corpus-fs/copy-rename.js b/workerd-corpus-fs/copy-rename.js new file mode 100644 index 000000000..405e316c0 --- /dev/null +++ b/workerd-corpus-fs/copy-rename.js @@ -0,0 +1,10 @@ +function renameAndCopy() { + // Copy and rename operations + let src = '/tmp/source.txt'; + let dst = '/tmp/dest.txt'; + fs.writeFileSync(src, 'data to copy'); + fs.copyFileSync(src, dst); + fs.renameSync(dst, '/tmp/renamed.txt'); + fs.unlinkSync(src); + fs.unlinkSync('/tmp/renamed.txt'); +} diff --git a/workerd-corpus-fs/dynamic-calls.js b/workerd-corpus-fs/dynamic-calls.js new file mode 100644 index 000000000..58f467fed --- /dev/null +++ b/workerd-corpus-fs/dynamic-calls.js @@ -0,0 +1,10 @@ +function dynamicCalls() { + // Dynamic function calls + let ops = ['writeFileSync', 'readFileSync', 'unlinkSync']; + let path = '/tmp/dynamic.txt'; + + fs[ops[0]](path, 'dynamic call'); + fs[ops[1]](path); + fs[ops[2]](path); +} + diff --git a/workerd-corpus-fs/encoding-variations.js b/workerd-corpus-fs/encoding-variations.js new file mode 100644 index 000000000..34e0c8a26 --- /dev/null +++ b/workerd-corpus-fs/encoding-variations.js @@ -0,0 +1,9 @@ +function encoding() { + // Different encoding variations + let path = '/tmp/encoding.txt'; + fs.writeFileSync(path, 'café', 'utf8'); + fs.readFileSync(path, 'utf8'); + fs.writeFileSync(path, Buffer.from([0x42, 0x43])); + fs.readFileSync(path); + fs.unlinkSync(path); +} \ No newline at end of file diff --git a/workerd-corpus-fs/fd-operations.js b/workerd-corpus-fs/fd-operations.js new file mode 100644 index 000000000..ec87f5c05 --- /dev/null +++ b/workerd-corpus-fs/fd-operations.js @@ -0,0 +1,9 @@ +function fd() { + // File descriptor operations + let fd = fs.openSync('/tmp/fd-test.txt', 'w+'); + fs.writeSync(fd, 'test data'); + let stats = fs.fstatSync(fd); + fs.closeSync(fd); +} + +fd(); diff --git a/workerd-corpus-fs/fs-chown-chmod-test.js b/workerd-corpus-fs/fs-chown-chmod-test.js new file mode 100644 index 000000000..efdb5b885 --- /dev/null +++ b/workerd-corpus-fs/fs-chown-chmod-test.js @@ -0,0 +1,573 @@ +strictEqual(typeof openSync, 'function'); +strictEqual(typeof closeSync, 'function'); +strictEqual(typeof statSync, 'function'); +strictEqual(typeof fstatSync, 'function'); +strictEqual(typeof lstatSync, 'function'); +strictEqual(typeof symlinkSync, 'function'); +strictEqual(typeof chmod, 'function'); +strictEqual(typeof lchmod, 'function'); +strictEqual(typeof fchmod, 'function'); +strictEqual(typeof chmodSync, 'function'); +strictEqual(typeof lchmodSync, 'function'); +strictEqual(typeof fchmodSync, 'function'); +strictEqual(typeof fs.chown, 'function'); +strictEqual(typeof lchown, 'function'); +strictEqual(typeof fchown, 'function'); +strictEqual(typeof chownSync, 'function'); +strictEqual(typeof lchownSync, 'function'); +strictEqual(typeof fchownSync, 'function'); +strictEqual(typeof promises.fs.chown, 'function'); +strictEqual(typeof promises.lchown, 'function'); + +const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' }; +const kOutOfRangeError = { code: 'ERR_OUT_OF_RANGE' }; + +function checkStat(path) { + const { uid, gid } = fs.statSync(path); + strictEqual(uid, 0); + strictEqual(gid, 0); +} + +function checkfStat(fd) { + const { uid, gid } = fs.fstatSync(fd); + strictEqual(uid, 0); + strictEqual(gid, 0); +} + +const path = '/tmp'; +const bufferPath = Buffer.from(path); +const urlPath = new URL(path, 'file:///'); + +const chownSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => fs.chownSync(123), kInvalidArgTypeError); + throws(() => fs.chownSync('/', {}), kInvalidArgTypeError); + throws(() => fs.chownSync('/', 0, {}), kInvalidArgTypeError); + throws(() => fs.chownSync(path, -1000, 0), kOutOfRangeError); + throws(() => fs.chownSync(path, 0, -1000), kOutOfRangeError); + + // We stat the file before and after to verify the impact + // of the fs.chown( operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkStat(path); + fs.chownSync(path, 1000, 1000); + checkStat(path); + + fs.chownSync(bufferPath, 1000, 1000); + checkStat(bufferPath); + + fs.chownSync(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + throws(() => fs.chownSync('/non-existent-path', 1000, 1000), { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'fs.chown', + }); + }, +}; + +const chownCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => fs.chown(123), kInvalidArgTypeError); + throws(() => fs.chown('/', {}), kInvalidArgTypeError); + throws(() => fs.chown('/', 0, {}), kInvalidArgTypeError); + throws(() => fs.chownSync(path, -1000, 0), kOutOfRangeError); + throws(() => fs.chownSync(path, 0, -1000), kOutOfRangeError); + + async function callChown(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + fs.chown(path, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkStat(path); + await callChown(path); + checkStat(path); + + await callChown(bufferPath); + checkStat(bufferPath); + + await callChown(urlPath); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + const { promise, resolve, reject } = Promise.withResolvers(); + fs.chown('/non-existent-path', 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'fs.chown(', + }); + }, +}; + +const chownPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.fs.chown(123), kInvalidArgTypeError); + await rejects(promises.fs.chown('/', {}), kInvalidArgTypeError); + await rejects(promises.fs.chown('/', 0, {}), kInvalidArgTypeError); + await rejects(promises.fs.chown(path, -1000, 0), kOutOfRangeError); + await rejects(promises.fs.chown(path, 0, -1000), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.fs.chown(path, 1000, 1000); + checkStat(path); + + await promises.fs.chown(bufferPath, 1000, 1000); + checkStat(bufferPath); + + await promises.fs.chown(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + await rejects(promises.fs.chown('/non-existent-path', 1000, 1000), { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'fs.chown(', + }); + }, +}; + +const lchownSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => lchownSync(123), kInvalidArgTypeError); + throws(() => lchownSync('/', {}), kInvalidArgTypeError); + throws(() => lchownSync('/', 0, {}), kInvalidArgTypeError); + throws(() => lchownSync(path, -1000, 0), kOutOfRangeError); + throws(() => lchownSync(path, 0, -1000), kOutOfRangeError); + + // We stat the file before and after to verify the impact + // of the fs.chown( operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkStat(path); + lchownSync(path, 1000, 1000); + checkStat(path); + + lchownSync(bufferPath, 1000, 1000); + checkStat(bufferPath); + + lchownSync(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + throws(() => lchownSync('/non-existent-path', 1000, 1000), { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'lchown', + }); + }, +}; + +const lchownCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => lchown(123), kInvalidArgTypeError); + throws(() => lchown('/', {}), kInvalidArgTypeError); + throws(() => lchown('/', 0, {}), kInvalidArgTypeError); + throws(() => lchownSync(path, -1000, 0), kOutOfRangeError); + throws(() => lchownSync(path, 0, -1000), kOutOfRangeError); + + async function callChown(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + lchown(path, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkStat(path); + await callChown(path); + checkStat(path); + + await callChown(bufferPath); + checkStat(bufferPath); + + await callChown(urlPath); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + const { promise, resolve, reject } = Promise.withResolvers(); + lchown('/non-existent-path', 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'lchown', + }); + }, +}; + +const lchownPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.lchown(123), kInvalidArgTypeError); + await rejects(promises.lchown('/', {}), kInvalidArgTypeError); + await rejects(promises.lchown('/', 0, {}), kInvalidArgTypeError); + await rejects(promises.lchown(path, -1000, 0), kOutOfRangeError); + await rejects(promises.lchown(path, 0, -1000), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.lchown(path, 1000, 1000); + checkStat(path); + + await promises.lchown(bufferPath, 1000, 1000); + checkStat(bufferPath); + + await promises.lchown(urlPath, 1000, 1000); + checkStat(urlPath); + + // A non-existent path should throw ENOENT + await rejects(promises.lchown('/non-existent-path', 1000, 1000), { + code: 'ENOENT', + syscall: 'lchown', + }); + }, +}; + +const fchownSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => fchownSync({}), kInvalidArgTypeError); + throws(() => fchownSync(123), kInvalidArgTypeError); + throws(() => fchownSync(123, {}), kInvalidArgTypeError); + throws(() => fchownSync(123, 0, {}), kInvalidArgTypeError); + throws(() => fchownSync(123, -1000, 0), kOutOfRangeError); + throws(() => fchownSync(123, 0, -1000), kOutOfRangeError); + + const fd = openSync('/tmp'); + + // We stat the file before and after to verify the impact + // of the fs.chown( operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkfStat(fd); + fchownSync(fd, 1000, 1000); + checkfStat(fd); + + throws(() => fchownSync(999, 1000, 1000), { + code: 'EBADF', + syscall: 'fstat', + }); + + closeSync(fd); + }, +}; + +const fchownCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => fchown({}), kInvalidArgTypeError); + throws(() => fchown(123), kInvalidArgTypeError); + throws(() => fchown(123, {}), kInvalidArgTypeError); + throws(() => fchown(123, 0, {}), kInvalidArgTypeError); + throws(() => fchown(123, -1000, 0), kOutOfRangeError); + throws(() => fchown(123, 0, -1000), kOutOfRangeError); + + const fd = openSync('/tmp'); + + async function callChown() { + const { promise, resolve, reject } = Promise.withResolvers(); + fchown(fd, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkfStat(fd); + await callChown(); + checkfStat(fd); + + const { promise, resolve, reject } = Promise.withResolvers(); + fchown(999, 1000, 1000, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: 'EBADF', + syscall: 'fstat', + }); + + closeSync(fd); + }, +}; + +// =========================================================================== + +const chmodSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => chmodSync(123), kInvalidArgTypeError); + throws(() => chmodSync('/', {}), kInvalidArgTypeError); + throws(() => chmodSync('/tmp', -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + chmodSync(path, 0o777); + checkStat(path); + + chmodSync(bufferPath, 0o777); + checkStat(bufferPath); + + chmodSync(urlPath, 0o777); + checkStat(urlPath); + + throws(() => chmodSync('/non-existent-path', 0o777), { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'chmod', + }); + }, +}; + +const chmodCallbackTest = { + async test() { + // Incorrect input types should throw. + throws(() => chmod(123), kInvalidArgTypeError); + throws(() => chmod('/', {}), kInvalidArgTypeError); + throws(() => chmod('/tmp', -1), kOutOfRangeError); + + async function callChmod(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + chmod(path, 0o000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + checkStat(path); + await callChmod(path); + checkStat(path); + + await callChmod(bufferPath); + checkStat(bufferPath); + + await callChmod(urlPath); + checkStat(bufferPath); + + const { promise, resolve, reject } = Promise.withResolvers(); + chmod('/non-existent-path', 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'chmod', + }); + }, +}; + +const chmodPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.chmod(123), kInvalidArgTypeError); + await rejects(promises.chmod('/', {}), kInvalidArgTypeError); + await rejects(promises.chmod('/tmp', -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.chmod(path, 0o777); + checkStat(path); + + await promises.chmod(bufferPath, 0o777); + checkStat(bufferPath); + + await promises.chmod(urlPath, 0o777); + checkStat(urlPath); + + await rejects(promises.chmod('/non-existent-path', 0o777), { + code: 'ENOENT', + syscall: 'chmod', + }); + }, +}; + +const lchmodSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => lchmodSync(123), kInvalidArgTypeError); + throws(() => lchmodSync('/', {}), kInvalidArgTypeError); + throws(() => lchmodSync('/tmp', -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + lchmodSync(path, 0o777); + checkStat(path); + + lchmodSync(bufferPath, 0o777); + checkStat(bufferPath); + + lchmodSync(urlPath, 0o777); + checkStat(urlPath); + + throws(() => lchmodSync('/non-existent-path', 0o777), { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'lchmod', + }); + }, +}; + +const lchmodCallbackTest = { + async test() { + // Incorrect input types should throw. + throws(() => lchmod(123), kInvalidArgTypeError); + throws(() => lchmod('/', {}), kInvalidArgTypeError); + throws(() => lchmod('/tmp', -1), kOutOfRangeError); + + async function callChmod(path) { + const { promise, resolve, reject } = Promise.withResolvers(); + lchmod(path, 0o000, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + checkStat(path); + await callChmod(path); + checkStat(path); + + await callChmod(bufferPath); + checkStat(bufferPath); + + await callChmod(urlPath); + checkStat(bufferPath); + + const { promise, resolve, reject } = Promise.withResolvers(); + lchmod('/non-existent-path', 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: 'ENOENT', + // Access because it is an access check under the covers. + syscall: 'lchmod', + }); + }, +}; + +const lchmodPromiseTest = { + async test() { + // Incorrect input types should reject the promise. + await rejects(promises.lchmod(123), kInvalidArgTypeError); + await rejects(promises.lchmod('/', {}), kInvalidArgTypeError); + await rejects(promises.lchmod('/tmp', -1), kOutOfRangeError); + + // Should be non-op + checkStat(path); + await promises.lchmod(path, 0o777); + checkStat(path); + + await promises.lchmod(bufferPath, 0o777); + checkStat(bufferPath); + + await promises.lchmod(urlPath, 0o777); + checkStat(urlPath); + + await rejects(promises.lchmod('/non-existent-path', 0o777), { + code: 'ENOENT', + syscall: 'lchmod', + }); + }, +}; + +const fchmodSyncTest = { + test() { + // Incorrect input types should throw. + throws(() => fchmodSync({}), kInvalidArgTypeError); + throws(() => fchmodSync(123), kInvalidArgTypeError); + throws(() => fchmodSync(123, {}), kInvalidArgTypeError); + throws(() => fchmodSync(123, -1000), kOutOfRangeError); + + const fd = openSync('/tmp'); + + // We stat the file before and after to verify the impact + // of the fs.chown( operation. Specifically, the uid and gid + // should not change since our impl is a non-op. + checkfStat(fd); + fchmodSync(fd, 0o777); + checkfStat(fd); + + throws(() => fchmodSync(999, 0o777), { + code: 'EBADF', + syscall: 'fstat', + }); + + closeSync(fd); + }, +}; + +const fchmodCallbackTest = { + async test() { + // Incorrect input types should throw synchronously + throws(() => fchmod({}), kInvalidArgTypeError); + throws(() => fchmod(123), kInvalidArgTypeError); + throws(() => fchmod(123, {}), kInvalidArgTypeError); + + const fd = openSync('/tmp'); + + async function callChmod() { + const { promise, resolve, reject } = Promise.withResolvers(); + fchmod(fd, 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await promise; + } + + // Should be non-op + checkfStat(fd); + await callChmod(); + checkfStat(fd); + + const { promise, resolve, reject } = Promise.withResolvers(); + fchmod(999, 0o777, (err) => { + if (err) return reject(err); + resolve(); + }); + await rejects(promise, { + code: 'EBADF', + syscall: 'fstat', + }); + + closeSync(fd); + }, +}; + +chownSyncTest.test(); +await chownCallbackTest.test(); +await chownPromiseTest.test(); +lchownSyncTest.test(); +await lchownCallbackTest.test(); +await lchownPromiseTest.test(); +fchownSyncTest.test(); +chmodSyncTest.test(); +await chmodCallbackTest.test(); +await chmodPromiseTest.test(); +lchmodSyncTest.test(); +await lchmodCallbackTest.test(); +fchmodSyncTest.test(); +await lchmodPromiseTest.test(); +await fchmodCallbackTest.test(); \ No newline at end of file diff --git a/workerd-corpus-fs/fs-dir-test.js b/workerd-corpus-fs/fs-dir-test.js new file mode 100644 index 000000000..874f25fae --- /dev/null +++ b/workerd-corpus-fs/fs-dir-test.js @@ -0,0 +1,839 @@ +// Copyright (c) 2017-2022 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +strictEqual(typeof fs.existsSync, 'function'); +strictEqual(typeof fs.writeFileSync, 'function'); +strictEqual(typeof fs.mkdirSync, 'function'); +strictEqual(typeof fs.mkdtempSync, 'function'); +strictEqual(typeof fs.rmSync, 'function'); +strictEqual(typeof fs.rmdirSync, 'function'); +strictEqual(typeof fs.readdirSync, 'function'); +strictEqual(typeof fs.mkdtemp, 'function'); +strictEqual(typeof fs.mkdir, 'function'); +strictEqual(typeof fs.rm, 'function'); +strictEqual(typeof fs.rmdir, 'function'); +strictEqual(typeof fs.readdir, 'function'); +strictEqual(typeof fs.opendirSync, 'function'); +strictEqual(typeof fs.opendir, 'function'); +strictEqual(typeof promises.fs.mkdir, 'function'); +strictEqual(typeof promises.fs.mkdtemp, 'function'); +strictEqual(typeof promises.fs.rm, 'function'); +strictEqual(typeof promises.fs.rmdir, 'function'); +strictEqual(typeof promises.fs.readdir, 'function'); +strictEqual(typeof promises.fs.opendir, 'function'); + +const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' }; +const kInvalidArgValueError = { code: 'ERR_INVALID_ARG_VALUE' }; +const kEPermError = { code: 'EPERM' }; +const kENoEntError = { code: 'ENOENT' }; +const kEExistError = { code: 'EEXIST' }; +const kENotDirError = { code: 'ENOTDIR' }; +const kENotEmptyError = { code: 'ENOTEMPTY' }; + +const mkdirSyncTest = { + test() { + throws(() => fs.mkdirSync(), kInvalidArgTypeError); + throws(() => fs.mkdirSync(123), kInvalidArgTypeError); + throws(() => fs.mkdirSync('/tmp/testdir', 'hello'), kInvalidArgTypeError); + throws( + () => fs.mkdirSync('/tmp/testdir', { recursive: 123 }), + kInvalidArgTypeError + ); + + // Make a directory. + ok(!fs.existsSync('/tmp/testdir')); + strictEqual(fs.mkdirSync('/tmp/testdir'), undefined); + ok(fs.existsSync('/tmp/testdir')); + + // Making a subdirectory in a non-existing path fails by default + ok(!fs.existsSync('/tmp/testdir/a/b/c')); + throws(() => fs.mkdirSync('/tmp/testdir/a/b/c'), kENoEntError); + + // But passing the recursive option allows the entire path to be created. + ok(!fs.existsSync('/tmp/testdir/a/b/c')); + strictEqual( + fs.mkdirSync('/tmp/testdir/a/b/c', { recursive: true }), + '/tmp/testdir/a' + ); + ok(fs.existsSync('/tmp/testdir/a/b/c')); + + // Cannot make a directory in a read-only location + throws(() => fs.mkdirSync('/bundle/a'), kEPermError); + + // Making a directory that already exists is a non-op + fs.mkdirSync('/tmp/testdir'); + + // Attempting to create a directory that already exists as a file throws + fs.writeFileSync('/tmp/abc', 'Hello World'); + throws(() => fs.mkdirSync('/tmp/abc'), kEExistError); + + // Attempting to create a directory recursively when a parent is a file + // throws + throws(() => fs.mkdirSync('/tmp/abc/foo', { recursive: true }), kENotDirError); + }, +}; + +const mkdirAsyncCallbackTest = { + async test() { + throws(() => mkdir(), kInvalidArgTypeError); + throws(() => mkdir(123), kInvalidArgTypeError); + throws(() => mkdir('/tmp/testdir', 'hello'), kInvalidArgTypeError); + throws( + () => mkdir('/tmp/testdir', { recursive: 123 }), + kInvalidArgTypeError + ); + + // Make a directory. + ok(!fs.existsSync('/tmp/testdir')); + await new Promise((resolve, reject) => { + mkdir('/tmp/testdir', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(fs.existsSync('/tmp/testdir')); + + // Making a subdirectory in a non-existing path fails by default + ok(!fs.existsSync('/tmp/testdir/a/b/c')); + await new Promise((resolve, reject) => { + mkdir('/tmp/testdir/a/b/c', (err) => { + if (err && err.code === kENoEntError.code) resolve(); + else reject(err); + }); + }); + + // But passing the recursive option allows the entire path to be created. + ok(!fs.existsSync('/tmp/testdir/a/b/c')); + await new Promise((resolve, reject) => { + mkdir('/tmp/testdir/a/b/c', { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(fs.existsSync('/tmp/testdir/a/b/c')); + + // Cannot make a directory in a read-only location + await new Promise((resolve, reject) => { + mkdir('/bundle/a', (err) => { + if (err && err.code === kEPermError.code) resolve(); + else reject(err); + }); + }); + + // Making a directory that already exists is a non-op + await new Promise((resolve, reject) => { + mkdir('/tmp/testdir', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + + // Attempting to create a directory that already exists as a file throws + fs.writeFileSync('/tmp/abc', 'Hello World'); + await new Promise((resolve, reject) => { + mkdir('/tmp/abc', (err) => { + if (err && err.code === kEExistError.code) resolve(); + else reject(err); + }); + }); + + // Attempting to create a directory recursively when a parent is a file + // throws + await new Promise((resolve, reject) => { + mkdir('/tmp/abc/foo', { recursive: true }, (err) => { + if (err && err.code === kENotDirError.code) resolve(); + else reject(err); + }); + }); + }, +}; + +const mkdirAsyncPromiseTest = { + async test() { + await rejects(promises.fs.mkdir(), kInvalidArgTypeError); + await rejects(promises.fs.mkdir(123), kInvalidArgTypeError); + await rejects( + promises.fs.mkdir('/tmp/testdir', 'hello'), + kInvalidArgTypeError + ); + await rejects( + promises.fs.mkdir('/tmp/testdir', { recursive: 123 }), + kInvalidArgTypeError + ); + + // Make a directory. + ok(!fs.existsSync('/tmp/testdir')); + await promises.fs.mkdir('/tmp/testdir'); + ok(fs.existsSync('/tmp/testdir')); + + // Making a subdirectory in a non-existing path fails by default + ok(!fs.existsSync('/tmp/testdir/a/b/c')); + await rejects(promises.fs.mkdir('/tmp/testdir/a/b/c'), kENoEntError); + + // But passing the recursive option allows the entire path to be created. + ok(!fs.existsSync('/tmp/testdir/a/b/c')); + await promises.fs.mkdir('/tmp/testdir/a/b/c', { recursive: true }); + ok(fs.existsSync('/tmp/testdir/a/b/c')); + + // Cannot make a directory in a read-only location + await rejects(promises.fs.mkdir('/bundle/a'), kEPermError); + + // Making a directory that already exists is a non-op + await promises.fs.mkdir('/tmp/testdir'); + + // Attempting to create a directory that already exists as a file throws + fs.writeFileSync('/tmp/abc', 'Hello World'); + await rejects(promises.fs.mkdir('/tmp/abc'), kEExistError); + + // Attempting to create a directory recursively when a parent is a file + // throws + await rejects( + promises.fs.mkdir('/tmp/abc/foo', { recursive: true }), + kENotDirError + ); + }, +}; + +const mkdtempSyncTest = { + test() { + throws(() => fs.mkdtempSync(), kInvalidArgTypeError); + const ret1 = fs.mkdtempSync('/tmp/testdir-'); + const ret2 = fs.mkdtempSync('/tmp/testdir-'); + match(ret1, /\/tmp\/testdir-\d+/); + match(ret2, /\/tmp\/testdir-\d+/); + ok(fs.existsSync(ret1)); + ok(fs.existsSync(ret2)); + throws(() => fs.mkdtempSync('/bundle/testdir-'), kEPermError); + }, +}; + +const mkdtempAsyncCallbackTest = { + async test() { + throws(() => fs.mkdtemp(), kInvalidArgTypeError); + const ret1 = await new Promise((resolve, reject) => { + fs.mkdtemp('/tmp/testdir-', (err, dir) => { + if (err) reject(err); + else resolve(dir); + }); + }); + const ret2 = await new Promise((resolve, reject) => { + fs.mkdtemp('/tmp/testdir-', (err, dir) => { + if (err) reject(err); + else resolve(dir); + }); + }); + match(ret1, /\/tmp\/testdir-\d+/); + match(ret2, /\/tmp\/testdir-\d+/); + ok(fs.existsSync(ret1)); + ok(fs.existsSync(ret2)); + await new Promise((resolve, reject) => { + fs.mkdtemp('/bundle/testdir-', (err) => { + if (err && err.code === kEPermError.code) resolve(); + else reject(err); + }); + }); + }, +}; + +const mkdtempAsyncPromiseTest = { + async test() { + await rejects(promises.fs.mkdtemp(), kInvalidArgTypeError); + const ret1 = await promises.fs.mkdtemp('/tmp/testdir-'); + const ret2 = await promises.fs.mkdtemp('/tmp/testdir-'); + match(ret1, /\/tmp\/testdir-\d+/); + match(ret2, /\/tmp\/testdir-\d+/); + ok(fs.existsSync(ret1)); + ok(fs.existsSync(ret2)); + await rejects(promises.fs.mkdtemp('/bundle/testdir-'), kEPermError); + }, +}; + +const rmSyncTest = { + test() { + // Passing incorrect types for options throws + throws( + () => fs.rmSync('/tmp/testdir', { recursive: 'yes' }), + kInvalidArgTypeError + ); + throws(() => fs.rmSync('/tmp/testdir', 'abc'), kInvalidArgTypeError); + throws( + () => fs.rmSync('/tmp/testdir', { force: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmSync('/tmp/testdir', { maxRetries: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmSync('/tmp/testdir', { retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmSync('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmSync('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), + kInvalidArgTypeError + ); + throws( + () => + fs.rmSync('/tmp/testdir', { maxRetries: 1, retryDelay: 1, force: 'yes' }), + kInvalidArgTypeError + ); + + throws( + () => fs.rmdirSync('/tmp/testdir', { recursive: 'yes' }), + kInvalidArgTypeError + ); + throws(() => fs.rmdirSync('/tmp/testdir', 'abc'), kInvalidArgTypeError); + throws( + () => fs.rmdirSync('/tmp/testdir', { maxRetries: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmdirSync('/tmp/testdir', { retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmdirSync('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmdirSync('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), + kInvalidArgTypeError + ); + + ok(!fs.existsSync('/tmp/testdir')); + fs.mkdirSync('/tmp/testdir'); + fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); + + // When the recusive option is not set, then removing a directory + // with children throws... + throws(() => fs.rmdirSync('/tmp/testdir'), kENotEmptyError); + ok(fs.existsSync('/tmp/testdir')); + + // But works when the recursive option is set + fs.rmdirSync('/tmp/testdir', { recursive: true }); + ok(!fs.existsSync('/tmp/testdir')); + + fs.mkdirSync('/tmp/testdir'); + fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); + fs.writeFileSync('/tmp/testdir/b.txt', 'Hello World'); + ok(fs.existsSync('/tmp/testdir/a.txt')); + + // trying to remove a file with fs.rmdir throws + throws(() => fs.rmdirSync('/tmp/testdir/a.txt'), kENotDirError); + + // removing a file with fs.rm works + fs.rmSync('/tmp/testdir/a.txt'); + ok(!fs.existsSync('/tmp/testdir/a.txt')); + + // Calling fs.rmSync when the directory is not empty throws + throws(() => fs.rmSync('/tmp/testdir'), kENotEmptyError); + ok(fs.existsSync('/tmp/testdir')); + + // But works when the recursive option is set + throws(() => fs.rmSync('/tmp/testdir')); + fs.rmSync('/tmp/testdir', { recursive: true }); + ok(!fs.existsSync('/tmp/testdir')); + }, +}; + +const rmAsyncCallbackTest = { + async test() { + // Passing incorrect types for options throws + throws( + () => fs.rm('/tmp/testdir', { recursive: 'yes' }), + kInvalidArgTypeError + ); + throws(() => fs.rm('/tmp/testdir', 'abc'), kInvalidArgTypeError); + throws(() => fs.rm('/tmp/testdir', { force: 'yes' }), kInvalidArgTypeError); + throws( + () => fs.rm('/tmp/testdir', { maxRetries: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rm('/tmp/testdir', { retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rm('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), + kInvalidArgTypeError + ); + throws( + () => fs.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 1, force: 'yes' }), + kInvalidArgTypeError + ); + + throws( + () => fs.rmdir('/tmp/testdir', { recursive: 'yes' }), + kInvalidArgTypeError + ); + throws(() => fs.rmdir('/tmp/testdir', 'abc'), kInvalidArgTypeError); + throws( + () => fs.rmdir('/tmp/testdir', { maxRetries: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmdir('/tmp/testdir', { retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmdir('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), + kInvalidArgTypeError + ); + throws( + () => fs.rmdir('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), + kInvalidArgTypeError + ); + + ok(!fs.existsSync('/tmp/testdir')); + fs.mkdirSync('/tmp/testdir'); + fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); + + // When the recusive option is not set, then removing a directory + // with children throws... + await new Promise((resolve, reject) => { + fs.rmdir('/tmp/testdir', (err) => { + if (err && err.code === kENotEmptyError.code) resolve(); + else reject(err); + }); + }); + + ok(fs.existsSync('/tmp/testdir')); + // But works when the recursive option is set + await new Promise((resolve, reject) => { + fs.rmdir('/tmp/testdir', { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(!fs.existsSync('/tmp/testdir')); + fs.mkdirSync('/tmp/testdir'); + fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); + fs.writeFileSync('/tmp/testdir/b.txt', 'Hello World'); + + ok(fs.existsSync('/tmp/testdir/a.txt')); + // trying to remove a file with fs.rmdir throws + await new Promise((resolve, reject) => { + fs.rmdir('/tmp/testdir/a.txt', (err) => { + if (err && err.code === kENotDirError.code) resolve(); + else reject(err); + }); + }); + // removing a file with fs.rm works + await new Promise((resolve, reject) => { + fs.rm('/tmp/testdir/a.txt', (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(!fs.existsSync('/tmp/testdir/a.txt')); + // Calling fs.rm when the directory is not empty throws + await new Promise((resolve, reject) => { + fs.rm('/tmp/testdir', (err) => { + if (err && err.code === kENotEmptyError.code) resolve(); + else reject(err); + }); + }); + ok(fs.existsSync('/tmp/testdir')); + // But works when the recursive option is set + await new Promise((resolve, reject) => { + fs.rm('/tmp/testdir', { recursive: true }, (err) => { + if (err) reject(err); + else resolve(); + }); + }); + ok(!fs.existsSync('/tmp/testdir')); + }, +}; + +const rmAsyncPromiseTest = { + async test() { + // Passing incorrect types for options throws + await rejects( + promises.fs.rm('/tmp/testdir', { recursive: 'yes' }), + kInvalidArgTypeError + ); + await rejects(promises.fs.rm('/tmp/testdir', 'abc'), kInvalidArgTypeError); + await rejects( + promises.fs.rm('/tmp/testdir', { force: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rm('/tmp/testdir', { maxRetries: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rm('/tmp/testdir', { retryDelay: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rm('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rm('/tmp/testdir', { + maxRetries: 1, + retryDelay: 1, + force: 'yes', + }), + kInvalidArgTypeError + ); + + await rejects( + promises.fs.rmdir('/tmp/testdir', { recursive: 'yes' }), + kInvalidArgTypeError + ); + await rejects(promises.fs.rmdir('/tmp/testdir', 'abc'), kInvalidArgTypeError); + await rejects( + promises.fs.rmdir('/tmp/testdir', { maxRetries: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rmdir('/tmp/testdir', { retryDelay: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rmdir('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), + kInvalidArgTypeError + ); + await rejects( + promises.fs.rmdir('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), + kInvalidArgTypeError + ); + + ok(!fs.existsSync('/tmp/testdir')); + fs.mkdirSync('/tmp/testdir'); + fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); + + // When the recusive option is not set, then removing a directory + // with children throws... + await rejects(promises.fs.rmdir('/tmp/testdir'), kENotEmptyError); + + ok(fs.existsSync('/tmp/testdir')); + // But works when the recursive option is set + await promises.fs.rmdir('/tmp/testdir', { recursive: true }); + ok(!fs.existsSync('/tmp/testdir')); + fs.mkdirSync('/tmp/testdir'); + fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); + fs.writeFileSync('/tmp/testdir/b.txt', 'Hello World'); + ok(fs.existsSync('/tmp/testdir/a.txt')); + // trying to remove a file with fs.rmdir throws + await rejects(promises.fs.rmdir('/tmp/testdir/a.txt'), kENotDirError); + // removing a file with fs.rm works + await promises.fs.rm('/tmp/testdir/a.txt'); + ok(!fs.existsSync('/tmp/testdir/a.txt')); + // Calling fs.rm when the directory is not empty throws + await rejects(promises.fs.rm('/tmp/testdir'), kENotEmptyError); + ok(fs.existsSync('/tmp/testdir')); + // But works when the recursive option is set + await promises.fs.rm('/tmp/testdir', { recursive: true }); + ok(!fs.existsSync('/tmp/testdir')); + }, +}; + +const readdirSyncTest = { + test() { + throws(() => fs.readdirSync(), kInvalidArgTypeError); + throws(() => fs.readdirSync(123), kInvalidArgTypeError); + throws( + () => fs.readdirSync('/tmp/testdir', { withFileTypes: 123 }), + kInvalidArgTypeError + ); + throws( + () => fs.readdirSync('/tmp/testdir', { recursive: 123 }), + kInvalidArgTypeError + ); + throws( + () => + fs.readdirSync('/tmp/testdir', { withFileTypes: true, recursive: 123 }), + kInvalidArgTypeError + ); + + deepStrictEqual(fs.readdirSync('/'), ['bundle', 'tmp', 'dev']); + + deepStrictEqual(fs.readdirSync('/', 'buffer'), [ + Buffer.from('bundle'), + Buffer.from('tmp'), + Buffer.from('dev'), + ]); + + { + const ents = fs.readdirSync('/', { withFileTypes: true }); + strictEqual(ents.length, 3); + + strictEqual(ents[0].name, 'bundle'); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + } + + { + const ents = fs.readdirSync('/', { + withFileTypes: true, + encoding: 'buffer', + }); + strictEqual(ents.length, 3); + + deepStrictEqual(ents[0].name, Buffer.from('bundle')); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + } + + { + const ents = fs.readdirSync('/', { withFileTypes: true, recursive: true }); + strictEqual(ents.length, 8); + + strictEqual(ents[0].name, 'bundle'); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + + strictEqual(ents[1].name, 'bundle/worker'); + strictEqual(ents[1].isDirectory(), false); + strictEqual(ents[1].isFile(), true); + strictEqual(ents[1].isBlockDevice(), false); + strictEqual(ents[1].isCharacterDevice(), false); + strictEqual(ents[1].isFIFO(), false); + strictEqual(ents[1].isSocket(), false); + strictEqual(ents[1].isSymbolicLink(), false); + strictEqual(ents[1].parentPath, '/bundle'); + + strictEqual(ents[4].name, 'dev/null'); + strictEqual(ents[4].isDirectory(), false); + strictEqual(ents[4].isFile(), false); + strictEqual(ents[4].isBlockDevice(), false); + strictEqual(ents[4].isCharacterDevice(), true); + strictEqual(ents[4].isFIFO(), false); + strictEqual(ents[4].isSocket(), false); + strictEqual(ents[4].isSymbolicLink(), false); + strictEqual(ents[4].parentPath, '/dev'); + } + }, +}; + +const readdirAsyncCallbackTest = { + async test() { + deepStrictEqual( + await new Promise((resolve, reject) => { + fs.readdir('/', (err, files) => { + if (err) reject(err); + else resolve(files); + }); + }), + ['bundle', 'tmp', 'dev'] + ); + + { + const ents = await new Promise((resolve, reject) => { + fs.readdir('/', { withFileTypes: true }, (err, files) => { + if (err) reject(err); + else resolve(files); + }); + }); + strictEqual(ents.length, 3); + + strictEqual(ents[0].name, 'bundle'); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + } + + { + const ents = await new Promise((resolve, reject) => { + fs.readdir( + '/', + { withFileTypes: true, encoding: 'buffer' }, + (err, files) => { + if (err) reject(err); + else resolve(files); + } + ); + }); + strictEqual(ents.length, 3); + + deepStrictEqual(ents[0].name, Buffer.from('bundle')); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + } + + { + const ents = await new Promise((resolve, reject) => { + fs.readdir('/', { withFileTypes: true, recursive: true }, (err, files) => { + if (err) reject(err); + else resolve(files); + }); + }); + strictEqual(ents.length, 8); + + strictEqual(ents[0].name, 'bundle'); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + + strictEqual(ents[1].name, 'bundle/worker'); + strictEqual(ents[1].isDirectory(), false); + strictEqual(ents[1].isFile(), true); + strictEqual(ents[1].isBlockDevice(), false); + strictEqual(ents[1].isCharacterDevice(), false); + strictEqual(ents[1].isFIFO(), false); + strictEqual(ents[1].isSocket(), false); + strictEqual(ents[1].isSymbolicLink(), false); + strictEqual(ents[1].parentPath, '/bundle'); + + strictEqual(ents[4].name, 'dev/null'); + strictEqual(ents[4].isDirectory(), false); + strictEqual(ents[4].isFile(), false); + strictEqual(ents[4].isBlockDevice(), false); + strictEqual(ents[4].isCharacterDevice(), true); + strictEqual(ents[4].isFIFO(), false); + strictEqual(ents[4].isSocket(), false); + strictEqual(ents[4].isSymbolicLink(), false); + strictEqual(ents[4].parentPath, '/dev'); + } + }, +}; + +const readdirAsyncPromiseTest = { + async test() { + deepStrictEqual(await promises.fs.readdir('/'), ['bundle', 'tmp', 'dev']); + + { + const ents = await promises.fs.readdir('/', { withFileTypes: true }); + strictEqual(ents.length, 3); + + strictEqual(ents[0].name, 'bundle'); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + } + + { + const ents = await promises.fs.readdir('/', { + withFileTypes: true, + encoding: 'buffer', + }); + strictEqual(ents.length, 3); + + deepStrictEqual(ents[0].name, Buffer.from('bundle')); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + } + + { + const ents = await promises.fs.readdir('/', { + withFileTypes: true, + recursive: true, + }); + strictEqual(ents.length, 8); + + strictEqual(ents[0].name, 'bundle'); + strictEqual(ents[0].isDirectory(), true); + strictEqual(ents[0].isFile(), false); + strictEqual(ents[0].isBlockDevice(), false); + strictEqual(ents[0].isCharacterDevice(), false); + strictEqual(ents[0].isFIFO(), false); + strictEqual(ents[0].isSocket(), false); + strictEqual(ents[0].isSymbolicLink(), false); + strictEqual(ents[0].parentPath, '/'); + + strictEqual(ents[1].name, 'bundle/worker'); + strictEqual(ents[1].isDirectory(), false); + strictEqual(ents[1].isFile(), true); + strictEqual(ents[1].isBlockDevice(), false); + strictEqual(ents[1].isCharacterDevice(), false); + strictEqual(ents[1].isFIFO(), false); + strictEqual(ents[1].isSocket(), false); + strictEqual(ents[1].isSymbolicLink(), false); + strictEqual(ents[1].parentPath, '/bundle'); + strictEqual(ents[4].name, 'dev/null'); + strictEqual(ents[4].isDirectory(), false); + strictEqual(ents[4].isFile(), false); + strictEqual(ents[4].isBlockDevice(), false); + strictEqual(ents[4].isCharacterDevice(), true); + strictEqual(ents[4].isFIFO(), false); + strictEqual(ents[4].isSocket(), false); + strictEqual(ents[4].isSymbolicLink(), false); + strictEqual(ents[4].parentPath, '/dev'); + } + }, +}; + +const opendirSyncTest = { + test() { + throws(() => fs.opendirSync(), kInvalidArgTypeError); + throws(() => fs.opendirSync(123), kInvalidArgTypeError); + throws(() => fs.opendirSync('/tmp', { encoding: 123 }), kInvalidArgValueError); + + const dir = fs.opendirSync('/', { recursive: true }); + strictEqual(dir.path, '/'); + strictEqual(dir.readSync().name, 'bundle'); + strictEqual(dir.readSync().name, 'bundle/worker'); + strictEqual(dir.readSync().name, 'tmp'); + strictEqual(dir.readSync().name, 'dev'); + strictEqual(dir.readSync().name, 'dev/null'); + strictEqual(dir.readSync().name, 'dev/zero'); + strictEqual(dir.readSync().name, 'dev/full'); + strictEqual(dir.readSync().name, 'dev/random'); + strictEqual(dir.readSync(), null); // All done. + dir.closeSync(); + + // Closing again throws + throws(() => dir.closeSync(), { code: 'ERR_DIR_CLOSED' }); + // Reading again throws + throws(() => dir.readSync(), { code: 'ERR_DIR_CLOSED' }); + }, +}; \ No newline at end of file diff --git a/workerd-corpus-fs/fs-promises.js b/workerd-corpus-fs/fs-promises.js new file mode 100644 index 000000000..feabad2c6 --- /dev/null +++ b/workerd-corpus-fs/fs-promises.js @@ -0,0 +1,10 @@ +function promises() { + // fs.promises API + let path = '/tmp/promise.txt'; + fs.promises.writeFile(path, 'promise data') + .then(() => fs.promises.readFile(path)) + .then(() => fs.promises.unlink(path)) + .catch(() => { }); +} + +promises(); \ No newline at end of file diff --git a/workerd-corpus-fs/fs-readstream-test.js b/workerd-corpus-fs/fs-readstream-test.js new file mode 100644 index 000000000..0b86eb5ad --- /dev/null +++ b/workerd-corpus-fs/fs-readstream-test.js @@ -0,0 +1,1038 @@ +strictEqual(typeof fs.ReadStream, 'function'); +strictEqual(typeof fs.createReadStream, 'function'); + +const simpleReadStreamTest = { + async test() { + const largeData = 'abc'.repeat(100_000); + fs.writeFileSync('/tmp/foo', largeData); + + const stream = fs.createReadStream('/tmp/foo', { + encoding: 'utf8', + highWaterMark: 10_000, + }); + + let data = ''; + for await (const chunk of stream) { + data += chunk; + } + strictEqual(data, largeData); + }, +}; + +function prepareFile() { + const path = '/tmp/elipses.txt'; + fs.writeFileSync(path, '…'.repeat(10000)); + return path; +} + +async function runTest(options) { + let paused = false; + let bytesRead = 0; + + const path = prepareFile(); + + const file = fs.createReadStream(path, options); + const fileSize = fs.statSync(path).size; + + strictEqual(file.bytesRead, 0); + + const promises = []; + + const { promise: openPromise, resolve: openResolve } = + Promise.withResolvers(); + const { promise: endPromise, resolve: endResolve } = Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + promises.push(openPromise); + promises.push(endPromise); + promises.push(closePromise); + + const onOpen = mock.fn((fd) => { + file.length = 0; + strictEqual(typeof fd, 'number'); + strictEqual(file.bytesRead, 0); + ok(file.readable); + file.pause(); + file.resume(); + file.pause(); + file.resume(); + openResolve(); + }); + + const onData = mock.fn((data) => { + ok(data instanceof Buffer); + ok(data.byteOffset % 8 === 0); + ok(!paused); + file.length += data.length; + + bytesRead += data.length; + strictEqual(file.bytesRead, bytesRead); + + paused = true; + file.pause(); + + setTimeout(function () { + paused = false; + file.resume(); + }, 10); + }); + + const onEnd = mock.fn(() => { + strictEqual(bytesRead, fileSize); + strictEqual(file.bytesRead, fileSize); + endResolve(); + }); + + const onClose = mock.fn(() => { + strictEqual(bytesRead, fileSize); + strictEqual(file.bytesRead, fileSize); + closeResolve(); + }); + + file.once('open', onOpen); + file.once('end', onEnd); + file.once('close', onClose); + file.on('data', onData); + + await Promise.all(fs.promises); + + strictEqual(file.length, 30000); + strictEqual(onOpen.mock.callCount(), 1); + strictEqual(onEnd.mock.callCount(), 1); + strictEqual(onClose.mock.callCount(), 1); + strictEqual(onData.mock.callCount(), 1); +} + +const readStreamTest1 = { + async test() { + await runTest({}); + }, +}; + +const readStreamTest2 = { + async test() { + const customFs = { + open: mock.fn((...args) => fs.open(...args)), + read: mock.fn((...args) => fs.read(...args)), + close: mock.fn((...args) => fs.close(...args)), + }; + await runTest({ + fs: customFs, + }); + strictEqual(customFs.open.mock.callCount(), 1); + strictEqual(customFs.read.mock.callCount(), 2); + strictEqual(customFs.close.mock.callCount(), 1); + }, +}; + +const readStreamTest3 = { + async test() { + const path = prepareFile(); + const file = fs.createReadStream(path, { encoding: 'utf8' }); + file.length = 0; + file.on('data', function (data) { + strictEqual(typeof data, 'string'); + file.length += data.length; + + for (let i = 0; i < data.length; i++) { + // http://www.fileformat.info/info/unicode/char/2026/index.htm + strictEqual(data[i], '\u2026'); + } + }); + + const { promise, resolve } = Promise.withResolvers(); + file.on('close', resolve); + + await promise; + + strictEqual(file.length, 10000); + }, +}; + +const readStreamTest4 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz'); + const file = fs.createReadStream(path, { bufferSize: 1, start: 1, end: 2 }); + let contentRead = ''; + file.on('data', function (data) { + contentRead += data.toString('utf-8'); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on('end', function (data) { + strictEqual(contentRead, 'yz'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest5 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const file = fs.createReadStream(path, { bufferSize: 1, start: 1 }); + file.data = ''; + file.on('data', function (data) { + file.data += data.toString('utf-8'); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on('end', function () { + strictEqual(file.data, 'yz\n'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest6 = { + async test() { + // Ref: https://github.com/nodejs/node-v0.x-archive/issues/2320 + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const file = fs.createReadStream(path, { bufferSize: 1.23, start: 1 }); + file.data = ''; + file.on('data', function (data) { + file.data += data.toString('utf-8'); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on('end', function () { + strictEqual(file.data, 'yz\n'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest7 = { + test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + throws(() => fs.createReadStream(path, { start: 10, end: 2 }), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); + }, +}; + +const readStreamTest8 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const stream = fs.createReadStream(path, { start: 0, end: 0 }); + stream.data = ''; + + stream.on('data', function (chunk) { + stream.data += chunk; + }); + const { promise, resolve } = Promise.withResolvers(); + stream.on('end', function () { + strictEqual(stream.data, 'x'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest9 = { + async test() { + // Verify that end works when start is not specified. + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const stream = new fs.createReadStream(path, { end: 1 }); + stream.data = ''; + + stream.on('data', function (chunk) { + stream.data += chunk; + }); + + const { promise, resolve } = Promise.withResolvers(); + stream.on('end', function () { + strictEqual(stream.data, 'xy'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest10 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + let file = fs.createReadStream(path, { autoClose: false }); + let data = ''; + file.on('data', function (chunk) { + data += chunk; + }); + const { promise, resolve, reject } = Promise.withResolvers(); + file.on('end', function () { + strictEqual(data, 'xyz\n'); + process.nextTick(function () { + ok(!file.closed); + ok(!file.destroyed); + fileNext().then(resolve, reject); + }); + }); + + await promise; + + async function fileNext() { + // This will tell us if the fd is usable again or not. + file = fs.createReadStream(null, { fd: file.fd, start: 0 }); + file.data = ''; + file.on('data', function (data) { + file.data += data; + }); + const { promise, resolve } = Promise.withResolvers(); + const { promise: endPromise, resolve: endResolve } = + Promise.withResolvers(); + file.on('end', function (err) { + strictEqual(file.data, 'xyz\n'); + endResolve(); + }); + file.on('close', resolve); + await Promise.all([promise, endPromise]); + ok(file.closed); + ok(file.destroyed); + } + }, +}; + +const readStreamTest11 = { + async test() { + // Just to make sure autoClose won't close the stream because of error. + const { promise, resolve, reject } = Promise.withResolvers(); + const file = fs.createReadStream(null, { fd: 13337, autoClose: false }); + file.on('data', () => reject(new Error('should not be called'))); + file.on('error', resolve); + await promise; + ok(!file.closed); + ok(!file.destroyed); + ok(file.fd); + }, +}; + +const readStreamTest12 = { + async test() { + // Make sure stream is destroyed when file does not exist. + const file = fs.createReadStream('/path/to/file/that/does/not/exist'); + const { promise, resolve, reject } = Promise.withResolvers(); + file.on('data', () => reject(new Error('should not be called'))); + file.on('error', resolve); + await promise; + ok(file.closed); + ok(file.destroyed); + }, +}; + +const readStreamTest13 = { + async test() { + const example = '/tmp/x.txt'; + fs.writeFileSync(example, 'xyz\n'); + fs.createReadStream(example, undefined); + fs.createReadStream(example, null); + fs.createReadStream(example, 'utf8'); + fs.createReadStream(example, { encoding: 'utf8' }); + + const createReadStreamErr = (path, opt, error) => { + throws(() => fs.createReadStream(path, opt), error); + }; + + const typeError = { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }; + + const rangeError = { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }; + + [123, 0, true, false].forEach((opts) => + createReadStreamErr(example, opts, typeError) + ); + + // Case 0: Should not throw if either start or end is undefined + [{}, { start: 0 }, { end: Infinity }].forEach((opts) => + fs.createReadStream(example, opts) + ); + + // Case 1: Should throw TypeError if either start or end is not of type 'number' + [ + { start: 'invalid' }, + { end: 'invalid' }, + { start: 'invalid', end: 'invalid' }, + ].forEach((opts) => createReadStreamErr(example, opts, typeError)); + + // Case 2: Should throw RangeError if either start or end is NaN + [{ start: NaN }, { end: NaN }, { start: NaN, end: NaN }].forEach((opts) => + createReadStreamErr(example, opts, rangeError) + ); + + // Case 3: Should throw RangeError if either start or end is negative + [{ start: -1 }, { end: -1 }, { start: -1, end: -1 }].forEach((opts) => + createReadStreamErr(example, opts, rangeError) + ); + + // Case 4: Should throw RangeError if either start or end is fractional + [{ start: 0.1 }, { end: 0.1 }, { start: 0.1, end: 0.1 }].forEach((opts) => + createReadStreamErr(example, opts, rangeError) + ); + + // Case 5: Should not throw if both start and end are whole numbers + fs.createReadStream(example, { start: 1, end: 5 }); + + // Case 6: Should throw RangeError if start is greater than end + createReadStreamErr(example, { start: 5, end: 1 }, rangeError); + + // Case 7: Should throw RangeError if start or end is not safe integer + const NOT_SAFE_INTEGER = 2 ** 53; + [ + { start: NOT_SAFE_INTEGER, end: Infinity }, + { start: 0, end: NOT_SAFE_INTEGER }, + ].forEach((opts) => createReadStreamErr(example, opts, rangeError)); + }, +}; + +const readStreamTest14 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + let data = ''; + let first = true; + + const stream = fs.createReadStream(path); + stream.setEncoding('utf8'); + stream.on('data', function (chunk) { + data += chunk; + if (first) { + first = false; + stream.resume(); + } + }); + + const { promise, resolve } = Promise.withResolvers(); + + process.nextTick(function () { + stream.pause(); + setTimeout(function () { + stream.resume(); + resolve(); + }, 100); + }); + + await promise; + strictEqual(data, 'xyz\n'); + }, +}; + +const readStreamTest15 = { + async test() { + const { promise, resolve } = Promise.withResolvers(); + fs.ReadStream.prototype.open = resolve; + fs.createReadStream('asd'); + await promise; + delete fs.ReadStream.prototype.open; + }, +}; + +// const fn = fixtures.path('elipses.txt'); +// const rangeFile = fixtures.path('x.txt'); + +const readStreamTest16 = { + async test() { + let paused = false; + const path = prepareFile(); + + const file = fs.ReadStream(path); + + const promises = []; + const { promise: openPromise, resolve: openResolve } = + Promise.withResolvers(); + const { promise: endPromise, resolve: endResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + promises.push(openPromise); + promises.push(endPromise); + promises.push(closePromise); + + file.on('open', function (fd) { + file.length = 0; + strictEqual(typeof fd, 'number'); + ok(file.readable); + + // GH-535 + file.pause(); + file.resume(); + file.pause(); + file.resume(); + openResolve(); + }); + + file.on('data', function (data) { + ok(data instanceof Buffer); + ok(!paused); + file.length += data.length; + + paused = true; + file.pause(); + + setTimeout(function () { + paused = false; + file.resume(); + }, 10); + }); + + file.on('end', endResolve); + + file.on('close', function () { + strictEqual(file.length, 30000); + closeResolve(); + }); + + await Promise.all(fs.promises); + }, +}; + +const readStreamTest17 = { + async test() { + const path = prepareFile(); + const file = fs.createReadStream(path, { __proto__: { encoding: 'utf8' } }); + file.length = 0; + file.on('data', function (data) { + strictEqual(typeof data, 'string'); + file.length += data.length; + + for (let i = 0; i < data.length; i++) { + // http://www.fileformat.info/info/unicode/char/2026/index.htm + strictEqual(data[i], '\u2026'); + } + }); + + const { promise, resolve } = Promise.withResolvers(); + file.on('close', function () { + strictEqual(file.length, 10000); + resolve(); + }); + + await promise; + }, +}; + +const readStreamTest18 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const options = { __proto__: { bufferSize: 1, start: 1, end: 2 } }; + const file = fs.createReadStream(path, options); + strictEqual(file.start, 1); + strictEqual(file.end, 2); + let contentRead = ''; + file.on('data', function (data) { + contentRead += data.toString('utf-8'); + }); + + const { promise, resolve } = Promise.withResolvers(); + file.on('end', function () { + strictEqual(contentRead, 'yz'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest19 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const options = { __proto__: { bufferSize: 1, start: 1 } }; + const file = fs.createReadStream(path, options); + strictEqual(file.start, 1); + file.data = ''; + file.on('data', function (data) { + file.data += data.toString('utf-8'); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on('end', function () { + strictEqual(file.data, 'yz\n'); + resolve(); + }); + await promise; + }, +}; + +// https://github.com/joyent/node/issues/2320 +const readStreamTest20 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const options = { __proto__: { bufferSize: 1.23, start: 1 } }; + const file = fs.createReadStream(path, options); + strictEqual(file.start, 1); + file.data = ''; + file.on('data', function (data) { + file.data += data.toString('utf-8'); + }); + const { promise, resolve } = Promise.withResolvers(); + file.on('end', function () { + strictEqual(file.data, 'yz\n'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest21 = { + test() { + const path = '/tmp/x.txt'; + throws(() => fs.createReadStream(path, { __proto__: { start: 10, end: 2 } }), { + code: 'ERR_OUT_OF_RANGE', + name: 'RangeError', + }); + }, +}; + +const readStreamTest22 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const options = { __proto__: { start: 0, end: 0 } }; + const stream = fs.createReadStream(path, options); + strictEqual(stream.start, 0); + strictEqual(stream.end, 0); + stream.data = ''; + + stream.on('data', function (chunk) { + stream.data += chunk; + }); + + const { promise, resolve } = Promise.withResolvers(); + stream.on('end', function () { + strictEqual(stream.data, 'x'); + resolve(); + }); + await promise; + }, +}; + +const readStreamTest23 = { + async test() { + const path = '/tmp/x.txt'; + let output = ''; + fs.writeFileSync(path, 'hello world'); + const fd = fs.openSync(path, 'r'); + const stream = fs.createReadStream(null, { fd: fd, encoding: 'utf8' }); + strictEqual(stream.path, undefined); + stream.on('data', (data) => { + output += data; + }); + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', resolve); + await promise; + strictEqual(output, 'hello world'); + }, +}; + +const readStreamTest24 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'hello world'); + const stream = fs.createReadStream(path); + const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); + const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); + stream.close(resolve1); + stream.close(resolve2); + await Promise.all([promise1, promise2]); + }, +}; + +const readStreamTest25 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'hello world'); + const stream = fs.createReadStream(path); + const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); + const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); + stream.destroy(null, resolve1); + stream.destroy(null, resolve2); + await Promise.all([promise1, promise2]); + }, +}; + +const readStreamTest26 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'hello world'); + const fh = await fs.promises.open(path, 'r'); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + fh.on('close', closeResolve); + const stream = fs.createReadStream(null, { fd: fh, encoding: 'utf8' }); + let data = ''; + stream.on('data', (chunk) => (data += chunk)); + const { promise, resolve } = Promise.withResolvers(); + stream.on('end', () => resolve()); + await Promise.all([promise, closePromise]); + strictEqual(data, 'hello world'); + strictEqual(fh.fd, undefined); + }, +}; + +const readStreamTest27 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + const { promise, resolve, reject } = Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + handle.on('close', resolve); + const stream = fs.createReadStream(null, { fd: handle }); + stream.on('data', reject); + stream.on('close', closeResolve); + handle.close(); + await Promise.all([[promise, closePromise]]); + }, +}; + +const readStreamTest28 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + const { promise: handlePromise, resolve: handleResolve } = + Promise.withResolvers(); + const { promise: streamPromise, resolve: streamResolve } = + Promise.withResolvers(); + handle.on('close', handleResolve); + const stream = fs.createReadStream(null, { fd: handle }); + stream.on('close', streamResolve); + stream.on('data', () => handle.close()); + await Promise.all([handlePromise, streamPromise]); + }, +}; + +const readStreamTest29 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + const { promise: handlePromise, resolve: handleResolve } = + Promise.withResolvers(); + const { promise: streamPromise, resolve: streamResolve } = + Promise.withResolvers(); + handle.on('close', handleResolve); + const stream = fs.createReadStream(null, { fd: handle }); + stream.on('close', streamResolve); + stream.close(); + await Promise.all([handlePromise, streamPromise]); + }, +}; + +const readStreamTest30 = { + async test() { + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + throws(() => fs.createReadStream(null, { fd: handle, fs: {} }), { + code: 'ERR_METHOD_NOT_IMPLEMENTED', + }); + handle.close(); + }, +}; + +const readStreamTest31 = { + async test() { + // AbortSignal option test + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + const controller = new AbortController(); + const { signal } = controller; + const stream = handle.fs.createReadStream({ signal }); + + stream.on('data', () => { + throw new Error('boom'); + }); + stream.on('end', () => { + throw new Error('boom'); + }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + stream.on('error', (err) => { + strictEqual(err.name, 'AbortError'); + errorResolve(); + }); + + handle.on('close', closeResolve); + stream.on('close', () => handle.close()); + + controller.abort(); + + await Promise.all([errorPromise, closePromise]); + }, +}; + +const readStreamTest32 = { + async test() { + // Already-aborted signal test + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + + const signal = AbortSignal.abort(); + const stream = handle.fs.createReadStream({ signal }); + + stream.on('data', () => { + throw new Error('boom'); + }); + stream.on('end', () => { + throw new Error('boom'); + }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + + stream.on('error', (err) => { + strictEqual(err.name, 'AbortError'); + errorResolve(); + }); + + handle.on('close', closeResolve); + stream.on('close', () => handle.close()); + + await Promise.all([errorPromise, closePromise]); + }, +}; + +const readStreamTest33 = { + async test() { + // Invalid signal type test + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + + for (const signal of [ + 1, + {}, + [], + '', + NaN, + 1n, + () => { }, + Symbol(), + false, + true, + ]) { + throws(() => handle.fs.createReadStream({ signal }), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + } + handle.close(); + }, +}; + +const readStreamTest34 = { + async test() { + // Custom abort reason test + const path = '/tmp/x.txt'; + fs.writeFileSync(path, 'xyz\n'); + const handle = await fs.promises.open(path, 'r'); + const controller = new AbortController(); + const { signal } = controller; + const reason = new Error('some silly abort reason'); + const stream = handle.fs.createReadStream({ signal }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: closePromise, resolve: closeResolve } = + Promise.withResolvers(); + + stream.on('error', (err) => { + strictEqual(err.name, 'AbortError'); + strictEqual(err.cause, reason); + errorResolve(); + }); + + handle.on('close', closeResolve); + stream.on('close', () => handle.close()); + + controller.abort(reason); + + await Promise.all([errorPromise, closePromise]); + }, +}; + +const emptyReadStreamTest = { + async test() { + fs.writeFileSync('/tmp/empty.txt', ''); + const stream = fs.createReadStream('/tmp/empty.txt'); + const { promise, resolve, reject } = Promise.withResolvers(); + stream.once('data', () => { + reject(new Error('should not emit data')); + }); + stream.once('end', resolve); + await promise; + strictEqual(stream.bytesRead, 0); + }, +}; + +const fileHandleReadableWebStreamTest = { + async test() { + fs.writeFileSync('/tmp/stream.txt', 'abcde'.repeat(1000)); + const fh = await fs.promises.open('/tmp/stream.txt', 'r'); + const stream = fh.readableWebStream(); + const enc = new TextEncoder(); + let data = ''; + for await (const chunk of stream) { + strictEqual(chunk instanceof Uint8Array, true); + data += new TextDecoder().decode(chunk); + } + strictEqual(data, 'abcde'.repeat(1000)); + strictEqual(fh.fd, undefined); + + // Should throw if the stream is closed. + throws(() => fh.readableWebStream(), { + code: 'EBADF', + }); + + const fh2 = await fs.promises.open('/tmp/stream.txt', 'r'); + const stream2 = fh2.readableWebStream({ autoClose: false }); + await fh2.close(); + const res = await stream2.getReader().read(); + strictEqual(res.done, true); + strictEqual(res.value, undefined); + }, +}; + +/** + * TODO(node-fs): Revisit + * Temporarily comment out. These are larger tests causing timeouts + * In CI. Will move them out to separate tests in a follow on PR +const readStreamTest98 = { + async test() { + const path = prepareFile(); + const content = fs.readFileSync(path); + + const N = 20; + let started = 0; + let done = 0; + + const arrayBuffers = new Set(); + const fs.promises = []; + + async function startRead() { + ++started; + const chunks = []; + const fs.promises = []; + const { promise, resolve } = Promise.withResolvers(); + fs.promises.push(promise); + fs.createReadStream(path) + .on('data', (chunk) => { + chunks.push(chunk); + arrayBuffers.add(chunk.buffer); + }) + .on('end', () => { + if (started < N) fs.promises.push(startRead()); + deepStrictEqual(Buffer.concat(chunks), content); + if (++done === N) { + const retainedMemory = [...arrayBuffers] + .map((ab) => ab.byteLength) + .reduce((a, b) => a + b); + ok( + retainedMemory / (N * content.length) <= 3, + `Retaining ${retainedMemory} bytes in ABs for ${N} ` + + `chunks of size ${content.length}` + ); + } + resolve(); + }); + await Promise.all(fs.promises); + } + + // Don’t start the reads all at once – that way we would have to allocate + // a large amount of memory upfront. + for (let i = 0; i < 6; ++i) { + fs.promises.push(startRead()); + } + await Promise.all(fs.promises); + }, +}; + +const readStreamTest99 = { + async test() { + const path = '/tmp/read_stream_pos_test.txt'; + fs.writeFileSync(path, ''); + + let counter = 0; + + const writeInterval = setInterval(() => { + counter = counter + 1; + const line = `hello at ${counter}\n`; + fs.writeFileSync(path, line, { flag: 'a' }); + }, 1); + + const hwm = 10; + let bufs = []; + let isLow = false; + let cur = 0; + let stream; + + const readInterval = setInterval(() => { + if (stream) return; + + stream = fs.createReadStream(path, { + highWaterMark: hwm, + start: cur, + }); + stream.on('data', (chunk) => { + cur += chunk.length; + bufs.push(chunk); + if (isLow) { + const brokenLines = Buffer.concat(bufs) + .toString() + .split('\n') + .filter((line) => { + const s = 'hello at'.slice(0, line.length); + if (line && !line.startsWith(s)) { + return true; + } + return false; + }); + strictEqual(brokenLines.length, 0); + exitTest(); + return; + } + if (chunk.length !== hwm) { + isLow = true; + } + }); + stream.on('end', () => { + stream = null; + isLow = false; + bufs = []; + }); + }, 10); + + // Time longer than 10 seconds to exit safely + await scheduler.wait(5_000); + + clearInterval(readInterval); + clearInterval(writeInterval); + + if (stream && !stream.destroyed) { + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', resolve); + stream.destroy(); + await promise; + } + }, +}; +**/ diff --git a/workerd-corpus-fs/fs-utimes-test.js b/workerd-corpus-fs/fs-utimes-test.js new file mode 100644 index 000000000..c6d60865f --- /dev/null +++ b/workerd-corpus-fs/fs-utimes-test.js @@ -0,0 +1,116 @@ +// Copyright (c) 2017-2025 Cloudflare, Inc. +// Licensed under the Apache 2.0 license found in the LICENSE file or at: +// https://opensource.org/licenses/Apache-2.0 +const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' }; + +function checkStat(path, mtimeMsCheck) { + const bigint = typeof mtimeMsCheck === 'bigint'; + const { atimeMs, mtimeMs, ctimeMs, birthtimeMs } = + typeof path === 'number' + ? ffs.statSync(path, { bigint }) + : fs.statSync(path, { bigint }); + strictEqual(mtimeMs, mtimeMsCheck); + strictEqual(ctimeMs, mtimeMsCheck); + strictEqual(atimeMs, bigint ? 0n : 0); + strictEqual(birthtimeMs, bigint ? 0n : 0); +} + +const utimesTest = { + async test() { + const fd = fs.openSync('/tmp/test.txt', 'w+'); + ok(existsSync('/tmp/test.txt')); + + checkStat(fd, 0n); + + fs.utimesSync('/tmp/test.txt', 1000, 2000); + checkStat(fd, 2000n); + + fs.utimesSync('/tmp/test.txt', 1000, new Date(0)); + checkStat(fd, 0); + + fs.utimesSync('/tmp/test.txt', 3000, '1970-01-01T01:00:00.000Z'); + checkStat(fd, 3600000n); + + fs.lutimesSync('/tmp/test.txt', 3000, 4000); + checkStat(fd, 4000n); + + fs.lutimesSync('/tmp/test.txt', 1000, new Date(0)); + checkStat(fd, 0); + + fs.lutimesSync('/tmp/test.txt', 3000, '1970-01-01T01:00:00.000Z'); + checkStat(fd, 3600000n); + + fs.futimesSync(fd, 5000, 6000); + checkStat(fd, 6000n); + + fs.futimesSync(fd, 1000, new Date(0)); + checkStat(fd, 0); + + fs.futimesSync(fd, 3000, '1970-01-01T01:00:00.000Z'); + checkStat(fd, 3600000n); + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fs.utims('/tmp/test.txt', 8000, new Date('not a valid date'), (err) => { + try { + ok(err); + strictEqual(err.name, 'TypeError'); + match(err.message, /The value cannot be converted/); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fs.utims('/tmp/test.txt', 8000, 9000, (err) => { + if (err) return reject(err); + try { + checkStat(fd, 9000n); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fs.lutimes('/tmp/test.txt', 8000, 10000, (err) => { + if (err) return reject(err); + try { + checkStat(fd, 10000n); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + { + const { promise, resolve, reject } = Promise.withResolvers(); + fs.futimes(fd, 7000, 11000, (err) => { + if (err) return reject(err); + try { + checkStat(fd, 11000n); + resolve(); + } catch (err) { + reject(err); + } + }); + await promise; + } + + await promises.fs.utims('/tmp/test.txt', 12000, 13000); + checkStat(fd, 13000n); + await promises.fs.lutimes('/tmp/test.txt', 14000, 15000); + checkStat(fd, 15000n); + + fs.closeSync(fd); + }, +}; diff --git a/workerd-corpus-fs/fs-webfs-api.js b/workerd-corpus-fs/fs-webfs-api.js new file mode 100644 index 000000000..881488003 --- /dev/null +++ b/workerd-corpus-fs/fs-webfs-api.js @@ -0,0 +1,118 @@ +// Web File System API testing - FileSystemDirectoryHandle and FileSystemFileHandle +// Tests WHATWG spec implementation and potential memory corruption in handle operations + +async function runWebFSTests() { + try { + // Test StorageManager API + let rootDir = await navigator.storage.getDirectory(); + console.log('Root directory name:', rootDir.name); + console.log('Root directory kind:', rootDir.kind); + + // Test bundle directory access + let bundleDir = await rootDir.getDirectoryHandle('bundle'); + console.log('Bundle directory name:', bundleDir.name); + + // Test directory iteration (potential for OOB in bundle files) + let entryCount = 0; + for await (let [name, handle] of bundleDir) { + console.log(`Bundle entry: ${name}, kind: ${handle.kind}`); + entryCount++; + + if (handle.kind === 'file' && entryCount <= 2) { + // Test file handle operations on bundle files + let fileHandle = handle; + let file = await fileHandle.getFile(); + console.log(`Bundle file ${name} size:`, file.size); + + // Test reading bundle file content (potential OOB reads) + let text = await file.text(); + console.log(`Bundle file ${name} content length:`, text.length); + + if (file.size > 0) { + let arrayBuffer = await file.arrayBuffer(); + console.log(`Bundle file ${name} buffer length:`, arrayBuffer.byteLength); + } + } + } + + // Test temp directory operations with Web FS API + let tempDir = await rootDir.getDirectoryHandle('tmp'); + let testFile = await tempDir.getFileHandle('webfs-test.txt', { create: true }); + console.log('Created file handle:', testFile.name); + + // Test FileSystemWritableFileStream + let writable = await testFile.createWritable(); + console.log('Created writable stream'); + + await writable.write('Hello Web FS!'); + await writable.write(new Uint8Array([65, 66, 67])); // ABC + await writable.close(); + console.log('Writable stream closed'); + + // Test reading the written file + let writtenFile = await testFile.getFile(); + let content = await writtenFile.text(); + console.log('Written content length:', content.length); + + // Test file handle operations with various write modes + let writable2 = await testFile.createWritable({ keepExistingData: true }); + await writable2.seek(0); + await writable2.write('REPLACED'); + await writable2.truncate(8); + await writable2.close(); + + let modifiedFile = await testFile.getFile(); + let modifiedContent = await modifiedFile.text(); + console.log('Modified content:', modifiedContent); + + // Test directory handle operations + let subDir = await tempDir.getDirectoryHandle('webfs-subdir', { create: true }); + let subFile = await subDir.getFileHandle('nested.txt', { create: true }); + + let subWritable = await subFile.createWritable(); + await subWritable.write('Nested file content'); + await subWritable.close(); + + // Test directory traversal and file access patterns + let subdirEntries = []; + for await (let [name, handle] of subDir) { + subdirEntries.push(name); + if (handle.kind === 'file') { + let file = await handle.getFile(); + console.log(`Subdir file ${name} size:`, file.size); + } + } + console.log('Subdir entries:', subdirEntries); + + // Test handle comparison + let sameFile = await subDir.getFileHandle('nested.txt'); + let isSame = await subFile.isSameEntry(sameFile); + console.log('Handle comparison result:', isSame); + + // Test error conditions + try { + await tempDir.getFileHandle('nonexistent-file'); + } catch (e) { + console.log('Expected file not found:', e.name); + } + + try { + await rootDir.getDirectoryHandle('invalid-dir'); + } catch (e) { + console.log('Expected dir not found:', e.name); + } + + // Test removal operations + await subFile.remove(); + await subDir.removeEntry('nested.txt').catch(() => console.log('File already removed')); + await tempDir.removeEntry('webfs-subdir', { recursive: true }); + await testFile.remove(); + + } catch (error) { + console.error('Web FS test error:', error.message, error.name); + } +} + +// Execute the Web FS tests +await runWebFSTests(); +console.log('Web File System API tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs-writestream-test.js b/workerd-corpus-fs/fs-writestream-test.js new file mode 100644 index 000000000..9f506aa62 --- /dev/null +++ b/workerd-corpus-fs/fs-writestream-test.js @@ -0,0 +1,491 @@ +strictEqual(typeof fs.WriteStream, 'function'); +strictEqual(typeof fs.createWriteStream, 'function'); + +const writeStreamTest1 = { + async test() { + const path = '/tmp/workerd-fs-fs.WriteStream-test1.txt'; + const { promise, resolve } = Promise.withResolvers(); + const stream = fs.WriteStream(path, { + fs: { + close(fd) { + ok(fd); + fs.closeSync(fd); + resolve(); + }, + }, + }); + stream.destroy(); + await promise; + }, +}; + +const writeStreamTest2 = { + async test() { + const path = '/tmp/workerd-fs-fs.WriteStream-test2.txt'; + const stream = fs.createWriteStream(path); + + const { promise, resolve, reject } = Promise.withResolvers(); + + stream.on('drain', reject); + stream.on('close', resolve); + stream.destroy(); + await promise; + }, +}; + +const writeStreamTest3 = { + async test() { + const path = '/tmp/workerd-fs-fs.WriteStream-test3.txt'; + const stream = fs.createWriteStream(path); + const { promise, resolve, reject } = Promise.withResolvers(); + stream.on('error', reject); + stream.on('close', resolve); + throws(() => stream.write(42), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + stream.destroy(); + await promise; + }, +}; + +const writeStreamTest4 = { + test() { + const example = '/tmp/workerd-fs-fs.WriteStream-test4.txt'; + fs.createWriteStream(example, undefined).end(); + fs.createWriteStream(example, null).end(); + fs.createWriteStream(example, 'utf8').end(); + fs.createWriteStream(example, { encoding: 'utf8' }).end(); + + const createWriteStreamErr = (path, opt) => { + throws(() => fs.createWriteStream(path, opt), { + code: 'ERR_INVALID_ARG_TYPE', + name: 'TypeError', + }); + }; + + createWriteStreamErr(example, 123); + createWriteStreamErr(example, 0); + createWriteStreamErr(example, true); + createWriteStreamErr(example, false); + }, +}; + +const writeStreamTest5 = { + async test() { + const { promise, resolve } = Promise.withResolvers(); + fs.WriteStream.prototype.open = resolve; + fs.createWriteStream('/tmp/test'); + await promise; + delete fs.WriteStream.prototype.open; + }, +}; + +const writeStreamTest6 = { + async test() { + const path = '/tmp/write-end-test0.txt'; + const fs = { + open: mock.fn(fs.open), + write: mock.fn(fs.write), + close: mock.fn(fs.close), + }; + const { promise, resolve } = Promise.withResolvers(); + const stream = fs.createWriteStream(path, { fs }); + stream.on('close', resolve); + stream.end('asd'); + + await promise; + strictEqual(fs.open.mock.callCount(), 1); + strictEqual(fs.write.mock.callCount(), 1); + strictEqual(fs.close.mock.callCount(), 1); + }, +}; + +const writeStreamTest7 = { + async test() { + const path = '/tmp/write-end-test1.txt'; + const fs = { + open: mock.fn(fs.open), + write: fs.write, + writev: mock.fn(fs.write), + close: mock.fn(fs.close), + }; + const stream = fs.createWriteStream(path, { fs }); + stream.write('asd'); + stream.write('asd'); + stream.write('asd'); + stream.end(); + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', resolve); + await promise; + + strictEqual(fs.open.mock.callCount(), 1); + strictEqual(fs.writev.mock.callCount(), 1); + strictEqual(fs.close.mock.callCount(), 1); + }, +}; + +let cnt = 0; +function nextFile() { + return `/tmp/${cnt++}.out`; +} + +const writeStreamTest8 = { + test() { + for (const flush of ['true', '', 0, 1, [], {}, Symbol()]) { + throws( + () => { + fs.createWriteStream(nextFile(), { flush }); + }, + { code: 'ERR_INVALID_ARG_TYPE' } + ); + } + }, +}; + +const writeStreamTest9 = { + async test() { + const fs = { + fsync: mock.fn(fs.fsync), + }; + const stream = fs.createWriteStream(nextFile(), { flush: true, fs }); + + const { promise, resolve, reject } = Promise.withResolvers(); + + stream.write('hello', (err) => { + if (err) return reject(); + stream.close((err) => { + if (err) return reject(err); + resolve(); + }); + }); + + await promise; + + strictEqual(fs.fsync.mock.callCount(), 1); + }, +}; + +const writeStreamTest10 = { + async test() { + const values = [undefined, null, false]; + const fs = { + fsync: mock.fn(fs.fsync), + }; + let cnt = 0; + + const { promise, resolve, reject } = Promise.withResolvers(); + + for (const flush of values) { + const file = nextFile(); + const stream = fs.createWriteStream(file, { flush }); + stream.write('hello world', (err) => { + if (err) return reject(err); + stream.close((err) => { + if (err) return reject(err); + strictEqual(fs.readFileSync(file, 'utf8'), 'hello world'); + cnt++; + if (cnt === values.length) { + strictEqual(fs.fsync.mock.callCount(), 0); + resolve(); + } + }); + }); + } + + await promise; + }, +}; + +const writeStreamTest11 = { + async test() { + const file = nextFile(); + const handle = await promises.open(file, 'w'); + const stream = handle.fs.createWriteStream({ flush: true }); + + const { promise, resolve, reject } = Promise.withResolvers(); + + stream.write('hello', (err) => { + if (err) return reject(err); + stream.close((err) => { + if (err) return reject(err); + strictEqual(fs.readFileSync(file, 'utf8'), 'hello'); + resolve(); + }); + }); + + await promise; + }, +}; + +const writeStreamTest12 = { + async test() { + const file = nextFile(); + const handle = await promises.open(file, 'w+'); + + const { promise, resolve } = Promise.withResolvers(); + handle.on('close', resolve); + const stream = fs.createWriteStream(null, { fd: handle }); + + stream.end('hello'); + stream.on('close', () => { + const output = fs.readFileSync(file, 'utf-8'); + strictEqual(output, 'hello'); + }); + + await promise; + }, +}; + +const writeStreamTest13 = { + async test() { + const file = nextFile(); + const handle = await promises.open(file, 'w+'); + let calls = 0; + const { write: originalWriteFunction, writev: originalWritevFunction } = + handle; + handle.write = mock.fn(handle.write.bind(handle)); + handle.writev = mock.fn(handle.writev.bind(handle)); + const stream = fs.createWriteStream(null, { fd: handle }); + stream.end('hello'); + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', () => { + console.log('test'); + ok(handle.write.mock.callCount() + handle.writev.mock.callCount() > 0); + resolve(); + }); + await promise; + }, +}; + +const writeStreamTest14 = { + async test() { + const path = '/tmp/out'; + + let writeCalls = 0; + const fs = { + write: mock.fn((...args) => { + switch (writeCalls++) { + case 0: { + return fs.write(...args); + } + case 1: { + args[args.length - 1](new Error('BAM')); + break; + } + default: { + // It should not be called again! + throw new Error('BOOM!'); + } + } + }), + close: mock.fn(fs.close), + }; + + const stream = fs.createWriteStream(path, { + highWaterMark: 10, + fs, + }); + + const { promise: errorPromise, resolve: errorResolve } = + Promise.withResolvers(); + const { promise: writePromise, resolve: writeResolve } = + Promise.withResolvers(); + + stream.on('error', (err) => { + strictEqual(stream.fd, null); + strictEqual(err.message, 'BAM'); + errorResolve(); + }); + + stream.write(Buffer.allocUnsafe(256), () => { + stream.write(Buffer.allocUnsafe(256), (err) => { + strictEqual(err.message, 'BAM'); + writeResolve(); + }); + }); + + await Promise.all([errorPromise, writePromise]); + }, +}; + +const writeStreamTest15 = { + async test() { + const file = '/tmp/write-end-test0.txt'; + const stream = fs.createWriteStream(file); + stream.end(); + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', resolve); + await promise; + }, +}; + +const writeStreamTest16 = { + async test() { + const file = '/tmp/write-end-test1.txt'; + const stream = fs.createWriteStream(file); + stream.end('a\n', 'utf8'); + const { promise, resolve } = Promise.withResolvers(); + stream.on('close', () => { + const content = fs.readFileSync(file, 'utf8'); + strictEqual(content, 'a\n'); + resolve(); + }); + await promise; + }, +}; + +const writeStreamTest17 = { + async test() { + const file = '/tmp/write-end-test2.txt'; + const stream = fs.createWriteStream(file); + stream.end(); + + const { promise: openPromise, resolve: openResolve } = + Promise.withResolvers(); + const { promise: finishPromise, resolve: finishResolve } = + Promise.withResolvers(); + stream.on('open', openResolve); + stream.on('finish', finishResolve); + await Promise.all([openPromise, finishPromise]); + }, +}; + +const writeStreamTest18 = { + async test() { + const examplePath = '/tmp/a'; + const dummyPath = '/tmp/b'; + const firstEncoding = 'base64'; + const secondEncoding = 'latin1'; + + const exampleReadStream = fs.createReadStream(examplePath, { + encoding: firstEncoding, + }); + + const dummyWriteStream = fs.createWriteStream(dummyPath, { + encoding: firstEncoding, + }); + + const { promise, resolve } = Promise.withResolvers(); + exampleReadStream.pipe(dummyWriteStream).on('finish', () => { + const assertWriteStream = new Writable({ + write: function (chunk, enc, next) { + const expected = Buffer.from('xyz\n'); + deepStrictEqual(expected, chunk); + }, + }); + assertWriteStream.setDefaultEncoding(secondEncoding); + fs.createReadStream(dummyPath, { + encoding: secondEncoding, + }) + .pipe(assertWriteStream) + .on('close', resolve); + }); + + await promise; + }, +}; + +const writeStreamTest19 = { + async test() { + const file = '/tmp/write-end-test3.txt'; + const stream = fs.createWriteStream(file); + const { promise: closePromise1, resolve: closeResolve1 } = + Promise.withResolvers(); + const { promise: closePromise2, resolve: closeResolve2 } = + Promise.withResolvers(); + stream.close(closeResolve1); + stream.close(closeResolve2); + await Promise.all([closePromise1, closePromise2]); + }, +}; + +const writeStreamTest20 = { + async test() { + const file = '/tmp/write-autoclose-opt1.txt'; + let stream = fs.createWriteStream(file, { flags: 'w+', autoClose: false }); + stream.write('Test1'); + stream.end(); + const { promise, resolve, reject } = Promise.withResolvers(); + stream.on('finish', () => { + stream.on('close', reject); + process.nextTick(() => { + strictEqual(stream.closed, false); + notStrictEqual(stream.fd, null); + resolve(); + }); + }); + await promise; + + const { promise: nextPromise, resolve: nextResolve } = + Promise.withResolvers(); + const stream2 = fs.createWriteStream(null, { fd: stream.fd, start: 0 }); + stream2.write('Test2'); + stream2.end(); + stream2.on('finish', () => { + strictEqual(stream2.closed, false); + stream2.on('close', () => { + strictEqual(stream2.fd, null); + strictEqual(stream2.closed, true); + nextResolve(); + }); + }); + + await nextPromise; + + const data = fs.readFileSync(file, 'utf8'); + strictEqual(data, 'Test2'); + }, +}; + +const writeStreamTest21 = { + async test() { + // This is to test success scenario where autoClose is true + const file = '/tmp/write-autoclose-opt2.txt'; + const stream = fs.createWriteStream(file, { autoClose: true }); + stream.write('Test3'); + stream.end(); + const { promise, resolve } = Promise.withResolvers(); + stream.on('finish', () => { + strictEqual(stream.closed, false); + stream.on('close', () => { + strictEqual(stream.fd, null); + strictEqual(stream.closed, true); + resolve(); + }); + }); + await promise; + }, +}; + +const writeStreamTest22 = { + test() { + throws(() => fs.WriteStream.prototype.autoClose, { + code: 'ERR_INVALID_THIS', + }); + }, +}; + +await simpleWriteStreamTest.test(); +await writeStreamTest1.test(); +await writeStreamTest2.test(); +await writeStreamTest3.test(); +writeStreamTest4.test(); +await writeStreamTest5.test(); +await writeStreamTest6.test(); +await writeStreamTest7.test(); +writeStreamTest8.test(); +await writeStreamTest9.test(); +await writeStreamTest10.test(); +await writeStreamTest11.test(); +await writeStreamTest12.test(); +await writeStreamTest13.test(); +await writeStreamTest14.test(); +await writeStreamTest15.test(); +await writeStreamTest16.test(); +await writeStreamTest17.test(); +await writeStreamTest18.test(); +await writeStreamTest19.test(); +await writeStreamTest20.test(); +await writeStreamTest21.test(); +writeStreamTest22.test(); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_basic_sync_ops.js b/workerd-corpus-fs/fs_basic_sync_ops.js new file mode 100644 index 000000000..a329f5b8c --- /dev/null +++ b/workerd-corpus-fs/fs_basic_sync_ops.js @@ -0,0 +1,50 @@ +// Basic synchronous filesystem operations targeting FileSystemModule C++ API +// Focus on stat, readdir, readFile operations with bundle and temp directories + +// Test basic stat operations on bundle directory (potential OOB reads) +let bundleStat = fs.statSync('/bundle'); +console.log('Bundle is directory:', bundleStat.isDirectory()); + +// Test readdir on bundle directory - critical for memory safety +let bundleFiles = fs.readdirSync('/bundle'); +console.log('Bundle files count:', bundleFiles.length); + +// Test reading bundle files with potential for OOB reads +if (bundleFiles.length > 0) { + let firstFile = bundleFiles[0]; + let content = fs.readFileSync(`/bundle/${firstFile}`); + console.log('First bundle file size:', content.length); + + // Test with different read positions and sizes + if (content.length > 10) { + let partial = fs.readFileSync(`/bundle/${firstFile}`, { encoding: 'utf8' }); + console.log('Content type:', typeof partial); + } +} + +// Test temp directory operations +fs.mkdirSync('/tmp/fuzz-test', { recursive: true }); +fs.writeFileSync('/tmp/fuzz-test/data.txt', 'Hello Fuzzer!'); + +let tempStat = fs.statSync('/tmp/fuzz-test/data.txt'); +console.log('Temp file size:', tempStat.size); + +let tempContent = fs.readFileSync('/tmp/fuzz-test/data.txt', 'utf8'); +console.log('Temp content:', tempContent); + +// Test file descriptor operations with potential for corruption +let fd = fs.openSync('/tmp/fuzz-test/data.txt', 'r+'); +let fdStat = fs.fstatSync(fd); +console.log('FD stat size:', fdStat.size); + +let buffer = Buffer.alloc(20); +let bytesRead = fs.readSync(fd, buffer, 0, 5, 0); +console.log('Bytes read via FD:', bytesRead); + +fs.closeSync(fd); + +// Cleanup +fs.unlinkSync('/tmp/fuzz-test/data.txt'); +fs.rmdirSync('/tmp/fuzz-test'); + +console.log('Sync operations test completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_buffer_operations.js b/workerd-corpus-fs/fs_buffer_operations.js new file mode 100644 index 000000000..14afd4159 --- /dev/null +++ b/workerd-corpus-fs/fs_buffer_operations.js @@ -0,0 +1,173 @@ +// Buffer and binary data operations testing +// Focus on potential buffer overflows, OOB reads, and memory corruption + +// Create various buffer sizes for testing +let smallBuffer = Buffer.alloc(10); +let mediumBuffer = Buffer.alloc(1024); +let largeBuffer = Buffer.alloc(64 * 1024); // 64KB + +// Test writing different buffer sizes +fs.writeFileSync('/tmp/small-buffer.bin', smallBuffer); +fs.writeFileSync('/tmp/medium-buffer.bin', mediumBuffer); + +// Fill buffers with test patterns +smallBuffer.fill(0xAA); +mediumBuffer.fill(0xBB); +largeBuffer.fill(0xCC); + +fs.writeFileSync('/tmp/pattern-small.bin', smallBuffer); +fs.writeFileSync('/tmp/pattern-medium.bin', mediumBuffer); +fs.writeFileSync('/tmp/pattern-large.bin', largeBuffer); + +// Test reading with various buffer configurations +let readBuffer = Buffer.alloc(100); + +// Test reading more than file size +let smallContent = fs.readFileSync('/tmp/pattern-small.bin'); +console.log('Small file size:', smallContent.length); + +let fd = fs.openSync('/tmp/pattern-medium.bin', 'r'); + +// Test various read configurations that might trigger boundary errors +try { + // Read at different offsets + fs.readSync(fd, readBuffer, 0, 50, 0); // Normal read + fs.readSync(fd, readBuffer, 50, 50, 100); // Read from middle + fs.readSync(fd, readBuffer, 0, 10, 1020); // Read near end + + // Test boundary conditions + fs.readSync(fd, readBuffer, 0, 1, 1023); // Last byte + + // Test reading beyond file end (should handle gracefully) + try { + fs.readSync(fd, readBuffer, 0, 100, 1000); // Beyond file + } catch (e) { + console.log('Beyond file read handled:', e.code); + } + +} catch (error) { + console.log('Read operation error:', error.code); +} + +fs.closeSync(fd); + +// Test writing with buffer overruns and underruns +let writeBuffer = Buffer.from('Test data for write operations'); +let writeFd = fs.openSync('/tmp/write-test.bin', 'w+'); + +// Test various write scenarios +try { + fs.writeSync(writeFd, writeBuffer, 0, writeBuffer.length, 0); + fs.writeSync(writeFd, writeBuffer, 0, 5, 100); // Write 5 bytes at offset 100 + + // Test writing with buffer boundaries + fs.writeSync(writeFd, writeBuffer, 10, 10, 50); + + // Test zero-length writes + fs.writeSync(writeFd, writeBuffer, 0, 0, 200); + +} catch (error) { + console.log('Write operation error:', error.code); +} + +// Test readv/writev operations (vectored I/O) +let vec1 = Buffer.from('Vector1'); +let vec2 = Buffer.from('Vector2'); +let vec3 = Buffer.from('Vector3'); + +try { + let bytesWritten = fs.writevSync(writeFd, [vec1, vec2, vec3], 300); + console.log('Vectored write bytes:', bytesWritten); +} catch (error) { + console.log('Writev error:', error.code); +} + +fs.closeSync(writeFd); + +// Test reading back with readv +let readFd = fs.openSync('/tmp/write-test.bin', 'r'); +let rvec1 = Buffer.alloc(7); +let rvec2 = Buffer.alloc(7); +let rvec3 = Buffer.alloc(7); + +try { + let bytesRead = fs.readvSync(readFd, [rvec1, rvec2, rvec3], 300); + console.log('Vectored read bytes:', bytesRead); + console.log('Read vectors:', rvec1.toString(), rvec2.toString(), rvec3.toString()); +} catch (error) { + console.log('Readv error:', error.code); +} + +fs.closeSync(readFd); + +// Test bundle file buffer operations (potential OOB reads) +let bundleFiles = fs.readdirSync('/bundle'); +if (bundleFiles.length > 0) { + let bundleFile = bundleFiles[0]; + let bundleContent = fs.readFileSync(`/bundle/${bundleFile}`); + + if (bundleContent.length > 0) { + // Test reading bundle content with different buffer sizes + let bundleFd = fs.openSync(`/bundle/${bundleFile}`, 'r'); + let tinyBuffer = Buffer.alloc(1); + let exactBuffer = Buffer.alloc(bundleContent.length); + let oversizedBuffer = Buffer.alloc(bundleContent.length + 100); + + // Test reading with exact size + try { + fs.readSync(bundleFd, exactBuffer, 0, bundleContent.length, 0); + console.log('Exact buffer read successful'); + } catch (e) { + console.log('Exact buffer read error:', e.code); + } + + // Test reading with oversized buffer (check for OOB) + try { + let bytesRead = fs.readSync(bundleFd, oversizedBuffer, 0, oversizedBuffer.length, 0); + console.log('Oversized buffer read bytes:', bytesRead); + } catch (e) { + console.log('Oversized buffer read error:', e.code); + } + + fs.closeSync(bundleFd); + } +} + +// Test TypedArray operations +let uint8Array = new Uint8Array(50); +let uint16Array = new Uint16Array(25); +let uint32Array = new Uint32Array(12); + +fs.writeFileSync('/tmp/uint8.bin', uint8Array); +fs.writeFileSync('/tmp/uint16.bin', uint16Array); +fs.writeFileSync('/tmp/uint32.bin', uint32Array); + +// Test reading into TypedArrays +let readArray = new Uint8Array(100); +let arrayFd = fs.openSync('/tmp/uint8.bin', 'r'); + +try { + let arrayBytesRead = fs.readSync(arrayFd, readArray, 0, readArray.length, 0); + console.log('TypedArray read bytes:', arrayBytesRead); +} catch (error) { + console.log('TypedArray read error:', error.code); +} + +fs.closeSync(arrayFd); + +// Cleanup +let cleanupFiles = [ + '/tmp/small-buffer.bin', '/tmp/medium-buffer.bin', + '/tmp/pattern-small.bin', '/tmp/pattern-medium.bin', '/tmp/pattern-large.bin', + '/tmp/write-test.bin', '/tmp/uint8.bin', '/tmp/uint16.bin', '/tmp/uint32.bin' +]; + +for (let file of cleanupFiles) { + try { + fs.unlinkSync(file); + } catch (e) { + console.log(`Cleanup error for ${file}:`, e.code); + } +} + +console.log('Buffer operations testing completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_bundle_boundary_tests.js b/workerd-corpus-fs/fs_bundle_boundary_tests.js new file mode 100644 index 000000000..a48f7ef16 --- /dev/null +++ b/workerd-corpus-fs/fs_bundle_boundary_tests.js @@ -0,0 +1,66 @@ +// Bundle file boundary testing - specifically targeting potential OOB reads +// Tests edge cases around bundle file access that could trigger memory corruption + +// Test bundle directory traversal +let bundleEntries = fs.readdirSync('/bundle', { recursive: true }); +console.log('Total bundle entries:', bundleEntries.length); + +// Test accessing bundle files with various path patterns +for (let entry of bundleEntries.slice(0, 3)) { + try { + let fullPath = `/bundle/${entry}`; + let stat = fs.statSync(fullPath); + + if (stat.isFile()) { + // Test reading at different positions to stress boundary conditions + let content = fs.readFileSync(fullPath); + console.log(`Bundle file ${entry} size:`, content.length); + + // Test partial reads that might trigger OOB + if (content.length > 0) { + let fd = fs.openSync(fullPath, 'r'); + let smallBuffer = Buffer.alloc(1); + let largeBuffer = Buffer.alloc(content.length + 100); // Intentionally oversized + + // Read at boundary positions + fs.readSync(fd, smallBuffer, 0, 1, 0); // First byte + if (content.length > 1) { + fs.readSync(fd, smallBuffer, 0, 1, content.length - 1); // Last byte + } + + // Test reading more than available (should be safe but test boundary) + try { + fs.readSync(fd, largeBuffer, 0, largeBuffer.length, 0); + } catch (e) { + console.log('Expected boundary error:', e.code); + } + + fs.closeSync(fd); + } + } + } catch (error) { + console.log(`Error accessing ${entry}:`, error.code); + } +} + +// Test invalid bundle paths (should fail gracefully) +try { + fs.readFileSync('/bundle/../../../etc/passwd'); +} catch (e) { + console.log('Path traversal blocked:', e.code); +} + +try { + fs.readFileSync('/bundle/nonexistent-file-xyz'); +} catch (e) { + console.log('Nonexistent file handled:', e.code); +} + +// Test bundle file with null bytes and special chars +try { + fs.readFileSync('/bundle/test\0file'); +} catch (e) { + console.log('Null byte path handled:', e.code); +} + +console.log('Bundle boundary tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_promises_api.js b/workerd-corpus-fs/fs_promises_api.js new file mode 100644 index 000000000..b3b1b90fd --- /dev/null +++ b/workerd-corpus-fs/fs_promises_api.js @@ -0,0 +1,88 @@ +// Promises-based filesystem API testing +// Tests async operations that could reveal race conditions or memory issues + +async function runPromiseTests() { + try { + // Test bundle access via promises + let bundleStat = await fs.promises.stat('/bundle'); + console.log('Async bundle stat:', bundleStat.isDirectory()); + + let bundleFiles = await fs.promises.readdir('/bundle'); + console.log('Async bundle files:', bundleFiles.length); + + // Test reading bundle files asynchronously + if (bundleFiles.length > 0) { + let firstFile = bundleFiles[0]; + let content = await fs.promises.readFile(`/bundle/${firstFile}`); + console.log('Async bundle file size:', content.length); + } + + // Test temp directory operations with promises + await fs.promises.mkdir('/tmp/async-test', { recursive: true }); + await fs.promises.writeFile('/tmp/async-test/data.bin', Buffer.from('Binary data test')); + + let tempStat = await fs.promises.stat('/tmp/async-test/data.bin'); + console.log('Async temp file size:', tempStat.size); + + // Test FileHandle operations (potential for fd leaks/corruption) + let fileHandle = await fs.promises.open('/tmp/async-test/data.bin', 'r+'); + console.log('FileHandle fd:', fileHandle.fd); + + let handleStat = await fileHandle.stat(); + console.log('FileHandle stat size:', handleStat.size); + + let readBuffer = Buffer.alloc(20); + let readResult = await fileHandle.read(readBuffer, 0, 10, 0); + console.log('FileHandle read bytes:', readResult.bytesRead); + + let writeBuffer = Buffer.from('ASYNC'); + let writeResult = await fileHandle.write(writeBuffer, 0, writeBuffer.length, handleStat.size); + console.log('FileHandle wrote bytes:', writeResult.bytesWritten); + + await fileHandle.close(); + console.log('FileHandle closed'); + + // Test concurrent async operations (stress test) + let promises = []; + for (let i = 0; i < 3; i++) { + promises.push( + fs.promises.writeFile(`/tmp/async-test/concurrent-${i}.txt`, `Concurrent data ${i}`) + ); + } + + await Promise.all(promises); + console.log('Concurrent writes completed'); + + // Test concurrent reads + let readPromises = []; + for (let i = 0; i < 3; i++) { + readPromises.push( + fs.promises.readFile(`/tmp/async-test/concurrent-${i}.txt`, 'utf8') + ); + } + + let results = await Promise.all(readPromises); + console.log('Concurrent reads completed:', results.length); + + // Test error handling with promises + try { + await fs.promises.readFile('/bundle/nonexistent-async-file'); + } catch (e) { + console.log('Async error handled:', e.code); + } + + // Cleanup + for (let i = 0; i < 3; i++) { + await fs.promises.unlink(`/tmp/async-test/concurrent-${i}.txt`); + } + await fs.promises.unlink('/tmp/async-test/data.bin'); + await fs.promises.rmdir('/tmp/async-test'); + + } catch (error) { + console.error('Promise test error:', error.message); + } +} + +// Execute the async tests +await runPromiseTests(); +console.log('Promise API tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_webfs_api.js b/workerd-corpus-fs/fs_webfs_api.js new file mode 100644 index 000000000..881488003 --- /dev/null +++ b/workerd-corpus-fs/fs_webfs_api.js @@ -0,0 +1,118 @@ +// Web File System API testing - FileSystemDirectoryHandle and FileSystemFileHandle +// Tests WHATWG spec implementation and potential memory corruption in handle operations + +async function runWebFSTests() { + try { + // Test StorageManager API + let rootDir = await navigator.storage.getDirectory(); + console.log('Root directory name:', rootDir.name); + console.log('Root directory kind:', rootDir.kind); + + // Test bundle directory access + let bundleDir = await rootDir.getDirectoryHandle('bundle'); + console.log('Bundle directory name:', bundleDir.name); + + // Test directory iteration (potential for OOB in bundle files) + let entryCount = 0; + for await (let [name, handle] of bundleDir) { + console.log(`Bundle entry: ${name}, kind: ${handle.kind}`); + entryCount++; + + if (handle.kind === 'file' && entryCount <= 2) { + // Test file handle operations on bundle files + let fileHandle = handle; + let file = await fileHandle.getFile(); + console.log(`Bundle file ${name} size:`, file.size); + + // Test reading bundle file content (potential OOB reads) + let text = await file.text(); + console.log(`Bundle file ${name} content length:`, text.length); + + if (file.size > 0) { + let arrayBuffer = await file.arrayBuffer(); + console.log(`Bundle file ${name} buffer length:`, arrayBuffer.byteLength); + } + } + } + + // Test temp directory operations with Web FS API + let tempDir = await rootDir.getDirectoryHandle('tmp'); + let testFile = await tempDir.getFileHandle('webfs-test.txt', { create: true }); + console.log('Created file handle:', testFile.name); + + // Test FileSystemWritableFileStream + let writable = await testFile.createWritable(); + console.log('Created writable stream'); + + await writable.write('Hello Web FS!'); + await writable.write(new Uint8Array([65, 66, 67])); // ABC + await writable.close(); + console.log('Writable stream closed'); + + // Test reading the written file + let writtenFile = await testFile.getFile(); + let content = await writtenFile.text(); + console.log('Written content length:', content.length); + + // Test file handle operations with various write modes + let writable2 = await testFile.createWritable({ keepExistingData: true }); + await writable2.seek(0); + await writable2.write('REPLACED'); + await writable2.truncate(8); + await writable2.close(); + + let modifiedFile = await testFile.getFile(); + let modifiedContent = await modifiedFile.text(); + console.log('Modified content:', modifiedContent); + + // Test directory handle operations + let subDir = await tempDir.getDirectoryHandle('webfs-subdir', { create: true }); + let subFile = await subDir.getFileHandle('nested.txt', { create: true }); + + let subWritable = await subFile.createWritable(); + await subWritable.write('Nested file content'); + await subWritable.close(); + + // Test directory traversal and file access patterns + let subdirEntries = []; + for await (let [name, handle] of subDir) { + subdirEntries.push(name); + if (handle.kind === 'file') { + let file = await handle.getFile(); + console.log(`Subdir file ${name} size:`, file.size); + } + } + console.log('Subdir entries:', subdirEntries); + + // Test handle comparison + let sameFile = await subDir.getFileHandle('nested.txt'); + let isSame = await subFile.isSameEntry(sameFile); + console.log('Handle comparison result:', isSame); + + // Test error conditions + try { + await tempDir.getFileHandle('nonexistent-file'); + } catch (e) { + console.log('Expected file not found:', e.name); + } + + try { + await rootDir.getDirectoryHandle('invalid-dir'); + } catch (e) { + console.log('Expected dir not found:', e.name); + } + + // Test removal operations + await subFile.remove(); + await subDir.removeEntry('nested.txt').catch(() => console.log('File already removed')); + await tempDir.removeEntry('webfs-subdir', { recursive: true }); + await testFile.remove(); + + } catch (error) { + console.error('Web FS test error:', error.message, error.name); + } +} + +// Execute the Web FS tests +await runWebFSTests(); +console.log('Web File System API tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/function-references.js b/workerd-corpus-fs/function-references.js new file mode 100644 index 000000000..620c7d352 --- /dev/null +++ b/workerd-corpus-fs/function-references.js @@ -0,0 +1,12 @@ +function refs() { + // Function reference variations + let writeFile = fs.writeFileSync; + let readFile = fs.readFileSync; + let deleteFile = fs.unlinkSync; + + let path = '/tmp/ref.txt'; + writeFile(path, 'function ref test'); + readFile(path); + deleteFile(path); +} +refs(); diff --git a/workerd-corpus-fs/invalid-args.js b/workerd-corpus-fs/invalid-args.js new file mode 100644 index 000000000..ff55eb320 --- /dev/null +++ b/workerd-corpus-fs/invalid-args.js @@ -0,0 +1,6 @@ +function testInv() { + fs.writeFileSync(null, 'data'); + fs.readSync(123, Buffer.alloc(10), -1); + fs.truncateSync('/tmp/test.txt', -5); +} +testInv(); \ No newline at end of file diff --git a/workerd-corpus-fs/large-buffers.js b/workerd-corpus-fs/large-buffers.js new file mode 100644 index 000000000..c7007d4e3 --- /dev/null +++ b/workerd-corpus-fs/large-buffers.js @@ -0,0 +1,9 @@ +function largeBuf() { + // Large buffer operations + let path = '/tmp/large.txt'; + let large = Buffer.alloc(1024, 65); + fs.writeFileSync(path, large); + let result = fs.readFileSync(path); + fs.unlinkSync(path); +} +largeBuf(); \ No newline at end of file diff --git a/workerd-corpus-fs/mixed-sync-async.js b/workerd-corpus-fs/mixed-sync-async.js new file mode 100644 index 000000000..d17211d61 --- /dev/null +++ b/workerd-corpus-fs/mixed-sync-async.js @@ -0,0 +1,12 @@ +function mixed() { + // Mix sync and async operations + let path = '/tmp/mixed.txt'; + fs.writeFileSync(path, 'sync'); + fs.readFile(path, (err, data) => { + if (!err) { + fs.appendFileSync(path, 'more'); + fs.unlink(path, () => { }); + } + }); +} +mixed(); \ No newline at end of file diff --git a/workerd-corpus-fs/multiple-fd-ops.js b/workerd-corpus-fs/multiple-fd-ops.js new file mode 100644 index 000000000..872a6d601 --- /dev/null +++ b/workerd-corpus-fs/multiple-fd-ops.js @@ -0,0 +1,15 @@ +function ops() { + // Multiple file descriptor operations + let path = '/tmp/multi-fd.txt'; + fs.writeFileSync(path, 'test data'); + + let fd1 = fs.openSync(path, 'r'); + let fd2 = fs.openSync(path, 'r'); + + fs.readSync(fd1, Buffer.alloc(4), 0, 4, 0); + fs.readSync(fd2, Buffer.alloc(4), 0, 4, 5); + + fs.closeSync(fd1); + fs.closeSync(fd2); + fs.unlinkSync(path); +} diff --git a/workerd-corpus-fs/open-modes.js b/workerd-corpus-fs/open-modes.js new file mode 100644 index 000000000..c4a55e1cb --- /dev/null +++ b/workerd-corpus-fs/open-modes.js @@ -0,0 +1,14 @@ +function modes() { + + // Different open modes + let fd; + try { + fd = fs.openSync('/tmp/modes.txt', 'r+'); + } catch (e) { + fd = fs.openSync('/tmp/modes.txt', 'w+'); + } + fs.writeSync(fd, 'mode test'); + fs.closeSync(fd); + fs.unlinkSync('/tmp/modes.txt'); +} +modes(); \ No newline at end of file diff --git a/workerd-corpus-fs/position-seeking.js b/workerd-corpus-fs/position-seeking.js new file mode 100644 index 000000000..4b9f643a5 --- /dev/null +++ b/workerd-corpus-fs/position-seeking.js @@ -0,0 +1,10 @@ +function seeking() { + // Position-based operations + let fd = fs.openSync('/tmp/pos.txt', 'w+'); + fs.writeSync(fd, 'ABCDEFGHIJ'); + let buf = Buffer.alloc(3); + fs.readSync(fd, buf, 0, 3, 2); + fs.closeSync(fd); + fs.unlinkSync('/tmp/pos.txt'); +} +seeking(); \ No newline at end of file diff --git a/workerd-corpus-fs/simple.js b/workerd-corpus-fs/simple.js new file mode 100644 index 000000000..bf6b81796 --- /dev/null +++ b/workerd-corpus-fs/simple.js @@ -0,0 +1 @@ +console.log("Hello"); diff --git a/workerd-corpus-fs/stat-operations.js b/workerd-corpus-fs/stat-operations.js new file mode 100644 index 000000000..fc17e3c25 --- /dev/null +++ b/workerd-corpus-fs/stat-operations.js @@ -0,0 +1,10 @@ +function statTest() { + // Stat operations + let path = '/tmp/stat.txt'; + fs.writeFileSync(path, 'test'); + let stats = fs.statSync(path); + stats.isFile(); + stats.isDirectory(); + fs.unlinkSync(path); +} +statTest(); \ No newline at end of file diff --git a/workerd-corpus-fs/truncate-operations.js b/workerd-corpus-fs/truncate-operations.js new file mode 100644 index 000000000..8169ab2bf --- /dev/null +++ b/workerd-corpus-fs/truncate-operations.js @@ -0,0 +1,11 @@ + + +// Truncate operations +function trunc() { + let path = '/tmp/truncate.txt'; + fs.writeFileSync(path, 'long content string'); + fs.truncateSync(path, 5); + fs.unlinkSync(path); +} + +trunc(); diff --git a/workerd-corpus-fs/unicode-paths.js b/workerd-corpus-fs/unicode-paths.js new file mode 100644 index 000000000..588a5c6ec --- /dev/null +++ b/workerd-corpus-fs/unicode-paths.js @@ -0,0 +1,2 @@ +// Unicode and special character paths +function unicode() { let path = '/tmp/🚀test.txt'; fs.writeFileSync(path, 'emoji path'); fs.readFileSync(path); console.log(path); fs.unlinkSync(path); } unicode(); \ No newline at end of file diff --git a/workerd-corpus-fs/vector-io.js b/workerd-corpus-fs/vector-io.js new file mode 100644 index 000000000..83b0503a8 --- /dev/null +++ b/workerd-corpus-fs/vector-io.js @@ -0,0 +1,11 @@ +function vecStuff() { + // Vector I/O operations (readv/writev) + let fd = fs.openSync('/tmp/vector.txt', 'w+'); + fs.writevSync(fd, [Buffer.from('part1'), Buffer.from('part2')]); + let buf1 = Buffer.alloc(5); + let buf2 = Buffer.alloc(5); + fs.readvSync(fd, [buf1, buf2], 0); + fs.closeSync(fd); + fs.unlinkSync('/tmp/vector.txt'); +} +vecStuff(); \ No newline at end of file From 50dce70ea7aabfe5499273e012ebdd403487f412 Mon Sep 17 00:00:00 2001 From: Martin Schwarzl Date: Mon, 18 Aug 2025 12:02:39 +0000 Subject: [PATCH 07/10] profile --- README.md | 6 + Sources/Fuzzilli/Compiler/Compiler.swift | 3 +- .../Fuzzilli/Compiler/JavaScriptParser.swift | 1 - Sources/Fuzzilli/Compiler/Parser/parser.js | 2 +- Sources/Fuzzilli/FuzzIL/Code.swift | 18 +- Sources/Fuzzilli/Fuzzer.swift | 10 +- .../FuzzilliCli/Profiles/WorkerdProfile.swift | 7 +- Sources/REPRLRun/main.swift | 64 +- Targets/workerd/README.md | 11 + Targets/workerd/fuzzbuild.sh | 1 + workerd-corpus-fs/append-operations.js | 14 - workerd-corpus-fs/async-callbacks.js | 17 - workerd-corpus-fs/basic-file-ops.js | 11 - workerd-corpus-fs/boundary-conditions.js | 10 - workerd-corpus-fs/buffer-edge-cases.js | 19 - workerd-corpus-fs/buffer-io.js | 9 - workerd-corpus-fs/computed-properties.js | 9 - workerd-corpus-fs/concurrent-operations.js | 14 - workerd-corpus-fs/constants-usage.js | 12 - workerd-corpus-fs/copy-rename.js | 10 - workerd-corpus-fs/dynamic-calls.js | 10 - workerd-corpus-fs/encoding-variations.js | 9 - workerd-corpus-fs/fd-operations.js | 9 - workerd-corpus-fs/fs-chown-chmod-test.js | 573 --------- workerd-corpus-fs/fs-dir-test.js | 839 ------------- workerd-corpus-fs/fs-promises.js | 10 - workerd-corpus-fs/fs-readstream-test.js | 1038 ----------------- workerd-corpus-fs/fs-utimes-test.js | 116 -- workerd-corpus-fs/fs-webfs-api.js | 118 -- workerd-corpus-fs/fs-writestream-test.js | 491 -------- workerd-corpus-fs/fs_basic_sync_ops.js | 50 - workerd-corpus-fs/fs_buffer_operations.js | 173 --- workerd-corpus-fs/fs_bundle_boundary_tests.js | 66 -- workerd-corpus-fs/fs_promises_api.js | 88 -- workerd-corpus-fs/fs_webfs_api.js | 118 -- workerd-corpus-fs/function-references.js | 12 - workerd-corpus-fs/invalid-args.js | 6 - workerd-corpus-fs/large-buffers.js | 9 - workerd-corpus-fs/mixed-sync-async.js | 12 - workerd-corpus-fs/multiple-fd-ops.js | 15 - workerd-corpus-fs/open-modes.js | 14 - workerd-corpus-fs/position-seeking.js | 10 - workerd-corpus-fs/simple.js | 1 - workerd-corpus-fs/stat-operations.js | 10 - workerd-corpus-fs/truncate-operations.js | 11 - workerd-corpus-fs/unicode-paths.js | 2 - workerd-corpus-fs/vector-io.js | 11 - 47 files changed, 52 insertions(+), 4017 deletions(-) create mode 100644 Targets/workerd/README.md create mode 100644 Targets/workerd/fuzzbuild.sh delete mode 100644 workerd-corpus-fs/append-operations.js delete mode 100644 workerd-corpus-fs/async-callbacks.js delete mode 100644 workerd-corpus-fs/basic-file-ops.js delete mode 100644 workerd-corpus-fs/boundary-conditions.js delete mode 100644 workerd-corpus-fs/buffer-edge-cases.js delete mode 100644 workerd-corpus-fs/buffer-io.js delete mode 100644 workerd-corpus-fs/computed-properties.js delete mode 100644 workerd-corpus-fs/concurrent-operations.js delete mode 100644 workerd-corpus-fs/constants-usage.js delete mode 100644 workerd-corpus-fs/copy-rename.js delete mode 100644 workerd-corpus-fs/dynamic-calls.js delete mode 100644 workerd-corpus-fs/encoding-variations.js delete mode 100644 workerd-corpus-fs/fd-operations.js delete mode 100644 workerd-corpus-fs/fs-chown-chmod-test.js delete mode 100644 workerd-corpus-fs/fs-dir-test.js delete mode 100644 workerd-corpus-fs/fs-promises.js delete mode 100644 workerd-corpus-fs/fs-readstream-test.js delete mode 100644 workerd-corpus-fs/fs-utimes-test.js delete mode 100644 workerd-corpus-fs/fs-webfs-api.js delete mode 100644 workerd-corpus-fs/fs-writestream-test.js delete mode 100644 workerd-corpus-fs/fs_basic_sync_ops.js delete mode 100644 workerd-corpus-fs/fs_buffer_operations.js delete mode 100644 workerd-corpus-fs/fs_bundle_boundary_tests.js delete mode 100644 workerd-corpus-fs/fs_promises_api.js delete mode 100644 workerd-corpus-fs/fs_webfs_api.js delete mode 100644 workerd-corpus-fs/function-references.js delete mode 100644 workerd-corpus-fs/invalid-args.js delete mode 100644 workerd-corpus-fs/large-buffers.js delete mode 100644 workerd-corpus-fs/mixed-sync-async.js delete mode 100644 workerd-corpus-fs/multiple-fd-ops.js delete mode 100644 workerd-corpus-fs/open-modes.js delete mode 100644 workerd-corpus-fs/position-seeking.js delete mode 100644 workerd-corpus-fs/simple.js delete mode 100644 workerd-corpus-fs/stat-operations.js delete mode 100644 workerd-corpus-fs/truncate-operations.js delete mode 100644 workerd-corpus-fs/unicode-paths.js delete mode 100644 workerd-corpus-fs/vector-io.js diff --git a/README.md b/README.md index cf083cce2..4e385ed1b 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,12 @@ Special thanks to all users of Fuzzilli who have reported bugs found by it! - [CVE-2020-1912](https://www.facebook.com/security/advisories/cve-2020-1912): Memory corruption when executing lazily compiled inner generator functions - [CVE-2020-1914](https://www.facebook.com/security/advisories/cve-2020-1914): Bytecode corruption when handling the SaveGeneratorLong instruction +#### [Workerd](https://github.com/cloudflare/workerd) +- [PR 4793](https://github.com/cloudflare/workerd/pull/4793): OOB write in writeSync due to missing bounds check +- [PR 4845](https://github.com/cloudflare/workerd/pull/4845): UAF in VFS file clone handling +- [PR 4828](https://github.com/cloudflare/workerd/pull/4828): Segmentation fault on undefined keys in DH crypto API. +- [PR 4853](https://github.com/cloudflare/workerd/pull/4853): Workerd hits illegal instruction due to missing branch in FileSystemModule::setLastModified. + ## Disclaimer This is not an officially supported Google product. diff --git a/Sources/Fuzzilli/Compiler/Compiler.swift b/Sources/Fuzzilli/Compiler/Compiler.swift index 4d010159b..43e8e58ff 100644 --- a/Sources/Fuzzilli/Compiler/Compiler.swift +++ b/Sources/Fuzzilli/Compiler/Compiler.swift @@ -1161,10 +1161,9 @@ public class JavaScriptCompiler { case .awaitExpression(let awaitExpression): // TODO await is also allowed at the top level of a module - /*if !contextAnalyzer.context.contains(.asyncFunction) { + if !contextAnalyzer.context.contains(.asyncFunction) { throw CompilerError.invalidNodeError("`await` is currently only supported in async functions") } - */ let argument = try compileExpression(awaitExpression.argument) return emit(Await(), withInputs: [argument]).output diff --git a/Sources/Fuzzilli/Compiler/JavaScriptParser.swift b/Sources/Fuzzilli/Compiler/JavaScriptParser.swift index b4874afc6..f6a12f15d 100644 --- a/Sources/Fuzzilli/Compiler/JavaScriptParser.swift +++ b/Sources/Fuzzilli/Compiler/JavaScriptParser.swift @@ -43,7 +43,6 @@ public class JavaScriptParser { do { try runParserScript(withArguments: []) } catch { - return nil } } diff --git a/Sources/Fuzzilli/Compiler/Parser/parser.js b/Sources/Fuzzilli/Compiler/Parser/parser.js index 211054bee..f42fe00dd 100644 --- a/Sources/Fuzzilli/Compiler/Parser/parser.js +++ b/Sources/Fuzzilli/Compiler/Parser/parser.js @@ -34,7 +34,7 @@ function tryReadFile(path) { // Parse the given JavaScript script and return an AST compatible with Fuzzilli's protobuf-based AST format. function parse(script, proto) { - let ast = Parser.parse(script, { allowAwaitOutsideFunction: true, plugins: ["topLevelAwait","v8intrinsic"] }); + let ast = Parser.parse(script, { plugins: ["v8intrinsic"] }); function assertNoError(err) { if (err) throw err; diff --git a/Sources/Fuzzilli/FuzzIL/Code.swift b/Sources/Fuzzilli/FuzzIL/Code.swift index f37d8c175..1e63b0ff9 100644 --- a/Sources/Fuzzilli/FuzzIL/Code.swift +++ b/Sources/Fuzzilli/FuzzIL/Code.swift @@ -239,21 +239,9 @@ public struct Code: Collection { throw FuzzilliError.codeVerificationError("variable \(input) is not visible anymore") } } - - if instr.op is Await { - // if !contextAnalyzer.context.contains(.asyncFunction) - // { - // if contextAnalyzer.context.contains(.subroutine) { - // if !contextAnalyzer.context.contains(.method) && !contextAnalyzer.context.contains(.classMethod) && !contextAnalyzer.context.contains(.javascript) { - // throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") - // } - // } - // } - // fall-through allow top-level await - } else { - guard instr.op.requiredContext.isSubset(of: contextAnalyzer.context) else { - throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") - } + + guard instr.op.requiredContext.isSubset(of: contextAnalyzer.context) else { + throw FuzzilliError.codeVerificationError("operation \(instr.op.name) inside an invalid context") } // Ensure that the instruction exists in the right context diff --git a/Sources/Fuzzilli/Fuzzer.swift b/Sources/Fuzzilli/Fuzzer.swift index 8752b725d..58dddc1ee 100644 --- a/Sources/Fuzzilli/Fuzzer.swift +++ b/Sources/Fuzzilli/Fuzzer.swift @@ -431,6 +431,7 @@ public class Fuzzer { } let execution = execute(program, purpose: .programImport) + var wasImported = false switch execution.outcome { case .crashed(let termsig): @@ -673,15 +674,6 @@ public class Fuzzer { let execution = runner.run(script, withTimeout: timeout ?? config.timeout) dispatchEvent(events.PostExecute, data: execution) - //Stdout - // if !execution.stdout.isEmpty { - // print(execution.stdout) - // } - - // if !execution.stderr.isEmpty { - // print(execution.stderr) - // } - return execution } diff --git a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift index 21ad1e4c5..1c45652b5 100644 --- a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift +++ b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,9 +15,8 @@ import Fuzzilli let workerdProfile = Profile( - processArgs: { randomize in - ["--reprl-fuzzilli"] - }, + processArgs: { randomize in ["fuzzilli"] }, + processEnv: [:], diff --git a/Sources/REPRLRun/main.swift b/Sources/REPRLRun/main.swift index f8ec75bf0..41fa352f6 100644 --- a/Sources/REPRLRun/main.swift +++ b/Sources/REPRLRun/main.swift @@ -1,8 +1,21 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + import Foundation import libreprl func convertToCArray(_ array: [String]) -> UnsafeMutablePointer?> { - print("Converting array to C array: \(array)") let buffer = UnsafeMutablePointer?>.allocate(capacity: array.count + 1) for (i, str) in array.enumerated() { buffer[i] = UnsafePointer(str.withCString(strdup)) @@ -11,25 +24,12 @@ func convertToCArray(_ array: [String]) -> UnsafeMutablePointer (status: Int32, exec_time: UInt64) { var exec_time: UInt64 = 0 var status: Int32 = 0 - print("Executing script: \(script)") script.withCString { ptr in status = reprl_execute(ctx, ptr, UInt64(script.utf8.count), 1_000_000, &exec_time, 0) } - print("Execution result: status = \(status), exec_time = \(exec_time)") - printREPRLOutput(ctx) return (status, exec_time) } @@ -65,32 +56,29 @@ func runREPRLTests() { var numFailures = 0 func expect_success(_ code: String) { - print("Expecting success for code: \(code)") if execute(code).status != 0 { print("Execution of \"\(code)\" failed") numFailures += 1 - } else { - print("Success for code: \(code)") } } func expect_failure(_ code: String) { - print("Expecting failure for code: \(code)") if execute(code).status == 0 { print("Execution of \"\(code)\" unexpectedly succeeded") numFailures += 1 - } else { - print("Failure as expected for code: \(code)") } } expect_success("42") expect_failure("throw 42") + // Verify that existing state is property reset between executions expect_success("globalProp = 42; Object.prototype.foo = \"bar\";") expect_success("if (typeof(globalProp) !== 'undefined') throw 'failure'") expect_success("if (typeof(({}).foo) !== 'undefined') throw 'failure'") + // Verify that rejected promises are properly reset between executions + // Only if async functions are available if execute("async function foo() {}").status == 0 { expect_failure("async function fail() { throw 42; }; fail()") expect_success("42") @@ -101,19 +89,17 @@ func runREPRLTests() { if numFailures == 0 { print("All tests passed!") } else { - print("Not all tests passed. REPRL support may not be properly implemented.") + print("Not all tests passed. That means REPRL support likely isn't properly implemented in the target engine") } } -print("Checking if REPRL works...") +// Check whether REPRL works at all if execute("").status != 0 { - print("Initial script execution failed, REPRL support does not appear to be working") - printREPRLOutput(ctx) + print("Script execution failed, REPRL support does not appear to be working") exit(1) -} else { - print("Initial REPRL check passed.") } +// Run a couple of tests now runREPRLTests() print("Enter code to run, then hit enter to execute it") @@ -124,15 +110,15 @@ while true { break } - print("Executing user input code...") let (status, exec_time) = execute(code) if status < 0 { print("Error during script execution: \(String(cString: reprl_get_last_error(ctx))). REPRL support in the target probably isn't working correctly...") - printREPRLOutput(ctx) continue } print("Execution finished with status \(status) (signaled: \(RIFSIGNALED(status) != 0), timed out: \(RIFTIMEDOUT(status) != 0)) and took \(exec_time / 1000)ms") + print("========== Fuzzout ==========\n\(String(cString: reprl_fetch_fuzzout(ctx)))") + print("========== Stdout ==========\n\(String(cString: reprl_fetch_stdout(ctx)))") + print("========== Stderr ==========\n\(String(cString: reprl_fetch_stderr(ctx)))") } - diff --git a/Targets/workerd/README.md b/Targets/workerd/README.md new file mode 100644 index 000000000..ab0bb858d --- /dev/null +++ b/Targets/workerd/README.md @@ -0,0 +1,11 @@ +# Target: workerd + +To build workerd for fuzzing: + +0. Clone [workerd](https://github.com/cloudflare/workerd/) +1. Follow the instructions [here](https://github.com/cloudflare/workerd/blob/main/README.md#getting-started) +2. Run the fuzzbuild.sh script in the workerd root directory to build workerd with the fuzzili configuration +3. Test if REPRL works: + `swift run REPRLRun fuzzilli --experimental` +4. Run Fuzzilli: + `swift run -c release FuzzilliCli --inspect=all --profile=workerd --additionalArguments=,--experimental` diff --git a/Targets/workerd/fuzzbuild.sh b/Targets/workerd/fuzzbuild.sh new file mode 100644 index 000000000..1ef748fd5 --- /dev/null +++ b/Targets/workerd/fuzzbuild.sh @@ -0,0 +1 @@ +bazel --nohome_rc --nosystem_rc build --config=fuzzilli //src/workerd/server:workerd --action_env=CC=clang-19 diff --git a/workerd-corpus-fs/append-operations.js b/workerd-corpus-fs/append-operations.js deleted file mode 100644 index 3ba6780ed..000000000 --- a/workerd-corpus-fs/append-operations.js +++ /dev/null @@ -1,14 +0,0 @@ - -function testAppend() { - // Append operations - let data = "This is a file containing a collection"; - var path2 = '/tmp/append.txt'; - fs.writeFileSync(path2, data); - fs.appendFileSync(path2, ' appended'); - fs.readFileSync(path2); - var readData = fs.readFileSync(path2, { encoding: 'utf8', flag: 'r' }); - console.log(readData); - fs.unlinkSync(path2); -} - -testAppend(); diff --git a/workerd-corpus-fs/async-callbacks.js b/workerd-corpus-fs/async-callbacks.js deleted file mode 100644 index 1f42f9d44..000000000 --- a/workerd-corpus-fs/async-callbacks.js +++ /dev/null @@ -1,17 +0,0 @@ - - -// Async callback operations -function asyncCB() { - let path = '/tmp/async.txt'; - fs.writeFile(path, 'async data', (err) => { - if (!err) { - fs.readFile(path, (err, data) => { - if (!err) { - fs.unlink(path, () => { }); - } - }); - } - }); -} - -asyncCB(); diff --git a/workerd-corpus-fs/basic-file-ops.js b/workerd-corpus-fs/basic-file-ops.js deleted file mode 100644 index 6909696c6..000000000 --- a/workerd-corpus-fs/basic-file-ops.js +++ /dev/null @@ -1,11 +0,0 @@ -// Basic file operations -function basicOps() { - let path = '/tmp/test.txt'; - let content = 'Hello World'; - fs.writeFileSync(path, content); - fs.readFileSync(path); - fs.existsSync(path); - fs.unlinkSync(path); -} - -basicOps(); \ No newline at end of file diff --git a/workerd-corpus-fs/boundary-conditions.js b/workerd-corpus-fs/boundary-conditions.js deleted file mode 100644 index 874fd690d..000000000 --- a/workerd-corpus-fs/boundary-conditions.js +++ /dev/null @@ -1,10 +0,0 @@ -// Boundary conditions -function boundaryCond() { - let path = '/tmp/boundary.txt'; - fs.writeFileSync(path, ''); // Empty file - let fd = fs.openSync(path, 'r+'); - fs.ftruncateSync(fd, 0); - fs.closeSync(fd); - fs.unlinkSync(path); -} -boundaryCond(); diff --git a/workerd-corpus-fs/buffer-edge-cases.js b/workerd-corpus-fs/buffer-edge-cases.js deleted file mode 100644 index 26e09fe63..000000000 --- a/workerd-corpus-fs/buffer-edge-cases.js +++ /dev/null @@ -1,19 +0,0 @@ -// Buffer edge cases -function bufferTest() { - let path = '/tmp/buf-edge.txt'; - let fd = fs.openSync(path, 'w+'); - - try { - fs.writeSync(fd, Buffer.alloc(0)); - fs.writeSync(fd, Buffer.alloc(1, 255)); - } catch (e) { } - - fs.closeSync(fd); - fs.unlinkSync(path); - fs.unlinkSync(path); - fs.unlinkSync(path); - fs.unlinkSync(path); -} - -bufferTest(); - diff --git a/workerd-corpus-fs/buffer-io.js b/workerd-corpus-fs/buffer-io.js deleted file mode 100644 index 563068236..000000000 --- a/workerd-corpus-fs/buffer-io.js +++ /dev/null @@ -1,9 +0,0 @@ -function bufferIO() { - // Buffer-based I/O operations - let path = '/tmp/buffer.txt'; - let buffer = Buffer.from('binary data', 'utf8'); - fs.writeFileSync(path, buffer); - let result = fs.readFileSync(path); - fs.unlinkSync(path); -} -bufferIO(); \ No newline at end of file diff --git a/workerd-corpus-fs/computed-properties.js b/workerd-corpus-fs/computed-properties.js deleted file mode 100644 index e8ea940c6..000000000 --- a/workerd-corpus-fs/computed-properties.js +++ /dev/null @@ -1,9 +0,0 @@ -function props() { - // Computed property access - let prop = 'Sync'; - let path = '/tmp/computed.txt'; - - fs['writeFile' + prop](path, 'computed'); - fs['readFile' + prop](path); - fs['unlink' + prop](path); -} \ No newline at end of file diff --git a/workerd-corpus-fs/concurrent-operations.js b/workerd-corpus-fs/concurrent-operations.js deleted file mode 100644 index b26425457..000000000 --- a/workerd-corpus-fs/concurrent-operations.js +++ /dev/null @@ -1,14 +0,0 @@ -function timeout() { - - // Concurrent file operations - let path1 = '/tmp/concurrent1.txt'; - let path2 = '/tmp/concurrent2.txt'; - - fs.writeFile(path1, 'data1', () => { }); - fs.writeFile(path2, 'data2', () => { }); - - setTimeout(() => { - try { fs.unlinkSync(path1); } catch (e) { } - try { fs.unlinkSync(path2); } catch (e) { } - }, 100); -} diff --git a/workerd-corpus-fs/constants-usage.js b/workerd-corpus-fs/constants-usage.js deleted file mode 100644 index bad3ff289..000000000 --- a/workerd-corpus-fs/constants-usage.js +++ /dev/null @@ -1,12 +0,0 @@ -function letAntsTest() { - // Using fs letants - let { letants } = fs; - let path = '/tmp/let.txt'; - fs.writeFileSync(path, 'data'); - try { - fs.copyFileSync(path, '/tmp/let2.txt', letants.COPYFILE_EXCL); - } catch (e) { } - fs.unlinkSync(path); -} - -letAntsTest(); \ No newline at end of file diff --git a/workerd-corpus-fs/copy-rename.js b/workerd-corpus-fs/copy-rename.js deleted file mode 100644 index 405e316c0..000000000 --- a/workerd-corpus-fs/copy-rename.js +++ /dev/null @@ -1,10 +0,0 @@ -function renameAndCopy() { - // Copy and rename operations - let src = '/tmp/source.txt'; - let dst = '/tmp/dest.txt'; - fs.writeFileSync(src, 'data to copy'); - fs.copyFileSync(src, dst); - fs.renameSync(dst, '/tmp/renamed.txt'); - fs.unlinkSync(src); - fs.unlinkSync('/tmp/renamed.txt'); -} diff --git a/workerd-corpus-fs/dynamic-calls.js b/workerd-corpus-fs/dynamic-calls.js deleted file mode 100644 index 58f467fed..000000000 --- a/workerd-corpus-fs/dynamic-calls.js +++ /dev/null @@ -1,10 +0,0 @@ -function dynamicCalls() { - // Dynamic function calls - let ops = ['writeFileSync', 'readFileSync', 'unlinkSync']; - let path = '/tmp/dynamic.txt'; - - fs[ops[0]](path, 'dynamic call'); - fs[ops[1]](path); - fs[ops[2]](path); -} - diff --git a/workerd-corpus-fs/encoding-variations.js b/workerd-corpus-fs/encoding-variations.js deleted file mode 100644 index 34e0c8a26..000000000 --- a/workerd-corpus-fs/encoding-variations.js +++ /dev/null @@ -1,9 +0,0 @@ -function encoding() { - // Different encoding variations - let path = '/tmp/encoding.txt'; - fs.writeFileSync(path, 'café', 'utf8'); - fs.readFileSync(path, 'utf8'); - fs.writeFileSync(path, Buffer.from([0x42, 0x43])); - fs.readFileSync(path); - fs.unlinkSync(path); -} \ No newline at end of file diff --git a/workerd-corpus-fs/fd-operations.js b/workerd-corpus-fs/fd-operations.js deleted file mode 100644 index ec87f5c05..000000000 --- a/workerd-corpus-fs/fd-operations.js +++ /dev/null @@ -1,9 +0,0 @@ -function fd() { - // File descriptor operations - let fd = fs.openSync('/tmp/fd-test.txt', 'w+'); - fs.writeSync(fd, 'test data'); - let stats = fs.fstatSync(fd); - fs.closeSync(fd); -} - -fd(); diff --git a/workerd-corpus-fs/fs-chown-chmod-test.js b/workerd-corpus-fs/fs-chown-chmod-test.js deleted file mode 100644 index efdb5b885..000000000 --- a/workerd-corpus-fs/fs-chown-chmod-test.js +++ /dev/null @@ -1,573 +0,0 @@ -strictEqual(typeof openSync, 'function'); -strictEqual(typeof closeSync, 'function'); -strictEqual(typeof statSync, 'function'); -strictEqual(typeof fstatSync, 'function'); -strictEqual(typeof lstatSync, 'function'); -strictEqual(typeof symlinkSync, 'function'); -strictEqual(typeof chmod, 'function'); -strictEqual(typeof lchmod, 'function'); -strictEqual(typeof fchmod, 'function'); -strictEqual(typeof chmodSync, 'function'); -strictEqual(typeof lchmodSync, 'function'); -strictEqual(typeof fchmodSync, 'function'); -strictEqual(typeof fs.chown, 'function'); -strictEqual(typeof lchown, 'function'); -strictEqual(typeof fchown, 'function'); -strictEqual(typeof chownSync, 'function'); -strictEqual(typeof lchownSync, 'function'); -strictEqual(typeof fchownSync, 'function'); -strictEqual(typeof promises.fs.chown, 'function'); -strictEqual(typeof promises.lchown, 'function'); - -const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' }; -const kOutOfRangeError = { code: 'ERR_OUT_OF_RANGE' }; - -function checkStat(path) { - const { uid, gid } = fs.statSync(path); - strictEqual(uid, 0); - strictEqual(gid, 0); -} - -function checkfStat(fd) { - const { uid, gid } = fs.fstatSync(fd); - strictEqual(uid, 0); - strictEqual(gid, 0); -} - -const path = '/tmp'; -const bufferPath = Buffer.from(path); -const urlPath = new URL(path, 'file:///'); - -const chownSyncTest = { - test() { - // Incorrect input types should throw. - throws(() => fs.chownSync(123), kInvalidArgTypeError); - throws(() => fs.chownSync('/', {}), kInvalidArgTypeError); - throws(() => fs.chownSync('/', 0, {}), kInvalidArgTypeError); - throws(() => fs.chownSync(path, -1000, 0), kOutOfRangeError); - throws(() => fs.chownSync(path, 0, -1000), kOutOfRangeError); - - // We stat the file before and after to verify the impact - // of the fs.chown( operation. Specifically, the uid and gid - // should not change since our impl is a non-op. - checkStat(path); - fs.chownSync(path, 1000, 1000); - checkStat(path); - - fs.chownSync(bufferPath, 1000, 1000); - checkStat(bufferPath); - - fs.chownSync(urlPath, 1000, 1000); - checkStat(urlPath); - - // A non-existent path should throw ENOENT - throws(() => fs.chownSync('/non-existent-path', 1000, 1000), { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'fs.chown', - }); - }, -}; - -const chownCallbackTest = { - async test() { - // Incorrect input types should throw synchronously - throws(() => fs.chown(123), kInvalidArgTypeError); - throws(() => fs.chown('/', {}), kInvalidArgTypeError); - throws(() => fs.chown('/', 0, {}), kInvalidArgTypeError); - throws(() => fs.chownSync(path, -1000, 0), kOutOfRangeError); - throws(() => fs.chownSync(path, 0, -1000), kOutOfRangeError); - - async function callChown(path) { - const { promise, resolve, reject } = Promise.withResolvers(); - fs.chown(path, 1000, 1000, (err) => { - if (err) return reject(err); - resolve(); - }); - await promise; - } - - // Should be non-op - checkStat(path); - await callChown(path); - checkStat(path); - - await callChown(bufferPath); - checkStat(bufferPath); - - await callChown(urlPath); - checkStat(urlPath); - - // A non-existent path should throw ENOENT - const { promise, resolve, reject } = Promise.withResolvers(); - fs.chown('/non-existent-path', 1000, 1000, (err) => { - if (err) return reject(err); - resolve(); - }); - await rejects(promise, { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'fs.chown(', - }); - }, -}; - -const chownPromiseTest = { - async test() { - // Incorrect input types should reject the promise. - await rejects(promises.fs.chown(123), kInvalidArgTypeError); - await rejects(promises.fs.chown('/', {}), kInvalidArgTypeError); - await rejects(promises.fs.chown('/', 0, {}), kInvalidArgTypeError); - await rejects(promises.fs.chown(path, -1000, 0), kOutOfRangeError); - await rejects(promises.fs.chown(path, 0, -1000), kOutOfRangeError); - - // Should be non-op - checkStat(path); - await promises.fs.chown(path, 1000, 1000); - checkStat(path); - - await promises.fs.chown(bufferPath, 1000, 1000); - checkStat(bufferPath); - - await promises.fs.chown(urlPath, 1000, 1000); - checkStat(urlPath); - - // A non-existent path should throw ENOENT - await rejects(promises.fs.chown('/non-existent-path', 1000, 1000), { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'fs.chown(', - }); - }, -}; - -const lchownSyncTest = { - test() { - // Incorrect input types should throw. - throws(() => lchownSync(123), kInvalidArgTypeError); - throws(() => lchownSync('/', {}), kInvalidArgTypeError); - throws(() => lchownSync('/', 0, {}), kInvalidArgTypeError); - throws(() => lchownSync(path, -1000, 0), kOutOfRangeError); - throws(() => lchownSync(path, 0, -1000), kOutOfRangeError); - - // We stat the file before and after to verify the impact - // of the fs.chown( operation. Specifically, the uid and gid - // should not change since our impl is a non-op. - checkStat(path); - lchownSync(path, 1000, 1000); - checkStat(path); - - lchownSync(bufferPath, 1000, 1000); - checkStat(bufferPath); - - lchownSync(urlPath, 1000, 1000); - checkStat(urlPath); - - // A non-existent path should throw ENOENT - throws(() => lchownSync('/non-existent-path', 1000, 1000), { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'lchown', - }); - }, -}; - -const lchownCallbackTest = { - async test() { - // Incorrect input types should throw synchronously - throws(() => lchown(123), kInvalidArgTypeError); - throws(() => lchown('/', {}), kInvalidArgTypeError); - throws(() => lchown('/', 0, {}), kInvalidArgTypeError); - throws(() => lchownSync(path, -1000, 0), kOutOfRangeError); - throws(() => lchownSync(path, 0, -1000), kOutOfRangeError); - - async function callChown(path) { - const { promise, resolve, reject } = Promise.withResolvers(); - lchown(path, 1000, 1000, (err) => { - if (err) return reject(err); - resolve(); - }); - await promise; - } - - // Should be non-op - checkStat(path); - await callChown(path); - checkStat(path); - - await callChown(bufferPath); - checkStat(bufferPath); - - await callChown(urlPath); - checkStat(urlPath); - - // A non-existent path should throw ENOENT - const { promise, resolve, reject } = Promise.withResolvers(); - lchown('/non-existent-path', 1000, 1000, (err) => { - if (err) return reject(err); - resolve(); - }); - await rejects(promise, { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'lchown', - }); - }, -}; - -const lchownPromiseTest = { - async test() { - // Incorrect input types should reject the promise. - await rejects(promises.lchown(123), kInvalidArgTypeError); - await rejects(promises.lchown('/', {}), kInvalidArgTypeError); - await rejects(promises.lchown('/', 0, {}), kInvalidArgTypeError); - await rejects(promises.lchown(path, -1000, 0), kOutOfRangeError); - await rejects(promises.lchown(path, 0, -1000), kOutOfRangeError); - - // Should be non-op - checkStat(path); - await promises.lchown(path, 1000, 1000); - checkStat(path); - - await promises.lchown(bufferPath, 1000, 1000); - checkStat(bufferPath); - - await promises.lchown(urlPath, 1000, 1000); - checkStat(urlPath); - - // A non-existent path should throw ENOENT - await rejects(promises.lchown('/non-existent-path', 1000, 1000), { - code: 'ENOENT', - syscall: 'lchown', - }); - }, -}; - -const fchownSyncTest = { - test() { - // Incorrect input types should throw. - throws(() => fchownSync({}), kInvalidArgTypeError); - throws(() => fchownSync(123), kInvalidArgTypeError); - throws(() => fchownSync(123, {}), kInvalidArgTypeError); - throws(() => fchownSync(123, 0, {}), kInvalidArgTypeError); - throws(() => fchownSync(123, -1000, 0), kOutOfRangeError); - throws(() => fchownSync(123, 0, -1000), kOutOfRangeError); - - const fd = openSync('/tmp'); - - // We stat the file before and after to verify the impact - // of the fs.chown( operation. Specifically, the uid and gid - // should not change since our impl is a non-op. - checkfStat(fd); - fchownSync(fd, 1000, 1000); - checkfStat(fd); - - throws(() => fchownSync(999, 1000, 1000), { - code: 'EBADF', - syscall: 'fstat', - }); - - closeSync(fd); - }, -}; - -const fchownCallbackTest = { - async test() { - // Incorrect input types should throw synchronously - throws(() => fchown({}), kInvalidArgTypeError); - throws(() => fchown(123), kInvalidArgTypeError); - throws(() => fchown(123, {}), kInvalidArgTypeError); - throws(() => fchown(123, 0, {}), kInvalidArgTypeError); - throws(() => fchown(123, -1000, 0), kOutOfRangeError); - throws(() => fchown(123, 0, -1000), kOutOfRangeError); - - const fd = openSync('/tmp'); - - async function callChown() { - const { promise, resolve, reject } = Promise.withResolvers(); - fchown(fd, 1000, 1000, (err) => { - if (err) return reject(err); - resolve(); - }); - await promise; - } - - // Should be non-op - checkfStat(fd); - await callChown(); - checkfStat(fd); - - const { promise, resolve, reject } = Promise.withResolvers(); - fchown(999, 1000, 1000, (err) => { - if (err) return reject(err); - resolve(); - }); - await rejects(promise, { - code: 'EBADF', - syscall: 'fstat', - }); - - closeSync(fd); - }, -}; - -// =========================================================================== - -const chmodSyncTest = { - test() { - // Incorrect input types should throw. - throws(() => chmodSync(123), kInvalidArgTypeError); - throws(() => chmodSync('/', {}), kInvalidArgTypeError); - throws(() => chmodSync('/tmp', -1), kOutOfRangeError); - - // Should be non-op - checkStat(path); - chmodSync(path, 0o777); - checkStat(path); - - chmodSync(bufferPath, 0o777); - checkStat(bufferPath); - - chmodSync(urlPath, 0o777); - checkStat(urlPath); - - throws(() => chmodSync('/non-existent-path', 0o777), { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'chmod', - }); - }, -}; - -const chmodCallbackTest = { - async test() { - // Incorrect input types should throw. - throws(() => chmod(123), kInvalidArgTypeError); - throws(() => chmod('/', {}), kInvalidArgTypeError); - throws(() => chmod('/tmp', -1), kOutOfRangeError); - - async function callChmod(path) { - const { promise, resolve, reject } = Promise.withResolvers(); - chmod(path, 0o000, (err) => { - if (err) return reject(err); - resolve(); - }); - await promise; - } - - checkStat(path); - await callChmod(path); - checkStat(path); - - await callChmod(bufferPath); - checkStat(bufferPath); - - await callChmod(urlPath); - checkStat(bufferPath); - - const { promise, resolve, reject } = Promise.withResolvers(); - chmod('/non-existent-path', 0o777, (err) => { - if (err) return reject(err); - resolve(); - }); - await rejects(promise, { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'chmod', - }); - }, -}; - -const chmodPromiseTest = { - async test() { - // Incorrect input types should reject the promise. - await rejects(promises.chmod(123), kInvalidArgTypeError); - await rejects(promises.chmod('/', {}), kInvalidArgTypeError); - await rejects(promises.chmod('/tmp', -1), kOutOfRangeError); - - // Should be non-op - checkStat(path); - await promises.chmod(path, 0o777); - checkStat(path); - - await promises.chmod(bufferPath, 0o777); - checkStat(bufferPath); - - await promises.chmod(urlPath, 0o777); - checkStat(urlPath); - - await rejects(promises.chmod('/non-existent-path', 0o777), { - code: 'ENOENT', - syscall: 'chmod', - }); - }, -}; - -const lchmodSyncTest = { - test() { - // Incorrect input types should throw. - throws(() => lchmodSync(123), kInvalidArgTypeError); - throws(() => lchmodSync('/', {}), kInvalidArgTypeError); - throws(() => lchmodSync('/tmp', -1), kOutOfRangeError); - - // Should be non-op - checkStat(path); - lchmodSync(path, 0o777); - checkStat(path); - - lchmodSync(bufferPath, 0o777); - checkStat(bufferPath); - - lchmodSync(urlPath, 0o777); - checkStat(urlPath); - - throws(() => lchmodSync('/non-existent-path', 0o777), { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'lchmod', - }); - }, -}; - -const lchmodCallbackTest = { - async test() { - // Incorrect input types should throw. - throws(() => lchmod(123), kInvalidArgTypeError); - throws(() => lchmod('/', {}), kInvalidArgTypeError); - throws(() => lchmod('/tmp', -1), kOutOfRangeError); - - async function callChmod(path) { - const { promise, resolve, reject } = Promise.withResolvers(); - lchmod(path, 0o000, (err) => { - if (err) return reject(err); - resolve(); - }); - await promise; - } - - checkStat(path); - await callChmod(path); - checkStat(path); - - await callChmod(bufferPath); - checkStat(bufferPath); - - await callChmod(urlPath); - checkStat(bufferPath); - - const { promise, resolve, reject } = Promise.withResolvers(); - lchmod('/non-existent-path', 0o777, (err) => { - if (err) return reject(err); - resolve(); - }); - await rejects(promise, { - code: 'ENOENT', - // Access because it is an access check under the covers. - syscall: 'lchmod', - }); - }, -}; - -const lchmodPromiseTest = { - async test() { - // Incorrect input types should reject the promise. - await rejects(promises.lchmod(123), kInvalidArgTypeError); - await rejects(promises.lchmod('/', {}), kInvalidArgTypeError); - await rejects(promises.lchmod('/tmp', -1), kOutOfRangeError); - - // Should be non-op - checkStat(path); - await promises.lchmod(path, 0o777); - checkStat(path); - - await promises.lchmod(bufferPath, 0o777); - checkStat(bufferPath); - - await promises.lchmod(urlPath, 0o777); - checkStat(urlPath); - - await rejects(promises.lchmod('/non-existent-path', 0o777), { - code: 'ENOENT', - syscall: 'lchmod', - }); - }, -}; - -const fchmodSyncTest = { - test() { - // Incorrect input types should throw. - throws(() => fchmodSync({}), kInvalidArgTypeError); - throws(() => fchmodSync(123), kInvalidArgTypeError); - throws(() => fchmodSync(123, {}), kInvalidArgTypeError); - throws(() => fchmodSync(123, -1000), kOutOfRangeError); - - const fd = openSync('/tmp'); - - // We stat the file before and after to verify the impact - // of the fs.chown( operation. Specifically, the uid and gid - // should not change since our impl is a non-op. - checkfStat(fd); - fchmodSync(fd, 0o777); - checkfStat(fd); - - throws(() => fchmodSync(999, 0o777), { - code: 'EBADF', - syscall: 'fstat', - }); - - closeSync(fd); - }, -}; - -const fchmodCallbackTest = { - async test() { - // Incorrect input types should throw synchronously - throws(() => fchmod({}), kInvalidArgTypeError); - throws(() => fchmod(123), kInvalidArgTypeError); - throws(() => fchmod(123, {}), kInvalidArgTypeError); - - const fd = openSync('/tmp'); - - async function callChmod() { - const { promise, resolve, reject } = Promise.withResolvers(); - fchmod(fd, 0o777, (err) => { - if (err) return reject(err); - resolve(); - }); - await promise; - } - - // Should be non-op - checkfStat(fd); - await callChmod(); - checkfStat(fd); - - const { promise, resolve, reject } = Promise.withResolvers(); - fchmod(999, 0o777, (err) => { - if (err) return reject(err); - resolve(); - }); - await rejects(promise, { - code: 'EBADF', - syscall: 'fstat', - }); - - closeSync(fd); - }, -}; - -chownSyncTest.test(); -await chownCallbackTest.test(); -await chownPromiseTest.test(); -lchownSyncTest.test(); -await lchownCallbackTest.test(); -await lchownPromiseTest.test(); -fchownSyncTest.test(); -chmodSyncTest.test(); -await chmodCallbackTest.test(); -await chmodPromiseTest.test(); -lchmodSyncTest.test(); -await lchmodCallbackTest.test(); -fchmodSyncTest.test(); -await lchmodPromiseTest.test(); -await fchmodCallbackTest.test(); \ No newline at end of file diff --git a/workerd-corpus-fs/fs-dir-test.js b/workerd-corpus-fs/fs-dir-test.js deleted file mode 100644 index 874f25fae..000000000 --- a/workerd-corpus-fs/fs-dir-test.js +++ /dev/null @@ -1,839 +0,0 @@ -// Copyright (c) 2017-2022 Cloudflare, Inc. -// Licensed under the Apache 2.0 license found in the LICENSE file or at: -// https://opensource.org/licenses/Apache-2.0 -strictEqual(typeof fs.existsSync, 'function'); -strictEqual(typeof fs.writeFileSync, 'function'); -strictEqual(typeof fs.mkdirSync, 'function'); -strictEqual(typeof fs.mkdtempSync, 'function'); -strictEqual(typeof fs.rmSync, 'function'); -strictEqual(typeof fs.rmdirSync, 'function'); -strictEqual(typeof fs.readdirSync, 'function'); -strictEqual(typeof fs.mkdtemp, 'function'); -strictEqual(typeof fs.mkdir, 'function'); -strictEqual(typeof fs.rm, 'function'); -strictEqual(typeof fs.rmdir, 'function'); -strictEqual(typeof fs.readdir, 'function'); -strictEqual(typeof fs.opendirSync, 'function'); -strictEqual(typeof fs.opendir, 'function'); -strictEqual(typeof promises.fs.mkdir, 'function'); -strictEqual(typeof promises.fs.mkdtemp, 'function'); -strictEqual(typeof promises.fs.rm, 'function'); -strictEqual(typeof promises.fs.rmdir, 'function'); -strictEqual(typeof promises.fs.readdir, 'function'); -strictEqual(typeof promises.fs.opendir, 'function'); - -const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' }; -const kInvalidArgValueError = { code: 'ERR_INVALID_ARG_VALUE' }; -const kEPermError = { code: 'EPERM' }; -const kENoEntError = { code: 'ENOENT' }; -const kEExistError = { code: 'EEXIST' }; -const kENotDirError = { code: 'ENOTDIR' }; -const kENotEmptyError = { code: 'ENOTEMPTY' }; - -const mkdirSyncTest = { - test() { - throws(() => fs.mkdirSync(), kInvalidArgTypeError); - throws(() => fs.mkdirSync(123), kInvalidArgTypeError); - throws(() => fs.mkdirSync('/tmp/testdir', 'hello'), kInvalidArgTypeError); - throws( - () => fs.mkdirSync('/tmp/testdir', { recursive: 123 }), - kInvalidArgTypeError - ); - - // Make a directory. - ok(!fs.existsSync('/tmp/testdir')); - strictEqual(fs.mkdirSync('/tmp/testdir'), undefined); - ok(fs.existsSync('/tmp/testdir')); - - // Making a subdirectory in a non-existing path fails by default - ok(!fs.existsSync('/tmp/testdir/a/b/c')); - throws(() => fs.mkdirSync('/tmp/testdir/a/b/c'), kENoEntError); - - // But passing the recursive option allows the entire path to be created. - ok(!fs.existsSync('/tmp/testdir/a/b/c')); - strictEqual( - fs.mkdirSync('/tmp/testdir/a/b/c', { recursive: true }), - '/tmp/testdir/a' - ); - ok(fs.existsSync('/tmp/testdir/a/b/c')); - - // Cannot make a directory in a read-only location - throws(() => fs.mkdirSync('/bundle/a'), kEPermError); - - // Making a directory that already exists is a non-op - fs.mkdirSync('/tmp/testdir'); - - // Attempting to create a directory that already exists as a file throws - fs.writeFileSync('/tmp/abc', 'Hello World'); - throws(() => fs.mkdirSync('/tmp/abc'), kEExistError); - - // Attempting to create a directory recursively when a parent is a file - // throws - throws(() => fs.mkdirSync('/tmp/abc/foo', { recursive: true }), kENotDirError); - }, -}; - -const mkdirAsyncCallbackTest = { - async test() { - throws(() => mkdir(), kInvalidArgTypeError); - throws(() => mkdir(123), kInvalidArgTypeError); - throws(() => mkdir('/tmp/testdir', 'hello'), kInvalidArgTypeError); - throws( - () => mkdir('/tmp/testdir', { recursive: 123 }), - kInvalidArgTypeError - ); - - // Make a directory. - ok(!fs.existsSync('/tmp/testdir')); - await new Promise((resolve, reject) => { - mkdir('/tmp/testdir', (err) => { - if (err) reject(err); - else resolve(); - }); - }); - ok(fs.existsSync('/tmp/testdir')); - - // Making a subdirectory in a non-existing path fails by default - ok(!fs.existsSync('/tmp/testdir/a/b/c')); - await new Promise((resolve, reject) => { - mkdir('/tmp/testdir/a/b/c', (err) => { - if (err && err.code === kENoEntError.code) resolve(); - else reject(err); - }); - }); - - // But passing the recursive option allows the entire path to be created. - ok(!fs.existsSync('/tmp/testdir/a/b/c')); - await new Promise((resolve, reject) => { - mkdir('/tmp/testdir/a/b/c', { recursive: true }, (err) => { - if (err) reject(err); - else resolve(); - }); - }); - ok(fs.existsSync('/tmp/testdir/a/b/c')); - - // Cannot make a directory in a read-only location - await new Promise((resolve, reject) => { - mkdir('/bundle/a', (err) => { - if (err && err.code === kEPermError.code) resolve(); - else reject(err); - }); - }); - - // Making a directory that already exists is a non-op - await new Promise((resolve, reject) => { - mkdir('/tmp/testdir', (err) => { - if (err) reject(err); - else resolve(); - }); - }); - - // Attempting to create a directory that already exists as a file throws - fs.writeFileSync('/tmp/abc', 'Hello World'); - await new Promise((resolve, reject) => { - mkdir('/tmp/abc', (err) => { - if (err && err.code === kEExistError.code) resolve(); - else reject(err); - }); - }); - - // Attempting to create a directory recursively when a parent is a file - // throws - await new Promise((resolve, reject) => { - mkdir('/tmp/abc/foo', { recursive: true }, (err) => { - if (err && err.code === kENotDirError.code) resolve(); - else reject(err); - }); - }); - }, -}; - -const mkdirAsyncPromiseTest = { - async test() { - await rejects(promises.fs.mkdir(), kInvalidArgTypeError); - await rejects(promises.fs.mkdir(123), kInvalidArgTypeError); - await rejects( - promises.fs.mkdir('/tmp/testdir', 'hello'), - kInvalidArgTypeError - ); - await rejects( - promises.fs.mkdir('/tmp/testdir', { recursive: 123 }), - kInvalidArgTypeError - ); - - // Make a directory. - ok(!fs.existsSync('/tmp/testdir')); - await promises.fs.mkdir('/tmp/testdir'); - ok(fs.existsSync('/tmp/testdir')); - - // Making a subdirectory in a non-existing path fails by default - ok(!fs.existsSync('/tmp/testdir/a/b/c')); - await rejects(promises.fs.mkdir('/tmp/testdir/a/b/c'), kENoEntError); - - // But passing the recursive option allows the entire path to be created. - ok(!fs.existsSync('/tmp/testdir/a/b/c')); - await promises.fs.mkdir('/tmp/testdir/a/b/c', { recursive: true }); - ok(fs.existsSync('/tmp/testdir/a/b/c')); - - // Cannot make a directory in a read-only location - await rejects(promises.fs.mkdir('/bundle/a'), kEPermError); - - // Making a directory that already exists is a non-op - await promises.fs.mkdir('/tmp/testdir'); - - // Attempting to create a directory that already exists as a file throws - fs.writeFileSync('/tmp/abc', 'Hello World'); - await rejects(promises.fs.mkdir('/tmp/abc'), kEExistError); - - // Attempting to create a directory recursively when a parent is a file - // throws - await rejects( - promises.fs.mkdir('/tmp/abc/foo', { recursive: true }), - kENotDirError - ); - }, -}; - -const mkdtempSyncTest = { - test() { - throws(() => fs.mkdtempSync(), kInvalidArgTypeError); - const ret1 = fs.mkdtempSync('/tmp/testdir-'); - const ret2 = fs.mkdtempSync('/tmp/testdir-'); - match(ret1, /\/tmp\/testdir-\d+/); - match(ret2, /\/tmp\/testdir-\d+/); - ok(fs.existsSync(ret1)); - ok(fs.existsSync(ret2)); - throws(() => fs.mkdtempSync('/bundle/testdir-'), kEPermError); - }, -}; - -const mkdtempAsyncCallbackTest = { - async test() { - throws(() => fs.mkdtemp(), kInvalidArgTypeError); - const ret1 = await new Promise((resolve, reject) => { - fs.mkdtemp('/tmp/testdir-', (err, dir) => { - if (err) reject(err); - else resolve(dir); - }); - }); - const ret2 = await new Promise((resolve, reject) => { - fs.mkdtemp('/tmp/testdir-', (err, dir) => { - if (err) reject(err); - else resolve(dir); - }); - }); - match(ret1, /\/tmp\/testdir-\d+/); - match(ret2, /\/tmp\/testdir-\d+/); - ok(fs.existsSync(ret1)); - ok(fs.existsSync(ret2)); - await new Promise((resolve, reject) => { - fs.mkdtemp('/bundle/testdir-', (err) => { - if (err && err.code === kEPermError.code) resolve(); - else reject(err); - }); - }); - }, -}; - -const mkdtempAsyncPromiseTest = { - async test() { - await rejects(promises.fs.mkdtemp(), kInvalidArgTypeError); - const ret1 = await promises.fs.mkdtemp('/tmp/testdir-'); - const ret2 = await promises.fs.mkdtemp('/tmp/testdir-'); - match(ret1, /\/tmp\/testdir-\d+/); - match(ret2, /\/tmp\/testdir-\d+/); - ok(fs.existsSync(ret1)); - ok(fs.existsSync(ret2)); - await rejects(promises.fs.mkdtemp('/bundle/testdir-'), kEPermError); - }, -}; - -const rmSyncTest = { - test() { - // Passing incorrect types for options throws - throws( - () => fs.rmSync('/tmp/testdir', { recursive: 'yes' }), - kInvalidArgTypeError - ); - throws(() => fs.rmSync('/tmp/testdir', 'abc'), kInvalidArgTypeError); - throws( - () => fs.rmSync('/tmp/testdir', { force: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmSync('/tmp/testdir', { maxRetries: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmSync('/tmp/testdir', { retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmSync('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmSync('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), - kInvalidArgTypeError - ); - throws( - () => - fs.rmSync('/tmp/testdir', { maxRetries: 1, retryDelay: 1, force: 'yes' }), - kInvalidArgTypeError - ); - - throws( - () => fs.rmdirSync('/tmp/testdir', { recursive: 'yes' }), - kInvalidArgTypeError - ); - throws(() => fs.rmdirSync('/tmp/testdir', 'abc'), kInvalidArgTypeError); - throws( - () => fs.rmdirSync('/tmp/testdir', { maxRetries: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmdirSync('/tmp/testdir', { retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmdirSync('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmdirSync('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), - kInvalidArgTypeError - ); - - ok(!fs.existsSync('/tmp/testdir')); - fs.mkdirSync('/tmp/testdir'); - fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); - - // When the recusive option is not set, then removing a directory - // with children throws... - throws(() => fs.rmdirSync('/tmp/testdir'), kENotEmptyError); - ok(fs.existsSync('/tmp/testdir')); - - // But works when the recursive option is set - fs.rmdirSync('/tmp/testdir', { recursive: true }); - ok(!fs.existsSync('/tmp/testdir')); - - fs.mkdirSync('/tmp/testdir'); - fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); - fs.writeFileSync('/tmp/testdir/b.txt', 'Hello World'); - ok(fs.existsSync('/tmp/testdir/a.txt')); - - // trying to remove a file with fs.rmdir throws - throws(() => fs.rmdirSync('/tmp/testdir/a.txt'), kENotDirError); - - // removing a file with fs.rm works - fs.rmSync('/tmp/testdir/a.txt'); - ok(!fs.existsSync('/tmp/testdir/a.txt')); - - // Calling fs.rmSync when the directory is not empty throws - throws(() => fs.rmSync('/tmp/testdir'), kENotEmptyError); - ok(fs.existsSync('/tmp/testdir')); - - // But works when the recursive option is set - throws(() => fs.rmSync('/tmp/testdir')); - fs.rmSync('/tmp/testdir', { recursive: true }); - ok(!fs.existsSync('/tmp/testdir')); - }, -}; - -const rmAsyncCallbackTest = { - async test() { - // Passing incorrect types for options throws - throws( - () => fs.rm('/tmp/testdir', { recursive: 'yes' }), - kInvalidArgTypeError - ); - throws(() => fs.rm('/tmp/testdir', 'abc'), kInvalidArgTypeError); - throws(() => fs.rm('/tmp/testdir', { force: 'yes' }), kInvalidArgTypeError); - throws( - () => fs.rm('/tmp/testdir', { maxRetries: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rm('/tmp/testdir', { retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rm('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), - kInvalidArgTypeError - ); - throws( - () => fs.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 1, force: 'yes' }), - kInvalidArgTypeError - ); - - throws( - () => fs.rmdir('/tmp/testdir', { recursive: 'yes' }), - kInvalidArgTypeError - ); - throws(() => fs.rmdir('/tmp/testdir', 'abc'), kInvalidArgTypeError); - throws( - () => fs.rmdir('/tmp/testdir', { maxRetries: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmdir('/tmp/testdir', { retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmdir('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), - kInvalidArgTypeError - ); - throws( - () => fs.rmdir('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), - kInvalidArgTypeError - ); - - ok(!fs.existsSync('/tmp/testdir')); - fs.mkdirSync('/tmp/testdir'); - fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); - - // When the recusive option is not set, then removing a directory - // with children throws... - await new Promise((resolve, reject) => { - fs.rmdir('/tmp/testdir', (err) => { - if (err && err.code === kENotEmptyError.code) resolve(); - else reject(err); - }); - }); - - ok(fs.existsSync('/tmp/testdir')); - // But works when the recursive option is set - await new Promise((resolve, reject) => { - fs.rmdir('/tmp/testdir', { recursive: true }, (err) => { - if (err) reject(err); - else resolve(); - }); - }); - ok(!fs.existsSync('/tmp/testdir')); - fs.mkdirSync('/tmp/testdir'); - fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); - fs.writeFileSync('/tmp/testdir/b.txt', 'Hello World'); - - ok(fs.existsSync('/tmp/testdir/a.txt')); - // trying to remove a file with fs.rmdir throws - await new Promise((resolve, reject) => { - fs.rmdir('/tmp/testdir/a.txt', (err) => { - if (err && err.code === kENotDirError.code) resolve(); - else reject(err); - }); - }); - // removing a file with fs.rm works - await new Promise((resolve, reject) => { - fs.rm('/tmp/testdir/a.txt', (err) => { - if (err) reject(err); - else resolve(); - }); - }); - ok(!fs.existsSync('/tmp/testdir/a.txt')); - // Calling fs.rm when the directory is not empty throws - await new Promise((resolve, reject) => { - fs.rm('/tmp/testdir', (err) => { - if (err && err.code === kENotEmptyError.code) resolve(); - else reject(err); - }); - }); - ok(fs.existsSync('/tmp/testdir')); - // But works when the recursive option is set - await new Promise((resolve, reject) => { - fs.rm('/tmp/testdir', { recursive: true }, (err) => { - if (err) reject(err); - else resolve(); - }); - }); - ok(!fs.existsSync('/tmp/testdir')); - }, -}; - -const rmAsyncPromiseTest = { - async test() { - // Passing incorrect types for options throws - await rejects( - promises.fs.rm('/tmp/testdir', { recursive: 'yes' }), - kInvalidArgTypeError - ); - await rejects(promises.fs.rm('/tmp/testdir', 'abc'), kInvalidArgTypeError); - await rejects( - promises.fs.rm('/tmp/testdir', { force: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rm('/tmp/testdir', { maxRetries: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rm('/tmp/testdir', { retryDelay: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rm('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rm('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rm('/tmp/testdir', { - maxRetries: 1, - retryDelay: 1, - force: 'yes', - }), - kInvalidArgTypeError - ); - - await rejects( - promises.fs.rmdir('/tmp/testdir', { recursive: 'yes' }), - kInvalidArgTypeError - ); - await rejects(promises.fs.rmdir('/tmp/testdir', 'abc'), kInvalidArgTypeError); - await rejects( - promises.fs.rmdir('/tmp/testdir', { maxRetries: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rmdir('/tmp/testdir', { retryDelay: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rmdir('/tmp/testdir', { maxRetries: 1, retryDelay: 'yes' }), - kInvalidArgTypeError - ); - await rejects( - promises.fs.rmdir('/tmp/testdir', { maxRetries: 'yes', retryDelay: 1 }), - kInvalidArgTypeError - ); - - ok(!fs.existsSync('/tmp/testdir')); - fs.mkdirSync('/tmp/testdir'); - fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); - - // When the recusive option is not set, then removing a directory - // with children throws... - await rejects(promises.fs.rmdir('/tmp/testdir'), kENotEmptyError); - - ok(fs.existsSync('/tmp/testdir')); - // But works when the recursive option is set - await promises.fs.rmdir('/tmp/testdir', { recursive: true }); - ok(!fs.existsSync('/tmp/testdir')); - fs.mkdirSync('/tmp/testdir'); - fs.writeFileSync('/tmp/testdir/a.txt', 'Hello World'); - fs.writeFileSync('/tmp/testdir/b.txt', 'Hello World'); - ok(fs.existsSync('/tmp/testdir/a.txt')); - // trying to remove a file with fs.rmdir throws - await rejects(promises.fs.rmdir('/tmp/testdir/a.txt'), kENotDirError); - // removing a file with fs.rm works - await promises.fs.rm('/tmp/testdir/a.txt'); - ok(!fs.existsSync('/tmp/testdir/a.txt')); - // Calling fs.rm when the directory is not empty throws - await rejects(promises.fs.rm('/tmp/testdir'), kENotEmptyError); - ok(fs.existsSync('/tmp/testdir')); - // But works when the recursive option is set - await promises.fs.rm('/tmp/testdir', { recursive: true }); - ok(!fs.existsSync('/tmp/testdir')); - }, -}; - -const readdirSyncTest = { - test() { - throws(() => fs.readdirSync(), kInvalidArgTypeError); - throws(() => fs.readdirSync(123), kInvalidArgTypeError); - throws( - () => fs.readdirSync('/tmp/testdir', { withFileTypes: 123 }), - kInvalidArgTypeError - ); - throws( - () => fs.readdirSync('/tmp/testdir', { recursive: 123 }), - kInvalidArgTypeError - ); - throws( - () => - fs.readdirSync('/tmp/testdir', { withFileTypes: true, recursive: 123 }), - kInvalidArgTypeError - ); - - deepStrictEqual(fs.readdirSync('/'), ['bundle', 'tmp', 'dev']); - - deepStrictEqual(fs.readdirSync('/', 'buffer'), [ - Buffer.from('bundle'), - Buffer.from('tmp'), - Buffer.from('dev'), - ]); - - { - const ents = fs.readdirSync('/', { withFileTypes: true }); - strictEqual(ents.length, 3); - - strictEqual(ents[0].name, 'bundle'); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - } - - { - const ents = fs.readdirSync('/', { - withFileTypes: true, - encoding: 'buffer', - }); - strictEqual(ents.length, 3); - - deepStrictEqual(ents[0].name, Buffer.from('bundle')); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - } - - { - const ents = fs.readdirSync('/', { withFileTypes: true, recursive: true }); - strictEqual(ents.length, 8); - - strictEqual(ents[0].name, 'bundle'); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - - strictEqual(ents[1].name, 'bundle/worker'); - strictEqual(ents[1].isDirectory(), false); - strictEqual(ents[1].isFile(), true); - strictEqual(ents[1].isBlockDevice(), false); - strictEqual(ents[1].isCharacterDevice(), false); - strictEqual(ents[1].isFIFO(), false); - strictEqual(ents[1].isSocket(), false); - strictEqual(ents[1].isSymbolicLink(), false); - strictEqual(ents[1].parentPath, '/bundle'); - - strictEqual(ents[4].name, 'dev/null'); - strictEqual(ents[4].isDirectory(), false); - strictEqual(ents[4].isFile(), false); - strictEqual(ents[4].isBlockDevice(), false); - strictEqual(ents[4].isCharacterDevice(), true); - strictEqual(ents[4].isFIFO(), false); - strictEqual(ents[4].isSocket(), false); - strictEqual(ents[4].isSymbolicLink(), false); - strictEqual(ents[4].parentPath, '/dev'); - } - }, -}; - -const readdirAsyncCallbackTest = { - async test() { - deepStrictEqual( - await new Promise((resolve, reject) => { - fs.readdir('/', (err, files) => { - if (err) reject(err); - else resolve(files); - }); - }), - ['bundle', 'tmp', 'dev'] - ); - - { - const ents = await new Promise((resolve, reject) => { - fs.readdir('/', { withFileTypes: true }, (err, files) => { - if (err) reject(err); - else resolve(files); - }); - }); - strictEqual(ents.length, 3); - - strictEqual(ents[0].name, 'bundle'); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - } - - { - const ents = await new Promise((resolve, reject) => { - fs.readdir( - '/', - { withFileTypes: true, encoding: 'buffer' }, - (err, files) => { - if (err) reject(err); - else resolve(files); - } - ); - }); - strictEqual(ents.length, 3); - - deepStrictEqual(ents[0].name, Buffer.from('bundle')); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - } - - { - const ents = await new Promise((resolve, reject) => { - fs.readdir('/', { withFileTypes: true, recursive: true }, (err, files) => { - if (err) reject(err); - else resolve(files); - }); - }); - strictEqual(ents.length, 8); - - strictEqual(ents[0].name, 'bundle'); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - - strictEqual(ents[1].name, 'bundle/worker'); - strictEqual(ents[1].isDirectory(), false); - strictEqual(ents[1].isFile(), true); - strictEqual(ents[1].isBlockDevice(), false); - strictEqual(ents[1].isCharacterDevice(), false); - strictEqual(ents[1].isFIFO(), false); - strictEqual(ents[1].isSocket(), false); - strictEqual(ents[1].isSymbolicLink(), false); - strictEqual(ents[1].parentPath, '/bundle'); - - strictEqual(ents[4].name, 'dev/null'); - strictEqual(ents[4].isDirectory(), false); - strictEqual(ents[4].isFile(), false); - strictEqual(ents[4].isBlockDevice(), false); - strictEqual(ents[4].isCharacterDevice(), true); - strictEqual(ents[4].isFIFO(), false); - strictEqual(ents[4].isSocket(), false); - strictEqual(ents[4].isSymbolicLink(), false); - strictEqual(ents[4].parentPath, '/dev'); - } - }, -}; - -const readdirAsyncPromiseTest = { - async test() { - deepStrictEqual(await promises.fs.readdir('/'), ['bundle', 'tmp', 'dev']); - - { - const ents = await promises.fs.readdir('/', { withFileTypes: true }); - strictEqual(ents.length, 3); - - strictEqual(ents[0].name, 'bundle'); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - } - - { - const ents = await promises.fs.readdir('/', { - withFileTypes: true, - encoding: 'buffer', - }); - strictEqual(ents.length, 3); - - deepStrictEqual(ents[0].name, Buffer.from('bundle')); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - } - - { - const ents = await promises.fs.readdir('/', { - withFileTypes: true, - recursive: true, - }); - strictEqual(ents.length, 8); - - strictEqual(ents[0].name, 'bundle'); - strictEqual(ents[0].isDirectory(), true); - strictEqual(ents[0].isFile(), false); - strictEqual(ents[0].isBlockDevice(), false); - strictEqual(ents[0].isCharacterDevice(), false); - strictEqual(ents[0].isFIFO(), false); - strictEqual(ents[0].isSocket(), false); - strictEqual(ents[0].isSymbolicLink(), false); - strictEqual(ents[0].parentPath, '/'); - - strictEqual(ents[1].name, 'bundle/worker'); - strictEqual(ents[1].isDirectory(), false); - strictEqual(ents[1].isFile(), true); - strictEqual(ents[1].isBlockDevice(), false); - strictEqual(ents[1].isCharacterDevice(), false); - strictEqual(ents[1].isFIFO(), false); - strictEqual(ents[1].isSocket(), false); - strictEqual(ents[1].isSymbolicLink(), false); - strictEqual(ents[1].parentPath, '/bundle'); - strictEqual(ents[4].name, 'dev/null'); - strictEqual(ents[4].isDirectory(), false); - strictEqual(ents[4].isFile(), false); - strictEqual(ents[4].isBlockDevice(), false); - strictEqual(ents[4].isCharacterDevice(), true); - strictEqual(ents[4].isFIFO(), false); - strictEqual(ents[4].isSocket(), false); - strictEqual(ents[4].isSymbolicLink(), false); - strictEqual(ents[4].parentPath, '/dev'); - } - }, -}; - -const opendirSyncTest = { - test() { - throws(() => fs.opendirSync(), kInvalidArgTypeError); - throws(() => fs.opendirSync(123), kInvalidArgTypeError); - throws(() => fs.opendirSync('/tmp', { encoding: 123 }), kInvalidArgValueError); - - const dir = fs.opendirSync('/', { recursive: true }); - strictEqual(dir.path, '/'); - strictEqual(dir.readSync().name, 'bundle'); - strictEqual(dir.readSync().name, 'bundle/worker'); - strictEqual(dir.readSync().name, 'tmp'); - strictEqual(dir.readSync().name, 'dev'); - strictEqual(dir.readSync().name, 'dev/null'); - strictEqual(dir.readSync().name, 'dev/zero'); - strictEqual(dir.readSync().name, 'dev/full'); - strictEqual(dir.readSync().name, 'dev/random'); - strictEqual(dir.readSync(), null); // All done. - dir.closeSync(); - - // Closing again throws - throws(() => dir.closeSync(), { code: 'ERR_DIR_CLOSED' }); - // Reading again throws - throws(() => dir.readSync(), { code: 'ERR_DIR_CLOSED' }); - }, -}; \ No newline at end of file diff --git a/workerd-corpus-fs/fs-promises.js b/workerd-corpus-fs/fs-promises.js deleted file mode 100644 index feabad2c6..000000000 --- a/workerd-corpus-fs/fs-promises.js +++ /dev/null @@ -1,10 +0,0 @@ -function promises() { - // fs.promises API - let path = '/tmp/promise.txt'; - fs.promises.writeFile(path, 'promise data') - .then(() => fs.promises.readFile(path)) - .then(() => fs.promises.unlink(path)) - .catch(() => { }); -} - -promises(); \ No newline at end of file diff --git a/workerd-corpus-fs/fs-readstream-test.js b/workerd-corpus-fs/fs-readstream-test.js deleted file mode 100644 index 0b86eb5ad..000000000 --- a/workerd-corpus-fs/fs-readstream-test.js +++ /dev/null @@ -1,1038 +0,0 @@ -strictEqual(typeof fs.ReadStream, 'function'); -strictEqual(typeof fs.createReadStream, 'function'); - -const simpleReadStreamTest = { - async test() { - const largeData = 'abc'.repeat(100_000); - fs.writeFileSync('/tmp/foo', largeData); - - const stream = fs.createReadStream('/tmp/foo', { - encoding: 'utf8', - highWaterMark: 10_000, - }); - - let data = ''; - for await (const chunk of stream) { - data += chunk; - } - strictEqual(data, largeData); - }, -}; - -function prepareFile() { - const path = '/tmp/elipses.txt'; - fs.writeFileSync(path, '…'.repeat(10000)); - return path; -} - -async function runTest(options) { - let paused = false; - let bytesRead = 0; - - const path = prepareFile(); - - const file = fs.createReadStream(path, options); - const fileSize = fs.statSync(path).size; - - strictEqual(file.bytesRead, 0); - - const promises = []; - - const { promise: openPromise, resolve: openResolve } = - Promise.withResolvers(); - const { promise: endPromise, resolve: endResolve } = Promise.withResolvers(); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - promises.push(openPromise); - promises.push(endPromise); - promises.push(closePromise); - - const onOpen = mock.fn((fd) => { - file.length = 0; - strictEqual(typeof fd, 'number'); - strictEqual(file.bytesRead, 0); - ok(file.readable); - file.pause(); - file.resume(); - file.pause(); - file.resume(); - openResolve(); - }); - - const onData = mock.fn((data) => { - ok(data instanceof Buffer); - ok(data.byteOffset % 8 === 0); - ok(!paused); - file.length += data.length; - - bytesRead += data.length; - strictEqual(file.bytesRead, bytesRead); - - paused = true; - file.pause(); - - setTimeout(function () { - paused = false; - file.resume(); - }, 10); - }); - - const onEnd = mock.fn(() => { - strictEqual(bytesRead, fileSize); - strictEqual(file.bytesRead, fileSize); - endResolve(); - }); - - const onClose = mock.fn(() => { - strictEqual(bytesRead, fileSize); - strictEqual(file.bytesRead, fileSize); - closeResolve(); - }); - - file.once('open', onOpen); - file.once('end', onEnd); - file.once('close', onClose); - file.on('data', onData); - - await Promise.all(fs.promises); - - strictEqual(file.length, 30000); - strictEqual(onOpen.mock.callCount(), 1); - strictEqual(onEnd.mock.callCount(), 1); - strictEqual(onClose.mock.callCount(), 1); - strictEqual(onData.mock.callCount(), 1); -} - -const readStreamTest1 = { - async test() { - await runTest({}); - }, -}; - -const readStreamTest2 = { - async test() { - const customFs = { - open: mock.fn((...args) => fs.open(...args)), - read: mock.fn((...args) => fs.read(...args)), - close: mock.fn((...args) => fs.close(...args)), - }; - await runTest({ - fs: customFs, - }); - strictEqual(customFs.open.mock.callCount(), 1); - strictEqual(customFs.read.mock.callCount(), 2); - strictEqual(customFs.close.mock.callCount(), 1); - }, -}; - -const readStreamTest3 = { - async test() { - const path = prepareFile(); - const file = fs.createReadStream(path, { encoding: 'utf8' }); - file.length = 0; - file.on('data', function (data) { - strictEqual(typeof data, 'string'); - file.length += data.length; - - for (let i = 0; i < data.length; i++) { - // http://www.fileformat.info/info/unicode/char/2026/index.htm - strictEqual(data[i], '\u2026'); - } - }); - - const { promise, resolve } = Promise.withResolvers(); - file.on('close', resolve); - - await promise; - - strictEqual(file.length, 10000); - }, -}; - -const readStreamTest4 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz'); - const file = fs.createReadStream(path, { bufferSize: 1, start: 1, end: 2 }); - let contentRead = ''; - file.on('data', function (data) { - contentRead += data.toString('utf-8'); - }); - const { promise, resolve } = Promise.withResolvers(); - file.on('end', function (data) { - strictEqual(contentRead, 'yz'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest5 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const file = fs.createReadStream(path, { bufferSize: 1, start: 1 }); - file.data = ''; - file.on('data', function (data) { - file.data += data.toString('utf-8'); - }); - const { promise, resolve } = Promise.withResolvers(); - file.on('end', function () { - strictEqual(file.data, 'yz\n'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest6 = { - async test() { - // Ref: https://github.com/nodejs/node-v0.x-archive/issues/2320 - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const file = fs.createReadStream(path, { bufferSize: 1.23, start: 1 }); - file.data = ''; - file.on('data', function (data) { - file.data += data.toString('utf-8'); - }); - const { promise, resolve } = Promise.withResolvers(); - file.on('end', function () { - strictEqual(file.data, 'yz\n'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest7 = { - test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - throws(() => fs.createReadStream(path, { start: 10, end: 2 }), { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError', - }); - }, -}; - -const readStreamTest8 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const stream = fs.createReadStream(path, { start: 0, end: 0 }); - stream.data = ''; - - stream.on('data', function (chunk) { - stream.data += chunk; - }); - const { promise, resolve } = Promise.withResolvers(); - stream.on('end', function () { - strictEqual(stream.data, 'x'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest9 = { - async test() { - // Verify that end works when start is not specified. - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const stream = new fs.createReadStream(path, { end: 1 }); - stream.data = ''; - - stream.on('data', function (chunk) { - stream.data += chunk; - }); - - const { promise, resolve } = Promise.withResolvers(); - stream.on('end', function () { - strictEqual(stream.data, 'xy'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest10 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - let file = fs.createReadStream(path, { autoClose: false }); - let data = ''; - file.on('data', function (chunk) { - data += chunk; - }); - const { promise, resolve, reject } = Promise.withResolvers(); - file.on('end', function () { - strictEqual(data, 'xyz\n'); - process.nextTick(function () { - ok(!file.closed); - ok(!file.destroyed); - fileNext().then(resolve, reject); - }); - }); - - await promise; - - async function fileNext() { - // This will tell us if the fd is usable again or not. - file = fs.createReadStream(null, { fd: file.fd, start: 0 }); - file.data = ''; - file.on('data', function (data) { - file.data += data; - }); - const { promise, resolve } = Promise.withResolvers(); - const { promise: endPromise, resolve: endResolve } = - Promise.withResolvers(); - file.on('end', function (err) { - strictEqual(file.data, 'xyz\n'); - endResolve(); - }); - file.on('close', resolve); - await Promise.all([promise, endPromise]); - ok(file.closed); - ok(file.destroyed); - } - }, -}; - -const readStreamTest11 = { - async test() { - // Just to make sure autoClose won't close the stream because of error. - const { promise, resolve, reject } = Promise.withResolvers(); - const file = fs.createReadStream(null, { fd: 13337, autoClose: false }); - file.on('data', () => reject(new Error('should not be called'))); - file.on('error', resolve); - await promise; - ok(!file.closed); - ok(!file.destroyed); - ok(file.fd); - }, -}; - -const readStreamTest12 = { - async test() { - // Make sure stream is destroyed when file does not exist. - const file = fs.createReadStream('/path/to/file/that/does/not/exist'); - const { promise, resolve, reject } = Promise.withResolvers(); - file.on('data', () => reject(new Error('should not be called'))); - file.on('error', resolve); - await promise; - ok(file.closed); - ok(file.destroyed); - }, -}; - -const readStreamTest13 = { - async test() { - const example = '/tmp/x.txt'; - fs.writeFileSync(example, 'xyz\n'); - fs.createReadStream(example, undefined); - fs.createReadStream(example, null); - fs.createReadStream(example, 'utf8'); - fs.createReadStream(example, { encoding: 'utf8' }); - - const createReadStreamErr = (path, opt, error) => { - throws(() => fs.createReadStream(path, opt), error); - }; - - const typeError = { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - }; - - const rangeError = { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError', - }; - - [123, 0, true, false].forEach((opts) => - createReadStreamErr(example, opts, typeError) - ); - - // Case 0: Should not throw if either start or end is undefined - [{}, { start: 0 }, { end: Infinity }].forEach((opts) => - fs.createReadStream(example, opts) - ); - - // Case 1: Should throw TypeError if either start or end is not of type 'number' - [ - { start: 'invalid' }, - { end: 'invalid' }, - { start: 'invalid', end: 'invalid' }, - ].forEach((opts) => createReadStreamErr(example, opts, typeError)); - - // Case 2: Should throw RangeError if either start or end is NaN - [{ start: NaN }, { end: NaN }, { start: NaN, end: NaN }].forEach((opts) => - createReadStreamErr(example, opts, rangeError) - ); - - // Case 3: Should throw RangeError if either start or end is negative - [{ start: -1 }, { end: -1 }, { start: -1, end: -1 }].forEach((opts) => - createReadStreamErr(example, opts, rangeError) - ); - - // Case 4: Should throw RangeError if either start or end is fractional - [{ start: 0.1 }, { end: 0.1 }, { start: 0.1, end: 0.1 }].forEach((opts) => - createReadStreamErr(example, opts, rangeError) - ); - - // Case 5: Should not throw if both start and end are whole numbers - fs.createReadStream(example, { start: 1, end: 5 }); - - // Case 6: Should throw RangeError if start is greater than end - createReadStreamErr(example, { start: 5, end: 1 }, rangeError); - - // Case 7: Should throw RangeError if start or end is not safe integer - const NOT_SAFE_INTEGER = 2 ** 53; - [ - { start: NOT_SAFE_INTEGER, end: Infinity }, - { start: 0, end: NOT_SAFE_INTEGER }, - ].forEach((opts) => createReadStreamErr(example, opts, rangeError)); - }, -}; - -const readStreamTest14 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - let data = ''; - let first = true; - - const stream = fs.createReadStream(path); - stream.setEncoding('utf8'); - stream.on('data', function (chunk) { - data += chunk; - if (first) { - first = false; - stream.resume(); - } - }); - - const { promise, resolve } = Promise.withResolvers(); - - process.nextTick(function () { - stream.pause(); - setTimeout(function () { - stream.resume(); - resolve(); - }, 100); - }); - - await promise; - strictEqual(data, 'xyz\n'); - }, -}; - -const readStreamTest15 = { - async test() { - const { promise, resolve } = Promise.withResolvers(); - fs.ReadStream.prototype.open = resolve; - fs.createReadStream('asd'); - await promise; - delete fs.ReadStream.prototype.open; - }, -}; - -// const fn = fixtures.path('elipses.txt'); -// const rangeFile = fixtures.path('x.txt'); - -const readStreamTest16 = { - async test() { - let paused = false; - const path = prepareFile(); - - const file = fs.ReadStream(path); - - const promises = []; - const { promise: openPromise, resolve: openResolve } = - Promise.withResolvers(); - const { promise: endPromise, resolve: endResolve } = - Promise.withResolvers(); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - promises.push(openPromise); - promises.push(endPromise); - promises.push(closePromise); - - file.on('open', function (fd) { - file.length = 0; - strictEqual(typeof fd, 'number'); - ok(file.readable); - - // GH-535 - file.pause(); - file.resume(); - file.pause(); - file.resume(); - openResolve(); - }); - - file.on('data', function (data) { - ok(data instanceof Buffer); - ok(!paused); - file.length += data.length; - - paused = true; - file.pause(); - - setTimeout(function () { - paused = false; - file.resume(); - }, 10); - }); - - file.on('end', endResolve); - - file.on('close', function () { - strictEqual(file.length, 30000); - closeResolve(); - }); - - await Promise.all(fs.promises); - }, -}; - -const readStreamTest17 = { - async test() { - const path = prepareFile(); - const file = fs.createReadStream(path, { __proto__: { encoding: 'utf8' } }); - file.length = 0; - file.on('data', function (data) { - strictEqual(typeof data, 'string'); - file.length += data.length; - - for (let i = 0; i < data.length; i++) { - // http://www.fileformat.info/info/unicode/char/2026/index.htm - strictEqual(data[i], '\u2026'); - } - }); - - const { promise, resolve } = Promise.withResolvers(); - file.on('close', function () { - strictEqual(file.length, 10000); - resolve(); - }); - - await promise; - }, -}; - -const readStreamTest18 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const options = { __proto__: { bufferSize: 1, start: 1, end: 2 } }; - const file = fs.createReadStream(path, options); - strictEqual(file.start, 1); - strictEqual(file.end, 2); - let contentRead = ''; - file.on('data', function (data) { - contentRead += data.toString('utf-8'); - }); - - const { promise, resolve } = Promise.withResolvers(); - file.on('end', function () { - strictEqual(contentRead, 'yz'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest19 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const options = { __proto__: { bufferSize: 1, start: 1 } }; - const file = fs.createReadStream(path, options); - strictEqual(file.start, 1); - file.data = ''; - file.on('data', function (data) { - file.data += data.toString('utf-8'); - }); - const { promise, resolve } = Promise.withResolvers(); - file.on('end', function () { - strictEqual(file.data, 'yz\n'); - resolve(); - }); - await promise; - }, -}; - -// https://github.com/joyent/node/issues/2320 -const readStreamTest20 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const options = { __proto__: { bufferSize: 1.23, start: 1 } }; - const file = fs.createReadStream(path, options); - strictEqual(file.start, 1); - file.data = ''; - file.on('data', function (data) { - file.data += data.toString('utf-8'); - }); - const { promise, resolve } = Promise.withResolvers(); - file.on('end', function () { - strictEqual(file.data, 'yz\n'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest21 = { - test() { - const path = '/tmp/x.txt'; - throws(() => fs.createReadStream(path, { __proto__: { start: 10, end: 2 } }), { - code: 'ERR_OUT_OF_RANGE', - name: 'RangeError', - }); - }, -}; - -const readStreamTest22 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const options = { __proto__: { start: 0, end: 0 } }; - const stream = fs.createReadStream(path, options); - strictEqual(stream.start, 0); - strictEqual(stream.end, 0); - stream.data = ''; - - stream.on('data', function (chunk) { - stream.data += chunk; - }); - - const { promise, resolve } = Promise.withResolvers(); - stream.on('end', function () { - strictEqual(stream.data, 'x'); - resolve(); - }); - await promise; - }, -}; - -const readStreamTest23 = { - async test() { - const path = '/tmp/x.txt'; - let output = ''; - fs.writeFileSync(path, 'hello world'); - const fd = fs.openSync(path, 'r'); - const stream = fs.createReadStream(null, { fd: fd, encoding: 'utf8' }); - strictEqual(stream.path, undefined); - stream.on('data', (data) => { - output += data; - }); - const { promise, resolve } = Promise.withResolvers(); - stream.on('close', resolve); - await promise; - strictEqual(output, 'hello world'); - }, -}; - -const readStreamTest24 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'hello world'); - const stream = fs.createReadStream(path); - const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); - const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); - stream.close(resolve1); - stream.close(resolve2); - await Promise.all([promise1, promise2]); - }, -}; - -const readStreamTest25 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'hello world'); - const stream = fs.createReadStream(path); - const { promise: promise1, resolve: resolve1 } = Promise.withResolvers(); - const { promise: promise2, resolve: resolve2 } = Promise.withResolvers(); - stream.destroy(null, resolve1); - stream.destroy(null, resolve2); - await Promise.all([promise1, promise2]); - }, -}; - -const readStreamTest26 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'hello world'); - const fh = await fs.promises.open(path, 'r'); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - fh.on('close', closeResolve); - const stream = fs.createReadStream(null, { fd: fh, encoding: 'utf8' }); - let data = ''; - stream.on('data', (chunk) => (data += chunk)); - const { promise, resolve } = Promise.withResolvers(); - stream.on('end', () => resolve()); - await Promise.all([promise, closePromise]); - strictEqual(data, 'hello world'); - strictEqual(fh.fd, undefined); - }, -}; - -const readStreamTest27 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - const { promise, resolve, reject } = Promise.withResolvers(); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - handle.on('close', resolve); - const stream = fs.createReadStream(null, { fd: handle }); - stream.on('data', reject); - stream.on('close', closeResolve); - handle.close(); - await Promise.all([[promise, closePromise]]); - }, -}; - -const readStreamTest28 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - const { promise: handlePromise, resolve: handleResolve } = - Promise.withResolvers(); - const { promise: streamPromise, resolve: streamResolve } = - Promise.withResolvers(); - handle.on('close', handleResolve); - const stream = fs.createReadStream(null, { fd: handle }); - stream.on('close', streamResolve); - stream.on('data', () => handle.close()); - await Promise.all([handlePromise, streamPromise]); - }, -}; - -const readStreamTest29 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - const { promise: handlePromise, resolve: handleResolve } = - Promise.withResolvers(); - const { promise: streamPromise, resolve: streamResolve } = - Promise.withResolvers(); - handle.on('close', handleResolve); - const stream = fs.createReadStream(null, { fd: handle }); - stream.on('close', streamResolve); - stream.close(); - await Promise.all([handlePromise, streamPromise]); - }, -}; - -const readStreamTest30 = { - async test() { - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - throws(() => fs.createReadStream(null, { fd: handle, fs: {} }), { - code: 'ERR_METHOD_NOT_IMPLEMENTED', - }); - handle.close(); - }, -}; - -const readStreamTest31 = { - async test() { - // AbortSignal option test - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - const controller = new AbortController(); - const { signal } = controller; - const stream = handle.fs.createReadStream({ signal }); - - stream.on('data', () => { - throw new Error('boom'); - }); - stream.on('end', () => { - throw new Error('boom'); - }); - - const { promise: errorPromise, resolve: errorResolve } = - Promise.withResolvers(); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - stream.on('error', (err) => { - strictEqual(err.name, 'AbortError'); - errorResolve(); - }); - - handle.on('close', closeResolve); - stream.on('close', () => handle.close()); - - controller.abort(); - - await Promise.all([errorPromise, closePromise]); - }, -}; - -const readStreamTest32 = { - async test() { - // Already-aborted signal test - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - - const signal = AbortSignal.abort(); - const stream = handle.fs.createReadStream({ signal }); - - stream.on('data', () => { - throw new Error('boom'); - }); - stream.on('end', () => { - throw new Error('boom'); - }); - - const { promise: errorPromise, resolve: errorResolve } = - Promise.withResolvers(); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - - stream.on('error', (err) => { - strictEqual(err.name, 'AbortError'); - errorResolve(); - }); - - handle.on('close', closeResolve); - stream.on('close', () => handle.close()); - - await Promise.all([errorPromise, closePromise]); - }, -}; - -const readStreamTest33 = { - async test() { - // Invalid signal type test - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - - for (const signal of [ - 1, - {}, - [], - '', - NaN, - 1n, - () => { }, - Symbol(), - false, - true, - ]) { - throws(() => handle.fs.createReadStream({ signal }), { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - }); - } - handle.close(); - }, -}; - -const readStreamTest34 = { - async test() { - // Custom abort reason test - const path = '/tmp/x.txt'; - fs.writeFileSync(path, 'xyz\n'); - const handle = await fs.promises.open(path, 'r'); - const controller = new AbortController(); - const { signal } = controller; - const reason = new Error('some silly abort reason'); - const stream = handle.fs.createReadStream({ signal }); - - const { promise: errorPromise, resolve: errorResolve } = - Promise.withResolvers(); - const { promise: closePromise, resolve: closeResolve } = - Promise.withResolvers(); - - stream.on('error', (err) => { - strictEqual(err.name, 'AbortError'); - strictEqual(err.cause, reason); - errorResolve(); - }); - - handle.on('close', closeResolve); - stream.on('close', () => handle.close()); - - controller.abort(reason); - - await Promise.all([errorPromise, closePromise]); - }, -}; - -const emptyReadStreamTest = { - async test() { - fs.writeFileSync('/tmp/empty.txt', ''); - const stream = fs.createReadStream('/tmp/empty.txt'); - const { promise, resolve, reject } = Promise.withResolvers(); - stream.once('data', () => { - reject(new Error('should not emit data')); - }); - stream.once('end', resolve); - await promise; - strictEqual(stream.bytesRead, 0); - }, -}; - -const fileHandleReadableWebStreamTest = { - async test() { - fs.writeFileSync('/tmp/stream.txt', 'abcde'.repeat(1000)); - const fh = await fs.promises.open('/tmp/stream.txt', 'r'); - const stream = fh.readableWebStream(); - const enc = new TextEncoder(); - let data = ''; - for await (const chunk of stream) { - strictEqual(chunk instanceof Uint8Array, true); - data += new TextDecoder().decode(chunk); - } - strictEqual(data, 'abcde'.repeat(1000)); - strictEqual(fh.fd, undefined); - - // Should throw if the stream is closed. - throws(() => fh.readableWebStream(), { - code: 'EBADF', - }); - - const fh2 = await fs.promises.open('/tmp/stream.txt', 'r'); - const stream2 = fh2.readableWebStream({ autoClose: false }); - await fh2.close(); - const res = await stream2.getReader().read(); - strictEqual(res.done, true); - strictEqual(res.value, undefined); - }, -}; - -/** - * TODO(node-fs): Revisit - * Temporarily comment out. These are larger tests causing timeouts - * In CI. Will move them out to separate tests in a follow on PR -const readStreamTest98 = { - async test() { - const path = prepareFile(); - const content = fs.readFileSync(path); - - const N = 20; - let started = 0; - let done = 0; - - const arrayBuffers = new Set(); - const fs.promises = []; - - async function startRead() { - ++started; - const chunks = []; - const fs.promises = []; - const { promise, resolve } = Promise.withResolvers(); - fs.promises.push(promise); - fs.createReadStream(path) - .on('data', (chunk) => { - chunks.push(chunk); - arrayBuffers.add(chunk.buffer); - }) - .on('end', () => { - if (started < N) fs.promises.push(startRead()); - deepStrictEqual(Buffer.concat(chunks), content); - if (++done === N) { - const retainedMemory = [...arrayBuffers] - .map((ab) => ab.byteLength) - .reduce((a, b) => a + b); - ok( - retainedMemory / (N * content.length) <= 3, - `Retaining ${retainedMemory} bytes in ABs for ${N} ` + - `chunks of size ${content.length}` - ); - } - resolve(); - }); - await Promise.all(fs.promises); - } - - // Don’t start the reads all at once – that way we would have to allocate - // a large amount of memory upfront. - for (let i = 0; i < 6; ++i) { - fs.promises.push(startRead()); - } - await Promise.all(fs.promises); - }, -}; - -const readStreamTest99 = { - async test() { - const path = '/tmp/read_stream_pos_test.txt'; - fs.writeFileSync(path, ''); - - let counter = 0; - - const writeInterval = setInterval(() => { - counter = counter + 1; - const line = `hello at ${counter}\n`; - fs.writeFileSync(path, line, { flag: 'a' }); - }, 1); - - const hwm = 10; - let bufs = []; - let isLow = false; - let cur = 0; - let stream; - - const readInterval = setInterval(() => { - if (stream) return; - - stream = fs.createReadStream(path, { - highWaterMark: hwm, - start: cur, - }); - stream.on('data', (chunk) => { - cur += chunk.length; - bufs.push(chunk); - if (isLow) { - const brokenLines = Buffer.concat(bufs) - .toString() - .split('\n') - .filter((line) => { - const s = 'hello at'.slice(0, line.length); - if (line && !line.startsWith(s)) { - return true; - } - return false; - }); - strictEqual(brokenLines.length, 0); - exitTest(); - return; - } - if (chunk.length !== hwm) { - isLow = true; - } - }); - stream.on('end', () => { - stream = null; - isLow = false; - bufs = []; - }); - }, 10); - - // Time longer than 10 seconds to exit safely - await scheduler.wait(5_000); - - clearInterval(readInterval); - clearInterval(writeInterval); - - if (stream && !stream.destroyed) { - const { promise, resolve } = Promise.withResolvers(); - stream.on('close', resolve); - stream.destroy(); - await promise; - } - }, -}; -**/ diff --git a/workerd-corpus-fs/fs-utimes-test.js b/workerd-corpus-fs/fs-utimes-test.js deleted file mode 100644 index c6d60865f..000000000 --- a/workerd-corpus-fs/fs-utimes-test.js +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2017-2025 Cloudflare, Inc. -// Licensed under the Apache 2.0 license found in the LICENSE file or at: -// https://opensource.org/licenses/Apache-2.0 -const kInvalidArgTypeError = { code: 'ERR_INVALID_ARG_TYPE' }; - -function checkStat(path, mtimeMsCheck) { - const bigint = typeof mtimeMsCheck === 'bigint'; - const { atimeMs, mtimeMs, ctimeMs, birthtimeMs } = - typeof path === 'number' - ? ffs.statSync(path, { bigint }) - : fs.statSync(path, { bigint }); - strictEqual(mtimeMs, mtimeMsCheck); - strictEqual(ctimeMs, mtimeMsCheck); - strictEqual(atimeMs, bigint ? 0n : 0); - strictEqual(birthtimeMs, bigint ? 0n : 0); -} - -const utimesTest = { - async test() { - const fd = fs.openSync('/tmp/test.txt', 'w+'); - ok(existsSync('/tmp/test.txt')); - - checkStat(fd, 0n); - - fs.utimesSync('/tmp/test.txt', 1000, 2000); - checkStat(fd, 2000n); - - fs.utimesSync('/tmp/test.txt', 1000, new Date(0)); - checkStat(fd, 0); - - fs.utimesSync('/tmp/test.txt', 3000, '1970-01-01T01:00:00.000Z'); - checkStat(fd, 3600000n); - - fs.lutimesSync('/tmp/test.txt', 3000, 4000); - checkStat(fd, 4000n); - - fs.lutimesSync('/tmp/test.txt', 1000, new Date(0)); - checkStat(fd, 0); - - fs.lutimesSync('/tmp/test.txt', 3000, '1970-01-01T01:00:00.000Z'); - checkStat(fd, 3600000n); - - fs.futimesSync(fd, 5000, 6000); - checkStat(fd, 6000n); - - fs.futimesSync(fd, 1000, new Date(0)); - checkStat(fd, 0); - - fs.futimesSync(fd, 3000, '1970-01-01T01:00:00.000Z'); - checkStat(fd, 3600000n); - - { - const { promise, resolve, reject } = Promise.withResolvers(); - fs.utims('/tmp/test.txt', 8000, new Date('not a valid date'), (err) => { - try { - ok(err); - strictEqual(err.name, 'TypeError'); - match(err.message, /The value cannot be converted/); - resolve(); - } catch (err) { - reject(err); - } - }); - await promise; - } - - { - const { promise, resolve, reject } = Promise.withResolvers(); - fs.utims('/tmp/test.txt', 8000, 9000, (err) => { - if (err) return reject(err); - try { - checkStat(fd, 9000n); - resolve(); - } catch (err) { - reject(err); - } - }); - await promise; - } - - { - const { promise, resolve, reject } = Promise.withResolvers(); - fs.lutimes('/tmp/test.txt', 8000, 10000, (err) => { - if (err) return reject(err); - try { - checkStat(fd, 10000n); - resolve(); - } catch (err) { - reject(err); - } - }); - await promise; - } - - { - const { promise, resolve, reject } = Promise.withResolvers(); - fs.futimes(fd, 7000, 11000, (err) => { - if (err) return reject(err); - try { - checkStat(fd, 11000n); - resolve(); - } catch (err) { - reject(err); - } - }); - await promise; - } - - await promises.fs.utims('/tmp/test.txt', 12000, 13000); - checkStat(fd, 13000n); - await promises.fs.lutimes('/tmp/test.txt', 14000, 15000); - checkStat(fd, 15000n); - - fs.closeSync(fd); - }, -}; diff --git a/workerd-corpus-fs/fs-webfs-api.js b/workerd-corpus-fs/fs-webfs-api.js deleted file mode 100644 index 881488003..000000000 --- a/workerd-corpus-fs/fs-webfs-api.js +++ /dev/null @@ -1,118 +0,0 @@ -// Web File System API testing - FileSystemDirectoryHandle and FileSystemFileHandle -// Tests WHATWG spec implementation and potential memory corruption in handle operations - -async function runWebFSTests() { - try { - // Test StorageManager API - let rootDir = await navigator.storage.getDirectory(); - console.log('Root directory name:', rootDir.name); - console.log('Root directory kind:', rootDir.kind); - - // Test bundle directory access - let bundleDir = await rootDir.getDirectoryHandle('bundle'); - console.log('Bundle directory name:', bundleDir.name); - - // Test directory iteration (potential for OOB in bundle files) - let entryCount = 0; - for await (let [name, handle] of bundleDir) { - console.log(`Bundle entry: ${name}, kind: ${handle.kind}`); - entryCount++; - - if (handle.kind === 'file' && entryCount <= 2) { - // Test file handle operations on bundle files - let fileHandle = handle; - let file = await fileHandle.getFile(); - console.log(`Bundle file ${name} size:`, file.size); - - // Test reading bundle file content (potential OOB reads) - let text = await file.text(); - console.log(`Bundle file ${name} content length:`, text.length); - - if (file.size > 0) { - let arrayBuffer = await file.arrayBuffer(); - console.log(`Bundle file ${name} buffer length:`, arrayBuffer.byteLength); - } - } - } - - // Test temp directory operations with Web FS API - let tempDir = await rootDir.getDirectoryHandle('tmp'); - let testFile = await tempDir.getFileHandle('webfs-test.txt', { create: true }); - console.log('Created file handle:', testFile.name); - - // Test FileSystemWritableFileStream - let writable = await testFile.createWritable(); - console.log('Created writable stream'); - - await writable.write('Hello Web FS!'); - await writable.write(new Uint8Array([65, 66, 67])); // ABC - await writable.close(); - console.log('Writable stream closed'); - - // Test reading the written file - let writtenFile = await testFile.getFile(); - let content = await writtenFile.text(); - console.log('Written content length:', content.length); - - // Test file handle operations with various write modes - let writable2 = await testFile.createWritable({ keepExistingData: true }); - await writable2.seek(0); - await writable2.write('REPLACED'); - await writable2.truncate(8); - await writable2.close(); - - let modifiedFile = await testFile.getFile(); - let modifiedContent = await modifiedFile.text(); - console.log('Modified content:', modifiedContent); - - // Test directory handle operations - let subDir = await tempDir.getDirectoryHandle('webfs-subdir', { create: true }); - let subFile = await subDir.getFileHandle('nested.txt', { create: true }); - - let subWritable = await subFile.createWritable(); - await subWritable.write('Nested file content'); - await subWritable.close(); - - // Test directory traversal and file access patterns - let subdirEntries = []; - for await (let [name, handle] of subDir) { - subdirEntries.push(name); - if (handle.kind === 'file') { - let file = await handle.getFile(); - console.log(`Subdir file ${name} size:`, file.size); - } - } - console.log('Subdir entries:', subdirEntries); - - // Test handle comparison - let sameFile = await subDir.getFileHandle('nested.txt'); - let isSame = await subFile.isSameEntry(sameFile); - console.log('Handle comparison result:', isSame); - - // Test error conditions - try { - await tempDir.getFileHandle('nonexistent-file'); - } catch (e) { - console.log('Expected file not found:', e.name); - } - - try { - await rootDir.getDirectoryHandle('invalid-dir'); - } catch (e) { - console.log('Expected dir not found:', e.name); - } - - // Test removal operations - await subFile.remove(); - await subDir.removeEntry('nested.txt').catch(() => console.log('File already removed')); - await tempDir.removeEntry('webfs-subdir', { recursive: true }); - await testFile.remove(); - - } catch (error) { - console.error('Web FS test error:', error.message, error.name); - } -} - -// Execute the Web FS tests -await runWebFSTests(); -console.log('Web File System API tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs-writestream-test.js b/workerd-corpus-fs/fs-writestream-test.js deleted file mode 100644 index 9f506aa62..000000000 --- a/workerd-corpus-fs/fs-writestream-test.js +++ /dev/null @@ -1,491 +0,0 @@ -strictEqual(typeof fs.WriteStream, 'function'); -strictEqual(typeof fs.createWriteStream, 'function'); - -const writeStreamTest1 = { - async test() { - const path = '/tmp/workerd-fs-fs.WriteStream-test1.txt'; - const { promise, resolve } = Promise.withResolvers(); - const stream = fs.WriteStream(path, { - fs: { - close(fd) { - ok(fd); - fs.closeSync(fd); - resolve(); - }, - }, - }); - stream.destroy(); - await promise; - }, -}; - -const writeStreamTest2 = { - async test() { - const path = '/tmp/workerd-fs-fs.WriteStream-test2.txt'; - const stream = fs.createWriteStream(path); - - const { promise, resolve, reject } = Promise.withResolvers(); - - stream.on('drain', reject); - stream.on('close', resolve); - stream.destroy(); - await promise; - }, -}; - -const writeStreamTest3 = { - async test() { - const path = '/tmp/workerd-fs-fs.WriteStream-test3.txt'; - const stream = fs.createWriteStream(path); - const { promise, resolve, reject } = Promise.withResolvers(); - stream.on('error', reject); - stream.on('close', resolve); - throws(() => stream.write(42), { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - }); - stream.destroy(); - await promise; - }, -}; - -const writeStreamTest4 = { - test() { - const example = '/tmp/workerd-fs-fs.WriteStream-test4.txt'; - fs.createWriteStream(example, undefined).end(); - fs.createWriteStream(example, null).end(); - fs.createWriteStream(example, 'utf8').end(); - fs.createWriteStream(example, { encoding: 'utf8' }).end(); - - const createWriteStreamErr = (path, opt) => { - throws(() => fs.createWriteStream(path, opt), { - code: 'ERR_INVALID_ARG_TYPE', - name: 'TypeError', - }); - }; - - createWriteStreamErr(example, 123); - createWriteStreamErr(example, 0); - createWriteStreamErr(example, true); - createWriteStreamErr(example, false); - }, -}; - -const writeStreamTest5 = { - async test() { - const { promise, resolve } = Promise.withResolvers(); - fs.WriteStream.prototype.open = resolve; - fs.createWriteStream('/tmp/test'); - await promise; - delete fs.WriteStream.prototype.open; - }, -}; - -const writeStreamTest6 = { - async test() { - const path = '/tmp/write-end-test0.txt'; - const fs = { - open: mock.fn(fs.open), - write: mock.fn(fs.write), - close: mock.fn(fs.close), - }; - const { promise, resolve } = Promise.withResolvers(); - const stream = fs.createWriteStream(path, { fs }); - stream.on('close', resolve); - stream.end('asd'); - - await promise; - strictEqual(fs.open.mock.callCount(), 1); - strictEqual(fs.write.mock.callCount(), 1); - strictEqual(fs.close.mock.callCount(), 1); - }, -}; - -const writeStreamTest7 = { - async test() { - const path = '/tmp/write-end-test1.txt'; - const fs = { - open: mock.fn(fs.open), - write: fs.write, - writev: mock.fn(fs.write), - close: mock.fn(fs.close), - }; - const stream = fs.createWriteStream(path, { fs }); - stream.write('asd'); - stream.write('asd'); - stream.write('asd'); - stream.end(); - const { promise, resolve } = Promise.withResolvers(); - stream.on('close', resolve); - await promise; - - strictEqual(fs.open.mock.callCount(), 1); - strictEqual(fs.writev.mock.callCount(), 1); - strictEqual(fs.close.mock.callCount(), 1); - }, -}; - -let cnt = 0; -function nextFile() { - return `/tmp/${cnt++}.out`; -} - -const writeStreamTest8 = { - test() { - for (const flush of ['true', '', 0, 1, [], {}, Symbol()]) { - throws( - () => { - fs.createWriteStream(nextFile(), { flush }); - }, - { code: 'ERR_INVALID_ARG_TYPE' } - ); - } - }, -}; - -const writeStreamTest9 = { - async test() { - const fs = { - fsync: mock.fn(fs.fsync), - }; - const stream = fs.createWriteStream(nextFile(), { flush: true, fs }); - - const { promise, resolve, reject } = Promise.withResolvers(); - - stream.write('hello', (err) => { - if (err) return reject(); - stream.close((err) => { - if (err) return reject(err); - resolve(); - }); - }); - - await promise; - - strictEqual(fs.fsync.mock.callCount(), 1); - }, -}; - -const writeStreamTest10 = { - async test() { - const values = [undefined, null, false]; - const fs = { - fsync: mock.fn(fs.fsync), - }; - let cnt = 0; - - const { promise, resolve, reject } = Promise.withResolvers(); - - for (const flush of values) { - const file = nextFile(); - const stream = fs.createWriteStream(file, { flush }); - stream.write('hello world', (err) => { - if (err) return reject(err); - stream.close((err) => { - if (err) return reject(err); - strictEqual(fs.readFileSync(file, 'utf8'), 'hello world'); - cnt++; - if (cnt === values.length) { - strictEqual(fs.fsync.mock.callCount(), 0); - resolve(); - } - }); - }); - } - - await promise; - }, -}; - -const writeStreamTest11 = { - async test() { - const file = nextFile(); - const handle = await promises.open(file, 'w'); - const stream = handle.fs.createWriteStream({ flush: true }); - - const { promise, resolve, reject } = Promise.withResolvers(); - - stream.write('hello', (err) => { - if (err) return reject(err); - stream.close((err) => { - if (err) return reject(err); - strictEqual(fs.readFileSync(file, 'utf8'), 'hello'); - resolve(); - }); - }); - - await promise; - }, -}; - -const writeStreamTest12 = { - async test() { - const file = nextFile(); - const handle = await promises.open(file, 'w+'); - - const { promise, resolve } = Promise.withResolvers(); - handle.on('close', resolve); - const stream = fs.createWriteStream(null, { fd: handle }); - - stream.end('hello'); - stream.on('close', () => { - const output = fs.readFileSync(file, 'utf-8'); - strictEqual(output, 'hello'); - }); - - await promise; - }, -}; - -const writeStreamTest13 = { - async test() { - const file = nextFile(); - const handle = await promises.open(file, 'w+'); - let calls = 0; - const { write: originalWriteFunction, writev: originalWritevFunction } = - handle; - handle.write = mock.fn(handle.write.bind(handle)); - handle.writev = mock.fn(handle.writev.bind(handle)); - const stream = fs.createWriteStream(null, { fd: handle }); - stream.end('hello'); - const { promise, resolve } = Promise.withResolvers(); - stream.on('close', () => { - console.log('test'); - ok(handle.write.mock.callCount() + handle.writev.mock.callCount() > 0); - resolve(); - }); - await promise; - }, -}; - -const writeStreamTest14 = { - async test() { - const path = '/tmp/out'; - - let writeCalls = 0; - const fs = { - write: mock.fn((...args) => { - switch (writeCalls++) { - case 0: { - return fs.write(...args); - } - case 1: { - args[args.length - 1](new Error('BAM')); - break; - } - default: { - // It should not be called again! - throw new Error('BOOM!'); - } - } - }), - close: mock.fn(fs.close), - }; - - const stream = fs.createWriteStream(path, { - highWaterMark: 10, - fs, - }); - - const { promise: errorPromise, resolve: errorResolve } = - Promise.withResolvers(); - const { promise: writePromise, resolve: writeResolve } = - Promise.withResolvers(); - - stream.on('error', (err) => { - strictEqual(stream.fd, null); - strictEqual(err.message, 'BAM'); - errorResolve(); - }); - - stream.write(Buffer.allocUnsafe(256), () => { - stream.write(Buffer.allocUnsafe(256), (err) => { - strictEqual(err.message, 'BAM'); - writeResolve(); - }); - }); - - await Promise.all([errorPromise, writePromise]); - }, -}; - -const writeStreamTest15 = { - async test() { - const file = '/tmp/write-end-test0.txt'; - const stream = fs.createWriteStream(file); - stream.end(); - const { promise, resolve } = Promise.withResolvers(); - stream.on('close', resolve); - await promise; - }, -}; - -const writeStreamTest16 = { - async test() { - const file = '/tmp/write-end-test1.txt'; - const stream = fs.createWriteStream(file); - stream.end('a\n', 'utf8'); - const { promise, resolve } = Promise.withResolvers(); - stream.on('close', () => { - const content = fs.readFileSync(file, 'utf8'); - strictEqual(content, 'a\n'); - resolve(); - }); - await promise; - }, -}; - -const writeStreamTest17 = { - async test() { - const file = '/tmp/write-end-test2.txt'; - const stream = fs.createWriteStream(file); - stream.end(); - - const { promise: openPromise, resolve: openResolve } = - Promise.withResolvers(); - const { promise: finishPromise, resolve: finishResolve } = - Promise.withResolvers(); - stream.on('open', openResolve); - stream.on('finish', finishResolve); - await Promise.all([openPromise, finishPromise]); - }, -}; - -const writeStreamTest18 = { - async test() { - const examplePath = '/tmp/a'; - const dummyPath = '/tmp/b'; - const firstEncoding = 'base64'; - const secondEncoding = 'latin1'; - - const exampleReadStream = fs.createReadStream(examplePath, { - encoding: firstEncoding, - }); - - const dummyWriteStream = fs.createWriteStream(dummyPath, { - encoding: firstEncoding, - }); - - const { promise, resolve } = Promise.withResolvers(); - exampleReadStream.pipe(dummyWriteStream).on('finish', () => { - const assertWriteStream = new Writable({ - write: function (chunk, enc, next) { - const expected = Buffer.from('xyz\n'); - deepStrictEqual(expected, chunk); - }, - }); - assertWriteStream.setDefaultEncoding(secondEncoding); - fs.createReadStream(dummyPath, { - encoding: secondEncoding, - }) - .pipe(assertWriteStream) - .on('close', resolve); - }); - - await promise; - }, -}; - -const writeStreamTest19 = { - async test() { - const file = '/tmp/write-end-test3.txt'; - const stream = fs.createWriteStream(file); - const { promise: closePromise1, resolve: closeResolve1 } = - Promise.withResolvers(); - const { promise: closePromise2, resolve: closeResolve2 } = - Promise.withResolvers(); - stream.close(closeResolve1); - stream.close(closeResolve2); - await Promise.all([closePromise1, closePromise2]); - }, -}; - -const writeStreamTest20 = { - async test() { - const file = '/tmp/write-autoclose-opt1.txt'; - let stream = fs.createWriteStream(file, { flags: 'w+', autoClose: false }); - stream.write('Test1'); - stream.end(); - const { promise, resolve, reject } = Promise.withResolvers(); - stream.on('finish', () => { - stream.on('close', reject); - process.nextTick(() => { - strictEqual(stream.closed, false); - notStrictEqual(stream.fd, null); - resolve(); - }); - }); - await promise; - - const { promise: nextPromise, resolve: nextResolve } = - Promise.withResolvers(); - const stream2 = fs.createWriteStream(null, { fd: stream.fd, start: 0 }); - stream2.write('Test2'); - stream2.end(); - stream2.on('finish', () => { - strictEqual(stream2.closed, false); - stream2.on('close', () => { - strictEqual(stream2.fd, null); - strictEqual(stream2.closed, true); - nextResolve(); - }); - }); - - await nextPromise; - - const data = fs.readFileSync(file, 'utf8'); - strictEqual(data, 'Test2'); - }, -}; - -const writeStreamTest21 = { - async test() { - // This is to test success scenario where autoClose is true - const file = '/tmp/write-autoclose-opt2.txt'; - const stream = fs.createWriteStream(file, { autoClose: true }); - stream.write('Test3'); - stream.end(); - const { promise, resolve } = Promise.withResolvers(); - stream.on('finish', () => { - strictEqual(stream.closed, false); - stream.on('close', () => { - strictEqual(stream.fd, null); - strictEqual(stream.closed, true); - resolve(); - }); - }); - await promise; - }, -}; - -const writeStreamTest22 = { - test() { - throws(() => fs.WriteStream.prototype.autoClose, { - code: 'ERR_INVALID_THIS', - }); - }, -}; - -await simpleWriteStreamTest.test(); -await writeStreamTest1.test(); -await writeStreamTest2.test(); -await writeStreamTest3.test(); -writeStreamTest4.test(); -await writeStreamTest5.test(); -await writeStreamTest6.test(); -await writeStreamTest7.test(); -writeStreamTest8.test(); -await writeStreamTest9.test(); -await writeStreamTest10.test(); -await writeStreamTest11.test(); -await writeStreamTest12.test(); -await writeStreamTest13.test(); -await writeStreamTest14.test(); -await writeStreamTest15.test(); -await writeStreamTest16.test(); -await writeStreamTest17.test(); -await writeStreamTest18.test(); -await writeStreamTest19.test(); -await writeStreamTest20.test(); -await writeStreamTest21.test(); -writeStreamTest22.test(); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_basic_sync_ops.js b/workerd-corpus-fs/fs_basic_sync_ops.js deleted file mode 100644 index a329f5b8c..000000000 --- a/workerd-corpus-fs/fs_basic_sync_ops.js +++ /dev/null @@ -1,50 +0,0 @@ -// Basic synchronous filesystem operations targeting FileSystemModule C++ API -// Focus on stat, readdir, readFile operations with bundle and temp directories - -// Test basic stat operations on bundle directory (potential OOB reads) -let bundleStat = fs.statSync('/bundle'); -console.log('Bundle is directory:', bundleStat.isDirectory()); - -// Test readdir on bundle directory - critical for memory safety -let bundleFiles = fs.readdirSync('/bundle'); -console.log('Bundle files count:', bundleFiles.length); - -// Test reading bundle files with potential for OOB reads -if (bundleFiles.length > 0) { - let firstFile = bundleFiles[0]; - let content = fs.readFileSync(`/bundle/${firstFile}`); - console.log('First bundle file size:', content.length); - - // Test with different read positions and sizes - if (content.length > 10) { - let partial = fs.readFileSync(`/bundle/${firstFile}`, { encoding: 'utf8' }); - console.log('Content type:', typeof partial); - } -} - -// Test temp directory operations -fs.mkdirSync('/tmp/fuzz-test', { recursive: true }); -fs.writeFileSync('/tmp/fuzz-test/data.txt', 'Hello Fuzzer!'); - -let tempStat = fs.statSync('/tmp/fuzz-test/data.txt'); -console.log('Temp file size:', tempStat.size); - -let tempContent = fs.readFileSync('/tmp/fuzz-test/data.txt', 'utf8'); -console.log('Temp content:', tempContent); - -// Test file descriptor operations with potential for corruption -let fd = fs.openSync('/tmp/fuzz-test/data.txt', 'r+'); -let fdStat = fs.fstatSync(fd); -console.log('FD stat size:', fdStat.size); - -let buffer = Buffer.alloc(20); -let bytesRead = fs.readSync(fd, buffer, 0, 5, 0); -console.log('Bytes read via FD:', bytesRead); - -fs.closeSync(fd); - -// Cleanup -fs.unlinkSync('/tmp/fuzz-test/data.txt'); -fs.rmdirSync('/tmp/fuzz-test'); - -console.log('Sync operations test completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_buffer_operations.js b/workerd-corpus-fs/fs_buffer_operations.js deleted file mode 100644 index 14afd4159..000000000 --- a/workerd-corpus-fs/fs_buffer_operations.js +++ /dev/null @@ -1,173 +0,0 @@ -// Buffer and binary data operations testing -// Focus on potential buffer overflows, OOB reads, and memory corruption - -// Create various buffer sizes for testing -let smallBuffer = Buffer.alloc(10); -let mediumBuffer = Buffer.alloc(1024); -let largeBuffer = Buffer.alloc(64 * 1024); // 64KB - -// Test writing different buffer sizes -fs.writeFileSync('/tmp/small-buffer.bin', smallBuffer); -fs.writeFileSync('/tmp/medium-buffer.bin', mediumBuffer); - -// Fill buffers with test patterns -smallBuffer.fill(0xAA); -mediumBuffer.fill(0xBB); -largeBuffer.fill(0xCC); - -fs.writeFileSync('/tmp/pattern-small.bin', smallBuffer); -fs.writeFileSync('/tmp/pattern-medium.bin', mediumBuffer); -fs.writeFileSync('/tmp/pattern-large.bin', largeBuffer); - -// Test reading with various buffer configurations -let readBuffer = Buffer.alloc(100); - -// Test reading more than file size -let smallContent = fs.readFileSync('/tmp/pattern-small.bin'); -console.log('Small file size:', smallContent.length); - -let fd = fs.openSync('/tmp/pattern-medium.bin', 'r'); - -// Test various read configurations that might trigger boundary errors -try { - // Read at different offsets - fs.readSync(fd, readBuffer, 0, 50, 0); // Normal read - fs.readSync(fd, readBuffer, 50, 50, 100); // Read from middle - fs.readSync(fd, readBuffer, 0, 10, 1020); // Read near end - - // Test boundary conditions - fs.readSync(fd, readBuffer, 0, 1, 1023); // Last byte - - // Test reading beyond file end (should handle gracefully) - try { - fs.readSync(fd, readBuffer, 0, 100, 1000); // Beyond file - } catch (e) { - console.log('Beyond file read handled:', e.code); - } - -} catch (error) { - console.log('Read operation error:', error.code); -} - -fs.closeSync(fd); - -// Test writing with buffer overruns and underruns -let writeBuffer = Buffer.from('Test data for write operations'); -let writeFd = fs.openSync('/tmp/write-test.bin', 'w+'); - -// Test various write scenarios -try { - fs.writeSync(writeFd, writeBuffer, 0, writeBuffer.length, 0); - fs.writeSync(writeFd, writeBuffer, 0, 5, 100); // Write 5 bytes at offset 100 - - // Test writing with buffer boundaries - fs.writeSync(writeFd, writeBuffer, 10, 10, 50); - - // Test zero-length writes - fs.writeSync(writeFd, writeBuffer, 0, 0, 200); - -} catch (error) { - console.log('Write operation error:', error.code); -} - -// Test readv/writev operations (vectored I/O) -let vec1 = Buffer.from('Vector1'); -let vec2 = Buffer.from('Vector2'); -let vec3 = Buffer.from('Vector3'); - -try { - let bytesWritten = fs.writevSync(writeFd, [vec1, vec2, vec3], 300); - console.log('Vectored write bytes:', bytesWritten); -} catch (error) { - console.log('Writev error:', error.code); -} - -fs.closeSync(writeFd); - -// Test reading back with readv -let readFd = fs.openSync('/tmp/write-test.bin', 'r'); -let rvec1 = Buffer.alloc(7); -let rvec2 = Buffer.alloc(7); -let rvec3 = Buffer.alloc(7); - -try { - let bytesRead = fs.readvSync(readFd, [rvec1, rvec2, rvec3], 300); - console.log('Vectored read bytes:', bytesRead); - console.log('Read vectors:', rvec1.toString(), rvec2.toString(), rvec3.toString()); -} catch (error) { - console.log('Readv error:', error.code); -} - -fs.closeSync(readFd); - -// Test bundle file buffer operations (potential OOB reads) -let bundleFiles = fs.readdirSync('/bundle'); -if (bundleFiles.length > 0) { - let bundleFile = bundleFiles[0]; - let bundleContent = fs.readFileSync(`/bundle/${bundleFile}`); - - if (bundleContent.length > 0) { - // Test reading bundle content with different buffer sizes - let bundleFd = fs.openSync(`/bundle/${bundleFile}`, 'r'); - let tinyBuffer = Buffer.alloc(1); - let exactBuffer = Buffer.alloc(bundleContent.length); - let oversizedBuffer = Buffer.alloc(bundleContent.length + 100); - - // Test reading with exact size - try { - fs.readSync(bundleFd, exactBuffer, 0, bundleContent.length, 0); - console.log('Exact buffer read successful'); - } catch (e) { - console.log('Exact buffer read error:', e.code); - } - - // Test reading with oversized buffer (check for OOB) - try { - let bytesRead = fs.readSync(bundleFd, oversizedBuffer, 0, oversizedBuffer.length, 0); - console.log('Oversized buffer read bytes:', bytesRead); - } catch (e) { - console.log('Oversized buffer read error:', e.code); - } - - fs.closeSync(bundleFd); - } -} - -// Test TypedArray operations -let uint8Array = new Uint8Array(50); -let uint16Array = new Uint16Array(25); -let uint32Array = new Uint32Array(12); - -fs.writeFileSync('/tmp/uint8.bin', uint8Array); -fs.writeFileSync('/tmp/uint16.bin', uint16Array); -fs.writeFileSync('/tmp/uint32.bin', uint32Array); - -// Test reading into TypedArrays -let readArray = new Uint8Array(100); -let arrayFd = fs.openSync('/tmp/uint8.bin', 'r'); - -try { - let arrayBytesRead = fs.readSync(arrayFd, readArray, 0, readArray.length, 0); - console.log('TypedArray read bytes:', arrayBytesRead); -} catch (error) { - console.log('TypedArray read error:', error.code); -} - -fs.closeSync(arrayFd); - -// Cleanup -let cleanupFiles = [ - '/tmp/small-buffer.bin', '/tmp/medium-buffer.bin', - '/tmp/pattern-small.bin', '/tmp/pattern-medium.bin', '/tmp/pattern-large.bin', - '/tmp/write-test.bin', '/tmp/uint8.bin', '/tmp/uint16.bin', '/tmp/uint32.bin' -]; - -for (let file of cleanupFiles) { - try { - fs.unlinkSync(file); - } catch (e) { - console.log(`Cleanup error for ${file}:`, e.code); - } -} - -console.log('Buffer operations testing completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_bundle_boundary_tests.js b/workerd-corpus-fs/fs_bundle_boundary_tests.js deleted file mode 100644 index a48f7ef16..000000000 --- a/workerd-corpus-fs/fs_bundle_boundary_tests.js +++ /dev/null @@ -1,66 +0,0 @@ -// Bundle file boundary testing - specifically targeting potential OOB reads -// Tests edge cases around bundle file access that could trigger memory corruption - -// Test bundle directory traversal -let bundleEntries = fs.readdirSync('/bundle', { recursive: true }); -console.log('Total bundle entries:', bundleEntries.length); - -// Test accessing bundle files with various path patterns -for (let entry of bundleEntries.slice(0, 3)) { - try { - let fullPath = `/bundle/${entry}`; - let stat = fs.statSync(fullPath); - - if (stat.isFile()) { - // Test reading at different positions to stress boundary conditions - let content = fs.readFileSync(fullPath); - console.log(`Bundle file ${entry} size:`, content.length); - - // Test partial reads that might trigger OOB - if (content.length > 0) { - let fd = fs.openSync(fullPath, 'r'); - let smallBuffer = Buffer.alloc(1); - let largeBuffer = Buffer.alloc(content.length + 100); // Intentionally oversized - - // Read at boundary positions - fs.readSync(fd, smallBuffer, 0, 1, 0); // First byte - if (content.length > 1) { - fs.readSync(fd, smallBuffer, 0, 1, content.length - 1); // Last byte - } - - // Test reading more than available (should be safe but test boundary) - try { - fs.readSync(fd, largeBuffer, 0, largeBuffer.length, 0); - } catch (e) { - console.log('Expected boundary error:', e.code); - } - - fs.closeSync(fd); - } - } - } catch (error) { - console.log(`Error accessing ${entry}:`, error.code); - } -} - -// Test invalid bundle paths (should fail gracefully) -try { - fs.readFileSync('/bundle/../../../etc/passwd'); -} catch (e) { - console.log('Path traversal blocked:', e.code); -} - -try { - fs.readFileSync('/bundle/nonexistent-file-xyz'); -} catch (e) { - console.log('Nonexistent file handled:', e.code); -} - -// Test bundle file with null bytes and special chars -try { - fs.readFileSync('/bundle/test\0file'); -} catch (e) { - console.log('Null byte path handled:', e.code); -} - -console.log('Bundle boundary tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_promises_api.js b/workerd-corpus-fs/fs_promises_api.js deleted file mode 100644 index b3b1b90fd..000000000 --- a/workerd-corpus-fs/fs_promises_api.js +++ /dev/null @@ -1,88 +0,0 @@ -// Promises-based filesystem API testing -// Tests async operations that could reveal race conditions or memory issues - -async function runPromiseTests() { - try { - // Test bundle access via promises - let bundleStat = await fs.promises.stat('/bundle'); - console.log('Async bundle stat:', bundleStat.isDirectory()); - - let bundleFiles = await fs.promises.readdir('/bundle'); - console.log('Async bundle files:', bundleFiles.length); - - // Test reading bundle files asynchronously - if (bundleFiles.length > 0) { - let firstFile = bundleFiles[0]; - let content = await fs.promises.readFile(`/bundle/${firstFile}`); - console.log('Async bundle file size:', content.length); - } - - // Test temp directory operations with promises - await fs.promises.mkdir('/tmp/async-test', { recursive: true }); - await fs.promises.writeFile('/tmp/async-test/data.bin', Buffer.from('Binary data test')); - - let tempStat = await fs.promises.stat('/tmp/async-test/data.bin'); - console.log('Async temp file size:', tempStat.size); - - // Test FileHandle operations (potential for fd leaks/corruption) - let fileHandle = await fs.promises.open('/tmp/async-test/data.bin', 'r+'); - console.log('FileHandle fd:', fileHandle.fd); - - let handleStat = await fileHandle.stat(); - console.log('FileHandle stat size:', handleStat.size); - - let readBuffer = Buffer.alloc(20); - let readResult = await fileHandle.read(readBuffer, 0, 10, 0); - console.log('FileHandle read bytes:', readResult.bytesRead); - - let writeBuffer = Buffer.from('ASYNC'); - let writeResult = await fileHandle.write(writeBuffer, 0, writeBuffer.length, handleStat.size); - console.log('FileHandle wrote bytes:', writeResult.bytesWritten); - - await fileHandle.close(); - console.log('FileHandle closed'); - - // Test concurrent async operations (stress test) - let promises = []; - for (let i = 0; i < 3; i++) { - promises.push( - fs.promises.writeFile(`/tmp/async-test/concurrent-${i}.txt`, `Concurrent data ${i}`) - ); - } - - await Promise.all(promises); - console.log('Concurrent writes completed'); - - // Test concurrent reads - let readPromises = []; - for (let i = 0; i < 3; i++) { - readPromises.push( - fs.promises.readFile(`/tmp/async-test/concurrent-${i}.txt`, 'utf8') - ); - } - - let results = await Promise.all(readPromises); - console.log('Concurrent reads completed:', results.length); - - // Test error handling with promises - try { - await fs.promises.readFile('/bundle/nonexistent-async-file'); - } catch (e) { - console.log('Async error handled:', e.code); - } - - // Cleanup - for (let i = 0; i < 3; i++) { - await fs.promises.unlink(`/tmp/async-test/concurrent-${i}.txt`); - } - await fs.promises.unlink('/tmp/async-test/data.bin'); - await fs.promises.rmdir('/tmp/async-test'); - - } catch (error) { - console.error('Promise test error:', error.message); - } -} - -// Execute the async tests -await runPromiseTests(); -console.log('Promise API tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/fs_webfs_api.js b/workerd-corpus-fs/fs_webfs_api.js deleted file mode 100644 index 881488003..000000000 --- a/workerd-corpus-fs/fs_webfs_api.js +++ /dev/null @@ -1,118 +0,0 @@ -// Web File System API testing - FileSystemDirectoryHandle and FileSystemFileHandle -// Tests WHATWG spec implementation and potential memory corruption in handle operations - -async function runWebFSTests() { - try { - // Test StorageManager API - let rootDir = await navigator.storage.getDirectory(); - console.log('Root directory name:', rootDir.name); - console.log('Root directory kind:', rootDir.kind); - - // Test bundle directory access - let bundleDir = await rootDir.getDirectoryHandle('bundle'); - console.log('Bundle directory name:', bundleDir.name); - - // Test directory iteration (potential for OOB in bundle files) - let entryCount = 0; - for await (let [name, handle] of bundleDir) { - console.log(`Bundle entry: ${name}, kind: ${handle.kind}`); - entryCount++; - - if (handle.kind === 'file' && entryCount <= 2) { - // Test file handle operations on bundle files - let fileHandle = handle; - let file = await fileHandle.getFile(); - console.log(`Bundle file ${name} size:`, file.size); - - // Test reading bundle file content (potential OOB reads) - let text = await file.text(); - console.log(`Bundle file ${name} content length:`, text.length); - - if (file.size > 0) { - let arrayBuffer = await file.arrayBuffer(); - console.log(`Bundle file ${name} buffer length:`, arrayBuffer.byteLength); - } - } - } - - // Test temp directory operations with Web FS API - let tempDir = await rootDir.getDirectoryHandle('tmp'); - let testFile = await tempDir.getFileHandle('webfs-test.txt', { create: true }); - console.log('Created file handle:', testFile.name); - - // Test FileSystemWritableFileStream - let writable = await testFile.createWritable(); - console.log('Created writable stream'); - - await writable.write('Hello Web FS!'); - await writable.write(new Uint8Array([65, 66, 67])); // ABC - await writable.close(); - console.log('Writable stream closed'); - - // Test reading the written file - let writtenFile = await testFile.getFile(); - let content = await writtenFile.text(); - console.log('Written content length:', content.length); - - // Test file handle operations with various write modes - let writable2 = await testFile.createWritable({ keepExistingData: true }); - await writable2.seek(0); - await writable2.write('REPLACED'); - await writable2.truncate(8); - await writable2.close(); - - let modifiedFile = await testFile.getFile(); - let modifiedContent = await modifiedFile.text(); - console.log('Modified content:', modifiedContent); - - // Test directory handle operations - let subDir = await tempDir.getDirectoryHandle('webfs-subdir', { create: true }); - let subFile = await subDir.getFileHandle('nested.txt', { create: true }); - - let subWritable = await subFile.createWritable(); - await subWritable.write('Nested file content'); - await subWritable.close(); - - // Test directory traversal and file access patterns - let subdirEntries = []; - for await (let [name, handle] of subDir) { - subdirEntries.push(name); - if (handle.kind === 'file') { - let file = await handle.getFile(); - console.log(`Subdir file ${name} size:`, file.size); - } - } - console.log('Subdir entries:', subdirEntries); - - // Test handle comparison - let sameFile = await subDir.getFileHandle('nested.txt'); - let isSame = await subFile.isSameEntry(sameFile); - console.log('Handle comparison result:', isSame); - - // Test error conditions - try { - await tempDir.getFileHandle('nonexistent-file'); - } catch (e) { - console.log('Expected file not found:', e.name); - } - - try { - await rootDir.getDirectoryHandle('invalid-dir'); - } catch (e) { - console.log('Expected dir not found:', e.name); - } - - // Test removal operations - await subFile.remove(); - await subDir.removeEntry('nested.txt').catch(() => console.log('File already removed')); - await tempDir.removeEntry('webfs-subdir', { recursive: true }); - await testFile.remove(); - - } catch (error) { - console.error('Web FS test error:', error.message, error.name); - } -} - -// Execute the Web FS tests -await runWebFSTests(); -console.log('Web File System API tests completed'); \ No newline at end of file diff --git a/workerd-corpus-fs/function-references.js b/workerd-corpus-fs/function-references.js deleted file mode 100644 index 620c7d352..000000000 --- a/workerd-corpus-fs/function-references.js +++ /dev/null @@ -1,12 +0,0 @@ -function refs() { - // Function reference variations - let writeFile = fs.writeFileSync; - let readFile = fs.readFileSync; - let deleteFile = fs.unlinkSync; - - let path = '/tmp/ref.txt'; - writeFile(path, 'function ref test'); - readFile(path); - deleteFile(path); -} -refs(); diff --git a/workerd-corpus-fs/invalid-args.js b/workerd-corpus-fs/invalid-args.js deleted file mode 100644 index ff55eb320..000000000 --- a/workerd-corpus-fs/invalid-args.js +++ /dev/null @@ -1,6 +0,0 @@ -function testInv() { - fs.writeFileSync(null, 'data'); - fs.readSync(123, Buffer.alloc(10), -1); - fs.truncateSync('/tmp/test.txt', -5); -} -testInv(); \ No newline at end of file diff --git a/workerd-corpus-fs/large-buffers.js b/workerd-corpus-fs/large-buffers.js deleted file mode 100644 index c7007d4e3..000000000 --- a/workerd-corpus-fs/large-buffers.js +++ /dev/null @@ -1,9 +0,0 @@ -function largeBuf() { - // Large buffer operations - let path = '/tmp/large.txt'; - let large = Buffer.alloc(1024, 65); - fs.writeFileSync(path, large); - let result = fs.readFileSync(path); - fs.unlinkSync(path); -} -largeBuf(); \ No newline at end of file diff --git a/workerd-corpus-fs/mixed-sync-async.js b/workerd-corpus-fs/mixed-sync-async.js deleted file mode 100644 index d17211d61..000000000 --- a/workerd-corpus-fs/mixed-sync-async.js +++ /dev/null @@ -1,12 +0,0 @@ -function mixed() { - // Mix sync and async operations - let path = '/tmp/mixed.txt'; - fs.writeFileSync(path, 'sync'); - fs.readFile(path, (err, data) => { - if (!err) { - fs.appendFileSync(path, 'more'); - fs.unlink(path, () => { }); - } - }); -} -mixed(); \ No newline at end of file diff --git a/workerd-corpus-fs/multiple-fd-ops.js b/workerd-corpus-fs/multiple-fd-ops.js deleted file mode 100644 index 872a6d601..000000000 --- a/workerd-corpus-fs/multiple-fd-ops.js +++ /dev/null @@ -1,15 +0,0 @@ -function ops() { - // Multiple file descriptor operations - let path = '/tmp/multi-fd.txt'; - fs.writeFileSync(path, 'test data'); - - let fd1 = fs.openSync(path, 'r'); - let fd2 = fs.openSync(path, 'r'); - - fs.readSync(fd1, Buffer.alloc(4), 0, 4, 0); - fs.readSync(fd2, Buffer.alloc(4), 0, 4, 5); - - fs.closeSync(fd1); - fs.closeSync(fd2); - fs.unlinkSync(path); -} diff --git a/workerd-corpus-fs/open-modes.js b/workerd-corpus-fs/open-modes.js deleted file mode 100644 index c4a55e1cb..000000000 --- a/workerd-corpus-fs/open-modes.js +++ /dev/null @@ -1,14 +0,0 @@ -function modes() { - - // Different open modes - let fd; - try { - fd = fs.openSync('/tmp/modes.txt', 'r+'); - } catch (e) { - fd = fs.openSync('/tmp/modes.txt', 'w+'); - } - fs.writeSync(fd, 'mode test'); - fs.closeSync(fd); - fs.unlinkSync('/tmp/modes.txt'); -} -modes(); \ No newline at end of file diff --git a/workerd-corpus-fs/position-seeking.js b/workerd-corpus-fs/position-seeking.js deleted file mode 100644 index 4b9f643a5..000000000 --- a/workerd-corpus-fs/position-seeking.js +++ /dev/null @@ -1,10 +0,0 @@ -function seeking() { - // Position-based operations - let fd = fs.openSync('/tmp/pos.txt', 'w+'); - fs.writeSync(fd, 'ABCDEFGHIJ'); - let buf = Buffer.alloc(3); - fs.readSync(fd, buf, 0, 3, 2); - fs.closeSync(fd); - fs.unlinkSync('/tmp/pos.txt'); -} -seeking(); \ No newline at end of file diff --git a/workerd-corpus-fs/simple.js b/workerd-corpus-fs/simple.js deleted file mode 100644 index bf6b81796..000000000 --- a/workerd-corpus-fs/simple.js +++ /dev/null @@ -1 +0,0 @@ -console.log("Hello"); diff --git a/workerd-corpus-fs/stat-operations.js b/workerd-corpus-fs/stat-operations.js deleted file mode 100644 index fc17e3c25..000000000 --- a/workerd-corpus-fs/stat-operations.js +++ /dev/null @@ -1,10 +0,0 @@ -function statTest() { - // Stat operations - let path = '/tmp/stat.txt'; - fs.writeFileSync(path, 'test'); - let stats = fs.statSync(path); - stats.isFile(); - stats.isDirectory(); - fs.unlinkSync(path); -} -statTest(); \ No newline at end of file diff --git a/workerd-corpus-fs/truncate-operations.js b/workerd-corpus-fs/truncate-operations.js deleted file mode 100644 index 8169ab2bf..000000000 --- a/workerd-corpus-fs/truncate-operations.js +++ /dev/null @@ -1,11 +0,0 @@ - - -// Truncate operations -function trunc() { - let path = '/tmp/truncate.txt'; - fs.writeFileSync(path, 'long content string'); - fs.truncateSync(path, 5); - fs.unlinkSync(path); -} - -trunc(); diff --git a/workerd-corpus-fs/unicode-paths.js b/workerd-corpus-fs/unicode-paths.js deleted file mode 100644 index 588a5c6ec..000000000 --- a/workerd-corpus-fs/unicode-paths.js +++ /dev/null @@ -1,2 +0,0 @@ -// Unicode and special character paths -function unicode() { let path = '/tmp/🚀test.txt'; fs.writeFileSync(path, 'emoji path'); fs.readFileSync(path); console.log(path); fs.unlinkSync(path); } unicode(); \ No newline at end of file diff --git a/workerd-corpus-fs/vector-io.js b/workerd-corpus-fs/vector-io.js deleted file mode 100644 index 83b0503a8..000000000 --- a/workerd-corpus-fs/vector-io.js +++ /dev/null @@ -1,11 +0,0 @@ -function vecStuff() { - // Vector I/O operations (readv/writev) - let fd = fs.openSync('/tmp/vector.txt', 'w+'); - fs.writevSync(fd, [Buffer.from('part1'), Buffer.from('part2')]); - let buf1 = Buffer.alloc(5); - let buf2 = Buffer.alloc(5); - fs.readvSync(fd, [buf1, buf2], 0); - fs.closeSync(fd); - fs.unlinkSync('/tmp/vector.txt'); -} -vecStuff(); \ No newline at end of file From b40e2c6f2fd8b319941c62ca1624d2a07942f276 Mon Sep 17 00:00:00 2001 From: mschwarzl Date: Tue, 9 Sep 2025 19:16:26 +0200 Subject: [PATCH 08/10] Update README.md --- Targets/workerd/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Targets/workerd/README.md b/Targets/workerd/README.md index ab0bb858d..2d6eca948 100644 --- a/Targets/workerd/README.md +++ b/Targets/workerd/README.md @@ -7,5 +7,5 @@ To build workerd for fuzzing: 2. Run the fuzzbuild.sh script in the workerd root directory to build workerd with the fuzzili configuration 3. Test if REPRL works: `swift run REPRLRun fuzzilli --experimental` -4. Run Fuzzilli: +4. Run Fuzzilli with a workerd config (See samples/reprl): `swift run -c release FuzzilliCli --inspect=all --profile=workerd --additionalArguments=,--experimental` From 946c2dc87b089a67a99ac8084e32098bef6e2ac2 Mon Sep 17 00:00:00 2001 From: mschwarzl Date: Sun, 14 Sep 2025 13:14:28 +0200 Subject: [PATCH 09/10] Update WorkerdProfile.swift --- Sources/FuzzilliCli/Profiles/WorkerdProfile.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift index 1c45652b5..d3fff4098 100644 --- a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift +++ b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift @@ -17,8 +17,7 @@ import Fuzzilli let workerdProfile = Profile( processArgs: { randomize in ["fuzzilli"] }, - - processEnv: [:], + processEnv: ["ASAN_OPTIONS" : "abort_on_error=1:symbolize=false", "UBSAN_OPTIONS" : "abort_on_error=1:symbolize=false"], maxExecsBeforeRespawn: 1000, From b556961fb7ae621e65f2c9d70288b9acf019d5de Mon Sep 17 00:00:00 2001 From: mschwarzl Date: Sun, 14 Sep 2025 13:16:27 +0200 Subject: [PATCH 10/10] added ASAN crasheds --- Sources/FuzzilliCli/Profiles/WorkerdProfile.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift index d3fff4098..4664826a4 100644 --- a/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift +++ b/Sources/FuzzilliCli/Profiles/WorkerdProfile.swift @@ -37,8 +37,13 @@ let workerdProfile = Profile( // Check that common crash types are detected. ("fuzzilli('FUZZILLI_CRASH', 0)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 1)", .shouldCrash), ("fuzzilli('FUZZILLI_CRASH', 2)", .shouldCrash), ("fuzzilli('FUZZILLI_CRASH', 3)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 4)", .shouldCrash), + // doesn't crash in workerd + //("fuzzilli('FUZZILLI_CRASH', 5)", .shouldCrash), + ("fuzzilli('FUZZILLI_CRASH', 6)", .shouldCrash), ], additionalCodeGenerators: [],