413 lines
14 KiB
TypeScript
413 lines
14 KiB
TypeScript
|
|
/**
|
||
|
|
* Protobuf序列化器边界情况测试
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { Component } from '../../../src/ECS/Component';
|
||
|
|
import { BigIntFactory } from '../../../src/ECS/Utils/BigIntCompatibility';
|
||
|
|
import { ProtobufSerializer } from '../../../src/Utils/Serialization/ProtobufSerializer';
|
||
|
|
import {
|
||
|
|
ProtoSerializable,
|
||
|
|
ProtoFloat,
|
||
|
|
ProtoInt32,
|
||
|
|
ProtoString,
|
||
|
|
ProtoBool,
|
||
|
|
ProtoBytes,
|
||
|
|
ProtoTimestamp,
|
||
|
|
ProtoDouble,
|
||
|
|
ProtoInt64,
|
||
|
|
ProtoStruct
|
||
|
|
} from '../../../src/Utils/Serialization/ProtobufDecorators';
|
||
|
|
|
||
|
|
// 边界测试组件
|
||
|
|
@ProtoSerializable('EdgeCaseComponent')
|
||
|
|
class EdgeCaseComponent extends Component {
|
||
|
|
@ProtoFloat(1)
|
||
|
|
public floatValue: number = 0;
|
||
|
|
|
||
|
|
@ProtoDouble(2)
|
||
|
|
public doubleValue: number = 0;
|
||
|
|
|
||
|
|
@ProtoInt32(3)
|
||
|
|
public intValue: number = 0;
|
||
|
|
|
||
|
|
@ProtoInt64(4)
|
||
|
|
public bigIntValue: any = BigIntFactory.zero();
|
||
|
|
|
||
|
|
@ProtoString(5)
|
||
|
|
public stringValue: string = '';
|
||
|
|
|
||
|
|
@ProtoBool(6)
|
||
|
|
public boolValue: boolean = false;
|
||
|
|
|
||
|
|
@ProtoBytes(7)
|
||
|
|
public bytesValue: Uint8Array = new Uint8Array();
|
||
|
|
|
||
|
|
@ProtoTimestamp(8)
|
||
|
|
public timestampValue: Date = new Date();
|
||
|
|
|
||
|
|
@ProtoStruct(9)
|
||
|
|
public structValue: any = {};
|
||
|
|
|
||
|
|
@ProtoFloat(10, { repeated: true })
|
||
|
|
public arrayValue: number[] = [];
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
super();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 不完整的组件(缺少字段)
|
||
|
|
@ProtoSerializable('IncompleteComponent')
|
||
|
|
class IncompleteComponent extends Component {
|
||
|
|
@ProtoString(1)
|
||
|
|
public name: string = '';
|
||
|
|
|
||
|
|
// 故意添加没有装饰器的字段
|
||
|
|
public undecoratedField: number = 42;
|
||
|
|
|
||
|
|
constructor(name: string = '') {
|
||
|
|
super();
|
||
|
|
this.name = name;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 有循环引用的组件
|
||
|
|
@ProtoSerializable('CircularComponent')
|
||
|
|
class CircularComponent extends Component {
|
||
|
|
@ProtoString(1)
|
||
|
|
public name: string = '';
|
||
|
|
|
||
|
|
@ProtoStruct(2)
|
||
|
|
public circular: any = null;
|
||
|
|
|
||
|
|
constructor(name: string = '') {
|
||
|
|
super();
|
||
|
|
this.name = name;
|
||
|
|
// 创建循环引用
|
||
|
|
this.circular = this;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 没有protobuf装饰器的组件
|
||
|
|
class NonSerializableComponent extends Component {
|
||
|
|
public data: string = 'test';
|
||
|
|
|
||
|
|
serialize(): any {
|
||
|
|
return { data: this.data };
|
||
|
|
}
|
||
|
|
|
||
|
|
deserialize(data: any): void {
|
||
|
|
this.data = data.data || this.data;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mock protobuf.js
|
||
|
|
const mockProtobuf = {
|
||
|
|
parse: jest.fn().mockReturnValue({
|
||
|
|
root: {
|
||
|
|
lookupType: jest.fn().mockImplementation((typeName: string) => {
|
||
|
|
return {
|
||
|
|
verify: jest.fn().mockReturnValue(null),
|
||
|
|
create: jest.fn().mockImplementation((data) => data),
|
||
|
|
encode: jest.fn().mockReturnValue({
|
||
|
|
finish: jest.fn().mockReturnValue(new Uint8Array([1, 2, 3, 4]))
|
||
|
|
}),
|
||
|
|
decode: jest.fn().mockImplementation(() => ({
|
||
|
|
floatValue: 3.14,
|
||
|
|
doubleValue: 2.718,
|
||
|
|
intValue: 42,
|
||
|
|
bigIntValue: BigIntFactory.create(999),
|
||
|
|
stringValue: 'test',
|
||
|
|
boolValue: true,
|
||
|
|
bytesValue: new Uint8Array([65, 66, 67]),
|
||
|
|
timestampValue: { seconds: 1609459200, nanos: 0 },
|
||
|
|
structValue: { fields: { key: { stringValue: 'value' } } },
|
||
|
|
arrayValue: [1.1, 2.2, 3.3],
|
||
|
|
name: 'TestComponent'
|
||
|
|
})),
|
||
|
|
toObject: jest.fn().mockImplementation((message) => message)
|
||
|
|
};
|
||
|
|
})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
};
|
||
|
|
|
||
|
|
describe('ProtobufSerializer边界情况测试', () => {
|
||
|
|
let serializer: ProtobufSerializer;
|
||
|
|
|
||
|
|
beforeEach(() => {
|
||
|
|
serializer = ProtobufSerializer.getInstance();
|
||
|
|
serializer.initialize(mockProtobuf);
|
||
|
|
jest.clearAllMocks();
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('极值测试', () => {
|
||
|
|
it('应该处理极大值', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.floatValue = Number.MAX_VALUE;
|
||
|
|
component.doubleValue = Number.MAX_VALUE;
|
||
|
|
component.intValue = Number.MAX_SAFE_INTEGER;
|
||
|
|
component.bigIntValue = BigIntFactory.create(Number.MAX_SAFE_INTEGER);
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
expect(result.size).toBeGreaterThan(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理极小值', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.floatValue = Number.MIN_VALUE;
|
||
|
|
component.doubleValue = Number.MIN_VALUE;
|
||
|
|
component.intValue = Number.MIN_SAFE_INTEGER;
|
||
|
|
component.bigIntValue = BigIntFactory.create(Number.MIN_SAFE_INTEGER);
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理特殊数值', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.floatValue = NaN;
|
||
|
|
component.doubleValue = Infinity;
|
||
|
|
component.intValue = 0;
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('空值和undefined测试', () => {
|
||
|
|
it('应该处理null值', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
(component as any).stringValue = null;
|
||
|
|
(component as any).structValue = null;
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理undefined值', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
(component as any).stringValue = undefined;
|
||
|
|
(component as any).floatValue = undefined;
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理空数组', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.arrayValue = [];
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('复杂数据类型测试', () => {
|
||
|
|
it('应该处理复杂对象结构', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.structValue = {
|
||
|
|
nested: {
|
||
|
|
array: [1, 2, 3],
|
||
|
|
object: { key: 'value' },
|
||
|
|
date: new Date(),
|
||
|
|
null: null,
|
||
|
|
undefined: undefined
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理Date对象', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.timestampValue = new Date('2021-01-01T00:00:00Z');
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理Uint8Array', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
component.bytesValue = new Uint8Array([0, 255, 128, 64]);
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('循环引用测试', () => {
|
||
|
|
it('应该处理循环引用对象', () => {
|
||
|
|
const component = new CircularComponent('circular');
|
||
|
|
|
||
|
|
// 应该回退到JSON序列化
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result).toBeDefined();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('不完整组件测试', () => {
|
||
|
|
it('应该处理缺少装饰器的字段', () => {
|
||
|
|
const component = new IncompleteComponent('test');
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
expect(result.componentType).toBe('IncompleteComponent');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('非序列化组件测试', () => {
|
||
|
|
it('应该回退到JSON序列化', () => {
|
||
|
|
const component = new NonSerializableComponent();
|
||
|
|
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('json');
|
||
|
|
expect(result.componentType).toBe('NonSerializableComponent');
|
||
|
|
expect(result.data).toEqual({ data: 'test' });
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('批量序列化边界测试', () => {
|
||
|
|
it('应该处理空数组', () => {
|
||
|
|
const results = serializer.serializeBatch([]);
|
||
|
|
expect(results).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理混合组件类型', () => {
|
||
|
|
const components = [
|
||
|
|
new EdgeCaseComponent(),
|
||
|
|
new NonSerializableComponent(),
|
||
|
|
new IncompleteComponent('mixed'),
|
||
|
|
];
|
||
|
|
|
||
|
|
const results = serializer.serializeBatch(components, { continueOnError: true });
|
||
|
|
expect(results.length).toBeGreaterThanOrEqual(2);
|
||
|
|
expect(results.some(r => r.type === 'protobuf')).toBe(true);
|
||
|
|
expect(results.some(r => r.type === 'json')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理批量数据', () => {
|
||
|
|
const components = Array.from({ length: 50 }, () => new EdgeCaseComponent());
|
||
|
|
|
||
|
|
const results = serializer.serializeBatch(components);
|
||
|
|
expect(results).toHaveLength(50);
|
||
|
|
expect(results.every(r => r.type === 'protobuf')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理序列化错误', () => {
|
||
|
|
// 模拟序列化失败
|
||
|
|
const mockType = mockProtobuf.parse().root.lookupType('ecs.EdgeCaseComponent');
|
||
|
|
mockType.verify.mockReturnValue('Validation error');
|
||
|
|
|
||
|
|
const components = [new EdgeCaseComponent()];
|
||
|
|
|
||
|
|
// continueOnError = false 应该抛出异常
|
||
|
|
expect(() => {
|
||
|
|
serializer.serializeBatch(components, { continueOnError: false });
|
||
|
|
}).toThrow();
|
||
|
|
|
||
|
|
// continueOnError = true 应该回退到JSON
|
||
|
|
const results = serializer.serializeBatch(components, { continueOnError: true });
|
||
|
|
expect(results).toHaveLength(1);
|
||
|
|
expect(results[0].type).toBe('json');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('反序列化边界测试', () => {
|
||
|
|
it('应该处理JSON类型的反序列化', () => {
|
||
|
|
const component = new NonSerializableComponent();
|
||
|
|
const serializedData = {
|
||
|
|
type: 'json' as const,
|
||
|
|
componentType: 'NonSerializableComponent',
|
||
|
|
data: { data: 'deserialized' },
|
||
|
|
size: 100
|
||
|
|
};
|
||
|
|
|
||
|
|
serializer.deserialize(component, serializedData);
|
||
|
|
expect(component.data).toBe('deserialized');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该优雅处理反序列化错误', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
const invalidData = {
|
||
|
|
type: 'protobuf' as const,
|
||
|
|
componentType: 'EdgeCaseComponent',
|
||
|
|
data: new Uint8Array([255, 255, 255, 255]),
|
||
|
|
size: 4
|
||
|
|
};
|
||
|
|
|
||
|
|
// 模拟解码失败
|
||
|
|
const mockType = mockProtobuf.parse().root.lookupType('ecs.EdgeCaseComponent');
|
||
|
|
mockType.decode.mockImplementation(() => {
|
||
|
|
throw new Error('Decode failed');
|
||
|
|
});
|
||
|
|
|
||
|
|
// 不应该抛出异常
|
||
|
|
expect(() => {
|
||
|
|
serializer.deserialize(component, invalidData);
|
||
|
|
}).not.toThrow();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该处理缺失的proto定义', () => {
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
// 清除proto名称以模拟缺失情况
|
||
|
|
(component as any)._protoName = undefined;
|
||
|
|
|
||
|
|
const serializedData = {
|
||
|
|
type: 'protobuf' as const,
|
||
|
|
componentType: 'EdgeCaseComponent',
|
||
|
|
data: new Uint8Array([1, 2, 3, 4]),
|
||
|
|
size: 4
|
||
|
|
};
|
||
|
|
|
||
|
|
// 不应该抛出异常
|
||
|
|
expect(() => {
|
||
|
|
serializer.deserialize(component, serializedData);
|
||
|
|
}).not.toThrow();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('缓存测试', () => {
|
||
|
|
it('应该能清空所有缓存', () => {
|
||
|
|
serializer.clearAllCaches();
|
||
|
|
const stats = serializer.getStats();
|
||
|
|
expect(stats.messageTypeCacheSize).toBe(0);
|
||
|
|
expect(stats.componentDataCacheSize).toBe(0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('性能选项测试', () => {
|
||
|
|
it('应该能禁用数据验证', () => {
|
||
|
|
serializer.setPerformanceOptions({ enableValidation: false });
|
||
|
|
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
const result = serializer.serialize(component);
|
||
|
|
expect(result.type).toBe('protobuf');
|
||
|
|
});
|
||
|
|
|
||
|
|
it('应该能禁用组件数据缓存', () => {
|
||
|
|
serializer.setPerformanceOptions({ enableComponentDataCache: false });
|
||
|
|
|
||
|
|
const component = new EdgeCaseComponent();
|
||
|
|
serializer.serialize(component);
|
||
|
|
|
||
|
|
const stats = serializer.getStats();
|
||
|
|
expect(stats.componentDataCacheSize).toBe(0);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('统计信息测试', () => {
|
||
|
|
it('应该返回正确的统计信息', () => {
|
||
|
|
const stats = serializer.getStats();
|
||
|
|
|
||
|
|
expect(typeof stats.registeredComponents).toBe('number');
|
||
|
|
expect(typeof stats.protobufAvailable).toBe('boolean');
|
||
|
|
expect(typeof stats.messageTypeCacheSize).toBe('number');
|
||
|
|
expect(typeof stats.componentDataCacheSize).toBe('number');
|
||
|
|
expect(typeof stats.enableComponentDataCache).toBe('boolean');
|
||
|
|
expect(typeof stats.maxCacheSize).toBe('number');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|