angular-cn/dev-infra/release/publish/test/move-next-into-feature-freeze.spec.ts
Paul Gschwendtner f96dcc5ce0 feat(dev-infra): tool for staging and publishing releases (#38656)
Creates a tool for staging and publishing releases as per the
new branching and versioning that has been outlined in the following
document. The tool is intended to be used across the organization to
ensure consistent branching/versioning and labeling:

https://docs.google.com/document/d/197kVillDwx-RZtSVOBtPb4BBIAw0E9RT3q3v6DZkykU/edit#heading=h.s3qlps8f4zq7dd

The tool implements the actions as outlined in the following
initial plan: https://hackmd.io/2Le8leq0S6G_R5VEVTNK9A.

The implementation slightly diverged in so far that it performs
staging and publishing together so that releasing is a single
convenient command. In case of errors for which re-running the
full command is not sufficient, we want to consider adding
recover functionality. e.g. when the staging completed, but the
actual NPM publishing aborted unexpectedly due to build errors.

PR Close #38656
2020-09-28 16:11:42 -04:00

149 lines
6.6 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getBranchPushMatcher} from '../../../utils/testing';
import {ActiveReleaseTrains} from '../../versioning/active-release-trains';
import * as npm from '../../versioning/npm-publish';
import {ReleaseTrain} from '../../versioning/release-trains';
import {MoveNextIntoFeatureFreezeAction} from '../actions/move-next-into-feature-freeze';
import * as externalCommands from '../external-commands';
import {getChangelogForVersion, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
describe('move next into feature-freeze action', () => {
it('should not activate if a feature-freeze release-train is active', async () => {
expect(await MoveNextIntoFeatureFreezeAction.isActive({
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.1')),
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
})).toBe(false);
});
it('should not activate if release-candidate release-train is active', async () => {
expect(await MoveNextIntoFeatureFreezeAction.isActive({
// No longer in feature-freeze but in release-candidate phase.
releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-rc.0')),
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
})).toBe(false);
});
it('should activate if no FF/RC release-train is active', async () => {
expect(await MoveNextIntoFeatureFreezeAction.isActive({
releaseCandidate: null,
next: new ReleaseTrain('master', parse('10.1.0-next.0')),
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
})).toBe(true);
});
it('should create pull requests and feature-freeze branch', async () => {
await expectVersionAndBranchToBeCreated(
{
releaseCandidate: null,
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
},
/* isNextPublishedToNpm */ true, '10.3.0-next.0', '10.2.0-next.1', '10.2.x');
});
it('should not increment the version if "next" version is not yet published', async () => {
await expectVersionAndBranchToBeCreated(
{
releaseCandidate: null,
next: new ReleaseTrain('master', parse('10.2.0-next.0')),
latest: new ReleaseTrain('10.0.x', parse('10.0.3')),
},
/* isNextPublishedToNpm */ false, '10.3.0-next.0', '10.2.0-next.0', '10.2.x');
});
/** Performs the action and expects versions and branches to be determined properly. */
async function expectVersionAndBranchToBeCreated(
active: ActiveReleaseTrains, isNextPublishedToNpm: boolean, expectedNextVersion: string,
expectedVersion: string, expectedNewBranch: string) {
const {repo, fork, instance, gitClient, releaseConfig} =
setupReleaseActionForTesting(MoveNextIntoFeatureFreezeAction, active, isNextPublishedToNpm);
const expectedNextUpdateBranch = `next-release-train-${expectedNextVersion}`;
const expectedStagingForkBranch = `release-stage-${expectedVersion}`;
const expectedTagName = expectedVersion;
// We first mock the commit status check for the next branch, then expect two pull
// requests from a fork that are targeting next and the new feature-freeze branch.
repo.expectBranchRequest('master', 'MASTER_COMMIT_SHA')
.expectCommitStatusCheck('MASTER_COMMIT_SHA', 'success')
.expectFindForkRequest(fork)
.expectPullRequestToBeCreated(expectedNewBranch, fork, expectedStagingForkBranch, 200)
.expectPullRequestWait(200)
.expectBranchRequest(expectedNewBranch, 'STAGING_COMMIT_SHA')
.expectCommitRequest(
'STAGING_COMMIT_SHA', `release: cut the v${expectedVersion} release\n\nPR Close #200.`)
.expectTagToBeCreated(expectedTagName, 'STAGING_COMMIT_SHA')
.expectReleaseToBeCreated(`v${expectedVersion}`, expectedTagName)
.expectChangelogFetch(expectedNewBranch, getChangelogForVersion(expectedVersion))
.expectPullRequestToBeCreated('master', fork, expectedNextUpdateBranch, 100);
// In the fork, we make the following branches appear as non-existent,
// so that the PRs can be created properly without collisions.
fork.expectBranchRequest(expectedStagingForkBranch, null)
.expectBranchRequest(expectedNextUpdateBranch, null);
await instance.perform();
expect(gitClient.pushed.length).toBe(3);
expect(gitClient.pushed[0])
.toEqual(
getBranchPushMatcher({
baseRepo: repo,
baseBranch: 'master',
targetRepo: repo,
targetBranch: expectedNewBranch,
expectedCommits: [],
}),
'Expected feature-freeze branch to be created upstream and based on "master".');
expect(gitClient.pushed[1])
.toEqual(
getBranchPushMatcher({
baseBranch: 'master',
baseRepo: repo,
targetBranch: expectedStagingForkBranch,
targetRepo: fork,
expectedCommits: [{
message: `release: cut the v${expectedVersion} release`,
files: ['package.json', 'CHANGELOG.md'],
}],
}),
'Expected release staging branch to be created in fork.');
expect(gitClient.pushed[2])
.toEqual(
getBranchPushMatcher({
baseBranch: 'master',
baseRepo: repo,
targetBranch: expectedNextUpdateBranch,
targetRepo: fork,
expectedCommits: [
{
message: `release: bump the next branch to v${expectedNextVersion}`,
files: ['package.json']
},
{
message: `docs: release notes for the v${expectedVersion} release`,
files: ['CHANGELOG.md']
},
],
}),
'Expected next release-train update branch be created in fork.');
expect(externalCommands.invokeReleaseBuildCommand).toHaveBeenCalledTimes(1);
expect(releaseConfig.generateReleaseNotesForHead).toHaveBeenCalledTimes(1);
expect(npm.runNpmPublish).toHaveBeenCalledTimes(2);
expect(npm.runNpmPublish).toHaveBeenCalledWith(`${testTmpDir}/dist/pkg1`, 'next', undefined);
expect(npm.runNpmPublish).toHaveBeenCalledWith(`${testTmpDir}/dist/pkg2`, 'next', undefined);
}
});