Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions .github/scripts/test_release_workflow.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test the dispatcher's actual selection and publication preflight scripts offline."""
import json
import hashlib
import os
from pathlib import Path
import shutil
Expand All @@ -24,6 +25,8 @@
['released-113', 'recovery-113', 'via113-110', 'via113-111', 'via113-112']
for stamps in ['normal', 'stamps']]
MATRIX115 = ['World upgrade matrix', 'Prepare verified migration inputs', 'Validate release workflow'] + ['Historical world shard ' + str(i) for i in range(6)]
CORE117 = ['Build, test, and audit', 'Analyze Java', 'Validation']
MATRIX117 = ['Prepare verified migration inputs', 'Validate release workflow'] + ['Historical world shard ' + str(i) for i in range(12)] + ['Native117 ' + x for x in ['servers','default','config','skin','issues','names','rejected','multiplayer','visibility','respawn','control']]
MATRIX116 = ['World upgrade matrix', 'Prepare verified migration inputs', 'Validate release workflow'] + ['Historical world shard ' + str(i) for i in range(12)]
MATRIX = [origin + ' / ' + stamps for origin in
['original-110', 'released-110', 'original-111', 'recovery-110']
Expand Down Expand Up @@ -53,18 +56,19 @@ def select(self, version, repository=REPOSITORY, ref='refs/heads/master-1.12'):
values = dict(line.split('=', 1) for line in output.read_text().splitlines()) if output.exists() else {}
return result.returncode, values

def validate(self, minecraft, names=None, mutation=None, release_exists=False, wrong_tag=False, audit_success=True):
def validate(self, minecraft, names=None, mutation=None, release_exists=False, wrong_tag=False, audit_success=True, qualification=None):
suffix, branch, forge = {
'1.10.2': ('110021', '1.10.2', '12.18.3.2511'),
'1.11.2': ('111021', '1.11.2-dev', '13.20.1.2588'),
'1.12.2': ('112021', 'master-1.12', '14.23.5.2859'),
'1.13.2': ('113021', 'master-1.13.2', '25.0.223'),
'1.14.4': ('114041', 'master-1.14.4', '28.2.26'),
'1.15.2': ('115021', 'master-1.15.2', '31.2.57'),
'1.16.5': ('116051', 'master-1.16.5', '36.2.42')}[minecraft]
'1.16.5': ('116051', 'master-1.16.5', '36.2.42'),
'1.17.1': ('117011', 'master-1.17.1', '37.1.1')}[minecraft]
version = '2.4.2.' + suffix
values = dict(minecraft_version=minecraft, forge_version=forge, loader_name='forge', loader_code='1',
java_version='8', java_toolchain_version='8.0.502+7', gradle_java_version='17', mod_id='lootablebodies',
java_version='16' if minecraft=='1.17.1' else '8', java_toolchain_version='16.0.2+7' if minecraft=='1.17.1' else '8.0.502+7', gradle_java_version='17', mod_id='lootablebodies',
mod_artifact='LootableBodies', mod_group='zone.moddev.mc.lootablebodies', mod_license='LGPL-2.1-only',
curseforge_project_id='240968', mod_version=version)
if mutation:
Expand All @@ -73,9 +77,15 @@ def validate(self, minecraft, names=None, mutation=None, release_exists=False, w
'1.13.2': MATRIX113 + ['Validate release workflow'],
'1.14.4': MATRIX114 + ['Validate release workflow'],
'1.15.2': MATRIX115, '1.16.5': MATRIX116}.get(minecraft, []) if names is None else names
if minecraft=='1.17.1' and names is None:checks=CORE117+MATRIX117

def command(args, **options):
if args[0] == 'git':
return SHA + '\n'
if '/actions/workflows/native117.yml/' in args[-1]:
row=dict(id=117,head_sha=SHA,head_branch='master-1.17.1',event='push',path='.github/workflows/native117.yml',conclusion='success')
if qualification:row.update(qualification)
return json.dumps({'workflow_runs':[row]})
if '/actions/workflows/' in args[-1]:
return json.dumps({'workflow_runs': [{'id': 1, 'display_title': 'Audit ' + minecraft + ' artifacts from ' + SHA,
'conclusion': 'success' if audit_success else 'failure'}]})
Expand All @@ -95,8 +105,8 @@ def existing(args, **options):
patch('subprocess.check_output', side_effect=command), patch('subprocess.run', side_effect=existing):
exec(compile(code, 'release-preflight', 'exec'), {})

def test_future_117_candidate_cannot_dispatch_publication(self):
code, values = self.select('2.4.8.117011')
def test_future_118_candidate_cannot_dispatch_publication(self):
code, values = self.select('2.4.9.118021')
self.assertNotEqual(0, code)
self.assertEqual({}, values)

Expand Down Expand Up @@ -217,6 +227,44 @@ def test_116_audit_handles_both_registered_workflow_names(self):
self.assertIn('LootableBodies 1.12 CI', [name.strip() for name in names])
self.assertIn('LootableBodies 1.16 CI', [name.strip() for name in names])

