/
githubmirror
/
meteor
Обзор
Документация
Войти
/
githubmirror
/
meteor
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
devel
packages/webapp/socket_file_tests.js
250 строк
7 KB
Italo José
Release 3.5.1 ⌛ (#14555)
06 авг 2026, 02:08
Не верифицирован
06 авг 2026, 02:08
efef960
Код
Авторство
О чём код?
import { writeFileSync, unlinkSync, statSync, readFileSync } from 'fs'; import { createServer } from 'net'; import { createServer as createServerHttp } from 'http'; import { removeExistingSocketFile, registerSocketFileCleanup, } from './socket_file.js'; import { EventEmitter } from 'events'; import { tmpdir, userInfo, platform } from 'os'; import { main, getGroupInfo } from './webapp_server'; import express from 'express'; const testSocketFile = `${tmpdir()}/socket_file_tests`; const getChownInfo = async (filePath) => { try { const stats = await statSync(filePath); return { uid: stats.uid, gid: stats.gid }; } catch (error) { console.error(`Error fetching ownership info for ${filePath}:`, error.message); return null; } }; const isMacOS = () => { return platform() === 'darwin'; }; const getGroupNameForGid = (gid) => { try { const data = readFileSync('/etc/group', 'utf8'); const line = data .trim() .split('\n') .find((groupLine) => { const [, , groupGid] = groupLine.trim().split(':'); return Number(groupGid) === gid; }); if (!line) return null; const [name] = line.trim().split(':'); return name || null; } catch { return null; } }; const getWritableGroupName = () => { const { gid, uid } = userInfo(); const gidsToTry = new Set(); if (typeof gid === 'number') { gidsToTry.add(gid); } if (typeof process.getgroups === 'function') { process.getgroups().forEach((groupId) => gidsToTry.add(groupId)); } if (typeof process.getgroups === 'function') { process.getgroups().forEach((groupId) => gidsToTry.add(groupId)); } for (const groupId of gidsToTry) { const groupName = getGroupNameForGid(groupId); if (groupName) { return groupName; } } if (Boolean(process.env.TRAVIS)) return 'travis'; if (isMacOS()) return 'staff'; return uid === 0 ? 'root' : null; }; const removeTestSocketFile = (path = testSocketFile) => { try { unlinkSync(path); } catch (error) { // Do nothing } } Tinytest.add("socket file - don't remove a non-socket file", test => { writeFileSync(testSocketFile, ""); test.throws( () => { removeExistingSocketFile(testSocketFile); }, /An existing file was found/ ); removeTestSocketFile() }); Tinytest.addAsync( 'socket file - remove a previously existing socket file', (test, done) => { removeTestSocketFile(); const server = createServer(); server.listen(testSocketFile); server.on('listening', Meteor.bindEnvironment(() => { test.isNotUndefined(statSync(testSocketFile)); removeExistingSocketFile(testSocketFile); test.throws( () => { statSync(testSocketFile); }, /ENOENT/ ); server.close(); done(); })); } ); Tinytest.add( 'socket file - no existing socket file, nothing to remove', test => { removeTestSocketFile(); removeExistingSocketFile(testSocketFile); } ); Tinytest.add('socket file - remove socket file on exit', test => { const testEventEmitter = new EventEmitter(); registerSocketFileCleanup(testSocketFile, testEventEmitter); ['exit', 'SIGINT', 'SIGHUP', 'SIGTERM'].forEach(signal => { writeFileSync(testSocketFile, ""); test.isNotUndefined(statSync(testSocketFile)); testEventEmitter.emit(signal); test.throws( () => { statSync(testSocketFile); }, /ENOENT/ ); }); }); Tinytest.add('socket file - no duplicate handlers on repeated registration', test => { const testEventEmitter = new EventEmitter(); registerSocketFileCleanup(testSocketFile, testEventEmitter); registerSocketFileCleanup(testSocketFile, testEventEmitter); ['exit', 'SIGINT', 'SIGHUP', 'SIGTERM'].forEach(signal => { test.equal(testEventEmitter.listenerCount(signal), 1); }); }); Tinytest.add( 'socket file - latest path wins on repeated registration', test => { const testSocketFile2 = `${testSocketFile}_v2`; const testEventEmitter = new EventEmitter(); registerSocketFileCleanup(testSocketFile, testEventEmitter); registerSocketFileCleanup(testSocketFile2, testEventEmitter); try { ['exit', 'SIGINT', 'SIGHUP', 'SIGTERM'].forEach(signal => { test.equal(testEventEmitter.listenerCount(signal), 1); }); writeFileSync(testSocketFile, ""); writeFileSync(testSocketFile2, ""); testEventEmitter.emit('exit'); // Original path was replaced; only the latest path should be cleaned up. test.isNotUndefined(statSync(testSocketFile)); test.throws(() => { statSync(testSocketFile2); }, /ENOENT/); } finally { removeTestSocketFile(testSocketFile); removeTestSocketFile(testSocketFile2); } } ); function prepareServer() { removeTestSocketFile(); removeExistingSocketFile(testSocketFile); const testEventEmitter = new EventEmitter(); registerSocketFileCleanup(testSocketFile, testEventEmitter); const server = createServer(); server.listen(testSocketFile); const app = express(); const httpServer = createServerHttp(app); return { httpServer, server }; } function closeServer({ httpServer, server }) { return new Promise((resolve) => { httpServer.on( "listening", Meteor.bindEnvironment(() => { process.env.UNIX_SOCKET_PATH = ""; process.env.UNIX_SOCKET_GROUP = ""; removeExistingSocketFile(testSocketFile); server.close(); httpServer.close(); resolve(); }) ); }); } testAsyncMulti( "socket usage - use socket file for inter-process communication", [ async (test) => { // use UNIX_SOCKET_PATH const { httpServer, server } = prepareServer(); process.env.UNIX_SOCKET_PATH = testSocketFile; const result = await main({ httpServer }); const currentGid = userInfo({ encoding: "utf8" })?.gid; test.equal((await getChownInfo(testSocketFile))?.gid, currentGid); return closeServer({ httpServer, server }); }, async (test) => { const isLinux = platform() === 'linux'; const isTravis = Boolean(process.env.TRAVIS); if (isLinux && !isTravis) { /* * Local Linux developers usually run Meteor as an unprivileged user. * Changing the socket file's group to "root" would require elevated * permissions, so we skip this assertion outside CI to avoid forcing * sudo usage. The behavior is still verified on macOS and in CI. */ test.ok(); return; } // use UNIX_SOCKET_PATH and UNIX_SOCKET_GROUP const groupToUse = getWritableGroupName(); if (!groupToUse) { // Skip when no writable group could be determined for the current user. test.isTrue(true); return; } const { httpServer, server } = prepareServer(); process.env.UNIX_SOCKET_PATH = testSocketFile; process.env.UNIX_SOCKET_GROUP = groupToUse; process.env.UNIX_SOCKET_PERMISSIONS = '777'; const result = await main({ httpServer }); test.equal(result, "DAEMON"); test.equal((await getChownInfo(testSocketFile))?.gid, getGroupInfo(process.env.UNIX_SOCKET_GROUP)?.gid); return closeServer({ httpServer, server }); }, ] );