mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-19 00:57:21 -05:00
ESLint has a whole new config format, so I figure it's a good time to make the config system saner. - First, we no longer have separate eslint-no-types configs. Lint performance shouldn't be enough of a problem to justify the relevant maintenance complexity. - Second, our base config should work out-of-the-box now. `npx eslint` will work as expected, without any CLI flags. You should still use `npm run lint` which adds the `--cached` flag for performance. - Third, whatever updates I did fixed style linting, which apparently has been bugged for quite some time, considering all the obvious mixed-tabs-and-spaces issues I found in the upgrade. Also here are some changes to our style rules. In particular: - Curly brackets (for objects etc) now have spaces inside them. Sorry for the huge change. ESLint doesn't support our old style, and most projects use Prettier style, so we might as well match them in this way. See https://github.com/eslint-stylistic/eslint-stylistic/issues/415 - String + number concatenation is no longer allowed. We now consistently use template strings for this.
440 lines
16 KiB
JavaScript
440 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('../assert');
|
|
const { extractChannelMessages } = require('../../dist/sim/battle');
|
|
|
|
describe('ServerStream', () => {
|
|
const omniscientPlayer = -1;
|
|
const spectatorPlayer = 0;
|
|
|
|
const createSplit = (player, secret, shared) => ([
|
|
`|split|p${player}`,
|
|
secret,
|
|
shared,
|
|
]);
|
|
|
|
describe('extractChannel', () => {
|
|
it('should return the same messages if no privileged messages are within it', () => {
|
|
const messages = [
|
|
'|-start|p1a: Hellfrog|ability: Flash Fire',
|
|
'|-start|p2a: Hellfrog|ability: Flash Fire',
|
|
'|-start|p3a: Hellfrog|ability: Flash Fire',
|
|
'|-start|p4a: Hellfrog|ability: Flash Fire',
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer]);
|
|
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), messages);
|
|
});
|
|
|
|
it('should return all privileged messages for an omniscient player', () => {
|
|
const messages = [
|
|
...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(3, '|-start|p3a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(4, '|-start|p4a: Aerodactyl|ability: Pressure', ''),
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer]);
|
|
|
|
const expectedMessages = [
|
|
'|-start|p1a: Aerodactyl|ability: Pressure',
|
|
'|-start|p2a: Aerodactyl|ability: Pressure',
|
|
'|-start|p3a: Aerodactyl|ability: Pressure',
|
|
'|-start|p4a: Aerodactyl|ability: Pressure',
|
|
].join('\n');
|
|
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedMessages);
|
|
});
|
|
|
|
it('should return player-privileged messages for each player', () => {
|
|
const expectedMessages = [
|
|
...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(3, '|-start|p3a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(4, '|-start|p4a: Aerodactyl|ability: Pressure', ''),
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(expectedMessages, [spectatorPlayer, 1, 2, 3, 4]);
|
|
|
|
for (const player of [1, 2, 3, 4]) {
|
|
const actualPlayerMessages = actualChannelMessages[player].join('\n');
|
|
const expectedPlayerMessages = [
|
|
`|-start|p${player}a: Aerodactyl|ability: Pressure`,
|
|
].join('\n');
|
|
assert.equal(actualPlayerMessages, expectedPlayerMessages);
|
|
}
|
|
|
|
assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), '');
|
|
});
|
|
|
|
it('should return privileged messages with non-privileged messages', () => {
|
|
const messages = [
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''),
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1, 2]);
|
|
|
|
const expectedOmniscientPlayerMessages = [
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
'|-start|p1a: Aerodactyl|ability: Pressure',
|
|
'|-start|p2a: Aerodactyl|ability: Pressure',
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
].join('\n');
|
|
const expectedPlayer1Messages = [
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
'|-start|p1a: Aerodactyl|ability: Pressure',
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
].join('\n');
|
|
const expectedPlayer2Messages = [
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
'|-start|p2a: Aerodactyl|ability: Pressure',
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
].join('\n');
|
|
const expectedSpectatorMessages = [
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
'|-start|p2b: Hellfrog|ability: Flash Fire',
|
|
].join('\n');
|
|
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages);
|
|
assert.equal(actualChannelMessages[1].join('\n'), expectedPlayer1Messages);
|
|
assert.equal(actualChannelMessages[2].join('\n'), expectedPlayer2Messages);
|
|
assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), expectedSpectatorMessages);
|
|
});
|
|
|
|
it('should return consecutive player-privileged messages for a player', () => {
|
|
const messages = [
|
|
...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(1, '|-start|p1b: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(1, '|-start|p1c: Aerodactyl|ability: Pressure', ''),
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1]);
|
|
|
|
const expectedPlayerMessages = [
|
|
'|-start|p1a: Aerodactyl|ability: Pressure',
|
|
'|-start|p1b: Aerodactyl|ability: Pressure',
|
|
'|-start|p1c: Aerodactyl|ability: Pressure',
|
|
].join('\n');
|
|
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedPlayerMessages);
|
|
assert.equal(actualChannelMessages[1].join('\n'), expectedPlayerMessages);
|
|
assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), '');
|
|
});
|
|
|
|
it('should return shared messages for non-privileged players', () => {
|
|
const messages = [
|
|
...createSplit(1, '|-heal|p1a: Rhyperior|420/420', '|-heal|p1a: Rhyperior|100/100'),
|
|
...createSplit(2, '|-heal|p2a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|100/100'),
|
|
...createSplit(3, '|-heal|p3a: Rhyperior|420/420', '|-heal|p3a: Rhyperior|100/100'),
|
|
...createSplit(4, '|-heal|p4a: Rhyperior|420/420', '|-heal|p4a: Rhyperior|100/100'),
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1, 2, 3, 4]);
|
|
|
|
for (const player of [1, 2, 3, 4]) {
|
|
const expectedPlayerMessages = [
|
|
'|-heal|p1a: Rhyperior|100/100',
|
|
'|-heal|p2a: Rhyperior|100/100',
|
|
'|-heal|p3a: Rhyperior|100/100',
|
|
'|-heal|p4a: Rhyperior|100/100',
|
|
];
|
|
expectedPlayerMessages[player - 1] = `|-heal|p${player}a: Rhyperior|420/420`;
|
|
assert.equal(actualChannelMessages[player].join('\n'), expectedPlayerMessages.join('\n'));
|
|
}
|
|
|
|
const expectedOmniscientPlayerMessages = [
|
|
'|-heal|p1a: Rhyperior|420/420',
|
|
'|-heal|p2a: Rhyperior|420/420',
|
|
'|-heal|p3a: Rhyperior|420/420',
|
|
'|-heal|p4a: Rhyperior|420/420',
|
|
].join('\n');
|
|
|
|
const expectedSpectatorMessages = [
|
|
'|-heal|p1a: Rhyperior|100/100',
|
|
'|-heal|p2a: Rhyperior|100/100',
|
|
'|-heal|p3a: Rhyperior|100/100',
|
|
'|-heal|p4a: Rhyperior|100/100',
|
|
].join('\n');
|
|
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages);
|
|
assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), expectedSpectatorMessages);
|
|
});
|
|
|
|
it('should return messages made up of secret and shared messages', () => {
|
|
const messages = [
|
|
...createSplit(1, '|-heal|p1a: Rhyperior|420/420', '|-heal|p1a: Rhyperior|100/100'),
|
|
...createSplit(1, '|-start|p1a: Aerodactyl|ability: Pressure', ''),
|
|
...createSplit(2, '|-heal|p2a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|100/100'),
|
|
...createSplit(2, '|-start|p2a: Aerodactyl|ability: Pressure', ''),
|
|
].join('\n');
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer, spectatorPlayer, 1, 2]);
|
|
|
|
const expectedOmniscientPlayerMessages = [
|
|
'|-heal|p1a: Rhyperior|420/420',
|
|
'|-start|p1a: Aerodactyl|ability: Pressure',
|
|
'|-heal|p2a: Rhyperior|420/420',
|
|
'|-start|p2a: Aerodactyl|ability: Pressure',
|
|
].join('\n');
|
|
const expectedPlayer1Messages = [
|
|
'|-heal|p1a: Rhyperior|420/420',
|
|
'|-start|p1a: Aerodactyl|ability: Pressure',
|
|
'|-heal|p2a: Rhyperior|100/100',
|
|
].join('\n');
|
|
const expectedPlayer2Messages = [
|
|
'|-heal|p1a: Rhyperior|100/100',
|
|
'|-heal|p2a: Rhyperior|420/420',
|
|
'|-start|p2a: Aerodactyl|ability: Pressure',
|
|
].join('\n');
|
|
const expectedSpectatorMessages = [
|
|
'|-heal|p1a: Rhyperior|100/100',
|
|
'|-heal|p2a: Rhyperior|100/100',
|
|
].join('\n');
|
|
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages);
|
|
assert.equal(actualChannelMessages[1].join('\n'), expectedPlayer1Messages);
|
|
assert.equal(actualChannelMessages[2].join('\n'), expectedPlayer2Messages);
|
|
assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), expectedSpectatorMessages);
|
|
});
|
|
|
|
it('should not extract channel messages for unspecified channels', () => {
|
|
const messages = [
|
|
...createSplit(1, '|-heal|p1a: Rhyperior|420/420', '|-heal|p1a: Rhyperior|100/100'),
|
|
...createSplit(2, '|-heal|p2a: Rhyperior|420/420', '|-heal|p2a: Rhyperior|100/100'),
|
|
...createSplit(3, '|-heal|p3a: Rhyperior|420/420', '|-heal|p3a: Rhyperior|100/100'),
|
|
...createSplit(4, '|-heal|p4a: Rhyperior|420/420', '|-heal|p4a: Rhyperior|100/100'),
|
|
].join('\n');
|
|
|
|
const expectedOmniscientPlayerMessages = [
|
|
'|-heal|p1a: Rhyperior|420/420',
|
|
'|-heal|p2a: Rhyperior|420/420',
|
|
'|-heal|p3a: Rhyperior|420/420',
|
|
'|-heal|p4a: Rhyperior|420/420',
|
|
].join('\n');
|
|
|
|
const actualChannelMessages = extractChannelMessages(messages, [omniscientPlayer]);
|
|
assert.equal(actualChannelMessages[omniscientPlayer].join('\n'), expectedOmniscientPlayerMessages);
|
|
assert.equal(actualChannelMessages[1].join('\n'), '');
|
|
assert.equal(actualChannelMessages[2].join('\n'), '');
|
|
assert.equal(actualChannelMessages[3].join('\n'), '');
|
|
assert.equal(actualChannelMessages[4].join('\n'), '');
|
|
assert.equal(actualChannelMessages[spectatorPlayer].join('\n'), '');
|
|
});
|
|
});
|
|
});
|
|
|
|
// These tests have not been updated to reflect the latest version of Sockets
|
|
// Anyone should feel free to try to rewrite them to work
|
|
|
|
/*
|
|
|
|
const assert = require('assert').strict;
|
|
const cluster = require('cluster');
|
|
|
|
describe('Sockets', function () {
|
|
const spawnWorker = () => (
|
|
new Promise(resolve => {
|
|
const worker = Sockets.spawnWorker();
|
|
worker.removeAllListeners('message');
|
|
resolve(worker);
|
|
})
|
|
);
|
|
|
|
before(function () {
|
|
cluster.settings.silent = true;
|
|
cluster.removeAllListeners('disconnect');
|
|
});
|
|
|
|
afterEach(function () {
|
|
for (const [workerid, worker] of Sockets.workers) {
|
|
worker.kill();
|
|
Sockets.workers.delete(workerid);
|
|
}
|
|
});
|
|
|
|
describe('master', function () {
|
|
it('should be able to spawn workers', function () {
|
|
Sockets.spawnWorker();
|
|
assert.equal(Sockets.workers.size, 1);
|
|
});
|
|
|
|
it('should be able to spawn workers on listen', function () {
|
|
Sockets.listen(0, '127.0.0.1', 1);
|
|
assert.equal(Sockets.workers.size, 1);
|
|
});
|
|
|
|
it('should be able to kill workers', function () {
|
|
return spawnWorker().then(worker => {
|
|
Sockets.killWorker(worker);
|
|
assert.equal(Sockets.workers.size, 0);
|
|
});
|
|
});
|
|
|
|
it('should be able to kill workers by PID', function () {
|
|
return spawnWorker().then(worker => {
|
|
Sockets.killPid(worker.process.pid);
|
|
assert.equal(Sockets.workers.size, 0);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('workers', function () {
|
|
// This composes a sequence of HOFs that send a message to a worker,
|
|
// wait for its response, then return the worker for the next function
|
|
// to use.
|
|
const chain = (eventHandler, msg) => worker => {
|
|
worker.once('message', eventHandler(worker));
|
|
msg = msg || `$
|
|
const {Session} = require('sockjs/lib/transport');
|
|
const socket = new Session('aaaaaaaa', server);
|
|
socket.remoteAddress = '127.0.0.1';
|
|
if (!('headers' in socket)) socket.headers = {};
|
|
socket.headers['x-forwarded-for'] = '';
|
|
socket.protocol = 'websocket';
|
|
socket.write = msg => process.send(msg);
|
|
server.emit('connection', socket);`;
|
|
worker.send(msg);
|
|
return worker;
|
|
};
|
|
|
|
const spawnSocket = eventHandler => spawnWorker().then(chain(eventHandler));
|
|
|
|
it('should allow sockets to connect', function () {
|
|
return spawnSocket(worker => data => {
|
|
const cmd = data.charAt(0);
|
|
const [sid, ip, protocol] = data.substr(1).split('\n');
|
|
assert.equal(cmd, '*');
|
|
assert.equal(sid, '1');
|
|
assert.equal(ip, '127.0.0.1');
|
|
assert.equal(protocol, 'websocket');
|
|
});
|
|
});
|
|
|
|
it('should allow sockets to disconnect', function () {
|
|
let querySocket;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
querySocket = `$
|
|
let socket = sockets.get(${sid});
|
|
process.send(!socket);`;
|
|
Sockets.socketDisconnect(worker, sid);
|
|
}).then(chain(worker => data => {
|
|
assert(data);
|
|
}, querySocket));
|
|
});
|
|
|
|
it('should allow sockets to send messages', function () {
|
|
const msg = 'ayy lmao';
|
|
let socketSend;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
socketSend = `>${sid}\n${msg}`;
|
|
}).then(chain(worker => data => {
|
|
assert.equal(data, msg);
|
|
}, socketSend));
|
|
});
|
|
|
|
it('should allow sockets to receive messages', function () {
|
|
let sid;
|
|
let msg;
|
|
let mockReceive;
|
|
return spawnSocket(worker => data => {
|
|
sid = data.substr(1, data.indexOf('\n'));
|
|
msg = '|/cmd rooms';
|
|
mockReceive = `$
|
|
let socket = sockets.get(${sid});
|
|
socket.emit('data', ${msg});`;
|
|
}).then(chain(worker => data => {
|
|
const cmd = data.charAt(0);
|
|
const params = data.substr(1).split('\n');
|
|
assert.equal(cmd, '<');
|
|
assert.equal(sid, params[0]);
|
|
assert.equal(msg, params[1]);
|
|
}, mockReceive));
|
|
});
|
|
|
|
it('should create a room for the first socket to get added to it', function () {
|
|
let queryChannel;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
const cid = 'global';
|
|
queryChannel = `$
|
|
let room = rooms.get(${cid});
|
|
process.send(room && room.has(${sid}));`;
|
|
Sockets.roomAdd(worker, cid, sid);
|
|
}).then(chain(worker => data => {
|
|
assert(data);
|
|
}, queryChannel));
|
|
});
|
|
|
|
it('should remove a room if the last socket gets removed from it', function () {
|
|
let queryChannel;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
const cid = 'global';
|
|
queryChannel = `$
|
|
process.send(!sockets.has(${sid}) && !rooms.has(${cid}));`;
|
|
Sockets.roomAdd(worker, cid, sid);
|
|
Sockets.roomRemove(worker, cid, sid);
|
|
}).then(chain(worker => data => {
|
|
assert(data);
|
|
}, queryChannel));
|
|
});
|
|
|
|
it('should send to all sockets in a room', function () {
|
|
const msg = 'ayy lmao';
|
|
const cid = 'global';
|
|
const roomSend = `#${cid}\n${msg}`;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
Sockets.roomAdd(worker, cid, sid);
|
|
}).then(chain(worker => data => {
|
|
assert.equal(data, msg);
|
|
}, roomSend));
|
|
});
|
|
|
|
it('should create a channel when moving a socket to it', function () {
|
|
let queryChannel;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
const cid = 'battle-ou-1';
|
|
const scid = '1';
|
|
queryChannel = `$
|
|
let channel = roomChannels[${cid}];
|
|
process.send(!!channel && (channel.get(${sid}) === ${scid}));`;
|
|
Sockets.channelMove(worker, cid, scid, sid);
|
|
}).then(chain(worker => data => {
|
|
assert(data);
|
|
}, queryChannel));
|
|
});
|
|
|
|
it('should remove a channel when removing its last socket', function () {
|
|
let queryChannel;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
const cid = 'battle-ou-1';
|
|
const scid = '1';
|
|
queryChannel = `$
|
|
let channel = roomChannels.get(${cid});
|
|
process.send(!!channel && (channel.get(${sid}) === ${scid}));`;
|
|
Sockets.channelMove(worker, cid, scid, sid);
|
|
Sockets.roomRemove(worker, cid, sid);
|
|
}).then(chain(worker => data => {
|
|
assert(data);
|
|
}, queryChannel));
|
|
});
|
|
|
|
it('should send to sockets in a channel', function () {
|
|
const cid = 'battle-ou-1';
|
|
const msg = 'ayy lmao';
|
|
const buf = `.${cid}\n\n|split\n\n${msg}\n\n`;
|
|
return spawnSocket(worker => data => {
|
|
const sid = data.substr(1, data.indexOf('\n'));
|
|
const scid = '1';
|
|
Sockets.channelMove(worker, cid, scid, sid);
|
|
}).then(chain(worker => data => {
|
|
assert.equal(data, msg);
|
|
}, buf));
|
|
});
|
|
});
|
|
});
|
|
|
|
*/
|