def test_117_routes_to_the_accepted_target(self):
code,values=self.select('2.4.8.117011')
self.assertEqual(0,code);self.assertEqual('master-1.17.1',values['source_branch'])

def test_117_requires_all_native_scenarios_and_twelve_historical_shards(self):
self.validate('1.17.1')
for missing in CORE117+MATRIX117:
with self.subTest(missing=missing),self.assertRaises(SystemExit):
self.validate('1.17.1',names=[x for x in CORE117+MATRIX117 if x!=missing])

def test_117_requires_an_exact_successful_merged_native_run(self):
for mutation in [{'head_sha':'b'*40},{'head_branch':'master-1.16.5'},{'event':'pull_request'},{'path':'.github/workflows/build.yml'},{'conclusion':'failure'}]:
with self.subTest(mutation=mutation),self.assertRaises(SystemExit):
self.validate('1.17.1',qualification=mutation)

def test_117_requires_the_independent_audit_and_java16(self):
with self.assertRaises(SystemExit):self.validate('1.17.1',audit_success=False)
with self.assertRaises(SystemExit):self.validate('1.17.1',mutation={'java_version':'8'})

def test_117_promotes_exact_six_files_and_rejects_changed_or_extra_bytes(self):
code=script('Verify all six native117 bytes before publication').split("python3 - <<'PY'\n",1)[1].split('\nPY',1)[0]
with tempfile.TemporaryDirectory() as directory:
root=Path(directory);bundle=root/'build/release/bundle';bundle.mkdir(parents=True);meta=root/'.github';meta.mkdir()
names=['main.jar','sources.jar','javadoc.jar','release.pom','CHANGELOG.txt','SHA256SUMS'];expected={}
for name in names:
data=('approved '+name).encode();(bundle/name).write_bytes(data);expected[name]=hashlib.sha256(data).hexdigest().upper()
(meta/'release117.json').write_text(json.dumps({'files':expected}))
previous=Path.cwd()
try:
os.chdir(root);exec(compile(code,'immutable-native117-promotion','exec'),{})
for name in names:
f=bundle/name;data=f.read_bytes();f.write_bytes(data+b'changed')
with self.assertRaises(SystemExit):exec(compile(code,'changed-native117-promotion','exec'),{})
f.write_bytes(data)
(bundle/'extra.txt').write_text('unapproved')
with self.assertRaises(SystemExit):exec(compile(code,'extra-native117-promotion','exec'),{})
finally:os.chdir(previous)

def test_identity_changes_are_rejected(self):
for mutation in [{'mod_id': 'other'}, {'minecraft_version': '1.10.2'}, {'mod_license': 'CC0-1.0'}]:
with self.subTest(mutation=mutation), self.assertRaises(SystemExit):
Expand Down
68 changes: 68 additions & 0 deletions .github/workflows/artifact-audit-1.17.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Audit LootableBodies 1.17 artifacts
run-name: Audit 1.17.1 artifacts from ${{ github.event.workflow_run.head_sha }}
on:
workflow_run:
workflows: [LootableBodies 1.17 CI]
types: [completed]
permissions:
contents: read
actions: read
jobs:
audit:
if: >-
github.repository == 'MinecraftModDevelopmentMods/LootableBodies' &&
github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'master-1.17.1'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.workflow_run.head_sha }}
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: '25.0.3+9.0.LTS'
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: '16.0.2+7'
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5
with:
distribution: temurin
java-version: '17.0.1+12'
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: LootableBodies-1.17.1-${{ github.event.workflow_run.head_sha }}
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
path: audited
- name: Independently verify every accepted byte
env:
SOURCE_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
python3 - <<'PY'
import hashlib,json,os
from pathlib import Path
expected=json.loads(Path('.github/release117.json').read_text())['files']
actual={p.name:hashlib.sha256(p.read_bytes()).hexdigest().upper() for p in Path('audited').iterdir() if p.is_file()}
if actual != expected:raise SystemExit('Native117 audited bytes differ from the accepted six files')
Path('independent-audit.json').write_text(json.dumps({'source':os.environ['SOURCE_SHA'],'files':actual,'all_six_exact':True},indent=2)+'\n')
PY
- name: Audit archive identity, class60, licences and mappings without rebuilding
env:
LOOTABLEBODIES_JAVA16_HOME: ${{ env.JAVA_HOME_16_X64 }}
LOOTABLEBODIES_MAVENIZER_JAVA_HOME: ${{ env.JAVA_HOME_25_X64 }}
run: |
chmod +x ./gradlew
./gradlew verifyPreparedReleaseArtifacts -PpreparedReleaseDir=audited --no-daemon --console=plain --stacktrace | tee archive-audit.log
if grep -E '^> Task :(compileJava|jar|sourcesJar|javadocJar)( |$)' archive-audit.log; then exit 1; fi
- name: Retain independent audit evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: native117-independent-audit-${{ github.event.workflow_run.head_sha }}
if-no-files-found: error
path: |
independent-audit.json
archive-audit.log
Loading
Loading