From 3af538cc6bfe42d6a85b5c905a897aea1ab3194f Mon Sep 17 00:00:00 2001 From: "JiaLi.Passion" Date: Thu, 24 May 2018 13:12:35 +0900 Subject: [PATCH] fix(node): node patched method should copy original delegate's symbol properties --- .travis.yml | 2 +- lib/common/utils.ts | 33 +++++++++++++++++++++++ lib/node/node.ts | 9 ++----- lib/node/node_util.ts | 16 +++++++++++ package.json | 2 +- test/node/fs.spec.ts | 59 ++++++++++++++++++++++++++++++++++++++++- test/node/timer.spec.ts | 30 +++++++++++++++++++++ test/node_tests.ts | 1 + yarn.lock | 6 ++--- 9 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 lib/node/node_util.ts create mode 100644 test/node/timer.spec.ts diff --git a/.travis.yml b/.travis.yml index 3ec8c6596..802a256f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ sudo: false dist: trusty cache: yarn node_js: - - 6 + - 8 env: global: - BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 958bd863d..e027cae4e 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -334,6 +334,36 @@ export function patchClass(className: string) { } } +export function copySymbolProperties(src: any, dest: any) { + if (typeof (Object as any).getOwnPropertySymbols !== 'function') { + return; + } + const symbols: any = (Object as any).getOwnPropertySymbols(src); + symbols.forEach((symbol: any) => { + const desc = Object.getOwnPropertyDescriptor(src, symbol); + Object.defineProperty(dest, symbol, { + get: function () { + return src[symbol]; + }, + set: function (value: any) { + if (desc && (!desc.writable || typeof desc.set !== 'function')) { + // if src[symbol] is not writable or not have a setter, just return + return; + } + src[symbol] = value; + }, + enumerable: desc ? desc.enumerable : true, + configurable: desc ? desc.configurable : true + }); + }); +} + +let shouldCopySymbolProperties = false; + +export function setShouldCopySymbolProperties(flag: boolean) { + shouldCopySymbolProperties = flag; +} + export function patchMethod( target: any, name: string, patchFn: (delegate: Function, delegateName: string, name: string) => (self: any, args: any[]) => @@ -360,6 +390,9 @@ export function patchMethod( return patchDelegate(this, arguments as any); }; attachOriginToPatched(proto[name], delegate); + if (shouldCopySymbolProperties) { + copySymbolProperties(delegate, proto[name]); + } } } return delegate; diff --git a/lib/node/node.ts b/lib/node/node.ts index b4ab2f9e6..9c6679d89 100644 --- a/lib/node/node.ts +++ b/lib/node/node.ts @@ -6,22 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ +import './node_util'; import './events'; import './fs'; import {findEventTasks} from '../common/events'; import {patchTimer} from '../common/timers'; -import {ArraySlice, bindArguments, isMix, patchMacroTask, patchMethod, patchMicroTask, patchOnProperties} from '../common/utils'; +import {ArraySlice, isMix, patchMacroTask, patchMicroTask} from '../common/utils'; const set = 'set'; const clear = 'clear'; -Zone.__load_patch('node_util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { - api.patchOnProperties = patchOnProperties; - api.patchMethod = patchMethod; - api.bindArguments = bindArguments; -}); - Zone.__load_patch('node_timers', (global: any, Zone: ZoneType) => { // Timers let globalUseTimeoutFromTimer = false; diff --git a/lib/node/node_util.ts b/lib/node/node_util.ts new file mode 100644 index 000000000..d37aa4aa0 --- /dev/null +++ b/lib/node/node_util.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {setShouldCopySymbolProperties, patchOnProperties, patchMethod, bindArguments} from '../common/utils'; + +Zone.__load_patch('node_util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { + api.patchOnProperties = patchOnProperties; + api.patchMethod = patchMethod; + api.bindArguments = bindArguments; + setShouldCopySymbolProperties(true); +}); diff --git a/package.json b/package.json index b93ffc01b..173d439be 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "dependencies": {}, "devDependencies": { "@types/jasmine": "2.2.33", - "@types/node": "^6.0.96", + "@types/node": "^8.10.17", "@types/systemjs": "^0.19.30", "assert": "^1.4.1", "bluebird": "^3.5.1", diff --git a/test/node/fs.spec.ts b/test/node/fs.spec.ts index e52d317ed..97d994c17 100644 --- a/test/node/fs.spec.ts +++ b/test/node/fs.spec.ts @@ -6,7 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -import {exists, unlink, unwatchFile, watch, watchFile, writeFile} from 'fs'; +import {exists, read, unlink, unwatchFile, watch, write, watchFile, writeFile, openSync, fstatSync, closeSync, unlinkSync} from 'fs'; +import * as util from 'util'; describe('nodejs file system', () => { describe('async method patch test', () => { @@ -88,4 +89,60 @@ describe('nodejs file system', () => { }); }); }); +}); + +describe('util.promisify', () => { + it('fs.exists should work with util.promisify', (done: DoneFn) => { + const promisifyExists = util.promisify(exists); + promisifyExists(__filename).then(r => { + expect(r).toBe(true); + done(); + }, err => { + fail(`should not be here with error: ${err}`); + }); + }); + + it('fs.read should work with util.promisify', (done: DoneFn) => { + const promisifyRead = util.promisify(read); + const fd = openSync(__filename, 'r'); + const stats = fstatSync(fd); + const bufferSize = stats.size; + const chunkSize = 512; + const buffer = new Buffer(bufferSize); + let bytesRead = 0; + // fd, buffer, offset, length, position, callback + promisifyRead(fd, buffer, bytesRead, chunkSize, bytesRead).then( + (value) => { + expect(value.bytesRead).toBe(chunkSize); + closeSync(fd); + done(); + }, err => { + closeSync(fd); + fail(`should not be here with error: ${error}.`); + }); + }); + + it('fs.write should work with util.promisify', (done: DoneFn) => { + const promisifyWrite = util.promisify(write); + const dest = __filename + 'write'; + const fd = openSync(dest, 'a'); + const stats = fstatSync(fd); + const chunkSize = 512; + const buffer = new Buffer(chunkSize); + for (let i = 0; i < chunkSize; i++) { + buffer[i] = 0; + } + // fd, buffer, offset, length, position, callback + promisifyWrite(fd, buffer, 0, chunkSize, 0).then( + (value) => { + expect(value.bytesWritten).toBe(chunkSize); + closeSync(fd); + unlinkSync(dest); + done(); + }, err => { + closeSync(fd); + unlinkSync(dest); + fail(`should not be here with error: ${error}.`); + }); + }); }); \ No newline at end of file diff --git a/test/node/timer.spec.ts b/test/node/timer.spec.ts new file mode 100644 index 000000000..082c0af32 --- /dev/null +++ b/test/node/timer.spec.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { promisify } from 'util'; + +describe('node timer', () => { + it('util.promisify should work with setTimeout', (done: DoneFn) => { + const setTimeoutPromise = promisify(setTimeout); + setTimeoutPromise(50, 'value').then(value => { + expect(value).toEqual('value'); + done(); + }, error => { + fail(`should not be here with error: ${error}.`); + }); + }); + + it('util.promisify should work with setImmediate', (done: DoneFn) => { + const setImmediatePromise = promisify(setImmediate); + setImmediatePromise('value').then(value => { + expect(value).toEqual('value'); + done(); + }, error => { + fail(`should not be here with error: ${error}.`); + }); + }); +}); \ No newline at end of file diff --git a/test/node_tests.ts b/test/node_tests.ts index 0fcc9b6aa..1366ac573 100644 --- a/test/node_tests.ts +++ b/test/node_tests.ts @@ -13,3 +13,4 @@ import './node/Error.spec'; import './node/crypto.spec'; import './node/http.spec'; import './node/console.spec'; +import './node/timer.spec'; diff --git a/yarn.lock b/yarn.lock index a26216695..cb72d9def 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6,9 +6,9 @@ version "2.2.33" resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.2.33.tgz#4715cfd2ca7fbd632fc7f1784f13e637bed028c5" -"@types/node@^6.0.96": - version "6.0.101" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.101.tgz#0c5911cfb434af4a51c0a499931fe6423207d921" +"@types/node@^8.10.17": + version "8.10.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.17.tgz#d48cf10f0dc6dcf59f827f5a3fc7a4a6004318d3" "@types/systemjs@^0.19.30": version "0.19.33"