mirror of
https://github.com/actions/setup-java.git
synced 2026-07-31 22:51:39 +08:00
* 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
560 lines
17 KiB
TypeScript
560 lines
17 KiB
TypeScript
import {jest, describe, it, expect, beforeEach} from '@jest/globals';
|
|
|
|
jest.unstable_mockModule('@actions/core', () => ({
|
|
info: jest.fn(),
|
|
warning: jest.fn(),
|
|
debug: jest.fn(),
|
|
error: jest.fn(),
|
|
notice: jest.fn(),
|
|
setFailed: jest.fn(),
|
|
setOutput: jest.fn(),
|
|
getInput: jest.fn(),
|
|
getBooleanInput: jest.fn(),
|
|
getMultilineInput: jest.fn(),
|
|
addPath: jest.fn(),
|
|
exportVariable: jest.fn(),
|
|
saveState: jest.fn(),
|
|
getState: jest.fn(),
|
|
setSecret: jest.fn(),
|
|
isDebug: jest.fn(() => false),
|
|
startGroup: jest.fn(),
|
|
endGroup: jest.fn(),
|
|
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
|
toPlatformPath: jest.fn((value: string) => value),
|
|
toWin32Path: jest.fn((value: string) => value),
|
|
toPosixPath: jest.fn((value: string) => value)
|
|
}));
|
|
|
|
jest.unstable_mockModule('fs', () => ({
|
|
default: {
|
|
readFileSync: jest.fn()
|
|
}
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/util.js', () => ({
|
|
getBooleanInput: jest.fn(),
|
|
getVersionFromFileContent: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/toolchains.js', () => ({
|
|
validateToolchainIds: jest.fn(),
|
|
configureToolchains: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/toolchain-ids.js', () => ({
|
|
validateToolchainIds: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/cache.js', () => ({
|
|
restore: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/cache-feature.js', () => ({
|
|
isCacheFeatureAvailable: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule(
|
|
'../src/distributions/distribution-factory.js',
|
|
() => ({
|
|
getJavaDistribution: jest.fn()
|
|
})
|
|
);
|
|
|
|
jest.unstable_mockModule('../src/auth.js', () => ({
|
|
configureAuthentication: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/maven-args.js', () => ({
|
|
configureMavenArgs: jest.fn()
|
|
}));
|
|
|
|
jest.unstable_mockModule('../src/problem-matcher.js', () => ({
|
|
configureProblemMatcher: jest.fn()
|
|
}));
|
|
|
|
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');
|
|
const auth = await import('../src/auth.js');
|
|
const mavenArgs = await import('../src/maven-args.js');
|
|
const problemMatcher = await import('../src/problem-matcher.js');
|
|
const {run} = await import('../src/setup-java.js');
|
|
|
|
const inputCallsOnImport = (core.getInput as jest.Mock).mock.calls.length;
|
|
const multilineInputCallsOnImport = (core.getMultilineInput as jest.Mock).mock
|
|
.calls.length;
|
|
|
|
describe('setup action orchestration', () => {
|
|
const inputs = new Map<string, string>();
|
|
const multilineInputs = new Map<string, string[]>();
|
|
const booleanInputs = new Map<string, boolean>();
|
|
|
|
beforeEach(() => {
|
|
jest.resetAllMocks();
|
|
inputs.clear();
|
|
multilineInputs.clear();
|
|
booleanInputs.clear();
|
|
|
|
(core.getInput as jest.Mock).mockImplementation((name: unknown) => {
|
|
return inputs.get(name as string) ?? '';
|
|
});
|
|
(core.getMultilineInput as jest.Mock).mockImplementation(
|
|
(name: unknown) => {
|
|
return multilineInputs.get(name as string) ?? [];
|
|
}
|
|
);
|
|
(util.getBooleanInput as jest.Mock).mockImplementation(
|
|
(name: unknown, defaultValue: unknown) => {
|
|
return booleanInputs.get(name as string) ?? defaultValue;
|
|
}
|
|
);
|
|
(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);
|
|
});
|
|
|
|
it('does not execute the action when imported', () => {
|
|
expect(inputCallsOnImport).toBe(0);
|
|
expect(multilineInputCallsOnImport).toBe(0);
|
|
});
|
|
|
|
it('requires java-version or java-version-file', async () => {
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
'java-version or java-version-file input expected'
|
|
);
|
|
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
|
|
expect(problemMatcher.configureProblemMatcher).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('requires distribution when java-version is provided', async () => {
|
|
multilineInputs.set('java-version', ['21']);
|
|
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
'distribution input is required'
|
|
);
|
|
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('requires distribution when it cannot be inferred from the version file', async () => {
|
|
inputs.set('java-version-file', '.java-version');
|
|
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('21'));
|
|
(util.getVersionFromFileContent as jest.Mock).mockReturnValue({
|
|
version: '21'
|
|
});
|
|
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
'distribution input is required when not specified in the version file'
|
|
);
|
|
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('fails when the version file has no supported version', async () => {
|
|
inputs.set('java-version-file', '.java-version');
|
|
inputs.set('distribution', 'temurin');
|
|
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('invalid'));
|
|
(util.getVersionFromFileContent as jest.Mock).mockReturnValue(undefined);
|
|
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
'No supported version was found in file .java-version'
|
|
);
|
|
expect(factory.getJavaDistribution).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('uses the distribution inferred from a version file', async () => {
|
|
inputs.set('java-version-file', '.sdkmanrc');
|
|
inputs.set('architecture', 'x64');
|
|
inputs.set('java-package', 'jdk');
|
|
inputs.set('distribution', 'zulu');
|
|
inputs.set('jdk-file', '/tmp/java.tar.gz');
|
|
multilineInputs.set('mvn-toolchain-id', ['file-jdk']);
|
|
booleanInputs.set('check-latest', true);
|
|
booleanInputs.set('force-download', true);
|
|
booleanInputs.set('set-default', false);
|
|
booleanInputs.set('verify-signature', true);
|
|
inputs.set('verify-signature-public-key', 'public-key');
|
|
(fs.readFileSync as jest.Mock).mockReturnValue(
|
|
Buffer.from('java=21.0.5-tem')
|
|
);
|
|
(util.getVersionFromFileContent as jest.Mock).mockReturnValue({
|
|
version: '21.0.5',
|
|
distribution: 'temurin'
|
|
});
|
|
const setupJava = jest.fn(async () => ({
|
|
version: '21.0.5+11',
|
|
path: '/opt/java/21'
|
|
}));
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
|
|
|
|
await run();
|
|
|
|
expect(util.getVersionFromFileContent).toHaveBeenCalledWith(
|
|
'java=21.0.5-tem',
|
|
'zulu',
|
|
'.sdkmanrc'
|
|
);
|
|
expect(factory.getJavaDistribution).toHaveBeenCalledWith(
|
|
'temurin',
|
|
{
|
|
version: '21.0.5',
|
|
architecture: 'x64',
|
|
packageType: 'jdk',
|
|
checkLatest: true,
|
|
forceDownload: true,
|
|
setDefault: false,
|
|
verifySignature: true,
|
|
verifySignaturePublicKey: 'public-key'
|
|
},
|
|
'/tmp/java.tar.gz'
|
|
);
|
|
expect(toolchainIds.validateToolchainIds).toHaveBeenCalledWith(
|
|
[],
|
|
'.sdkmanrc',
|
|
['file-jdk']
|
|
);
|
|
expect(toolchains.configureToolchains).toHaveBeenCalledWith(
|
|
'21.0.5',
|
|
'temurin',
|
|
'/opt/java/21',
|
|
'file-jdk'
|
|
);
|
|
expect(core.setFailed).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('installs multiple JDKs in order with matching toolchain IDs', async () => {
|
|
inputs.set('distribution', 'temurin');
|
|
inputs.set('architecture', 'x64');
|
|
inputs.set('java-package', 'jdk');
|
|
multilineInputs.set('java-version', ['17', '21']);
|
|
multilineInputs.set('mvn-toolchain-id', ['java-17', 'java-21']);
|
|
|
|
const setupJava17 = jest.fn(async () => ({
|
|
version: '17.0.12+7',
|
|
path: '/opt/java/17'
|
|
}));
|
|
const setupJava21 = jest.fn(async () => ({
|
|
version: '21.0.4+7',
|
|
path: '/opt/java/21'
|
|
}));
|
|
(factory.getJavaDistribution as jest.Mock)
|
|
.mockReturnValueOnce({setupJava: setupJava17})
|
|
.mockReturnValueOnce({setupJava: setupJava21});
|
|
|
|
await run();
|
|
|
|
expect(factory.getJavaDistribution).toHaveBeenNthCalledWith(
|
|
1,
|
|
'temurin',
|
|
expect.objectContaining({version: '17'}),
|
|
''
|
|
);
|
|
expect(factory.getJavaDistribution).toHaveBeenNthCalledWith(
|
|
2,
|
|
'temurin',
|
|
expect.objectContaining({version: '21'}),
|
|
''
|
|
);
|
|
expect(toolchains.configureToolchains).toHaveBeenNthCalledWith(
|
|
1,
|
|
'17',
|
|
'temurin',
|
|
'/opt/java/17',
|
|
'java-17'
|
|
);
|
|
expect(toolchains.configureToolchains).toHaveBeenNthCalledWith(
|
|
2,
|
|
'21',
|
|
'temurin',
|
|
'/opt/java/21',
|
|
'java-21'
|
|
);
|
|
expect(setupJava17.mock.invocationCallOrder[0]).toBeLessThan(
|
|
setupJava21.mock.invocationCallOrder[0]
|
|
);
|
|
});
|
|
|
|
it('uses the resolved version for the latest Maven toolchain', async () => {
|
|
inputs.set('distribution', 'temurin');
|
|
multilineInputs.set('java-version', ['latest']);
|
|
const setupJava = jest.fn(async () => ({
|
|
version: '24.0.2+12',
|
|
path: '/opt/java/24'
|
|
}));
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
|
|
|
|
await run();
|
|
|
|
expect(toolchains.configureToolchains).toHaveBeenCalledWith(
|
|
'24.0.2+12',
|
|
'temurin',
|
|
'/opt/java/24',
|
|
undefined
|
|
);
|
|
});
|
|
|
|
it('starts cache restoration before post-install steps and awaits it before finishing', async () => {
|
|
inputs.set('distribution', 'temurin');
|
|
inputs.set('cache', 'maven');
|
|
inputs.set('cache-dependency-path', '**/pom.xml');
|
|
multilineInputs.set('java-version', ['21']);
|
|
multilineInputs.set('cache-path', [
|
|
'/custom/maven/repository',
|
|
'!/custom/maven/repository/excluded'
|
|
]);
|
|
const cacheRestore = deferred<void>();
|
|
let resolveSetupJava: (() => void) | undefined;
|
|
const setupJava = jest.fn(
|
|
() =>
|
|
new Promise<{version: string; path: string}>(resolve => {
|
|
resolveSetupJava = () =>
|
|
resolve({
|
|
version: '21.0.4+7',
|
|
path: '/opt/java/21'
|
|
});
|
|
})
|
|
);
|
|
(cache.restore as jest.Mock).mockReturnValue(cacheRestore.promise);
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({setupJava});
|
|
|
|
const runPromise = run();
|
|
try {
|
|
await tick();
|
|
|
|
expect(cacheFeature.isCacheFeatureAvailable).toHaveBeenCalled();
|
|
expect(cache.restore).toHaveBeenCalledWith('maven', '**/pom.xml', [
|
|
'/custom/maven/repository',
|
|
'!/custom/maven/repository/excluded'
|
|
]);
|
|
expect(toolchains.configureToolchains).not.toHaveBeenCalled();
|
|
|
|
resolveSetupJava?.();
|
|
await tick();
|
|
expect(problemMatcher.configureProblemMatcher).toHaveBeenCalledWith(
|
|
expect.stringMatching(/\.github[/\\]java\.json$/)
|
|
);
|
|
|
|
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]
|
|
);
|
|
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(() => {
|
|
completed = true;
|
|
});
|
|
await tick();
|
|
expect(completed).toBe(false);
|
|
} finally {
|
|
resolveSetupJava?.();
|
|
cacheRestore.resolve();
|
|
await runPromise;
|
|
}
|
|
|
|
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');
|
|
multilineInputs.set('java-version', ['21']);
|
|
(cacheFeature.isCacheFeatureAvailable as jest.Mock).mockReturnValue(false);
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
|
|
setupJava: jest.fn(async () => ({
|
|
version: '21.0.4+7',
|
|
path: '/opt/java/21'
|
|
}))
|
|
});
|
|
|
|
await run();
|
|
|
|
expect(cache.restore).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not initialize cache modules when cache input is absent', 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'
|
|
}))
|
|
});
|
|
|
|
await run();
|
|
|
|
expect(cacheFeature.isCacheFeatureAvailable).not.toHaveBeenCalled();
|
|
expect(cache.restore).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('reports unsupported distributions through core.setFailed', async () => {
|
|
inputs.set('distribution', 'unsupported');
|
|
multilineInputs.set('java-version', ['21']);
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue(null);
|
|
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith(
|
|
'No supported distribution was found for input unsupported'
|
|
);
|
|
expect(toolchains.configureToolchains).not.toHaveBeenCalled();
|
|
expect(problemMatcher.configureProblemMatcher).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('reports collaborator failures and stops post-install configuration', async () => {
|
|
inputs.set('distribution', 'temurin');
|
|
multilineInputs.set('java-version', ['21']);
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
|
|
setupJava: jest.fn(async () => {
|
|
throw new Error('download failed');
|
|
})
|
|
});
|
|
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith('download failed');
|
|
expect(toolchains.configureToolchains).not.toHaveBeenCalled();
|
|
expect(problemMatcher.configureProblemMatcher).not.toHaveBeenCalled();
|
|
expect(auth.configureAuthentication).not.toHaveBeenCalled();
|
|
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();
|
|
expect(cache.restore).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('reports post-install failures and skips later collaborators', async () => {
|
|
inputs.set('distribution', 'temurin');
|
|
inputs.set('cache', 'maven');
|
|
multilineInputs.set('java-version', ['21']);
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
|
|
setupJava: jest.fn(async () => ({
|
|
version: '21.0.4+7',
|
|
path: '/opt/java/21'
|
|
}))
|
|
});
|
|
(auth.configureAuthentication as jest.Mock).mockRejectedValue(
|
|
new Error('authentication failed')
|
|
);
|
|
|
|
await run();
|
|
|
|
expect(problemMatcher.configureProblemMatcher).toHaveBeenCalled();
|
|
expect(core.setFailed).toHaveBeenCalledWith('authentication failed');
|
|
expect(mavenArgs.configureMavenArgs).not.toHaveBeenCalled();
|
|
expect(cache.restore).toHaveBeenCalled();
|
|
});
|
|
|
|
it('keeps Java setup errors deterministic when cache restore also fails', async () => {
|
|
inputs.set('distribution', 'temurin');
|
|
inputs.set('cache', 'maven');
|
|
multilineInputs.set('java-version', ['21']);
|
|
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
|
|
setupJava: jest.fn(async () => {
|
|
throw new Error('download failed');
|
|
})
|
|
});
|
|
(cache.restore as jest.Mock).mockRejectedValue(
|
|
new Error('cache restore failed')
|
|
);
|
|
|
|
await run();
|
|
|
|
expect(core.setFailed).toHaveBeenCalledWith('download failed');
|
|
});
|
|
});
|
|
|
|
function deferred<T>() {
|
|
let resolve!: (value: T | PromiseLike<T>) => void;
|
|
let reject!: (reason?: unknown) => void;
|
|
const promise = new Promise<T>((res, rej) => {
|
|
resolve = res;
|
|
reject = rej;
|
|
});
|
|
return {promise, resolve, reject};
|
|
}
|
|
|
|
async function tick() {
|
|
await new Promise(resolve => setTimeout(resolve, 0));
|
|
}
|