Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add destroy method to block tracker classes #106

Merged
merged 51 commits into from
Aug 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
cee7443
Rewrite tests using Jest
mcmire Apr 11, 2022
cb17ef7
ok
mcmire Apr 21, 2022
2da319c
wip
mcmire Apr 22, 2022
074399c
wip - finish fixing SubscribeBlockTracker
mcmire Apr 22, 2022
5518187
Address coverage for PollingBlockTracker
mcmire Apr 22, 2022
d0a133c
Fix subscribe tests and achieve 100% coverage
mcmire Apr 23, 2022
a86b143
Clean some things up
mcmire Apr 23, 2022
0122100
Clean up one more thing
mcmire Apr 23, 2022
6dd5051
And one more
mcmire Apr 23, 2022
130aabe
Shorten some test names
mcmire Apr 23, 2022
a28d74e
Finally get up to 100% coverage
mcmire Apr 25, 2022
aaa6a79
Rename some more tests
mcmire Apr 25, 2022
732bdd7
More test names
mcmire Apr 25, 2022
5229e84
remove explicit config for setupAfterEnv.ts
mcmire Apr 25, 2022
c475715
Ignore coverage for BaseBlockTracker constructor arguments
mcmire Apr 25, 2022
610f4b4
Update .eslintrc.js
mcmire Apr 25, 2022
b8909a4
Remove commented-out thresholds
mcmire Apr 25, 2022
32ea121
note
mcmire Apr 25, 2022
b9a0390
Update tsconfig.build.json
mcmire Apr 25, 2022
d315b12
Copy new tsconfig.json and tsconfig.build.json from the upcoming modu…
mcmire Apr 25, 2022
2f4bb6d
Change the 'build' command to use tsconfig.build.json
mcmire Apr 26, 2022
c93e930
Don't export BaseBlockTracker
mcmire Apr 26, 2022
0ea5a8f
Make BaseBlockTracker an abstract class (only for semantics)
mcmire Apr 26, 2022
db013cd
Also call super in SubscribeBlockTracker's _start
mcmire Apr 26, 2022
b5caa50
Change _start and _end to abstract methods
mcmire Apr 26, 2022
26bbead
Move inlineSources and sourceMap to build file, exclude dist/ by default
mcmire Apr 26, 2022
1139302
Update tsconfig.json to match module template
mcmire Apr 26, 2022
710e45a
Update src/PollingBlockTracker.ts
mcmire Apr 28, 2022
c3e20c9
Respond to more feedback
mcmire Apr 28, 2022
46b50aa
No need to pass originalSetTimeout in
mcmire May 2, 2022
1e0434a
Fix lint errors
mcmire May 2, 2022
d9efdd6
Use ECMA private fields
mcmire May 2, 2022
5d8e7b3
Make this a private method
mcmire May 2, 2022
c127002
Remove the call before calling it
mcmire May 2, 2022
ec4a03e
Fix copypasta w/ methodToAddListener
mcmire May 2, 2022
ff6da12
Add `destroy` method to block tracker classes
mcmire Apr 27, 2022
2f0b9b2
[TEMP] Add dist/ for testing
mcmire May 2, 2022
26be231
Make this test better
mcmire May 3, 2022
797fdc3
Adjust this test name
mcmire May 3, 2022
ab8e7b4
Add misisng 'methodToAddListener'
mcmire May 3, 2022
65594f4
Add missing 'methodToGetLatestBlock'
mcmire May 3, 2022
a3bf8ed
Tweak more test names
mcmire May 3, 2022
57d0b84
thrownError -> thrownString (in cases where it's a string)
mcmire May 3, 2022
d716aee
Merge branch 'main' into convert-to-jest
Gudahtt Aug 5, 2022
c627eae
Revert "[TEMP] Add dist/ for testing"
mcmire Aug 8, 2022
56b75f8
Copy over doc update from other branch
mcmire Aug 8, 2022
34750b4
Merge branch 'convert-to-jest' into add-destroy
mcmire Aug 8, 2022
d678c35
Merge branch 'main' into add-destroy
mcmire Aug 15, 2022
7d956e6
Cancel first, then end
mcmire Aug 15, 2022
591b213
Address the same issues with the unclear test as in the other PR
mcmire Aug 15, 2022
a004c19
Fix linting error
mcmire Aug 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/BaseBlockTracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ export abstract class BaseBlockTracker extends SafeEventEmitter {
this._setupInternalEvents();
}

async destroy() {
this._cancelBlockResetTimeout();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this come before this._maybeEnd()? _maybeEnd() might take some time to resolve for the SubscribeBlockTracker, because of the eth_unsubscribe call.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good call. Fixed in 7d956e6.

await this._maybeEnd();
super.removeAllListeners();
}

isRunning(): boolean {
return this._isRunning;
}
Expand Down
109 changes: 109 additions & 0 deletions src/PollingBlockTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,115 @@ describe('PollingBlockTracker', () => {
});
});

