CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
daemon-logger.test.js289 linesDownload Raw Back to serve
1/**2 * @license3 * Copyright 2025 Qwen Team4 * SPDX-License-Identifier: Apache-2.05 */6import * as os from 'node:os';7import * as path from 'node:path';8import { mkdtempSync, readFileSync, existsSync, writeFileSync, rmSync, realpathSync, lstatSync, } from 'node:fs';9import { describe, it, expect, afterEach, beforeEach } from 'vitest';10import { buildDaemonLogLine, initDaemonLogger } from './daemon-logger.js';11describe('buildDaemonLogLine', () => {12    const FIXED = new Date('2026-05-26T03:14:15.926Z');13    it('formats INFO with no ctx', () => {14        expect(buildDaemonLogLine({15            level: 'INFO',16            message: 'daemon started',17            now: FIXED,18        })).toBe('2026-05-26T03:14:15.926Z [INFO] [DAEMON] daemon started\n');19    });20    it('renders ctx fields in fixed order', () => {21        const line = buildDaemonLogLine({22            level: 'ERROR',23            message: 'route failed',24            now: FIXED,25            ctx: {26                sessionId: 'sess-1',27                route: 'POST /session/:id/prompt',28                clientId: 'client-x',29                childPid: 4242,30                channelId: 'ch-9',31            },32        });33        expect(line).toBe('2026-05-26T03:14:15.926Z [ERROR] [DAEMON] ' +34            'route=POST /session/:id/prompt sessionId=sess-1 clientId=client-x ' +35            'childPid=4242 channelId=ch-9 route failed\n');36    });37    it('appends extra ctx keys sorted lexicographically after fixed keys', () => {38        const line = buildDaemonLogLine({39            level: 'WARN',40            message: 'note',41            now: FIXED,42            ctx: { zeta: 1, alpha: 'a', sessionId: 's' },43        });44        expect(line).toBe('2026-05-26T03:14:15.926Z [WARN] [DAEMON] sessionId=s alpha=a zeta=1 note\n');45    });46    it('JSON.stringify-quotes values that contain spaces or =', () => {47        const line = buildDaemonLogLine({48            level: 'INFO',49            message: 'hi',50            now: FIXED,51            ctx: { weird: 'has space', eq: 'a=b' },52        });53        expect(line).toBe('2026-05-26T03:14:15.926Z [INFO] [DAEMON] eq="a=b" weird="has space" hi\n');54    });55    it('appends error stack as indented continuation lines', () => {56        const err = new Error('boom');57        err.stack =58            'Error: boom\n    at fn (file.ts:1:1)\n    at main (file.ts:2:2)';59        const line = buildDaemonLogLine({60            level: 'ERROR',61            message: 'failed',62            now: FIXED,63            err,64        });65        expect(line).toBe('2026-05-26T03:14:15.926Z [ERROR] [DAEMON] failed\n' +66            '  Error: boom\n' +67            '      at fn (file.ts:1:1)\n' +68            '      at main (file.ts:2:2)\n');69    });70    it('falls back to err.message when stack missing', () => {71        const err = { name: 'Plain', message: 'no stack' };72        const line = buildDaemonLogLine({73            level: 'ERROR',74            message: 'failed',75            now: FIXED,76            err,77        });78        expect(line).toBe('2026-05-26T03:14:15.926Z [ERROR] [DAEMON] failed\n' +79            '  Plain: no stack\n');80    });81});82describe('initDaemonLogger opt-out', () => {83    const originalEnv = process.env['QWEN_DAEMON_LOG_FILE'];84    afterEach(() => {85        if (originalEnv === undefined)86            delete process.env['QWEN_DAEMON_LOG_FILE'];87        else88            process.env['QWEN_DAEMON_LOG_FILE'] = originalEnv;89    });90    for (const val of ['0', 'false', 'off', 'no', 'False', ' OFF ']) {91        it(`returns no-op logger when QWEN_DAEMON_LOG_FILE=${JSON.stringify(val)}`, () => {92            process.env['QWEN_DAEMON_LOG_FILE'] = val;93            const stderr = [];94            const logger = initDaemonLogger({95                boundWorkspace: '/tmp/ws',96                baseDir: '/tmp/nonexistent-should-not-touch',97                stderr: (s) => stderr.push(s),98            });99            logger.info('hello');100            logger.warn('there');101            logger.error('boom');102            logger.raw('raw');103            expect(stderr).toEqual([]); // no-op = nothing104            expect(logger.getLogPath()).toBe('');105            expect(logger.getDaemonId()).toBe('');106        });107    }108});109describe('initDaemonLogger file init', () => {110    let tmp;111    beforeEach(() => {112        tmp = mkdtempSync(path.join(os.tmpdir(), 'daemon-log-'));113    });114    afterEach(() => {115        try {116            rmSync(tmp, { recursive: true, force: true });117        }118        catch {119            // cleanup best-effort120        }121    });122    it('derives daemon-scoped daemon-id and creates log file', () => {123        const logger = initDaemonLogger({124            boundWorkspace: '/workspace/foo',125            pid: 1234,126            baseDir: tmp,127        });128        expect(logger.getDaemonId()).toBe('daemon:1234');129        expect(logger.getLogPath()).toBe(path.join(tmp, 'daemon', 'serve-1234.log'));130        expect(existsSync(logger.getLogPath())).toBe(true);131        expect(readFileSync(logger.getLogPath(), 'utf8')).toMatch(/\[INFO\] \[DAEMON\] workspace=\/workspace\/foo workspaceHash=[0-9a-f]{8} daemon started pid=1234/);132    });133    it('falls back to no-op when mkdir fails', () => {134        const stderr = [];135        // Create a file where the directory should be -> mkdir EEXIST/ENOTDIR136        const blockingFile = path.join(tmp, 'daemon');137        writeFileSync(blockingFile, 'blocker');138        const logger = initDaemonLogger({139            boundWorkspace: '/w',140            pid: 1,141            baseDir: tmp,142            stderr: (s) => stderr.push(s),143        });144        expect(logger.getLogPath()).toBe('');145        expect(stderr.join('\n')).toMatch(/daemon log disabled/);146        expect(() => logger.info('after')).not.toThrow();147    });148});149describe('initDaemonLogger raw', () => {150    let tmp;151    beforeEach(() => {152        tmp = mkdtempSync(path.join(os.tmpdir(), 'daemon-log-'));153    });154    afterEach(() => {155        try {156            rmSync(tmp, { recursive: true, force: true });157        }158        catch {159            // cleanup best-effort160        }161    });162    it('appends prefixed line, no stderr tee', async () => {163        const stderr = [];164        const logger = initDaemonLogger({165            boundWorkspace: '/w',166            pid: 1,167            baseDir: tmp,168            stderr: (s) => stderr.push(s),169        });170        const stderrBefore = stderr.length;171        logger.raw('[serve pid=123 cwd=/x] child crashed', 'warn');172        logger.raw('[serve pid=123 cwd=/x] another');173        await logger.flush();174        const content = readFileSync(logger.getLogPath(), 'utf8');175        expect(content).toContain('[WARN] [DAEMON] [serve pid=123 cwd=/x] child crashed\n');176        expect(content).toContain('[INFO] [DAEMON] [serve pid=123 cwd=/x] another\n');177        // No new stderr lines from raw()178        expect(stderr.length).toBe(stderrBefore);179    });180});181describe('initDaemonLogger info/warn/error', () => {182    let tmp;183    beforeEach(() => {184        tmp = mkdtempSync(path.join(os.tmpdir(), 'daemon-log-'));185    });186    afterEach(() => {187        try {188            rmSync(tmp, { recursive: true, force: true });189        }190        catch {191            // cleanup best-effort192        }193    });194    it('info appends to file and tees to stderr', async () => {195        const stderr = [];196        const fixed = new Date('2026-05-26T03:14:15.926Z');197        const logger = initDaemonLogger({198            boundWorkspace: '/w',199            pid: 1,200            baseDir: tmp,201            stderr: (s) => stderr.push(s),202            now: () => fixed,203        });204        logger.info('hello', { route: 'GET /' });205        await logger.flush();206        const content = readFileSync(logger.getLogPath(), 'utf8');207        expect(content).toContain('[INFO] [DAEMON] route=GET / hello\n');208        // Stderr saw the same line (after boot banner, which isn't teed here).209        const teedLines = stderr.filter((s) => s.includes('[INFO] [DAEMON]'));210        expect(teedLines).toHaveLength(1);211    });212    it('error appends err.stack as continuation', async () => {213        const logger = initDaemonLogger({214            boundWorkspace: '/w',215            pid: 1,216            baseDir: tmp,217        });218        const err = new Error('boom');219        logger.error('route failed', err, { route: 'POST /x' });220        await logger.flush();221        const content = readFileSync(logger.getLogPath(), 'utf8');222        expect(content).toMatch(/\[ERROR\] \[DAEMON\] route=POST \/x route failed\n {2}Error: boom/);223    });224    it('flush awaits all pending appends', async () => {225        const logger = initDaemonLogger({226            boundWorkspace: '/w',227            pid: 1,228            baseDir: tmp,229        });230        for (let i = 0; i < 50; i++)231            logger.info(`msg-${i}`);232        await logger.flush();233        const lines = readFileSync(logger.getLogPath(), 'utf8').split('\n');234        const msgLines = lines.filter((l) => /msg-\d+$/.test(l));235        expect(msgLines).toHaveLength(50);236        for (let i = 0; i < 50; i++) {237            expect(msgLines[i]).toContain(`msg-${i}`);238        }239    });240    it('warns once on append failure and keeps trying', async () => {241        const logger = initDaemonLogger({242            boundWorkspace: '/w',243            pid: 1,244            baseDir: tmp,245            stderr: () => { },246        });247        // Sabotage by removing the parent directory so subsequent appendFile fails with ENOENT.248        rmSync(path.dirname(logger.getLogPath()), { recursive: true, force: true });249        logger.info('after-rm-1');250        logger.info('after-rm-2');251        await logger.flush();252        // No throw — degraded path swallows.253    });254});255describe('initDaemonLogger latest symlink', () => {256    let tmp;257    beforeEach(() => {258        tmp = mkdtempSync(path.join(os.tmpdir(), 'daemon-log-'));259    });260    afterEach(() => {261        try {262            rmSync(tmp, { recursive: true, force: true });263        }264        catch {265            // cleanup best-effort266        }267    });268    it('creates daemon/latest pointing to the current log', async () => {269        const logger = initDaemonLogger({270            boundWorkspace: '/w',271            pid: 42,272            baseDir: tmp,273        });274        // Allow the async symlink to settle.275        await new Promise((r) => setTimeout(r, 50));276        const linkPath = path.join(tmp, 'daemon', 'latest');277        expect(lstatSync(linkPath).isSymbolicLink()).toBe(true);278        expect(realpathSync(linkPath)).toBe(realpathSync(logger.getLogPath()));279    });280    it('updates latest on subsequent init in same dir', async () => {281        const a = initDaemonLogger({ boundWorkspace: '/w', pid: 1, baseDir: tmp });282        await new Promise((r) => setTimeout(r, 50));283        const b = initDaemonLogger({ boundWorkspace: '/w', pid: 2, baseDir: tmp });284        await new Promise((r) => setTimeout(r, 50));285        expect(realpathSync(path.join(tmp, 'daemon', 'latest'))).toBe(realpathSync(b.getLogPath()));286        expect(realpathSync(a.getLogPath())).not.toBe(realpathSync(b.getLogPath()));287    });288});289//# sourceMappingURL=daemon-logger.test.js.map
basant307/AI_Governance_Project · CoolFace