Skip to content
Draft
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
186 changes: 186 additions & 0 deletions __tests__/distributors/jetbrains-installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import {
afterAll
} from '@jest/globals';
import https from 'https';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {HttpClient, HttpClientResponse} from '@actions/http-client';
import type {IncomingMessage} from 'http';
import {Readable} from 'stream';
Expand Down Expand Up @@ -476,6 +479,109 @@ describe('findPackageForDownload', () => {
}
);

describe('hotfix releases', () => {
beforeEach(() => {
jest.spyOn(HttpClient.prototype, 'getJson').mockResolvedValue({
statusCode: 200,
headers: {},
result: [
release('jbr-release-25.0.4b508.27', false),
release('jbr-release-25.0.4.1b635.9', false),
release('jbr-release-25.0.4.1b635.79', false),
release('jbr-release-25.0.3.2b1000.1', false)
]
});
jest.spyOn(HttpClient.prototype, 'head').mockResolvedValue(response(200));
});

it.each(['linux', 'osx', 'windows'])(
'selects the patched JBR archive and checksum on %s',
async platform => {
const distribution = new JetBrainsDistribution({
version: '25',
architecture: 'x64',
packageType: 'jdk',
checkLatest: true
});
distribution['getPlatformOption'] = () => platform;
const url = `https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-25.0.4.1-${platform}-x64-b635.79.tar.gz`;

const result = await distribution['findPackageForDownload']('25');

expect(result).toEqual({
version: '25.0.4+1.635.79',
url,
checksum: {
algorithm: 'sha512',
value: JETBRAINS_CHECKSUM,
source: `${url}.checksum`
}
});
expect(HttpClient.prototype.head).toHaveBeenCalledWith(url);
expect(spyHttpClientGet).toHaveBeenCalledTimes(1);
expect(spyHttpClientGet).toHaveBeenCalledWith(`${url}.checksum`);
}
);

it.each([
['latest', '25.0.4+1.635.79'],
['25.0.4', '25.0.4+1.635.79'],
['25.0.4.0', '25.0.4+508.27'],
['25.0.4.1', '25.0.4+1.635.79'],
['25.0.4.1+635.79', '25.0.4+1.635.79'],
['25.0.4+1.635.9', '25.0.4+1.635.9'],
['25.0.4+508.27', '25.0.4+508.27']
])('resolves %s to %s', async (input, expected) => {
const distribution = new JetBrainsDistribution({
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: true
});

await expect(
distribution['findPackageForDownload'](distribution['version'])
).resolves.toMatchObject({version: expected});
});

it('resolves early-access hotfix releases separately', async () => {
jest.spyOn(HttpClient.prototype, 'getJson').mockResolvedValue({
statusCode: 200,
headers: {},
result: [
release('jbr-release-25.0.4.1b635.79', true),
release('jbr-release-25.0.4.2b636.1', false)
]
});
const distribution = new JetBrainsDistribution({
version: '25.0.4.1-ea',
architecture: 'x64',
packageType: 'jdk',
checkLatest: true
});

await expect(
distribution['findPackageForDownload'](distribution['version'])
).resolves.toMatchObject({version: '25.0.4+1.635.79'});
});

it.each(['25.0.4.2', '25.0.4.1+635.80'])(
'does not substitute another patch or build for %s',
async input => {
const distribution = new JetBrainsDistribution({
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: true
});

await expect(
distribution['findPackageForDownload'](distribution['version'])
).rejects.toThrow(/No matching version found/);
}
);
});