describe('destroy', () => {
it('should stop the block tracker if any "latest" and "sync" events were added previously', async () => {
recordCallsToSetTimeout();

await withPollingBlockTracker(async ({ blockTracker }) => {
blockTracker.on('latest', EMPTY_FUNCTION);
await new Promise<void>((resolve) => {
blockTracker.on('sync', resolve);
});
expect(blockTracker.isRunning()).toBe(true);

await blockTracker.destroy();

expect(blockTracker.isRunning()).toBe(false);
});
});

it('should not start a timer to clear the current block number if called after removing all listeners but before enough time passes that the cache would have been cleared', async () => {
const setTimeoutRecorder = recordCallsToSetTimeout();
const blockTrackerOptions = {
pollingInterval: 100,
blockResetDuration: 200,
};

await withPollingBlockTracker(
{
provider: {
stubs: [
{
methodName: 'eth_blockNumber',
response: {
result: '0x0',
},
},
],
},
blockTracker: blockTrackerOptions,
},
async ({ blockTracker }) => {
blockTracker.on('latest', EMPTY_FUNCTION);
blockTracker.on('sync', EMPTY_FUNCTION);
await new Promise((resolve) => {
blockTracker.on('_waitingForNextIteration', resolve);
});
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x0');
blockTracker.removeAllListeners();
expect(
setTimeoutRecorder.calls.some((call) => {
return call.duration === blockTrackerOptions.blockResetDuration;
}),
).toBe(true);

await blockTracker.destroy();

expect(
setTimeoutRecorder.calls.some((call) => {
return call.duration === blockTrackerOptions.blockResetDuration;
}),
).toBe(false);

await new Promise((resolve) =>
originalSetTimeout(resolve, blockTrackerOptions.blockResetDuration),
);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x0');
},
);
});

it('should only clear the current block number if enough time passes after all "latest" and "sync" events are removed', async () => {
const setTimeoutRecorder = recordCallsToSetTimeout();
const blockTrackerOptions = {
pollingInterval: 100,
blockResetDuration: 200,
};

await withPollingBlockTracker(
{
provider: {
stubs: [
{
methodName: 'eth_blockNumber',
response: {
result: '0x0',
},
},
],
},
blockTracker: blockTrackerOptions,
},
async ({ blockTracker }) => {
blockTracker.on('latest', EMPTY_FUNCTION);
blockTracker.on('sync', EMPTY_FUNCTION);
await new Promise((resolve) => {
blockTracker.on('_waitingForNextIteration', resolve);
});
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x0');
blockTracker.removeAllListeners();
await setTimeoutRecorder.nextMatchingDuration(
blockTrackerOptions.blockResetDuration,
);

await blockTracker.destroy();

expect(blockTracker.getCurrentBlock()).toBeNull();
},
);
});
});

