diff --git a/dist/platforms/ubuntu/steps/runsteps.sh b/dist/platforms/ubuntu/steps/runsteps.sh index 53f42eeb..1885c0ba 100644 --- a/dist/platforms/ubuntu/steps/runsteps.sh +++ b/dist/platforms/ubuntu/steps/runsteps.sh @@ -3,11 +3,19 @@ # # Run steps # -source /steps/set_extra_git_configs.sh -source /steps/set_gitcredential.sh +# STEPS_DIR defaults to /steps (the Docker container mount point). Host-mode +# execution (no Docker, see src/model/host-runner.ts) runs this script +# directly against the real filesystem and overrides STEPS_DIR to point at +# the CLI's own dist/platforms/ubuntu/steps instead - everything else in +# this file is unchanged either way. +# +STEPS_DIR="${STEPS_DIR:-/steps}" + +source "$STEPS_DIR/set_extra_git_configs.sh" +source "$STEPS_DIR/set_gitcredential.sh" if [ "$SKIP_ACTIVATION" != "true" ]; then - source /steps/activate.sh + source "$STEPS_DIR/activate.sh" # If we didn't activate successfully, exit with the exit code from the activation step. if [[ $UNITY_EXIT_CODE -ne 0 ]]; then @@ -25,10 +33,20 @@ if [ "$ACTIVATE_ONLY" = "true" ]; then exit $UNITY_EXIT_CODE fi -source /steps/build.sh +# RUN_TESTS=true (used by `game-ci test --docker`, see game-ci/cli's +# UnityTestCommand) runs the classic Docker/Hub-image-driven Unity batchmode +# test flow instead of a build - same activation/license-return steps either +# way, only the middle step differs. +if [ "$RUN_TESTS" = "true" ]; then + source "$STEPS_DIR/test.sh" + STEP_EXIT_CODE=$TEST_RUNNER_EXIT_CODE +else + source "$STEPS_DIR/build.sh" + STEP_EXIT_CODE=$BUILD_EXIT_CODE +fi if [ "$SKIP_ACTIVATION" != "true" ]; then - source /steps/return_license.sh + source "$STEPS_DIR/return_license.sh" fi # @@ -41,7 +59,7 @@ rm -r "$ACTIVATE_LICENSE_PATH" # Instructions for debugging # -if [[ $BUILD_EXIT_CODE -gt 0 ]]; then +if [[ $STEP_EXIT_CODE -gt 0 ]]; then echo "" echo "###########################" echo "# Failure #" @@ -55,7 +73,7 @@ echo "" fi; # -# Exit with code from the build step. +# Exit with code from the build/test step. # -exit $BUILD_EXIT_CODE +exit $STEP_EXIT_CODE diff --git a/dist/platforms/ubuntu/steps/test.sh b/dist/platforms/ubuntu/steps/test.sh new file mode 100644 index 00000000..6dc6e300 --- /dev/null +++ b/dist/platforms/ubuntu/steps/test.sh @@ -0,0 +1,312 @@ +#!/usr/bin/env bash + +# +# Set and display project path +# + +UNITY_PROJECT_PATH="$GITHUB_WORKSPACE/$PROJECT_PATH" +echo "Using project path \"$UNITY_PROJECT_PATH\"." + +# +# Set and display the artifacts path +# + +echo "Using artifacts path \"$ARTIFACTS_PATH\" to save test results." +FULL_ARTIFACTS_PATH=$GITHUB_WORKSPACE/$ARTIFACTS_PATH + +# +# Set and display the coverage results path +# + +echo "Using coverage results path \"$COVERAGE_RESULTS_PATH\" to save test coverage results." +FULL_COVERAGE_RESULTS_PATH=$GITHUB_WORKSPACE/$COVERAGE_RESULTS_PATH + +# +# Display custom parameters +# + +echo "Using custom parameters $CUSTOM_PARAMETERS." + +# The following tests are 2019 mode (requires Unity 2019.2.11f1 or later) +# Reference: https://docs.unity3d.com/2019.3/Documentation/Manual/CommandLineArguments.html + +# +# Display the unity version +# + +echo "Using Unity version \"$UNITY_VERSION\" to test." + +# +# Setup token for private package registry. +# + +if [ -n "$PRIVATE_REGISTRY_TOKEN" ]; then + echo "Private registry token detected, creating .upmconfig.toml" + + UPM_CONFIG_TOML_PATH="$HOME/.upmconfig.toml" + echo "Creating toml at path: $UPM_CONFIG_TOML_PATH" + + touch $UPM_CONFIG_TOML_PATH + + cat > "$UPM_CONFIG_TOML_PATH" <> "$UPM_CONFIG_TOML_PATH" < /dev/null + then + echo "jq could not be found. This is required for package mode, and is likely the result of using a custom Docker image. Please use the default image or install jq to your custom image." + exit 1 + fi + + echo "" + echo "###########################" + echo "# Package Folder #" + echo "###########################" + echo "" + + ls -la "$UNITY_PROJECT_PATH" + echo "" + + echo "Creating an empty Unity project to add the package $PACKAGE_NAME to." + + TEMP_PROJECT_PATH="./TempProject" + + unity-editor \ + -batchmode \ + -createProject "$TEMP_PROJECT_PATH" \ + -quit + + # use jq to add the package to the temp project through manually modifying Packages/manifest.json + echo "Adding package to the temporary project's dependencies and testables..." + echo "" + + PACKAGE_MANIFEST_PATH="$TEMP_PROJECT_PATH/Packages/manifest.json" + if [ ! -f "$PACKAGE_MANIFEST_PATH" ]; then + echo "Packages/manifest.json was not created properly. This indicates a problem with the CLI, not with your package. Logging directories and aborting..." + + echo "" + echo "###########################" + echo "# Temp Project Folder #" + echo "###########################" + echo "" + + ls -a "$TEMP_PROJECT_PATH" + + echo "" + echo "################################" + echo "# Temp Project Packages Folder #" + echo "################################" + echo "" + + ls -a "$TEMP_PROJECT_PATH/Packages" + + exit 1 + fi + + PACKAGE_MANIFEST_JSON=$(cat "$PACKAGE_MANIFEST_PATH") + if [ -z "$SCOPED_REGISTRY_URL" ] || [ -z "$REGISTRY_SCOPES" ]; then + echo "$PACKAGE_MANIFEST_JSON" | \ + jq \ + --arg packageName "$PACKAGE_NAME" \ + --arg projectPath "$UNITY_PROJECT_PATH" \ + '.dependencies += {"com.unity.testtools.codecoverage": "1.1.1"} | .dependencies += {"\($packageName)": "file:\($projectPath)"} | . += {testables: ["\($packageName)"]}' \ + > "$PACKAGE_MANIFEST_PATH" + + else + + echo "$PACKAGE_MANIFEST_JSON" | \ + jq \ + --arg packageName "$PACKAGE_NAME" \ + --arg projectPath "$UNITY_PROJECT_PATH" \ + --arg scopedRegistryUrl "$SCOPED_REGISTRY_URL" \ + --argjson registryScopes "$(echo "[\"$REGISTRY_SCOPES\"]" | sed 's/,/","/g')" \ + '.dependencies += {"com.unity.testtools.codecoverage": "1.1.1"} | + .dependencies += {"\($packageName)": "file:\($projectPath)"} | + . += {testables: ["\($packageName)"]} | + . += {scopedRegistries: [{"name":"dependency", "url":"\($scopedRegistryUrl)", scopes: $registryScopes}] }' \ + > "$PACKAGE_MANIFEST_PATH" + fi + + UNITY_PROJECT_PATH="$TEMP_PROJECT_PATH" + +fi + +# +# Overall info +# + +echo "" +echo "###########################" +echo "# Artifacts folder #" +echo "###########################" +echo "" +echo "Creating \"$FULL_ARTIFACTS_PATH\" if it does not exist." +mkdir -p $FULL_ARTIFACTS_PATH + +echo "" +echo "###########################" +echo "# Project directory #" +echo "###########################" +echo "" +ls -alh "$UNITY_PROJECT_PATH" + +# +# coverageEnabled gates the code-coverage flags entirely (game-ci/unity-test-runner#311): +# some Unity versions/configurations crash or fail to compile with +# com.unity.testtools.codecoverage's instrumentation enabled. Default true +# to match prior behavior when unset. +# +COVERAGE_FLAGS=() +if [ -z "$COVERAGE_ENABLED" ] || [ "$COVERAGE_ENABLED" = "true" ]; then + COVERAGE_FLAGS=(-coverageResultsPath "$FULL_COVERAGE_RESULTS_PATH" -enableCodeCoverage -debugCodeOptimization -coverageOptions "$COVERAGE_OPTIONS") +else + echo "Code coverage disabled (coverageEnabled=false)." +fi + +# +# Testing for each platform +# +for platform in ${TEST_PLATFORMS//;/ }; do + if [[ "$platform" == "standalone" ]]; then + echo "" + echo "###########################" + echo "# Building Standalone #" + echo "###########################" + echo "" + + # Create directories if they do not exist + mkdir -p "$UNITY_PROJECT_PATH/Assets/Editor/" + mkdir -p "$UNITY_PROJECT_PATH/Assets/Player/" + # Copy the scripts + cp -R "${TEST_RUNNER_ACTION_DIR:-/UnityTestRunnerAction}/Assets/Editor/" "$UNITY_PROJECT_PATH/Assets/Editor/" + cp -R "${TEST_RUNNER_ACTION_DIR:-/UnityTestRunnerAction}/Assets/Player/" "$UNITY_PROJECT_PATH/Assets/Player/" + # Verify recursive paths + ls -Ralph "$UNITY_PROJECT_PATH/Assets/Editor/" + ls -Ralph "$UNITY_PROJECT_PATH/Assets/Player/" + + runTests="-runTests -testPlatform StandaloneLinux64 -builtTestRunnerPath $UNITY_PROJECT_PATH/Build/UnityTestRunner-Standalone" + else + echo "" + echo "###########################" + echo "# Testing in $platform #" + echo "###########################" + echo "" + + if [[ "$platform" != "COMBINE_RESULTS" ]]; then + runTests="-runTests -testPlatform $platform -testResults $FULL_ARTIFACTS_PATH/$platform-results.xml" + else + runTests="-quit" + fi + fi + + eval unity-editor \ + -batchmode \ + -logFile "\"$FULL_ARTIFACTS_PATH/$platform.log\"" \ + -projectPath "\"$UNITY_PROJECT_PATH\"" \ + $runTests \ + $COVERAGE_FLAGS \ + $CUSTOM_PARAMETERS + + # Catch exit code + TEST_EXIT_CODE=$? + + # Print unity log output + cat "$FULL_ARTIFACTS_PATH/$platform.log" + + if [[ $TEST_EXIT_CODE -eq 0 && "$platform" == "standalone" ]]; then + echo "" + echo "###########################" + echo "# Testing Standalone #" + echo "###########################" + echo "" + + # Code Coverage currently only supports code ran in the Editor and not in Standalone/Player. + # https://docs.unity3d.com/Packages/com.unity.testtools.codecoverage@1.2/manual/TechnicalDetails.html#how-it-works + + xvfb-run -a -e /dev/stdout "$UNITY_PROJECT_PATH/Build/UnityTestRunner-Standalone" \ + -batchmode \ + -nographics \ + -logFile "$FULL_ARTIFACTS_PATH/$platform-player.log" \ + -testResults "$FULL_ARTIFACTS_PATH/$platform-results.xml" + + # Catch exit code + TEST_EXIT_CODE=$? + + # Print player log output + cat "$FULL_ARTIFACTS_PATH/$platform-player.log" + fi + + # Display results + if [ $TEST_EXIT_CODE -eq 0 ]; then + echo "Run succeeded, no failures occurred"; + elif [ $TEST_EXIT_CODE -eq 2 ]; then + echo "Run succeeded, some tests failed"; + elif [ $TEST_EXIT_CODE -eq 3 ]; then + echo "Run failure (other failure)"; + else + echo "Unexpected exit code $TEST_EXIT_CODE"; + fi + + if [ $TEST_EXIT_CODE -ne 0 ]; then + TEST_RUNNER_EXIT_CODE=$TEST_EXIT_CODE + fi + + echo "" + echo "###########################" + echo "# $platform Results #" + echo "###########################" + echo "" + + if [[ "$platform" != "COMBINE_RESULTS" ]]; then + cat "$FULL_ARTIFACTS_PATH/$platform-results.xml" + cat "$FULL_ARTIFACTS_PATH/$platform-results.xml" | grep test-run | grep Passed + fi +done + +# TEST_RUNNER_EXIT_CODE stays 0 if every platform above passed (loop only +# ever raises it, never resets it back down). +if [ -z "$TEST_RUNNER_EXIT_CODE" ]; then + TEST_RUNNER_EXIT_CODE=0 +fi + +# +# Permissions +# + +# Make a given user owner of all artifacts +if [[ -n "$CHOWN_FILES_TO" ]]; then + chown -R "$CHOWN_FILES_TO" "$UNITY_PROJECT_PATH" + chown -R "$CHOWN_FILES_TO" "$FULL_ARTIFACTS_PATH" + if [ -d "$FULL_COVERAGE_RESULTS_PATH" ]; then + chown -R "$CHOWN_FILES_TO" "$FULL_COVERAGE_RESULTS_PATH" + fi +fi + +# Add read permissions for everyone to all artifacts +chmod -R a+r "$UNITY_PROJECT_PATH" +chmod -R a+r "$FULL_ARTIFACTS_PATH" + +# Check if coverage results directory exists +if [ -d "$FULL_COVERAGE_RESULTS_PATH" ]; then + chmod -R a+r "$FULL_COVERAGE_RESULTS_PATH" +elif [ -z "$COVERAGE_ENABLED" ] || [ "$COVERAGE_ENABLED" = "true" ]; then + echo "Coverage results directory does not exist. If you are expecting coverage results, please make sure the Code Coverage package is installed in your unity project and that it is set up correctly." +fi diff --git a/dist/test-standalone-scripts/.gitignore b/dist/test-standalone-scripts/.gitignore new file mode 100644 index 00000000..a61293ce --- /dev/null +++ b/dist/test-standalone-scripts/.gitignore @@ -0,0 +1,56 @@ +# +# Note: Non default ignore file, as this only tests Builder script. +# + +[Ll]ibrary/ +[Tt]emp/ +[Oo]bj/ +[Bb]uild/ +[Bb]uilds/ +[Ll]ogs/ + +# Additional ignores +[Bb]in/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# [Aa]ssets/AssetStoreTools* + +# IDEs +.vs/ +.idea/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +#*.csproj +*.unityproj +#*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.unitypackage + +# Crashlytics generated file +crashlytics-build.properties diff --git a/dist/test-standalone-scripts/Assets/Editor.meta b/dist/test-standalone-scripts/Assets/Editor.meta new file mode 100644 index 00000000..53d7b55b --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dea28d93f6267af4f8661eb2043f749a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction.meta b/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction.meta new file mode 100644 index 00000000..3754dc08 --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0cf2129f2cbdf3b4185e808b8098349d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction/PlayerBuildModifier.cs b/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction/PlayerBuildModifier.cs new file mode 100644 index 00000000..4381b2ba --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction/PlayerBuildModifier.cs @@ -0,0 +1,49 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.TestTools; +using UnityEngine; +using UnityEngine.TestTools; +using UnityTestRunnerAction; + +[assembly: TestPlayerBuildModifier(typeof(HeadlessPlayModeSetup))] +[assembly: PostBuildCleanup(typeof(HeadlessPlayModeSetup))] + +namespace UnityTestRunnerAction +{ + public class HeadlessPlayModeSetup : ITestPlayerBuildModifier, IPostBuildCleanup + { + private static bool s_RunningPlayerTests; + public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions) + { + // Do not launch the player after the build completes. Disable the PlayerConnection. + playerOptions.options &= ~(BuildOptions.AutoRunPlayer | BuildOptions.ConnectToHost | BuildOptions.WaitForPlayerConnection); + + // Not supporting Mac currently. + playerOptions.target = SystemInfo.operatingSystemFamily == OperatingSystemFamily.Windows ? BuildTarget.StandaloneWindows64 : BuildTarget.StandaloneLinux64; + + string[] commandLineArgs = Environment.GetCommandLineArgs(); + playerOptions.locationPathName = commandLineArgs[Array.IndexOf(commandLineArgs, "-builtTestRunnerPath") + 1]; ; + + // Instruct the cleanup to exit the Editor if the run came from the command line. + // The variable is static because the cleanup is being invoked in a new instance of the class. + s_RunningPlayerTests = true; + return playerOptions; + } + + public void Cleanup() + { + if (s_RunningPlayerTests && IsRunningTestsFromCommandLine()) + { + // Exit the Editor on the next update, allowing for other PostBuildCleanup steps to run. + EditorApplication.update += () => { EditorApplication.Exit(0); }; + } + } + + private static bool IsRunningTestsFromCommandLine() + { + var commandLineArgs = Environment.GetCommandLineArgs(); + return commandLineArgs.Any(value => value == "-runTests"); + } + } +} diff --git a/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction/PlayerBuildModifier.cs.meta b/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction/PlayerBuildModifier.cs.meta new file mode 100644 index 00000000..6bd9175d --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Editor/UnityTestRunnerAction/PlayerBuildModifier.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 500127a78ea2408479825b0807929249 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Assets/Player.meta b/dist/test-standalone-scripts/Assets/Player.meta new file mode 100644 index 00000000..c53fdb62 --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Player.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dbe67f3a46ffb8643acd08c86957d9bf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction.meta b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction.meta new file mode 100644 index 00000000..0365f641 --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7d97b4f9ec1fb744a9aab82d6c21100d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/TestRunCallback.cs b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/TestRunCallback.cs new file mode 100644 index 00000000..e8e861f8 --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/TestRunCallback.cs @@ -0,0 +1,92 @@ +using NUnit.Framework.Interfaces; +using System; +using System.Xml; +using UnityEngine; +using UnityEngine.TestRunner; +using UnityTestRunnerAction; + +[assembly: TestRunCallback(typeof(MyTestRunCallback))] + +namespace UnityTestRunnerAction +{ + public class MyTestRunCallback : ITestRunCallback + { + private const string k_nUnitVersion = "3.5.0.0"; + + private const string k_TestRunNode = "test-run"; + private const string k_Id = "id"; + private const string k_Testcasecount = "testcasecount"; + private const string k_Result = "result"; + private const string k_Total = "total"; + private const string k_Passed = "passed"; + private const string k_Failed = "failed"; + private const string k_Inconclusive = "inconclusive"; + private const string k_Skipped = "skipped"; + private const string k_Asserts = "asserts"; + private const string k_EngineVersion = "engine-version"; + private const string k_ClrVersion = "clr-version"; + private const string k_StartTime = "start-time"; + private const string k_EndTime = "end-time"; + private const string k_Duration = "duration"; + + private const string k_TimeFormat = "u"; + + private ITest fullTest; + + public void RunStarted(ITest testsToRun) + { + if (fullTest == null) + { + fullTest = testsToRun; + } + } + + public void RunFinished(ITestResult testResults) + { + if (testResults.Test != fullTest) + { + return; + } + + string[] commandLineArgs = Environment.GetCommandLineArgs(); + string testResultsPath = commandLineArgs[Array.IndexOf(commandLineArgs, "-testResults") + 1]; + using (var writer = XmlWriter.Create(testResultsPath, new XmlWriterSettings() { Indent = true })) + { + // Manually add the outer test-run node, because testResults.ToXml doesn't include it. + + var testRunNode = new TNode(k_TestRunNode); + + testRunNode.AddAttribute(k_Id, "2"); + testRunNode.AddAttribute(k_Testcasecount, (testResults.PassCount + testResults.FailCount + testResults.SkipCount + testResults.InconclusiveCount).ToString()); + testRunNode.AddAttribute(k_Result, testResults.ResultState.ToString()); + testRunNode.AddAttribute(k_Total, (testResults.PassCount + testResults.FailCount + testResults.SkipCount + testResults.InconclusiveCount).ToString()); + testRunNode.AddAttribute(k_Passed, testResults.PassCount.ToString()); + testRunNode.AddAttribute(k_Failed, testResults.FailCount.ToString()); + testRunNode.AddAttribute(k_Inconclusive, testResults.InconclusiveCount.ToString()); + testRunNode.AddAttribute(k_Skipped, testResults.SkipCount.ToString()); + testRunNode.AddAttribute(k_Asserts, testResults.AssertCount.ToString()); + testRunNode.AddAttribute(k_EngineVersion, k_nUnitVersion); + testRunNode.AddAttribute(k_ClrVersion, Environment.Version.ToString()); + testRunNode.AddAttribute(k_StartTime, testResults.StartTime.ToString(k_TimeFormat)); + testRunNode.AddAttribute(k_EndTime, testResults.EndTime.ToString(k_TimeFormat)); + testRunNode.AddAttribute(k_Duration, testResults.Duration.ToString()); + + var resultNode = testResults.ToXml(true); + testRunNode.ChildNodes.Add(resultNode); + + testRunNode.WriteTo(writer); + writer.Flush(); + } + + Application.Quit(testResults.ResultState.Status == TestStatus.Failed ? 2 : 0); + } + + public void TestStarted(ITest test) + { + } + + public void TestFinished(ITestResult result) + { + } + } +} diff --git a/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/TestRunCallback.cs.meta b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/TestRunCallback.cs.meta new file mode 100644 index 00000000..9c9f7771 --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/TestRunCallback.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ee1aa3805d7b51f46a3ddefe39d76ba5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/UnityTestRunnerAction.asmdef b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/UnityTestRunnerAction.asmdef new file mode 100644 index 00000000..3050849c --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/UnityTestRunnerAction.asmdef @@ -0,0 +1,15 @@ +{ + "name": "UnityTestRunnerAction", + "references": [ + "GUID:27619889b8ba8c24980f49ee34dbb44a" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [] +} \ No newline at end of file diff --git a/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/UnityTestRunnerAction.asmdef.meta b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/UnityTestRunnerAction.asmdef.meta new file mode 100644 index 00000000..4d447371 --- /dev/null +++ b/dist/test-standalone-scripts/Assets/Player/UnityTestRunnerAction/UnityTestRunnerAction.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8a9bfe020dc3a8747afebc3a87516973 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/dist/test-standalone-scripts/Packages/manifest.json b/dist/test-standalone-scripts/Packages/manifest.json new file mode 100644 index 00000000..bae1d688 --- /dev/null +++ b/dist/test-standalone-scripts/Packages/manifest.json @@ -0,0 +1,49 @@ +{ + "dependencies": { + "com.unity.2d.sprite": "1.0.0", + "com.unity.2d.tilemap": "1.0.0", + "com.unity.ads": "2.0.8", + "com.unity.analytics": "3.3.2", + "com.unity.collab-proxy": "1.2.16", + "com.unity.ext.nunit": "1.0.0", + "com.unity.ide.rider": "1.1.0", + "com.unity.ide.vscode": "1.1.2", + "com.unity.package-manager-ui": "2.2.0", + "com.unity.purchasing": "2.0.6", + "com.unity.test-framework": "1.0.18", + "com.unity.textmeshpro": "2.0.1", + "com.unity.timeline": "1.1.0", + "com.unity.ugui": "1.0.0", + "com.unity.modules.ai": "1.0.0", + "com.unity.modules.androidjni": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.assetbundle": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.cloth": "1.0.0", + "com.unity.modules.director": "1.0.0", + "com.unity.modules.imageconversion": "1.0.0", + "com.unity.modules.imgui": "1.0.0", + "com.unity.modules.jsonserialize": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0", + "com.unity.modules.physics": "1.0.0", + "com.unity.modules.physics2d": "1.0.0", + "com.unity.modules.screencapture": "1.0.0", + "com.unity.modules.terrain": "1.0.0", + "com.unity.modules.terrainphysics": "1.0.0", + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.ui": "1.0.0", + "com.unity.modules.uielements": "1.0.0", + "com.unity.modules.umbra": "1.0.0", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.modules.unitywebrequestassetbundle": "1.0.0", + "com.unity.modules.unitywebrequestaudio": "1.0.0", + "com.unity.modules.unitywebrequesttexture": "1.0.0", + "com.unity.modules.unitywebrequestwww": "1.0.0", + "com.unity.modules.vehicles": "1.0.0", + "com.unity.modules.video": "1.0.0", + "com.unity.modules.vr": "1.0.0", + "com.unity.modules.wind": "1.0.0", + "com.unity.modules.xr": "1.0.0" + } +} diff --git a/dist/test-standalone-scripts/ProjectSettings/AudioManager.asset b/dist/test-standalone-scripts/ProjectSettings/AudioManager.asset new file mode 100644 index 00000000..40ed38ad Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/AudioManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/ClusterInputManager.asset b/dist/test-standalone-scripts/ProjectSettings/ClusterInputManager.asset new file mode 100644 index 00000000..f9df0535 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/ClusterInputManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/DynamicsManager.asset b/dist/test-standalone-scripts/ProjectSettings/DynamicsManager.asset new file mode 100644 index 00000000..b5dffeb2 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/DynamicsManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/EditorBuildSettings.asset b/dist/test-standalone-scripts/ProjectSettings/EditorBuildSettings.asset new file mode 100644 index 00000000..789dd69a Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/EditorBuildSettings.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/EditorSettings.asset b/dist/test-standalone-scripts/ProjectSettings/EditorSettings.asset new file mode 100644 index 00000000..0e9a94a1 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/EditorSettings.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/GraphicsSettings.asset b/dist/test-standalone-scripts/ProjectSettings/GraphicsSettings.asset new file mode 100644 index 00000000..94ef0ba1 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/GraphicsSettings.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/InputManager.asset b/dist/test-standalone-scripts/ProjectSettings/InputManager.asset new file mode 100644 index 00000000..7a48ab5f Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/InputManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/NavMeshAreas.asset b/dist/test-standalone-scripts/ProjectSettings/NavMeshAreas.asset new file mode 100644 index 00000000..eaba5b14 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/NavMeshAreas.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/Physics2DSettings.asset b/dist/test-standalone-scripts/ProjectSettings/Physics2DSettings.asset new file mode 100644 index 00000000..74a3e6cc Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/Physics2DSettings.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/PresetManager.asset b/dist/test-standalone-scripts/ProjectSettings/PresetManager.asset new file mode 100644 index 00000000..ec72a0e0 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/PresetManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/ProjectSettings.asset b/dist/test-standalone-scripts/ProjectSettings/ProjectSettings.asset new file mode 100644 index 00000000..87709fd3 --- /dev/null +++ b/dist/test-standalone-scripts/ProjectSettings/ProjectSettings.asset @@ -0,0 +1,619 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!129 &1 +PlayerSettings: + m_ObjectHideFlags: 0 + serializedVersion: 18 + productGUID: 52397747394a6224aa209092f5947d3d + AndroidProfiler: 0 + AndroidFilterTouchesWhenObscured: 0 + AndroidEnableSustainedPerformanceMode: 0 + defaultScreenOrientation: 4 + targetDevice: 2 + useOnDemandResources: 0 + accelerometerFrequency: 60 + companyName: DefaultCompany + productName: test-standalone-scripts + defaultCursor: {fileID: 0} + cursorHotspot: {x: 0, y: 0} + m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} + m_ShowUnitySplashScreen: 1 + m_ShowUnitySplashLogo: 1 + m_SplashScreenOverlayOpacity: 1 + m_SplashScreenAnimation: 1 + m_SplashScreenLogoStyle: 1 + m_SplashScreenDrawMode: 0 + m_SplashScreenBackgroundAnimationZoom: 1 + m_SplashScreenLogoAnimationZoom: 1 + m_SplashScreenBackgroundLandscapeAspect: 1 + m_SplashScreenBackgroundPortraitAspect: 1 + m_SplashScreenBackgroundLandscapeUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenBackgroundPortraitUvs: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + m_SplashScreenLogos: [] + m_VirtualRealitySplashScreen: {fileID: 0} + m_HolographicTrackingLossScreen: {fileID: 0} + defaultScreenWidth: 1024 + defaultScreenHeight: 768 + defaultScreenWidthWeb: 960 + defaultScreenHeightWeb: 600 + m_StereoRenderingPath: 0 + m_ActiveColorSpace: 0 + m_MTRendering: 1 + m_StackTraceTypes: 010000000100000001000000010000000100000001000000 + iosShowActivityIndicatorOnLoading: -1 + androidShowActivityIndicatorOnLoading: -1 + displayResolutionDialog: 0 + iosUseCustomAppBackgroundBehavior: 0 + iosAllowHTTPDownload: 1 + allowedAutorotateToPortrait: 1 + allowedAutorotateToPortraitUpsideDown: 1 + allowedAutorotateToLandscapeRight: 1 + allowedAutorotateToLandscapeLeft: 1 + useOSAutorotation: 1 + use32BitDisplayBuffer: 1 + preserveFramebufferAlpha: 0 + disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 1 + androidUseSwappy: 0 + androidBlitType: 0 + defaultIsNativeResolution: 1 + macRetinaSupport: 1 + runInBackground: 0 + captureSingleScreen: 0 + muteOtherAudioSources: 0 + Prepare IOS For Recording: 0 + Force IOS Speakers When Recording: 0 + deferSystemGesturesMode: 0 + hideHomeButton: 0 + submitAnalytics: 1 + usePlayerLog: 1 + bakeCollisionMeshes: 0 + forceSingleInstance: 0 + useFlipModelSwapchain: 1 + resizableWindow: 0 + useMacAppStoreValidation: 0 + macAppStoreCategory: public.app-category.games + gpuSkinning: 0 + graphicsJobs: 0 + xboxPIXTextureCapture: 0 + xboxEnableAvatar: 0 + xboxEnableKinect: 0 + xboxEnableKinectAutoTracking: 0 + xboxEnableFitness: 0 + visibleInBackground: 1 + allowFullscreenSwitch: 1 + graphicsJobMode: 0 + fullscreenMode: 1 + xboxSpeechDB: 0 + xboxEnableHeadOrientation: 0 + xboxEnableGuest: 0 + xboxEnablePIXSampling: 0 + metalFramebufferOnly: 0 + xboxOneResolution: 0 + xboxOneSResolution: 0 + xboxOneXResolution: 3 + xboxOneMonoLoggingLevel: 0 + xboxOneLoggingLevel: 1 + xboxOneDisableEsram: 0 + xboxOnePresentImmediateThreshold: 0 + switchQueueCommandMemory: 1048576 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 + vulkanEnableSetSRGBWrite: 0 + m_SupportedAspectRatios: + 4:3: 1 + 5:4: 1 + 16:10: 1 + 16:9: 1 + Others: 1 + bundleVersion: 1.0 + preloadedAssets: [] + metroInputSource: 0 + wsaTransparentSwapchain: 0 + m_HolographicPauseOnTrackingLoss: 1 + xboxOneDisableKinectGpuReservation: 1 + xboxOneEnable7thCore: 1 + vrSettings: + cardboard: + depthFormat: 0 + enableTransitionView: 0 + daydream: + depthFormat: 0 + useSustainedPerformanceMode: 0 + enableVideoLayer: 0 + useProtectedVideoMemory: 0 + minimumSupportedHeadTracking: 0 + maximumSupportedHeadTracking: 1 + hololens: + depthFormat: 1 + depthBufferSharingEnabled: 1 + lumin: + depthFormat: 0 + frameTiming: 2 + enableGLCache: 0 + glCacheMaxBlobSize: 524288 + glCacheMaxFileSize: 8388608 + oculus: + sharedDepthBuffer: 1 + dashSupport: 1 + lowOverheadMode: 0 + protectedContext: 0 + v2Signing: 0 + enable360StereoCapture: 0 + isWsaHolographicRemotingEnabled: 0 + protectGraphicsMemory: 0 + enableFrameTimingStats: 0 + useHDRDisplay: 0 + m_ColorGamuts: 00000000 + targetPixelDensity: 30 + resolutionScalingMode: 0 + androidSupportedAspectRatio: 1 + androidMaxAspectRatio: 2.1 + applicationIdentifier: {} + buildNumber: {} + AndroidBundleVersionCode: 1 + AndroidMinSdkVersion: 16 + AndroidTargetSdkVersion: 0 + AndroidPreferredInstallLocation: 1 + aotOptions: + stripEngineCode: 1 + iPhoneStrippingLevel: 0 + iPhoneScriptCallOptimization: 0 + ForceInternetPermission: 0 + ForceSDCardPermission: 0 + CreateWallpaper: 0 + APKExpansionFiles: 0 + keepLoadedShadersAlive: 0 + StripUnusedMeshComponents: 0 + VertexChannelCompressionMask: 4054 + iPhoneSdkVersion: 988 + iOSTargetOSVersionString: 9.0 + tvOSSdkVersion: 0 + tvOSRequireExtendedGameController: 0 + tvOSTargetOSVersionString: 9.0 + uIPrerenderedIcon: 0 + uIRequiresPersistentWiFi: 0 + uIRequiresFullScreen: 1 + uIStatusBarHidden: 1 + uIExitOnSuspend: 0 + uIStatusBarStyle: 0 + iPhoneSplashScreen: {fileID: 0} + iPhoneHighResSplashScreen: {fileID: 0} + iPhoneTallHighResSplashScreen: {fileID: 0} + iPhone47inSplashScreen: {fileID: 0} + iPhone55inPortraitSplashScreen: {fileID: 0} + iPhone55inLandscapeSplashScreen: {fileID: 0} + iPhone58inPortraitSplashScreen: {fileID: 0} + iPhone58inLandscapeSplashScreen: {fileID: 0} + iPadPortraitSplashScreen: {fileID: 0} + iPadHighResPortraitSplashScreen: {fileID: 0} + iPadLandscapeSplashScreen: {fileID: 0} + iPadHighResLandscapeSplashScreen: {fileID: 0} + iPhone65inPortraitSplashScreen: {fileID: 0} + iPhone65inLandscapeSplashScreen: {fileID: 0} + iPhone61inPortraitSplashScreen: {fileID: 0} + iPhone61inLandscapeSplashScreen: {fileID: 0} + appleTVSplashScreen: {fileID: 0} + appleTVSplashScreen2x: {fileID: 0} + tvOSSmallIconLayers: [] + tvOSSmallIconLayers2x: [] + tvOSLargeIconLayers: [] + tvOSLargeIconLayers2x: [] + tvOSTopShelfImageLayers: [] + tvOSTopShelfImageLayers2x: [] + tvOSTopShelfImageWideLayers: [] + tvOSTopShelfImageWideLayers2x: [] + iOSLaunchScreenType: 0 + iOSLaunchScreenPortrait: {fileID: 0} + iOSLaunchScreenLandscape: {fileID: 0} + iOSLaunchScreenBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreenFillPct: 100 + iOSLaunchScreenSize: 100 + iOSLaunchScreenCustomXibPath: + iOSLaunchScreeniPadType: 0 + iOSLaunchScreeniPadImage: {fileID: 0} + iOSLaunchScreeniPadBackgroundColor: + serializedVersion: 2 + rgba: 0 + iOSLaunchScreeniPadFillPct: 100 + iOSLaunchScreeniPadSize: 100 + iOSLaunchScreeniPadCustomXibPath: + iOSUseLaunchScreenStoryboard: 0 + iOSLaunchScreenCustomStoryboardPath: + iOSDeviceRequirements: [] + iOSURLSchemes: [] + iOSBackgroundModes: 0 + iOSMetalForceHardShadows: 0 + metalEditorSupport: 1 + metalAPIValidation: 1 + iOSRenderExtraFrameOnPause: 0 + appleDeveloperTeamID: + iOSManualSigningProvisioningProfileID: + tvOSManualSigningProvisioningProfileID: + iOSManualSigningProvisioningProfileType: 0 + tvOSManualSigningProvisioningProfileType: 0 + appleEnableAutomaticSigning: 0 + iOSRequireARKit: 0 + iOSAutomaticallyDetectAndAddCapabilities: 1 + appleEnableProMotion: 0 + clonedFromGUID: 00000000000000000000000000000000 + templatePackageId: + templateDefaultScene: + AndroidTargetArchitectures: 1 + AndroidSplashScreenScale: 0 + androidSplashScreen: {fileID: 0} + AndroidKeystoreName: + AndroidKeyaliasName: + AndroidBuildApkPerCpuArchitecture: 0 + AndroidTVCompatibility: 0 + AndroidIsGame: 1 + AndroidEnableTango: 0 + androidEnableBanner: 1 + androidUseLowAccuracyLocation: 0 + androidUseCustomKeystore: 0 + m_AndroidBanners: + - width: 320 + height: 180 + banner: {fileID: 0} + androidGamepadSupportLevel: 0 + AndroidValidateAppBundleSize: 1 + AndroidAppBundleSizeToValidate: 150 + resolutionDialogBanner: {fileID: 0} + m_BuildTargetIcons: [] + m_BuildTargetPlatformIcons: [] + m_BuildTargetBatching: [] + m_BuildTargetGraphicsAPIs: [] + m_BuildTargetVRSettings: [] + openGLRequireES31: 0 + openGLRequireES31AEP: 0 + openGLRequireES32: 0 + vuforiaEnabled: 0 + m_TemplateCustomTags: {} + mobileMTRendering: + Android: 1 + iPhone: 1 + tvOS: 1 + m_BuildTargetGroupLightmapEncodingQuality: [] + m_BuildTargetGroupLightmapSettings: [] + playModeTestRunnerEnabled: 0 + runPlayModeTestAsEditModeTest: 0 + actionOnDotNetUnhandledException: 1 + enableInternalProfiler: 0 + logObjCUncaughtExceptions: 1 + enableCrashReportAPI: 0 + cameraUsageDescription: + locationUsageDescription: + microphoneUsageDescription: + switchNetLibKey: + switchSocketMemoryPoolSize: 6144 + switchSocketAllocatorPoolSize: 128 + switchSocketConcurrencyLimit: 14 + switchScreenResolutionBehavior: 2 + switchUseCPUProfiler: 0 + switchApplicationID: 0x01004b9000490000 + switchNSODependencies: + switchTitleNames_0: + switchTitleNames_1: + switchTitleNames_2: + switchTitleNames_3: + switchTitleNames_4: + switchTitleNames_5: + switchTitleNames_6: + switchTitleNames_7: + switchTitleNames_8: + switchTitleNames_9: + switchTitleNames_10: + switchTitleNames_11: + switchTitleNames_12: + switchTitleNames_13: + switchTitleNames_14: + switchPublisherNames_0: + switchPublisherNames_1: + switchPublisherNames_2: + switchPublisherNames_3: + switchPublisherNames_4: + switchPublisherNames_5: + switchPublisherNames_6: + switchPublisherNames_7: + switchPublisherNames_8: + switchPublisherNames_9: + switchPublisherNames_10: + switchPublisherNames_11: + switchPublisherNames_12: + switchPublisherNames_13: + switchPublisherNames_14: + switchIcons_0: {fileID: 0} + switchIcons_1: {fileID: 0} + switchIcons_2: {fileID: 0} + switchIcons_3: {fileID: 0} + switchIcons_4: {fileID: 0} + switchIcons_5: {fileID: 0} + switchIcons_6: {fileID: 0} + switchIcons_7: {fileID: 0} + switchIcons_8: {fileID: 0} + switchIcons_9: {fileID: 0} + switchIcons_10: {fileID: 0} + switchIcons_11: {fileID: 0} + switchIcons_12: {fileID: 0} + switchIcons_13: {fileID: 0} + switchIcons_14: {fileID: 0} + switchSmallIcons_0: {fileID: 0} + switchSmallIcons_1: {fileID: 0} + switchSmallIcons_2: {fileID: 0} + switchSmallIcons_3: {fileID: 0} + switchSmallIcons_4: {fileID: 0} + switchSmallIcons_5: {fileID: 0} + switchSmallIcons_6: {fileID: 0} + switchSmallIcons_7: {fileID: 0} + switchSmallIcons_8: {fileID: 0} + switchSmallIcons_9: {fileID: 0} + switchSmallIcons_10: {fileID: 0} + switchSmallIcons_11: {fileID: 0} + switchSmallIcons_12: {fileID: 0} + switchSmallIcons_13: {fileID: 0} + switchSmallIcons_14: {fileID: 0} + switchManualHTML: + switchAccessibleURLs: + switchLegalInformation: + switchMainThreadStackSize: 1048576 + switchPresenceGroupId: + switchLogoHandling: 0 + switchReleaseVersion: 0 + switchDisplayVersion: 1.0.0 + switchStartupUserAccount: 0 + switchTouchScreenUsage: 0 + switchSupportedLanguagesMask: 0 + switchLogoType: 0 + switchApplicationErrorCodeCategory: + switchUserAccountSaveDataSize: 0 + switchUserAccountSaveDataJournalSize: 0 + switchApplicationAttribute: 0 + switchCardSpecSize: -1 + switchCardSpecClock: -1 + switchRatingsMask: 0 + switchRatingsInt_0: 0 + switchRatingsInt_1: 0 + switchRatingsInt_2: 0 + switchRatingsInt_3: 0 + switchRatingsInt_4: 0 + switchRatingsInt_5: 0 + switchRatingsInt_6: 0 + switchRatingsInt_7: 0 + switchRatingsInt_8: 0 + switchRatingsInt_9: 0 + switchRatingsInt_10: 0 + switchRatingsInt_11: 0 + switchLocalCommunicationIds_0: + switchLocalCommunicationIds_1: + switchLocalCommunicationIds_2: + switchLocalCommunicationIds_3: + switchLocalCommunicationIds_4: + switchLocalCommunicationIds_5: + switchLocalCommunicationIds_6: + switchLocalCommunicationIds_7: + switchParentalControl: 0 + switchAllowsScreenshot: 1 + switchAllowsVideoCapturing: 1 + switchAllowsRuntimeAddOnContentInstall: 0 + switchDataLossConfirmation: 0 + switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 + switchSupportedNpadStyles: 6 + switchNativeFsCacheSize: 32 + switchIsHoldTypeHorizontal: 0 + switchSupportedNpadCount: 8 + switchSocketConfigEnabled: 0 + switchTcpInitialSendBufferSize: 32 + switchTcpInitialReceiveBufferSize: 64 + switchTcpAutoSendBufferSizeMax: 256 + switchTcpAutoReceiveBufferSizeMax: 256 + switchUdpSendBufferSize: 9 + switchUdpReceiveBufferSize: 42 + switchSocketBufferEfficiency: 4 + switchSocketInitializeEnabled: 1 + switchNetworkInterfaceManagerInitializeEnabled: 1 + switchPlayerConnectionEnabled: 1 + ps4NPAgeRating: 12 + ps4NPTitleSecret: + ps4NPTrophyPackPath: + ps4ParentalLevel: 11 + ps4ContentID: ED1633-NPXX51362_00-0000000000000000 + ps4Category: 0 + ps4MasterVersion: 01.00 + ps4AppVersion: 01.00 + ps4AppType: 0 + ps4ParamSfxPath: + ps4VideoOutPixelFormat: 0 + ps4VideoOutInitialWidth: 1920 + ps4VideoOutBaseModeInitialWidth: 1920 + ps4VideoOutReprojectionRate: 60 + ps4PronunciationXMLPath: + ps4PronunciationSIGPath: + ps4BackgroundImagePath: + ps4StartupImagePath: + ps4StartupImagesFolder: + ps4IconImagesFolder: + ps4SaveDataImagePath: + ps4SdkOverride: + ps4BGMPath: + ps4ShareFilePath: + ps4ShareOverlayImagePath: + ps4PrivacyGuardImagePath: + ps4NPtitleDatPath: + ps4RemotePlayKeyAssignment: -1 + ps4RemotePlayKeyMappingDir: + ps4PlayTogetherPlayerCount: 0 + ps4EnterButtonAssignment: 2 + ps4ApplicationParam1: 0 + ps4ApplicationParam2: 0 + ps4ApplicationParam3: 0 + ps4ApplicationParam4: 0 + ps4DownloadDataSize: 0 + ps4GarlicHeapSize: 2048 + ps4ProGarlicHeapSize: 2560 + playerPrefsMaxSize: 32768 + ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ + ps4pnSessions: 1 + ps4pnPresence: 1 + ps4pnFriends: 1 + ps4pnGameCustomData: 1 + playerPrefsSupport: 0 + enableApplicationExit: 0 + resetTempFolder: 1 + restrictedAudioUsageRights: 0 + ps4UseResolutionFallback: 0 + ps4ReprojectionSupport: 0 + ps4UseAudio3dBackend: 0 + ps4SocialScreenEnabled: 0 + ps4ScriptOptimizationLevel: 2 + ps4Audio3dVirtualSpeakerCount: 14 + ps4attribCpuUsage: 0 + ps4PatchPkgPath: + ps4PatchLatestPkgPath: + ps4PatchChangeinfoPath: + ps4PatchDayOne: 0 + ps4attribUserManagement: 0 + ps4attribMoveSupport: 0 + ps4attrib3DSupport: 0 + ps4attribShareSupport: 0 + ps4attribExclusiveVR: 0 + ps4disableAutoHideSplash: 0 + ps4videoRecordingFeaturesUsed: 0 + ps4contentSearchFeaturesUsed: 0 + ps4attribEyeToEyeDistanceSettingVR: 0 + ps4IncludedModules: [] + monoEnv: + splashScreenBackgroundSourceLandscape: {fileID: 0} + splashScreenBackgroundSourcePortrait: {fileID: 0} + blurSplashScreenBackground: 1 + spritePackerPolicy: + webGLMemorySize: 32 + webGLExceptionSupport: 1 + webGLNameFilesAsHashes: 0 + webGLDataCaching: 1 + webGLDebugSymbols: 0 + webGLEmscriptenArgs: + webGLModulesDirectory: + webGLTemplate: APPLICATION:Default + webGLAnalyzeBuildSize: 0 + webGLUseEmbeddedResources: 0 + webGLCompressionFormat: 0 + webGLLinkerTarget: 1 + webGLThreadsSupport: 0 + webGLWasmStreaming: 0 + scriptingDefineSymbols: {} + platformArchitecture: {} + scriptingBackend: + Standalone: 1 + il2cppCompilerConfiguration: {} + managedStrippingLevel: {} + incrementalIl2cppBuild: {} + allowUnsafeCode: 0 + additionalIl2CppArgs: + scriptingRuntimeVersion: 1 + gcIncremental: 0 + gcWBarrierValidation: 0 + apiCompatibilityLevelPerPlatform: {} + m_RenderingPath: 1 + m_MobileRenderingPath: 1 + metroPackageName: test-standalone-scripts + metroPackageVersion: + metroCertificatePath: + metroCertificatePassword: + metroCertificateSubject: + metroCertificateIssuer: + metroCertificateNotAfter: 0000000000000000 + metroApplicationDescription: test-standalone-scripts + wsaImages: {} + metroTileShortName: + metroTileShowName: 0 + metroMediumTileShowName: 0 + metroLargeTileShowName: 0 + metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 + metroDefaultTileSize: 1 + metroTileForegroundText: 2 + metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} + metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, + a: 1} + metroSplashScreenUseBackgroundColor: 0 + platformCapabilities: {} + metroTargetDeviceFamilies: {} + metroFTAName: + metroFTAFileTypes: [] + metroProtocolName: + XboxOneProductId: + XboxOneUpdateKey: + XboxOneSandboxId: + XboxOneContentId: + XboxOneTitleId: + XboxOneSCId: + XboxOneGameOsOverridePath: + XboxOnePackagingOverridePath: + XboxOneAppManifestOverridePath: + XboxOneVersion: 1.0.0.0 + XboxOnePackageEncryption: 0 + XboxOnePackageUpdateGranularity: 2 + XboxOneDescription: + XboxOneLanguage: + - enus + XboxOneCapability: [] + XboxOneGameRating: {} + XboxOneIsContentPackage: 0 + XboxOneEnableGPUVariability: 1 + XboxOneSockets: {} + XboxOneSplashScreen: {fileID: 0} + XboxOneAllowedProductIds: [] + XboxOnePersistentLocalStorageSize: 0 + XboxOneXTitleMemory: 8 + xboxOneScriptCompiler: 1 + XboxOneOverrideIdentityName: + vrEditorSettings: + daydream: + daydreamIconForeground: {fileID: 0} + daydreamIconBackground: {fileID: 0} + cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_SignPackage: 1 + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: + facebookSdkVersion: + facebookAppId: + facebookCookies: 1 + facebookLogging: 1 + facebookStatus: 1 + facebookXfbml: 0 + facebookFrictionlessRequests: 1 + apiCompatibilityLevel: 6 + cloudProjectId: + framebufferDepthMemorylessMode: 0 + projectName: + organizationId: + cloudEnabled: 0 + enableNativePlatformBackendsForNewInputSystem: 0 + disableOldInputManagerSupport: 0 + legacyClampBlendShapeWeights: 0 diff --git a/dist/test-standalone-scripts/ProjectSettings/ProjectVersion.txt b/dist/test-standalone-scripts/ProjectSettings/ProjectVersion.txt new file mode 100644 index 00000000..87079c13 --- /dev/null +++ b/dist/test-standalone-scripts/ProjectSettings/ProjectVersion.txt @@ -0,0 +1,2 @@ +m_EditorVersion: 2019.2.11f1 +m_EditorVersionWithRevision: 2019.2.11f1 (5f859a4cfee5) diff --git a/dist/test-standalone-scripts/ProjectSettings/QualitySettings.asset b/dist/test-standalone-scripts/ProjectSettings/QualitySettings.asset new file mode 100644 index 00000000..1398719b Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/QualitySettings.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/TagManager.asset b/dist/test-standalone-scripts/ProjectSettings/TagManager.asset new file mode 100644 index 00000000..fb3e9150 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/TagManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/TimeManager.asset b/dist/test-standalone-scripts/ProjectSettings/TimeManager.asset new file mode 100644 index 00000000..8ca1c3bf Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/TimeManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/UnityConnectSettings.asset b/dist/test-standalone-scripts/ProjectSettings/UnityConnectSettings.asset new file mode 100644 index 00000000..4edbd474 Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/UnityConnectSettings.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/VFXManager.asset b/dist/test-standalone-scripts/ProjectSettings/VFXManager.asset new file mode 100644 index 00000000..4e92658d Binary files /dev/null and b/dist/test-standalone-scripts/ProjectSettings/VFXManager.asset differ diff --git a/dist/test-standalone-scripts/ProjectSettings/XRSettings.asset b/dist/test-standalone-scripts/ProjectSettings/XRSettings.asset new file mode 100644 index 00000000..482590c1 --- /dev/null +++ b/dist/test-standalone-scripts/ProjectSettings/XRSettings.asset @@ -0,0 +1,10 @@ +{ + "m_SettingKeys": [ + "VR Device Disabled", + "VR Device User Alert" + ], + "m_SettingValues": [ + "False", + "False" + ] +} \ No newline at end of file diff --git a/src/command-options/docker-test-options.ts b/src/command-options/docker-test-options.ts new file mode 100644 index 00000000..a218d2f8 --- /dev/null +++ b/src/command-options/docker-test-options.ts @@ -0,0 +1,198 @@ +import type { YargsInstance } from '../dependencies.ts'; +import { IOptions } from './options-interface.ts'; +import * as os from 'node:os'; + +function defaultDockerMemoryLimit(): string { + const bytesInMegabyte = 1024 * 1024; + + let memoryMultiplier: number; + switch (os.platform()) { + case 'linux': + memoryMultiplier = 0.95; + break; + case 'win32': + memoryMultiplier = 0.8; + break; + default: + memoryMultiplier = 0.75; + break; + } + + return `${Math.floor((os.totalmem() / bytesInMegabyte) * memoryMultiplier)}m`; +} + +/** + * Options for `game-ci test`'s classic Docker/Hub-image-driven test flow - + * the same batchmode `-runTests` invocation unity-test-runner's action uses, + * as an alternative to the `unityCliArgs` passthrough to Unity's own + * experimental `unity test` CLI (see UnityTestOptions). Duplicates a handful + * of docker/runtime flags from BuildOptions rather than sharing that module + * directly - BuildOptions.configure() demands `targetPlatform`, which tests + * don't need. + */ +export class DockerTestOptions implements IOptions { + public static configure(yargs: YargsInstance): void { + yargs + .option('docker', { + description: String.dedent` + Run the classic Docker/Hub-image-driven Unity batchmode test flow + instead of Unity's own experimental \`unity test\` CLI. Requires a + Unity Editor Docker image (see customImage/containerRegistry*).`, + type: 'boolean', + demandOption: false, + default: false, + }) + .option('local', { + description: String.dedent` + Only meaningful with --docker. Run the same batchmode test flow + directly on this machine instead of inside a container - for + self-hosted runners with Unity already installed and licensed. + Mirrors the orchestrator's own local (host) provider.`, + type: 'boolean', + demandOption: false, + default: false, + }) + .option('testPlatforms', { + description: String.dedent` + Semicolon-separated test platforms to run, e.g. "playmode;editmode". + Use "COMBINE_RESULTS" as an extra entry to merge prior platforms' + results into one summary, and "standalone" to build and run tests + in a standalone player instead of in-editor.`, + type: 'string', + demandOption: false, + default: 'playmode;editmode', + }) + .option('artifactsPath', { + description: 'Path (relative to the project) to write test result XML and logs to.', + type: 'string', + demandOption: false, + default: 'artifacts', + }) + .option('coverageEnabled', { + description: String.dedent` + Enable Unity's code coverage instrumentation during the test run. + Some Unity versions/configurations fail or crash with this on - + see game-ci/unity-test-runner#302, #306, #301. Set to false to + omit -enableCodeCoverage/-coverageOptions/-coverageResultsPath + entirely.`, + type: 'boolean', + demandOption: false, + default: true, + }) + .option('coverageResultsPath', { + description: 'Path (relative to the project) to write code coverage results to.', + type: 'string', + demandOption: false, + default: 'CodeCoverage', + }) + .option('coverageOptions', { + description: "Additional arguments to pass to Unity's code coverage package.", + type: 'string', + demandOption: false, + default: '', + }) + .option('packageMode', { + description: String.dedent` + Test a Unity package (projectPath points at the package folder, + not a full project) by creating a throwaway project and adding + the package as a local file dependency.`, + type: 'boolean', + demandOption: false, + default: false, + }) + .option('packageName', { + description: 'Package name to add to the throwaway project. Required when packageMode is set.', + type: 'string', + demandOption: false, + default: '', + }) + .option('scopedRegistryUrl', { + description: 'Scoped registry URL to add to the throwaway project when in packageMode.', + type: 'string', + demandOption: false, + default: '', + }) + .option('registryScopes', { + description: 'Comma-separated scopes for scopedRegistryUrl.', + type: 'string', + demandOption: false, + default: '', + }) + .option('privateRegistryToken', { + description: 'Auth token for a private UPM registry, written to .upmconfig.toml before testing.', + type: 'string', + demandOption: false, + default: '', + }) + .option('privateRegistryUser', { + description: 'Username for a private UPM registry that requires one even with token auth (e.g. Azure DevOps).', + type: 'string', + demandOption: false, + default: '', + }) + .option('runAsHostUser', { + description: String.dedent`Linux only. Run as a user matching the host system's UID/GID instead of the + container's default root user, so artifacts aren't left root-owned on the host.`, + type: 'boolean', + demandOption: false, + default: false, + }) + .option('useHostNetwork', { + description: 'Linux only. Initialises Docker using the host network.', + type: 'boolean', + demandOption: false, + default: false, + }) + .option('dockerCpuLimit', { + description: 'Number of CPU cores to assign the docker container. Defaults to all available cores.', + type: 'string', + demandOption: false, + default: os.cpus().length.toString(), + }) + .option('dockerMemoryLimit', { + description: String.dedent`Amount of memory to assign the docker container. Defaults to 95% of total system + memory rounded down to the nearest megabyte on Linux and 80% on Windows.`, + type: 'string', + demandOption: false, + default: defaultDockerMemoryLimit(), + }) + .option('dockerShmSize', { + description: String.dedent`Size of /dev/shm to assign the docker container, using the format + (m or g). Some Unity versions/configurations fail with "Insufficient shared memory available" against + Docker's 64m default - see game-ci/unity-test-runner#307/#308.`, + type: 'string', + demandOption: false, + default: '', + }) + .option('dockerIsolationMode', { + description: 'Windows only. Isolation mode for the docker container: process, hyperv, or default.', + type: 'string', + demandOption: false, + default: 'default', + }) + .option('containerRegistryRepository', { + description: 'Container registry and repository to pull the Unity editor image from. Only applies if customImage is not set.', + type: 'string', + demandOption: false, + default: 'unityci/editor', + }) + .option('containerRegistryImageVersion', { + description: 'Container registry image rolling version. Only applies if customImage is not set.', + type: 'string', + demandOption: false, + default: '3', + }) + .option('sshPublicKeysDirectoryPath', { + description: 'Path to a directory containing SSH public keys to forward to the container.', + type: 'string', + demandOption: false, + default: '', + }) + .option('dockerWorkspacePath', { + description: 'The path to mount the workspace inside the docker container.', + type: 'string', + demandOption: false, + default: '/github/workspace', + }); + } +} diff --git a/src/command/test/unity-test-command.test.ts b/src/command/test/unity-test-command.test.ts index 2b7dfed8..5833e696 100644 --- a/src/command/test/unity-test-command.test.ts +++ b/src/command/test/unity-test-command.test.ts @@ -1,15 +1,24 @@ import { describe, it, expect, mock, afterEach } from 'bun:test'; import { UnityTestCommand } from './unity-test-command.ts'; import { UnityCliAdapter } from '../../model/unity-cli-adapter.ts'; +import { Docker } from '../../model/index.ts'; +import { HostRunner } from '../../model/host-runner.ts'; +import { PlatformSetup } from '../../logic/unity/platform-setup/index.ts'; const originalIsAvailable = UnityCliAdapter.isAvailable; const originalTest = UnityCliAdapter.test; +const originalDockerRun = Docker.run; +const originalHostRunnerRun = HostRunner.run; +const originalPlatformSetup = PlatformSetup.setup; afterEach(() => { - // Both are shared statics — restore them so other test files that - // exercise the real UnityCliAdapter aren't affected by this file's mocks. + // All statics — restore them so other test files that exercise the real + // implementations aren't affected by this file's mocks. UnityCliAdapter.isAvailable = originalIsAvailable; UnityCliAdapter.test = originalTest; + Docker.run = originalDockerRun; + HostRunner.run = originalHostRunnerRun; + PlatformSetup.setup = originalPlatformSetup; }); describe('UnityTestCommand', () => { @@ -52,4 +61,52 @@ describe('UnityTestCommand', () => { await expect(command.execute({} as any)).rejects.toThrow(/test:.*failed.*unity wrote to stderr/); }); + + it('--docker runs the classic batchmode flow via Docker.run, not the unity CLI', async () => { + const isAvailableMock = mock(() => Promise.resolve(true)); + UnityCliAdapter.isAvailable = isAvailableMock; + PlatformSetup.setup = mock(() => Promise.resolve()); + const dockerRunMock = mock(() => Promise.resolve()); + Docker.run = dockerRunMock; + + const command = new UnityTestCommand('test'); + const result = await command.execute({ + docker: true, + hostPlatform: 'linux', + engineVersion: '2022.3.20f1', + } as any); + + expect(result).toBe(true); + expect(isAvailableMock).not.toHaveBeenCalled(); + expect(dockerRunMock).toHaveBeenCalledTimes(1); + const [image, options] = dockerRunMock.mock.calls[0] as unknown as [string, any]; + expect(image).toContain('base'); // NoTarget resolves to the 'base' editor image + expect(options.runTests).toBe(true); + }); + + it('--docker --local runs directly on the host via HostRunner.run, not Docker', async () => { + const dockerRunMock = mock(() => Promise.resolve()); + Docker.run = dockerRunMock; + const hostRunnerMock = mock(() => Promise.resolve()); + HostRunner.run = hostRunnerMock; + + const command = new UnityTestCommand('test'); + const result = await command.execute({ docker: true, local: true, hostPlatform: 'linux' } as any); + + expect(result).toBe(true); + expect(dockerRunMock).not.toHaveBeenCalled(); + expect(hostRunnerMock).toHaveBeenCalledTimes(1); + const [options] = hostRunnerMock.mock.calls[0] as unknown as [any]; + expect(options.runTests).toBe(true); + }); + + it('--docker on macOS throws instead of trying to pull a nonexistent macOS editor image', async () => { + PlatformSetup.setup = mock(() => Promise.resolve()); + + const command = new UnityTestCommand('test'); + + await expect( + command.execute({ docker: true, hostPlatform: 'darwin', engineVersion: '2022.3.20f1' } as any), + ).rejects.toThrow(/macOS/i); + }); }); diff --git a/src/command/test/unity-test-command.ts b/src/command/test/unity-test-command.ts index 43dd2511..bdc20563 100644 --- a/src/command/test/unity-test-command.ts +++ b/src/command/test/unity-test-command.ts @@ -2,22 +2,41 @@ import { CommandInterface } from '../command-interface.ts'; import { CommandBase } from '../command-base.ts'; import { UnityCliAdapter } from '../../model/unity-cli-adapter.ts'; import { UnityTestOptions } from '../../command-options/unity-test-options.ts'; +import { DockerTestOptions } from '../../command-options/docker-test-options.ts'; +import { ProjectOptions } from '../../command-options/project-options.ts'; +import { UnityOptions } from '../../command-options/unity-options.ts'; +import { Docker, RunnerImageTag } from '../../model/index.ts'; +import { HostRunner } from '../../model/host-runner.ts'; +import { PlatformSetup } from '../../logic/unity/platform-setup/index.ts'; import type { YargsInstance, Options } from '../../dependencies.ts'; /** - * `game-ci test` — runs Unity's own official test runner via Unity CLI's - * `test` command (docs.unity.com/en-us/unity-cli, still experimental - * upstream), as an alternative to unity-test-runner's Docker/Hub-driven - * flow. See game-ci/roadmap#11 workstream 3 and game-ci/cli#58. + * `game-ci test` — two independent ways to run Unity tests, chosen via + * --docker: * - * Requires the `unity` CLI binary on PATH. Unity's own docs don't publish - * a flag table for `test` (unlike `install`/`install-modules`) — they - * explicitly point to `unity test --help` on the installed binary as the - * authoritative reference. So this command doesn't invent or guess at flags: - * everything after --unityCliArgs is passed through to `unity test` verbatim. + * - Default (--docker not set): wraps Unity's own official, still + * experimental `test` CLI command (docs.unity.com/en-us/unity-cli). + * Requires the `unity` binary on PATH. See game-ci/roadmap#11 + * workstream 3 and game-ci/cli#58. + * + * - --docker: the classic Docker/Hub-image-driven batchmode test flow + * (-runTests) unity-test-runner's action uses today - editmode/playmode/ + * standalone/package-mode testing, coverage, artifact collection. Add + * --local to run the same flow directly on this machine instead of in a + * container, for self-hosted runners with Unity already installed (see + * HostRunner) - mirrors orchestrator's own local (host) provider vs its + * docker provider. */ export class UnityTestCommand extends CommandBase implements CommandInterface { public async execute(options: Options): Promise { + if (options.docker) { + return this.executeDocker(options); + } + + return this.executeUnityCli(options); + } + + private async executeUnityCli(options: Options): Promise { const extraArgs = String(options.unityCliArgs || '') .split(' ') .map((arg) => arg.trim()) @@ -27,7 +46,8 @@ export class UnityTestCommand extends CommandBase implements CommandInterface { if (!available) { throw new Error( "test: requires Unity's official `unity` CLI binary on PATH " + - '(https://docs.unity.com/en-us/unity-cli). Not found in this environment.', + '(https://docs.unity.com/en-us/unity-cli). Not found in this environment, or pass --docker ' + + 'to run the classic Docker/Hub-image-driven test flow instead.', ); } @@ -44,7 +64,62 @@ export class UnityTestCommand extends CommandBase implements CommandInterface { } } + private async executeDocker(options: Options): Promise { + const { hostPlatform, local } = options; + + if (local) { + await HostRunner.run({ ...options, runTests: true }); + return true; + } + + // Docker test mode is currently only wired up for Linux containers - + // dist/platforms/ubuntu/steps/test.sh + runsteps.sh's RUN_TESTS branch. + // Windows' entrypoint.ps1 doesn't know about RUN_TESTS yet (it always + // runs build.ps1), so running this on a Windows host today would + // silently attempt a BUILD instead of a test rather than failing + // loudly - reject it explicitly instead. macOS has no Unity Editor + // Docker images at all. Checked before PlatformSetup.setup runs, so + // this fails fast instead of after prompting for credentials. + if (hostPlatform !== 'linux') { + throw new Error( + `--docker's classic batchmode test flow is currently only supported on Linux hosts/containers ` + + `(got hostPlatform=${hostPlatform}). ${ + hostPlatform === 'darwin' + ? 'No Unity Editor Docker images exist for macOS - omit --docker to use the native `unity test` CLI instead.' + : 'Windows Docker test support is tracked separately (the container-side scripts only handle builds so far).' + }`, + ); + } + + // No target platform is being built - resolves to the 'base' editor + // image, same one activate/NoTarget already uses (see RunnerImageTag's + // targetPlatformSuffixes.noTarget). UnityOptions.configure() defaults + // targetPlatform to StandaloneWindows64 (build's default, not test's) - + // overridden back to NoTarget in configureOptions() below, so this + // should already be NoTarget unless a caller explicitly passed one. + const testOptions = { ...options, targetPlatform: options.targetPlatform || 'NoTarget' }; + + const image = new RunnerImageTag(testOptions); + if (log.isVerbose) log.debug('Using image:', image); + + await PlatformSetup.setup(testOptions); + + await log.group('Unity test', async () => { + await Docker.run(image.toString(), { ...testOptions, runTests: true }); + }); + + return true; + } + public async configureOptions(yargs: YargsInstance): Promise { + await ProjectOptions.configure(yargs); + await UnityOptions.configure(yargs); + // UnityOptions defaults targetPlatform to StandaloneWindows64 (a build + // concern) - tests don't build anything, so default to NoTarget's + // 'base' editor image instead. A caller can still pass --targetPlatform + // explicitly (e.g. for --testPlatforms=standalone scenarios). + yargs.default('targetPlatform', 'NoTarget'); await UnityTestOptions.configure(yargs); + await DockerTestOptions.configure(yargs); } } diff --git a/src/logic/unity/environment.ts b/src/logic/unity/environment.ts index 930eae80..a2bc5a0c 100644 --- a/src/logic/unity/environment.ts +++ b/src/logic/unity/environment.ts @@ -35,6 +35,22 @@ const UnityEnvironment = { { name: 'RUN_AS_HOST_USER', value: options.runAsHostUser ? 'true' : '' }, { name: 'ENABLE_GPU', value: options.enableGpu ? 'true' : '' }, { name: 'GIT_CONFIG_EXTENSIONS', value: options.gitConfigExtensions }, + // Consumed by dist/platforms/*/steps/runsteps.sh + test.sh (`game-ci + // test --docker`, see UnityTestCommand) - a no-op for build/activate + // since these are all empty/undefined there and getEnvVarString drops + // empty values. + { name: 'RUN_TESTS', value: options.runTests ? 'true' : '' }, + { name: 'TEST_PLATFORMS', value: options.testPlatforms }, + { name: 'ARTIFACTS_PATH', value: options.artifactsPath }, + { name: 'COVERAGE_RESULTS_PATH', value: options.coverageResultsPath }, + { name: 'COVERAGE_OPTIONS', value: options.coverageOptions }, + { name: 'COVERAGE_ENABLED', value: options.coverageEnabled === false ? 'false' : 'true' }, + { name: 'PACKAGE_MODE', value: options.packageMode ? 'true' : '' }, + { name: 'PACKAGE_NAME', value: options.packageName }, + { name: 'SCOPED_REGISTRY_URL', value: options.scopedRegistryUrl }, + { name: 'REGISTRY_SCOPES', value: options.registryScopes }, + { name: 'PRIVATE_REGISTRY_TOKEN', value: options.privateRegistryToken }, + { name: 'PRIVATE_REGISTRY_USER', value: options.privateRegistryUser }, ] as DockerParameter[]; }, }; diff --git a/src/model/host-runner.ts b/src/model/host-runner.ts new file mode 100644 index 00000000..535802a1 --- /dev/null +++ b/src/model/host-runner.ts @@ -0,0 +1,78 @@ +import type { Options } from '../dependencies.ts'; +import { System } from './system/system.ts'; +import { ImageEnvironmentFactory } from './image-environment-factory.ts'; +import { UnityEnvironment } from '../logic/unity/environment.ts'; +import { path, fsSync as fs } from '../dependencies.ts'; + +/** + * Runs the same activate/test/build step scripts Docker.run mounts into a + * container, directly on this machine instead - for self-hosted runners + * with Unity already installed. Mirrors MacBuilder (macOS never had a + * Docker path to begin with), generalized to Linux/Windows so `--local` + * (see DockerTestOptions) works cross-platform. Mirrors orchestrator's own + * `local` provider, which runs the equivalent build/test commands directly + * on the host rather than dispatching to a container/cloud provider. + * + * Deliberately does NOT invoke entrypoint.sh: that script does + * container-only setup (randomizing /etc/machine-id, creating a matching + * host user via useradd/groupadd) that would mutate a real, persistent + * self-hosted machine rather than a throwaway container - genuinely + * dangerous outside Docker. Goes straight to runsteps.sh (activate -> test + * or build -> return license), the same core steps.sh scripts already use. + */ +class HostRunner { + private static buildEnv(options: Options): Record { + const { currentWorkDir, cliDistPath, hostPlatform } = options; + const extraVariables = options.engine === 'unity' ? UnityEnvironment.getVariables(options) : []; + const variables = ImageEnvironmentFactory.getEnvironmentVariables(options, extraVariables); + const env: Record = {}; + for (const { name, value } of variables) { + if (value === '' || value === undefined) continue; + env[name] = value.toString(); + } + if (currentWorkDir) env.GITHUB_WORKSPACE = currentWorkDir; + + const stepsDir = HostRunner.stepsDir(hostPlatform, cliDistPath); + env.STEPS_DIR = stepsDir; + env.TEST_RUNNER_ACTION_DIR = path.join(cliDistPath, 'test-standalone-scripts'); + + // entrypoint.sh normally creates this before sourcing runsteps.sh - + // replicated here since HostRunner bypasses entrypoint.sh entirely. + const activateLicensePath = path.join(currentWorkDir || process.cwd(), '_activate-license~'); + if (!fs.existsSync(activateLicensePath)) fs.mkdirSync(activateLicensePath, { recursive: true }); + env.ACTIVATE_LICENSE_PATH = activateLicensePath; + + return env; + } + + private static stepsDir(hostPlatform: string, cliDistPath: string): string { + switch (hostPlatform) { + case 'win32': + return path.join(cliDistPath, 'platforms', 'windows'); + case 'linux': + default: + return path.join(cliDistPath, 'platforms', 'ubuntu', 'steps'); + } + } + + public static async run(options: Options, silent = false) { + const { cliDistPath, hostPlatform } = options; + + if (hostPlatform !== 'linux') { + throw new Error( + `--local is currently only supported when running on Linux (got hostPlatform=${hostPlatform}). ` + + 'macOS already runs natively via MacBuilder without needing --local; Windows host-mode support is tracked separately.', + ); + } + + log.warning('running the process directly on this host (no Docker)'); + + const runstepsPath = path.join(cliDistPath, 'platforms', 'ubuntu', 'steps', 'runsteps.sh'); + await System.run(`bash "${runstepsPath}"`, undefined, { + silent, + env: HostRunner.buildEnv(options), + }); + } +} + +export { HostRunner };