test(dev-infra): add testing for integration of ReleaseNotes in publishing tooling (#41967)
Add testing of the ReleaseNotes integration with the release publishing tooling. PR Close #41967
This commit is contained in:
parent
e89e3cb375
commit
6d87895ffd
|
@ -5855,18 +5855,20 @@ function getLocalChangelogFilePath(projectDir) {
|
|||
}
|
||||
/** Release note generation. */
|
||||
class ReleaseNotes {
|
||||
constructor(version, config) {
|
||||
constructor(version, startingRef, endingRef) {
|
||||
this.version = version;
|
||||
this.config = config;
|
||||
this.startingRef = startingRef;
|
||||
this.endingRef = endingRef;
|
||||
/** An instance of GitClient. */
|
||||
this.git = GitClient.getInstance();
|
||||
/** A promise resolving to a list of Commits since the latest semver tag on the branch. */
|
||||
this.commits = getCommitsInRange(this.git.getLatestSemverTag().format(), 'HEAD');
|
||||
this.commits = this.getCommitsInRange(this.startingRef, this.endingRef);
|
||||
/** The configuration for release notes. */
|
||||
this.config = this.getReleaseConfig().releaseNotes;
|
||||
}
|
||||
/** Construct a release note generation instance. */
|
||||
static fromLatestTagToHead(version, config) {
|
||||
static fromRange(version, startingRef, endingRef) {
|
||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||
return new ReleaseNotes(version, config);
|
||||
return new ReleaseNotes(version, startingRef, endingRef);
|
||||
});
|
||||
}
|
||||
/** Retrieve the release note generated for a Github Release. */
|
||||
|
@ -5888,7 +5890,7 @@ class ReleaseNotes {
|
|||
promptForReleaseTitle() {
|
||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||
if (this.title === undefined) {
|
||||
if (this.config.releaseNotes.useReleaseTitle) {
|
||||
if (this.config.useReleaseTitle) {
|
||||
this.title = yield promptInput('Please provide a title for the release:');
|
||||
}
|
||||
else {
|
||||
|
@ -5906,14 +5908,24 @@ class ReleaseNotes {
|
|||
commits: yield this.commits,
|
||||
github: this.git.remoteConfig,
|
||||
version: this.version.format(),
|
||||
groupOrder: this.config.releaseNotes.groupOrder,
|
||||
hiddenScopes: this.config.releaseNotes.hiddenScopes,
|
||||
groupOrder: this.config.groupOrder,
|
||||
hiddenScopes: this.config.hiddenScopes,
|
||||
title: yield this.promptForReleaseTitle(),
|
||||
});
|
||||
}
|
||||
return this.renderContext;
|
||||
});
|
||||
}
|
||||
// These methods are used for access to the utility functions while allowing them to be
|
||||
// overwritten in subclasses during testing.
|
||||
getCommitsInRange(from, to) {
|
||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||
return getCommitsInRange(from, to);
|
||||
});
|
||||
}
|
||||
getReleaseConfig(config) {
|
||||
return getReleaseConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6194,7 +6206,7 @@ class ReleaseAction {
|
|||
*/
|
||||
stageVersionForBranchAndCreatePullRequest(newVersion, pullRequestBaseBranch) {
|
||||
return tslib.__awaiter(this, void 0, void 0, function* () {
|
||||
const releaseNotes = yield ReleaseNotes.fromLatestTagToHead(newVersion, this.config);
|
||||
const releaseNotes = yield ReleaseNotes.fromRange(newVersion, this.git.getLatestSemverTag().format(), 'HEAD');
|
||||
yield this.updateProjectVersion(newVersion);
|
||||
yield this.prependReleaseNotesToChangelog(releaseNotes);
|
||||
yield this.waitForEditsAndCreateReleaseCommit(newVersion);
|
||||
|
|
|
@ -6,8 +6,6 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as semver from 'semver';
|
||||
|
||||
import {assertNoErrors, getConfig, NgDevConfig} from '../../utils/config';
|
||||
|
||||
/** Interface describing a built package. */
|
||||
|
|
|
@ -350,7 +350,8 @@ export abstract class ReleaseAction {
|
|||
protected async stageVersionForBranchAndCreatePullRequest(
|
||||
newVersion: semver.SemVer, pullRequestBaseBranch: string):
|
||||
Promise<{releaseNotes: ReleaseNotes, pullRequest: PullRequest}> {
|
||||
const releaseNotes = await ReleaseNotes.fromLatestTagToHead(newVersion, this.config);
|
||||
const releaseNotes =
|
||||
await ReleaseNotes.fromRange(newVersion, this.git.getLatestSemverTag().format(), 'HEAD');
|
||||
await this.updateProjectVersion(newVersion);
|
||||
await this.prependReleaseNotesToChangelog(releaseNotes);
|
||||
await this.waitForEditsAndCreateReleaseCommit(newVersion);
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
import {renderFile} from 'ejs';
|
||||
import {join} from 'path';
|
||||
import * as semver from 'semver';
|
||||
import {CommitFromGitLog} from '../../../commit-message/parse';
|
||||
|
||||
import {getCommitsInRange} from '../../../commit-message/utils';
|
||||
import {promptInput} from '../../../utils/console';
|
||||
import {GitClient} from '../../../utils/git/index';
|
||||
import {ReleaseConfig} from '../../config/index';
|
||||
import {DevInfraReleaseConfig, getReleaseConfig, ReleaseNotesConfig} from '../../config/index';
|
||||
import {changelogPath} from '../constants';
|
||||
import {RenderContext} from './context';
|
||||
|
||||
|
@ -21,13 +22,10 @@ export function getLocalChangelogFilePath(projectDir: string): string {
|
|||
return join(projectDir, changelogPath);
|
||||
}
|
||||
|
||||
|
||||
/** Release note generation. */
|
||||
export class ReleaseNotes {
|
||||
/** Construct a release note generation instance. */
|
||||
static async fromLatestTagToHead(version: semver.SemVer, config: ReleaseConfig):
|
||||
Promise<ReleaseNotes> {
|
||||
return new ReleaseNotes(version, config);
|
||||
static async fromRange(version: semver.SemVer, startingRef: string, endingRef: string) {
|
||||
return new ReleaseNotes(version, startingRef, endingRef);
|
||||
}
|
||||
|
||||
/** An instance of GitClient. */
|
||||
|
@ -37,9 +35,13 @@ export class ReleaseNotes {
|
|||
/** The title to use for the release. */
|
||||
private title: string|false|undefined;
|
||||
/** A promise resolving to a list of Commits since the latest semver tag on the branch. */
|
||||
private commits = getCommitsInRange(this.git.getLatestSemverTag().format(), 'HEAD');
|
||||
private commits: Promise<CommitFromGitLog[]> =
|
||||
this.getCommitsInRange(this.startingRef, this.endingRef);
|
||||
/** The configuration for release notes. */
|
||||
private config: ReleaseNotesConfig = this.getReleaseConfig().releaseNotes;
|
||||
|
||||
private constructor(public readonly version: semver.SemVer, private config: ReleaseConfig) {}
|
||||
protected constructor(
|
||||
public version: semver.SemVer, private startingRef: string, private endingRef: string) {}
|
||||
|
||||
/** Retrieve the release note generated for a Github Release. */
|
||||
async getGithubReleaseEntry(): Promise<string> {
|
||||
|
@ -61,7 +63,7 @@ export class ReleaseNotes {
|
|||
*/
|
||||
async promptForReleaseTitle() {
|
||||
if (this.title === undefined) {
|
||||
if (this.config.releaseNotes.useReleaseTitle) {
|
||||
if (this.config.useReleaseTitle) {
|
||||
this.title = await promptInput('Please provide a title for the release:');
|
||||
} else {
|
||||
this.title = false;
|
||||
|
@ -77,11 +79,22 @@ export class ReleaseNotes {
|
|||
commits: await this.commits,
|
||||
github: this.git.remoteConfig,
|
||||
version: this.version.format(),
|
||||
groupOrder: this.config.releaseNotes.groupOrder,
|
||||
hiddenScopes: this.config.releaseNotes.hiddenScopes,
|
||||
groupOrder: this.config.groupOrder,
|
||||
hiddenScopes: this.config.hiddenScopes,
|
||||
title: await this.promptForReleaseTitle(),
|
||||
});
|
||||
}
|
||||
return this.renderContext;
|
||||
}
|
||||
|
||||
|
||||
// These methods are used for access to the utility functions while allowing them to be
|
||||
// overwritten in subclasses during testing.
|
||||
protected async getCommitsInRange(from: string, to?: string) {
|
||||
return getCommitsInRange(from, to);
|
||||
}
|
||||
|
||||
protected getReleaseConfig(config?: Partial<DevInfraReleaseConfig>) {
|
||||
return getReleaseConfig(config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import {actions} from '../actions/index';
|
|||
import {changelogPath} from '../constants';
|
||||
import {ReleaseNotes} from '../release-notes/release-notes';
|
||||
|
||||
import {fakeNpmPackageQueryRequest, getChangelogForVersion, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
||||
import {fakeNpmPackageQueryRequest, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils';
|
||||
|
||||
describe('common release action logic', () => {
|
||||
const baseReleaseTrains: ActiveReleaseTrains = {
|
||||
|
@ -87,7 +87,6 @@ describe('common release action logic', () => {
|
|||
|
||||
describe('changelog cherry-picking', () => {
|
||||
const {version, branchName} = baseReleaseTrains.latest;
|
||||
const fakeReleaseNotes = getChangelogForVersion(version.format());
|
||||
const forkBranchName = `changelog-cherry-pick-${version}`;
|
||||
|
||||
it('should prepend the changelog to the next branch', async () => {
|
||||
|
@ -96,8 +95,7 @@ describe('common release action logic', () => {
|
|||
|
||||
// Expect the changelog to be fetched and return a fake changelog to test that
|
||||
// it is properly appended. Also expect a pull request to be created in the fork.
|
||||
repo.expectChangelogFetch(branchName, fakeReleaseNotes)
|
||||
.expectFindForkRequest(fork)
|
||||
repo.expectFindForkRequest(fork)
|
||||
.expectPullRequestToBeCreated('master', fork, forkBranchName, 200)
|
||||
.expectPullRequestWait(200);
|
||||
|
||||
|
@ -107,7 +105,7 @@ describe('common release action logic', () => {
|
|||
await instance.testCherryPickWithPullRequest(version, branchName);
|
||||
|
||||
const changelogContent = readFileSync(join(testTmpDir, changelogPath), 'utf8');
|
||||
expect(changelogContent).toEqual(`${fakeReleaseNotes}Existing changelog`);
|
||||
expect(changelogContent).toEqual(`Changelog Entry for 10.0.1\n\nExisting changelog`);
|
||||
});
|
||||
|
||||
it('should push changes to a fork for creating a pull request', async () => {
|
||||
|
@ -116,8 +114,7 @@ describe('common release action logic', () => {
|
|||
|
||||
// Expect the changelog to be fetched and return a fake changelog to test that
|
||||
// it is properly appended. Also expect a pull request to be created in the fork.
|
||||
repo.expectChangelogFetch(branchName, fakeReleaseNotes)
|
||||
.expectFindForkRequest(fork)
|
||||
repo.expectFindForkRequest(fork)
|
||||
.expectPullRequestToBeCreated('master', fork, forkBranchName, 200)
|
||||
.expectPullRequestWait(200);
|
||||
|
||||
|
@ -155,12 +152,12 @@ class TestAction extends ReleaseAction {
|
|||
}
|
||||
|
||||
async testBuildAndPublish(version: semver.SemVer, publishBranch: string, distTag: string) {
|
||||
const releaseNotes = await ReleaseNotes.fromLatestTagToHead(version, this.config);
|
||||
const releaseNotes = await ReleaseNotes.fromRange(version, '', '');
|
||||
await this.buildAndPublish(releaseNotes, publishBranch, distTag);
|
||||
}
|
||||
|
||||
async testCherryPickWithPullRequest(version: semver.SemVer, branch: string) {
|
||||
const releaseNotes = await ReleaseNotes.fromLatestTagToHead(version, this.config);
|
||||
const releaseNotes = await ReleaseNotes.fromRange(version, '', '');
|
||||
await this.cherryPickChangelogIntoNextBranch(releaseNotes, branch);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,13 +60,6 @@ export class GithubTestingRepo {
|
|||
return this;
|
||||
}
|
||||
|
||||
expectChangelogFetch(branch: string, content: string): this {
|
||||
nock(this.repoApiUrl).get(`/contents/%2FCHANGELOG.md`).query(p => p.ref === branch).reply(200, {
|
||||
content: new Buffer(content).toString('base64')
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
expectCommitRequest(sha: string, message: string): this {
|
||||
nock(this.repoApiUrl).get(`/commits/${sha}`).reply(200, {commit: {message}});
|
||||
return this;
|
||||
|
|
|
@ -13,7 +13,7 @@ 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';
|
||||
import {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 () => {
|
||||
|
@ -84,7 +84,6 @@ describe('move next into feature-freeze action', () => {
|
|||
'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,
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @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 * as semver from 'semver';
|
||||
|
||||
import {DevInfraReleaseConfig, ReleaseConfig} from '../../../config';
|
||||
import {ReleaseNotes} from '../../release-notes/release-notes';
|
||||
|
||||
/**
|
||||
* Mock version of the ReleaseNotes for testing, preventing actual calls to git for commits and
|
||||
* returning versioned entry strings.
|
||||
*/
|
||||
class MockReleaseNotes extends ReleaseNotes {
|
||||
static async fromRange(version: semver.SemVer, startingRef: string, endingRef: string) {
|
||||
return new MockReleaseNotes(version, startingRef, endingRef);
|
||||
}
|
||||
|
||||
async getChangelogEntry() {
|
||||
return `Changelog Entry for ${this.version}`;
|
||||
}
|
||||
|
||||
async getGithubReleaseEntry() {
|
||||
return `Github Release Entry for ${this.version}`;
|
||||
}
|
||||
|
||||
// Overrides of utility functions which call out to other tools and are unused in this mock.
|
||||
protected async getCommitsInRange(from: string, to?: string) {
|
||||
return [];
|
||||
}
|
||||
protected getReleaseConfig(config?: Partial<DevInfraReleaseConfig>) {
|
||||
return {} as ReleaseConfig;
|
||||
}
|
||||
}
|
||||
|
||||
/** Replace the ReleaseNotes static builder function with the MockReleaseNotes builder function. */
|
||||
export function installMockReleaseNotes() {
|
||||
spyOn(ReleaseNotes, 'fromRange').and.callFake(MockReleaseNotes.fromRange);
|
||||
}
|
|
@ -11,7 +11,6 @@ import * as nock from 'nock';
|
|||
import {join} from 'path';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import * as commitMessageUtils from '../../../commit-message/utils';
|
||||
import {GithubConfig} from '../../../utils/config';
|
||||
import * as console from '../../../utils/console';
|
||||
import {getBranchPushMatcher, installVirtualGitClientSpies, VirtualGitClient} from '../../../utils/testing';
|
||||
|
@ -22,9 +21,9 @@ import {_npmPackageInfoCache, NpmPackageInfo} from '../../versioning/npm-registr
|
|||
import {ReleaseAction, ReleaseActionConstructor} from '../actions';
|
||||
import * as constants from '../constants';
|
||||
import * as externalCommands from '../external-commands';
|
||||
import {buildDateStamp} from '../release-notes/context';
|
||||
|
||||
import {GithubTestingRepo} from './github-api-testing';
|
||||
import {installMockReleaseNotes} from './release-notes/release-notes-utils';
|
||||
|
||||
/**
|
||||
* Temporary directory which will be used as project directory in tests. Note that
|
||||
|
@ -70,7 +69,7 @@ export function setupReleaseActionForTesting<T extends ReleaseAction>(
|
|||
actionCtor: ReleaseActionConstructor<T>, active: ActiveReleaseTrains,
|
||||
isNextPublishedToNpm = true): TestReleaseAction<T> {
|
||||
installVirtualGitClientSpies();
|
||||
spyOn(commitMessageUtils, 'getCommitsInRange').and.returnValue(Promise.resolve([]));
|
||||
installMockReleaseNotes();
|
||||
|
||||
// Reset existing HTTP interceptors.
|
||||
nock.cleanAll();
|
||||
|
@ -123,11 +122,6 @@ export function parse(version: string): semver.SemVer {
|
|||
return semver.parse(version)!;
|
||||
}
|
||||
|
||||
/** Gets a changelog for the specified version. */
|
||||
export function getChangelogForVersion(version: string): string {
|
||||
return `<a name="${version}"></a>\n# ${version} (${buildDateStamp()})\n\n\n`;
|
||||
}
|
||||
|
||||
export async function expectStagingAndPublishWithoutCherryPick(
|
||||
action: TestReleaseAction, expectedBranch: string, expectedVersion: string,
|
||||
expectedNpmDistTag: string) {
|
||||
|
@ -197,7 +191,6 @@ export async function expectStagingAndPublishWithCherryPick(
|
|||
'STAGING_COMMIT_SHA', `release: cut the v${expectedVersion} release\n\nPR Close #200.`)
|
||||
.expectTagToBeCreated(expectedTagName, 'STAGING_COMMIT_SHA')
|
||||
.expectReleaseToBeCreated(`v${expectedVersion}`, expectedTagName)
|
||||
.expectChangelogFetch(expectedBranch, getChangelogForVersion(expectedVersion))
|
||||
.expectPullRequestToBeCreated('master', fork, expectedCherryPickForkBranch, 300)
|
||||
.expectPullRequestWait(300);
|
||||
|
||||
|
|
Loading…
Reference in New Issue