Skip to content

feat(cli): add test for readSocketPath #4284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ jobs:
- name: Install helm
uses: azure/[email protected]

- name: Fetch dependencies from cache
id: cache-yarn
uses: actions/cache@v2
with:
path: "**/node_modules"
key: yarn-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-build-
# NOTE@jsjoeio
# disabling this until we can audit the build process
# and the usefulness of this step
# See: https://github.com/cdr/code-server/issues/4287
# - name: Fetch dependencies from cache
# id: cache-yarn
# uses: actions/cache@v2
# with:
# path: "**/node_modules"
# key: yarn-build-${{ hashFiles('**/yarn.lock') }}
# restore-keys: |
# yarn-build-

- name: Install dependencies
# if: steps.cache-yarn.outputs.cache-hit != 'true'
Expand Down
40 changes: 26 additions & 14 deletions src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { promises as fs } from "fs"
import yaml from "js-yaml"
import * as os from "os"
import * as path from "path"
import { canConnect, generateCertificate, generatePassword, humanPath, paths } from "./util"
import { canConnect, generateCertificate, generatePassword, humanPath, paths, isNodeJSErrnoException } from "./util"

const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")

export enum Feature {
// No current experimental features!
Expand Down Expand Up @@ -657,6 +659,27 @@ function bindAddrFromAllSources(...argsConfig: Args[]): Addr {
return addr
}

/**
* Reads the socketPath based on path passed in.
*
* The one usually passed in is the DEFAULT_SOCKET_PATH.
*
* If it can't read the path, it throws an error and returns undefined.
*/
export async function readSocketPath(path: string): Promise<string | undefined> {
try {
return await fs.readFile(path, "utf8")
} catch (error) {
// If it doesn't exist, we don't care.
// But if it fails for some reason, we should throw.
// We want to surface that to the user.
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
throw error
}
}
return undefined
}

/**
* Determine if it looks like the user is trying to open a file or folder in an
* existing instance. The arguments here should be the arguments the user
Expand All @@ -668,32 +691,21 @@ export const shouldOpenInExistingInstance = async (args: Args): Promise<string |
return process.env.VSCODE_IPC_HOOK_CLI
}

const readSocketPath = async (): Promise<string | undefined> => {
try {
return await fs.readFile(path.join(os.tmpdir(), "vscode-ipc"), "utf8")
} catch (error: any) {
if (error.code !== "ENOENT") {
throw error
}
}
return undefined
}

// If these flags are set then assume the user is trying to open in an
// existing instance since these flags have no effect otherwise.
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
return args[cur as keyof Args] ? prev + 1 : prev
}, 0)
if (openInFlagCount > 0) {
return readSocketPath()
return readSocketPath(DEFAULT_SOCKET_PATH)
}

// It's possible the user is trying to spawn another instance of code-server.
// Check if any unrelated flags are set (check against one because `_` always
// exists), that a file or directory was passed, and that the socket is
// active.
if (Object.keys(args).length === 1 && args._.length > 0) {
const socketPath = await readSocketPath()
const socketPath = await readSocketPath(DEFAULT_SOCKET_PATH)
if (socketPath && (await canConnect(socketPath))) {
return socketPath
}
Expand Down
2 changes: 1 addition & 1 deletion src/node/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ export function escapeHtml(unsafe: string): string {
* it has a .code property.
*/
export function isNodeJSErrnoException(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error && (error as NodeJS.ErrnoException).code !== undefined
return error !== undefined && (error as NodeJS.ErrnoException).code !== undefined
}

// TODO: Replace with proper templating system.
Expand Down
44 changes: 41 additions & 3 deletions test/unit/node/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import {
setDefaults,
shouldOpenInExistingInstance,
splitOnFirstEquals,
readSocketPath,
} from "../../../src/node/cli"
import { tmpdir } from "../../../src/node/constants"
import { shouldSpawnCliProcess } from "../../../src/node/main"
import { generatePassword, paths } from "../../../src/node/util"
import { useEnv } from "../../utils/helpers"
import { useEnv, tmpdir } from "../../utils/helpers"

type Mutable<T> = {
-readonly [P in keyof T]: T[P]
Expand Down Expand Up @@ -390,10 +390,11 @@ describe("parser", () => {

describe("cli", () => {
let args: Mutable<Args> = { _: [] }
const testDir = path.join(tmpdir, "tests/cli")
let testDir: string
const vscodeIpcPath = path.join(os.tmpdir(), "vscode-ipc")

beforeAll(async () => {
testDir = await tmpdir("cli")
await fs.rmdir(testDir, { recursive: true })
await fs.mkdir(testDir, { recursive: true })
})
Expand Down Expand Up @@ -667,3 +668,40 @@ password: ${password}
cert: false`)
})
})

describe("readSocketPath", () => {
const fileContents = "readSocketPath file contents"
let tmpDirPath: string
let tmpFilePath: string

beforeEach(async () => {
tmpDirPath = await tmpdir("readSocketPath")
tmpFilePath = path.join(tmpDirPath, "readSocketPath.txt")
await fs.writeFile(tmpFilePath, fileContents)
})

afterEach(async () => {
await fs.rmdir(tmpDirPath, { recursive: true })
})

it("should throw an error if it can't read the file", async () => {
// TODO@jsjoeio - implement
// Test it on a directory.... ESDIR
// TODO@jsjoeio - implement
expect(() => readSocketPath(tmpDirPath)).rejects.toThrow("EISDIR")
})
it("should return undefined if it can't read the file", async () => {
// TODO@jsjoeio - implement
const socketPath = await readSocketPath(path.join(tmpDirPath, "not-a-file"))
expect(socketPath).toBeUndefined()
})
it("should return the file contents", async () => {
const contents = await readSocketPath(tmpFilePath)
expect(contents).toBe(fileContents)
})
it("should return the same file contents for two different calls", async () => {
const contents1 = await readSocketPath(tmpFilePath)
const contents2 = await readSocketPath(tmpFilePath)
expect(contents2).toBe(contents1)
})
})