Compare commits

...

2 Commits

Author SHA1 Message Date
Ryo Oka
f7f3f70c57 Support activeBuildProfile parameter (#738)
* feat: add `-activeBuildProfile`

* feat: descriptive error in case `-activeBuildProfile` is passed without actual value
2025-09-30 11:55:14 +02:00
Frostebite
c6c8236152 fix: mock github checks in tests (#724)
* fix: load fetch polyfill before tests

* refactor: extract cloud runner test helpers

* fix: load fetch polyfill before tests
2025-08-06 06:07:52 +01:00
12 changed files with 170 additions and 61 deletions

View File

@@ -3,6 +3,11 @@ name: Cloud Runner CI Pipeline
on:
push: { branches: [cloud-runner-develop, cloud-runner-preview, main] }
workflow_dispatch:
inputs:
runGithubIntegrationTests:
description: 'Run GitHub Checks integration tests'
required: false
default: 'false'
permissions:
checks: write
@@ -207,3 +212,20 @@ jobs:
name: ${{ matrix.providerStrategy }} Build (${{ matrix.targetPlatform }})
path: ${{ steps.unity-build.outputs.BUILD_ARTIFACT }}
retention-days: 14
githubChecksIntegration:
name: GitHub Checks Integration
runs-on: ubuntu-latest
if: github.event_name == 'workflow_dispatch' && github.event.inputs.runGithubIntegrationTests == 'true'
env:
RUN_GITHUB_INTEGRATION_TESTS: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- run: yarn install --frozen-lockfile
- run: yarn test cloud-runner-github-checks-integration-test --detectOpenHandles --forceExit --runInBand
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -56,14 +56,23 @@ namespace UnityBuilderAction
// of either `UnityEditor.BuildPlayerOptions` or `UnityEditor.BuildPlayerWithProfileOptions`
dynamic buildPlayerOptions;
if (options["customBuildProfile"] != "") {
if (options.TryGetValue("activeBuildProfile", out var buildProfilePath)) {
if (string.IsNullOrEmpty(buildProfilePath)) {
throw new Exception("`-activeBuildProfile` is set but with an empty value; this shouldn't happen");
}
#if UNITY_6000_0_OR_NEWER
// Load build profile from Assets folder
BuildProfile buildProfile = AssetDatabase.LoadAssetAtPath<BuildProfile>(options["customBuildProfile"]);
var buildProfile = AssetDatabase.LoadAssetAtPath<BuildProfile>(buildProfilePath)
?? throw new Exception("Build profile file not found at path: " + buildProfilePath);
// Set it as active
BuildProfile.SetActiveBuildProfile(buildProfile);
#if !BUILD_PROFILE_LOADED
throw new Exception("Build profile's define symbol not present before script execution; shouldn't happen");
#endif // BUILD_PROFILE_LOADED
// no need to set active profile, as already set by `-activeBuildProfile` CLI argument
// BuildProfile.SetActiveBuildProfile(buildProfile);
Debug.Log($"build profile: {buildProfile.name}");
// Define BuildPlayerWithProfileOptions
buildPlayerOptions = new BuildPlayerWithProfileOptions {
@@ -71,12 +80,16 @@ namespace UnityBuilderAction
locationPathName = options["customBuildPath"],
options = buildOptions,
};
#else
#else // UNITY_6000_0_OR_NEWER
throw new Exception("Build profiles are not supported by this version of Unity (" + Application.unityVersion +")");
#endif
#endif // UNITY_6000_0_OR_NEWER
} else {
#if BUILD_PROFILE_LOADED
throw new Exception("Build profile's define symbol present; shouldn't happen");
#endif // BUILD_PROFILE_LOADED
// Gather values from project
var scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(s => s.path).ToArray();

View File

@@ -21,6 +21,19 @@ namespace UnityBuilderAction.Input
EditorApplication.Exit(110);
}
#if UNITY_6000_0_OR_NEWER
var buildProfileSupport = true;
#else
var buildProfileSupport = false;
#endif // UNITY_6000_0_OR_NEWER
string buildProfile;
if (buildProfileSupport && validatedOptions.TryGetValue("activeBuildProfile", out buildProfile)) {
if (validatedOptions.ContainsKey("buildTarget")) {
Console.WriteLine("Extra argument -buildTarget");
EditorApplication.Exit(122);
}
} else {
string buildTarget;
if (!validatedOptions.TryGetValue("buildTarget", out buildTarget)) {
Console.WriteLine("Missing argument -buildTarget");
@@ -31,6 +44,7 @@ namespace UnityBuilderAction.Input
Console.WriteLine(buildTarget + " is not a defined " + typeof(BuildTarget).Name);
EditorApplication.Exit(121);
}
}
string customBuildPath;
if (!validatedOptions.TryGetValue("customBuildPath", out customBuildPath)) {

View File

@@ -153,10 +153,11 @@ echo ""
-password "$UNITY_PASSWORD" \
-customBuildName "$BUILD_NAME" \
-projectPath "$UNITY_PROJECT_PATH" \
-buildTarget "$BUILD_TARGET" \
$( [ -z "$BUILD_PROFILE" ] && echo "-buildTarget $BUILD_TARGET") \
-customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \
-customBuildProfile "$BUILD_PROFILE" \
${BUILD_PROFILE:+-activeBuildProfile} ${BUILD_PROFILE:+"$BUILD_PROFILE"} \
-executeMethod "$BUILD_METHOD" \
-buildVersion "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \

View File

@@ -125,10 +125,11 @@ unity-editor \
$( [ "${MANUAL_EXIT}" == "true" ] || echo "-quit" ) \
-customBuildName "$BUILD_NAME" \
-projectPath "$UNITY_PROJECT_PATH" \
-buildTarget "$BUILD_TARGET" \
$( [ -z "$BUILD_PROFILE" ] && echo "-buildTarget $BUILD_TARGET" ) \
-customBuildTarget "$BUILD_TARGET" \
-customBuildPath "$CUSTOM_BUILD_PATH" \
-customBuildProfile "$BUILD_PROFILE" \
${BUILD_PROFILE:+-activeBuildProfile} ${BUILD_PROFILE:+"$BUILD_PROFILE"} \
-executeMethod "$BUILD_METHOD" \
-buildVersion "$VERSION" \
-androidVersionCode "$ANDROID_VERSION_CODE" \

View File

@@ -166,7 +166,6 @@ $unityArgs = @(
"-customBuildName", "`"$Env:BUILD_NAME`"",
"-projectPath", "`"$Env:UNITY_PROJECT_PATH`"",
"-executeMethod", "`"$Env:BUILD_METHOD`"",
"-buildTarget", "`"$Env:BUILD_TARGET`"",
"-customBuildTarget", "`"$Env:BUILD_TARGET`"",
"-customBuildPath", "`"$Env:CUSTOM_BUILD_PATH`"",
"-customBuildProfile", "`"$Env:BUILD_PROFILE`"",
@@ -181,6 +180,13 @@ $unityArgs = @(
"-logfile", "-"
) + $customParametersArray
if (-not $Env:BUILD_PROFILE) {
$unityArgs += @("-buildTarget", $Env:BUILD_TARGET)
}
if ($Env:BUILD_PROFILE) {
$unityArgs += @("-activeBuildProfile", $Env:BUILD_PROFILE)
}
# Remove null items as that will fail the Start-Process call
$unityArgs = $unityArgs | Where-Object { $_ -ne $null }

View File

@@ -25,6 +25,8 @@ module.exports = {
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
modulePathIgnorePatterns: ['<rootDir>/lib/', '<rootDir>/dist/'],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// Files that will be run before Jest is loaded to set globals like fetch
setupFiles: ['<rootDir>/src/jest.globals.ts'],
// A list of paths to modules that run some code to configure or set up the testing framework after the environment is ready
setupFilesAfterEnv: ['<rootDir>/src/jest.setup.ts'],
};

View File

@@ -0,0 +1,29 @@
// Integration test for exercising real GitHub check creation and updates.
import CloudRunner from '../model/cloud-runner/cloud-runner';
import UnityVersioning from '../model/unity-versioning';
import GitHub from '../model/github';
import { TIMEOUT_INFINITE, createParameters } from '../test-utils/cloud-runner-test-helpers';
const runIntegration = process.env.RUN_GITHUB_INTEGRATION_TESTS === 'true';
const describeOrSkip = runIntegration ? describe : describe.skip;
describeOrSkip('Cloud Runner Github Checks Integration', () => {
it(
'creates and updates a real GitHub check',
async () => {
const buildParameter = await createParameters({
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
await CloudRunner.setup(buildParameter);
const checkId = await GitHub.createGitHubCheck(`integration create`);
expect(checkId).not.toEqual('');
await GitHub.updateGitHubCheck(`1 ${new Date().toISOString()}`, `integration`);
await GitHub.updateGitHubCheck(`2 ${new Date().toISOString()}`, `integration`, `success`, `completed`);
},
TIMEOUT_INFINITE,
);
});

3
src/jest.globals.ts Normal file
View File

@@ -0,0 +1,3 @@
import { fetch as undiciFetch, Headers, Request, Response } from 'undici';
Object.assign(globalThis, { fetch: undiciFetch, Headers, Request, Response });

View File

@@ -1,59 +1,65 @@
import { BuildParameters } from '../..';
import CloudRunner from '../cloud-runner';
import UnityVersioning from '../../unity-versioning';
import { Cli } from '../../cli/cli';
import CloudRunnerOptions from '../options/cloud-runner-options';
import setups from './cloud-runner-suite.test';
import { OptionValues } from 'commander';
import GitHub from '../../github';
export const TIMEOUT_INFINITE = 1e9;
async function CreateParameters(overrides: OptionValues | undefined) {
if (overrides) Cli.options = overrides;
return BuildParameters.create();
}
import { TIMEOUT_INFINITE, createParameters } from '../../../test-utils/cloud-runner-test-helpers';
describe('Cloud Runner Github Checks', () => {
setups();
it('Responds', () => {});
if (CloudRunnerOptions.cloudRunnerDebug) {
it(
'Check Handling Direct',
async () => {
// Setup parameters
const buildParameter = await CreateParameters({
versioning: 'None',
projectPath: 'test-project',
unityVersion: UnityVersioning.read('test-project'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
await CloudRunner.setup(buildParameter);
CloudRunner.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'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
GitHub.forceAsyncTest = true;
await CloudRunner.setup(buildParameter);
CloudRunner.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,
);
}
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: {},
});
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'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
await CloudRunner.setup(buildParameter);
CloudRunner.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'),
asyncCloudRunner: `true`,
githubChecks: `true`,
});
GitHub.forceAsyncTest = true;
await CloudRunner.setup(buildParameter);
CloudRunner.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,
);
});

View File

@@ -0,0 +1,11 @@
import { BuildParameters } from '../model';
import { Cli } from '../model/cli/cli';
import { OptionValues } from 'commander';
export const TIMEOUT_INFINITE = 1e9;
export async function createParameters(overrides?: OptionValues) {
if (overrides) Cli.options = overrides;
return BuildParameters.create();
}

View File

@@ -20,7 +20,8 @@ MonoBehaviour:
rid: 200022742090383361
m_OverrideGlobalSceneList: 0
m_Scenes: []
m_ScriptingDefines: []
m_ScriptingDefines:
- BUILD_PROFILE_LOADED
m_PlayerSettingsYaml:
m_Settings: []
references: