Spaces:
Running
Running
| /** | |
| * Unit tests for LayerStats.compute() | |
| * Validates: Requirements 19.1, 19.2, 19.4, 19.5 | |
| */ | |
| import { describe, it, expect } from 'vitest'; | |
| // Extract the pure compute logic for testing (mirrors LayerStats.compute) | |
| function computeLayerStats(parsedModel) { | |
| const nodes = (parsedModel && parsedModel.graph && parsedModel.graph.nodes) ? parsedModel.graph.nodes : []; | |
| const edges = (parsedModel && parsedModel.graph && parsedModel.graph.edges) ? parsedModel.graph.edges : []; | |
| const initializers = (parsedModel && parsedModel.initializers) ? parsedModel.initializers : []; | |
| const totalNodes = nodes.length; | |
| const totalEdges = edges.length; | |
| const totalInitializers = initializers.length; | |
| const countMap = {}; | |
| nodes.forEach((node) => { | |
| const op = node.opType || 'Unknown'; | |
| countMap[op] = (countMap[op] || 0) + 1; | |
| }); | |
| const opTypeCounts = Object.entries(countMap) | |
| .map(([opType, count]) => ({ | |
| opType, | |
| count, | |
| percentage: totalNodes > 0 ? parseFloat(((count / totalNodes) * 100).toFixed(1)) : 0, | |
| })) | |
| .sort((a, b) => b.count - a.count); | |
| return { opTypeCounts, totalNodes, totalEdges, totalInitializers }; | |
| } | |
| describe('LayerStats.compute', () => { | |
| it('should count operator types correctly', () => { | |
| const model = { | |
| graph: { | |
| nodes: [ | |
| { id: '1', opType: 'Conv' }, | |
| { id: '2', opType: 'ReLU' }, | |
| { id: '3', opType: 'Conv' }, | |
| { id: '4', opType: 'Conv' }, | |
| { id: '5', opType: 'ReLU' }, | |
| ], | |
| edges: [{ source: '1', target: '2' }], | |
| }, | |
| initializers: [{ name: 'w1' }, { name: 'w2' }], | |
| }; | |
| const stats = computeLayerStats(model); | |
| expect(stats.totalNodes).toBe(5); | |
| expect(stats.totalEdges).toBe(1); | |
| expect(stats.totalInitializers).toBe(2); | |
| expect(stats.opTypeCounts).toHaveLength(2); | |
| // Sorted descending by count | |
| expect(stats.opTypeCounts[0]).toEqual({ opType: 'Conv', count: 3, percentage: 60 }); | |
| expect(stats.opTypeCounts[1]).toEqual({ opType: 'ReLU', count: 2, percentage: 40 }); | |
| }); | |
| it('should return sum of counts equal to totalNodes', () => { | |
| const model = { | |
| graph: { | |
| nodes: [ | |
| { id: '1', opType: 'Conv' }, | |
| { id: '2', opType: 'BatchNorm' }, | |
| { id: '3', opType: 'Conv' }, | |
| { id: '4', opType: 'Add' }, | |
| ], | |
| edges: [], | |
| }, | |
| initializers: [], | |
| }; | |
| const stats = computeLayerStats(model); | |
| const sumCounts = stats.opTypeCounts.reduce((s, e) => s + e.count, 0); | |
| expect(sumCounts).toBe(stats.totalNodes); | |
| }); | |
| it('should handle empty model gracefully', () => { | |
| const stats = computeLayerStats({ graph: { nodes: [], edges: [] }, initializers: [] }); | |
| expect(stats.totalNodes).toBe(0); | |
| expect(stats.totalEdges).toBe(0); | |
| expect(stats.totalInitializers).toBe(0); | |
| expect(stats.opTypeCounts).toHaveLength(0); | |
| }); | |
| it('should handle null/undefined model', () => { | |
| const stats = computeLayerStats(null); | |
| expect(stats.totalNodes).toBe(0); | |
| expect(stats.opTypeCounts).toHaveLength(0); | |
| }); | |
| it('should label nodes without opType as Unknown', () => { | |
| const model = { | |
| graph: { nodes: [{ id: '1' }, { id: '2', opType: '' }], edges: [] }, | |
| initializers: [], | |
| }; | |
| const stats = computeLayerStats(model); | |
| expect(stats.opTypeCounts).toHaveLength(1); | |
| expect(stats.opTypeCounts[0].opType).toBe('Unknown'); | |
| expect(stats.opTypeCounts[0].count).toBe(2); | |
| }); | |
| it('should sort by count descending', () => { | |
| const model = { | |
| graph: { | |
| nodes: [ | |
| { id: '1', opType: 'A' }, | |
| { id: '2', opType: 'B' }, | |
| { id: '3', opType: 'B' }, | |
| { id: '4', opType: 'C' }, | |
| { id: '5', opType: 'C' }, | |
| { id: '6', opType: 'C' }, | |
| ], | |
| edges: [], | |
| }, | |
| initializers: [], | |
| }; | |
| const stats = computeLayerStats(model); | |
| expect(stats.opTypeCounts[0].opType).toBe('C'); | |
| expect(stats.opTypeCounts[1].opType).toBe('B'); | |
| expect(stats.opTypeCounts[2].opType).toBe('A'); | |
| }); | |
| it('should compute percentages that sum close to 100', () => { | |
| const model = { | |
| graph: { | |
| nodes: [ | |
| { id: '1', opType: 'Conv' }, | |
| { id: '2', opType: 'Conv' }, | |
| { id: '3', opType: 'ReLU' }, | |
| ], | |
| edges: [], | |
| }, | |
| initializers: [], | |
| }; | |
| const stats = computeLayerStats(model); | |
| const sumPct = stats.opTypeCounts.reduce((s, e) => s + e.percentage, 0); | |
| // Allow small rounding tolerance | |
| expect(Math.abs(sumPct - 100)).toBeLessThan(1); | |
| }); | |
| }); | |