Support Java release tags with exact abbreviated builds

Normalize jdk-prefixed numeric versions and pad abbreviated build requests so exact build constraints survive version matching. Keep platform and package validation consistent, document the syntax, and add regression coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Bruno Borges
2026-10-05 18:06:14 +02:00
parent d0e6e4dbf7
commit ca21d6d9c1
12 changed files with 250 additions and 94 deletions

View File

@@ -374,6 +374,49 @@ describe('findInToolcache', () => {
);
});
describe('exact build requests in tool-cache', () => {
afterEach(() => {
jest.resetAllMocks();
});
it.each(['jdk-27+35', '27+35', '27.0+35', '27.0.0+35'])(
'only matches the requested build for %s',
input => {
const findAllVersions = jest.mocked(tc.findAllVersions);
findAllVersions.mockReturnValue([
'27.0.1-35',
'27.0.0-36',
'27.0.0-ea.35',
'27.0.0-35'
]);
jest
.mocked(util.getToolcachePath)
.mockImplementation(
(toolname, version, architecture) =>
`/hostedtoolcache/${toolname}/${version}/${architecture}`
);
const distribution = new EmptyJavaBase({
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
expect(distribution['findInToolcache']()).toEqual({
version: '27.0.0+35',
path: '/hostedtoolcache/Java_Empty_jdk/27.0.0-35/x64'
});
findAllVersions.mockReturnValue([
'27.0.1-35',
'27.0.0-36',
'27.0.0-ea.35'
]);
expect(distribution['findInToolcache']()).toBeNull();
}
);
});
describe('setupJava', () => {
const actualJavaVersion = '11.0.9';
const installedJavaVersion = '11.0.8';
@@ -1749,6 +1792,15 @@ describe('normalizeVersion', () => {
const DummyJavaBase = JavaBase as any;
it.each([
['jdk-27+35', {version: '27.0.0+35', stable: true, latest: false}],
['27+35', {version: '27.0.0+35', stable: true, latest: false}],
['27.0+35', {version: '27.0.0+35', stable: true, latest: false}],
['jdk-27', {version: '27', stable: true, latest: false}],
['jdk-27.0', {version: '27.0', stable: true, latest: false}],
['jdk-27-ea', {version: '27', stable: false, latest: false}],
['jdk-27+35-ea', {version: '27.0.0+35', stable: false, latest: false}],
['jdk-27-ea.35', {version: '27.0.0+35', stable: false, latest: false}],
['jdk-26.0.2.1+1', {version: '26.0.2+1.1', stable: true, latest: false}],
['11', {version: '11', stable: true, latest: false}],
['11.0', {version: '11.0', stable: true, latest: false}],
['11.0.10', {version: '11.0.10', stable: true, latest: false}],
@@ -1778,6 +1830,24 @@ describe('normalizeVersion', () => {
);
});
it.each([
'jdk-',
'jdk-latest',
'jdk-27.x',
'jdk->=27',
'jdk8u442-b06',
'temurin-27+35',
'jdk-27-ea+35',
'jdk-27+',
'jdk-27+35..1',
'27+35..1',
'jdk-27..0+35'
])('rejects malformed or unsupported release tags: %s', input => {
expect(() =>
DummyJavaBase.prototype.normalizeVersion.call(null, input)
).toThrow(/is not valid SemVer notation for a Java version/);
});
it.each(['latest-ea', 'latest.1', 'LATEST-EA', ' latest-ea '])(
'normalizeVersion should throw a targeted error for latest combined with a qualifier (%s)',
version => {

View File

@@ -82,7 +82,7 @@ describe('getJavaDistribution', () => {
);
});
it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24'])(
it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24', 'jdk-23+35'])(
"rejects Temurin java-package 'jdk+jmods' for version %s",
async version => {
await expect(
@@ -96,7 +96,15 @@ describe('getJavaDistribution', () => {
}
);
it.each(['24', '24.0.1.1', '25.0.4.1+1', '25-ea', '>=21', 'latest'])(
it.each([
'24',
'24.0.1.1',
'25.0.4.1+1',
'25-ea',
'>=21',
'latest',
'jdk-27+35'
])(
"accepts Temurin java-package 'jdk+jmods' for version %s",
async version => {
expect(
@@ -117,6 +125,17 @@ describe('getJavaDistribution', () => {
).toBeNull();
});
it('does not change the distribution or package for a jdk-prefixed version', async () => {
const distribution = await getJavaDistribution(
JavaDistribution.Zulu,
installerOptions('jre', 'jdk-27+35')
);
expect(distribution!['distribution']).toBe('Zulu');
expect(distribution!['packageType']).toBe('jre');
expect(distribution!['version']).toBe('27.0.0+35');
});
it.each(['adopt', 'adopt-hotspot', 'adopt-openj9'])(
'does not support legacy Adopt distribution %s',
async distributionName => {

View File

@@ -378,7 +378,7 @@ describe('findPackageForDownload', () => {
expect(resolvedVersion.version).toBe('16.0.2+7');
});
describe('OpenJDK patch (respin) versions', () => {
describe('OpenJDK versions and release tags', () => {
const makeRelease = (
semverVersion: string,
openjdkVersion: string,
@@ -402,6 +402,21 @@ describe('findPackageForDownload', () => {
});
const respinManifest = [
makeRelease('27.0.1+35', '27.0.1+35', {
major: 27,
security: 1,
build: 35
}),
makeRelease('27.0.0+36', '27+36', {
major: 27,
security: 0,
build: 36
}),
makeRelease('27.0.0+35', '27+35', {
major: 27,
security: 0,
build: 35
}),
makeRelease('26.0.2+101', '26.0.2.1+1', {
major: 26,
security: 2,
@@ -427,6 +442,13 @@ describe('findPackageForDownload', () => {
];
it.each([
['jdk-27+35', '27.0.0+35'],
['27+35', '27.0.0+35'],
['27.0+35', '27.0.0+35'],
['jdk-27.0.0+35', '27.0.0+35'],
['jdk-27', '27.0.1+35'],
['27', '27.0.1+35'],
['jdk-26.0.2.1+1', '26.0.2+101'],
['26.0.2.1+1', '26.0.2+101'],
['26.0.2+10', '26.0.2+10'],
['26', '26.0.2+101'],
@@ -452,21 +474,27 @@ describe('findPackageForDownload', () => {
expect(resolvedVersion).not.toHaveProperty('openjdkVersion');
});
it('does not match a non-existent respin', async () => {
const distribution = new TemurinDistribution(
{
version: '26.0.2.2+1',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['getAvailableVersions'] = async () => respinManifest as any;
await expect(
distribution['findPackageForDownload'](distribution['version'])
).rejects.toThrow(/No matching version found for SemVer '26.0.2\+2.1'/);
});
it.each(['26.0.2.2+1', 'jdk-27+34', '27+34'])(
'does not match a non-existent build or respin: %s',
async input => {
const distribution = new TemurinDistribution(
{
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['getAvailableVersions'] = async () =>
respinManifest as any;
await expect(
distribution['findPackageForDownload'](distribution['version'])
).rejects.toThrow(
`No matching version found for SemVer '${distribution['version']}'`
);
}
);
});
it('version is found but binaries list is empty', async () => {