Spaces:
Running
Running
| /** | |
| * Unit tests for TensorShapeInspector | |
| * Validates: Requirements 21.1, 21.2, 21.3, 21.4, 21.5 | |
| */ | |
| import { describe, it, expect, beforeEach } from 'vitest'; | |
| /** | |
| * Extract the pure tensor lookup logic for testing (mirrors TensorShapeInspector._buildTensorInfoMap + lookupTensor). | |
| * @param {Object} parsedModel | |
| * @returns {{ lookup: (name: string) => Object|null, size: number }} | |
| */ | |
| function buildTensorLookup(parsedModel) { | |
| const map = new Map(); | |
| // 1. Inputs | |
| if (Array.isArray(parsedModel.inputs)) { | |
| for (const inp of parsedModel.inputs) { | |
| if (inp.name) { | |
| map.set(inp.name, { | |
| name: inp.name, | |
| shape: inp.shape || [], | |
| dataType: inp.dataType || 'UNKNOWN' | |
| }); | |
| } | |
| } | |
| } | |
| // 2. Outputs | |
| if (Array.isArray(parsedModel.outputs)) { | |
| for (const out of parsedModel.outputs) { | |
| if (out.name) { | |
| map.set(out.name, { | |
| name: out.name, | |
| shape: out.shape || [], | |
| dataType: out.dataType || 'UNKNOWN' | |
| }); | |
| } | |
| } | |
| } | |
| // 3. value_info (intermediate tensors) | |
| const valueInfo = parsedModel.graph && parsedModel.graph.valueInfo; | |
| if (valueInfo && typeof valueInfo === 'object') { | |
| const entries = Object.entries(valueInfo); | |
| for (const [key, vi] of entries) { | |
| if (key && !map.has(key)) { | |
| map.set(key, { | |
| name: vi.name || key, | |
| shape: vi.shape || [], | |
| dataType: vi.dataType || 'UNKNOWN' | |
| }); | |
| } | |
| } | |
| } | |
| return { | |
| lookup: (name) => map.get(name) || null, | |
| size: map.size | |
| }; | |
| } | |
| /** | |
| * Build tooltip text (mirrors TensorShapeInspector._buildTooltipHTML logic, text-only). | |
| */ | |
| function buildTooltipText(tensorName, tensorInfo) { | |
| const displayName = tensorName || 'Unnamed tensor'; | |
| if (tensorInfo) { | |
| const shapeStr = tensorInfo.shape && tensorInfo.shape.length > 0 | |
| ? '[' + tensorInfo.shape.join(', ') + ']' | |
| : 'unknown'; | |
| return { name: displayName, shape: shapeStr, dataType: tensorInfo.dataType }; | |
| } | |
| return { name: displayName, shape: 'unknown', dataType: null }; | |
| } | |
| describe('TensorShapeInspector - Tensor Lookup', () => { | |
| let model; | |
| beforeEach(() => { | |
| model = { | |
| inputs: [ | |
| { name: 'input_0', shape: [1, 3, 224, 224], dataType: 'FLOAT' } | |
| ], | |
| outputs: [ | |
| { name: 'output_0', shape: [1, 1000], dataType: 'FLOAT' } | |
| ], | |
| graph: { | |
| nodes: [], | |
| edges: [], | |
| valueInfo: { | |
| 'conv1_out': { name: 'conv1_out', shape: [1, 64, 112, 112], dataType: 'FLOAT' }, | |
| 'relu1_out': { name: 'relu1_out', shape: [1, 64, 112, 112], dataType: 'FLOAT' } | |
| } | |
| }, | |
| initializers: [] | |
| }; | |
| }); | |
| it('should find input tensors by name', () => { | |
| const { lookup } = buildTensorLookup(model); | |
| const info = lookup('input_0'); | |
| expect(info).not.toBeNull(); | |
| expect(info.name).toBe('input_0'); | |
| expect(info.shape).toEqual([1, 3, 224, 224]); | |
| expect(info.dataType).toBe('FLOAT'); | |
| }); | |
| it('should find output tensors by name', () => { | |
| const { lookup } = buildTensorLookup(model); | |
| const info = lookup('output_0'); | |
| expect(info).not.toBeNull(); | |
| expect(info.shape).toEqual([1, 1000]); | |
| expect(info.dataType).toBe('FLOAT'); | |
| }); | |
| it('should find intermediate tensors from valueInfo', () => { | |
| const { lookup } = buildTensorLookup(model); | |
| const info = lookup('conv1_out'); | |
| expect(info).not.toBeNull(); | |
| expect(info.shape).toEqual([1, 64, 112, 112]); | |
| expect(info.dataType).toBe('FLOAT'); | |
| }); | |
| it('should return null for unknown tensor names', () => { | |
| const { lookup } = buildTensorLookup(model); | |
| expect(lookup('nonexistent_tensor')).toBeNull(); | |
| }); | |
| it('should return null for empty/null name', () => { | |
| const { lookup } = buildTensorLookup(model); | |
| expect(lookup('')).toBeNull(); | |
| expect(lookup(null)).toBeNull(); | |
| expect(lookup(undefined)).toBeNull(); | |
| }); | |
| it('should count all tensors from all sources', () => { | |
| const { size } = buildTensorLookup(model); | |
| // 1 input + 1 output + 2 valueInfo = 4 | |
| expect(size).toBe(4); | |
| }); | |
| it('should handle model with no valueInfo', () => { | |
| model.graph.valueInfo = {}; | |
| const { lookup, size } = buildTensorLookup(model); | |
| expect(size).toBe(2); // only input + output | |
| expect(lookup('conv1_out')).toBeNull(); | |
| }); | |
| it('should handle model with no inputs or outputs', () => { | |
| const emptyModel = { inputs: [], outputs: [], graph: { valueInfo: {} }, initializers: [] }; | |
| const { size } = buildTensorLookup(emptyModel); | |
| expect(size).toBe(0); | |
| }); | |
| it('should prioritize inputs/outputs over valueInfo for same name', () => { | |
| model.graph.valueInfo['input_0'] = { name: 'input_0', shape: [999], dataType: 'INT32' }; | |
| const { lookup } = buildTensorLookup(model); | |
| const info = lookup('input_0'); | |
| // Input should take priority | |
| expect(info.shape).toEqual([1, 3, 224, 224]); | |
| expect(info.dataType).toBe('FLOAT'); | |
| }); | |
| it('should handle tensors with missing shape', () => { | |
| model.graph.valueInfo['no_shape'] = { name: 'no_shape', dataType: 'FLOAT' }; | |
| const { lookup } = buildTensorLookup(model); | |
| const info = lookup('no_shape'); | |
| expect(info).not.toBeNull(); | |
| expect(info.shape).toEqual([]); | |
| }); | |
| it('should handle tensors with missing dataType', () => { | |
| model.graph.valueInfo['no_dtype'] = { name: 'no_dtype', shape: [1, 10] }; | |
| const { lookup } = buildTensorLookup(model); | |
| const info = lookup('no_dtype'); | |
| expect(info.dataType).toBe('UNKNOWN'); | |
| }); | |
| }); | |
| describe('TensorShapeInspector - Tooltip Content', () => { | |
| it('should show shape and type for known tensor', () => { | |
| const tensorInfo = { name: 'conv1_out', shape: [1, 64, 112, 112], dataType: 'FLOAT' }; | |
| const result = buildTooltipText('conv1_out', tensorInfo); | |
| expect(result.name).toBe('conv1_out'); | |
| expect(result.shape).toBe('[1, 64, 112, 112]'); | |
| expect(result.dataType).toBe('FLOAT'); | |
| }); | |
| it('should show "Shape: unknown" when tensor info is null', () => { | |
| const result = buildTooltipText('mystery_tensor', null); | |
| expect(result.name).toBe('mystery_tensor'); | |
| expect(result.shape).toBe('unknown'); | |
| expect(result.dataType).toBeNull(); | |
| }); | |
| it('should show "Shape: unknown" when tensor has empty shape', () => { | |
| const tensorInfo = { name: 'empty', shape: [], dataType: 'FLOAT' }; | |
| const result = buildTooltipText('empty', tensorInfo); | |
| expect(result.shape).toBe('unknown'); | |
| }); | |
| it('should handle dynamic dimensions in shape', () => { | |
| const tensorInfo = { name: 'dynamic', shape: ['batch', 3, 224, 224], dataType: 'FLOAT' }; | |
| const result = buildTooltipText('dynamic', tensorInfo); | |
| expect(result.shape).toBe('[batch, 3, 224, 224]'); | |
| }); | |
| it('should show "Unnamed tensor" when name is empty', () => { | |
| const result = buildTooltipText('', null); | |
| expect(result.name).toBe('Unnamed tensor'); | |
| }); | |
| }); | |