fix(framework): read pinned policy from repo root and compare pages unordered
ci/woodpecker/pr/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed
This commit is contained in:
1 parent
ed5332b73b
commit
01662b281f
4 files changed
+75
-7
No files matched your search
@@ -318,7 +318,11 @@ class Client:
|
||||
by_id = all(isinstance(row, dict) and row.get('id') is not None for row in previous + value)
|
||||
before = [row['id'] for row in previous] if by_id else previous
|
||||
current = [row['id'] for row in value] if by_id else value
|
||||
if current == before:
|
||||
if by_id:
|
||||
repeated = sorted(current, key=lambda item: json.dumps(item, sort_keys=True)) == sorted(before, key=lambda item: json.dumps(item, sort_keys=True))
|
||||
else:
|
||||
repeated = sorted(json.dumps(row, sort_keys=True) for row in current) == sorted(json.dumps(row, sort_keys=True) for row in before)
|
||||
if repeated:
|
||||
if len(result) < expected_total:
|
||||
raise Failure(6, 'incomplete', 'Provider returned fewer records than total_count')
|
||||
return result
|
||||
@@ -644,7 +648,7 @@ def declared_method(client, p, requested):
|
||||
revision = base['sha']
|
||||
if run(['git', 'cat-file', '-e', revision + '^{commit}']).returncode:
|
||||
raise Failure(3, 'policy', 'POLICY-REFUSED: fetch the pinned target revision before merging')
|
||||
listing = run(['git', 'ls-tree', revision, '--', '.mosaic/repo.json'])
|
||||
listing = run(['git', 'ls-tree', '--full-tree', revision, '--', '.mosaic/repo.json'])
|
||||
if listing.returncode:
|
||||
raise Failure(6, 'verification', 'Cannot inspect pinned target declaration')
|
||||
if not listing.stdout.strip():
|
||||
|
||||
@@ -190,7 +190,7 @@ if ! git cat-file -e "$BASE_SHA^{commit}" 2>/dev/null; then
|
||||
echo "POLICY-REFUSED: pinned base commit is not available locally; fetch it before merging." >&2
|
||||
exit 3
|
||||
fi
|
||||
if ! git ls-tree "$BASE_SHA" -- .mosaic/repo.json > "$pinned_dir/listing" 2>/dev/null; then
|
||||
if ! git ls-tree --full-tree "$BASE_SHA" -- .mosaic/repo.json > "$pinned_dir/listing" 2>/dev/null; then
|
||||
echo "POLICY-REFUSED: cannot inspect pinned base declaration." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
@@ -125,6 +125,30 @@ class Contract(unittest.TestCase):
|
||||
with self.assertRaises(self.api.Failure) as caught: c.pages('/collection')
|
||||
self.assertEqual((caught.exception.code,caught.exception.kind),(6,'incomplete'))
|
||||
|
||||
def test_forge_reordered_repeated_pages_preserve_multiplicity(self):
|
||||
import urllib.parse
|
||||
cases=[
|
||||
([{'id':21},{'id':'22'}],[{'id':'22'},{'id':21}]),
|
||||
([{'name':'one','detail':{'a':1,'b':2}},{'name':'two'}],[{'name':'two'},{'detail':{'b':2,'a':1},'name':'one'}]),
|
||||
]
|
||||
for provider in ('gitea','forgejo'):
|
||||
for first,second in cases:
|
||||
with self.subTest(provider=provider,first=first):
|
||||
c=self.api.Client('git.example.test','owner/repo'); c.provider=provider
|
||||
calls=[]
|
||||
def http(path):
|
||||
page=int(urllib.parse.parse_qs(urllib.parse.urlsplit(path).query)['page'][0]); calls.append(page)
|
||||
return first if page==1 else second
|
||||
c.http=http
|
||||
self.assertEqual(c.pages('/reviews/1/comments'),first)
|
||||
self.assertEqual(calls,[1,2])
|
||||
for first,second in [([{'id':1},{'id':1},{'id':2}],[{'id':1},{'id':2},{'id':2}]), ([{'name':'a'},{'name':'a'},{'name':'b'}],[{'name':'a'},{'name':'b'},{'name':'b'}])]:
|
||||
with self.subTest(multiplicity=first):
|
||||
c=self.api.Client('git.example.test','owner/repo'); c.provider='gitea'
|
||||
c.http=lambda path: first if 'page=1' in path else second
|
||||
with self.assertRaises(self.api.Failure) as caught: c.pages('/collection')
|
||||
self.assertEqual(caught.exception.code,6)
|
||||
|
||||
def test_forge_partial_overlap_fails_closed(self):
|
||||
for provider in ('gitea','forgejo'):
|
||||
for first,second in [([{'id':1},{'id':2}],[{'id':2},{'id':3}]), ([{'name':'one'},{'name':'two'}],[{'name':'two'},{'name':'three'}])]:
|
||||
|
||||
@@ -69,6 +69,28 @@ class Policy(unittest.TestCase):
|
||||
self.assertEqual(api.declared_method(c,p,None),'squash')
|
||||
finally: os.chdir(old)
|
||||
|
||||
def test_adapter_reads_pinned_declaration_from_subdirectory(self):
|
||||
import os, importlib.util
|
||||
spec=importlib.util.spec_from_file_location('subdir_policy',HERE/'forge-api.py')
|
||||
api=importlib.util.module_from_spec(spec); spec.loader.exec_module(api)
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root=pathlib.Path(directory); (root/'.mosaic').mkdir(); (root/'subdir').mkdir()
|
||||
def git(*args):
|
||||
return subprocess.run(['git',*args],cwd=root,capture_output=True,text=True,check=True).stdout.strip()
|
||||
git('init','-q'); git('remote','add','origin','https://git.example.test/owner/stack.git')
|
||||
doc=dict(schema_version=3,integration_trunk='next',release_branch='main',flow='trunk-release',canonical_remote='https://git.example.test/owner/stack',canonical_clone='host:/src/project',merge=dict(allowed_methods=['merge'],default_method='merge',shapes={'promotion':'merge'}))
|
||||
(root/'.mosaic/repo.json').write_text(json.dumps(doc)); git('add','.mosaic/repo.json')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','-qm','policy')
|
||||
p={'head':{'ref':'next','repo':{'full_name':'owner/stack'}},'base':{'ref':'main','sha':git('rev-parse','HEAD'),'repo':{'full_name':'owner/stack'}}}
|
||||
c=api.Client('git.example.test','owner/stack'); c.http=lambda path: {'default_branch':'main'}
|
||||
old=os.getcwd()
|
||||
try:
|
||||
for cwd in (root,root/'subdir'):
|
||||
with self.subTest(cwd=cwd):
|
||||
os.chdir(cwd)
|
||||
self.assertEqual(api.declared_method(c,p,None),'merge')
|
||||
finally: os.chdir(old)
|
||||
|
||||
def test_branch_precheck_still_requires_absorb_mapping(self):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
path=pathlib.Path(directory)/'repo.json'
|
||||
@@ -166,16 +188,34 @@ class Policy(unittest.TestCase):
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','--allow-empty','-qm','absent')
|
||||
absent=git('rev-parse','HEAD')
|
||||
(repo/'.mosaic').mkdir()
|
||||
doc=dict(schema_version=3,integration_trunk='next',release_branch='main',flow='trunk-release',canonical_remote='https://github.com/owner/repo',canonical_clone='host:/src/repo',merge=dict(allowed_methods=['merge'],default_method='merge',shapes={'promotion':'merge'}))
|
||||
path=repo/'.mosaic/repo.json'; path.write_text(json.dumps(doc)); git('add','.mosaic/repo.json')
|
||||
doc=dict(schema_version=3,integration_trunk='next',release_branch='main',flow='trunk-release',canonical_remote='https://github.com/owner/repo',canonical_clone='host:/src/repo',merge=dict(allowed_methods=['squash','merge'],default_method='squash',shapes={'promotion':'merge'}))
|
||||
path=repo/'.mosaic/repo.json'
|
||||
merge=doc.pop('merge'); path.write_text(json.dumps(doc)); git('add','.mosaic/repo.json')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','-qm','declared transition')
|
||||
transition=git('rev-parse','HEAD')
|
||||
doc['merge']=merge; path.write_text(json.dumps(doc)); git('add','.mosaic/repo.json')
|
||||
git('-c','user.name=Fixture','-c','[email protected]','commit','-qm','declared')
|
||||
declared=git('rev-parse','HEAD')
|
||||
script=git_tools/'pr-metadata.sh'
|
||||
metadata=dict(baseRefName='main',baseRefOid='f'*40,baseRepository='owner/repo',headRefName='feature',headRefOid='a'*40,headRepository='owner/repo')
|
||||
env=dict(os.environ,HOME=str(root),GIT_CONFIG_GLOBAL='/dev/null',GIT_CONFIG_NOSYSTEM='1')
|
||||
def invoke():
|
||||
def invoke(cwd=repo):
|
||||
script.write_text("#!/bin/sh\nprintf '%s\\n' "+repr(json.dumps(metadata))+"\n"); script.chmod(0o755)
|
||||
return subprocess.run(['bash',str(git_tools/'pr-merge.sh'),'-n','1','--dry-run'],cwd=repo,env=env,capture_output=True,text=True)
|
||||
return subprocess.run(['bash',str(git_tools/'pr-merge.sh'),'-n','1','--dry-run'],cwd=cwd,env=env,capture_output=True,text=True)
|
||||
subdir=repo/'subdir'; subdir.mkdir()
|
||||
metadata['baseRefOid']=declared
|
||||
for head, code, message in [('feature',1,'transition'),('next',3,'does not select legacy squash')]:
|
||||
with self.subTest(cwd='subdir', head=head):
|
||||
metadata['headRefName']=head
|
||||
metadata['baseRefOid']=transition if head=='feature' else declared
|
||||
root_result=invoke()
|
||||
sub_result=invoke(subdir)
|
||||
self.assertEqual(root_result.returncode,code,root_result.stdout+root_result.stderr)
|
||||
self.assertEqual(sub_result.returncode,code,sub_result.stdout+sub_result.stderr)
|
||||
self.assertIn(message,root_result.stderr.lower())
|
||||
self.assertIn(message,sub_result.stderr.lower())
|
||||
self.assertEqual(sub_result.stderr,root_result.stderr)
|
||||
metadata['baseRefOid']='f'*40
|
||||
for head in ['feature','next']:
|
||||
with self.subTest(head=head):
|
||||
metadata['headRefName']=head
|
||||
|
||||
Reference in new issue
Block a user