一、问题(本轮实测)
`draw-ui` 与 `oil-motion` 目录里**各自带一个内嵌 `.git`** ⇒ 上一次提交把它们记成了 **gitlink(子模块指针)**
⇒ 仓库里只存了一个不属于任何远端的 commit id,**别人克隆下来这两份是空的** ✗(`git status` 显示 ` m draw-ui` / ` m oil-motion` = 子模块内容有改动)。
二、处置(可回退)
· 把两处的 `.git` **挪走**(⛔ 不是删除)⇒ `归档/内嵌git-20261008/{draw-ui,oil-motion}.git`;
· `git rm --cached` 掉那两个 gitlink,再 `git add` 两个目录 ⇒ **按正常文件入库**(内容才真的进仓库)。
三、副作用(如实记)
挪走 `.git` 后,这两个技能**不能再原地 `git pull` 取上游更新**(要更新得重新拉一份覆盖);
如需恢复其本地仓库,把 `归档/内嵌git-20261008/` 里的 `.git` 挪回原处即可。
70 lines
4.9 KiB
Python
70 lines
4.9 KiB
Python
"""通过 CLI 验证测量边界;只使用临时合成图,不声称为真实页面效果评估。"""
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from PIL import Image
|
|
SCRIPT=Path(__file__).resolve().parents[1]/'scripts'/'measure_reference.py'
|
|
class MeasureReferenceTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp=tempfile.TemporaryDirectory(prefix='reference measurement ')
|
|
self.root=Path(self.tmp.name)
|
|
self.image=Image.new('RGBA',(40,40),(12,16,17,255))
|
|
self.image.save(self.root/'source.png')
|
|
def tearDown(self):self.tmp.cleanup()
|
|
def run_cli(self,data,out='result'):
|
|
p=self.root/'manifest.json';p.write_text(json.dumps({'reference':'source.png',**data}))
|
|
result=subprocess.run([sys.executable,str(SCRIPT),'--manifest',str(p),'--out-dir',str(self.root/out)],capture_output=True,text=True)
|
|
return result
|
|
def region(self,**kw):return {'name':'sample','kind':'color','box':[0,0,40,40],**kw}
|
|
def read(self,out='result'):return json.loads((self.root/out/'measurements.json').read_text())
|
|
def test_color_and_width_mapping(self):
|
|
r=self.run_cli({'css_viewport_width':20,'regions':[self.region()]})
|
|
self.assertEqual(r.returncode,0,r.stderr)
|
|
d=self.read();self.assertEqual(d['regions'][0]['color']['median_hex'],'#0C1011')
|
|
self.assertEqual(d['regions'][0]['css_box'],[0,0,20,20]);self.assertFalse(d['regions'][0]['dom_comparable'])
|
|
def test_transparent_pixels_excluded(self):
|
|
self.image.paste((255,0,0,0),(0,0,20,40));self.image.save(self.root/'source.png')
|
|
r=self.run_cli({'regions':[self.region()]});self.assertEqual(r.returncode,0,r.stderr)
|
|
c=self.read()['regions'][0]['color'];self.assertEqual(c['median_hex'],'#0C1011');self.assertEqual(c['excluded_nonopaque'],800)
|
|
def test_no_opaque_samples_rejected(self):
|
|
self.image.putalpha(0);self.image.save(self.root/'source.png')
|
|
r=self.run_cli({'regions':[self.region()]});self.assertEqual(r.returncode,2);self.assertFalse((self.root/'result').exists())
|
|
def test_boundary_and_invalid_types_rejected(self):
|
|
for box in [[-1,0,20,20],[0,0,41,40],[0,0,0,20],[0.5,0,20,20],[True,0,20,20]]:
|
|
with self.subTest(box=box):
|
|
r=self.run_cli({'regions':[self.region(box=box)]});self.assertEqual(r.returncode,2);self.assertFalse((self.root/'result').exists())
|
|
def test_inset(self):
|
|
r=self.run_cli({'regions':[self.region(inset=2)]});self.assertEqual(r.returncode,0)
|
|
self.assertEqual(self.read()['regions'][0]['color']['opaque_samples'],36*36)
|
|
r=self.run_cli({'regions':[self.region(inset=20)]},out='invalid');self.assertEqual(r.returncode,2)
|
|
def test_existing_output_refused(self):
|
|
data={'regions':[self.region()]};self.assertEqual(self.run_cli(data).returncode,0)
|
|
before=(self.root/'result/measurements.json').read_bytes()
|
|
self.assertEqual(self.run_cli(data).returncode,2);self.assertEqual(before,(self.root/'result/measurements.json').read_bytes())
|
|
def test_duplicate_names_refused(self):
|
|
self.assertEqual(self.run_cli({'regions':[self.region(),self.region()]}).returncode,2)
|
|
def test_grid_reports_unequal_widths(self):
|
|
regions=[{'name':'a','box':[0,2,10,10],'group':'cards'},{'name':'b','box':[13,2,15,10],'group':'cards'}]
|
|
self.assertEqual(self.run_cli({'regions':regions}).returncode,0)
|
|
g=self.read()['groups'][0];self.assertEqual(g['x_gaps'],[3]);self.assertEqual(g['width_spread'],5)
|
|
def test_ink_not_dom_comparable_or_grid(self):
|
|
region={'name':'title','box':[0,0,20,20],'box_type':'ink','selector':'h1'}
|
|
self.assertEqual(self.run_cli({'regions':[region]}).returncode,0)
|
|
self.assertFalse(self.read()['regions'][0]['dom_comparable'])
|
|
self.assertEqual(self.run_cli({'regions':[{**region,'group':'bad'}]},out='invalid').returncode,2)
|
|
def test_scan_includes_final_run(self):
|
|
self.image.paste((230,225,215,255),(0,30,40,40));self.image.save(self.root/'source.png')
|
|
scan={'name':'band','x':1,'y':0,'height':40,'min_channel':175,'max_channel_spread':45,'min_run':5}
|
|
self.assertEqual(self.run_cli({'vertical_scans':[scan]}).returncode,0)
|
|
self.assertEqual(self.read()['vertical_scans'][0]['runs'],[{'y_start':30,'y_end_exclusive':40,'height':10}])
|
|
def test_no_mapping_when_not_known(self):
|
|
self.assertEqual(self.run_cli({'regions':[self.region()]}).returncode,0)
|
|
self.assertNotIn('css_box',self.read()['regions'][0]);self.assertIsNone(self.read()['css_mapping']['css_per_image_pixel'])
|
|
def test_svg_escapes_labels(self):
|
|
self.assertEqual(self.run_cli({'regions':[self.region(name='<script>bad</script>')]}).returncode,0)
|
|
svg=(self.root/'result/annotations.svg').read_text();self.assertNotIn('<script>',svg);self.assertIn('<script>',svg)
|
|
if __name__=='__main__':unittest.main()
|