it('version is not found', async () => {
const distribution = new JetBrainsDistribution({
version: '8.0.452',
Expand Down Expand Up @@ -569,4 +675,84 @@ describe('findPackageForDownload', () => {
source: `${result.url}.checksum`
});
});

describe('compareVersions', () => {
it.each([
['25.0.4+1.635.79', '25.0.4+508.27', 1],
['25.0.5+1.1', '25.0.4+1.635.79', 1],
['25.0.4+10.1.1', '25.0.4+2.999.99', 1],
['25.0.4+1.635.79', '25.0.4+1.635.9', 1],
['25.0.4+1.636.1', '25.0.4+1.635.79', 1],
['25.0.4+0.635.79', '25.0.4+635.79', 0],
['11.0.11+1522', '11.0.11+1522.1', -1],
['25.0.4', '25.0.4+508.27', -1]
])('compares %s with %s', (first, second, expected) => {
const distribution = new JetBrainsDistribution({
version: '25',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

expect(Math.sign(distribution['compareVersions'](first, second))).toBe(
expected
);
expect(
Math.sign(distribution['compareVersions'](second, first)) || 0
).toBe(-expected || 0);
});
});

describe('hotfix releases in the tool cache', () => {
let cacheRoot: string;
const previousToolcache = process.env['RUNNER_TOOL_CACHE'];

beforeEach(async () => {
cacheRoot = await fs.promises.mkdtemp(
path.join(os.tmpdir(), 'jetbrains-')
);
process.env['RUNNER_TOOL_CACHE'] = cacheRoot;
for (const version of ['25.0.4-508.27', '25.0.4-1.635.79']) {
const javaPath = path.join(
cacheRoot,
'Java_JetBrains_jdk',
version,
'x64'
);
await fs.promises.mkdir(javaPath, {recursive: true});
await fs.promises.writeFile(`${javaPath}.complete`, '');
}
});

afterEach(async () => {
if (previousToolcache === undefined) {
delete process.env['RUNNER_TOOL_CACHE'];
} else {
process.env['RUNNER_TOOL_CACHE'] = previousToolcache;
}
await fs.promises.rm(cacheRoot, {recursive: true, force: true});
});

it.each(['25', '25.0.4.1', '25.0.4.1+635.79'])(
'reuses the patched JBR for %s',
input => {
const distribution = new JetBrainsDistribution({
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});

expect(distribution['findInToolcache']()).toEqual({
version: '25.0.4+1.635.79',
path: path.join(
cacheRoot,
'Java_JetBrains_jdk',
'25.0.4-1.635.79',
'x64'
)
});
}
);
});
});
44 changes: 41 additions & 3 deletions dist/setup/282.index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ class JetBrainsDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
async findPackageForDownload(range) {
const versionsRaw = await this.getAvailableVersions();
const versions = versionsRaw.map(v => {
const formattedVersion = `${v.semver}+${v.build}`;
const formattedVersion = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .normalizeJavaVersionToSemver */ .zZ)(`${v.semver}+${v.build}`);
return {
version: formattedVersion,
url: v.url
};
});
const satisfiedVersions = versions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .isVersionSatisfies */ .y)(range, item.version))
.filter(item => this.isVersionSatisfied(range, item.version))
.sort((a, b) => {
return -semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.version, b.version);
return -this.compareVersions(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
Expand All @@ -59,6 +59,44 @@ class JetBrainsDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
checksum: await this.fetchChecksum(`${resolvedFullVersion.url}.checksum`, ['sha512', 'sha256'])
};
}
normalizeVersion(version) {
const normalized = super.normalizeVersion(version);
if (/^\d+\.\d+\.\d+(?:\.\d+)+(?:-ea)?$/.test(version)) {
// A patch-only request must match any JBR build of that patch.
normalized.version = version.replace(/-ea$/, '');
}
return normalized;
}
isVersionSatisfied(range, version) {
const patchRequest = range.match(/^(\d+\.\d+\.\d+)((?:\.\d+)+)$/);
if (patchRequest) {
const candidate = semver__WEBPACK_IMPORTED_MODULE_3___default().parse(version);
return (candidate !== null &&
semver__WEBPACK_IMPORTED_MODULE_3___default().eq(patchRequest[1], candidate) &&
(candidate.build.slice(0, -2).join('.') || '0') ===
patchRequest[2].slice(1));
}
return super.isVersionSatisfied(range, version);
}
compareVersions(a, b) {
const mainComparison = semver__WEBPACK_IMPORTED_MODULE_3___default().compare(a, b);
if (mainComparison !== 0) {
return mainComparison;
}
// JBR builds have two components (one in older releases). Any preceding
// metadata components are Java patch fields and take priority over builds.
const first = new (semver__WEBPACK_IMPORTED_MODULE_3___default().SemVer)(a).build;
const second = new (semver__WEBPACK_IMPORTED_MODULE_3___default().SemVer)(b).build;
const firstPatch = first.slice(0, -2);
const secondPatch = second.slice(0, -2);
for (let index = 0; index < Math.max(firstPatch.length, secondPatch.length); index++) {
const patchComparison = semver__WEBPACK_IMPORTED_MODULE_3___default().compareIdentifiers(firstPatch[index] ?? '0', secondPatch[index] ?? '0');
if (patchComparison !== 0) {
return patchComparison;
}
}
return semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(first.length ? `0.0.0+${first.slice(-2).join('.')}` : '0.0.0', second.length ? `0.0.0+${second.slice(-2).join('.')}` : '0.0.0');
}
async downloadTool(javaRelease) {
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
const javaArchivePath = await this.downloadAndVerify(javaRelease);
Expand Down
65 changes: 61 additions & 4 deletions src/distributions/jetbrains/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {
extractJdkFile,
getGitHubToken,
getNextPageUrlFromLinkHeader,
isVersionSatisfies,
MAX_PAGINATION_PAGES,
normalizeJavaVersionToSemver,
validatePaginationUrl
} from '../../util.js';
import {OutgoingHttpHeaders} from 'http';
Expand All @@ -38,7 +38,9 @@ export class JetBrainsDistribution extends JavaBase {
const versionsRaw = await this.getAvailableVersions();

const versions = versionsRaw.map(v => {
const formattedVersion = `${v.semver}+${v.build}`;
const formattedVersion = normalizeJavaVersionToSemver(
`${v.semver}+${v.build}`
);

return {
version: formattedVersion,
Expand All @@ -47,9 +49,9 @@ export class JetBrainsDistribution extends JavaBase {
});

const satisfiedVersions = versions
.filter(item => isVersionSatisfies(range, item.version))
.filter(item => this.isVersionSatisfied(range, item.version))
.sort((a, b) => {
return -semver.compareBuild(a.version, b.version);
return -this.compareVersions(a.version, b.version);
});

const resolvedFullVersion =
Expand All @@ -74,6 +76,61 @@ export class JetBrainsDistribution extends JavaBase {
};
}

protected normalizeVersion(version: string) {
const normalized = super.normalizeVersion(version);
if (/^\d+\.\d+\.\d+(?:\.\d+)+(?:-ea)?$/.test(version)) {
// A patch-only request must match any JBR build of that patch.
normalized.version = version.replace(/-ea$/, '');
}
return normalized;
}

protected isVersionSatisfied(range: string, version: string): boolean {
const patchRequest = range.match(/^(\d+\.\d+\.\d+)((?:\.\d+)+)$/);
if (patchRequest) {
const candidate = semver.parse(version);
return (
candidate !== null &&
semver.eq(patchRequest[1], candidate) &&
(candidate.build.slice(0, -2).join('.') || '0') ===
patchRequest[2].slice(1)
);
}
return super.isVersionSatisfied(range, version);
}

protected compareVersions(a: string, b: string): number {
const mainComparison = semver.compare(a, b);
if (mainComparison !== 0) {
return mainComparison;
}

// JBR builds have two components (one in older releases). Any preceding
// metadata components are Java patch fields and take priority over builds.
const first = new semver.SemVer(a).build;
const second = new semver.SemVer(b).build;
const firstPatch = first.slice(0, -2);
const secondPatch = second.slice(0, -2);
for (
let index = 0;
index < Math.max(firstPatch.length, secondPatch.length);
index++
) {
const patchComparison = semver.compareIdentifiers(
firstPatch[index] ?? '0',
secondPatch[index] ?? '0'
);
if (patchComparison !== 0) {
return patchComparison;
}
}

return semver.compareBuild(
first.length ? `0.0.0+${first.slice(-2).join('.')}` : '0.0.0',
second.length ? `0.0.0+${second.slice(-2).join('.')}` : '0.0.0'
);
}

protected async downloadTool(
javaRelease: JavaDownloadRelease
): Promise<JavaInstallerResults> {
Expand Down
Loading