mirror of
https://gitea.com/actions/setup-java.git
synced 2026-08-05 02:31:18 +00:00
Add JDK caching
Cache resolved JDK tool-cache entries by exact platform and release identity, with a default-on cache-jdk input and explicit opt-out. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
beforeAll,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
|
||||
// Mock @actions/cache before importing source modules
|
||||
const real_cache_module = await import('@actions/cache');
|
||||
@@ -163,6 +164,32 @@ describe('cleanup', () => {
|
||||
|
||||
expect(spyCacheSave).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('saves the JDK cache without dependency caching', async () => {
|
||||
const key = 'setup-java-jdk-v1-Linux-x64-key';
|
||||
(core.getInput as jest.Mock).mockReturnValue('');
|
||||
(core.getState as jest.Mock<any>).mockImplementation((name: string) =>
|
||||
name === 'jdk-caches'
|
||||
? JSON.stringify([{key, path: '/toolcache/java'}])
|
||||
: ''
|
||||
);
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
spyCacheSave.mockResolvedValue(1);
|
||||
|
||||
await cleanup();
|
||||
|
||||
expect(spyCacheSave).toHaveBeenCalledWith(['/toolcache/java'], key);
|
||||
});
|
||||
|
||||
it('does not save a JDK cache when cache-jdk is disabled', async () => {
|
||||
(core.getInput as jest.Mock<any>).mockImplementation((name: string) =>
|
||||
name === 'cache-jdk' ? 'false' : ''
|
||||
);
|
||||
|
||||
await cleanup();
|
||||
|
||||
expect(spyCacheSave).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function resetState() {
|
||||
|
||||
@@ -70,6 +70,10 @@ jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
}
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../../src/jdk-cache.js', () => ({
|
||||
restoreJdk: jest.fn()
|
||||
}));
|
||||
|
||||
const real_util_module = await import('../../src/util.js');
|
||||
jest.unstable_mockModule('../../src/util.js', () => ({
|
||||
...real_util_module,
|
||||
@@ -86,6 +90,7 @@ jest.unstable_mockModule('../../src/util.js', () => ({
|
||||
const core = await import('@actions/core');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const util = await import('../../src/util.js');
|
||||
const jdkCache = await import('../../src/jdk-cache.js');
|
||||
const {JavaBase} = await import('../../src/distributions/base-installer.js');
|
||||
|
||||
class EmptyJavaBase extends JavaBase {
|
||||
@@ -465,6 +470,7 @@ describe('setupJava', () => {
|
||||
checkLatest: false,
|
||||
forceDownload: true
|
||||
});
|
||||
|
||||
const findInToolcache = jest.fn(() => ({
|
||||
version: actualJavaVersion,
|
||||
path: javaPathInstalled
|
||||
@@ -486,6 +492,37 @@ describe('setupJava', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('restores the exact resolved JDK before downloading', async () => {
|
||||
mockJavaBase = new EmptyJavaBase({
|
||||
version: '11',
|
||||
architecture: 'x86',
|
||||
packageType: 'jdk',
|
||||
checkLatest: true,
|
||||
cacheJdk: true
|
||||
});
|
||||
const downloadTool = jest.spyOn(mockJavaBase as any, 'downloadTool');
|
||||
(jdkCache.restoreJdk as jest.Mock).mockResolvedValue(true);
|
||||
jest
|
||||
.spyOn(mockJavaBase as any, 'getRestoredJdkPath')
|
||||
.mockReturnValue(javaPathInstalled);
|
||||
|
||||
await expect(mockJavaBase.setupJava()).resolves.toEqual({
|
||||
version: actualJavaVersion,
|
||||
path: javaPathInstalled
|
||||
});
|
||||
|
||||
expect(jdkCache.restoreJdk).toHaveBeenCalledWith({
|
||||
distribution: 'Empty',
|
||||
packageType: 'jdk',
|
||||
architecture: 'x86',
|
||||
version: actualJavaVersion,
|
||||
source: `some/random_url/java/${actualJavaVersion}`,
|
||||
path: path.join('Java_Empty_jdk', actualJavaVersion)
|
||||
});
|
||||
expect(downloadTool).not.toHaveBeenCalled();
|
||||
expect(spyCoreInfo).not.toHaveBeenCalledWith('Trying to download...');
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
{
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {}
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../src/cache-feature.js', () => ({
|
||||
isCacheFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
const cache = await import('@actions/cache');
|
||||
const core = await import('@actions/core');
|
||||
const cacheFeature = await import('../src/cache-feature.js');
|
||||
const {buildJdkCacheKey, restoreJdk, saveJdkCaches} =
|
||||
await import('../src/jdk-cache.js');
|
||||
|
||||
const jdk = {
|
||||
distribution: 'temurin',
|
||||
packageType: 'jdk',
|
||||
architecture: 'x64',
|
||||
version: '21.0.8+9',
|
||||
source: 'sha256:abc123',
|
||||
path: '/toolcache/Java_temurin_jdk/21.0.8-9'
|
||||
};
|
||||
|
||||
describe('JDK cache', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
process.env['RUNNER_OS'] = 'Linux';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
delete process.env['RUNNER_OS'];
|
||||
});
|
||||
|
||||
it('builds distinct keys for incompatible JDK identities', () => {
|
||||
const key = buildJdkCacheKey(jdk);
|
||||
|
||||
expect(key).toMatch(/^setup-java-jdk-v1-Linux-x64-[a-f0-9]{64}$/);
|
||||
expect(buildJdkCacheKey({...jdk, architecture: 'aarch64'})).not.toBe(key);
|
||||
expect(buildJdkCacheKey({...jdk, distribution: 'zulu'})).not.toBe(key);
|
||||
expect(buildJdkCacheKey({...jdk, packageType: 'jre'})).not.toBe(key);
|
||||
expect(buildJdkCacheKey({...jdk, version: '21.0.7+6'})).not.toBe(key);
|
||||
expect(buildJdkCacheKey({...jdk, source: 'sha256:def456'})).not.toBe(key);
|
||||
});
|
||||
|
||||
it('restores and records an exact JDK cache hit', async () => {
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(buildJdkCacheKey(jdk));
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
|
||||
await expect(restoreJdk(jdk)).resolves.toBe(true);
|
||||
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[jdk.path],
|
||||
buildJdkCacheKey(jdk)
|
||||
);
|
||||
const architecturePath = path.join(jdk.path, 'x64');
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(architecturePath);
|
||||
expect(fs.existsSync).toHaveBeenCalledWith(`${architecturePath}.complete`);
|
||||
expect(core.saveState).toHaveBeenCalledWith(
|
||||
'jdk-caches',
|
||||
expect.stringContaining(buildJdkCacheKey(jdk))
|
||||
);
|
||||
});
|
||||
|
||||
it('falls back to download when restoration fails', async () => {
|
||||
(cache.restoreCache as jest.Mock).mockRejectedValue(
|
||||
new Error('cache unavailable')
|
||||
);
|
||||
|
||||
await expect(restoreJdk(jdk)).resolves.toBe(false);
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
'Failed to restore JDK cache: cache unavailable'
|
||||
);
|
||||
});
|
||||
|
||||
it('saves a downloaded JDK recorded during restore', async () => {
|
||||
const key = buildJdkCacheKey(jdk);
|
||||
(core.getState as jest.Mock).mockReturnValue(
|
||||
JSON.stringify([{key, path: jdk.path}])
|
||||
);
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
(cache.saveCache as jest.Mock).mockResolvedValue(1);
|
||||
|
||||
await saveJdkCaches();
|
||||
|
||||
expect(cache.saveCache).toHaveBeenCalledWith([jdk.path], key);
|
||||
});
|
||||
|
||||
it('does not save an exact JDK cache hit again', async () => {
|
||||
const key = buildJdkCacheKey(jdk);
|
||||
(core.getState as jest.Mock).mockReturnValue(
|
||||
JSON.stringify([{key, path: jdk.path, matchedKey: key}])
|
||||
);
|
||||
|
||||
await saveJdkCaches();
|
||||
|
||||
expect(cache.saveCache).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -217,6 +217,7 @@ describe('setup action orchestration', () => {
|
||||
packageType: 'jdk',
|
||||
checkLatest: true,
|
||||
forceDownload: true,
|
||||
cacheJdk: true,
|
||||
setDefault: false,
|
||||
verifySignature: true,
|
||||
verifySignaturePublicKey: 'public-key'
|
||||
@@ -457,6 +458,7 @@ describe('setup action orchestration', () => {
|
||||
it('does not initialize cache modules when cache input is absent', async () => {
|
||||
inputs.set('distribution', 'temurin');
|
||||
multilineInputs.set('java-version', ['21']);
|
||||
booleanInputs.set('cache-jdk', false);
|
||||
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
|
||||
setupJava: jest.fn(async () => ({
|
||||
version: '21.0.4+7',
|
||||
@@ -468,6 +470,11 @@ describe('setup action orchestration', () => {
|
||||
|
||||
expect(cacheFeature.isCacheFeatureAvailable).not.toHaveBeenCalled();
|
||||
expect(cache.restore).not.toHaveBeenCalled();
|
||||
expect(factory.getJavaDistribution).toHaveBeenCalledWith(
|
||||
'temurin',
|
||||
expect.objectContaining({cacheJdk: false}),
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
it('reports unsupported distributions through core.setFailed', async () => {
|
||||
|
||||
Reference in New Issue
Block a user