CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
cpuProfiler.test.ts358 linesDownload Raw Back to utils
1/**2 * @license3 * Copyright 2025 Qwen4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as fs from 'node:fs';8import * as os from 'node:os';9import * as path from 'node:path';10 11import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';12 13// Mock cleanup.ts to avoid pulling in @qwen-code/qwen-code-core dependency chain14vi.mock('./cleanup.js', () => ({15  registerCleanup: vi.fn(),16}));17 18import {19  _resetCpuProfilerForTest,20  _setSessionFactoryForTest,21  clearCpuProfileRateLimit,22  isCpuProfileRecording,23  startCpuProfile,24  stopCpuProfile,25} from './cpuProfiler.js';26 27function createMockSession() {28  const mockProfile = {29    nodes: [30      {31        id: 1,32        callFrame: {33          functionName: 'test',34          scriptId: '1',35          url: '',36          lineNumber: 0,37          columnNumber: 0,38        },39        hitCount: 10,40        children: [],41      },42    ],43    startTime: 0,44    endTime: 1000000,45    samples: [1],46    timeDeltas: [100],47  };48 49  const post = vi.fn().mockImplementation((method: string) => {50    if (method === 'Profiler.stop') {51      return Promise.resolve({ profile: mockProfile });52    }53    return Promise.resolve(undefined);54  });55  const connect = vi.fn();56  const disconnect = vi.fn();57 58  return { post, connect, disconnect };59}60 61describe('cpuProfiler', () => {62  let tmpDir: string;63  let mockSession: ReturnType<typeof createMockSession>;64 65  beforeEach(() => {66    _resetCpuProfilerForTest();67    clearCpuProfileRateLimit();68 69    mockSession = createMockSession();70    _setSessionFactoryForTest(async () => mockSession);71 72    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cpu-profiler-test-'));73  });74 75  afterEach(() => {76    _resetCpuProfilerForTest();77    _setSessionFactoryForTest(null);78    vi.restoreAllMocks();79    try {80      fs.rmSync(tmpDir, { recursive: true, force: true });81    } catch {82      // ignore83    }84  });85 86  describe('isCpuProfileRecording', () => {87    it('returns false when not recording', () => {88      expect(isCpuProfileRecording()).toBe(false);89    });90 91    it('returns true when recording', async () => {92      await startCpuProfile();93      expect(isCpuProfileRecording()).toBe(true);94    });95  });96 97  describe('startCpuProfile', () => {98    it('starts profiling successfully', async () => {99      const result = await startCpuProfile();100      expect(result).toEqual({ ok: true });101      expect(mockSession.post).toHaveBeenCalledWith('Profiler.enable');102      expect(mockSession.post).toHaveBeenCalledWith(103        'Profiler.setSamplingInterval',104        { interval: 1000 },105      );106      expect(mockSession.post).toHaveBeenCalledWith('Profiler.start');107    });108 109    it('accepts custom sampling interval', async () => {110      await startCpuProfile({ samplingInterval: 500 });111      expect(mockSession.post).toHaveBeenCalledWith(112        'Profiler.setSamplingInterval',113        { interval: 500 },114      );115    });116 117    it('returns error when already recording', async () => {118      await startCpuProfile();119      const result = await startCpuProfile();120      expect(result).toEqual({121        ok: false,122        error: 'CPU profiling is already in progress.',123      });124    });125 126    it('returns error and resets state on session failure', async () => {127      _setSessionFactoryForTest(async () => {128        throw new Error('Connection refused');129      });130 131      const result = await startCpuProfile();132      expect(result.ok).toBe(false);133      if (!result.ok) {134        expect(result.error).toContain('Connection refused');135      }136      expect(isCpuProfileRecording()).toBe(false);137    });138  });139 140  describe('stopCpuProfile', () => {141    it('stops and writes profile file', async () => {142      await startCpuProfile();143      const result = await stopCpuProfile({ outputDir: tmpDir });144 145      expect(result.ok).toBe(true);146      if (result.ok) {147        expect(result.filePath).toMatch(/qwen-code-cpu-\d+-.*\.cpuprofile$/);148        expect(fs.existsSync(result.filePath)).toBe(true);149 150        const content = JSON.parse(fs.readFileSync(result.filePath, 'utf8'));151        expect(content.nodes).toBeDefined();152        expect(content.startTime).toBeDefined();153      }154    });155 156    it('returns error when not recording', async () => {157      const result = await stopCpuProfile({ outputDir: tmpDir });158      expect(result).toEqual({159        ok: false,160        error: 'CPU profiler is not recording.',161      });162    });163 164    it('calls Profiler.stop and Profiler.disable', async () => {165      await startCpuProfile();166      mockSession.post.mockClear();167      await stopCpuProfile({ outputDir: tmpDir });168 169      expect(mockSession.post).toHaveBeenCalledWith('Profiler.stop');170      expect(mockSession.post).toHaveBeenCalledWith('Profiler.disable');171    });172 173    it('sets file permissions to 0o600', async () => {174      await startCpuProfile();175      const result = await stopCpuProfile({ outputDir: tmpDir });176 177      if (result.ok && process.platform !== 'win32') {178        const stats = fs.statSync(result.filePath);179        expect(stats.mode & 0o777).toBe(0o600);180      }181    });182  });183 184  describe('rate limiting', () => {185    it('enforces rate limit between writes', async () => {186      const now = new Date('2026-05-29T10:00:00.000Z');187 188      await startCpuProfile();189      const first = await stopCpuProfile({ outputDir: tmpDir, now });190      expect(first.ok).toBe(true);191 192      // Second write within rate limit window193      await startCpuProfile();194      const second = await stopCpuProfile({195        outputDir: tmpDir,196        now: new Date(now.getTime() + 5000), // 5s later, within 30s limit197      });198      expect(second.ok).toBe(false);199      if (!second.ok) {200        expect(second.error).toContain('rate limit');201      }202    });203 204    it('allows write after rate limit expires', async () => {205      const now = new Date('2026-05-29T10:00:00.000Z');206 207      await startCpuProfile();208      await stopCpuProfile({ outputDir: tmpDir, now });209 210      // After rate limit window211      await startCpuProfile();212      const result = await stopCpuProfile({213        outputDir: tmpDir,214        now: new Date(now.getTime() + 31000), // 31s later215      });216      expect(result.ok).toBe(true);217    });218 219    it('resets state to idle when rate-limited so user can retry', async () => {220      const now = new Date('2026-05-29T10:00:00.000Z');221 222      await startCpuProfile();223      await stopCpuProfile({ outputDir: tmpDir, now });224 225      // Start a new recording, then try to stop within rate limit window226      await startCpuProfile();227      const rateLimited = await stopCpuProfile({228        outputDir: tmpDir,229        now: new Date(now.getTime() + 5000),230      });231      expect(rateLimited.ok).toBe(false);232 233      // State should be reset — a new startCpuProfile() must succeed234      const restart = await startCpuProfile();235      expect(restart.ok).toBe(true);236    });237  });238 239  describe('old profile cleanup', () => {240    it('removes old profiles beyond max count', async () => {241      // Create 5 existing profiles242      for (let i = 0; i < 5; i++) {243        const name = `qwen-code-cpu-99999-2026-05-29T0${i}-00-00-000Z.cpuprofile`;244        fs.writeFileSync(path.join(tmpDir, name), '{}');245        // Stagger mtime so sort is deterministic246        const mtime = new Date(Date.now() - (5 - i) * 1000);247        fs.utimesSync(path.join(tmpDir, name), mtime, mtime);248      }249 250      clearCpuProfileRateLimit();251      await startCpuProfile();252      const result = await stopCpuProfile({253        outputDir: tmpDir,254        maxProfiles: 5,255      });256      expect(result.ok).toBe(true);257 258      const files = fs259        .readdirSync(tmpDir)260        .filter((f) => f.endsWith('.cpuprofile'));261      // Should have at most 5 files (new one replaces oldest)262      expect(files.length).toBeLessThanOrEqual(5);263    });264  });265 266  describe('conflict handling', () => {267    it('rejects second start while recording', async () => {268      const first = await startCpuProfile();269      expect(first.ok).toBe(true);270 271      const second = await startCpuProfile();272      expect(second.ok).toBe(false);273      if (!second.ok) {274        expect(second.error).toContain('already in progress');275      }276    });277 278    it('resets state after stop so new recording can start', async () => {279      await startCpuProfile();280      await stopCpuProfile({ outputDir: tmpDir });281 282      clearCpuProfileRateLimit();283      const result = await startCpuProfile();284      expect(result.ok).toBe(true);285    });286  });287 288  describe('initCpuProfiler', () => {289    it('is idempotent — calling twice does not error', async () => {290      const { initCpuProfiler } = await import('./cpuProfiler.js');291      // First call292      initCpuProfiler();293      // Second call should be a no-op294      initCpuProfiler();295      // No error thrown means success296    });297 298    it('does not start recording when env var is unset', async () => {299      _resetCpuProfilerForTest();300      delete process.env['QWEN_CODE_CPU_PROFILE'];301      const { initCpuProfiler } = await import('./cpuProfiler.js');302      _resetCpuProfilerForTest();303      initCpuProfiler();304      expect(isCpuProfileRecording()).toBe(false);305    });306  });307 308  describe('SIGUSR1 toggle (via start/stop cycle)', () => {309    it('simulates signal toggle: start then stop', async () => {310      // Simulate what handleSigusr1 does internally311      expect(isCpuProfileRecording()).toBe(false);312 313      // First signal: start314      const startResult = await startCpuProfile();315      expect(startResult.ok).toBe(true);316      expect(isCpuProfileRecording()).toBe(true);317 318      // Second signal: stop319      const stopResult = await stopCpuProfile({ outputDir: tmpDir });320      expect(stopResult.ok).toBe(true);321      expect(isCpuProfileRecording()).toBe(false);322    });323 324    it('ignores stop when in idle state', async () => {325      const result = await stopCpuProfile({ outputDir: tmpDir });326      expect(result.ok).toBe(false);327      if (!result.ok) {328        expect(result.error).toContain('not recording');329      }330    });331  });332 333  describe('empty profile guard', () => {334    it('returns error when V8 returns empty profile', async () => {335      _resetCpuProfilerForTest();336      const emptyMock = {337        post: vi.fn().mockImplementation((method: string) => {338          if (method === 'Profiler.stop') {339            return Promise.resolve({ profile: undefined });340          }341          return Promise.resolve(undefined);342        }),343        connect: vi.fn(),344        disconnect: vi.fn(),345      };346      _setSessionFactoryForTest(async () => emptyMock);347 348      await startCpuProfile();349      const result = await stopCpuProfile({ outputDir: tmpDir });350 351      expect(result.ok).toBe(false);352      if (!result.ok) {353        expect(result.error).toContain('empty profile');354      }355    });356  });357});358 
basant307/AI_Governance_Project · CoolFace