describe('getLatestBlock', () => {
it('should start the block tracker immediately after being called', async () => {
recordCallsToSetTimeout();
Expand Down
91 changes: 91 additions & 0 deletions src/SubscribeBlockTracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const METHODS_TO_GET_LATEST_BLOCK = [
'getLatestBlock',
'checkForLatestBlock',
] as const;
const originalSetTimeout = setTimeout;

describe('SubscribeBlockTracker', () => {
describe('constructor', () => {
Expand All @@ -39,6 +40,96 @@ describe('SubscribeBlockTracker', () => {
});
});

describe('destroy', () => {
it('should stop the block tracker if any "latest" and "sync" events were added previously', async () => {
recordCallsToSetTimeout();

await withSubscribeBlockTracker(async ({ blockTracker }) => {
blockTracker.on('latest', EMPTY_FUNCTION);
await new Promise<void>((resolve) => {
blockTracker.on('sync', resolve);
});
expect(blockTracker.isRunning()).toBe(true);

await blockTracker.destroy();

expect(blockTracker.isRunning()).toBe(false);
});
});

it('should not start a timer to clear the current block number if called after removing all listeners but before enough time passes that the cache would have been cleared', async () => {
const setTimeoutRecorder = recordCallsToSetTimeout();
const blockResetDuration = 500;

await withSubscribeBlockTracker(
{
blockTracker: {
blockResetDuration,
},
provider: {
stubs: [
{
methodName: 'eth_blockNumber',
response: {
result: '0x0',
},
},
],
},
},
async ({ blockTracker }) => {
blockTracker.on('latest', EMPTY_FUNCTION);
await new Promise((resolve) => {
blockTracker.on('sync', resolve);
});
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x0');
blockTracker.removeAllListeners();
expect(setTimeoutRecorder.calls).not.toHaveLength(0);

await blockTracker.destroy();

expect(setTimeoutRecorder.calls).toHaveLength(0);
await new Promise((resolve) =>
originalSetTimeout(resolve, blockResetDuration),
);
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x0');
},
);
});

it('should only clear the current block number if enough time passes after all "latest" and "sync" events are removed', async () => {
const setTimeoutRecorder = recordCallsToSetTimeout();

await withSubscribeBlockTracker(
{
provider: {
stubs: [
{
methodName: 'eth_blockNumber',
response: {
result: '0x0',
},
},
],
},
},
async ({ blockTracker }) => {
blockTracker.on('latest', EMPTY_FUNCTION);
await new Promise((resolve) => {
blockTracker.on('sync', resolve);
});
expect(blockTracker.getCurrentBlock()).toStrictEqual('0x0');
blockTracker.removeAllListeners();
await setTimeoutRecorder.next();

await blockTracker.destroy();

expect(blockTracker.getCurrentBlock()).toBeNull();
},
);
});
});

METHODS_TO_GET_LATEST_BLOCK.forEach((methodToGetLatestBlock) => {
describe(`${methodToGetLatestBlock}`, () => {
it('should start the block tracker immediately after being called', async () => {
Expand Down
23 changes: 23 additions & 0 deletions tests/recordCallsToSetTimeout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,29 @@ class SetTimeoutRecorder {
this.#numAutomaticCallsRemaining = numAutomaticCalls;
}

/**
* Removes the first `setTimeout` call from the call stack and calls it, or
* waits until one appears.
*
* @returns A promise that resolves when the first `setTimeout` call is
* called.
*/
next(): Promise<void> {
return new Promise<void>((resolve) => {
if (this.calls.length > 0) {
const call = this.calls.shift() as SetTimeoutCall;
call.callback();
resolve();
} else {
this.#events.once('setTimeoutAdded', () => {
const call = this.calls.shift() as SetTimeoutCall;
call.callback();
resolve();
});
}
});
}

/**
* Looks for the first `setTimeout` call in the call stack that matches the
* given duration and calls it, removing it from the call stack, or waits
Expand Down