mirror of
https://github.com/actions/setup-java.git
synced 2026-08-01 16:22:58 +00:00
Optimize Maven configuration warm path (#1182)
* Optimize Maven configuration warm path Avoid eager Maven XML initialization on warm JDK runs by using deterministic serializers for new Maven settings/toolchains files, lazy-loading xmlbuilder2 for existing toolchains merges, and deferring Maven configuration modules until after Java setup. Add targeted tests for XML escaping, lazy xmlbuilder2 loading, concurrent Maven configuration, and a manual benchmark workflow for warm-path validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dc58426-5e20-44cb-af16-8da0965fac3b * Address Maven optimization PR feedback Make the toolchain XML generator consistently async, remove redundant Maven configuration await handling, and reuse the existing XML test helper. Configure CodeQL to skip generated dist output so newly split vendored chunks do not report duplicate generated-code alerts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dc58426-5e20-44cb-af16-8da0965fac3b * Apply rubber duck review suggestions Document XML attribute escaping, simplify Maven configuration awaiting, and add a regression test that feeds fast-path toolchains output into the merge path. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dc58426-5e20-44cb-af16-8da0965fac3b * Delete .github/codeql/codeql-config.yml * Update codeql-analysis.yml * Replace xmlbuilder2 in Maven toolchain merge Use fast-xml-parser for existing toolchains.xml parsing and serialize merged Maven toolchains deterministically. This removes the bundled xmlbuilder2 DOM/XML builder chunk from dist while preserving merge behavior for custom attributes, custom toolchains, partial entries, duplicate filtering, and escaping. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dc58426-5e20-44cb-af16-8da0965fac3b * Move Maven benchmark out of setup-java Remove the Maven warm-path benchmark workflow and helper script from setup-java. Benchmark coverage is being moved to actions/setup-java-benchmarks so this action repository only carries the runtime optimization, tests, and generated distribution artifacts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4dc58426-5e20-44cb-af16-8da0965fac3b --------- Copilot-Session: 4dc58426-5e20-44cb-af16-8da0965fac3b
This commit is contained in:
@@ -13,6 +13,7 @@ import * as io from '@actions/io';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import os from 'os';
|
||||
import {XMLParser} from 'fast-xml-parser';
|
||||
|
||||
// Mock @actions/core before importing source modules that depend on it
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
@@ -271,6 +272,41 @@ describe('auth tests', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('escapes settings.xml values while preserving parsed semantics', () => {
|
||||
const id = `packages&<>"'é`;
|
||||
const username = `USER&<>"'é`;
|
||||
const password = `TOKEN&<>"'é`;
|
||||
const gpgPassphrase = `GPG&<>"'é`;
|
||||
|
||||
const xml = auth.generate(id, username, password, gpgPassphrase);
|
||||
const parsed = parseXmlObject(xml) as any;
|
||||
|
||||
expect(parsed.settings.interactiveMode).toBe('false');
|
||||
expect(xmlElementText(xml, 'id')).toBe(id);
|
||||
expect(xmlElementText(xml, 'username')).toBe(`\${env.${username}}`);
|
||||
expect(xmlElementText(xml, 'password')).toBe(`\${env.${password}}`);
|
||||
expect(xmlElementText(xml, 'gpg.passphraseEnvName')).toBe(gpgPassphrase);
|
||||
expect(parsed.settings.activeProfiles.activeProfile).toBe('setup-java-gpg');
|
||||
});
|
||||
|
||||
function xmlElementText(xml: string, tagName: string): string {
|
||||
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
|
||||
expect(match).not.toBeNull();
|
||||
return (parseXmlObject(`<value>${match?.[1]}</value>`) as {value: string})
|
||||
.value;
|
||||
}
|
||||
|
||||
function parseXmlObject(xml: string): unknown {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: '@',
|
||||
parseAttributeValue: false,
|
||||
parseTagValue: false,
|
||||
trimValues: true
|
||||
});
|
||||
return parser.parse(xml);
|
||||
}
|
||||
|
||||
it('uses deprecated input aliases and warns', () => {
|
||||
const mockGetInput = core.getInput as jest.MockedFunction<
|
||||
typeof core.getInput
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import {describe, expect, it, jest} from '@jest/globals';
|
||||
|
||||
const mockXmlBuilderFactory = jest.fn();
|
||||
const mockParse = jest.fn(() => ({
|
||||
toolchains: {
|
||||
toolchain: [
|
||||
{
|
||||
type: 'foo',
|
||||
provides: {id: 'custom'},
|
||||
configuration: {fooHome: '/opt/foo'}
|
||||
}
|
||||
]
|
||||
}
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('fast-xml-parser', () => {
|
||||
mockXmlBuilderFactory();
|
||||
return {
|
||||
XMLParser: jest.fn().mockImplementation(() => ({
|
||||
parse: mockParse
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
const toolchains = await import('../src/toolchains.js');
|
||||
|
||||
describe('Maven XML loading', () => {
|
||||
it('does not load fast-xml-parser for new toolchains.xml generation', async () => {
|
||||
const xml = await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
'21',
|
||||
'temurin',
|
||||
'temurin_21',
|
||||
'/opt/java/21'
|
||||
);
|
||||
|
||||
expect(xml).toContain('<id>temurin_21</id>');
|
||||
expect(mockXmlBuilderFactory).not.toHaveBeenCalled();
|
||||
expect(mockParse).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('loads fast-xml-parser for existing toolchains.xml merge generation', async () => {
|
||||
await expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
'<toolchains><toolchain><type>foo</type></toolchain></toolchains>',
|
||||
'21',
|
||||
'temurin',
|
||||
'temurin_21',
|
||||
'/opt/java/21'
|
||||
)
|
||||
).resolves.toContain('<id>temurin_21</id>');
|
||||
|
||||
expect(mockXmlBuilderFactory).toHaveBeenCalledTimes(1);
|
||||
expect(mockParse).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -41,6 +41,10 @@ jest.unstable_mockModule('../src/toolchains.js', () => ({
|
||||
configureToolchains: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../src/toolchain-ids.js', () => ({
|
||||
validateToolchainIds: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../src/cache.js', () => ({
|
||||
restore: jest.fn()
|
||||
}));
|
||||
@@ -72,6 +76,7 @@ const core = await import('@actions/core');
|
||||
const fs = (await import('fs')).default;
|
||||
const util = await import('../src/util.js');
|
||||
const toolchains = await import('../src/toolchains.js');
|
||||
const toolchainIds = await import('../src/toolchain-ids.js');
|
||||
const cache = await import('../src/cache.js');
|
||||
const cacheFeature = await import('../src/cache-feature.js');
|
||||
const factory = await import('../src/distributions/distribution-factory.js');
|
||||
@@ -109,6 +114,9 @@ describe('setup action orchestration', () => {
|
||||
}
|
||||
);
|
||||
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
(toolchainIds.validateToolchainIds as jest.Mock).mockImplementation(
|
||||
() => undefined
|
||||
);
|
||||
(toolchains.configureToolchains as jest.Mock).mockResolvedValue(undefined);
|
||||
(auth.configureAuthentication as jest.Mock).mockResolvedValue(undefined);
|
||||
(cache.restore as jest.Mock).mockResolvedValue(undefined);
|
||||
@@ -215,7 +223,7 @@ describe('setup action orchestration', () => {
|
||||
},
|
||||
'/tmp/java.tar.gz'
|
||||
);
|
||||
expect(toolchains.validateToolchainIds).toHaveBeenCalledWith(
|
||||
expect(toolchainIds.validateToolchainIds).toHaveBeenCalledWith(
|
||||
[],
|
||||
'.sdkmanrc',
|
||||
['file-jdk']
|
||||
@@ -342,23 +350,29 @@ describe('setup action orchestration', () => {
|
||||
);
|
||||
|
||||
expect(
|
||||
(problemMatcher.configureProblemMatcher as jest.Mock).mock
|
||||
.invocationCallOrder[0]
|
||||
).toBeLessThan(
|
||||
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(
|
||||
(problemMatcher.configureProblemMatcher as jest.Mock).mock
|
||||
.invocationCallOrder[0]
|
||||
).toBeLessThan(
|
||||
(toolchains.configureToolchains as jest.Mock).mock
|
||||
.invocationCallOrder[0]
|
||||
).toBeLessThan(
|
||||
(problemMatcher.configureProblemMatcher as jest.Mock).mock
|
||||
.invocationCallOrder[0]
|
||||
);
|
||||
expect(
|
||||
(problemMatcher.configureProblemMatcher as jest.Mock).mock
|
||||
.invocationCallOrder[0]
|
||||
).toBeLessThan(
|
||||
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(
|
||||
(auth.configureAuthentication as jest.Mock).mock.invocationCallOrder[0]
|
||||
).toBeLessThan(
|
||||
(mavenArgs.configureMavenArgs as jest.Mock).mock.invocationCallOrder[0]
|
||||
);
|
||||
expect(
|
||||
(toolchains.configureToolchains as jest.Mock).mock
|
||||
.invocationCallOrder[0]
|
||||
).toBeLessThan(
|
||||
(mavenArgs.configureMavenArgs as jest.Mock).mock.invocationCallOrder[0]
|
||||
);
|
||||
|
||||
let completed = false;
|
||||
runPromise.then(() => {
|
||||
@@ -375,6 +389,54 @@ describe('setup action orchestration', () => {
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('overlaps independent Maven settings and toolchains configuration', async () => {
|
||||
inputs.set('distribution', 'temurin');
|
||||
multilineInputs.set('java-version', ['21']);
|
||||
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
|
||||
setupJava: jest.fn(async () => ({
|
||||
version: '21.0.4+7',
|
||||
path: '/opt/java/21'
|
||||
}))
|
||||
});
|
||||
const authentication = deferred<void>();
|
||||
const toolchainConfiguration = deferred<void>();
|
||||
(auth.configureAuthentication as jest.Mock).mockReturnValue(
|
||||
authentication.promise
|
||||
);
|
||||
(toolchains.configureToolchains as jest.Mock).mockReturnValue(
|
||||
toolchainConfiguration.promise
|
||||
);
|
||||
|
||||
const runPromise = run();
|
||||
try {
|
||||
await tick();
|
||||
await tick();
|
||||
|
||||
expect(auth.configureAuthentication).toHaveBeenCalled();
|
||||
expect(toolchains.configureToolchains).toHaveBeenCalledWith(
|
||||
'21',
|
||||
'temurin',
|
||||
'/opt/java/21',
|
||||
undefined
|
||||
);
|
||||
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();
|
||||
|
||||
authentication.resolve();
|
||||
await tick();
|
||||
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();
|
||||
|
||||
toolchainConfiguration.resolve();
|
||||
await runPromise;
|
||||
} finally {
|
||||
authentication.resolve();
|
||||
toolchainConfiguration.resolve();
|
||||
await runPromise;
|
||||
}
|
||||
|
||||
expect(mavenArgs.configureMavenArgs).toHaveBeenCalled();
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('skips cache restoration when the cache feature is unavailable', async () => {
|
||||
inputs.set('distribution', 'temurin');
|
||||
inputs.set('cache', 'maven');
|
||||
|
||||
+137
-24
@@ -13,6 +13,7 @@ import * as fs from 'fs';
|
||||
import os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as io from '@actions/io';
|
||||
import {XMLParser} from 'fast-xml-parser';
|
||||
|
||||
// Mock @actions/core before importing source modules that depend on it
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
@@ -95,7 +96,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(altHome)).toBe(true);
|
||||
expect(fs.existsSync(altToolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(altToolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -140,7 +141,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -149,7 +150,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -221,7 +222,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -230,7 +231,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -306,7 +307,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -315,7 +316,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -383,7 +384,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -392,7 +393,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -453,7 +454,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -462,7 +463,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -545,7 +546,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -554,7 +555,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -604,7 +605,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -613,7 +614,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -662,7 +663,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -671,7 +672,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -745,7 +746,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -754,7 +755,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -824,7 +825,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -833,7 +834,7 @@ describe('toolchains tests', () => {
|
||||
)
|
||||
);
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -888,7 +889,7 @@ describe('toolchains tests', () => {
|
||||
expect(updated).toContain(`<jdkHome>${jdkInfo.jdkHome}</jdkHome>`);
|
||||
}, 100000);
|
||||
|
||||
it('generates valid toolchains.xml with minimal configuration', () => {
|
||||
it('generates valid toolchains.xml with minimal configuration', async () => {
|
||||
const jdkInfo = {
|
||||
version: 'JAVA_VERSION',
|
||||
vendor: 'JAVA_VENDOR',
|
||||
@@ -914,7 +915,7 @@ describe('toolchains tests', () => {
|
||||
</toolchains>`;
|
||||
|
||||
expect(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
@@ -924,6 +925,29 @@ describe('toolchains tests', () => {
|
||||
).toEqual(expectedToolchains);
|
||||
}, 100000);
|
||||
|
||||
it('escapes new toolchains.xml values while preserving parsed semantics', () => {
|
||||
const jdkInfo = {
|
||||
version: `21&<>"'é`,
|
||||
vendor: `Temurin&<>"'é`,
|
||||
id: `temurin&<>"'é`,
|
||||
jdkHome: `/opt/java&<>"'é`
|
||||
};
|
||||
|
||||
const xml = toolchains.generateNewToolchainDefinition(
|
||||
jdkInfo.version,
|
||||
jdkInfo.vendor,
|
||||
jdkInfo.id,
|
||||
jdkInfo.jdkHome
|
||||
);
|
||||
const parsed = parseXmlObject(xml) as any;
|
||||
|
||||
expect(parsed.toolchains.toolchain[0].type).toBe('jdk');
|
||||
expect(xmlElementText(xml, 'version')).toBe(jdkInfo.version);
|
||||
expect(xmlElementText(xml, 'vendor')).toBe(jdkInfo.vendor);
|
||||
expect(xmlElementText(xml, 'id')).toBe(jdkInfo.id);
|
||||
expect(xmlElementText(xml, 'jdkHome')).toBe(jdkInfo.jdkHome);
|
||||
});
|
||||
|
||||
it('creates toolchains.xml with correct id when none is supplied', async () => {
|
||||
const version = '17';
|
||||
const distributionName = 'temurin';
|
||||
@@ -946,7 +970,7 @@ describe('toolchains tests', () => {
|
||||
expect(fs.existsSync(m2Dir)).toBe(true);
|
||||
expect(fs.existsSync(toolchainsFile)).toBe(true);
|
||||
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(
|
||||
toolchains.generateToolchainDefinition(
|
||||
await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
version,
|
||||
distributionName,
|
||||
@@ -956,6 +980,75 @@ describe('toolchains tests', () => {
|
||||
);
|
||||
}, 100000);
|
||||
|
||||
it('merges a second JDK into a toolchains.xml produced by the new-file fast path', async () => {
|
||||
const firstJdk = {
|
||||
version: '17',
|
||||
vendor: 'temurin',
|
||||
id: 'temurin_17',
|
||||
jdkHome: '/opt/java/17'
|
||||
};
|
||||
const secondJdk = {
|
||||
version: '21',
|
||||
vendor: 'temurin',
|
||||
id: 'temurin_21',
|
||||
jdkHome: '/opt/java/21'
|
||||
};
|
||||
|
||||
const firstToolchains = await toolchains.generateToolchainDefinition(
|
||||
'',
|
||||
firstJdk.version,
|
||||
firstJdk.vendor,
|
||||
firstJdk.id,
|
||||
firstJdk.jdkHome
|
||||
);
|
||||
const mergedToolchains = await toolchains.generateToolchainDefinition(
|
||||
firstToolchains,
|
||||
secondJdk.version,
|
||||
secondJdk.vendor,
|
||||
secondJdk.id,
|
||||
secondJdk.jdkHome
|
||||
);
|
||||
|
||||
for (const jdk of [firstJdk, secondJdk]) {
|
||||
expect(mergedToolchains).toContain(`<id>${jdk.id}</id>`);
|
||||
expect(mergedToolchains).toContain(`<jdkHome>${jdk.jdkHome}</jdkHome>`);
|
||||
}
|
||||
expect((mergedToolchains.match(/<toolchain>/g) || []).length).toBe(2);
|
||||
});
|
||||
|
||||
it('preserves custom attributes and elements when merging existing toolchains.xml', async () => {
|
||||
const originalFile = `<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.0.0" customRoot="A & B">
|
||||
<toolchain customAttr="custom & value">
|
||||
<type>foo</type>
|
||||
<provides customProvides="yes">
|
||||
<custom attr="custom " attr">baz & qux</custom>
|
||||
</provides>
|
||||
<configuration>
|
||||
<fooHome>/usr/local/bin/foo</fooHome>
|
||||
</configuration>
|
||||
</toolchain>
|
||||
</toolchains>`;
|
||||
|
||||
const mergedToolchains = await toolchains.generateToolchainDefinition(
|
||||
originalFile,
|
||||
'21&<>"\'',
|
||||
'Temurin&<>"\'',
|
||||
'temurin_21&<>"\'',
|
||||
'/opt/java/21&<>"\''
|
||||
);
|
||||
const parsed = parseXmlObject(mergedToolchains) as any;
|
||||
const merged = parsed.toolchains.toolchain;
|
||||
|
||||
expect(parsed.toolchains['@customRoot']).toBe('A & B');
|
||||
expect(merged).toHaveLength(2);
|
||||
expect(merged[0].provides.id).toBe('temurin_21&<>"\'');
|
||||
expect(merged[0].configuration.jdkHome).toBe('/opt/java/21&<>"\'');
|
||||
expect(merged[1]['@customAttr']).toBe('custom & value');
|
||||
expect(merged[1].provides['@customProvides']).toBe('yes');
|
||||
expect(merged[1].provides.custom['#text']).toBe('baz & qux');
|
||||
expect(merged[1].provides.custom['@attr']).toBe('custom " attr');
|
||||
});
|
||||
|
||||
it('preserves toolchains from previous executions across multiple setup-java runs', async () => {
|
||||
// Regression test for https://github.com/actions/setup-java/issues/1099
|
||||
// Running setup-java several times in the same job (e.g. multiple steps / multiple
|
||||
@@ -1071,3 +1164,23 @@ describe('validateToolchainIds', () => {
|
||||
).toThrow(expectedMessage);
|
||||
});
|
||||
});
|
||||
|
||||
function xmlElementText(xml: string, tagName: string): string {
|
||||
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
|
||||
expect(match).not.toBeNull();
|
||||
return (parseXmlObject(`<value>${match?.[1]}</value>`) as {value: string})
|
||||
.value;
|
||||
}
|
||||
|
||||
function parseXmlObject(xml: string): unknown {
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: '@',
|
||||
textNodeName: '#text',
|
||||
parseAttributeValue: false,
|
||||
parseTagValue: false,
|
||||
trimValues: true,
|
||||
isArray: tagName => tagName === 'toolchain'
|
||||
});
|
||||
return parser.parse(xml);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user