一、问题(本轮实测)
`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` 挪回原处即可。
88 lines
5.2 KiB
Python
88 lines
5.2 KiB
Python
"""从真实CLI入口验证尺寸与截图前提,不启动浏览器。"""
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from threading import Thread
|
|
from PIL import Image
|
|
|
|
SCRIPTS = Path(__file__).resolve().parents[1] / 'scripts'
|
|
|
|
class CalibrationTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.root = Path(self.temp.name)
|
|
for name in ('reference', 'candidate'):
|
|
Image.new('RGB', (32, 64), 'white').save(self.root / (name+'.png'))
|
|
def tearDown(self):
|
|
self.temp.cleanup()
|
|
def compare(self, *extra):
|
|
return subprocess.run([sys.executable, str(SCRIPTS/'compare_mockup.py'), '--reference', str(self.root/'reference.png'), '--candidate', str(self.root/'candidate.png'), '--out-dir', str(self.root/'comparison'), *extra], capture_output=True, text=True)
|
|
def test_equal_and_invalid_clip(self):
|
|
self.assertEqual(self.compare().returncode, 0)
|
|
metrics=json.loads((self.root/'comparison/comparison-metrics.json').read_text())
|
|
self.assertFalse(metrics['resized'])
|
|
self.assertEqual(metrics['regions'][0]['mean_abs_diff'], 0)
|
|
for clip in ['bad:0,0,33,2', '../escape:0,0,1,1', 'full:0,0,1,1', 'bad:0,0,-1,1']:
|
|
with self.subTest(clip=clip):
|
|
self.assertNotEqual(self.compare('--clip', clip).returncode, 0)
|
|
def test_width_mismatch(self):
|
|
Image.new('RGB', (33,64), 'white').save(self.root/'candidate.png')
|
|
self.assertNotEqual(self.compare('--allow-height-difference').returncode, 0)
|
|
self.assertFalse((self.root/'comparison').exists())
|
|
def test_short_page_preserves_missing_tail(self):
|
|
Image.new('RGB', (32,50), 'white').save(self.root/'candidate.png')
|
|
self.assertNotEqual(self.compare().returncode, 0)
|
|
self.assertEqual(self.compare('--allow-height-difference','--clip','tail:0,50,32,14').returncode, 0)
|
|
metrics=json.loads((self.root/'comparison/comparison-metrics.json').read_text())
|
|
self.assertEqual(metrics['height_delta'], -14)
|
|
self.assertFalse(metrics['geometry_match'])
|
|
with Image.open(metrics['unmatched_tails']['reference']) as tail:
|
|
self.assertEqual(tail.size, (32,14))
|
|
self.assertIn('status',metrics['regions'][-1])
|
|
def test_capture_contract(self):
|
|
expected={'url':'http://127.0.0.1:4187/','title':'测试页','viewport':{'width':32,'height':64},'dpr':1,'full_page':False,'screenshot_scale':'css'}
|
|
capture={**expected,'fonts_ready':True,'images_ready':True,'document_height':64,'sections':[{'name':'hero','y':2}]}
|
|
asset={'src':'http://127.0.0.1:4187/logo-v2.png','natural_width':2048}
|
|
capture['assets']={'logo':asset}
|
|
manifest={'expected':expected,'capture_metadata':'capture.json','reference':'reference.png','candidate':'candidate.png','section_positions':{'hero':1},'expected_assets':{'logo':asset}}
|
|
(self.root/'run.json').write_text(json.dumps(manifest))
|
|
variants=[({},True),({'title':'其他项目'},False),({'url':'http://127.0.0.1:9999/'},False),({'fonts_ready':False},False),({'images_ready':False},False),({'dpr':2},False),({'viewport':{'width':31,'height':64}},False),({'screenshot_scale':'device'},False),({'assets':{}},False),({'assets':{'logo':{**asset,'src':'http://127.0.0.1:4187/old.png'}}},False),({'assets':{'logo':{**asset,'natural_width':100}}},False)]
|
|
for index,(changes,valid) in enumerate(variants):
|
|
with self.subTest(changes=changes):
|
|
(self.root/'capture.json').write_text(json.dumps({**capture,**changes}))
|
|
out=self.root/f'verify-{index}'
|
|
result=subprocess.run([sys.executable,str(SCRIPTS/'verify_capture.py'),'--manifest',str(self.root/'run.json'),'--out-dir',str(out)],capture_output=True,text=True)
|
|
self.assertEqual(result.returncode==0,valid,result.stderr)
|
|
if valid:
|
|
self.assertEqual(json.loads((out/'verification.json').read_text())['section_positions'][0]['delta_y'],1)
|
|
else:
|
|
self.assertFalse(out.exists())
|
|
|
|
def test_preflight_identity(self):
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self):
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write('<title>测试页</title>'.encode())
|
|
def log_message(self,*args):
|
|
pass
|
|
server=HTTPServer(('127.0.0.1',0),Handler)
|
|
thread=Thread(target=server.serve_forever,daemon=True)
|
|
thread.start()
|
|
try:
|
|
for title,valid in [('测试页',True),('其他项目',False)]:
|
|
(self.root/'run.json').write_text(json.dumps({'expected':{'url':f'http://127.0.0.1:{server.server_port}/','title':title}}))
|
|
result=subprocess.run([sys.executable,str(SCRIPTS/'verify_capture.py'),'--manifest',str(self.root/'run.json'),'--preflight'],capture_output=True,text=True)
|
|
self.assertEqual(result.returncode==0,valid,result.stderr)
|
|
finally:
|
|
server.shutdown()
|
|
server.server_close()
|
|
thread.join()
|
|
|
|
if __name__=='__main__':
|
|
unittest.main()
|