Skip to content

Commit 8311bcd

Browse files
committed
fix(ci): set the Go release's guest mode in git, and test against the real publish step
Review follow-up. chmod made the two jobs agree only while both kept writing the files 0644 by their own route. Both jobs now stage the guests with git add --chmod=-x, so the committed mode is set in git whatever either job left on disk. The test no longer compares the build job's tree with a tree it builds itself at 100644. It runs the build job's staging on executable guests, then runs the real publish step on an executable artifact against the tree the build job reported, and expects publish to accept it.
1 parent 3b468ae commit 8311bcd

2 files changed

Lines changed: 78 additions & 39 deletions

File tree

‎.github/workflows/release-golang.yml‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,12 +205,11 @@ jobs:
205205
git checkout --detach
206206
# The guests are gitignored on main; -f adds them to this commit only.
207207
read -ra guests <<< "$GUESTS"
208-
# Cargo writes the guests executable, and `publish` writes them
209-
# 0644 from the artifact. Commit them 0644 here too, or the tree
210-
# this job tests is not the one `publish` builds, and it refuses
211-
# the release.
212-
chmod 0644 "${guests[@]}"
213-
git add -f "${guests[@]}"
208+
# --chmod=-x: the committed mode is set in git, not read from the
209+
# file. Cargo writes the guests executable and `publish` writes them
210+
# 0644, so a mode taken from disk made this job's tree differ from
211+
# the one `publish` builds, and it refused the release.
212+
git add -f --chmod=-x "${guests[@]}"
214213
git commit -q -m "release(golang): v$VERSION with its WASI guests"
215214
tree=$(git rev-parse 'HEAD^{tree}')
216215
@@ -303,7 +302,9 @@ jobs:
303302
git config user.name "github-actions[bot]"
304303
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
305304
git checkout --detach
306-
git add -f "${guests[@]}"
305+
# The mode is set in git, as the build job sets it, so the two trees
306+
# agree whatever mode either job left the files in.
307+
git add -f --chmod=-x "${guests[@]}"
307308
git commit -m "release(golang): v$VERSION with its WASI guests" \
308309
-m "Built from $GITHUB_SHA by $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
309310
tree=$(git rev-parse 'HEAD^{tree}')

‎scripts/__tests__/go-release-artifact.test.mjs‎

Lines changed: 70 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { execFileSync, spawnSync } from 'node:child_process'
22
import { createHash } from 'node:crypto'
33
import {
4+
chmodSync,
45
existsSync,
56
mkdirSync,
67
mkdtempSync,
@@ -243,45 +244,82 @@ describe.skipIf(!BASH_HAS_MAPFILE)(
243244
* `build` reports the tree it tested and `publish` refuses any other, so the
244245
* two must stage the guests identically. Cargo writes them executable and
245246
* `publish` writes them 0644 from the artifact, which made the trees differ
246-
* in mode alone: the first release refused itself. This runs the build job's
247-
* staging, up to the tree it reports, on executable guests.
247+
* in mode alone: the first release refused itself. Both now set the mode in
248+
* git (`--chmod=-x`), so it no longer depends on the files.
248249
*/
249250
const STAGE_STEP = WORKFLOW.jobs.build.steps.find((step) => step.id === 'stage')
250251
const STAGE_UNTIL = 'fetched=$(mktemp -d)'
251252

253+
/**
254+
* Run the build job's staging, up to the tree it reports, on guests left
255+
* executable as cargo leaves them, then return to `main`. Returns the tree.
256+
*/
257+
function runBuildStaging() {
258+
const script = String(STAGE_STEP.run)
259+
const cut = script.indexOf(STAGE_UNTIL)
260+
expect(
261+
cut,
262+
`the stage step no longer contains ${STAGE_UNTIL}`,
263+
).toBeGreaterThan(0)
264+
for (const guest of GUESTS) {
265+
const built = join(repo, guest)
266+
mkdirSync(dirname(built), { recursive: true })
267+
writeFileSync(built, `guest ${guest}\n`, { mode: 0o755 })
268+
}
269+
const result = spawnSync(
270+
'bash',
271+
['-c', `${script.slice(0, cut)}\necho "$tree"`],
272+
{
273+
cwd: repo,
274+
encoding: 'utf8',
275+
env: {
276+
PATH: process.env.PATH,
277+
HOME: repo,
278+
GIT_CONFIG_NOSYSTEM: '1',
279+
GUESTS: WORKFLOW.env.GUESTS,
280+
VERSION: '0.1.0-alpha.1',
281+
},
282+
},
283+
)
284+
expect(result.status, result.stderr).toBe(0)
285+
// Forced: the guests on disk are still 0755 while the commit holds them
286+
// 0644, which git reads as a local change.
287+
git(repo, 'checkout', '-q', '-f', 'main')
288+
return result.stdout.trim().split('\n').at(-1)
289+
}
290+
252291
describe('release-golang.yml build: Stage the release commit', () => {
253-
it('reports the tree publish builds, whatever mode cargo left the guests in', () => {
292+
it('commits the guests non-executable, whatever mode cargo left them in', () => {
254293
expect(STAGE_STEP).toBeDefined()
255-
const script = String(STAGE_STEP.run)
256-
const cut = script.indexOf(STAGE_UNTIL)
257-
expect(
258-
cut,
259-
`the stage step no longer contains ${STAGE_UNTIL}`,
260-
).toBeGreaterThan(0)
261-
294+
const tree = runBuildStaging()
262295
for (const guest of GUESTS) {
263-
stage(guest, `guest ${guest}\n`)
264-
const built = join(repo, guest)
265-
mkdirSync(dirname(built), { recursive: true })
266-
writeFileSync(built, `guest ${guest}\n`, { mode: 0o755 })
296+
expect(git(repo, 'ls-tree', tree, guest).split(/\s+/)[0], guest).toBe(
297+
'100644',
298+
)
267299
}
268-
const result = spawnSync(
269-
'bash',
270-
['-c', `${script.slice(0, cut)}\necho "$tree"`],
271-
{
272-
cwd: repo,
273-
encoding: 'utf8',
274-
env: {
275-
PATH: process.env.PATH,
276-
HOME: repo,
277-
GIT_CONFIG_NOSYSTEM: '1',
278-
GUESTS: WORKFLOW.env.GUESTS,
279-
VERSION: '0.1.0-alpha.1',
280-
},
281-
},
282-
)
283-
expect(result.status, result.stderr).toBe(0)
284-
git(repo, 'checkout', '-q', 'main')
285-
expect(result.stdout.trim().split('\n').at(-1)).toBe(expectedTree())
286300
})
287301
})
302+
303+
describe.skipIf(!BASH_HAS_MAPFILE)(
304+
'release-golang.yml build and publish build the same tree',
305+
() => {
306+
it.skipIf(!GNU_COREUTILS)(
307+
'publish accepts the tree build reports, from an executable artifact',
308+
() => {
309+
const tree = runBuildStaging()
310+
// The artifact executable too: the two jobs agree through git, not
311+
// through what either one wrote to disk.
312+
writeManifest(
313+
GUESTS.map((guest) => {
314+
const line = stage(guest, `guest ${guest}\n`)
315+
chmodSync(join(repo, 'release-out', guest), 0o755)
316+
return line
317+
}),
318+
)
319+
const result = runCommitStep(tree)
320+
expect(result.stderr + result.stdout).not.toContain('::error::')
321+
expect(result.status).toBe(0)
322+
},
323+
)
324+
},
325+
)

0 commit comments

Comments
 (0)