134 خطوط
6.9 KiB
Python
134 خطوط
6.9 KiB
Python
import math
|
|
import struct
|
|
import pytest
|
|
from app.parser.geometry import point,coordinate,transform_point,color
|
|
from app.models.schematic import Point
|
|
from app.parser.record_decoder import detect_format,binary_records,properties,ParseError
|
|
from app.parser.schdoc_parser import parse_schdoc
|
|
from app.parser.ole_reader import read_document
|
|
|
|
@pytest.mark.parametrize('data,expected',[(b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1','ole'),(b'|HEADER=x','ascii'),(b'\xef\xbb\xbf|HEADER=x','ascii')])
|
|
def test_detect(data,expected): assert detect_format(data)==expected
|
|
|
|
@pytest.mark.parametrize('data',[b'',b'<html/>',b'PK\x03\x04',b'\0'*1024])
|
|
def test_unknown_format(data):
|
|
with pytest.raises(ParseError): detect_format(data)
|
|
|
|
def test_length_prefix_and_indexing():
|
|
payloads=[b'|HEADER=Example\0',b'|RECORD=31\0',b'|RECORD=27|X1=42\0']
|
|
stream=b''.join(struct.pack('<I',len(p))+p for p in payloads)
|
|
records=binary_records(stream,'FileHeader')
|
|
assert [r.index for r in records]==[-1,0,1]
|
|
assert records[2].properties['X1']=='42'
|
|
|
|
@pytest.mark.parametrize('data',[b'\x05',struct.pack('<I',100)+b'x',struct.pack('<I',0),struct.pack('<I',0x7fffff)+b'x'])
|
|
def test_truncated_or_excessive_record(data):
|
|
with pytest.raises(ParseError): binary_records(data,'FileHeader')
|
|
|
|
def test_binary_record_does_not_shift_owners():
|
|
p=b'|HEADER=x\0';s=struct.pack('<I',len(p))+p+struct.pack('<I',0x1000002)+b'aa'
|
|
r=binary_records(s,'FileHeader');assert r[1].binary and r[1].index==0
|
|
|
|
def test_utf8_precedence_and_case():
|
|
p=properties('|%UTF8%Comment=مقاومت Ω|Comment=legacy|Text=a=b'.encode())
|
|
assert p['COMMENT']=='مقاومت Ω' and p['TEXT']=='a=b'
|
|
|
|
def test_fraction_and_axis():
|
|
p={'LOCATION.X':'12','LOCATION.X_FRAC':'50000','LOCATION.Y':'-3','LOCATION.Y_FRAC':'25000'}
|
|
assert coordinate(p,'LOCATION.X')==12.5
|
|
assert point(p)==Point(x=12.5,y=2.75)
|
|
assert color('8388608')=='#000080'
|
|
|
|
@pytest.mark.parametrize('rotation,expected',[(0,(12,20)),(90,(10,18)),(180,(8,20)),(270,(10,22))])
|
|
def test_rotation(rotation,expected):
|
|
p=transform_point(Point(x=2,y=0),Point(x=10,y=20),rotation)
|
|
assert p.x==pytest.approx(expected[0]) and p.y==pytest.approx(expected[1])
|
|
|
|
def test_mirror_then_rotate():
|
|
p=transform_point(Point(x=2,y=1),Point(x=10,y=20),90,True)
|
|
assert p==Point(x=11,y=22)
|
|
|
|
def test_component_extraction_and_absolute_geometry(ascii_schematic):
|
|
d=parse_schdoc(ascii_schematic,'test.SchDoc','test')
|
|
c=d.components[0]
|
|
assert (c.designator,c.value,c.comment,c.library_reference)==('R1','4K7','4K7','R_Symbol')
|
|
assert c.rotation==90 and c.mirrored and c.parameters['Manufacturer']=='Test'
|
|
assert c.pins[0].number=='1' and c.pins[0].name=='A'
|
|
assert c.pins[0].connection==Point(x=100,y=-230)
|
|
# Placed child geometry must not be rotated/mirrored again.
|
|
line=next(p for p in d.primitives if p.object_id=='FileHeader:2')
|
|
assert line.path=='M100,-200 L100,-220'
|
|
assert line.component_id==c.id
|
|
assert len({x.id for x in d.objects})==len(d.objects)
|
|
assert set(c.primitive_ids)==set(d.symbols[0].primitive_ids)
|
|
assert not any(p.text=='Test' for p in d.primitives)
|
|
|
|
def test_wire_path_and_connection_vertices(ascii_schematic):
|
|
d=parse_schdoc(ascii_schematic,'test.SchDoc','test')
|
|
o=next(o for o in d.objects if o.kind=='wire')
|
|
p=next(p for p in d.primitives if p.object_id==o.id)
|
|
assert p.path=='M100,-230 L150,-230'
|
|
assert o.connection_points==[Point(x=100,y=-230),Point(x=150,y=-230)]
|
|
|
|
def test_part_visibility(ascii_schematic):
|
|
data=ascii_schematic+b'|RECORD=13|OwnerIndex=1|OwnerPartId=2|Location.X=2|Corner.X=3\n'
|
|
d=parse_schdoc(data,'test.SchDoc','test')
|
|
assert not d.objects[-1].visible and not d.objects[-1].primitive_ids
|
|
|
|
def test_unsupported_preserved(ascii_schematic):
|
|
d=parse_schdoc(ascii_schematic+b'|RECORD=999|UnknownProperty=important\n','test.SchDoc','test')
|
|
assert d.objects[-1].properties['UNKNOWNPROPERTY']=='important'
|
|
assert any(x.code=='unsupported_record' and x.record_type==999 for x in d.diagnostics)
|
|
|
|
def test_bad_numeric_geometry_reports_partial(ascii_schematic):
|
|
d=parse_schdoc(ascii_schematic+b'|RECORD=13|Location.X=nan\n','test.SchDoc','test')
|
|
assert any(x.code=='malformed_record' and x.severity=='error' for x in d.diagnostics)
|
|
assert d.objects[-1].properties['LOCATION.X']=='nan'
|
|
|
|
def test_unknown_version(ascii_schematic):
|
|
with pytest.raises(ParseError,match='Unsupported schematic version'):
|
|
parse_schdoc(ascii_schematic.replace(b'Version 5.0',b'Version 8.0'),'test.SchDoc','test')
|
|
|
|
def test_ole_malformed_sector_table():
|
|
with pytest.raises(ParseError): read_document(b'\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1'+b'\0'*504)
|
|
|
|
def test_owner_cycle(ascii_schematic):
|
|
d=parse_schdoc(ascii_schematic+b'|RECORD=13|OwnerIndex=9\n','test.SchDoc','test')
|
|
assert any(x.code=='malformed_record' for x in d.diagnostics)
|
|
|
|
@pytest.mark.parametrize('typ,props',[
|
|
(5,'|LocationCount=4|X1=0|Y1=0|X2=5|Y2=9|X3=10|Y3=9|X4=15|Y4=0'),
|
|
(7,'|LocationCount=3|X1=0|Y1=0|X2=5|Y2=9|X3=10|Y3=0|IsSolid=T'),
|
|
(8,'|Radius=5|SecondaryRadius=10'),(9,'|Radius=5|StartAngle=30|EndAngle=180'),
|
|
(10,'|Corner.X=20|Corner.Y=10|CornerXRadius=2|CornerYRadius=3'),
|
|
(11,'|Radius=10|SecondaryRadius=5|StartAngle=45|EndAngle=200'),
|
|
(12,'|Radius=5'),(14,'|Corner.X=10|Corner.Y=20'),
|
|
(15,'|XSize=100|YSize=60'),(18,'|Name=DATA|Width=50|Style=3'),
|
|
(22,''),(26,'|LocationCount=2|X1=0|Y1=0|X2=20|Y2=30'),(29,''),
|
|
(37,'|Corner.X=10|Corner.Y=20'),(225,'|Corner.X=100|Corner.Y=50')])
|
|
def test_supported_shapes(ascii_schematic,typ,props):
|
|
d=parse_schdoc(ascii_schematic+f'|RECORD={typ}{props}\n'.encode(),'test.SchDoc','test')
|
|
obj=d.objects[-1]
|
|
assert obj.primitive_ids
|
|
assert not any(x.code=='malformed_record' for x in d.diagnostics)
|
|
assert all(math.isfinite(p.bounds.width) for p in d.primitives)
|
|
|
|
def test_clipped_text_does_not_expand_fit_bounds(ascii_schematic):
|
|
line='|RECORD=28|Location.X=10|Location.Y=10|Corner.X=60|Corner.Y=40|FontID=1|WordWrap=T|ClipToRect=T|Text='+('Long note text '*100)+'\n'
|
|
d=parse_schdoc(ascii_schematic+line.encode(),'test.SchDoc','test')
|
|
obj=d.objects[-1]
|
|
assert obj.bounds.x>=10 and obj.bounds.x+obj.bounds.width<=60
|
|
assert obj.bounds.y>=-40 and obj.bounds.y+obj.bounds.height<=-10
|
|
assert any(p.clip for p in d.primitives if p.object_id==obj.id)
|
|
|
|
def test_invalid_record_type_is_preserved(ascii_schematic):
|
|
d=parse_schdoc(ascii_schematic+b'|RECORD=abc|OriginalField=value\n','test.SchDoc','test')
|
|
assert d.objects[-1].properties['ORIGINALFIELD']=='value'
|
|
assert any(w.code=='malformed_record' for w in d.diagnostics)
|
|
|
|
def test_implementation_parameter_is_not_drawn(ascii_schematic):
|
|
data=ascii_schematic+b'|RECORD=48|OwnerIndex=1\n|RECORD=41|OwnerIndex=9|Name=ModelParam|Text=must-not-render\n'
|
|
d=parse_schdoc(data,'test.SchDoc','test')
|
|
assert not d.objects[-1].visible
|
|
assert not any(p.text=='must-not-render' for p in d.primitives)
|