mirror of
https://github.com/game-ci/unity-builder.git
synced 2026-06-11 00:13:56 -07:00
Rename Cloud Runner to Orchestrator (#775)
* Rename "Cloud Runner" to "Orchestrator" across entire codebase Breaking change: All CloudRunner classes, options, environment variables, and action.yml inputs have been renamed to Orchestrator equivalents. - Renamed src/model/cloud-runner/ directory to src/model/orchestrator/ - Renamed all cloud-runner-* files to orchestrator-* - Renamed all CloudRunner* classes to Orchestrator* (15+ classes) - Renamed all cloudRunner* properties to orchestrator* equivalents - Renamed CLOUD_RUNNER_* env vars to ORCHESTRATOR_* - Updated action.yml [CloudRunner] markers to [Orchestrator] - Updated workflow files and package.json test scripts - Updated all runtime strings (cache paths, log messages, branch refs) - Rebuilt dist/index.js No backward compatibility layer is provided. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Remove tracked log/temp files and add to .gitignore Remove $LOG_FILE and temp/job-log.txt debug artifacts that should not be in the repository. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import BuildParameters from '../../build-parameters';
|
||||
import { Cli } from '../../cli/cli';
|
||||
|
||||
export async function CreateParameters(overrides: any) {
|
||||
if (overrides) Cli.options = overrides;
|
||||
|
||||
return BuildParameters.create();
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import Orchestrator from '../../orchestrator';
|
||||
import { BuildParameters, ImageTag } from '../../..';
|
||||
import UnityVersioning from '../../../unity-versioning';
|
||||
import { Cli } from '../../../cli/cli';
|
||||
import OrchestratorLogger from '../../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../../options/orchestrator-options';
|
||||
import setups from '../orchestrator-suite.test';
|
||||
import * as fs from 'node:fs';
|
||||
import { OrchestratorSystem } from '../../services/core/orchestrator-system';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator Caching', () => {
|
||||
it('Responds', () => {});
|
||||
setups();
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it('Run one build it should not use cache, run subsequent build which should use cache', async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
image: 'ubuntu',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
containerHookFiles: `debug-cache`,
|
||||
orchestratorBranch: `orchestrator-develop`,
|
||||
orchestratorDebug: true,
|
||||
};
|
||||
|
||||
// For AWS LocalStack tests, explicitly set provider strategy to 'aws'
|
||||
// This ensures we use AWS LocalStack instead of defaulting to local-docker
|
||||
// But don't override if k8s provider is already set
|
||||
if (
|
||||
process.env.AWS_S3_ENDPOINT &&
|
||||
process.env.AWS_S3_ENDPOINT.includes('localhost') &&
|
||||
OrchestratorOptions.providerStrategy !== 'k8s'
|
||||
) {
|
||||
overrides.providerStrategy = 'aws';
|
||||
overrides.containerHookFiles += `,aws-s3-pull-cache,aws-s3-upload-cache`;
|
||||
}
|
||||
if (OrchestratorOptions.providerStrategy === `k8s`) {
|
||||
overrides.containerHookFiles += `,aws-s3-pull-cache,aws-s3-upload-cache`;
|
||||
}
|
||||
const buildParameter = await CreateParameters(overrides);
|
||||
expect(buildParameter.projectPath).toEqual(overrides.projectPath);
|
||||
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
const resultsObject = await Orchestrator.run(buildParameter, baseImage.toString());
|
||||
const results = resultsObject.BuildResults;
|
||||
const libraryString = 'Rebuilding Library because the asset database could not be found!';
|
||||
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
|
||||
|
||||
expect(resultsObject.BuildSucceeded).toBe(true);
|
||||
|
||||
// Keep minimal assertions to reduce brittleness
|
||||
expect(results).not.toContain(cachePushFail);
|
||||
|
||||
OrchestratorLogger.log(`run 1 succeeded`);
|
||||
|
||||
if (OrchestratorOptions.providerStrategy === `local-docker`) {
|
||||
await OrchestratorSystem.Run(`tree ./orchestrator-cache/cache`);
|
||||
await OrchestratorSystem.Run(
|
||||
`cp ./orchestrator-cache/cache/${buildParameter.cacheKey}/Library/lib-${buildParameter.buildGuid}.tar ./`,
|
||||
);
|
||||
await OrchestratorSystem.Run(`mkdir results`);
|
||||
await OrchestratorSystem.Run(`tar -xf lib-${buildParameter.buildGuid}.tar -C ./results`);
|
||||
await OrchestratorSystem.Run(`tree -d ./results`);
|
||||
const cacheFolderExists = fs.existsSync(`orchestrator-cache/cache/${overrides.cacheKey}`);
|
||||
expect(cacheFolderExists).toBeTruthy();
|
||||
}
|
||||
const buildParameter2 = await CreateParameters(overrides);
|
||||
|
||||
buildParameter2.cacheKey = buildParameter.cacheKey;
|
||||
const baseImage2 = new ImageTag(buildParameter2);
|
||||
const results2Object = await Orchestrator.run(buildParameter2, baseImage2.toString());
|
||||
const results2 = results2Object.BuildResults;
|
||||
OrchestratorLogger.log(`run 2 succeeded`);
|
||||
|
||||
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
|
||||
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
|
||||
'There is 0 files/dir in the cache pulled contents for Library',
|
||||
);
|
||||
const build2NotContainsZeroLFSCacheFilesMessage = !results2.includes(
|
||||
'There is 0 files/dir in the cache pulled contents for LFS',
|
||||
);
|
||||
|
||||
expect(build2ContainsCacheKey).toBeTruthy();
|
||||
expect(results2).toContain('Activation successful');
|
||||
expect(results2Object.BuildSucceeded).toBe(true);
|
||||
const splitResults = results2.split('Activation successful');
|
||||
expect(splitResults[splitResults.length - 1]).not.toContain(libraryString);
|
||||
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
|
||||
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
|
||||
}, 1_000_000_000);
|
||||
afterAll(async () => {
|
||||
// Clean up cache files to prevent disk space issues
|
||||
if (OrchestratorOptions.providerStrategy === `local-docker` || OrchestratorOptions.providerStrategy === `aws`) {
|
||||
const cachePath = `./orchestrator-cache`;
|
||||
if (fs.existsSync(cachePath)) {
|
||||
try {
|
||||
OrchestratorLogger.log(`Cleaning up cache directory: ${cachePath}`);
|
||||
|
||||
// Try to change ownership first (if running as root or with sudo)
|
||||
// Then try multiple cleanup methods to handle permission issues
|
||||
await OrchestratorSystem.Run(
|
||||
`chmod -R u+w ${cachePath} 2>/dev/null || chown -R $(whoami) ${cachePath} 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
// Try regular rm first
|
||||
await OrchestratorSystem.Run(`rm -rf ${cachePath}/* 2>/dev/null || true`);
|
||||
|
||||
// If that fails, try with sudo if available
|
||||
await OrchestratorSystem.Run(`sudo rm -rf ${cachePath}/* 2>/dev/null || true`);
|
||||
|
||||
// As last resort, try to remove files one by one, ignoring permission errors
|
||||
await OrchestratorSystem.Run(
|
||||
`find ${cachePath} -type f -exec rm -f {} + 2>/dev/null || find ${cachePath} -type f -delete 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
// Remove empty directories
|
||||
await OrchestratorSystem.Run(`find ${cachePath} -type d -empty -delete 2>/dev/null || true`);
|
||||
} catch (error: any) {
|
||||
OrchestratorLogger.log(`Failed to cleanup cache: ${error.message}`);
|
||||
|
||||
// Don't throw - cleanup failures shouldn't fail the test suite
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
import Orchestrator from '../../orchestrator';
|
||||
import { BuildParameters } from '../../..';
|
||||
import UnityVersioning from '../../../unity-versioning';
|
||||
import { Cli } from '../../../cli/cli';
|
||||
import OrchestratorLogger from '../../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../../options/orchestrator-options';
|
||||
import setups from '../orchestrator-suite.test';
|
||||
import SharedWorkspaceLocking from '../../services/core/shared-workspace-locking';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator Locking', () => {
|
||||
setups();
|
||||
it('Responds', () => {});
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it(`Simple Locking End2End Flow`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters);
|
||||
expect(await SharedWorkspaceLocking.DoesCacheKeyTopLevelExist(buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
const isExpectedUnlockedBeforeLocking =
|
||||
(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)) === false;
|
||||
expect(isExpectedUnlockedBeforeLocking).toBeTruthy();
|
||||
const result = await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters);
|
||||
expect(result).toBeTruthy();
|
||||
const lines = await SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}`);
|
||||
expect(lines.map((x) => x.replace(`/`, ``)).includes(buildParameters.cacheKey));
|
||||
expect(await SharedWorkspaceLocking.DoesCacheKeyTopLevelExist(buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
const allLocks = await SharedWorkspaceLocking.GetAllLocksForWorkspace(newWorkspaceName, buildParameters);
|
||||
expect(
|
||||
(
|
||||
await SharedWorkspaceLocking.ReadLines(
|
||||
`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParameters.cacheKey}/`,
|
||||
)
|
||||
).filter((x) => x.endsWith(`${newWorkspaceName}_workspace_lock`)),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
(
|
||||
await SharedWorkspaceLocking.ReadLines(
|
||||
`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParameters.cacheKey}/`,
|
||||
)
|
||||
).filter((x) => x.endsWith(`${newWorkspaceName}_workspace`)),
|
||||
).toHaveLength(1);
|
||||
expect(allLocks.filter((x) => x.endsWith(`${newWorkspaceName}_workspace_lock`)).length).toBeGreaterThan(0);
|
||||
const isExpectedLockedAfterLocking =
|
||||
(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)) === true;
|
||||
expect(isExpectedLockedAfterLocking).toBeTruthy();
|
||||
const locksBeforeRelease = await SharedWorkspaceLocking.GetAllLocksForWorkspace(
|
||||
newWorkspaceName,
|
||||
buildParameters,
|
||||
);
|
||||
OrchestratorLogger.log(JSON.stringify(locksBeforeRelease, undefined, 4));
|
||||
expect(locksBeforeRelease.length).toBe(1);
|
||||
await SharedWorkspaceLocking.ReleaseWorkspace(newWorkspaceName, runId, buildParameters);
|
||||
const locks = await SharedWorkspaceLocking.GetAllLocksForWorkspace(newWorkspaceName, buildParameters);
|
||||
expect(locks.length).toBe(0);
|
||||
const isExpectedNotLockedAfterReleasing =
|
||||
(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)) === false;
|
||||
expect(isExpectedNotLockedAfterReleasing).toBeTruthy();
|
||||
const lockingResult2 = await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters);
|
||||
expect(lockingResult2).toBeTruthy();
|
||||
expect((await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)) === true).toBeTruthy();
|
||||
await SharedWorkspaceLocking.ReleaseWorkspace(newWorkspaceName, runId, buildParameters);
|
||||
expect(
|
||||
(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)) === false,
|
||||
).toBeTruthy();
|
||||
await SharedWorkspaceLocking.CleanupWorkspace(newWorkspaceName, buildParameters);
|
||||
OrchestratorLogger.log(`Starting get or create`);
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
}, 350000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,174 @@
|
||||
import Orchestrator from '../../orchestrator';
|
||||
import { ImageTag } from '../../..';
|
||||
import UnityVersioning from '../../../unity-versioning';
|
||||
import OrchestratorLogger from '../../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../../options/orchestrator-options';
|
||||
import setups from './../orchestrator-suite.test';
|
||||
import * as fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { OrchestratorFolders } from '../../options/orchestrator-folders';
|
||||
import SharedWorkspaceLocking from '../../services/core/shared-workspace-locking';
|
||||
import { CreateParameters } from '../create-test-parameter';
|
||||
import { OrchestratorSystem } from '../../services/core/orchestrator-system';
|
||||
|
||||
describe('Orchestrator Retain Workspace', () => {
|
||||
it('Responds', () => {});
|
||||
setups();
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it('Run one build it should not already be retained, run subsequent build which should use retained workspace', async () => {
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 1,
|
||||
orchestratorDebug: true,
|
||||
};
|
||||
const buildParameter = await CreateParameters(overrides);
|
||||
expect(buildParameter.projectPath).toEqual(overrides.projectPath);
|
||||
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
const resultsObject = await Orchestrator.run(buildParameter, baseImage.toString());
|
||||
const results = resultsObject.BuildResults;
|
||||
const libraryString = 'Rebuilding Library because the asset database could not be found!';
|
||||
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
|
||||
|
||||
expect(resultsObject.BuildSucceeded).toBe(true);
|
||||
|
||||
// Keep minimal assertions to reduce brittleness
|
||||
expect(results).not.toContain(cachePushFail);
|
||||
|
||||
if (OrchestratorOptions.providerStrategy === `local-docker`) {
|
||||
const cacheFolderExists = fs.existsSync(`orchestrator-cache/cache/${overrides.cacheKey}`);
|
||||
expect(cacheFolderExists).toBeTruthy();
|
||||
await OrchestratorSystem.Run(`tree -d ./orchestrator-cache`);
|
||||
}
|
||||
|
||||
OrchestratorLogger.log(`run 1 succeeded`);
|
||||
|
||||
// Clean up k3d node between builds to free space, but preserve Unity image
|
||||
if (OrchestratorOptions.providerStrategy === 'k8s') {
|
||||
try {
|
||||
OrchestratorLogger.log('Cleaning up k3d node between builds (preserving Unity image)...');
|
||||
const K3D_NODE_CONTAINERS = ['k3d-unity-builder-agent-0', 'k3d-unity-builder-server-0'];
|
||||
for (const NODE of K3D_NODE_CONTAINERS) {
|
||||
// Remove stopped containers only - DO NOT touch images
|
||||
// Removing images risks removing the Unity image which causes "no space left" errors
|
||||
await OrchestratorSystem.Run(
|
||||
`docker exec ${NODE} sh -c "crictl rm --all 2>/dev/null || true" || true`,
|
||||
true,
|
||||
true,
|
||||
);
|
||||
}
|
||||
OrchestratorLogger.log('Cleanup between builds completed (containers removed, images preserved)');
|
||||
} catch (cleanupError) {
|
||||
OrchestratorLogger.logWarning(`Failed to cleanup between builds: ${cleanupError}`);
|
||||
|
||||
// Continue anyway
|
||||
}
|
||||
}
|
||||
|
||||
// await OrchestratorSystem.Run(`tree -d ./orchestrator-cache/${}`);
|
||||
const buildParameter2 = await CreateParameters(overrides);
|
||||
|
||||
buildParameter2.cacheKey = buildParameter.cacheKey;
|
||||
const baseImage2 = new ImageTag(buildParameter2);
|
||||
const results2Object = await Orchestrator.run(buildParameter2, baseImage2.toString());
|
||||
const results2 = results2Object.BuildResults;
|
||||
OrchestratorLogger.log(`run 2 succeeded`);
|
||||
|
||||
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
|
||||
const build2ContainsBuildGuid1FromRetainedWorkspace = results2.includes(buildParameter.buildGuid);
|
||||
const build2ContainsRetainedWorkspacePhrase = results2.includes(`Retained Workspace:`);
|
||||
const build2ContainsWorkspaceExistsAlreadyPhrase = results2.includes(`Retained Workspace Already Exists!`);
|
||||
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
|
||||
'There is 0 files/dir in the cache pulled contents for Library',
|
||||
);
|
||||
const build2NotContainsZeroLFSCacheFilesMessage = !results2.includes(
|
||||
'There is 0 files/dir in the cache pulled contents for LFS',
|
||||
);
|
||||
|
||||
expect(build2ContainsCacheKey).toBeTruthy();
|
||||
expect(build2ContainsRetainedWorkspacePhrase).toBeTruthy();
|
||||
expect(build2ContainsWorkspaceExistsAlreadyPhrase).toBeTruthy();
|
||||
expect(build2ContainsBuildGuid1FromRetainedWorkspace).toBeTruthy();
|
||||
expect(results2Object.BuildSucceeded).toBe(true);
|
||||
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
|
||||
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
|
||||
const splitResults = results2.split('Activation successful');
|
||||
expect(splitResults[splitResults.length - 1]).not.toContain(libraryString);
|
||||
}, 1_000_000_000);
|
||||
afterAll(async () => {
|
||||
await SharedWorkspaceLocking.CleanupWorkspace(Orchestrator.lockedWorkspace || ``, Orchestrator.buildParameters);
|
||||
if (
|
||||
fs.existsSync(`./orchestrator-cache/${path.basename(OrchestratorFolders.uniqueOrchestratorJobFolderAbsolute)}`)
|
||||
) {
|
||||
OrchestratorLogger.log(
|
||||
`Cleaning up ./orchestrator-cache/${path.basename(OrchestratorFolders.uniqueOrchestratorJobFolderAbsolute)}`,
|
||||
);
|
||||
try {
|
||||
const workspaceCachePath = `./orchestrator-cache/${path.basename(
|
||||
OrchestratorFolders.uniqueOrchestratorJobFolderAbsolute,
|
||||
)}`;
|
||||
|
||||
// Try to fix permissions first to avoid permission denied errors
|
||||
await OrchestratorSystem.Run(
|
||||
`chmod -R u+w ${workspaceCachePath} 2>/dev/null || chown -R $(whoami) ${workspaceCachePath} 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
// Try regular rm first
|
||||
await OrchestratorSystem.Run(`rm -rf ${workspaceCachePath} 2>/dev/null || true`);
|
||||
|
||||
// If that fails, try with sudo if available
|
||||
await OrchestratorSystem.Run(`sudo rm -rf ${workspaceCachePath} 2>/dev/null || true`);
|
||||
|
||||
// As last resort, try to remove files one by one, ignoring permission errors
|
||||
await OrchestratorSystem.Run(
|
||||
`find ${workspaceCachePath} -type f -exec rm -f {} + 2>/dev/null || find ${workspaceCachePath} -type f -delete 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
// Remove empty directories
|
||||
await OrchestratorSystem.Run(`find ${workspaceCachePath} -type d -empty -delete 2>/dev/null || true`);
|
||||
} catch (error: any) {
|
||||
OrchestratorLogger.log(`Failed to cleanup workspace: ${error.message}`);
|
||||
|
||||
// Don't throw - cleanup failures shouldn't fail the test suite
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up cache files to prevent disk space issues
|
||||
const cachePath = `./orchestrator-cache`;
|
||||
if (fs.existsSync(cachePath)) {
|
||||
try {
|
||||
OrchestratorLogger.log(`Cleaning up cache directory: ${cachePath}`);
|
||||
|
||||
// Try to change ownership first (if running as root or with sudo)
|
||||
// Then try multiple cleanup methods to handle permission issues
|
||||
await OrchestratorSystem.Run(
|
||||
`chmod -R u+w ${cachePath} 2>/dev/null || chown -R $(whoami) ${cachePath} 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
// Try regular rm first
|
||||
await OrchestratorSystem.Run(`rm -rf ${cachePath}/* 2>/dev/null || true`);
|
||||
|
||||
// If that fails, try with sudo if available
|
||||
await OrchestratorSystem.Run(`sudo rm -rf ${cachePath}/* 2>/dev/null || true`);
|
||||
|
||||
// As last resort, try to remove files one by one, ignoring permission errors
|
||||
await OrchestratorSystem.Run(
|
||||
`find ${cachePath} -type f -exec rm -f {} + 2>/dev/null || find ${cachePath} -type f -delete 2>/dev/null || true`,
|
||||
);
|
||||
|
||||
// Remove empty directories
|
||||
await OrchestratorSystem.Run(`find ${cachePath} -type d -empty -delete 2>/dev/null || true`);
|
||||
} catch (error: any) {
|
||||
OrchestratorLogger.log(`Failed to cleanup cache: ${error.message}`);
|
||||
|
||||
// Don't throw - cleanup failures shouldn't fail the test suite
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,107 @@
|
||||
import Orchestrator from '../../orchestrator';
|
||||
import UnityVersioning from '../../../unity-versioning';
|
||||
import { Cli } from '../../../cli/cli';
|
||||
import OrchestratorLogger from '../../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../../options/orchestrator-options';
|
||||
import setups from '../orchestrator-suite.test';
|
||||
import BuildParameters from '../../../build-parameters';
|
||||
import ImageTag from '../../../image-tag';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator Kubernetes', () => {
|
||||
it('Responds', () => {});
|
||||
setups();
|
||||
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
const enableK8sE2E = process.env.ENABLE_K8S_E2E === 'true';
|
||||
|
||||
const testBody = async () => {
|
||||
if (OrchestratorOptions.providerStrategy !== `k8s`) {
|
||||
return;
|
||||
}
|
||||
process.env.USE_IL2CPP = 'false';
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
providerStrategy: 'k8s',
|
||||
buildPlatform: 'linux',
|
||||
orchestratorDebug: true,
|
||||
};
|
||||
const buildParameter = await CreateParameters(overrides);
|
||||
expect(buildParameter.projectPath).toEqual(overrides.projectPath);
|
||||
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
const resultsObject = await Orchestrator.run(buildParameter, baseImage.toString());
|
||||
const results = resultsObject.BuildResults;
|
||||
const libraryString = 'Rebuilding Library because the asset database could not be found!';
|
||||
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
|
||||
const buildSucceededString = 'Build succeeded';
|
||||
|
||||
const fallbackLogsUnavailableMessage =
|
||||
'Pod logs unavailable - pod may have been terminated before logs could be collected.';
|
||||
const incompleteLogsMessage =
|
||||
'Pod logs incomplete - "Collected Logs" marker not found. Pod may have been terminated before post-build completed.';
|
||||
|
||||
// Check if pod was evicted due to resource constraints - this is a test infrastructure failure
|
||||
// Evictions indicate the cluster doesn't have enough resources, which is a test environment issue
|
||||
if (
|
||||
results.includes('The node was low on resource: ephemeral-storage') ||
|
||||
results.includes('TerminationByKubelet') ||
|
||||
results.includes('Evicted')
|
||||
) {
|
||||
throw new Error(
|
||||
`Test failed: Pod was evicted due to resource constraints (ephemeral-storage). ` +
|
||||
`This indicates the test environment doesn't have enough disk space. ` +
|
||||
`Results: ${results.slice(0, 500)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// If we hit the aggressive fallback path and couldn't retrieve any logs from the pod,
|
||||
// don't assert on specific Unity log contents – just assert that we got the fallback message.
|
||||
// This makes the test resilient to cluster-level evictions / PreStop hook failures while still
|
||||
// ensuring Orchestrator surfaces a useful message in BuildResults.
|
||||
// However, if we got logs but they're incomplete (missing "Collected Logs"), the test should fail
|
||||
// as this indicates the build didn't complete successfully (pod was evicted/killed).
|
||||
if (results.includes(fallbackLogsUnavailableMessage)) {
|
||||
// Complete failure - no logs at all (acceptable for eviction scenarios)
|
||||
expect(results).toContain(fallbackLogsUnavailableMessage);
|
||||
OrchestratorLogger.log('Test passed with fallback message (pod was evicted before any logs were written)');
|
||||
} else if (results.includes(incompleteLogsMessage)) {
|
||||
// Incomplete logs - we got some output but missing "Collected Logs" (build didn't complete)
|
||||
// This should fail the test as the build didn't succeed
|
||||
throw new Error(
|
||||
`Build did not complete successfully: ${incompleteLogsMessage}\n` +
|
||||
`This indicates the pod was evicted or killed before post-build completed.\n` +
|
||||
`Build results:\n${results.slice(0, 500)}`,
|
||||
);
|
||||
} else {
|
||||
// Normal case - logs are complete
|
||||
expect(results).toContain('Collected Logs');
|
||||
expect(results).toContain(libraryString);
|
||||
expect(results).toContain(buildSucceededString);
|
||||
expect(results).not.toContain(cachePushFail);
|
||||
}
|
||||
|
||||
OrchestratorLogger.log(`run 1 succeeded`);
|
||||
};
|
||||
|
||||
if (enableK8sE2E) {
|
||||
it('Run one build it using K8s without error', testBody, 1_000_000_000);
|
||||
} else {
|
||||
it.skip('Run one build it using K8s without error - disabled (no outbound network)', () => {
|
||||
OrchestratorLogger.log('Skipping K8s e2e (ENABLE_K8S_E2E not true)');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export default class InvalidProvider {}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { BuildParameters, ImageTag } from '../..';
|
||||
import Orchestrator from '../orchestrator';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import { OptionValues } from 'commander';
|
||||
|
||||
async function CreateParameters(overrides: OptionValues | undefined) {
|
||||
if (overrides) Cli.options = overrides;
|
||||
|
||||
return BuildParameters.create();
|
||||
}
|
||||
describe('Orchestrator Async Workflows', () => {
|
||||
setups();
|
||||
it('Responds', () => {});
|
||||
|
||||
if (OrchestratorOptions.orchestratorDebug && OrchestratorOptions.providerStrategy !== `local-docker`) {
|
||||
it('Async Workflows', async () => {
|
||||
// Setup parameters
|
||||
const buildParameter = await CreateParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
asyncOrchestrator: `true`,
|
||||
githubChecks: `true`,
|
||||
providerStrategy: 'k8s',
|
||||
buildPlatform: 'linux',
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
});
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
|
||||
// Run the job
|
||||
await Orchestrator.run(buildParameter, baseImage.toString());
|
||||
|
||||
// wait for 15 seconds
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000 * 60 * 12));
|
||||
}, 1_000_000_000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import BuildParameters from '../../build-parameters';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import Orchestrator from '../orchestrator';
|
||||
import { OrchestratorSystem } from '../services/core/orchestrator-system';
|
||||
import { Caching } from '../remote-client/caching';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import GitHub from '../../github';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
describe('Orchestrator (Remote Client) Caching', () => {
|
||||
it('responds', () => {});
|
||||
if (OrchestratorOptions.providerStrategy === `local-docker`) {
|
||||
it('Simple caching works', async () => {
|
||||
Cli.options = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
};
|
||||
GitHub.githubInputEnabled = false;
|
||||
const buildParameter = await BuildParameters.create();
|
||||
Orchestrator.buildParameters = buildParameter;
|
||||
|
||||
// Create test folder
|
||||
const testFolder = path.resolve(__dirname, Cli.options.cacheKey);
|
||||
fs.mkdirSync(testFolder);
|
||||
|
||||
// Create cache folder
|
||||
const cacheFolder = path.resolve(__dirname, `cache-${Cli.options.cacheKey}`);
|
||||
fs.mkdirSync(cacheFolder);
|
||||
|
||||
// Add test file to test folders
|
||||
fs.writeFileSync(path.resolve(testFolder, 'test.txt'), Cli.options.cacheKey);
|
||||
await Caching.PushToCache(cacheFolder, testFolder, `${Cli.options.cacheKey}`);
|
||||
|
||||
// Delete test folder
|
||||
fs.rmdirSync(testFolder, { recursive: true });
|
||||
await Caching.PullFromCache(
|
||||
cacheFolder.replace(/\\/g, `/`),
|
||||
testFolder.replace(/\\/g, `/`),
|
||||
`${Cli.options.cacheKey}`,
|
||||
);
|
||||
await OrchestratorSystem.Run(`du -h ${__dirname}`);
|
||||
|
||||
// Compare validity to original hash
|
||||
expect(fs.readFileSync(path.resolve(testFolder, 'test.txt'), { encoding: 'utf8' }).toString()).toContain(
|
||||
Cli.options.cacheKey,
|
||||
);
|
||||
fs.rmdirSync(testFolder, { recursive: true });
|
||||
fs.rmdirSync(cacheFolder, { recursive: true });
|
||||
|
||||
GitHub.githubInputEnabled = true;
|
||||
delete Cli.options;
|
||||
}, 1000000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
import { BuildParameters, Orchestrator, ImageTag, Input } from '../..';
|
||||
import { TaskParameterSerializer } from '../services/core/task-parameter-serializer';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import GitHub from '../../github';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import { OrchestratorStatics } from '../options/orchestrator-statics';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
const originalValue = GitHub.githubInputEnabled;
|
||||
GitHub.githubInputEnabled = false;
|
||||
const results = await BuildParameters.create();
|
||||
GitHub.githubInputEnabled = originalValue;
|
||||
delete Cli.options;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
describe('Orchestrator Sync Environments', () => {
|
||||
setups();
|
||||
const testSecretName = 'testSecretName';
|
||||
const testSecretValue = 'testSecretValue';
|
||||
it('Responds', () => {});
|
||||
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it('All build parameters sent to orchestrator as env vars', async () => {
|
||||
// Setup parameters
|
||||
const buildParameter = await CreateParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
targetPlatform: 'StandaloneWindows64',
|
||||
customJob: `
|
||||
- name: 'step 1'
|
||||
image: 'ubuntu'
|
||||
commands: 'printenv'
|
||||
secrets:
|
||||
- name: '${testSecretName}'
|
||||
value: '${testSecretValue}'
|
||||
`,
|
||||
orchestratorDebug: true,
|
||||
});
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
if (baseImage.toString().includes('undefined')) {
|
||||
throw new Error(`Base image is undefined`);
|
||||
}
|
||||
|
||||
// Run the job
|
||||
const file = (await Orchestrator.run(buildParameter, baseImage.toString())).BuildResults;
|
||||
|
||||
// Assert results
|
||||
// expect(file).toContain(JSON.stringify(buildParameter));
|
||||
expect(file).toContain(`${Input.ToEnvVarFormat(testSecretName)}=${testSecretValue}`);
|
||||
const environmentVariables = TaskParameterSerializer.createOrchestratorEnvironmentVariables(buildParameter);
|
||||
const secrets = TaskParameterSerializer.readDefaultSecrets().map((x) => {
|
||||
return {
|
||||
name: x.EnvironmentVariable,
|
||||
value: x.ParameterValue,
|
||||
};
|
||||
});
|
||||
|
||||
// Apply the same localhost -> host.docker.internal replacement that the Docker provider does
|
||||
// This ensures the test expectations match what's actually in the output
|
||||
const endpointEnvironmentNames = new Set([
|
||||
'AWS_S3_ENDPOINT',
|
||||
'AWS_ENDPOINT',
|
||||
'AWS_CLOUD_FORMATION_ENDPOINT',
|
||||
'AWS_ECS_ENDPOINT',
|
||||
'AWS_KINESIS_ENDPOINT',
|
||||
'AWS_CLOUD_WATCH_LOGS_ENDPOINT',
|
||||
'INPUT_AWSS3ENDPOINT',
|
||||
'INPUT_AWSENDPOINT',
|
||||
]);
|
||||
const combined = [...environmentVariables, ...secrets]
|
||||
.filter((element) => element.value !== undefined && element.value !== '' && typeof element.value !== 'function')
|
||||
.map((x) => {
|
||||
if (typeof x.value === `string`) {
|
||||
x.value = x.value.replace(/\s+/g, '');
|
||||
|
||||
// Apply localhost -> host.docker.internal replacement for LocalStack endpoints
|
||||
// when using local-docker or aws provider (which uses Docker)
|
||||
if (
|
||||
endpointEnvironmentNames.has(x.name) &&
|
||||
(x.value.startsWith('http://localhost') || x.value.startsWith('http://127.0.0.1')) &&
|
||||
(OrchestratorOptions.providerStrategy === 'local-docker' ||
|
||||
OrchestratorOptions.providerStrategy === 'aws')
|
||||
) {
|
||||
x.value = x.value
|
||||
.replace('http://localhost', 'http://host.docker.internal')
|
||||
.replace('http://127.0.0.1', 'http://host.docker.internal');
|
||||
}
|
||||
}
|
||||
|
||||
return x;
|
||||
})
|
||||
.filter((element) => {
|
||||
return !['UNITY_LICENSE', 'UNITY_LICENSE', 'CUSTOM_JOB', 'CUSTOM_JOB'].includes(element.name);
|
||||
});
|
||||
const newLinePurgedFile = file
|
||||
.replace(/\s+/g, '')
|
||||
.replace(new RegExp(`\\[${OrchestratorStatics.logPrefix}\\]`, 'g'), '');
|
||||
for (const element of combined) {
|
||||
expect(newLinePurgedFile).toContain(`${element.name}`);
|
||||
OrchestratorLogger.log(`Contains ${element.name}`);
|
||||
const fullNameEqualValue = `${element.name}=${element.value}`;
|
||||
expect(newLinePurgedFile).toContain(fullNameEqualValue);
|
||||
}
|
||||
}, 1_000_000_000);
|
||||
}
|
||||
});
|
||||
|
||||
describe('Orchestrator Environment Serializer', () => {
|
||||
setups();
|
||||
const testSecretName = 'testSecretName';
|
||||
const testSecretValue = 'testSecretValue';
|
||||
it('Orchestrator Parameter Serialization', async () => {
|
||||
// Setup parameters
|
||||
const buildParameter = await CreateParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
customJob: `
|
||||
- name: 'step 1'
|
||||
image: 'alpine'
|
||||
commands: 'printenv'
|
||||
secrets:
|
||||
- name: '${testSecretName}'
|
||||
value: '${testSecretValue}'
|
||||
`,
|
||||
});
|
||||
|
||||
const result = TaskParameterSerializer.createOrchestratorEnvironmentVariables(buildParameter);
|
||||
expect(result.find((x) => Number.parseInt(x.name)) !== undefined).toBeFalsy();
|
||||
const result2 = TaskParameterSerializer.createOrchestratorEnvironmentVariables(buildParameter);
|
||||
expect(result2.find((x) => Number.parseInt(x.name)) !== undefined).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import Orchestrator from '../orchestrator';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import GitHub from '../../github';
|
||||
import { TIMEOUT_INFINITE, createParameters } from '../../../test-utils/orchestrator-test-helpers';
|
||||
describe('Orchestrator Github Checks', () => {
|
||||
setups();
|
||||
it('Responds', () => {});
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock GitHub API requests to avoid real network calls
|
||||
jest.spyOn(GitHub as any, 'createGitHubCheckRequest').mockResolvedValue({
|
||||
status: 201,
|
||||
data: { id: '1' },
|
||||
});
|
||||
jest.spyOn(GitHub as any, 'updateGitHubCheckRequest').mockResolvedValue({
|
||||
status: 200,
|
||||
data: {},
|
||||
});
|
||||
// eslint-disable-next-line unicorn/no-useless-undefined
|
||||
jest.spyOn(GitHub as any, 'runUpdateAsyncChecksWorkflow').mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it(
|
||||
'Check Handling Direct',
|
||||
async () => {
|
||||
// Setup parameters
|
||||
const buildParameter = await createParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
asyncOrchestrator: `true`,
|
||||
githubChecks: `true`,
|
||||
});
|
||||
await Orchestrator.setup(buildParameter);
|
||||
Orchestrator.buildParameters.githubCheckId = await GitHub.createGitHubCheck(`direct create`);
|
||||
await GitHub.updateGitHubCheck(`1 ${new Date().toISOString()}`, `direct`);
|
||||
await GitHub.updateGitHubCheck(`2 ${new Date().toISOString()}`, `direct`, `success`, `completed`);
|
||||
},
|
||||
TIMEOUT_INFINITE,
|
||||
);
|
||||
it(
|
||||
'Check Handling Via Async Workflow',
|
||||
async () => {
|
||||
// Setup parameters
|
||||
const buildParameter = await createParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
asyncOrchestrator: `true`,
|
||||
githubChecks: `true`,
|
||||
});
|
||||
GitHub.forceAsyncTest = true;
|
||||
await Orchestrator.setup(buildParameter);
|
||||
Orchestrator.buildParameters.githubCheckId = await GitHub.createGitHubCheck(`async create`);
|
||||
await GitHub.updateGitHubCheck(`1 ${new Date().toISOString()}`, `async`);
|
||||
await GitHub.updateGitHubCheck(`2 ${new Date().toISOString()}`, `async`, `success`, `completed`);
|
||||
GitHub.forceAsyncTest = false;
|
||||
},
|
||||
TIMEOUT_INFINITE,
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
import Orchestrator from '../orchestrator';
|
||||
import { BuildParameters, ImageTag } from '../..';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import { ContainerHookService } from '../services/hooks/container-hook-service';
|
||||
import { CommandHookService } from '../services/hooks/command-hook-service';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator Custom Hooks And Steps', () => {
|
||||
it('Responds', () => {});
|
||||
setups();
|
||||
it('Check parsing and reading of steps', async () => {
|
||||
const yamlString = `hook: before
|
||||
commands: echo "test"`;
|
||||
const yamlString2 = `- hook: before
|
||||
commands: echo "test"`;
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
image: 'ubuntu',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
};
|
||||
Orchestrator.setup(await CreateParameters(overrides));
|
||||
const stringObject = ContainerHookService.ParseContainerHooks(yamlString);
|
||||
const stringObject2 = ContainerHookService.ParseContainerHooks(yamlString2);
|
||||
|
||||
OrchestratorLogger.log(yamlString);
|
||||
OrchestratorLogger.log(JSON.stringify(stringObject, undefined, 4));
|
||||
|
||||
expect(stringObject.length).toBe(1);
|
||||
expect(stringObject[0].hook).toBe(`before`);
|
||||
expect(stringObject2.length).toBe(1);
|
||||
expect(stringObject2[0].hook).toBe(`before`);
|
||||
|
||||
const getCustomStepsFromFiles = ContainerHookService.GetContainerHooksFromFiles(`before`);
|
||||
OrchestratorLogger.log(JSON.stringify(getCustomStepsFromFiles, undefined, 4));
|
||||
});
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it('Should be 1 before and 1 after hook', async () => {
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
image: 'ubuntu',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
containerHookFiles: `my-test-step-pre-build,my-test-step-post-build`,
|
||||
commandHookFiles: `my-test-hook-pre-build,my-test-hook-post-build`,
|
||||
};
|
||||
const buildParameter2 = await CreateParameters(overrides);
|
||||
await Orchestrator.setup(buildParameter2);
|
||||
const beforeHooks = CommandHookService.GetCustomHooksFromFiles(`before`);
|
||||
const afterHooks = CommandHookService.GetCustomHooksFromFiles(`after`);
|
||||
expect(beforeHooks).toHaveLength(1);
|
||||
expect(afterHooks).toHaveLength(1);
|
||||
});
|
||||
it('Should be 1 before and 1 after step', async () => {
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
image: 'ubuntu',
|
||||
containerHookFiles: `my-test-step-pre-build,my-test-step-post-build`,
|
||||
commandHookFiles: `my-test-hook-pre-build,my-test-hook-post-build`,
|
||||
};
|
||||
const buildParameter2 = await CreateParameters(overrides);
|
||||
await Orchestrator.setup(buildParameter2);
|
||||
const beforeSteps = ContainerHookService.GetContainerHooksFromFiles(`before`);
|
||||
const afterSteps = ContainerHookService.GetContainerHooksFromFiles(`after`);
|
||||
expect(beforeSteps).toHaveLength(1);
|
||||
expect(afterSteps).toHaveLength(1);
|
||||
});
|
||||
it('Run build once - check for pre and post custom hooks run contents', async () => {
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
containerHookFiles: `my-test-step-pre-build,my-test-step-post-build`,
|
||||
commandHookFiles: `my-test-hook-pre-build,my-test-hook-post-build`,
|
||||
orchestratorDebug: true,
|
||||
};
|
||||
const buildParameter2 = await CreateParameters(overrides);
|
||||
const baseImage2 = new ImageTag(buildParameter2);
|
||||
const results2Object = await Orchestrator.run(buildParameter2, baseImage2.toString());
|
||||
const results2 = results2Object.BuildResults;
|
||||
OrchestratorLogger.log(`run 2 succeeded`);
|
||||
|
||||
const buildContainsBuildSucceeded = results2.includes('Build succeeded');
|
||||
const buildContainsPreBuildHookRunMessage = results2.includes('before-build hook test!!');
|
||||
const buildContainsPostBuildHookRunMessage = results2.includes('after-build hook test!');
|
||||
|
||||
const buildContainsPreBuildStepMessage = results2.includes('before-build step test!');
|
||||
const buildContainsPostBuildStepMessage = results2.includes('after-build step test!');
|
||||
|
||||
// Skip "Build succeeded" check for local-docker and aws when using ubuntu image (Unity doesn't run)
|
||||
if (
|
||||
OrchestratorOptions.providerStrategy !== 'local' &&
|
||||
OrchestratorOptions.providerStrategy !== 'local-docker' &&
|
||||
OrchestratorOptions.providerStrategy !== 'aws'
|
||||
) {
|
||||
expect(buildContainsBuildSucceeded).toBeTruthy();
|
||||
}
|
||||
expect(buildContainsPreBuildHookRunMessage).toBeTruthy();
|
||||
expect(buildContainsPostBuildHookRunMessage).toBeTruthy();
|
||||
expect(buildContainsPreBuildStepMessage).toBeTruthy();
|
||||
expect(buildContainsPostBuildStepMessage).toBeTruthy();
|
||||
}, 1_000_000_000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { BuildParameters, ImageTag } from '../..';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import GitHub from '../../github';
|
||||
import setups from './orchestrator-suite.test';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
const originalValue = GitHub.githubInputEnabled;
|
||||
GitHub.githubInputEnabled = false;
|
||||
const results = await BuildParameters.create();
|
||||
GitHub.githubInputEnabled = originalValue;
|
||||
delete Cli.options;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
describe('Orchestrator Image', () => {
|
||||
setups();
|
||||
const testSecretName = 'testSecretName';
|
||||
const testSecretValue = 'testSecretValue';
|
||||
it('Can create valid image from normal config', async () => {
|
||||
// Setup parameters
|
||||
const buildParameter = await CreateParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
targetPlatform: 'StandaloneWindows64',
|
||||
customJob: `
|
||||
- name: 'step 1'
|
||||
image: 'ubuntu'
|
||||
commands: 'printenv'
|
||||
secrets:
|
||||
- name: '${testSecretName}'
|
||||
value: '${testSecretValue}'
|
||||
`,
|
||||
});
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
if (buildParameter.targetPlatform === undefined) {
|
||||
throw new Error(`target platform includes undefined`);
|
||||
}
|
||||
if (baseImage.toString().includes('undefined')) {
|
||||
throw new Error(`Base image ${baseImage.toString()} includes undefined`);
|
||||
}
|
||||
if (baseImage.toString().includes('NaN')) {
|
||||
throw new Error(`Base image ${baseImage.toString()} includes nan`);
|
||||
}
|
||||
}, 1_000_000_000);
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { ImageTag } from '../..';
|
||||
import Orchestrator from '../orchestrator';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import fs from 'node:fs';
|
||||
import { CreateParameters } from './create-test-parameter';
|
||||
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||
|
||||
describe('Orchestrator Local Docker Workflows', () => {
|
||||
setups();
|
||||
it('Responds', () => {});
|
||||
|
||||
if (OrchestratorOptions.providerStrategy === `local-docker`) {
|
||||
it('inspect stateful folder of workflows', async () => {
|
||||
const testValue = `the state in a job exits in the expected local-docker folder`;
|
||||
|
||||
// Setup parameters
|
||||
const buildParameter = await CreateParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
customJob: `
|
||||
- name: 'step 1'
|
||||
image: 'ubuntu'
|
||||
commands: 'echo "${testValue}" >> /data/test-out-state.txt'
|
||||
`,
|
||||
});
|
||||
const buildParameter2 = await CreateParameters({
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.read('test-project'),
|
||||
customJob: `
|
||||
- name: 'step 1'
|
||||
image: 'ubuntu'
|
||||
commands: 'cat /data/test-out-state.txt >> /data/test-out-state-2.txt'
|
||||
`,
|
||||
});
|
||||
const baseImage = new ImageTag(buildParameter);
|
||||
|
||||
// Run the job
|
||||
await Orchestrator.run(buildParameter, baseImage.toString());
|
||||
await Orchestrator.run(buildParameter2, baseImage.toString());
|
||||
|
||||
const outputFile = fs.readFileSync(`./orchestrator-cache/test-out-state.txt`, `utf-8`);
|
||||
expect(outputFile).toMatch(testValue);
|
||||
|
||||
const outputFile2 = fs.readFileSync(`./orchestrator-cache/test-out-state-2.txt`, `utf-8`);
|
||||
expect(outputFile2).toMatch(testValue);
|
||||
OrchestratorLogger.log(outputFile);
|
||||
}, 1_000_000_000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import SharedWorkspaceLocking from '../services/core/shared-workspace-locking';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import BuildParameters from '../../build-parameters';
|
||||
import Orchestrator from '../orchestrator';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator Locking Core', () => {
|
||||
setups();
|
||||
it('Responds', () => {});
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it(`Create Workspace`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
}, 150000);
|
||||
it(`Create Workspace And Lock Workspace`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const runId = uuidv4();
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
}, 150000);
|
||||
it(`0 free workspaces after locking`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.HasWorkspaceLock(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.GetAllWorkspaces(buildParameters)).toHaveLength(1);
|
||||
expect(await SharedWorkspaceLocking.GetAllLocksForWorkspace(newWorkspaceName, buildParameters)).toHaveLength(1);
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
|
||||
const files = await SharedWorkspaceLocking.ReadLines(
|
||||
`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParameters.cacheKey}/`,
|
||||
);
|
||||
|
||||
const lockFilesExist =
|
||||
files.filter((x) => {
|
||||
return x.includes(newWorkspaceName) && x.endsWith(`_lock`);
|
||||
}).length > 0;
|
||||
|
||||
expect(files).toHaveLength(2);
|
||||
expect(
|
||||
files.filter((x) => {
|
||||
return x.includes(newWorkspaceName) && x.endsWith(`_lock`);
|
||||
}),
|
||||
).toHaveLength(1);
|
||||
expect(lockFilesExist).toBeTruthy();
|
||||
const result: string[] = [];
|
||||
const workspaces = await SharedWorkspaceLocking.GetAllWorkspaces(buildParameters);
|
||||
for (const element of workspaces) {
|
||||
expect((await SharedWorkspaceLocking.GetAllWorkspaces(buildParameters)).join()).toContain(element);
|
||||
expect(await SharedWorkspaceLocking.GetAllWorkspaces(buildParameters)).toHaveLength(1);
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(element, buildParameters)).toBeTruthy();
|
||||
await new Promise((promise) => setTimeout(promise, 1500));
|
||||
const isLocked = await SharedWorkspaceLocking.IsWorkspaceLocked(element, buildParameters);
|
||||
const isBelowMax = await SharedWorkspaceLocking.IsWorkspaceBelowMax(element, buildParameters);
|
||||
OrchestratorLogger.log(`workspace ${element} locked:${isLocked} below max:${isBelowMax}`);
|
||||
const lock = files.find((x) => {
|
||||
return x.endsWith(`_lock`);
|
||||
});
|
||||
expect(lock).toContain(element);
|
||||
expect(isLocked).toBeTruthy();
|
||||
expect(isBelowMax).toBeTruthy();
|
||||
if (!isLocked && isBelowMax) {
|
||||
result.push(element);
|
||||
}
|
||||
}
|
||||
expect(result).toHaveLength(0);
|
||||
expect(await SharedWorkspaceLocking.GetFreeWorkspaces(buildParameters)).toHaveLength(0);
|
||||
}, 300000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,156 @@
|
||||
import SharedWorkspaceLocking from '../services/core/shared-workspace-locking';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import BuildParameters from '../../build-parameters';
|
||||
import Orchestrator from '../orchestrator';
|
||||
|
||||
async function CreateParameters(overrides: any) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator Locking Get Locked Workspace', () => {
|
||||
setups();
|
||||
it('Responds', () => {});
|
||||
if (OrchestratorOptions.orchestratorDebug) {
|
||||
it(`Get locked workspace From No Workspace`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
}, 150000);
|
||||
it(`Get locked workspace from unlocked`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(Orchestrator.lockedWorkspace).toMatch(newWorkspaceName);
|
||||
}, 300000);
|
||||
it(`Get locked workspace from locked`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
const runId2 = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.HasWorkspaceLock(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceBelowMax(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId2, buildParameters)).toBeTruthy();
|
||||
expect(Orchestrator.lockedWorkspace).not.toMatch(newWorkspaceName);
|
||||
}, 300000);
|
||||
it(`Get locked workspace after double lock and one unlock`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
const runId2 = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.ReleaseWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeFalsy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.HasWorkspaceLock(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId2, buildParameters)).toBeTruthy();
|
||||
expect(Orchestrator.lockedWorkspace).not.toContain(newWorkspaceName);
|
||||
}, 300000);
|
||||
it(`Get locked workspace after double lock and unlock`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
const runId2 = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.ReleaseWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeFalsy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.HasWorkspaceLock(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.ReleaseWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.IsWorkspaceLocked(newWorkspaceName, buildParameters)).toBeFalsy();
|
||||
expect(await SharedWorkspaceLocking.DoesWorkspaceExist(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId2, buildParameters)).toBeTruthy();
|
||||
expect(Orchestrator.lockedWorkspace).toContain(newWorkspaceName);
|
||||
}, 300000);
|
||||
it(`Get locked workspace from unlocked was locked`, async () => {
|
||||
const overrides: any = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
maxRetainedWorkspaces: 3,
|
||||
};
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
|
||||
const newWorkspaceName = `test-workspace-${uuidv4()}`;
|
||||
const runId = uuidv4();
|
||||
Orchestrator.buildParameters = buildParameters;
|
||||
expect(await SharedWorkspaceLocking.CreateWorkspace(newWorkspaceName, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.LockWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.ReleaseWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(await SharedWorkspaceLocking.GetLockedWorkspace(newWorkspaceName, runId, buildParameters)).toBeTruthy();
|
||||
expect(Orchestrator.lockedWorkspace).toMatch(newWorkspaceName);
|
||||
}, 300000);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,89 @@
|
||||
import Orchestrator from '../orchestrator';
|
||||
import { BuildParameters, ImageTag } from '../..';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import { OrchestratorSystem } from '../services/core/orchestrator-system';
|
||||
import { OptionValues } from 'commander';
|
||||
|
||||
async function CreateParameters(overrides: OptionValues | undefined) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator pre-built rclone steps', () => {
|
||||
it('Responds', () => {});
|
||||
it('Simple test to check if file is loaded', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
setups();
|
||||
|
||||
(() => {
|
||||
// Determine environment capability to run rclone operations
|
||||
const isCI = process.env.GITHUB_ACTIONS === 'true';
|
||||
const isWindows = process.platform === 'win32';
|
||||
let rcloneAvailable = false;
|
||||
let bashAvailable = !isWindows; // assume available on non-Windows
|
||||
if (!isCI) {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
execSync('rclone version', { stdio: 'ignore' });
|
||||
rcloneAvailable = true;
|
||||
} catch {
|
||||
rcloneAvailable = false;
|
||||
}
|
||||
if (isWindows) {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
execSync('bash --version', { stdio: 'ignore' });
|
||||
bashAvailable = true;
|
||||
} catch {
|
||||
bashAvailable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const hasRcloneRemote = Boolean(process.env.RCLONE_REMOTE || process.env.rcloneRemote);
|
||||
const shouldRunRclone = (isCI && hasRcloneRemote) || (rcloneAvailable && (!isWindows || bashAvailable));
|
||||
|
||||
if (shouldRunRclone) {
|
||||
it('Run build and prebuilt rclone cache pull, cache push and upload build', async () => {
|
||||
const remote = process.env.RCLONE_REMOTE || process.env.rcloneRemote || 'local:./temp/rclone-remote';
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey: `test-case-${uuidv4()}`,
|
||||
containerHookFiles: `rclone-pull-cache,rclone-upload-cache,rclone-upload-build`,
|
||||
storageProvider: 'rclone',
|
||||
rcloneRemote: remote,
|
||||
orchestratorDebug: true,
|
||||
} as unknown as OptionValues;
|
||||
|
||||
const buildParameters = await CreateParameters(overrides);
|
||||
const baseImage = new ImageTag(buildParameters);
|
||||
const results = await Orchestrator.run(buildParameters, baseImage.toString());
|
||||
OrchestratorLogger.log(`rclone run succeeded`);
|
||||
expect(results.BuildSucceeded).toBe(true);
|
||||
|
||||
// List remote root to validate the remote is accessible (best-effort)
|
||||
try {
|
||||
const lines = await OrchestratorSystem.RunAndReadLines(`rclone lsf ${remote}`);
|
||||
OrchestratorLogger.log(lines.join(','));
|
||||
} catch {
|
||||
// Ignore errors when listing remote root (best-effort validation)
|
||||
}
|
||||
}, 1_000_000_000);
|
||||
} else {
|
||||
it.skip('Run build and prebuilt rclone steps - rclone not configured', () => {
|
||||
OrchestratorLogger.log('rclone not configured (no CLI/remote); skipping rclone test');
|
||||
});
|
||||
}
|
||||
})();
|
||||
});
|
||||
@@ -0,0 +1,207 @@
|
||||
import Orchestrator from '../orchestrator';
|
||||
import { BuildParameters, ImageTag } from '../..';
|
||||
import UnityVersioning from '../../unity-versioning';
|
||||
import { Cli } from '../../cli/cli';
|
||||
import OrchestratorLogger from '../services/core/orchestrator-logger';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import setups from './orchestrator-suite.test';
|
||||
import { OrchestratorSystem } from '../services/core/orchestrator-system';
|
||||
import { OptionValues } from 'commander';
|
||||
import OrchestratorOptions from '../options/orchestrator-options';
|
||||
|
||||
async function CreateParameters(overrides: OptionValues | undefined) {
|
||||
if (overrides) {
|
||||
Cli.options = overrides;
|
||||
}
|
||||
|
||||
return await BuildParameters.create();
|
||||
}
|
||||
|
||||
describe('Orchestrator pre-built S3 steps', () => {
|
||||
it('Responds', () => {});
|
||||
it('Simple test to check if file is loaded', () => {
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
setups();
|
||||
(() => {
|
||||
// Determine environment capability to run S3 operations
|
||||
const isCI = process.env.GITHUB_ACTIONS === 'true';
|
||||
let awsAvailable = false;
|
||||
if (!isCI) {
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
execSync('aws --version', { stdio: 'ignore' });
|
||||
awsAvailable = true;
|
||||
} catch {
|
||||
awsAvailable = false;
|
||||
}
|
||||
}
|
||||
const hasAwsCreds = Boolean(process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY);
|
||||
const shouldRunS3 = (isCI && hasAwsCreds) || awsAvailable;
|
||||
|
||||
// Only run the test if we have AWS creds in CI, or the AWS CLI is available locally
|
||||
if (shouldRunS3) {
|
||||
it('Run build and prebuilt s3 cache pull, cache push and upload build', async () => {
|
||||
const cacheKey = `test-case-${uuidv4()}`;
|
||||
const buildGuid = `test-build-${uuidv4()}`;
|
||||
|
||||
// Use customJob to run only S3 hooks without a full Unity build
|
||||
// This is a quick validation test for S3 operations, not a full build test
|
||||
const overrides = {
|
||||
versioning: 'None',
|
||||
projectPath: 'test-project',
|
||||
unityVersion: UnityVersioning.determineUnityVersion('test-project', UnityVersioning.read('test-project')),
|
||||
targetPlatform: 'StandaloneLinux64',
|
||||
cacheKey,
|
||||
buildGuid,
|
||||
orchestratorDebug: true,
|
||||
|
||||
// Use customJob to run a minimal job that sets up test data and then runs S3 hooks
|
||||
customJob: `
|
||||
- name: setup-test-data
|
||||
image: ubuntu
|
||||
commands: |
|
||||
# Create test cache directories and files to simulate what S3 hooks would work with
|
||||
mkdir -p /data/cache/${cacheKey}/Library/test-package
|
||||
mkdir -p /data/cache/${cacheKey}/lfs/test-asset
|
||||
mkdir -p /data/cache/${cacheKey}/build
|
||||
echo "test-library-content" > /data/cache/${cacheKey}/Library/test-package/test.txt
|
||||
echo "test-lfs-content" > /data/cache/${cacheKey}/lfs/test-asset/test.txt
|
||||
echo "test-build-content" > /data/cache/${cacheKey}/build/build-${buildGuid}.tar
|
||||
echo "Test data created successfully"
|
||||
- name: test-s3-pull-cache
|
||||
image: amazon/aws-cli
|
||||
commands: |
|
||||
# Test aws-s3-pull-cache hook logic (simplified)
|
||||
if command -v aws > /dev/null 2>&1; then
|
||||
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
|
||||
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID" --profile default || true
|
||||
fi
|
||||
if [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
|
||||
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY" --profile default || true
|
||||
fi
|
||||
if [ -n "$AWS_DEFAULT_REGION" ]; then
|
||||
aws configure set region "$AWS_DEFAULT_REGION" --profile default || true
|
||||
fi
|
||||
ENDPOINT_ARGS=""
|
||||
if [ -n "$AWS_S3_ENDPOINT" ]; then ENDPOINT_ARGS="--endpoint-url $AWS_S3_ENDPOINT"; fi
|
||||
echo "S3 pull cache hook test completed"
|
||||
else
|
||||
echo "AWS CLI not available, skipping aws-s3-pull-cache test"
|
||||
fi
|
||||
- name: test-s3-upload-cache
|
||||
image: amazon/aws-cli
|
||||
commands: |
|
||||
# Test aws-s3-upload-cache hook logic (simplified)
|
||||
if command -v aws > /dev/null 2>&1; then
|
||||
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
|
||||
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID" --profile default || true
|
||||
fi
|
||||
if [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
|
||||
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY" --profile default || true
|
||||
fi
|
||||
ENDPOINT_ARGS=""
|
||||
if [ -n "$AWS_S3_ENDPOINT" ]; then ENDPOINT_ARGS="--endpoint-url $AWS_S3_ENDPOINT"; fi
|
||||
echo "S3 upload cache hook test completed"
|
||||
else
|
||||
echo "AWS CLI not available, skipping aws-s3-upload-cache test"
|
||||
fi
|
||||
- name: test-s3-upload-build
|
||||
image: amazon/aws-cli
|
||||
commands: |
|
||||
# Test aws-s3-upload-build hook logic (simplified)
|
||||
if command -v aws > /dev/null 2>&1; then
|
||||
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
|
||||
aws configure set aws_access_key_id "$AWS_ACCESS_KEY_ID" --profile default || true
|
||||
fi
|
||||
if [ -n "$AWS_SECRET_ACCESS_KEY" ]; then
|
||||
aws configure set aws_secret_access_key "$AWS_SECRET_ACCESS_KEY" --profile default || true
|
||||
fi
|
||||
ENDPOINT_ARGS=""
|
||||
if [ -n "$AWS_S3_ENDPOINT" ]; then ENDPOINT_ARGS="--endpoint-url $AWS_S3_ENDPOINT"; fi
|
||||
echo "S3 upload build hook test completed"
|
||||
else
|
||||
echo "AWS CLI not available, skipping aws-s3-upload-build test"
|
||||
fi
|
||||
`,
|
||||
};
|
||||
const buildParameter2 = await CreateParameters(overrides);
|
||||
const baseImage2 = new ImageTag(buildParameter2);
|
||||
const results2Object = await Orchestrator.run(buildParameter2, baseImage2.toString());
|
||||
OrchestratorLogger.log(`S3 hooks test succeeded`);
|
||||
expect(results2Object.BuildSucceeded).toBe(true);
|
||||
|
||||
// Only run S3 operations if environment supports it
|
||||
if (shouldRunS3) {
|
||||
// Get S3 endpoint for LocalStack compatibility
|
||||
// Convert host.docker.internal to localhost for host-side test execution
|
||||
let s3Endpoint = OrchestratorOptions.awsS3Endpoint || process.env.AWS_S3_ENDPOINT;
|
||||
if (s3Endpoint && s3Endpoint.includes('host.docker.internal')) {
|
||||
s3Endpoint = s3Endpoint.replace('host.docker.internal', 'localhost');
|
||||
OrchestratorLogger.log(`Converted endpoint from host.docker.internal to localhost: ${s3Endpoint}`);
|
||||
}
|
||||
const endpointArguments = s3Endpoint ? `--endpoint-url ${s3Endpoint}` : '';
|
||||
|
||||
// Configure AWS credentials if available (needed for LocalStack)
|
||||
// LocalStack accepts any credentials, but they must be provided
|
||||
if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
|
||||
try {
|
||||
await OrchestratorSystem.Run(
|
||||
`aws configure set aws_access_key_id "${process.env.AWS_ACCESS_KEY_ID}" --profile default || true`,
|
||||
);
|
||||
await OrchestratorSystem.Run(
|
||||
`aws configure set aws_secret_access_key "${process.env.AWS_SECRET_ACCESS_KEY}" --profile default || true`,
|
||||
);
|
||||
if (process.env.AWS_REGION) {
|
||||
await OrchestratorSystem.Run(
|
||||
`aws configure set region "${process.env.AWS_REGION}" --profile default || true`,
|
||||
);
|
||||
}
|
||||
} catch (configError) {
|
||||
OrchestratorLogger.log(`Failed to configure AWS credentials: ${configError}`);
|
||||
}
|
||||
} else {
|
||||
// For LocalStack, use default test credentials if none provided
|
||||
const defaultAccessKey = 'test';
|
||||
const defaultSecretKey = 'test';
|
||||
try {
|
||||
await OrchestratorSystem.Run(
|
||||
`aws configure set aws_access_key_id "${defaultAccessKey}" --profile default || true`,
|
||||
);
|
||||
await OrchestratorSystem.Run(
|
||||
`aws configure set aws_secret_access_key "${defaultSecretKey}" --profile default || true`,
|
||||
);
|
||||
await OrchestratorSystem.Run(`aws configure set region "us-east-1" --profile default || true`);
|
||||
OrchestratorLogger.log('Using default LocalStack test credentials');
|
||||
} catch (configError) {
|
||||
OrchestratorLogger.log(`Failed to configure default AWS credentials: ${configError}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await OrchestratorSystem.RunAndReadLines(
|
||||
`aws ${endpointArguments} s3 ls s3://${Orchestrator.buildParameters.awsStackName}/orchestrator-cache/`,
|
||||
);
|
||||
OrchestratorLogger.log(`S3 verification successful: ${results.join(`,`)}`);
|
||||
} catch (s3Error: any) {
|
||||
// Log the error but don't fail the test - S3 upload might have failed during build
|
||||
// The build itself succeeded, which is what we're primarily testing
|
||||
OrchestratorLogger.log(
|
||||
`S3 verification failed (this is expected if upload failed during build): ${s3Error?.message || s3Error}`,
|
||||
);
|
||||
|
||||
// Check if the error is due to missing credentials or connection issues
|
||||
const errorMessage = (s3Error?.message || s3Error?.toString() || '').toLowerCase();
|
||||
if (errorMessage.includes('invalidaccesskeyid') || errorMessage.includes('could not connect')) {
|
||||
OrchestratorLogger.log('S3 verification skipped due to credential or connection issues');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1_000_000_000);
|
||||
} else {
|
||||
it.skip('Run build and prebuilt s3 cache pull, cache push and upload build - AWS not configured', () => {
|
||||
OrchestratorLogger.log('AWS not configured (no creds/CLI); skipping S3 test');
|
||||
});
|
||||
}
|
||||
})();
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Cli } from '../../cli/cli';
|
||||
import GitHub from '../../github';
|
||||
|
||||
describe('Orchestrator', () => {
|
||||
it('Responds', () => {});
|
||||
});
|
||||
|
||||
const setups = () => {
|
||||
beforeAll(() => {
|
||||
GitHub.githubInputEnabled = false;
|
||||
});
|
||||
beforeEach(() => {
|
||||
Cli.options = {};
|
||||
});
|
||||
afterEach(() => {
|
||||
if (Cli.options !== undefined) {
|
||||
delete Cli.options;
|
||||
}
|
||||
});
|
||||
afterAll(() => {
|
||||
GitHub.githubInputEnabled = true;
|
||||
});
|
||||
};
|
||||
|
||||
export default setups;
|
||||
@@ -0,0 +1,151 @@
|
||||
import { GitHubUrlInfo } from '../../providers/provider-url-parser';
|
||||
|
||||
// Import the mocked ProviderGitManager
|
||||
import { ProviderGitManager } from '../../providers/provider-git-manager';
|
||||
|
||||
// Mock @actions/core to fix fs.promises compatibility issue
|
||||
jest.mock('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
error: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fs module
|
||||
jest.mock('fs');
|
||||
|
||||
// Mock the entire provider-git-manager module
|
||||
jest.mock('../../providers/provider-git-manager', () => {
|
||||
const originalModule = jest.requireActual('../../providers/provider-git-manager');
|
||||
|
||||
return {
|
||||
...originalModule,
|
||||
ProviderGitManager: {
|
||||
...originalModule.ProviderGitManager,
|
||||
cloneRepository: jest.fn(),
|
||||
updateRepository: jest.fn(),
|
||||
getProviderModulePath: jest.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
const mockProviderGitManager = ProviderGitManager as jest.Mocked<typeof ProviderGitManager>;
|
||||
|
||||
describe('ProviderGitManager', () => {
|
||||
const mockUrlInfo: GitHubUrlInfo = {
|
||||
type: 'github',
|
||||
owner: 'test-user',
|
||||
repo: 'test-repo',
|
||||
branch: 'main',
|
||||
url: 'https://github.com/test-user/test-repo',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('cloneRepository', () => {
|
||||
it('successfully clones a repository', async () => {
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
localPath: '/path/to/cloned/repo',
|
||||
};
|
||||
mockProviderGitManager.cloneRepository.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await mockProviderGitManager.cloneRepository(mockUrlInfo);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.localPath).toBe('/path/to/cloned/repo');
|
||||
});
|
||||
|
||||
it('handles clone errors', async () => {
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
localPath: '/path/to/cloned/repo',
|
||||
error: 'Clone failed',
|
||||
};
|
||||
mockProviderGitManager.cloneRepository.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await mockProviderGitManager.cloneRepository(mockUrlInfo);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toContain('Clone failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateRepository', () => {
|
||||
it('successfully updates a repository when updates are available', async () => {
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
updated: true,
|
||||
};
|
||||
mockProviderGitManager.updateRepository.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await mockProviderGitManager.updateRepository(mockUrlInfo);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.updated).toBe(true);
|
||||
});
|
||||
|
||||
it('reports no updates when repository is up to date', async () => {
|
||||
const expectedResult = {
|
||||
success: true,
|
||||
updated: false,
|
||||
};
|
||||
mockProviderGitManager.updateRepository.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await mockProviderGitManager.updateRepository(mockUrlInfo);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.updated).toBe(false);
|
||||
});
|
||||
|
||||
it('handles update errors', async () => {
|
||||
const expectedResult = {
|
||||
success: false,
|
||||
updated: false,
|
||||
error: 'Update failed',
|
||||
};
|
||||
mockProviderGitManager.updateRepository.mockResolvedValue(expectedResult);
|
||||
|
||||
const result = await mockProviderGitManager.updateRepository(mockUrlInfo);
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.updated).toBe(false);
|
||||
expect(result.error).toContain('Update failed');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getProviderModulePath', () => {
|
||||
it('returns the specified path when provided', () => {
|
||||
const urlInfoWithPath = { ...mockUrlInfo, path: 'src/providers' };
|
||||
const localPath = '/path/to/repo';
|
||||
const expectedPath = '/path/to/repo/src/providers';
|
||||
|
||||
mockProviderGitManager.getProviderModulePath.mockReturnValue(expectedPath);
|
||||
|
||||
const result = mockProviderGitManager.getProviderModulePath(urlInfoWithPath, localPath);
|
||||
|
||||
expect(result).toBe(expectedPath);
|
||||
});
|
||||
|
||||
it('finds common entry points when no path specified', () => {
|
||||
const localPath = '/path/to/repo';
|
||||
const expectedPath = '/path/to/repo/index.js';
|
||||
|
||||
mockProviderGitManager.getProviderModulePath.mockReturnValue(expectedPath);
|
||||
|
||||
const result = mockProviderGitManager.getProviderModulePath(mockUrlInfo, localPath);
|
||||
|
||||
expect(result).toBe(expectedPath);
|
||||
});
|
||||
|
||||
it('returns repository root when no entry point found', () => {
|
||||
const localPath = '/path/to/repo';
|
||||
|
||||
mockProviderGitManager.getProviderModulePath.mockReturnValue(localPath);
|
||||
|
||||
const result = mockProviderGitManager.getProviderModulePath(mockUrlInfo, localPath);
|
||||
|
||||
expect(result).toBe(localPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import loadProvider, { ProviderLoader } from '../../providers/provider-loader';
|
||||
import { ProviderInterface } from '../../providers/provider-interface';
|
||||
import { ProviderGitManager } from '../../providers/provider-git-manager';
|
||||
|
||||
// Mock the git manager
|
||||
jest.mock('../../providers/provider-git-manager');
|
||||
const mockProviderGitManager = ProviderGitManager as jest.Mocked<typeof ProviderGitManager>;
|
||||
|
||||
describe('provider-loader', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('loadProvider', () => {
|
||||
it('loads a built-in provider dynamically', async () => {
|
||||
const provider: ProviderInterface = await loadProvider('./test', {} as any);
|
||||
expect(typeof provider.runTaskInWorkflow).toBe('function');
|
||||
});
|
||||
|
||||
it('loads a local provider from relative path', async () => {
|
||||
const provider: ProviderInterface = await loadProvider('./test', {} as any);
|
||||
expect(typeof provider.runTaskInWorkflow).toBe('function');
|
||||
});
|
||||
|
||||
it('loads a GitHub provider', async () => {
|
||||
const mockLocalPath = '/path/to/cloned/repo';
|
||||
const mockModulePath = '/path/to/cloned/repo/index.js';
|
||||
|
||||
mockProviderGitManager.ensureRepositoryAvailable.mockResolvedValue(mockLocalPath);
|
||||
mockProviderGitManager.getProviderModulePath.mockReturnValue(mockModulePath);
|
||||
|
||||
// For now, just test that the git manager methods are called correctly
|
||||
// The actual import testing is complex due to dynamic imports
|
||||
await expect(loadProvider('https://github.com/user/repo', {} as any)).rejects.toThrow();
|
||||
expect(mockProviderGitManager.ensureRepositoryAvailable).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws when provider package is missing', async () => {
|
||||
await expect(loadProvider('non-existent-package', {} as any)).rejects.toThrow('non-existent-package');
|
||||
});
|
||||
|
||||
it('throws when provider does not implement ProviderInterface', async () => {
|
||||
await expect(loadProvider('../tests/fixtures/invalid-provider', {} as any)).rejects.toThrow(
|
||||
'does not implement ProviderInterface',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when provider does not export a constructor', async () => {
|
||||
// Test with a non-existent module that will fail to load
|
||||
await expect(loadProvider('./non-existent-constructor-module', {} as any)).rejects.toThrow(
|
||||
'Failed to load provider package',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProviderLoader class', () => {
|
||||
it('loads providers using the static method', async () => {
|
||||
const provider: ProviderInterface = await ProviderLoader.loadProvider('./test', {} as any);
|
||||
expect(typeof provider.runTaskInWorkflow).toBe('function');
|
||||
});
|
||||
|
||||
it('returns available providers', () => {
|
||||
const providers = ProviderLoader.getAvailableProviders();
|
||||
expect(providers).toContain('aws');
|
||||
expect(providers).toContain('k8s');
|
||||
expect(providers).toContain('test');
|
||||
});
|
||||
|
||||
it('cleans up cache', async () => {
|
||||
mockProviderGitManager.cleanupOldRepositories.mockResolvedValue();
|
||||
|
||||
await ProviderLoader.cleanupCache(7);
|
||||
|
||||
expect(mockProviderGitManager.cleanupOldRepositories).toHaveBeenCalledWith(7);
|
||||
});
|
||||
|
||||
it('analyzes provider sources', () => {
|
||||
const githubInfo = ProviderLoader.analyzeProviderSource('https://github.com/user/repo');
|
||||
expect(githubInfo.type).toBe('github');
|
||||
if (githubInfo.type === 'github') {
|
||||
expect(githubInfo.owner).toBe('user');
|
||||
expect(githubInfo.repo).toBe('repo');
|
||||
}
|
||||
|
||||
const localInfo = ProviderLoader.analyzeProviderSource('./local-provider');
|
||||
expect(localInfo.type).toBe('local');
|
||||
if (localInfo.type === 'local') {
|
||||
expect(localInfo.path).toBe('./local-provider');
|
||||
}
|
||||
|
||||
const npmInfo = ProviderLoader.analyzeProviderSource('my-package');
|
||||
expect(npmInfo.type).toBe('npm');
|
||||
if (npmInfo.type === 'npm') {
|
||||
expect(npmInfo.packageName).toBe('my-package');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
import { parseProviderSource, generateCacheKey, isGitHubSource } from '../../providers/provider-url-parser';
|
||||
|
||||
describe('provider-url-parser', () => {
|
||||
describe('parseProviderSource', () => {
|
||||
it('parses HTTPS GitHub URLs correctly', () => {
|
||||
const result = parseProviderSource('https://github.com/user/repo');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'main',
|
||||
path: '',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses HTTPS GitHub URLs with branch', () => {
|
||||
const result = parseProviderSource('https://github.com/user/repo/tree/develop');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'develop',
|
||||
path: '',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses HTTPS GitHub URLs with path', () => {
|
||||
const result = parseProviderSource('https://github.com/user/repo/tree/main/src/providers');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'main',
|
||||
path: 'src/providers',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses GitHub URLs with .git extension', () => {
|
||||
const result = parseProviderSource('https://github.com/user/repo.git');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'main',
|
||||
path: '',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses SSH GitHub URLs', () => {
|
||||
const result = parseProviderSource('git@github.com:user/repo.git');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'main',
|
||||
path: '',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses shorthand GitHub references', () => {
|
||||
const result = parseProviderSource('user/repo');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'main',
|
||||
path: '',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses shorthand GitHub references with branch', () => {
|
||||
const result = parseProviderSource('user/repo@develop');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'develop',
|
||||
path: '',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses shorthand GitHub references with path', () => {
|
||||
const result = parseProviderSource('user/repo@main/src/providers');
|
||||
expect(result).toEqual({
|
||||
type: 'github',
|
||||
owner: 'user',
|
||||
repo: 'repo',
|
||||
branch: 'main',
|
||||
path: 'src/providers',
|
||||
url: 'https://github.com/user/repo',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses local relative paths', () => {
|
||||
const result = parseProviderSource('./my-provider');
|
||||
expect(result).toEqual({
|
||||
type: 'local',
|
||||
path: './my-provider',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses local absolute paths', () => {
|
||||
const result = parseProviderSource('/path/to/provider');
|
||||
expect(result).toEqual({
|
||||
type: 'local',
|
||||
path: '/path/to/provider',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses Windows paths', () => {
|
||||
const result = parseProviderSource('C:\\path\\to\\provider');
|
||||
expect(result).toEqual({
|
||||
type: 'local',
|
||||
path: 'C:\\path\\to\\provider',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses NPM package names', () => {
|
||||
const result = parseProviderSource('my-provider-package');
|
||||
expect(result).toEqual({
|
||||
type: 'npm',
|
||||
packageName: 'my-provider-package',
|
||||
});
|
||||
});
|
||||
|
||||
it('parses scoped NPM package names', () => {
|
||||
const result = parseProviderSource('@scope/my-provider');
|
||||
expect(result).toEqual({
|
||||
type: 'npm',
|
||||
packageName: '@scope/my-provider',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('generateCacheKey', () => {
|
||||
it('generates valid cache keys for GitHub URLs', () => {
|
||||
const urlInfo = {
|
||||
type: 'github' as const,
|
||||
owner: 'user',
|
||||
repo: 'my-repo',
|
||||
branch: 'develop',
|
||||
url: 'https://github.com/user/my-repo',
|
||||
};
|
||||
|
||||
const key = generateCacheKey(urlInfo);
|
||||
expect(key).toBe('github_user_my-repo_develop');
|
||||
});
|
||||
|
||||
it('handles special characters in cache keys', () => {
|
||||
const urlInfo = {
|
||||
type: 'github' as const,
|
||||
owner: 'user-name',
|
||||
repo: 'my.repo',
|
||||
branch: 'feature/branch',
|
||||
url: 'https://github.com/user-name/my.repo',
|
||||
};
|
||||
|
||||
const key = generateCacheKey(urlInfo);
|
||||
expect(key).toBe('github_user-name_my_repo_feature_branch');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isGitHubSource', () => {
|
||||
it('identifies GitHub URLs correctly', () => {
|
||||
expect(isGitHubSource('https://github.com/user/repo')).toBe(true);
|
||||
expect(isGitHubSource('git@github.com:user/repo.git')).toBe(true);
|
||||
expect(isGitHubSource('user/repo')).toBe(true);
|
||||
expect(isGitHubSource('user/repo@develop')).toBe(true);
|
||||
});
|
||||
|
||||
it('identifies non-GitHub sources correctly', () => {
|
||||
expect(isGitHubSource('./local-provider')).toBe(false);
|
||||
expect(isGitHubSource('/absolute/path')).toBe(false);
|
||||
expect(isGitHubSource('npm-package')).toBe(false);
|
||||
expect(isGitHubSource('@scope/package')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user