Skip to content

Commit 9995748

Browse files
committed
add tests and fix missing cases
1 parent 776c042 commit 9995748

8 files changed

+362
-16
lines changed

lib/diagnostics_channel.js

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ function tracingChannelFrom(nameOrChannels, name) {
281281
}
282282

283283
class TracingChannel {
284-
#generatorChannel
284+
#nextChannel
285285

286286
constructor(nameOrChannels) {
287287
for (let i = 0; i < traceEvents.length; ++i) {
@@ -436,10 +436,10 @@ class TracingChannel {
436436
return ReflectApply(fn, thisArg, args);
437437
}
438438

439-
const { channel, start, end, asyncStart, asyncEnd, error } = this;
439+
const { start, end, asyncStart, asyncEnd, error } = this;
440440

441441
if (!this.#nextChannel) {
442-
this.#nextChannel = this.tracingChannel({
442+
this.#nextChannel = tracingChannel({
443443
start: channel(start.name.slice(0, -6) + ':next:start'),
444444
end: channel(end.name.slice(0, -4) + ':next:end'),
445445
asyncStart: channel(asyncStart.name.slice(0, -11) + ':next:asyncStart'),
@@ -448,22 +448,26 @@ class TracingChannel {
448448
});
449449
}
450450

451-
const iter = this.#traceMaybePromise(fn, context, thisArg, ...args);
451+
const nextChannel = this.#nextChannel;
452+
453+
const wrapIter = (iter) => {
454+
const { next: iterNext, return: iterReturn, throw: iterThrow } = iter;
452455

453-
return iter instanceof Promise
454-
? iter.then((iter, method) => {
455-
const { next: iterNext, return: iterReturn, throw: iterThrow } = iter;
456+
iter.next = (...args) =>
457+
nextChannel.#traceMaybePromise(iterNext, context, iter, ...args);
458+
iter.return = (...args) =>
459+
nextChannel.#traceMaybePromise(iterReturn, context, iter, ...args);
460+
iter.throw = (...args) =>
461+
nextChannel.#traceMaybePromise(iterThrow, context, iter, ...args);
462+
463+
return iter;
464+
};
456465

457-
iter.next = (...args) =>
458-
this.#nextChannel.#traceMaybePromise(iterNext, ctx, iter, ...args);
459-
iter.return = (...args) =>
460-
this.#nextChannel.#traceMaybePromise(iterReturn, ctx, iter, ...args);
461-
iter.throw = (...args) =>
462-
this.#nextChannel.#traceMaybePromise(iterThrow, ctx, iter, ...args);
466+
const result = this.#traceMaybePromise(fn, context, thisArg, ...args);
463467

464-
return iter;
465-
})
466-
: iter;
468+
return result instanceof Promise
469+
? PromisePrototypeThen(result, wrapIter)
470+
: wrapIter(result);
467471
}
468472

469473
#traceMaybePromise(fn, context = {}, thisArg, ...args) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
const assert = require('assert');
6+
7+
const channel = dc.tracingChannel('test');
8+
const nextChannel = dc.tracingChannel('test:next');
9+
10+
const expectedResult = { foo: 'bar' };
11+
const input = { foo: 'bar' };
12+
const thisArg = { baz: 'buz' };
13+
14+
function check(found) {
15+
assert.deepStrictEqual(found, input);
16+
}
17+
18+
function checkNextAsync(found) {
19+
check(found);
20+
assert.strictEqual(found.error, undefined);
21+
assert.deepStrictEqual(found.result, { value: expectedResult, done: false });
22+
}
23+
24+
// async function* returns an AsyncGenerator synchronously, so no asyncStart/asyncEnd
25+
// for the fn call itself
26+
const handlers = {
27+
start: common.mustCall(check),
28+
end: common.mustCall(check),
29+
asyncStart: common.mustNotCall(),
30+
asyncEnd: common.mustNotCall(),
31+
error: common.mustNotCall(),
32+
};
33+
34+
// next() on an AsyncGenerator returns a Promise
35+
const nextHandlers = {
36+
start: common.mustCall(check),
37+
end: common.mustCall(check),
38+
asyncStart: common.mustCall(checkNextAsync),
39+
asyncEnd: common.mustCall(checkNextAsync),
40+
error: common.mustNotCall(),
41+
};
42+
43+
channel.subscribe(handlers);
44+
nextChannel.subscribe(nextHandlers);
45+
46+
const iter = channel.traceIterator(common.mustCall(async function*(value) {
47+
assert.deepStrictEqual(this, thisArg);
48+
yield value;
49+
}), input, thisArg, expectedResult);
50+
51+
// next() returns a Promise since iter is an AsyncGenerator
52+
iter.next().then(common.mustCall((result) => {
53+
assert.deepStrictEqual(result, { value: expectedResult, done: false });
54+
}));
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
6+
const channel = dc.tracingChannel('test');
7+
const nextChannel = dc.tracingChannel('test:next');
8+
9+
const handlers = {
10+
start: common.mustNotCall(),
11+
end: common.mustNotCall(),
12+
asyncStart: common.mustNotCall(),
13+
asyncEnd: common.mustNotCall(),
14+
error: common.mustNotCall(),
15+
};
16+
17+
const nextHandlers = {
18+
start: common.mustNotCall(),
19+
end: common.mustNotCall(),
20+
asyncStart: common.mustNotCall(),
21+
asyncEnd: common.mustNotCall(),
22+
error: common.mustNotCall(),
23+
};
24+
25+
// Subscribe after traceIterator call - no events should fire for the iterator
26+
// or for subsequent next() calls since the iterator was not wrapped
27+
const iter = channel.traceIterator(function*() {
28+
yield 1;
29+
}, {});
30+
31+
channel.subscribe(handlers);
32+
nextChannel.subscribe(nextHandlers);
33+
34+
iter.next();
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
const assert = require('assert');
6+
7+
const channel = dc.tracingChannel('test');
8+
const nextChannel = dc.tracingChannel('test:next');
9+
10+
const expectedError = new Error('test');
11+
const input = { foo: 'bar' };
12+
const thisArg = { baz: 'buz' };
13+
14+
function check(found) {
15+
assert.deepStrictEqual(found, input);
16+
}
17+
18+
function checkError(found) {
19+
check(found);
20+
assert.deepStrictEqual(found.error, expectedError);
21+
}
22+
23+
// Two traceIterator calls: one for next() error, one for throw() error
24+
const handlers = {
25+
start: common.mustCall(check, 2),
26+
end: common.mustCall(check, 2),
27+
asyncStart: common.mustNotCall(),
28+
asyncEnd: common.mustNotCall(),
29+
error: common.mustNotCall(),
30+
};
31+
32+
// next() and throw() each produce one error
33+
const nextHandlers = {
34+
start: common.mustCall(check, 2),
35+
end: common.mustCall(check, 2),
36+
asyncStart: common.mustNotCall(),
37+
asyncEnd: common.mustNotCall(),
38+
error: common.mustCall(checkError, 2),
39+
};
40+
41+
channel.subscribe(handlers);
42+
nextChannel.subscribe(nextHandlers);
43+
44+
// Test next(): generator throws on first next() call
45+
const iter1 = channel.traceIterator(common.mustCall(function*() {
46+
assert.deepStrictEqual(this, thisArg);
47+
throw expectedError;
48+
}), input, thisArg);
49+
50+
assert.throws(() => iter1.next(), expectedError);
51+
52+
// Test throw(): propagates error through the iterator
53+
const iter2 = channel.traceIterator(common.mustCall(function*() {
54+
yield 1;
55+
}), input, thisArg);
56+
57+
assert.throws(() => iter2.throw(expectedError), expectedError);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
const assert = require('assert');
6+
7+
const channel = dc.tracingChannel('test');
8+
const nextChannel = dc.tracingChannel('test:next');
9+
10+
const expectedResult = { foo: 'bar' };
11+
const input = { foo: 'bar' };
12+
const thisArg = { baz: 'buz' };
13+
14+
// An async generator that will be returned from the async fn
15+
async function* asyncGen(value) {
16+
yield value;
17+
}
18+
const asyncIter = asyncGen(expectedResult);
19+
20+
function check(found) {
21+
assert.deepStrictEqual(found, input);
22+
}
23+
24+
function checkAsync(found) {
25+
check(found);
26+
assert.strictEqual(found.error, undefined);
27+
assert.strictEqual(found.result, asyncIter);
28+
}
29+
30+
function checkNextAsync(found) {
31+
check(found);
32+
assert.strictEqual(found.error, undefined);
33+
assert.deepStrictEqual(found.result, { value: expectedResult, done: false });
34+
}
35+
36+
// async fn returns a Promise, so main channel fires asyncStart/asyncEnd
37+
const handlers = {
38+
start: common.mustCall(check),
39+
end: common.mustCall(check),
40+
asyncStart: common.mustCall(checkAsync),
41+
asyncEnd: common.mustCall(checkAsync),
42+
error: common.mustNotCall(),
43+
};
44+
45+
// next() on an AsyncGenerator returns a Promise
46+
const nextHandlers = {
47+
start: common.mustCall(check),
48+
end: common.mustCall(check),
49+
asyncStart: common.mustCall(checkNextAsync),
50+
asyncEnd: common.mustCall(checkNextAsync),
51+
error: common.mustNotCall(),
52+
};
53+
54+
channel.subscribe(handlers);
55+
nextChannel.subscribe(nextHandlers);
56+
57+
// fn is async: returns a Promise resolving to an AsyncGenerator
58+
// traceIterator returns a Promise resolving to the wrapped AsyncGenerator
59+
channel.traceIterator(common.mustCall(async function() {
60+
assert.deepStrictEqual(this, thisArg);
61+
return asyncIter;
62+
}), input, thisArg).then(common.mustCall((iter) => {
63+
// next() on AsyncGenerator returns a Promise
64+
iter.next().then(common.mustCall((result) => {
65+
assert.deepStrictEqual(result, { value: expectedResult, done: false });
66+
}));
67+
}));
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const dc = require('diagnostics_channel');
5+
const assert = require('assert');
6+
7+
const channel = dc.tracingChannel('test');
8+
const nextChannel = dc.tracingChannel('test:next');
9+
10+
const expectedResult = { foo: 'bar' };
11+
const input = { foo: 'bar' };
12+
const thisArg = { baz: 'buz' };
13+
14+
// A sync iterator that will be returned from the async fn
15+
const syncIter = [expectedResult][Symbol.iterator]();
16+
17+
function check(found) {
18+
assert.deepStrictEqual(found, input);
19+
}
20+
21+
function checkAsync(found) {
22+
check(found);
23+
assert.strictEqual(found.error, undefined);
24+
assert.strictEqual(found.result, syncIter);
25+
}
26+
27+
// async fn returns a Promise, so main channel fires asyncStart/asyncEnd
28+
const handlers = {
29+
start: common.mustCall(check),
30+
end: common.mustCall(check),
31+
asyncStart: common.mustCall(checkAsync),
32+
asyncEnd: common.mustCall(checkAsync),
33+
error: common.mustNotCall(),
34+
};
35+
36+
// next() on the wrapped sync iterator returns synchronously
37+
const nextHandlers = {
38+
start: common.mustCall(check),
39+
end: common.mustCall(check),
40+
asyncStart: common.mustNotCall(),
41+
asyncEnd: common.mustNotCall(),
42+
error: common.mustNotCall(),
43+
};
44+
45+
channel.subscribe(handlers);
46+
nextChannel.subscribe(nextHandlers);
47+
48+
// fn is async: returns a Promise resolving to a sync iterator
49+
// traceIterator returns a Promise resolving to the wrapped iterator
50+
channel.traceIterator(common.mustCall(async function() {
51+
assert.deepStrictEqual(this, thisArg);
52+
return syncIter;
53+
}), input, thisArg).then(common.mustCall((iter) => {
54+
assert.deepStrictEqual(iter.next(), { value: expectedResult, done: false });
55+
}));
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { AsyncLocalStorage } = require('async_hooks');
5+
const dc = require('diagnostics_channel');
6+
const assert = require('assert');
7+
8+
const channel = dc.tracingChannel('test');
9+
const nextChannel = dc.tracingChannel('test:next');
10+
const store = new AsyncLocalStorage();
11+
12+
const fnContext = { fn: 'context' };
13+
const nextContext = { next: 'context' };
14+
15+
// Bind a store for the fn call (creates the generator object)
16+
channel.start.bindStore(store, common.mustCall(() => fnContext));
17+
18+
// Bind a store for each next() call (runs the generator body)
19+
nextChannel.start.bindStore(store, common.mustCall(() => nextContext));
20+
21+
assert.strictEqual(store.getStore(), undefined);
22+
23+
const iter = channel.traceIterator(common.mustCall(function*() {
24+
// Generator body runs during next(), inside nextChannel.start.runStores
25+
assert.deepStrictEqual(store.getStore(), nextContext);
26+
yield 1;
27+
}), {});
28+
29+
assert.strictEqual(store.getStore(), undefined);
30+
iter.next();
31+
assert.strictEqual(store.getStore(), undefined);

0 commit comments

Comments
 (0)