From e73eb742088e4a6fc5dabd3e6f67e4e6d6034099 Mon Sep 17 00:00:00 2001 From: Asher Date: Thu, 4 Apr 2019 18:24:21 -0500 Subject: [PATCH] Fix sending dates through the protocol Fixes #253. --- packages/protocol/src/browser/modules/fs.ts | 16 +- packages/protocol/src/common/util.ts | 7 + packages/protocol/src/node/modules/fs.ts | 8 +- packages/protocol/src/node/server.ts | 1 + packages/protocol/src/proto/node.proto | 5 + packages/protocol/src/proto/node_pb.d.ts | 27 +++ packages/protocol/src/proto/node_pb.js | 201 ++++++++++++++++++- packages/protocol/test/child_process.test.ts | 5 + packages/protocol/test/fs.test.ts | 19 +- 9 files changed, 267 insertions(+), 22 deletions(-) diff --git a/packages/protocol/src/browser/modules/fs.ts b/packages/protocol/src/browser/modules/fs.ts index a42c8ef09..8984120c0 100644 --- a/packages/protocol/src/browser/modules/fs.ts +++ b/packages/protocol/src/browser/modules/fs.ts @@ -317,17 +317,7 @@ export class FsModule { } class Stats implements fs.Stats { - public readonly atime: Date; - public readonly mtime: Date; - public readonly ctime: Date; - public readonly birthtime: Date; - - public constructor(private readonly stats: IStats) { - this.atime = new Date(stats.atime); - this.mtime = new Date(stats.mtime); - this.ctime = new Date(stats.ctime); - this.birthtime = new Date(stats.birthtime); - } + public constructor(private readonly stats: IStats) {} public get dev(): number { return this.stats.dev; } public get ino(): number { return this.stats.ino; } @@ -339,6 +329,10 @@ class Stats implements fs.Stats { public get size(): number { return this.stats.size; } public get blksize(): number { return this.stats.blksize; } public get blocks(): number { return this.stats.blocks; } + public get atime(): Date { return this.stats.atime; } + public get mtime(): Date { return this.stats.mtime; } + public get ctime(): Date { return this.stats.ctime; } + public get birthtime(): Date { return this.stats.birthtime; } public get atimeMs(): number { return this.stats.atimeMs; } public get mtimeMs(): number { return this.stats.mtimeMs; } public get ctimeMs(): number { return this.stats.ctimeMs; } diff --git a/packages/protocol/src/common/util.ts b/packages/protocol/src/common/util.ts index e49835a91..35d3221ad 100644 --- a/packages/protocol/src/common/util.ts +++ b/packages/protocol/src/common/util.ts @@ -65,6 +65,11 @@ export const argumentToProto = ( const arg = new Argument.ProxyValue(); arg.setId(storeProxy(currentValue)); message.setProxy(arg); + } else if (currentValue instanceof Date + || (currentValue && typeof currentValue.getTime === "function")) { + const arg = new Argument.DateValue(); + arg.setDate(currentValue.toString()); + message.setDate(arg); } else if (currentValue !== null && typeof currentValue === "object") { const arg = new Argument.ObjectValue(); const map = arg.getDataMap(); @@ -136,6 +141,8 @@ export const protoToArgument = ( } return createProxy(currentMessage.getProxy()!.getId()); + case Argument.MsgCase.DATE: + return new Date(currentMessage.getDate()!.getDate()); case Argument.MsgCase.OBJECT: const obj: { [Key: string]: any } = {}; currentMessage.getObject()!.getDataMap().forEach((argument, key) => { diff --git a/packages/protocol/src/node/modules/fs.ts b/packages/protocol/src/node/modules/fs.ts index b18182e9b..b59a6728e 100644 --- a/packages/protocol/src/node/modules/fs.ts +++ b/packages/protocol/src/node/modules/fs.ts @@ -24,10 +24,10 @@ export interface Stats { mtimeMs: number; ctimeMs: number; birthtimeMs: number; - atime: Date | string; - mtime: Date | string; - ctime: Date | string; - birthtime: Date | string; + atime: Date; + mtime: Date; + ctime: Date; + birthtime: Date; _isFile: boolean; _isDirectory: boolean; _isBlockDevice: boolean; diff --git a/packages/protocol/src/node/server.ts b/packages/protocol/src/node/server.ts index 0f24cd58d..b0952ad28 100644 --- a/packages/protocol/src/node/server.ts +++ b/packages/protocol/src/node/server.ts @@ -317,6 +317,7 @@ export class Server { logger.trace(() => [ "sending reject", field("id", id) , + field("message", error.message), ]); const failedMessage = new Method.Fail(); diff --git a/packages/protocol/src/proto/node.proto b/packages/protocol/src/proto/node.proto index 43c006542..2ad9b33d2 100644 --- a/packages/protocol/src/proto/node.proto +++ b/packages/protocol/src/proto/node.proto @@ -40,6 +40,10 @@ message Argument { message UndefinedValue {} + message DateValue { + string date = 1; + } + oneof msg { ErrorValue error = 1; BufferValue buffer = 2; @@ -52,6 +56,7 @@ message Argument { double number = 9; string string = 10; bool boolean = 11; + DateValue date = 12; } } diff --git a/packages/protocol/src/proto/node_pb.d.ts b/packages/protocol/src/proto/node_pb.d.ts index b70d87f71..28bd568ec 100644 --- a/packages/protocol/src/proto/node_pb.d.ts +++ b/packages/protocol/src/proto/node_pb.d.ts @@ -59,6 +59,11 @@ export class Argument extends jspb.Message { getBoolean(): boolean; setBoolean(value: boolean): void; + hasDate(): boolean; + clearDate(): void; + getDate(): Argument.DateValue | undefined; + setDate(value?: Argument.DateValue): void; + getMsgCase(): Argument.MsgCase; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Argument.AsObject; @@ -83,6 +88,7 @@ export namespace Argument { number: number, string: string, pb_boolean: boolean, + date?: Argument.DateValue.AsObject, } export class ErrorValue extends jspb.Message { @@ -248,6 +254,26 @@ export namespace Argument { } } + export class DateValue extends jspb.Message { + getDate(): string; + setDate(value: string): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): DateValue.AsObject; + static toObject(includeInstance: boolean, msg: DateValue): DateValue.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: DateValue, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): DateValue; + static deserializeBinaryFromReader(message: DateValue, reader: jspb.BinaryReader): DateValue; + } + + export namespace DateValue { + export type AsObject = { + date: string, + } + } + export enum MsgCase { MSG_NOT_SET = 0, ERROR = 1, @@ -261,6 +287,7 @@ export namespace Argument { NUMBER = 9, STRING = 10, BOOLEAN = 11, + DATE = 12, } } diff --git a/packages/protocol/src/proto/node_pb.js b/packages/protocol/src/proto/node_pb.js index c452afd3e..c3eed692b 100644 --- a/packages/protocol/src/proto/node_pb.js +++ b/packages/protocol/src/proto/node_pb.js @@ -14,6 +14,7 @@ var global = Function('return this')(); goog.exportSymbol('proto.Argument', null, global); goog.exportSymbol('proto.Argument.ArrayValue', null, global); goog.exportSymbol('proto.Argument.BufferValue', null, global); +goog.exportSymbol('proto.Argument.DateValue', null, global); goog.exportSymbol('proto.Argument.ErrorValue', null, global); goog.exportSymbol('proto.Argument.FunctionValue', null, global); goog.exportSymbol('proto.Argument.NullValue', null, global); @@ -223,6 +224,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.Argument.UndefinedValue.displayName = 'proto.Argument.UndefinedValue'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.Argument.DateValue = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.Argument.DateValue, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.Argument.DateValue.displayName = 'proto.Argument.DateValue'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -505,7 +527,7 @@ if (goog.DEBUG && !COMPILED) { * @private {!Array>} * @const */ -proto.Argument.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10,11]]; +proto.Argument.oneofGroups_ = [[1,2,3,4,5,6,7,8,9,10,11,12]]; /** * @enum {number} @@ -522,7 +544,8 @@ proto.Argument.MsgCase = { UNDEFINED: 8, NUMBER: 9, STRING: 10, - BOOLEAN: 11 + BOOLEAN: 11, + DATE: 12 }; /** @@ -571,7 +594,8 @@ proto.Argument.toObject = function(includeInstance, msg) { undefined: (f = msg.getUndefined()) && proto.Argument.UndefinedValue.toObject(includeInstance, f), number: +jspb.Message.getFieldWithDefault(msg, 9, 0.0), string: jspb.Message.getFieldWithDefault(msg, 10, ""), - pb_boolean: jspb.Message.getFieldWithDefault(msg, 11, false) + pb_boolean: jspb.Message.getFieldWithDefault(msg, 11, false), + date: (f = msg.getDate()) && proto.Argument.DateValue.toObject(includeInstance, f) }; if (includeInstance) { @@ -660,6 +684,11 @@ proto.Argument.deserializeBinaryFromReader = function(msg, reader) { var value = /** @type {boolean} */ (reader.readBool()); msg.setBoolean(value); break; + case 12: + var value = new proto.Argument.DateValue; + reader.readMessage(value,proto.Argument.DateValue.deserializeBinaryFromReader); + msg.setDate(value); + break; default: reader.skipField(); break; @@ -774,6 +803,14 @@ proto.Argument.serializeBinaryToWriter = function(message, writer) { f ); } + f = message.getDate(); + if (f != null) { + writer.writeMessage( + 12, + f, + proto.Argument.DateValue.serializeBinaryToWriter + ); + } }; @@ -1837,6 +1874,131 @@ proto.Argument.UndefinedValue.serializeBinaryToWriter = function(message, writer }; + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto suitable for use in Soy templates. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. + * @param {boolean=} opt_includeInstance Whether to include the JSPB instance + * for transitional soy proto support: http://goto/soy-param-migration + * @return {!Object} + */ +proto.Argument.DateValue.prototype.toObject = function(opt_includeInstance) { + return proto.Argument.DateValue.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Whether to include the JSPB + * instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.Argument.DateValue} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.Argument.DateValue.toObject = function(includeInstance, msg) { + var obj = { + date: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.Argument.DateValue} + */ +proto.Argument.DateValue.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.Argument.DateValue; + return proto.Argument.DateValue.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.Argument.DateValue} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.Argument.DateValue} + */ +proto.Argument.DateValue.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setDate(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.Argument.DateValue.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.Argument.DateValue.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.Argument.DateValue} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.Argument.DateValue.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getDate(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string date = 1; + * @return {string} + */ +proto.Argument.DateValue.prototype.getDate = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** @param {string} value */ +proto.Argument.DateValue.prototype.setDate = function(value) { + jspb.Message.setProto3StringField(this, 1, value); +}; + + /** * optional ErrorValue error = 1; * @return {?proto.Argument.ErrorValue} @@ -2199,6 +2361,39 @@ proto.Argument.prototype.hasBoolean = function() { }; +/** + * optional DateValue date = 12; + * @return {?proto.Argument.DateValue} + */ +proto.Argument.prototype.getDate = function() { + return /** @type{?proto.Argument.DateValue} */ ( + jspb.Message.getWrapperField(this, proto.Argument.DateValue, 12)); +}; + + +/** @param {?proto.Argument.DateValue|undefined} value */ +proto.Argument.prototype.setDate = function(value) { + jspb.Message.setOneofWrapperField(this, 12, proto.Argument.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + */ +proto.Argument.prototype.clearDate = function() { + this.setDate(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.Argument.prototype.hasDate = function() { + return jspb.Message.getField(this, 12) != null; +}; + + /** * Oneof group definitions for this message. Each group defines the field diff --git a/packages/protocol/test/child_process.test.ts b/packages/protocol/test/child_process.test.ts index 54e8a9e20..d49d38774 100644 --- a/packages/protocol/test/child_process.test.ts +++ b/packages/protocol/test/child_process.test.ts @@ -53,6 +53,11 @@ describe("child_process", () => { await expect(getStdout(proc)).resolves.toContain("hi=donkey\n"); }); + + it("should eval", async () => { + const proc = cp.spawn("node", ["-e", "console.log('foo')"]); + await expect(getStdout(proc)).resolves.toContain("foo"); + }); }); describe("fork", () => { diff --git a/packages/protocol/test/fs.test.ts b/packages/protocol/test/fs.test.ts index f009ef4bc..d3c29e313 100644 --- a/packages/protocol/test/fs.test.ts +++ b/packages/protocol/test/fs.test.ts @@ -4,6 +4,8 @@ import * as util from "util"; import { Module } from "../src/common/proxy"; import { createClient, Helper } from "./helpers"; +// tslint:disable deprecation to use fs.exists + describe("fs", () => { const client = createClient(); // tslint:disable-next-line no-any @@ -242,6 +244,14 @@ describe("fs", () => { await util.promisify(nativeFs.close)(fd); }); + it("should futimes existing file with date", async () => { + const file = await helper.createTmpFile(); + const fd = await util.promisify(nativeFs.open)(file, "w"); + await expect(util.promisify(fs.futimes)(fd, new Date(), new Date())) + .resolves.toBeUndefined(); + await util.promisify(nativeFs.close)(fd); + }); + it("should fail to futimes nonexistent file", async () => { await expect(util.promisify(fs.futimes)(99999, 9999, 9999)) .rejects.toThrow("EBADF"); @@ -346,7 +356,7 @@ describe("fs", () => { it("should read existing file", async () => { const fd = await util.promisify(nativeFs.open)(__filename, "r"); const stat = await util.promisify(nativeFs.fstat)(fd); - const buffer = new Buffer(stat.size); + const buffer = Buffer.alloc(stat.size); let bytesRead = 0; let chunkSize = 2048; while (bytesRead < stat.size) { @@ -364,7 +374,7 @@ describe("fs", () => { }); it("should fail to read nonexistent file", async () => { - await expect(util.promisify(fs.read)(99999, new Buffer(10), 9999, 999, 999)) + await expect(util.promisify(fs.read)(99999, Buffer.alloc(10), 9999, 999, 999)) .rejects.toThrow("EBADF"); }); }); @@ -466,6 +476,7 @@ describe("fs", () => { expect(stat).toMatchObject({ size: nativeStat.size, }); + expect(typeof stat.mtime.getTime()).toBe("number"); expect(stat.isFile()).toBe(true); }); @@ -493,7 +504,7 @@ describe("fs", () => { const destination = helper.tmpFile(); await expect(util.promisify(fs.symlink)(source, destination)) .resolves.toBeUndefined(); - expect(util.promisify(nativeFs.exists)(source)) + await expect(util.promisify(nativeFs.exists)(source)) .resolves.toBe(true); }); @@ -525,7 +536,7 @@ describe("fs", () => { const file = await helper.createTmpFile(); await expect(util.promisify(fs.unlink)(file)) .resolves.toBeUndefined(); - expect(util.promisify(nativeFs.exists)(file)) + await expect(util.promisify(nativeFs.exists)(file)) .resolves.toBe(false); });