diff --git a/node/README.md b/node/README.md index d0d638d059e5b..8d9f4ff40fc7e 100644 --- a/node/README.md +++ b/node/README.md @@ -23,7 +23,7 @@ deno standard library as it's a compatiblity module. - [ ] https - [x] module - [ ] net -- [ ] os +- [x] os _partly_ - [x] path - [ ] perf_hooks - [x] process _partly_ diff --git a/node/module.ts b/node/module.ts index 599ae23258ab3..f27ef25f2d2c4 100644 --- a/node/module.ts +++ b/node/module.ts @@ -25,6 +25,7 @@ import * as nodeFS from "./fs.ts"; import * as nodeUtil from "./util.ts"; import * as nodePath from "./path.ts"; import * as nodeTimers from "./timers.ts"; +import * as nodeOs from "./os.ts"; import * as path from "../path/mod.ts"; import { assert } from "../testing/asserts.ts"; @@ -582,6 +583,7 @@ nativeModulePolyfill.set("fs", createNativeModule("fs", nodeFS)); nativeModulePolyfill.set("util", createNativeModule("util", nodeUtil)); nativeModulePolyfill.set("path", createNativeModule("path", nodePath)); nativeModulePolyfill.set("timers", createNativeModule("timers", nodeTimers)); +nativeModulePolyfill.set("os", createNativeModule("os", nodeOs)); function loadNativeModule( _filename: string, request: string diff --git a/node/module_test.ts b/node/module_test.ts index 91744d94d522e..a869179ddfbe7 100644 --- a/node/module_test.ts +++ b/node/module_test.ts @@ -42,3 +42,9 @@ test(function requireIndexJS() { const { isIndex } = require_("./tests/cjs"); assert(isIndex); }); + +test(function requireNodeOs() { + const os = require_("os"); + assert(os.arch); + assert(typeof os.arch() == "string"); +}); diff --git a/node/os.ts b/node/os.ts new file mode 100644 index 0000000000000..ee64bfcebd905 --- /dev/null +++ b/node/os.ts @@ -0,0 +1,221 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. +import { notImplemented } from "./_utils.ts"; + +const SEE_GITHUB_ISSUE = "See https://github.com/denoland/deno/issues/3802"; + +interface CPUTimes { + /** The number of milliseconds the CPU has spent in user mode */ + user: number; + + /** The number of milliseconds the CPU has spent in nice mode */ + nice: number; + + /** The number of milliseconds the CPU has spent in sys mode */ + sys: number; + + /** The number of milliseconds the CPU has spent in idle mode */ + idle: number; + + /** The number of milliseconds the CPU has spent in irq mode */ + irq: number; +} + +interface CPUCoreInfo { + model: string; + + /** in MHz */ + speed: number; + + times: CPUTimes; +} + +interface NetworkAddress { + /** The assigned IPv4 or IPv6 address */ + address: string; + + /** The IPv4 or IPv6 network mask */ + netmask: string; + + family: "IPv4" | "IPv6"; + + /** The MAC address of the network interface */ + mac: string; + + /** true if the network interface is a loopback or similar interface that is not remotely accessible; otherwise false */ + internal: boolean; + + /** The numeric IPv6 scope ID (only specified when family is IPv6) */ + scopeid?: number; + + /** The assigned IPv4 or IPv6 address with the routing prefix in CIDR notation. If the netmask is invalid, this property is set to null. */ + cidr: string; +} + +interface NetworkInterfaces { + [key: string]: NetworkAddress[]; +} + +export interface UserInfoOptions { + encoding: string; +} + +interface UserInfo { + username: string; + uid: number; + gid: number; + shell: string; + homedir: string; +} + +/** Returns the operating system CPU architecture for which the Deno binary was compiled */ +export function arch(): string { + return Deno.build.arch; +} + +/** Not yet implemented */ +export function cpus(): CPUCoreInfo[] { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function endianness(): "BE" | "LE" { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function freemem(): number { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function getPriority(pid = 0): number { + validateInt32(pid, "pid"); + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Returns the string path of the current user's home directory. */ +export function homedir(): string { + return Deno.dir("home"); +} + +/** Returns the host name of the operating system as a string. */ +export function hostname(): string { + return Deno.hostname(); +} + +/** Not yet implemented */ +export function loadavg(): number[] { + if (Deno.build.os == "win") { + return [0, 0, 0]; + } + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function networkInterfaces(): NetworkInterfaces { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function platform(): string { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function release(): string { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function setPriority(pid: number, priority?: number): void { + /* The node API has the 'pid' as the first parameter and as optional. + This makes for a problematic implementation in Typescript. */ + if (priority === undefined) { + priority = pid; + pid = 0; + } + validateInt32(pid, "pid"); + validateInt32(priority, "priority", -20, 19); + + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function tmpdir(): string { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function totalmem(): number { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function type(): string { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function uptime(): number { + notImplemented(SEE_GITHUB_ISSUE); +} + +/** Not yet implemented */ +export function userInfo( + /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ + options: UserInfoOptions = { encoding: "utf-8" } +): UserInfo { + notImplemented(SEE_GITHUB_ISSUE); +} + +export const constants = { + // UV_UDP_REUSEADDR: 4, //see https://nodejs.org/docs/latest-v12.x/api/os.html#os_libuv_constants + dlopen: { + // see https://nodejs.org/docs/latest-v12.x/api/os.html#os_dlopen_constants + }, + errno: { + // see https://nodejs.org/docs/latest-v12.x/api/os.html#os_error_constants + }, + signals: Deno.Signal, + priority: { + // see https://nodejs.org/docs/latest-v12.x/api/os.html#os_priority_constants + } +}; + +export const EOL = Deno.build.os == "win" ? "\r\n" : "\n"; + +const validateInt32 = ( + value: number, + name: string, + min = -2147483648, + max = 2147483647 +): void => { + // The defaults for min and max correspond to the limits of 32-bit integers. + if (!Number.isInteger(value)) { + throw new Error(`${name} must be 'an integer' but was ${value}`); + } + if (value < min || value > max) { + throw new Error( + `${name} must be >= ${min} && <= ${max}. Value was ${value}` + ); + } +}; diff --git a/node/os_test.ts b/node/os_test.ts new file mode 100644 index 0000000000000..88f0113ec568f --- /dev/null +++ b/node/os_test.ts @@ -0,0 +1,275 @@ +import { test } from "../testing/mod.ts"; +import { + assert, + assertThrows, + assertEquals, + AssertionError +} from "../testing/asserts.ts"; +import * as os from "./os.ts"; + +test({ + name: "build architecture is a string", + fn() { + assertEquals(typeof os.arch(), "string"); + } +}); + +test({ + name: "home directory is a string", + fn() { + assertEquals(typeof os.homedir(), "string"); + } +}); + +test({ + name: "hostname is a string", + fn() { + assertEquals(typeof os.hostname(), "string"); + } +}); + +test({ + name: "getPriority(): PID must be a 32 bit integer", + fn() { + assertThrows( + () => { + os.getPriority(3.15); + }, + Error, + "pid must be 'an integer'" + ); + assertThrows( + () => { + os.getPriority(9999999999); + }, + Error, + "must be >= -2147483648 && <= 2147483647" + ); + } +}); + +test({ + name: "setPriority(): PID must be a 32 bit integer", + fn() { + assertThrows( + () => { + os.setPriority(3.15, 0); + }, + Error, + "pid must be 'an integer'" + ); + assertThrows( + () => { + os.setPriority(9999999999, 0); + }, + Error, + "pid must be >= -2147483648 && <= 2147483647" + ); + } +}); + +test({ + name: "setPriority(): priority must be an integer between -20 and 19", + fn() { + assertThrows( + () => { + os.setPriority(0, 3.15); + }, + Error, + "priority must be 'an integer'" + ); + assertThrows( + () => { + os.setPriority(0, -21); + }, + Error, + "priority must be >= -20 && <= 19" + ); + assertThrows( + () => { + os.setPriority(0, 20); + }, + Error, + "priority must be >= -20 && <= 19" + ); + assertThrows( + () => { + os.setPriority(0, 9999999999); + }, + Error, + "priority must be >= -20 && <= 19" + ); + } +}); + +test({ + name: + "setPriority(): if only one argument specified, then this is the priority, NOT the pid", + fn() { + assertThrows( + () => { + os.setPriority(3.15); + }, + Error, + "priority must be 'an integer'" + ); + assertThrows( + () => { + os.setPriority(-21); + }, + Error, + "priority must be >= -20 && <= 19" + ); + assertThrows( + () => { + os.setPriority(20); + }, + Error, + "priority must be >= -20 && <= 19" + ); + assertThrows( + () => { + os.setPriority(9999999999); + }, + Error, + "priority must be >= -20 && <= 19" + ); + } +}); + +test({ + name: "Signals are as expected", + fn() { + // Test a few random signals for equality + assertEquals(os.constants.signals.SIGKILL, Deno.Signal.SIGKILL); + assertEquals(os.constants.signals.SIGCONT, Deno.Signal.SIGCONT); + assertEquals(os.constants.signals.SIGXFSZ, Deno.Signal.SIGXFSZ); + } +}); + +test({ + name: "EOL is as expected", + fn() { + assert(os.EOL == "\r\n" || os.EOL == "\n"); + } +}); + +// Method is currently implemented correctly for windows but not for any other os +test({ + name: "Load average is an array of 3 numbers", + fn() { + try { + const result = os.loadavg(); + assert(result.length == 3); + assertEquals(typeof result[0], "number"); + assertEquals(typeof result[1], "number"); + assertEquals(typeof result[2], "number"); + } catch (error) { + if (!(Object.getPrototypeOf(error) === Error.prototype)) { + const errMsg = `Unexpected error class: ${error.name}`; + throw new AssertionError(errMsg); + } else if (!error.message.includes("Not implemented")) { + throw new AssertionError( + "Expected this error to contain 'Not implemented'" + ); + } + } + } +}); + +test({ + name: "APIs not yet implemented", + fn() { + assertThrows( + () => { + os.cpus(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.endianness(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.freemem(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.getPriority(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.networkInterfaces(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.platform(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.release(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.setPriority(0); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.tmpdir(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.totalmem(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.type(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.uptime(); + }, + Error, + "Not implemented" + ); + assertThrows( + () => { + os.userInfo(); + }, + Error, + "Not implemented" + ); + } +});