chore(vscode): update to 1.53.2
These conflicts will be resolved in the following commits. We do it this way so that PR review is possible.
This commit is contained in:
22
lib/vscode/src/vs/base/parts/ipc/browser/ipc.mp.ts
Normal file
22
lib/vscode/src/vs/base/parts/ipc/browser/ipc.mp.ts
Normal file
@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCClient` on top of DOM `MessagePort`.
|
||||
*/
|
||||
export class Client extends MessagePortClient implements IDisposable {
|
||||
|
||||
/**
|
||||
* @param clientId a way to uniquely identify this client among
|
||||
* other clients. this is important for routing because every
|
||||
* client can also be a server
|
||||
*/
|
||||
constructor(port: MessagePort, clientId: string) {
|
||||
super(port, clientId);
|
||||
}
|
||||
}
|
@ -11,6 +11,11 @@ export interface Sender {
|
||||
send(channel: string, msg: unknown): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Electron `Protocol` leverages Electron style IPC communication (`ipcRenderer`, `ipcMain`)
|
||||
* for the implementation of the `IMessagePassingProtocol`. That style of API requires a channel
|
||||
* name for sending data.
|
||||
*/
|
||||
export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
constructor(private sender: Sender, readonly onMessage: Event<VSBuffer>) { }
|
||||
@ -23,7 +28,7 @@ export class Protocol implements IMessagePassingProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
disconnect(): void {
|
||||
this.sender.send('vscode:disconnect', null);
|
||||
}
|
||||
}
|
||||
|
78
lib/vscode/src/vs/base/parts/ipc/common/ipc.mp.ts
Normal file
78
lib/vscode/src/vs/base/parts/ipc/common/ipc.mp.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
/**
|
||||
* Declare minimal `MessageEvent` and `MessagePort` interfaces here
|
||||
* so that this utility can be used both from `browser` and
|
||||
* `electron-main` namespace where message ports are available.
|
||||
*/
|
||||
|
||||
export interface MessageEvent {
|
||||
|
||||
/**
|
||||
* For our use we only consider `Uint8Array` a valid data transfer
|
||||
* via message ports because our protocol implementation is buffer based.
|
||||
*/
|
||||
data: Uint8Array;
|
||||
}
|
||||
|
||||
export interface MessagePort {
|
||||
|
||||
addEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
|
||||
removeEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
|
||||
|
||||
postMessage(message: Uint8Array): void;
|
||||
|
||||
start(): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The MessagePort `Protocol` leverages MessagePort style IPC communication
|
||||
* for the implementation of the `IMessagePassingProtocol`. That style of API
|
||||
* is a simple `onmessage` / `postMessage` pattern.
|
||||
*/
|
||||
export class Protocol implements IMessagePassingProtocol {
|
||||
|
||||
readonly onMessage = Event.fromDOMEventEmitter<VSBuffer>(this.port, 'message', (e: MessageEvent) => VSBuffer.wrap(e.data));
|
||||
|
||||
constructor(private port: MessagePort) {
|
||||
|
||||
// we must call start() to ensure messages are flowing
|
||||
port.start();
|
||||
}
|
||||
|
||||
send(message: VSBuffer): void {
|
||||
this.port.postMessage(message.buffer);
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
this.port.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCClient` on top of MessagePort style IPC communication.
|
||||
*/
|
||||
export class Client extends IPCClient implements IDisposable {
|
||||
|
||||
private protocol: Protocol;
|
||||
|
||||
constructor(port: MessagePort, clientId: string) {
|
||||
const protocol = new Protocol(port);
|
||||
super(protocol, clientId);
|
||||
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.protocol.disconnect();
|
||||
}
|
||||
}
|
@ -365,8 +365,8 @@ export class Protocol extends Disposable implements IMessagePassingProtocol {
|
||||
private readonly _onMessage = new Emitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
|
||||
|
||||
private readonly _onClose = new Emitter<void>();
|
||||
readonly onClose: Event<void> = this._onClose.event;
|
||||
private readonly _onDidDispose = new Emitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
|
||||
constructor(socket: ISocket) {
|
||||
super();
|
||||
@ -380,7 +380,7 @@ export class Protocol extends Disposable implements IMessagePassingProtocol {
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._socket.onClose(() => this._onClose.fire()));
|
||||
this._register(this._socket.onClose(() => this._onDidDispose.fire()));
|
||||
}
|
||||
|
||||
drain(): Promise<void> {
|
||||
@ -406,7 +406,7 @@ export class Client<TContext = string> extends IPCClient<TContext> {
|
||||
return new Client(new Protocol(socket), id);
|
||||
}
|
||||
|
||||
get onClose(): Event<void> { return this.protocol.onClose; }
|
||||
get onDidDispose(): Event<void> { return this.protocol.onDidDispose; }
|
||||
|
||||
constructor(private protocol: Protocol | PersistentProtocol, id: TContext, ipcLogger: IIPCLogger | null = null) {
|
||||
super(protocol, id, ipcLogger);
|
||||
@ -621,8 +621,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
private readonly _onMessage = new BufferedEmitter<VSBuffer>();
|
||||
readonly onMessage: Event<VSBuffer> = this._onMessage.event;
|
||||
|
||||
private readonly _onClose = new BufferedEmitter<void>();
|
||||
readonly onClose: Event<void> = this._onClose.event;
|
||||
private readonly _onDidDispose = new BufferedEmitter<void>();
|
||||
readonly onDidDispose: Event<void> = this._onDidDispose.event;
|
||||
|
||||
private readonly _onSocketClose = new BufferedEmitter<void>();
|
||||
readonly onSocketClose: Event<void> = this._onSocketClose.event;
|
||||
@ -747,6 +747,10 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
return this._socket;
|
||||
}
|
||||
|
||||
public getMillisSinceLastIncomingData(): number {
|
||||
return Date.now() - this._socketReader.lastReadTime;
|
||||
}
|
||||
|
||||
public beginAcceptReconnection(socket: ISocket, initialDataChunk: VSBuffer | null): void {
|
||||
this._isReconnecting = true;
|
||||
|
||||
@ -783,7 +787,7 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
}
|
||||
|
||||
public acceptDisconnect(): void {
|
||||
this._onClose.fire();
|
||||
this._onDidDispose.fire();
|
||||
}
|
||||
|
||||
private _receiveMessage(msg: ProtocolMessage): void {
|
||||
@ -820,7 +824,7 @@ export class PersistentProtocol implements IMessagePassingProtocol {
|
||||
} else if (msg.type === ProtocolMessageType.Control) {
|
||||
this._onControlMessage.fire(msg.data);
|
||||
} else if (msg.type === ProtocolMessageType.Disconnect) {
|
||||
this._onClose.fire();
|
||||
this._onDidDispose.fire();
|
||||
} else if (msg.type === ProtocolMessageType.ReplayRequest) {
|
||||
// Send again all unacknowledged messages
|
||||
const toSend = this._outgoingUnackMsg.toArray();
|
||||
|
@ -505,6 +505,7 @@ export interface IIPCLogger {
|
||||
|
||||
export class ChannelClient implements IChannelClient, IDisposable {
|
||||
|
||||
private isDisposed: boolean = false;
|
||||
private state: State = State.Uninitialized;
|
||||
private activeRequests = new Set<IDisposable>();
|
||||
private handlers = new Map<number, IHandler>();
|
||||
@ -525,9 +526,15 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||
|
||||
return {
|
||||
call(command: string, arg?: any, cancellationToken?: CancellationToken) {
|
||||
if (that.isDisposed) {
|
||||
return Promise.reject(errors.canceled());
|
||||
}
|
||||
return that.requestPromise(channelName, command, arg, cancellationToken);
|
||||
},
|
||||
listen(event: string, arg: any) {
|
||||
if (that.isDisposed) {
|
||||
return Promise.reject(errors.canceled());
|
||||
}
|
||||
return that.requestEvent(channelName, event, arg);
|
||||
}
|
||||
} as T;
|
||||
@ -725,6 +732,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.isDisposed = true;
|
||||
if (this.protocolListener) {
|
||||
this.protocolListener.dispose();
|
||||
this.protocolListener = null;
|
||||
|
@ -3,10 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IPCServer, ClientConnectionEvent } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { ipcMain, WebContents } from 'electron';
|
||||
import { Protocol as ElectronProtocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
|
||||
@ -18,9 +18,13 @@ interface IIPCEvent {
|
||||
function createScopedOnMessageEvent(senderId: number, eventName: string): Event<VSBuffer | null> {
|
||||
const onMessage = Event.fromNodeEventEmitter<IIPCEvent>(ipcMain, eventName, (event, message) => ({ event, message }));
|
||||
const onMessageFromSender = Event.filter(onMessage, ({ event }) => event.sender.id === senderId);
|
||||
|
||||
return Event.map(onMessageFromSender, ({ message }) => message ? VSBuffer.wrap(message) : message);
|
||||
}
|
||||
|
||||
/**
|
||||
* An implemention of `IPCServer` on top of Electron `ipcMain` API.
|
||||
*/
|
||||
export class Server extends IPCServer {
|
||||
|
||||
private static readonly Clients = new Map<number, IDisposable>();
|
||||
@ -41,7 +45,7 @@ export class Server extends IPCServer {
|
||||
|
||||
const onMessage = createScopedOnMessageEvent(id, 'vscode:message') as Event<VSBuffer>;
|
||||
const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'vscode:disconnect')), onDidClientReconnect.event);
|
||||
const protocol = new Protocol(webContents, onMessage);
|
||||
const protocol = new ElectronProtocol(webContents, onMessage);
|
||||
|
||||
return { protocol, onDidClientDisconnect };
|
||||
});
|
57
lib/vscode/src/vs/base/parts/ipc/electron-main/ipc.mp.ts
Normal file
57
lib/vscode/src/vs/base/parts/ipc/electron-main/ipc.mp.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { BrowserWindow, ipcMain, IpcMainEvent, MessagePortMain } from 'electron';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCClient` on top of Electron `MessagePortMain`.
|
||||
*/
|
||||
export class Client extends MessagePortClient implements IDisposable {
|
||||
|
||||
/**
|
||||
* @param clientId a way to uniquely identify this client among
|
||||
* other clients. this is important for routing because every
|
||||
* client can also be a server
|
||||
*/
|
||||
constructor(port: MessagePortMain, clientId: string) {
|
||||
super({
|
||||
addEventListener: (type, listener) => port.addListener(type, listener),
|
||||
removeEventListener: (type, listener) => port.removeListener(type, listener),
|
||||
postMessage: message => port.postMessage(message),
|
||||
start: () => port.start(),
|
||||
close: () => port.close()
|
||||
}, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method opens a message channel connection
|
||||
* in the target window. The target window needs
|
||||
* to use the `Server` from `electron-sandbox/ipc.mp`.
|
||||
*/
|
||||
export async function connect(window: BrowserWindow): Promise<MessagePortMain> {
|
||||
|
||||
// Assert healthy window to talk to
|
||||
if (window.isDestroyed() || window.webContents.isDestroyed()) {
|
||||
throw new Error('ipc.mp#connect: Cannot talk to window because it is closed or destroyed');
|
||||
}
|
||||
|
||||
// Ask to create message channel inside the window
|
||||
// and send over a UUID to correlate the response
|
||||
const nonce = generateUuid();
|
||||
window.webContents.send('vscode:createMessageChannel', nonce);
|
||||
|
||||
// Wait until the window has returned the `MessagePort`
|
||||
// We need to filter by the `nonce` to ensure we listen
|
||||
// to the right response.
|
||||
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePortMain }>(ipcMain, 'vscode:createMessageChannelResult', (e: IpcMainEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
|
||||
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
|
||||
|
||||
return port;
|
||||
}
|
@ -5,28 +5,34 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IPCClient } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { Protocol as ElectronProtocol } from 'vs/base/parts/ipc/common/ipc.electron';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
/**
|
||||
* An implemention of `IPCClient` on top of Electron `ipcRenderer` IPC communication
|
||||
* provided from sandbox globals (via preload script).
|
||||
*/
|
||||
export class Client extends IPCClient implements IDisposable {
|
||||
|
||||
private protocol: Protocol;
|
||||
private protocol: ElectronProtocol;
|
||||
|
||||
private static createProtocol(): Protocol {
|
||||
private static createProtocol(): ElectronProtocol {
|
||||
const onMessage = Event.fromNodeEventEmitter<VSBuffer>(ipcRenderer, 'vscode:message', (_, message) => VSBuffer.wrap(message));
|
||||
ipcRenderer.send('vscode:hello');
|
||||
return new Protocol(ipcRenderer, onMessage);
|
||||
|
||||
return new ElectronProtocol(ipcRenderer, onMessage);
|
||||
}
|
||||
|
||||
constructor(id: string) {
|
||||
const protocol = Client.createProtocol();
|
||||
super(protocol, id);
|
||||
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.protocol.dispose();
|
||||
this.protocol.disconnect();
|
||||
}
|
||||
}
|
51
lib/vscode/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts
Normal file
51
lib/vscode/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts
Normal file
@ -0,0 +1,51 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Protocol as MessagePortProtocol } from 'vs/base/parts/ipc/common/ipc.mp';
|
||||
|
||||
/**
|
||||
* An implementation of a `IPCServer` on top of MessagePort style IPC communication.
|
||||
* The clients register themselves via Electron IPC transfer.
|
||||
*/
|
||||
export class Server extends IPCServer {
|
||||
|
||||
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
|
||||
|
||||
// Clients connect via `vscode:createMessageChannel` to get a
|
||||
// `MessagePort` that is ready to be used. For every connection
|
||||
// we create a pair of message ports and send it back.
|
||||
//
|
||||
// The `nonce` is included so that the main side has a chance to
|
||||
// correlate the response back to the sender.
|
||||
const onCreateMessageChannel = Event.fromNodeEventEmitter<string>(ipcRenderer, 'vscode:createMessageChannel', (_, nonce: string) => nonce);
|
||||
|
||||
return Event.map(onCreateMessageChannel, nonce => {
|
||||
|
||||
// Create a new pair of ports and protocol for this connection
|
||||
const { port1: incomingPort, port2: outgoingPort } = new MessageChannel();
|
||||
const protocol = new MessagePortProtocol(incomingPort);
|
||||
|
||||
const result: ClientConnectionEvent = {
|
||||
protocol,
|
||||
// Not part of the standard spec, but in Electron we get a `close` event
|
||||
// when the other side closes. We can use this to detect disconnects
|
||||
// (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close)
|
||||
onDidClientDisconnect: Event.fromDOMEventEmitter(incomingPort, 'close')
|
||||
};
|
||||
|
||||
// Send one port back to the requestor
|
||||
ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]);
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super(Server.getOnDidClientConnect());
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
|
||||
import { createHash } from 'crypto';
|
||||
import { Socket, Server as NetServer, createConnection, createServer } from 'net';
|
||||
import * as zlib from 'zlib';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { join } from 'vs/base/common/path';
|
||||
@ -120,24 +121,130 @@ const enum ReadState {
|
||||
export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
|
||||
public readonly socket: NodeSocket;
|
||||
public readonly permessageDeflate: boolean;
|
||||
private _totalIncomingWireBytes: number;
|
||||
private _totalIncomingDataBytes: number;
|
||||
private _totalOutgoingWireBytes: number;
|
||||
private _totalOutgoingDataBytes: number;
|
||||
private readonly _zlibInflate: zlib.InflateRaw | null;
|
||||
private readonly _zlibDeflate: zlib.DeflateRaw | null;
|
||||
private _zlibDeflateFlushWaitingCount: number;
|
||||
private readonly _onDidZlibFlush = this._register(new Emitter<void>());
|
||||
private readonly _recordInflateBytes: boolean;
|
||||
private readonly _recordedInflateBytes: Buffer[] = [];
|
||||
private readonly _pendingInflateData: Buffer[] = [];
|
||||
private readonly _pendingDeflateData: Buffer[] = [];
|
||||
private readonly _incomingData: ChunkStream;
|
||||
private readonly _onData = this._register(new Emitter<VSBuffer>());
|
||||
private readonly _onClose = this._register(new Emitter<void>());
|
||||
private _isEnded: boolean = false;
|
||||
|
||||
private readonly _state = {
|
||||
state: ReadState.PeekHeader,
|
||||
readLen: Constants.MinHeaderByteSize,
|
||||
fin: 0,
|
||||
mask: 0
|
||||
};
|
||||
|
||||
constructor(socket: NodeSocket) {
|
||||
public get totalIncomingWireBytes(): number {
|
||||
return this._totalIncomingWireBytes;
|
||||
}
|
||||
|
||||
public get totalIncomingDataBytes(): number {
|
||||
return this._totalIncomingDataBytes;
|
||||
}
|
||||
|
||||
public get totalOutgoingWireBytes(): number {
|
||||
return this._totalOutgoingWireBytes;
|
||||
}
|
||||
|
||||
public get totalOutgoingDataBytes(): number {
|
||||
return this._totalOutgoingDataBytes;
|
||||
}
|
||||
|
||||
public get recordedInflateBytes(): VSBuffer {
|
||||
if (this._recordInflateBytes) {
|
||||
return VSBuffer.wrap(Buffer.concat(this._recordedInflateBytes));
|
||||
}
|
||||
return VSBuffer.alloc(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a socket which can communicate using WebSocket frames.
|
||||
*
|
||||
* **NOTE**: When using the permessage-deflate WebSocket extension, if parts of inflating was done
|
||||
* in a different zlib instance, we need to pass all those bytes into zlib, otherwise the inflate
|
||||
* might hit an inflated portion referencing a distance too far back.
|
||||
*
|
||||
* @param socket The underlying socket
|
||||
* @param permessageDeflate Use the permessage-deflate WebSocket extension
|
||||
* @param inflateBytes "Seed" zlib inflate with these bytes.
|
||||
* @param recordInflateBytes Record all bytes sent to inflate
|
||||
*/
|
||||
constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean) {
|
||||
super();
|
||||
this.socket = socket;
|
||||
this._totalIncomingWireBytes = 0;
|
||||
this._totalIncomingDataBytes = 0;
|
||||
this._totalOutgoingWireBytes = 0;
|
||||
this._totalOutgoingDataBytes = 0;
|
||||
this.permessageDeflate = permessageDeflate;
|
||||
this._recordInflateBytes = recordInflateBytes;
|
||||
if (permessageDeflate) {
|
||||
// See https://tools.ietf.org/html/rfc7692#page-16
|
||||
// To simplify our logic, we don't negociate the window size
|
||||
// and simply dedicate (2^15) / 32kb per web socket
|
||||
this._zlibInflate = zlib.createInflateRaw({
|
||||
windowBits: 15
|
||||
});
|
||||
this._zlibInflate.on('error', (err) => {
|
||||
// zlib errors are fatal, since we have no idea how to recover
|
||||
console.error(err);
|
||||
onUnexpectedError(err);
|
||||
this._onClose.fire();
|
||||
});
|
||||
this._zlibInflate.on('data', (data: Buffer) => {
|
||||
this._pendingInflateData.push(data);
|
||||
});
|
||||
if (inflateBytes) {
|
||||
this._zlibInflate.write(inflateBytes.buffer);
|
||||
this._zlibInflate.flush(() => {
|
||||
this._pendingInflateData.length = 0;
|
||||
});
|
||||
}
|
||||
|
||||
this._zlibDeflate = zlib.createDeflateRaw({
|
||||
windowBits: 15
|
||||
});
|
||||
this._zlibDeflate.on('error', (err) => {
|
||||
// zlib errors are fatal, since we have no idea how to recover
|
||||
console.error(err);
|
||||
onUnexpectedError(err);
|
||||
this._onClose.fire();
|
||||
});
|
||||
this._zlibDeflate.on('data', (data: Buffer) => {
|
||||
this._pendingDeflateData.push(data);
|
||||
});
|
||||
} else {
|
||||
this._zlibInflate = null;
|
||||
this._zlibDeflate = null;
|
||||
}
|
||||
this._zlibDeflateFlushWaitingCount = 0;
|
||||
this._incomingData = new ChunkStream();
|
||||
this._register(this.socket.onData(data => this._acceptChunk(data)));
|
||||
this._register(this.socket.onClose(() => this._onClose.fire()));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.socket.dispose();
|
||||
if (this._zlibDeflateFlushWaitingCount > 0) {
|
||||
// Wait for any outstanding writes to finish before disposing
|
||||
this._register(this._onDidZlibFlush.event(() => {
|
||||
this.dispose();
|
||||
}));
|
||||
} else {
|
||||
this.socket.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public onData(listener: (e: VSBuffer) => void): IDisposable {
|
||||
@ -145,7 +252,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
}
|
||||
|
||||
public onClose(listener: () => void): IDisposable {
|
||||
return this.socket.onClose(listener);
|
||||
return this._onClose.event(listener);
|
||||
}
|
||||
|
||||
public onEnd(listener: () => void): IDisposable {
|
||||
@ -153,6 +260,36 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
}
|
||||
|
||||
public write(buffer: VSBuffer): void {
|
||||
this._totalOutgoingDataBytes += buffer.byteLength;
|
||||
|
||||
if (this._zlibDeflate) {
|
||||
this._zlibDeflate.write(<Buffer>buffer.buffer);
|
||||
|
||||
this._zlibDeflateFlushWaitingCount++;
|
||||
// See https://zlib.net/manual.html#Constants
|
||||
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => {
|
||||
this._zlibDeflateFlushWaitingCount--;
|
||||
let data = Buffer.concat(this._pendingDeflateData);
|
||||
this._pendingDeflateData.length = 0;
|
||||
|
||||
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
|
||||
data = data.slice(0, data.length - 4);
|
||||
|
||||
if (!this._isEnded) {
|
||||
// Avoid ERR_STREAM_WRITE_AFTER_END
|
||||
this._write(VSBuffer.wrap(data), true);
|
||||
}
|
||||
|
||||
if (this._zlibDeflateFlushWaitingCount === 0) {
|
||||
this._onDidZlibFlush.fire();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._write(buffer, false);
|
||||
}
|
||||
}
|
||||
|
||||
private _write(buffer: VSBuffer, compressed: boolean): void {
|
||||
let headerLen = Constants.MinHeaderByteSize;
|
||||
if (buffer.byteLength < 126) {
|
||||
headerLen += 0;
|
||||
@ -163,7 +300,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
}
|
||||
const header = VSBuffer.alloc(headerLen);
|
||||
|
||||
header.writeUInt8(0b10000010, 0);
|
||||
if (compressed) {
|
||||
// The RSV1 bit indicates a compressed frame
|
||||
header.writeUInt8(0b11000010, 0);
|
||||
} else {
|
||||
header.writeUInt8(0b10000010, 0);
|
||||
}
|
||||
if (buffer.byteLength < 126) {
|
||||
header.writeUInt8(buffer.byteLength, 1);
|
||||
} else if (buffer.byteLength < 2 ** 16) {
|
||||
@ -184,10 +326,12 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
|
||||
}
|
||||
|
||||
this._totalOutgoingWireBytes += header.byteLength + buffer.byteLength;
|
||||
this.socket.write(VSBuffer.concat([header, buffer]));
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
this._isEnded = true;
|
||||
this.socket.end();
|
||||
}
|
||||
|
||||
@ -195,6 +339,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
if (data.byteLength === 0) {
|
||||
return;
|
||||
}
|
||||
this._totalIncomingWireBytes += data.byteLength;
|
||||
|
||||
this._incomingData.acceptChunk(data);
|
||||
|
||||
@ -203,14 +348,15 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
if (this._state.state === ReadState.PeekHeader) {
|
||||
// peek to see if we can read the entire header
|
||||
const peekHeader = this._incomingData.peek(this._state.readLen);
|
||||
// const firstByte = peekHeader.readUInt8(0);
|
||||
// const finBit = (firstByte & 0b10000000) >>> 7;
|
||||
const firstByte = peekHeader.readUInt8(0);
|
||||
const finBit = (firstByte & 0b10000000) >>> 7;
|
||||
const secondByte = peekHeader.readUInt8(1);
|
||||
const hasMask = (secondByte & 0b10000000) >>> 7;
|
||||
const len = (secondByte & 0b01111111);
|
||||
|
||||
this._state.state = ReadState.ReadHeader;
|
||||
this._state.readLen = Constants.MinHeaderByteSize + (hasMask ? 4 : 0) + (len === 126 ? 2 : 0) + (len === 127 ? 8 : 0);
|
||||
this._state.fin = finBit;
|
||||
this._state.mask = 0;
|
||||
|
||||
} else if (this._state.state === ReadState.ReadHeader) {
|
||||
@ -263,13 +409,37 @@ export class WebSocketNodeSocket extends Disposable implements ISocket {
|
||||
this._state.readLen = Constants.MinHeaderByteSize;
|
||||
this._state.mask = 0;
|
||||
|
||||
this._onData.fire(body);
|
||||
if (this._zlibInflate) {
|
||||
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
|
||||
if (this._recordInflateBytes) {
|
||||
this._recordedInflateBytes.push(Buffer.from(<Buffer>body.buffer));
|
||||
}
|
||||
this._zlibInflate.write(<Buffer>body.buffer);
|
||||
if (this._state.fin) {
|
||||
if (this._recordInflateBytes) {
|
||||
this._recordedInflateBytes.push(Buffer.from([0x00, 0x00, 0xff, 0xff]));
|
||||
}
|
||||
this._zlibInflate.write(Buffer.from([0x00, 0x00, 0xff, 0xff]));
|
||||
}
|
||||
this._zlibInflate.flush(() => {
|
||||
const data = Buffer.concat(this._pendingInflateData);
|
||||
this._pendingInflateData.length = 0;
|
||||
this._totalIncomingDataBytes += data.length;
|
||||
this._onData.fire(VSBuffer.wrap(data));
|
||||
});
|
||||
} else {
|
||||
this._totalIncomingDataBytes += body.byteLength;
|
||||
this._onData.fire(body);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public drain(): Promise<void> {
|
||||
return this.socket.drain();
|
||||
public async drain(): Promise<void> {
|
||||
if (this._zlibDeflateFlushWaitingCount > 0) {
|
||||
await Event.toPromise(this._onDidZlibFlush.event);
|
||||
}
|
||||
await this.socket.drain();
|
||||
}
|
||||
}
|
||||
|
||||
|
58
lib/vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts
Normal file
58
lib/vscode/src/vs/base/parts/ipc/test/browser/ipc.mp.test.ts
Normal file
@ -0,0 +1,58 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp';
|
||||
|
||||
suite('IPC, MessagePorts', () => {
|
||||
|
||||
test('message passing', async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
const client1 = new MessagePortClient(port1, 'client1');
|
||||
const client2 = new MessagePortClient(port2, 'client2');
|
||||
|
||||
client1.registerChannel('client1', {
|
||||
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
|
||||
switch (command) {
|
||||
case 'testMethodClient1': return Promise.resolve('success1');
|
||||
default: return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
},
|
||||
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
default: throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client2.registerChannel('client2', {
|
||||
call(_: unknown, command: string, arg: any, cancellationToken: CancellationToken): Promise<any> {
|
||||
switch (command) {
|
||||
case 'testMethodClient2': return Promise.resolve('success2');
|
||||
default: return Promise.reject(new Error('not implemented'));
|
||||
}
|
||||
},
|
||||
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
default: throw new Error('not implemented');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const channelClient1 = client2.getChannel('client1');
|
||||
assert.strictEqual(await channelClient1.call('testMethodClient1'), 'success1');
|
||||
|
||||
const channelClient2 = client1.getChannel('client2');
|
||||
assert.strictEqual(await channelClient2.call('testMethodClient2'), 'success2');
|
||||
|
||||
client1.dispose();
|
||||
client2.dispose();
|
||||
});
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Client as MessagePortClient } from 'vs/base/parts/ipc/browser/ipc.mp';
|
||||
|
||||
suite('IPC, MessagePorts', () => {
|
||||
|
||||
test('message port close event', async () => {
|
||||
const { port1, port2 } = new MessageChannel();
|
||||
|
||||
new MessagePortClient(port1, 'client1');
|
||||
const client2 = new MessagePortClient(port2, 'client2');
|
||||
|
||||
// This test ensures that Electron's API for the close event
|
||||
// does not break because we rely on it to dispose client
|
||||
// connections from the server.
|
||||
//
|
||||
// This event is not provided by browser MessagePort API though.
|
||||
const whenClosed = new Promise<boolean>(resolve => port1.addEventListener('close', () => resolve(true)));
|
||||
|
||||
client2.dispose();
|
||||
|
||||
assert.ok(await whenClosed);
|
||||
});
|
||||
});
|
@ -11,7 +11,7 @@ import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
function createClient(): Client {
|
||||
return new Client(getPathFromAmdModule(require, 'bootstrap-fork'), {
|
||||
serverName: 'TestServer',
|
||||
env: { AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true }
|
||||
env: { VSCODE_AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true }
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -135,13 +135,13 @@ suite('IPC, Socket Protocol', () => {
|
||||
|
||||
a.send(VSBuffer.fromString('foobarfarboo'));
|
||||
const msg1 = await bMessages.waitForOne();
|
||||
assert.equal(msg1.toString(), 'foobarfarboo');
|
||||
assert.strictEqual(msg1.toString(), 'foobarfarboo');
|
||||
|
||||
const buffer = VSBuffer.alloc(1);
|
||||
buffer.writeUInt8(123, 0);
|
||||
a.send(buffer);
|
||||
const msg2 = await bMessages.waitForOne();
|
||||
assert.equal(msg2.readUInt8(0), 123);
|
||||
assert.strictEqual(msg2.readUInt8(0), 123);
|
||||
});
|
||||
|
||||
|
||||
@ -160,7 +160,7 @@ suite('IPC, Socket Protocol', () => {
|
||||
|
||||
a.send(VSBuffer.fromString(JSON.stringify(data)));
|
||||
const msg = await bMessages.waitForOne();
|
||||
assert.deepEqual(JSON.parse(msg.toString()), data);
|
||||
assert.deepStrictEqual(JSON.parse(msg.toString()), data);
|
||||
});
|
||||
|
||||
});
|
||||
@ -179,49 +179,49 @@ suite('PersistentProtocol reconnection', () => {
|
||||
const bMessages = new MessageStream(b);
|
||||
|
||||
a.send(VSBuffer.fromString('a1'));
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
a.send(VSBuffer.fromString('a2'));
|
||||
assert.equal(a.unacknowledgedCount, 2);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a.unacknowledgedCount, 2);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
a.send(VSBuffer.fromString('a3'));
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
const a1 = await bMessages.waitForOne();
|
||||
assert.equal(a1.toString(), 'a1');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a1.toString(), 'a1');
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
const a2 = await bMessages.waitForOne();
|
||||
assert.equal(a2.toString(), 'a2');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a2.toString(), 'a2');
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
const a3 = await bMessages.waitForOne();
|
||||
assert.equal(a3.toString(), 'a3');
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(a3.toString(), 'a3');
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
|
||||
b.send(VSBuffer.fromString('b1'));
|
||||
assert.equal(a.unacknowledgedCount, 3);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
assert.strictEqual(a.unacknowledgedCount, 3);
|
||||
assert.strictEqual(b.unacknowledgedCount, 1);
|
||||
|
||||
const b1 = await aMessages.waitForOne();
|
||||
assert.equal(b1.toString(), 'b1');
|
||||
assert.equal(a.unacknowledgedCount, 0);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b1.toString(), 'b1');
|
||||
assert.strictEqual(a.unacknowledgedCount, 0);
|
||||
assert.strictEqual(b.unacknowledgedCount, 1);
|
||||
|
||||
a.send(VSBuffer.fromString('a4'));
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 1);
|
||||
assert.strictEqual(a.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b.unacknowledgedCount, 1);
|
||||
|
||||
const b2 = await bMessages.waitForOne();
|
||||
assert.equal(b2.toString(), 'a4');
|
||||
assert.equal(a.unacknowledgedCount, 1);
|
||||
assert.equal(b.unacknowledgedCount, 0);
|
||||
assert.strictEqual(b2.toString(), 'a4');
|
||||
assert.strictEqual(a.unacknowledgedCount, 1);
|
||||
assert.strictEqual(b.unacknowledgedCount, 0);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -121,7 +121,7 @@
|
||||
font-size: 11px;
|
||||
padding: 0 6px;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
height: 27.5px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ import { Color } from 'vs/base/common/color';
|
||||
import { registerCodicon, Codicon } from 'vs/base/common/codicons';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { renderCodicons } from 'vs/base/browser/codicons';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
|
||||
export interface IQuickInputOptions {
|
||||
idPrefix: string;
|
||||
@ -72,7 +72,7 @@ const $ = dom.$;
|
||||
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
|
||||
const backButtonIcon = registerCodicon('quick-input-back', Codicon.arrowLeft, localize('backButtonIcon', 'Icon for the back button in the quick input dialog.'));
|
||||
const backButtonIcon = registerCodicon('quick-input-back', Codicon.arrowLeft);
|
||||
|
||||
const backButton = {
|
||||
iconClass: backButtonIcon.classNames,
|
||||
@ -967,7 +967,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
const validationMessage = this.validationMessage || '';
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderCodicons(escape(validationMessage)));
|
||||
dom.reset(this.ui.message, ...renderLabelWithIcons(escape(validationMessage)));
|
||||
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
|
||||
}
|
||||
this.ui.customButton.label = this.customLabel || '';
|
||||
@ -1103,7 +1103,7 @@ class InputBox extends QuickInput implements IInputBox {
|
||||
const validationMessage = this.validationMessage || this.noValidationMessage;
|
||||
if (this._lastValidationMessage !== validationMessage) {
|
||||
this._lastValidationMessage = validationMessage;
|
||||
dom.reset(this.ui.message, ...renderCodicons(validationMessage));
|
||||
dom.reset(this.ui.message, ...renderLabelWithIcons(validationMessage));
|
||||
this.showMessageDecoration(this.validationMessage ? Severity.Error : Severity.Ignore);
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IQuickPickItem, IQuickPickItemButtonEvent, IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { matchesFuzzyCodiconAware, parseCodicons } from 'vs/base/common/codicon';
|
||||
import { matchesFuzzyIconAware, parseLabelWithIcons } from 'vs/base/common/iconLabels';
|
||||
import { compareAnything } from 'vs/base/common/comparers';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
@ -34,6 +34,7 @@ interface IListElement {
|
||||
readonly index: number;
|
||||
readonly item: IQuickPickItem;
|
||||
readonly saneLabel: string;
|
||||
readonly saneMeta?: string;
|
||||
readonly saneAriaLabel: string;
|
||||
readonly saneDescription?: string;
|
||||
readonly saneDetail?: string;
|
||||
@ -49,6 +50,7 @@ class ListElement implements IListElement, IDisposable {
|
||||
index!: number;
|
||||
item!: IQuickPickItem;
|
||||
saneLabel!: string;
|
||||
saneMeta!: string;
|
||||
saneAriaLabel!: string;
|
||||
saneDescription?: string;
|
||||
saneDetail?: string;
|
||||
@ -127,7 +129,7 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
|
||||
const row2 = dom.append(rows, $('.quick-input-list-row'));
|
||||
|
||||
// Label
|
||||
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportCodicons: true });
|
||||
data.label = new IconLabel(row1, { supportHighlights: true, supportDescriptionHighlights: true, supportIcons: true });
|
||||
|
||||
// Keybinding
|
||||
const keybindingContainer = dom.append(row1, $('.quick-input-list-entry-keybinding'));
|
||||
@ -190,12 +192,11 @@ class ListElementRenderer implements IListRenderer<ListElement, IListElementTemp
|
||||
if (button.alwaysVisible) {
|
||||
cssClasses = cssClasses ? `${cssClasses} always-visible` : 'always-visible';
|
||||
}
|
||||
const action = new Action(`id-${index}`, '', cssClasses, true, () => {
|
||||
const action = new Action(`id-${index}`, '', cssClasses, true, async () => {
|
||||
element.fireButtonTriggered({
|
||||
button,
|
||||
item: element.item
|
||||
});
|
||||
return Promise.resolve();
|
||||
});
|
||||
action.tooltip = button.tooltip || '';
|
||||
return action;
|
||||
@ -248,6 +249,7 @@ export class QuickInputList {
|
||||
matchOnDescription = false;
|
||||
matchOnDetail = false;
|
||||
matchOnLabel = true;
|
||||
matchOnMeta = true;
|
||||
sortByLabel = true;
|
||||
private readonly _onChangedAllVisibleChecked = new Emitter<boolean>();
|
||||
onChangedAllVisibleChecked: Event<boolean> = this._onChangedAllVisibleChecked.event;
|
||||
@ -421,10 +423,11 @@ export class QuickInputList {
|
||||
if (item.type !== 'separator') {
|
||||
const previous = index && inputElements[index - 1];
|
||||
const saneLabel = item.label && item.label.replace(/\r?\n/g, ' ');
|
||||
const saneMeta = item.meta && item.meta.replace(/\r?\n/g, ' ');
|
||||
const saneDescription = item.description && item.description.replace(/\r?\n/g, ' ');
|
||||
const saneDetail = item.detail && item.detail.replace(/\r?\n/g, ' ');
|
||||
const saneAriaLabel = item.ariaLabel || [saneLabel, saneDescription, saneDetail]
|
||||
.map(s => s && parseCodicons(s).text)
|
||||
.map(s => s && parseLabelWithIcons(s).text)
|
||||
.filter(s => !!s)
|
||||
.join(', ');
|
||||
|
||||
@ -432,6 +435,7 @@ export class QuickInputList {
|
||||
index,
|
||||
item,
|
||||
saneLabel,
|
||||
saneMeta,
|
||||
saneAriaLabel,
|
||||
saneDescription,
|
||||
saneDetail,
|
||||
@ -597,14 +601,15 @@ export class QuickInputList {
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by value (since we support codicons, use codicon aware fuzzy matching)
|
||||
// Filter by value (since we support icons in labels, use $(..) aware fuzzy matching)
|
||||
else {
|
||||
this.elements.forEach(element => {
|
||||
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneLabel))) : undefined;
|
||||
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDescription || ''))) : undefined;
|
||||
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyCodiconAware(query, parseCodicons(element.saneDetail || ''))) : undefined;
|
||||
const labelHighlights = this.matchOnLabel ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneLabel))) : undefined;
|
||||
const descriptionHighlights = this.matchOnDescription ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDescription || ''))) : undefined;
|
||||
const detailHighlights = this.matchOnDetail ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneDetail || ''))) : undefined;
|
||||
const metaHighlights = this.matchOnMeta ? withNullAsUndefined(matchesFuzzyIconAware(query, parseLabelWithIcons(element.saneMeta || ''))) : undefined;
|
||||
|
||||
if (labelHighlights || descriptionHighlights || detailHighlights) {
|
||||
if (labelHighlights || descriptionHighlights || detailHighlights || metaHighlights) {
|
||||
element.labelHighlights = labelHighlights;
|
||||
element.descriptionHighlights = descriptionHighlights;
|
||||
element.detailHighlights = detailHighlights;
|
||||
|
@ -21,6 +21,7 @@ export interface IQuickPickItem {
|
||||
type?: 'item';
|
||||
id?: string;
|
||||
label: string;
|
||||
meta?: string;
|
||||
ariaLabel?: string;
|
||||
description?: string;
|
||||
detail?: string;
|
||||
|
@ -7,7 +7,7 @@
|
||||
// #######################################################################
|
||||
// ### ###
|
||||
// ### electron.d.ts types we need in a common layer for reuse ###
|
||||
// ### (copied from Electron 9.x) ###
|
||||
// ### (copied from Electron 11.x) ###
|
||||
// ### ###
|
||||
// #######################################################################
|
||||
|
||||
@ -212,7 +212,7 @@ export interface SaveDialogReturnValue {
|
||||
|
||||
export interface FileFilter {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/file-filter
|
||||
// Docs: https://electronjs.org/docs/api/structures/file-filter
|
||||
|
||||
extensions: string[];
|
||||
name: string;
|
||||
@ -220,7 +220,7 @@ export interface FileFilter {
|
||||
|
||||
export interface InputEvent {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/input-event
|
||||
// Docs: https://electronjs.org/docs/api/structures/input-event
|
||||
|
||||
/**
|
||||
* An array of modifiers of the event, can be `shift`, `control`, `ctrl`, `alt`,
|
||||
@ -232,7 +232,7 @@ export interface InputEvent {
|
||||
|
||||
export interface MouseInputEvent extends InputEvent {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/mouse-input-event
|
||||
// Docs: https://electronjs.org/docs/api/structures/mouse-input-event
|
||||
|
||||
/**
|
||||
* The button pressed, can be `left`, `middle`, `right`.
|
||||
|
@ -35,6 +35,17 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {any} message
|
||||
* @param {MessagePort[]} transfer
|
||||
*/
|
||||
postMessage(channel, message, transfer) {
|
||||
if (validateIPC(channel)) {
|
||||
ipcRenderer.postMessage(channel, message, transfer);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} channel
|
||||
* @param {any[]} args
|
||||
@ -114,6 +125,7 @@
|
||||
*/
|
||||
process: {
|
||||
get platform() { return process.platform; },
|
||||
get arch() { return process.arch; },
|
||||
get env() { return process.env; },
|
||||
get versions() { return process.versions; },
|
||||
get type() { return 'renderer'; },
|
||||
|
@ -7,35 +7,54 @@
|
||||
// #######################################################################
|
||||
// ### ###
|
||||
// ### electron.d.ts types we expose from electron-sandbox ###
|
||||
// ### (copied from Electron 9.x) ###
|
||||
// ### (copied from Electron 11.x) ###
|
||||
// ### ###
|
||||
// #######################################################################
|
||||
|
||||
export interface IpcRendererEvent extends Event {
|
||||
|
||||
// Docs: https://electronjs.org/docs/api/structures/ipc-renderer-event
|
||||
|
||||
/**
|
||||
* A list of MessagePorts that were transferred with this message
|
||||
*/
|
||||
ports: MessagePort[];
|
||||
/**
|
||||
* The `IpcRenderer` instance that emitted the event originally
|
||||
*/
|
||||
sender: IpcRenderer;
|
||||
/**
|
||||
* The `webContents.id` that sent the message, you can call
|
||||
* `event.sender.sendTo(event.senderId, ...)` to reply to the message, see
|
||||
* ipcRenderer.sendTo for more information. This only applies to messages sent from
|
||||
* a different renderer. Messages sent directly from the main process set
|
||||
* `event.senderId` to `0`.
|
||||
*/
|
||||
senderId: number;
|
||||
}
|
||||
|
||||
export interface IpcRenderer {
|
||||
/**
|
||||
* Listens to `channel`, when a new message arrives `listener` would be called with
|
||||
* `listener(event, args...)`.
|
||||
*/
|
||||
on(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
|
||||
|
||||
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
|
||||
/**
|
||||
* Adds a one time `listener` function for the event. This `listener` is invoked
|
||||
* only the next time a message is sent to `channel`, after which it is removed.
|
||||
*/
|
||||
once(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
|
||||
|
||||
once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this;
|
||||
/**
|
||||
* Removes the specified `listener` from the listener array for the specified
|
||||
* `channel`.
|
||||
*/
|
||||
removeListener(channel: string, listener: (event: unknown, ...args: any[]) => void): void;
|
||||
|
||||
removeListener(channel: string, listener: (...args: any[]) => void): this;
|
||||
/**
|
||||
* Send an asynchronous message to the main process via `channel`, along with
|
||||
* arguments. Arguments will be serialized with the Structured Clone Algorithm,
|
||||
* just like `postMessage`, so prototype chains will not be included. Sending
|
||||
* Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception.
|
||||
* just like `window.postMessage`, so prototype chains will not be included.
|
||||
* Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an
|
||||
* exception.
|
||||
*
|
||||
* > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special
|
||||
* Electron objects is deprecated, and will begin throwing an exception starting
|
||||
@ -43,8 +62,51 @@ export interface IpcRenderer {
|
||||
*
|
||||
* The main process handles it by listening for `channel` with the `ipcMain`
|
||||
* module.
|
||||
*
|
||||
* If you need to transfer a `MessagePort` to the main process, use
|
||||
* `ipcRenderer.postMessage`.
|
||||
*
|
||||
* If you want to receive a single response from the main process, like the result
|
||||
* of a method call, consider using `ipcRenderer.invoke`.
|
||||
*/
|
||||
send(channel: string, ...args: any[]): void;
|
||||
/**
|
||||
* Resolves with the response from the main process.
|
||||
*
|
||||
* Send a message to the main process via `channel` and expect a result
|
||||
* asynchronously. Arguments will be serialized with the Structured Clone
|
||||
* Algorithm, just like `window.postMessage`, so prototype chains will not be
|
||||
* included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw
|
||||
* an exception.
|
||||
*
|
||||
* > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special
|
||||
* Electron objects is deprecated, and will begin throwing an exception starting
|
||||
* with Electron 9.
|
||||
*
|
||||
* The main process should listen for `channel` with `ipcMain.handle()`.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* If you need to transfer a `MessagePort` to the main process, use
|
||||
* `ipcRenderer.postMessage`.
|
||||
*
|
||||
* If you do not need a response to the message, consider using `ipcRenderer.send`.
|
||||
*/
|
||||
invoke(channel: string, ...args: any[]): Promise<any>;
|
||||
/**
|
||||
* Send a message to the main process, optionally transferring ownership of zero or
|
||||
* more `MessagePort` objects.
|
||||
*
|
||||
* The transferred `MessagePort` objects will be available in the main process as
|
||||
* `MessagePortMain` objects by accessing the `ports` property of the emitted
|
||||
* event.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* For more information on using `MessagePort` and `MessageChannel`, see the MDN
|
||||
* documentation.
|
||||
*/
|
||||
postMessage(channel: string, message: any, transfer?: MessagePort[]): void;
|
||||
}
|
||||
|
||||
export interface WebFrame {
|
||||
@ -70,16 +132,23 @@ export interface CrashReporter {
|
||||
* with crashes that occur in other renderer processes or in the main process.
|
||||
*
|
||||
* **Note:** Parameters have limits on the length of the keys and values. Key names
|
||||
* must be no longer than 39 bytes, and values must be no longer than 127 bytes.
|
||||
* must be no longer than 39 bytes, and values must be no longer than 20320 bytes.
|
||||
* Keys with names longer than the maximum will be silently ignored. Key values
|
||||
* longer than the maximum length will be truncated.
|
||||
*
|
||||
* **Note:** On linux values that are longer than 127 bytes will be chunked into
|
||||
* multiple keys, each 127 bytes in length. E.g. `addExtraParameter('foo',
|
||||
* 'a'.repeat(130))` will result in two chunked keys `foo__1` and `foo__2`, the
|
||||
* first will contain the first 127 bytes and the second will contain the remaining
|
||||
* 3 bytes. On your crash reporting backend you should stitch together keys in
|
||||
* this format.
|
||||
*/
|
||||
addExtraParameter(key: string, value: string): void;
|
||||
}
|
||||
|
||||
export interface ProcessMemoryInfo {
|
||||
|
||||
// Docs: http://electronjs.org/docs/api/structures/process-memory-info
|
||||
// Docs: https://electronjs.org/docs/api/structures/process-memory-info
|
||||
|
||||
/**
|
||||
* The amount of memory not shared by other processes, such as JS heap or HTML
|
||||
@ -133,10 +202,7 @@ export interface CrashReporterStartOptions {
|
||||
rateLimit?: boolean;
|
||||
/**
|
||||
* If true, crash reports will be compressed and uploaded with `Content-Encoding:
|
||||
* gzip`. Not all collection servers support compressed payloads. Default is
|
||||
* `false`.
|
||||
*
|
||||
* @platform darwin,win32
|
||||
* gzip`. Default is `false`.
|
||||
*/
|
||||
compress?: boolean;
|
||||
/**
|
||||
|
@ -14,6 +14,12 @@ export interface ISandboxNodeProcess extends INodeProcess {
|
||||
*/
|
||||
readonly platform: 'win32' | 'linux' | 'darwin';
|
||||
|
||||
/**
|
||||
* The process.arch property returns a string identifying the CPU architecture
|
||||
* on which the Node.js process is running.
|
||||
*/
|
||||
readonly arch: string;
|
||||
|
||||
/**
|
||||
* The type will always be Electron renderer.
|
||||
*/
|
||||
@ -48,7 +54,7 @@ export interface ISandboxNodeProcess extends INodeProcess {
|
||||
* The order of overwrites is `process.env` < `shellEnv` < `userEnv`.
|
||||
*
|
||||
* It is critical that every process awaits this method early on startup to get the right
|
||||
* set of environment in `process.env`.
|
||||
* set of environment in `process.env`.
|
||||
*/
|
||||
resolveEnv(userEnv: IProcessEnvironment): Promise<void>;
|
||||
|
||||
@ -76,7 +82,7 @@ export interface ISandboxNodeProcess extends INodeProcess {
|
||||
export interface ISandboxContext {
|
||||
|
||||
/**
|
||||
* Wether the renderer runs with `sandbox` enabled or not.
|
||||
* Whether the renderer runs with `sandbox` enabled or not.
|
||||
*/
|
||||
sandbox: boolean;
|
||||
}
|
||||
|
@ -200,9 +200,9 @@ export class Storage extends Disposable implements IStorage {
|
||||
return parseInt(value, 10);
|
||||
}
|
||||
|
||||
set(key: string, value: string | boolean | number | null | undefined): Promise<void> {
|
||||
async set(key: string, value: string | boolean | number | null | undefined): Promise<void> {
|
||||
if (this.state === StorageState.Closed) {
|
||||
return Promise.resolve(); // Return early if we are already closed
|
||||
return; // Return early if we are already closed
|
||||
}
|
||||
|
||||
// We remove the key for undefined/null values
|
||||
@ -216,7 +216,7 @@ export class Storage extends Disposable implements IStorage {
|
||||
// Return early if value already set
|
||||
const currentValue = this.cache.get(key);
|
||||
if (currentValue === valueStr) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update in cache and pending
|
||||
@ -231,15 +231,15 @@ export class Storage extends Disposable implements IStorage {
|
||||
return this.flushDelayer.trigger(() => this.flushPending());
|
||||
}
|
||||
|
||||
delete(key: string): Promise<void> {
|
||||
async delete(key: string): Promise<void> {
|
||||
if (this.state === StorageState.Closed) {
|
||||
return Promise.resolve(); // Return early if we are already closed
|
||||
return; // Return early if we are already closed
|
||||
}
|
||||
|
||||
// Remove from cache and add to pending
|
||||
const wasDeleted = this.cache.delete(key);
|
||||
if (!wasDeleted) {
|
||||
return Promise.resolve(); // Return early if value already deleted
|
||||
return; // Return early if value already deleted
|
||||
}
|
||||
|
||||
if (!this.pendingDeletes.has(key)) {
|
||||
@ -257,7 +257,7 @@ export class Storage extends Disposable implements IStorage {
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.state === StorageState.Closed) {
|
||||
return Promise.resolve(); // return if already closed
|
||||
return; // return if already closed
|
||||
}
|
||||
|
||||
// Update state
|
||||
@ -282,9 +282,9 @@ export class Storage extends Disposable implements IStorage {
|
||||
return this.pendingInserts.size > 0 || this.pendingDeletes.size > 0;
|
||||
}
|
||||
|
||||
private flushPending(): Promise<void> {
|
||||
private async flushPending(): Promise<void> {
|
||||
if (!this.hasPending) {
|
||||
return Promise.resolve(); // return early if nothing to do
|
||||
return; // return early if nothing to do
|
||||
}
|
||||
|
||||
// Get pending data
|
||||
@ -305,9 +305,9 @@ export class Storage extends Disposable implements IStorage {
|
||||
});
|
||||
}
|
||||
|
||||
whenFlushed(): Promise<void> {
|
||||
async whenFlushed(): Promise<void> {
|
||||
if (!this.hasPending) {
|
||||
return Promise.resolve(); // return early if nothing to do
|
||||
return; // return early if nothing to do
|
||||
}
|
||||
|
||||
return new Promise(resolve => this.whenFlushedCallbacks.push(resolve));
|
||||
|
@ -412,11 +412,17 @@ export class SQLiteStorageDatabase implements IStorageDatabase {
|
||||
}
|
||||
|
||||
class SQLiteStorageDatabaseLogger {
|
||||
|
||||
// to reduce lots of output, require an environment variable to enable tracing
|
||||
// this helps when running with --verbose normally where the storage tracing
|
||||
// might hide useful output to look at
|
||||
static readonly VSCODE_TRACE_STORAGE = 'VSCODE_TRACE_STORAGE';
|
||||
|
||||
private readonly logTrace: ((msg: string) => void) | undefined;
|
||||
private readonly logError: ((error: string | Error) => void) | undefined;
|
||||
|
||||
constructor(options?: ISQLiteStorageDatabaseLoggingOptions) {
|
||||
if (options && typeof options.logTrace === 'function') {
|
||||
if (options && typeof options.logTrace === 'function' && process.env[SQLiteStorageDatabaseLogger.VSCODE_TRACE_STORAGE]) {
|
||||
this.logTrace = options.logTrace;
|
||||
}
|
||||
|
||||
|
@ -5,40 +5,39 @@
|
||||
|
||||
import { SQLiteStorageDatabase, ISQLiteStorageDatabaseOptions } from 'vs/base/parts/storage/node/storage';
|
||||
import { Storage, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/parts/storage/common/storage';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { tmpdir } from 'os';
|
||||
import { equal, ok } from 'assert';
|
||||
import { mkdirp, writeFile, exists, unlink, rimraf, RimRafMode } from 'vs/base/node/pfs';
|
||||
import { strictEqual, ok } from 'assert';
|
||||
import { mkdirp, writeFile, exists, unlink, rimraf } from 'vs/base/node/pfs';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
|
||||
suite('Storage Library', function () {
|
||||
flakySuite('Storage Library', function () {
|
||||
|
||||
// Given issues such as https://github.com/microsoft/vscode/issues/108113
|
||||
// we see random test failures when accessing the native file system.
|
||||
this.retries(3);
|
||||
this.timeout(1000 * 20);
|
||||
let testDir: string;
|
||||
|
||||
function uniqueStorageDir(): string {
|
||||
const id = generateUuid();
|
||||
setup(function () {
|
||||
testDir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary');
|
||||
|
||||
return join(tmpdir(), 'vsctests', id, 'storage2', id);
|
||||
}
|
||||
return mkdirp(testDir);
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
return rimraf(testDir);
|
||||
});
|
||||
|
||||
test('basics', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
const storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
|
||||
|
||||
await storage.init();
|
||||
|
||||
// Empty fallbacks
|
||||
equal(storage.get('foo', 'bar'), 'bar');
|
||||
equal(storage.getNumber('foo', 55), 55);
|
||||
equal(storage.getBoolean('foo', true), true);
|
||||
strictEqual(storage.get('foo', 'bar'), 'bar');
|
||||
strictEqual(storage.getNumber('foo', 55), 55);
|
||||
strictEqual(storage.getBoolean('foo', true), true);
|
||||
|
||||
let changes = new Set<string>();
|
||||
storage.onDidChangeStorage(key => {
|
||||
@ -55,19 +54,19 @@ suite('Storage Library', function () {
|
||||
let flushPromiseResolved = false;
|
||||
storage.whenFlushed().then(() => flushPromiseResolved = true);
|
||||
|
||||
equal(storage.get('bar'), 'foo');
|
||||
equal(storage.getNumber('barNumber'), 55);
|
||||
equal(storage.getBoolean('barBoolean'), true);
|
||||
strictEqual(storage.get('bar'), 'foo');
|
||||
strictEqual(storage.getNumber('barNumber'), 55);
|
||||
strictEqual(storage.getBoolean('barBoolean'), true);
|
||||
|
||||
equal(changes.size, 3);
|
||||
strictEqual(changes.size, 3);
|
||||
ok(changes.has('bar'));
|
||||
ok(changes.has('barNumber'));
|
||||
ok(changes.has('barBoolean'));
|
||||
|
||||
let setPromiseResolved = false;
|
||||
await Promise.all([set1Promise, set2Promise, set3Promise]).then(() => setPromiseResolved = true);
|
||||
equal(setPromiseResolved, true);
|
||||
equal(flushPromiseResolved, true);
|
||||
strictEqual(setPromiseResolved, true);
|
||||
strictEqual(flushPromiseResolved, true);
|
||||
|
||||
changes = new Set<string>();
|
||||
|
||||
@ -75,7 +74,7 @@ suite('Storage Library', function () {
|
||||
storage.set('bar', 'foo');
|
||||
storage.set('barNumber', 55);
|
||||
storage.set('barBoolean', true);
|
||||
equal(changes.size, 0);
|
||||
strictEqual(changes.size, 0);
|
||||
|
||||
// Simple deletes
|
||||
const delete1Promise = storage.delete('bar');
|
||||
@ -86,7 +85,7 @@ suite('Storage Library', function () {
|
||||
ok(!storage.getNumber('barNumber'));
|
||||
ok(!storage.getBoolean('barBoolean'));
|
||||
|
||||
equal(changes.size, 3);
|
||||
strictEqual(changes.size, 3);
|
||||
ok(changes.has('bar'));
|
||||
ok(changes.has('barNumber'));
|
||||
ok(changes.has('barBoolean'));
|
||||
@ -97,19 +96,16 @@ suite('Storage Library', function () {
|
||||
storage.delete('bar');
|
||||
storage.delete('barNumber');
|
||||
storage.delete('barBoolean');
|
||||
equal(changes.size, 0);
|
||||
strictEqual(changes.size, 0);
|
||||
|
||||
let deletePromiseResolved = false;
|
||||
await Promise.all([delete1Promise, delete2Promise, delete3Promise]).then(() => deletePromiseResolved = true);
|
||||
equal(deletePromiseResolved, true);
|
||||
strictEqual(deletePromiseResolved, true);
|
||||
|
||||
await storage.close();
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('external changes', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
await mkdirp(storageDir);
|
||||
|
||||
class TestSQLiteStorageDatabase extends SQLiteStorageDatabase {
|
||||
private readonly _onDidChangeItemsExternal = new Emitter<IStorageItemsChangeEvent>();
|
||||
@ -120,7 +116,7 @@ suite('Storage Library', function () {
|
||||
}
|
||||
}
|
||||
|
||||
const database = new TestSQLiteStorageDatabase(join(storageDir, 'storage.db'));
|
||||
const database = new TestSQLiteStorageDatabase(join(testDir, 'storage.db'));
|
||||
const storage = new Storage(database);
|
||||
|
||||
let changes = new Set<string>();
|
||||
@ -138,35 +134,31 @@ suite('Storage Library', function () {
|
||||
const changed = new Map<string, string>();
|
||||
changed.set('foo', 'bar');
|
||||
database.fireDidChangeItemsExternal({ changed });
|
||||
equal(changes.size, 0);
|
||||
strictEqual(changes.size, 0);
|
||||
|
||||
// Change is accepted if valid
|
||||
changed.set('foo', 'bar1');
|
||||
database.fireDidChangeItemsExternal({ changed });
|
||||
ok(changes.has('foo'));
|
||||
equal(storage.get('foo'), 'bar1');
|
||||
strictEqual(storage.get('foo'), 'bar1');
|
||||
changes.clear();
|
||||
|
||||
// Delete is accepted
|
||||
const deleted = new Set<string>(['foo']);
|
||||
database.fireDidChangeItemsExternal({ deleted });
|
||||
ok(changes.has('foo'));
|
||||
equal(storage.get('foo', undefined), undefined);
|
||||
strictEqual(storage.get('foo', undefined), undefined);
|
||||
changes.clear();
|
||||
|
||||
// Nothing happens if changing to same value
|
||||
database.fireDidChangeItemsExternal({ deleted });
|
||||
equal(changes.size, 0);
|
||||
strictEqual(changes.size, 0);
|
||||
|
||||
await storage.close();
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('close flushes data', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
await mkdirp(storageDir);
|
||||
|
||||
let storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
|
||||
await storage.init();
|
||||
|
||||
const set1Promise = storage.set('foo', 'bar');
|
||||
@ -175,26 +167,26 @@ suite('Storage Library', function () {
|
||||
let flushPromiseResolved = false;
|
||||
storage.whenFlushed().then(() => flushPromiseResolved = true);
|
||||
|
||||
equal(storage.get('foo'), 'bar');
|
||||
equal(storage.get('bar'), 'foo');
|
||||
strictEqual(storage.get('foo'), 'bar');
|
||||
strictEqual(storage.get('bar'), 'foo');
|
||||
|
||||
let setPromiseResolved = false;
|
||||
Promise.all([set1Promise, set2Promise]).then(() => setPromiseResolved = true);
|
||||
|
||||
await storage.close();
|
||||
|
||||
equal(setPromiseResolved, true);
|
||||
equal(flushPromiseResolved, true);
|
||||
strictEqual(setPromiseResolved, true);
|
||||
strictEqual(flushPromiseResolved, true);
|
||||
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
|
||||
await storage.init();
|
||||
|
||||
equal(storage.get('foo'), 'bar');
|
||||
equal(storage.get('bar'), 'foo');
|
||||
strictEqual(storage.get('foo'), 'bar');
|
||||
strictEqual(storage.get('bar'), 'foo');
|
||||
|
||||
await storage.close();
|
||||
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
|
||||
await storage.init();
|
||||
|
||||
const delete1Promise = storage.delete('foo');
|
||||
@ -208,23 +200,19 @@ suite('Storage Library', function () {
|
||||
|
||||
await storage.close();
|
||||
|
||||
equal(deletePromiseResolved, true);
|
||||
strictEqual(deletePromiseResolved, true);
|
||||
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
|
||||
await storage.init();
|
||||
|
||||
ok(!storage.get('foo'));
|
||||
ok(!storage.get('bar'));
|
||||
|
||||
await storage.close();
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('conflicting updates', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
await mkdirp(storageDir);
|
||||
|
||||
let storage = new Storage(new SQLiteStorageDatabase(join(storageDir, 'storage.db')));
|
||||
let storage = new Storage(new SQLiteStorageDatabase(join(testDir, 'storage.db')));
|
||||
await storage.init();
|
||||
|
||||
let changes = new Set<string>();
|
||||
@ -239,8 +227,8 @@ suite('Storage Library', function () {
|
||||
let flushPromiseResolved = false;
|
||||
storage.whenFlushed().then(() => flushPromiseResolved = true);
|
||||
|
||||
equal(storage.get('foo'), 'bar3');
|
||||
equal(changes.size, 1);
|
||||
strictEqual(storage.get('foo'), 'bar3');
|
||||
strictEqual(changes.size, 1);
|
||||
ok(changes.has('foo'));
|
||||
|
||||
let setPromiseResolved = false;
|
||||
@ -255,7 +243,7 @@ suite('Storage Library', function () {
|
||||
|
||||
ok(!storage.get('bar'));
|
||||
|
||||
equal(changes.size, 1);
|
||||
strictEqual(changes.size, 1);
|
||||
ok(changes.has('bar'));
|
||||
|
||||
let setAndDeletePromiseResolved = false;
|
||||
@ -263,14 +251,10 @@ suite('Storage Library', function () {
|
||||
ok(setAndDeletePromiseResolved);
|
||||
|
||||
await storage.close();
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('corrupt DB recovers', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const storageFile = join(storageDir, 'storage.db');
|
||||
const storageFile = join(testDir, 'storage.db');
|
||||
|
||||
let storage = new Storage(new SQLiteStorageDatabase(storageFile));
|
||||
await storage.init();
|
||||
@ -281,34 +265,22 @@ suite('Storage Library', function () {
|
||||
|
||||
await storage.set('foo', 'bar');
|
||||
|
||||
equal(storage.get('bar'), 'foo');
|
||||
equal(storage.get('foo'), 'bar');
|
||||
strictEqual(storage.get('bar'), 'foo');
|
||||
strictEqual(storage.get('foo'), 'bar');
|
||||
|
||||
await storage.close();
|
||||
|
||||
storage = new Storage(new SQLiteStorageDatabase(storageFile));
|
||||
await storage.init();
|
||||
|
||||
equal(storage.get('bar'), 'foo');
|
||||
equal(storage.get('foo'), 'bar');
|
||||
strictEqual(storage.get('bar'), 'foo');
|
||||
strictEqual(storage.get('foo'), 'bar');
|
||||
|
||||
await storage.close();
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
});
|
||||
|
||||
suite('SQLite Storage Library', function () {
|
||||
|
||||
// Given issues such as https://github.com/microsoft/vscode/issues/108113
|
||||
// we see random test failures when accessing the native file system.
|
||||
this.retries(3);
|
||||
this.timeout(1000 * 20);
|
||||
|
||||
function uniqueStorageDir(): string {
|
||||
const id = generateUuid();
|
||||
|
||||
return join(tmpdir(), 'vsctests', id, 'storage', id);
|
||||
}
|
||||
flakySuite('SQLite Storage Library', function () {
|
||||
|
||||
function toSet(elements: string[]): Set<string> {
|
||||
const set = new Set<string>();
|
||||
@ -317,6 +289,18 @@ suite('SQLite Storage Library', function () {
|
||||
return set;
|
||||
}
|
||||
|
||||
let storageDir: string;
|
||||
|
||||
setup(function () {
|
||||
storageDir = getRandomTestPath(tmpdir(), 'vsctests', 'storagelibrary');
|
||||
|
||||
return mkdirp(storageDir);
|
||||
});
|
||||
|
||||
teardown(function () {
|
||||
return rimraf(storageDir);
|
||||
});
|
||||
|
||||
async function testDBBasics(path: string, logError?: (error: Error | string) => void) {
|
||||
let options!: ISQLiteStorageDatabaseOptions;
|
||||
if (logError) {
|
||||
@ -335,49 +319,49 @@ suite('SQLite Storage Library', function () {
|
||||
items.set(JSON.stringify({ foo: 'bar' }), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
let storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await storage.updateItems({ insert: items });
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size);
|
||||
equal(storedItems.get('foo'), 'bar');
|
||||
equal(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
equal(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
strictEqual(storedItems.size, items.size);
|
||||
strictEqual(storedItems.get('foo'), 'bar');
|
||||
strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
await storage.updateItems({ delete: toSet(['foo']) });
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size - 1);
|
||||
strictEqual(storedItems.size, items.size - 1);
|
||||
ok(!storedItems.has('foo'));
|
||||
equal(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
equal(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
await storage.updateItems({ insert: items });
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size);
|
||||
equal(storedItems.get('foo'), 'bar');
|
||||
equal(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
equal(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
strictEqual(storedItems.size, items.size);
|
||||
strictEqual(storedItems.get('foo'), 'bar');
|
||||
strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
const itemsChange = new Map<string, string>();
|
||||
itemsChange.set('foo', 'otherbar');
|
||||
await storage.updateItems({ insert: itemsChange });
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.get('foo'), 'otherbar');
|
||||
strictEqual(storedItems.get('foo'), 'otherbar');
|
||||
|
||||
await storage.updateItems({ delete: toSet(['foo', 'bar', 'some/foo/path', JSON.stringify({ foo: 'bar' })]) });
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await storage.updateItems({ insert: items, delete: toSet(['foo', 'some/foo/path', 'other']) });
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 1);
|
||||
equal(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
strictEqual(storedItems.size, 1);
|
||||
strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
await storage.updateItems({ delete: toSet([JSON.stringify({ foo: 'bar' })]) });
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
let recoveryCalled = false;
|
||||
await storage.close(() => {
|
||||
@ -386,35 +370,19 @@ suite('SQLite Storage Library', function () {
|
||||
return new Map();
|
||||
});
|
||||
|
||||
equal(recoveryCalled, false);
|
||||
strictEqual(recoveryCalled, false);
|
||||
}
|
||||
|
||||
test('basics', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
await testDBBasics(join(storageDir, 'storage.db'));
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (open multiple times)', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
await testDBBasics(join(storageDir, 'storage.db'));
|
||||
await testDBBasics(join(storageDir, 'storage.db'));
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (corrupt DB falls back to empty DB)', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const corruptDBPath = join(storageDir, 'broken.db');
|
||||
await writeFile(corruptDBPath, 'This is a broken DB');
|
||||
|
||||
@ -424,15 +392,9 @@ suite('SQLite Storage Library', function () {
|
||||
});
|
||||
|
||||
ok(expectedError);
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (corrupt DB restores from previous backup)', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const storagePath = join(storageDir, 'storage.db');
|
||||
let storage = new SQLiteStorageDatabase(storagePath);
|
||||
|
||||
@ -449,10 +411,10 @@ suite('SQLite Storage Library', function () {
|
||||
storage = new SQLiteStorageDatabase(storagePath);
|
||||
|
||||
const storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size);
|
||||
equal(storedItems.get('foo'), 'bar');
|
||||
equal(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
equal(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
strictEqual(storedItems.size, items.size);
|
||||
strictEqual(storedItems.get('foo'), 'bar');
|
||||
strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
let recoveryCalled = false;
|
||||
await storage.close(() => {
|
||||
@ -461,16 +423,10 @@ suite('SQLite Storage Library', function () {
|
||||
return new Map();
|
||||
});
|
||||
|
||||
equal(recoveryCalled, false);
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
strictEqual(recoveryCalled, false);
|
||||
});
|
||||
|
||||
test('basics (corrupt DB falls back to empty DB if backup is corrupt)', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const storagePath = join(storageDir, 'storage.db');
|
||||
let storage = new SQLiteStorageDatabase(storagePath);
|
||||
|
||||
@ -488,24 +444,12 @@ suite('SQLite Storage Library', function () {
|
||||
storage = new SQLiteStorageDatabase(storagePath);
|
||||
|
||||
const storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await testDBBasics(storagePath);
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('basics (DB that becomes corrupt during runtime stores all state from cache on close)', async () => {
|
||||
if (isWindows) {
|
||||
await Promise.resolve(); // Windows will fail to write to open DB due to locking
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
(isWindows ? test.skip /* Windows will fail to write to open DB due to locking */ : test)('basics (DB that becomes corrupt during runtime stores all state from cache on close)', async () => {
|
||||
const storagePath = join(storageDir, 'storage.db');
|
||||
let storage = new SQLiteStorageDatabase(storagePath);
|
||||
|
||||
@ -518,7 +462,7 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.close();
|
||||
|
||||
const backupPath = `${storagePath}.backup`;
|
||||
equal(await exists(backupPath), true);
|
||||
strictEqual(await exists(backupPath), true);
|
||||
|
||||
storage = new SQLiteStorageDatabase(storagePath);
|
||||
await storage.getItems();
|
||||
@ -540,16 +484,16 @@ suite('SQLite Storage Library', function () {
|
||||
return items;
|
||||
});
|
||||
|
||||
equal(recoveryCalled, true);
|
||||
equal(await exists(backupPath), true);
|
||||
strictEqual(recoveryCalled, true);
|
||||
strictEqual(await exists(backupPath), true);
|
||||
|
||||
storage = new SQLiteStorageDatabase(storagePath);
|
||||
|
||||
const storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size);
|
||||
equal(storedItems.get('foo'), 'bar');
|
||||
equal(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
equal(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
strictEqual(storedItems.size, items.size);
|
||||
strictEqual(storedItems.get('foo'), 'bar');
|
||||
strictEqual(storedItems.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(storedItems.get(JSON.stringify({ foo: 'bar' })), JSON.stringify({ bar: 'foo' }));
|
||||
|
||||
recoveryCalled = false;
|
||||
await storage.close(() => {
|
||||
@ -558,16 +502,10 @@ suite('SQLite Storage Library', function () {
|
||||
return new Map();
|
||||
});
|
||||
|
||||
equal(recoveryCalled, false);
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
strictEqual(recoveryCalled, false);
|
||||
});
|
||||
|
||||
test('real world example', async function () {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
let storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db'));
|
||||
|
||||
const items1 = new Map<string, string>();
|
||||
@ -587,7 +525,7 @@ suite('SQLite Storage Library', function () {
|
||||
items3.set('very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.very.long.key.', 'is long');
|
||||
|
||||
let storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await Promise.all([
|
||||
await storage.updateItems({ insert: items1 }),
|
||||
@ -595,28 +533,28 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.updateItems({ insert: items3 })
|
||||
]);
|
||||
|
||||
equal(await storage.checkIntegrity(true), 'ok');
|
||||
equal(await storage.checkIntegrity(false), 'ok');
|
||||
strictEqual(await storage.checkIntegrity(true), 'ok');
|
||||
strictEqual(await storage.checkIntegrity(false), 'ok');
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items1.size + items2.size + items3.size);
|
||||
strictEqual(storedItems.size, items1.size + items2.size + items3.size);
|
||||
|
||||
const items1Keys: string[] = [];
|
||||
items1.forEach((value, key) => {
|
||||
items1Keys.push(key);
|
||||
equal(storedItems.get(key), value);
|
||||
strictEqual(storedItems.get(key), value);
|
||||
});
|
||||
|
||||
const items2Keys: string[] = [];
|
||||
items2.forEach((value, key) => {
|
||||
items2Keys.push(key);
|
||||
equal(storedItems.get(key), value);
|
||||
strictEqual(storedItems.get(key), value);
|
||||
});
|
||||
|
||||
const items3Keys: string[] = [];
|
||||
items3.forEach((value, key) => {
|
||||
items3Keys.push(key);
|
||||
equal(storedItems.get(key), value);
|
||||
strictEqual(storedItems.get(key), value);
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
@ -626,7 +564,7 @@ suite('SQLite Storage Library', function () {
|
||||
]);
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await Promise.all([
|
||||
await storage.updateItems({ insert: items1 }),
|
||||
@ -638,27 +576,19 @@ suite('SQLite Storage Library', function () {
|
||||
]);
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items1.size + items2.size + items3.size);
|
||||
strictEqual(storedItems.size, items1.size + items2.size + items3.size);
|
||||
|
||||
await storage.close();
|
||||
|
||||
storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db'));
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items1.size + items2.size + items3.size);
|
||||
strictEqual(storedItems.size, items1.size + items2.size + items3.size);
|
||||
|
||||
await storage.close();
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('very large item value', async function () {
|
||||
this.timeout(20000);
|
||||
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
let storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db'));
|
||||
|
||||
const items = new Map<string, string>();
|
||||
@ -675,9 +605,9 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.updateItems({ insert: items });
|
||||
|
||||
let storedItems = await storage.getItems();
|
||||
equal(items.get('colorthemedata'), storedItems.get('colorthemedata'));
|
||||
equal(items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
|
||||
equal(items.get('super.large.string'), storedItems.get('super.large.string'));
|
||||
strictEqual(items.get('colorthemedata'), storedItems.get('colorthemedata'));
|
||||
strictEqual(items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
|
||||
strictEqual(items.get('super.large.string'), storedItems.get('super.large.string'));
|
||||
|
||||
uuid = generateUuid();
|
||||
value = [];
|
||||
@ -689,27 +619,23 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.updateItems({ insert: items });
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(items.get('colorthemedata'), storedItems.get('colorthemedata'));
|
||||
equal(items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
|
||||
equal(items.get('super.large.string'), storedItems.get('super.large.string'));
|
||||
strictEqual(items.get('colorthemedata'), storedItems.get('colorthemedata'));
|
||||
strictEqual(items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
|
||||
strictEqual(items.get('super.large.string'), storedItems.get('super.large.string'));
|
||||
|
||||
const toDelete = new Set<string>();
|
||||
toDelete.add('super.large.string');
|
||||
await storage.updateItems({ delete: toDelete });
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(items.get('colorthemedata'), storedItems.get('colorthemedata'));
|
||||
equal(items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
|
||||
strictEqual(items.get('colorthemedata'), storedItems.get('colorthemedata'));
|
||||
strictEqual(items.get('commandpalette.mru.cache'), storedItems.get('commandpalette.mru.cache'));
|
||||
ok(!storedItems.get('super.large.string'));
|
||||
|
||||
await storage.close();
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('multiple concurrent writes execute in sequence', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
await mkdirp(storageDir);
|
||||
|
||||
class TestStorage extends Storage {
|
||||
getStorage(): IStorageDatabase {
|
||||
@ -750,25 +676,19 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.set('some/foo3/path', 'some/bar/path');
|
||||
|
||||
const items = await storage.getStorage().getItems();
|
||||
equal(items.get('foo'), 'bar');
|
||||
equal(items.get('some/foo/path'), 'some/bar/path');
|
||||
equal(items.has('foo1'), false);
|
||||
equal(items.has('some/foo1/path'), false);
|
||||
equal(items.get('foo2'), 'bar');
|
||||
equal(items.get('some/foo2/path'), 'some/bar/path');
|
||||
equal(items.get('foo3'), 'bar');
|
||||
equal(items.get('some/foo3/path'), 'some/bar/path');
|
||||
strictEqual(items.get('foo'), 'bar');
|
||||
strictEqual(items.get('some/foo/path'), 'some/bar/path');
|
||||
strictEqual(items.has('foo1'), false);
|
||||
strictEqual(items.has('some/foo1/path'), false);
|
||||
strictEqual(items.get('foo2'), 'bar');
|
||||
strictEqual(items.get('some/foo2/path'), 'some/bar/path');
|
||||
strictEqual(items.get('foo3'), 'bar');
|
||||
strictEqual(items.get('some/foo3/path'), 'some/bar/path');
|
||||
|
||||
await storage.close();
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('lots of INSERT & DELETE (below inline max)', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db'));
|
||||
|
||||
const items = new Map<string, string>();
|
||||
@ -784,23 +704,17 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.updateItems({ insert: items });
|
||||
|
||||
let storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size);
|
||||
strictEqual(storedItems.size, items.size);
|
||||
|
||||
await storage.updateItems({ delete: keys });
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await storage.close();
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
|
||||
test('lots of INSERT & DELETE (above inline max)', async () => {
|
||||
const storageDir = uniqueStorageDir();
|
||||
|
||||
await mkdirp(storageDir);
|
||||
|
||||
const storage = new SQLiteStorageDatabase(join(storageDir, 'storage.db'));
|
||||
|
||||
const items = new Map<string, string>();
|
||||
@ -816,15 +730,13 @@ suite('SQLite Storage Library', function () {
|
||||
await storage.updateItems({ insert: items });
|
||||
|
||||
let storedItems = await storage.getItems();
|
||||
equal(storedItems.size, items.size);
|
||||
strictEqual(storedItems.size, items.size);
|
||||
|
||||
await storage.updateItems({ delete: keys });
|
||||
|
||||
storedItems = await storage.getItems();
|
||||
equal(storedItems.size, 0);
|
||||
strictEqual(storedItems.size, 0);
|
||||
|
||||
await storage.close();
|
||||
|
||||
await rimraf(storageDir, RimRafMode.MOVE);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user