/** * Unit tests for SystemSpecs + HeuristicModelMemoryEstimator. * * Strategy: * - HeuristicModelMemoryEstimator is pure — directly drive it with model ids. * - NodeSystemSpecsProvider depends on `os.*` so we test: * a) caching (same instance returned twice), * b) shape (all required fields present, sane numbers). * We don't pin platform-specific values since CI hardware varies. */ import { NodeSystemSpecsProvider, HeuristicModelMemoryEstimator, } from '../src/system/specs'; describe('HeuristicModelMemoryEstimator', () => { const est = new HeuristicModelMemoryEstimator(); test('extracts parameter count from "7B" suffix', () => { // 7B q4 default: 7 * 0.6 + 1 = 5.2 expect(est.estimate('llama-3.2-7b-q4_K_M')).toBeCloseTo(5.2, 1); }); test('extracts parameter count from "70B"', () => { // 70B q4 default: 70 * 0.6 + 1 = 43 expect(est.estimate('llama-3-70b-instruct-q4_0')).toBeCloseTo(43, 0); }); test('q8 quantization uses higher byte/param', () => { // 7B q8: 7 * 1.0 + 1 = 8 expect(est.estimate('mistral-7b-q8_0')).toBeCloseTo(8, 1); }); test('fp16 uses 2 bytes/param', () => { // 7B fp16: 7 * 2.0 + 1 = 15 expect(est.estimate('mistral-7b-fp16')).toBeCloseTo(15, 1); }); test('q5 sits between q4 and q6', () => { const q4 = est.estimate('foo-7b-q4'); const q5 = est.estimate('foo-7b-q5'); const q6 = est.estimate('foo-7b-q6'); expect(q4).toBeLessThan(q5); expect(q5).toBeLessThan(q6); }); test('falls back to 7B when parameter count is absent', () => { // unknown size → 7B q4 default → 5.2 expect(est.estimate('some-model-no-size')).toBeCloseTo(5.2, 1); }); test('decimal parameter counts like "3.8b"', () => { // 3.8B q4: 3.8 * 0.6 + 1 = 3.28 expect(est.estimate('phi-3.8b-q4')).toBeCloseTo(3.28, 1); }); test('handles empty / undefined input gracefully', () => { expect(est.estimate('')).toBeCloseTo(5.2, 1); // defaults expect(est.estimate(undefined as any)).toBeCloseTo(5.2, 1); }); }); describe('NodeSystemSpecsProvider', () => { test('returns the same cached object on repeated calls', () => { const provider = new NodeSystemSpecsProvider(); const a = provider.get(); const b = provider.get(); expect(a).toBe(b); }); test('produces a sane shape', () => { const specs = new NodeSystemSpecsProvider().get(); expect(specs.totalRamGB).toBeGreaterThan(0); expect(specs.cpuCount).toBeGreaterThanOrEqual(1); expect(specs.platform).toMatch(/^(darwin|linux|win32|freebsd|openbsd|sunos|aix)$/); expect(specs.arch.length).toBeGreaterThan(0); expect(typeof specs.isAppleSilicon).toBe('boolean'); expect(specs.safeModelBudgetGB).toBeGreaterThanOrEqual(2); expect(specs.safeModelBudgetGB).toBeLessThanOrEqual(specs.totalRamGB); expect(specs.summary).toMatch(/RAM/); }); test('safe budget is at most ~65% of total RAM (Apple Silicon ceiling)', () => { const specs = new NodeSystemSpecsProvider().get(); // Even on Apple Silicon (most generous ratio) the budget is capped at // 0.65 of total. Use 0.7 as a soft upper bound for any platform. expect(specs.safeModelBudgetGB).toBeLessThanOrEqual(specs.totalRamGB * 0.7 + 1); }); });