test: refactor notification tests on the basis of promises rather than polling
This commit is contained in:
parent
db50893fa1
commit
21a64db140
|
@ -20,7 +20,6 @@ import {
|
||||||
Message,
|
Message,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import invariant from '../src/util/assert';
|
import invariant from '../src/util/assert';
|
||||||
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
|
|
||||||
import {MOCK_PORT, url} from './url';
|
import {MOCK_PORT, url} from './url';
|
||||||
import {
|
import {
|
||||||
AccountInfo,
|
AccountInfo,
|
||||||
|
@ -29,8 +28,10 @@ import {
|
||||||
BlockSignatures,
|
BlockSignatures,
|
||||||
Commitment,
|
Commitment,
|
||||||
ConfirmedBlock,
|
ConfirmedBlock,
|
||||||
|
Context,
|
||||||
EpochInfo,
|
EpochInfo,
|
||||||
InflationGovernor,
|
InflationGovernor,
|
||||||
|
Logs,
|
||||||
SlotInfo,
|
SlotInfo,
|
||||||
} from '../src/connection';
|
} from '../src/connection';
|
||||||
import {sleep} from '../src/util/sleep';
|
import {sleep} from '../src/util/sleep';
|
||||||
|
@ -3551,50 +3552,84 @@ describe('Connection', function () {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('account change notification', async () => {
|
describe('given an open websocket connection', () => {
|
||||||
if (mockServer) {
|
beforeEach(async () => {
|
||||||
console.log('non-live test skipped');
|
// Open the socket connection and wait for it to become pingable.
|
||||||
return;
|
connection._rpcWebSocket.connect();
|
||||||
}
|
// eslint-disable-next-line no-constant-condition
|
||||||
const connection = new Connection(url, 'confirmed');
|
while (true) {
|
||||||
const owner = Keypair.generate();
|
try {
|
||||||
|
await connection._rpcWebSocket.notify('ping');
|
||||||
let subscriptionId;
|
break;
|
||||||
try {
|
// eslint-disable-next-line no-empty
|
||||||
const notificationPromise = new Promise<AccountInfo<Buffer>>(
|
} catch {}
|
||||||
resolve => {
|
await sleep(100);
|
||||||
subscriptionId = connection.onAccountChange(
|
|
||||||
owner.publicKey,
|
|
||||||
resolve,
|
|
||||||
'confirmed',
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
connection.requestAirdrop(owner.publicKey, LAMPORTS_PER_SOL);
|
|
||||||
const notificationPayload = await notificationPromise;
|
|
||||||
expect(notificationPayload.lamports).to.eq(LAMPORTS_PER_SOL);
|
|
||||||
expect(notificationPayload.owner.equals(SystemProgram.programId)).to.be
|
|
||||||
.true;
|
|
||||||
} finally {
|
|
||||||
if (subscriptionId != null) {
|
|
||||||
connection.removeAccountChangeListener(subscriptionId);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
|
|
||||||
it('program account change notification', async () => {
|
it('account change notification', async () => {
|
||||||
connection._commitment = 'confirmed';
|
const connection = new Connection(url, 'confirmed');
|
||||||
|
const owner = Keypair.generate();
|
||||||
|
|
||||||
const owner = Keypair.generate();
|
let subscriptionId: number | undefined;
|
||||||
const programAccount = Keypair.generate();
|
try {
|
||||||
const balanceNeeded = await connection.getMinimumBalanceForRentExemption(
|
const accountInfoPromise = new Promise<AccountInfo<Buffer>>(
|
||||||
0,
|
resolve => {
|
||||||
);
|
subscriptionId = connection.onAccountChange(
|
||||||
|
owner.publicKey,
|
||||||
|
resolve,
|
||||||
|
'confirmed',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
connection.requestAirdrop(owner.publicKey, LAMPORTS_PER_SOL);
|
||||||
|
const accountInfo = await accountInfoPromise;
|
||||||
|
expect(accountInfo.lamports).to.eq(LAMPORTS_PER_SOL);
|
||||||
|
expect(accountInfo.owner.equals(SystemProgram.programId)).to.be.true;
|
||||||
|
} finally {
|
||||||
|
if (subscriptionId != null) {
|
||||||
|
await connection.removeAccountChangeListener(subscriptionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let notified = false;
|
it('program account change notification', async () => {
|
||||||
const subscriptionId = connection.onProgramAccountChange(
|
connection._commitment = 'confirmed';
|
||||||
SystemProgram.programId,
|
|
||||||
(keyedAccountInfo: KeyedAccountInfo) => {
|
const owner = Keypair.generate();
|
||||||
|
const programAccount = Keypair.generate();
|
||||||
|
const balanceNeeded =
|
||||||
|
await connection.getMinimumBalanceForRentExemption(0);
|
||||||
|
|
||||||
|
let subscriptionId: number | undefined;
|
||||||
|
try {
|
||||||
|
const keyedAccountInfoPromise = new Promise<KeyedAccountInfo>(
|
||||||
|
resolve => {
|
||||||
|
subscriptionId = connection.onProgramAccountChange(
|
||||||
|
SystemProgram.programId,
|
||||||
|
resolve,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
await helpers.airdrop({
|
||||||
|
connection,
|
||||||
|
address: owner.publicKey,
|
||||||
|
amount: LAMPORTS_PER_SOL,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
SystemProgram.transfer({
|
||||||
|
fromPubkey: owner.publicKey,
|
||||||
|
toPubkey: programAccount.publicKey,
|
||||||
|
lamports: balanceNeeded,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await sendAndConfirmTransaction(connection, transaction, [owner], {
|
||||||
|
commitment: 'confirmed',
|
||||||
|
});
|
||||||
|
|
||||||
|
const keyedAccountInfo = await keyedAccountInfoPromise;
|
||||||
if (keyedAccountInfo.accountId.equals(programAccount.publicKey)) {
|
if (keyedAccountInfo.accountId.equals(programAccount.publicKey)) {
|
||||||
expect(keyedAccountInfo.accountInfo.lamports).to.eq(balanceNeeded);
|
expect(keyedAccountInfo.accountInfo.lamports).to.eq(balanceNeeded);
|
||||||
expect(
|
expect(
|
||||||
|
@ -3602,129 +3637,89 @@ describe('Connection', function () {
|
||||||
SystemProgram.programId,
|
SystemProgram.programId,
|
||||||
),
|
),
|
||||||
).to.be.true;
|
).to.be.true;
|
||||||
notified = true;
|
|
||||||
}
|
}
|
||||||
},
|
} finally {
|
||||||
);
|
if (subscriptionId != null) {
|
||||||
|
await connection.removeProgramAccountChangeListener(subscriptionId);
|
||||||
await helpers.airdrop({
|
}
|
||||||
connection,
|
}
|
||||||
address: owner.publicKey,
|
|
||||||
amount: LAMPORTS_PER_SOL,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
it('slot notification', async () => {
|
||||||
const transaction = new Transaction().add(
|
let subscriptionId: number | undefined;
|
||||||
SystemProgram.transfer({
|
try {
|
||||||
fromPubkey: owner.publicKey,
|
const notifiedSlotInfo = await new Promise<SlotInfo>(resolve => {
|
||||||
toPubkey: programAccount.publicKey,
|
subscriptionId = connection.onSlotChange(resolve);
|
||||||
lamports: balanceNeeded,
|
});
|
||||||
}),
|
expect(notifiedSlotInfo.parent).to.be.at.least(0);
|
||||||
);
|
expect(notifiedSlotInfo.root).to.be.at.least(0);
|
||||||
await sendAndConfirmTransaction(connection, transaction, [owner], {
|
expect(notifiedSlotInfo.slot).to.be.at.least(1);
|
||||||
commitment: 'confirmed',
|
} finally {
|
||||||
});
|
if (subscriptionId != null) {
|
||||||
} catch (err) {
|
await connection.removeSlotChangeListener(subscriptionId);
|
||||||
await connection.removeProgramAccountChangeListener(subscriptionId);
|
}
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = 0;
|
|
||||||
while (!notified) {
|
|
||||||
if (++i === 30) {
|
|
||||||
throw new Error('Program change notification not observed');
|
|
||||||
}
|
}
|
||||||
// Sleep for a 1/4 of a slot, notifications only occur after a block is
|
|
||||||
// processed
|
|
||||||
await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND);
|
|
||||||
}
|
|
||||||
|
|
||||||
await connection.removeProgramAccountChangeListener(subscriptionId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('slot notification', async () => {
|
|
||||||
let notifiedSlotInfo: SlotInfo | undefined;
|
|
||||||
const subscriptionId = connection.onSlotChange(slotInfo => {
|
|
||||||
notifiedSlotInfo = slotInfo;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for notification
|
it('root notification', async () => {
|
||||||
let i = 0;
|
let subscriptionId: number | undefined;
|
||||||
while (!notifiedSlotInfo) {
|
try {
|
||||||
if (++i === 30) {
|
const atLeastTwoRoots = await new Promise<number[]>(resolve => {
|
||||||
throw new Error('Slot change notification not observed');
|
const roots: number[] = [];
|
||||||
|
subscriptionId = connection.onRootChange(root => {
|
||||||
|
if (roots.length === 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
roots.push(root);
|
||||||
|
if (roots.length === 2) {
|
||||||
|
// Collect at least two, then resolve.
|
||||||
|
resolve(roots);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
expect(atLeastTwoRoots[1]).to.be.greaterThan(atLeastTwoRoots[0]);
|
||||||
|
} finally {
|
||||||
|
if (subscriptionId != null) {
|
||||||
|
await connection.removeRootChangeListener(subscriptionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Sleep for a 1/4 of a slot, notifications only occur after a block is
|
|
||||||
// processed
|
|
||||||
await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(notifiedSlotInfo.parent).to.be.at.least(0);
|
|
||||||
expect(notifiedSlotInfo.root).to.be.at.least(0);
|
|
||||||
expect(notifiedSlotInfo.slot).to.be.at.least(1);
|
|
||||||
|
|
||||||
await connection.removeSlotChangeListener(subscriptionId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('root notification', async () => {
|
|
||||||
let roots: number[] = [];
|
|
||||||
const subscriptionId = connection.onRootChange(root => {
|
|
||||||
roots.push(root);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for mockCallback to receive a call
|
it('logs notification', async () => {
|
||||||
let i = 0;
|
let subscriptionId: number | undefined;
|
||||||
while (roots.length < 2) {
|
const owner = Keypair.generate();
|
||||||
if (++i === 30) {
|
try {
|
||||||
throw new Error('Root change notification not observed');
|
const logPromise = new Promise<[Logs, Context]>(resolve => {
|
||||||
}
|
subscriptionId = connection.onLogs(
|
||||||
// Sleep for a 1/4 of a slot, notifications only occur after a block is
|
|
||||||
// processed
|
|
||||||
await sleep((250 * DEFAULT_TICKS_PER_SLOT) / NUM_TICKS_PER_SECOND);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(roots[1]).to.be.greaterThan(roots[0]);
|
|
||||||
await connection.removeRootChangeListener(subscriptionId);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('logs notification', async () => {
|
|
||||||
let listener: number | undefined;
|
|
||||||
const owner = Keypair.generate();
|
|
||||||
const [logsRes, ctx] = await new Promise(resolve => {
|
|
||||||
let received = false;
|
|
||||||
listener = connection.onLogs(
|
|
||||||
owner.publicKey,
|
|
||||||
(logs, ctx) => {
|
|
||||||
if (!logs.err) {
|
|
||||||
received = true;
|
|
||||||
resolve([logs, ctx]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'processed',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send transactions until the log subscription receives an event
|
|
||||||
(async () => {
|
|
||||||
while (!received) {
|
|
||||||
// Execute a transaction so that we can pickup its logs.
|
|
||||||
await connection.requestAirdrop(
|
|
||||||
owner.publicKey,
|
owner.publicKey,
|
||||||
1 * LAMPORTS_PER_SOL,
|
(logs, ctx) => {
|
||||||
|
if (!logs.err) {
|
||||||
|
resolve([logs, ctx]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'processed',
|
||||||
);
|
);
|
||||||
await sleep(1000);
|
});
|
||||||
|
|
||||||
|
// Execute a transaction so that we can pickup its logs.
|
||||||
|
await connection.requestAirdrop(owner.publicKey, LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
const [logsRes, ctx] = await logPromise;
|
||||||
|
expect(ctx.slot).to.be.greaterThan(0);
|
||||||
|
expect(logsRes.logs.length).to.eq(2);
|
||||||
|
expect(logsRes.logs[0]).to.eq(
|
||||||
|
'Program 11111111111111111111111111111111 invoke [1]',
|
||||||
|
);
|
||||||
|
expect(logsRes.logs[1]).to.eq(
|
||||||
|
'Program 11111111111111111111111111111111 success',
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
if (subscriptionId != null) {
|
||||||
|
await connection.removeOnLogsListener(subscriptionId);
|
||||||
}
|
}
|
||||||
})();
|
}
|
||||||
});
|
});
|
||||||
expect(ctx.slot).to.be.greaterThan(0);
|
});
|
||||||
expect(logsRes.logs.length).to.eq(2);
|
|
||||||
expect(logsRes.logs[0]).to.eq(
|
|
||||||
'Program 11111111111111111111111111111111 invoke [1]',
|
|
||||||
);
|
|
||||||
expect(logsRes.logs[1]).to.eq(
|
|
||||||
'Program 11111111111111111111111111111111 success',
|
|
||||||
);
|
|
||||||
await connection.removeOnLogsListener(listener!);
|
|
||||||
}).timeout(60 * 1000);
|
|
||||||
|
|
||||||
it('https request', async () => {
|
it('https request', async () => {
|
||||||
const connection = new Connection('https://api.mainnet-beta.solana.com');
|
const connection = new Connection('https://api.mainnet-beta.solana.com');
|
||||||
|
|
Loading…
Reference in New Issue