diff --git a/.woodpecker.yml b/.woodpecker.yml new file mode 100644 index 0000000..ca23d65 --- /dev/null +++ b/.woodpecker.yml @@ -0,0 +1,74 @@ +workspace: + base: /studio + path: idle-survivors + +# We use a singleton "matrix" to set variables, because formal variables are a +# Woodpecker next feature. +matrix: + include: + - BUILD_NAME: idle-survivors + - UNITY_ACTIVATION_FILE: ./unity3d.alf + - IMAGE: unityci/editor + - IMAGE_VERSION: 1 + # The GitLab version pulls the Unity version from the project information, + # but I don't think Woodpecker will use that as a variable in image names. + # We set the version as a variable instead and check that it's right. + - UNITY_VERSION: 2022.3.4f1 + - UNITY_DIR: /studio/idle-survivors + - VERSION_NUMBER_VAR: ${CI_COMMIT_BRANCH}-${CI_BUILD_NUMBER} + - VERSION_BUILD_VAR: ${CI_COMMIT_HASH} + +pipeline: + check-unity-version: + image: ${IMAGE}:${UNITY_VERSION}-base-${IMAGE_VERSION} + commands: + - test "$UNITY_VERSION" = $(grep "m_EditorVersion:" "${UNITY_DIR}/ProjectSettings/ProjectVersion.txt" | cut -d' ' -f2) + setup-license: + image: ${IMAGE}:${UNITY_VERSION}-base-${IMAGE_VERSION} + commands: + - chmod +x ${UNITY_DIR}/ci/before_script.sh + - ${UNITY_DIR}/ci/before_script.sh + secrets: [unity_license] + test-playmode: + image: ${IMAGE}:${UNITY_VERSION}-base-${IMAGE_VERSION} + group: test + commands: + - chmod +x ${UNITY_DIR}/ci/test.sh + - ${UNITY_DIR}/ci/test.sh + environment: + - TEST_PLATFORM=playmode + - TESTING_TYPE=NUNIT + test-editmode: + image: ${IMAGE}:${UNITY_VERSION}-base-${IMAGE_VERSION} + group: test + commands: + - chmod +x ${UNITY_DIR}/ci/test.sh + - ${UNITY_DIR}/ci/test.sh + environment: + - TEST_PLATFORM=editmode + - TESTING_TYPE=NUNIT + build-standalone-windows: + image: ${IMAGE}:${UNITY_VERSION}-windows-mono-${IMAGE_VERSION} + group: build + commands: + - chmod +x ./ci/build.sh + - ./ci/build.sh + environment: + - BUILD_TARGET=StandaloneWindows64 + package: + image: ${IMAGE}:${UNITY_VERSION}-windows-mono-${IMAGE_VERSION} + commands: + - tar -C ${UNITY_DIR}/Builds -cvzf ${VERSION_NUMBER_VAR}.tar.gz . + when: + event: tag + publish: + image: woodpeckerci/plugin-gitea-release + settings: + api-key: + from_secret: gitea_api_key + files: + - ${VERSION_NUMBER_VAR}.tar.gz + target: main + base_url: https://git.sunturtle.xyz + when: + event: tag diff --git a/Assets/Scripts/Editor/BuildCommand.cs b/Assets/Scripts/Editor/BuildCommand.cs new file mode 100644 index 0000000..e693598 --- /dev/null +++ b/Assets/Scripts/Editor/BuildCommand.cs @@ -0,0 +1,288 @@ +using UnityEditor; +using System.Linq; +using System; +using System.IO; + +static class BuildCommand +{ + private const string KEYSTORE_PASS = "KEYSTORE_PASS"; + private const string KEY_ALIAS_PASS = "KEY_ALIAS_PASS"; + private const string KEY_ALIAS_NAME = "KEY_ALIAS_NAME"; + private const string KEYSTORE = "keystore.keystore"; + private const string BUILD_OPTIONS_ENV_VAR = "BuildOptions"; + private const string ANDROID_BUNDLE_VERSION_CODE = "VERSION_BUILD_VAR"; + private const string ANDROID_APP_BUNDLE = "BUILD_APP_BUNDLE"; + private const string SCRIPTING_BACKEND_ENV_VAR = "SCRIPTING_BACKEND"; + private const string VERSION_NUMBER_VAR = "VERSION_NUMBER_VAR"; + private const string VERSION_iOS = "VERSION_BUILD_VAR"; + + static string GetArgument(string name) + { + string[] args = Environment.GetCommandLineArgs(); + for (int i = 0; i < args.Length; i++) + { + if (args[i].Contains(name)) + { + return args[i + 1]; + } + } + return null; + } + + static string[] GetEnabledScenes() + { + return ( + from scene in EditorBuildSettings.scenes + where scene.enabled + where !string.IsNullOrEmpty(scene.path) + select scene.path + ).ToArray(); + } + + static BuildTarget GetBuildTarget() + { + string buildTargetName = GetArgument("customBuildTarget"); + Console.WriteLine(":: Received customBuildTarget " + buildTargetName); + + if (buildTargetName.ToLower() == "android") + { +#if !UNITY_5_6_OR_NEWER + // https://issuetracker.unity3d.com/issues/buildoptions-dot-acceptexternalmodificationstoplayer-causes-unityexception-unknown-project-type-0 + // Fixed in Unity 5.6.0 + // side effect to fix android build system: + EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Internal; +#endif + } + + if (buildTargetName.TryConvertToEnum(out BuildTarget target)) + return target; + + Console.WriteLine($":: {nameof(buildTargetName)} \"{buildTargetName}\" not defined on enum {nameof(BuildTarget)}, using {nameof(BuildTarget.NoTarget)} enum to build"); + + return BuildTarget.NoTarget; + } + + static string GetBuildPath() + { + string buildPath = GetArgument("customBuildPath"); + Console.WriteLine(":: Received customBuildPath " + buildPath); + if (buildPath == "") + { + throw new Exception("customBuildPath argument is missing"); + } + return buildPath; + } + + static string GetBuildName() + { + string buildName = GetArgument("customBuildName"); + Console.WriteLine(":: Received customBuildName " + buildName); + if (buildName == "") + { + throw new Exception("customBuildName argument is missing"); + } + return buildName; + } + + static string GetFixedBuildPath(BuildTarget buildTarget, string buildPath, string buildName) + { + if (buildTarget.ToString().ToLower().Contains("windows")) { + buildName += ".exe"; + } else if (buildTarget == BuildTarget.Android) { +#if UNITY_2018_3_OR_NEWER + buildName += EditorUserBuildSettings.buildAppBundle ? ".aab" : ".apk"; +#else + buildName += ".apk"; +#endif + } + return buildPath + buildName; + } + + static BuildOptions GetBuildOptions() + { + if (TryGetEnv(BUILD_OPTIONS_ENV_VAR, out string envVar)) { + string[] allOptionVars = envVar.Split(','); + BuildOptions allOptions = BuildOptions.None; + BuildOptions option; + string optionVar; + int length = allOptionVars.Length; + + Console.WriteLine($":: Detecting {BUILD_OPTIONS_ENV_VAR} env var with {length} elements ({envVar})"); + + for (int i = 0; i < length; i++) { + optionVar = allOptionVars[i]; + + if (optionVar.TryConvertToEnum(out option)) { + allOptions |= option; + } + else { + Console.WriteLine($":: Cannot convert {optionVar} to {nameof(BuildOptions)} enum, skipping it."); + } + } + + return allOptions; + } + + return BuildOptions.None; + } + + // https://stackoverflow.com/questions/1082532/how-to-tryparse-for-enum-value + static bool TryConvertToEnum(this string strEnumValue, out TEnum value) + { + if (!Enum.IsDefined(typeof(TEnum), strEnumValue)) + { + value = default; + return false; + } + + value = (TEnum)Enum.Parse(typeof(TEnum), strEnumValue); + return true; + } + + static bool TryGetEnv(string key, out string value) + { + value = Environment.GetEnvironmentVariable(key); + return !string.IsNullOrEmpty(value); + } + + static void SetScriptingBackendFromEnv(BuildTarget platform) { + var targetGroup = BuildPipeline.GetBuildTargetGroup(platform); + if (TryGetEnv(SCRIPTING_BACKEND_ENV_VAR, out string scriptingBackend)) { + if (scriptingBackend.TryConvertToEnum(out ScriptingImplementation backend)) { + Console.WriteLine($":: Setting ScriptingBackend to {backend}"); + PlayerSettings.SetScriptingBackend(targetGroup, backend); + } else { + string possibleValues = string.Join(", ", Enum.GetValues(typeof(ScriptingImplementation)).Cast()); + throw new Exception($"Could not find '{scriptingBackend}' in ScriptingImplementation enum. Possible values are: {possibleValues}"); + } + } else { + var defaultBackend = PlayerSettings.GetDefaultScriptingBackend(targetGroup); + Console.WriteLine($":: Using project's configured ScriptingBackend (should be {defaultBackend} for targetGroup {targetGroup}"); + } + } + + static void PerformBuild() + { + var buildTarget = GetBuildTarget(); + + Console.WriteLine(":: Performing build"); + if (TryGetEnv(VERSION_NUMBER_VAR, out var bundleVersionNumber)) + { + if (buildTarget == BuildTarget.iOS) + { + bundleVersionNumber = GetIosVersion(); + } + Console.WriteLine($":: Setting bundleVersionNumber to '{bundleVersionNumber}' (Length: {bundleVersionNumber.Length})"); + PlayerSettings.bundleVersion = bundleVersionNumber; + } + + if (buildTarget == BuildTarget.Android) { + HandleAndroidAppBundle(); + HandleAndroidBundleVersionCode(); + HandleAndroidKeystore(); + } + + var buildPath = GetBuildPath(); + var buildName = GetBuildName(); + var buildOptions = GetBuildOptions(); + var fixedBuildPath = GetFixedBuildPath(buildTarget, buildPath, buildName); + + SetScriptingBackendFromEnv(buildTarget); + + var buildReport = BuildPipeline.BuildPlayer(GetEnabledScenes(), fixedBuildPath, buildTarget, buildOptions); + + if (buildReport.summary.result != UnityEditor.Build.Reporting.BuildResult.Succeeded) + throw new Exception($"Build ended with {buildReport.summary.result} status"); + + Console.WriteLine(":: Done with build"); + } + + private static void HandleAndroidAppBundle() + { + if (TryGetEnv(ANDROID_APP_BUNDLE, out string value)) + { +#if UNITY_2018_3_OR_NEWER + if (bool.TryParse(value, out bool buildAppBundle)) + { + EditorUserBuildSettings.buildAppBundle = buildAppBundle; + Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected, set buildAppBundle to {value}."); + } + else + { + Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected but the value \"{value}\" is not a boolean."); + } +#else + Console.WriteLine($":: {ANDROID_APP_BUNDLE} env var detected but does not work with lower Unity version than 2018.3"); +#endif + } + } + + private static void HandleAndroidBundleVersionCode() + { + if (TryGetEnv(ANDROID_BUNDLE_VERSION_CODE, out string value)) + { + if (int.TryParse(value, out int version)) + { + PlayerSettings.Android.bundleVersionCode = version; + Console.WriteLine($":: {ANDROID_BUNDLE_VERSION_CODE} env var detected, set the bundle version code to {value}."); + } + else + Console.WriteLine($":: {ANDROID_BUNDLE_VERSION_CODE} env var detected but the version value \"{value}\" is not an integer."); + } + } + + private static string GetIosVersion() + { + if (TryGetEnv(VERSION_iOS, out string value)) + { + if (int.TryParse(value, out int version)) + { + Console.WriteLine($":: {VERSION_iOS} env var detected, set the version to {value}."); + return version.ToString(); + } + else + Console.WriteLine($":: {VERSION_iOS} env var detected but the version value \"{value}\" is not an integer."); + } + + throw new ArgumentNullException(nameof(value), $":: Error finding {VERSION_iOS} env var"); + } + + private static void HandleAndroidKeystore() + { +#if UNITY_2019_1_OR_NEWER + PlayerSettings.Android.useCustomKeystore = false; +#endif + + if (!File.Exists(KEYSTORE)) { + Console.WriteLine($":: {KEYSTORE} not found, skipping setup, using Unity's default keystore"); + return; + } + + PlayerSettings.Android.keystoreName = KEYSTORE; + + string keystorePass; + string keystoreAliasPass; + + if (TryGetEnv(KEY_ALIAS_NAME, out string keyaliasName)) { + PlayerSettings.Android.keyaliasName = keyaliasName; + Console.WriteLine($":: using ${KEY_ALIAS_NAME} env var on PlayerSettings"); + } else { + Console.WriteLine($":: ${KEY_ALIAS_NAME} env var not set, using Project's PlayerSettings"); + } + + if (!TryGetEnv(KEYSTORE_PASS, out keystorePass)) { + Console.WriteLine($":: ${KEYSTORE_PASS} env var not set, skipping setup, using Unity's default keystore"); + return; + } + + if (!TryGetEnv(KEY_ALIAS_PASS, out keystoreAliasPass)) { + Console.WriteLine($":: ${KEY_ALIAS_PASS} env var not set, skipping setup, using Unity's default keystore"); + return; + } +#if UNITY_2019_1_OR_NEWER + PlayerSettings.Android.useCustomKeystore = true; +#endif + PlayerSettings.Android.keystorePass = keystorePass; + PlayerSettings.Android.keyaliasPass = keystoreAliasPass; + } +} diff --git a/ci/before_script.sh b/ci/before_script.sh new file mode 100644 index 0000000..1e1effa --- /dev/null +++ b/ci/before_script.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -e +set -x +mkdir -p /root/.cache/unity3d +mkdir -p /root/.local/share/unity3d/Unity/ +set +x + +unity_license_destination=/root/.local/share/unity3d/Unity/Unity_lic.ulf +android_keystore_destination=keystore.keystore + + +upper_case_build_target=${BUILD_TARGET^^}; + +if [ "$upper_case_build_target" = "ANDROID" ] +then + if [ -n $ANDROID_KEYSTORE_BASE64 ] + then + echo "'\$ANDROID_KEYSTORE_BASE64' found, decoding content into ${android_keystore_destination}" + echo $ANDROID_KEYSTORE_BASE64 | base64 --decode > ${android_keystore_destination} + else + echo '$ANDROID_KEYSTORE_BASE64'" env var not found, building with Unity's default debug keystore" + fi +fi + +if [ -n "$UNITY_LICENSE" ] +then + echo "Writing '\$UNITY_LICENSE' to license file ${unity_license_destination}" + echo "${UNITY_LICENSE}" | tr -d '\r' > ${unity_license_destination} +else + echo "'\$UNITY_LICENSE' env var not found" +fi diff --git a/ci/build.sh b/ci/build.sh new file mode 100644 index 0000000..d92a8c5 --- /dev/null +++ b/ci/build.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +set -e +set -x + +echo "Building for $BUILD_TARGET" + +export BUILD_PATH=$UNITY_DIR/Builds/$BUILD_TARGET/ +mkdir -p $BUILD_PATH + +${UNITY_EXECUTABLE:-xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' unity-editor} \ + -projectPath $UNITY_DIR \ + -quit \ + -batchmode \ + -nographics \ + -buildTarget $BUILD_TARGET \ + -customBuildTarget $BUILD_TARGET \ + -customBuildName $BUILD_NAME \ + -customBuildPath $BUILD_PATH \ + -executeMethod BuildCommand.PerformBuild \ + -logFile /dev/stdout + +UNITY_EXIT_CODE=$? + +if [ $UNITY_EXIT_CODE -eq 0 ]; then + echo "Run succeeded, no failures occurred"; +elif [ $UNITY_EXIT_CODE -eq 2 ]; then + echo "Run succeeded, some tests failed"; +elif [ $UNITY_EXIT_CODE -eq 3 ]; then + echo "Run failure (other failure)"; +else + echo "Unexpected exit code $UNITY_EXIT_CODE"; +fi + +ls -la $BUILD_PATH +[ -n "$(ls -A $BUILD_PATH)" ] # fail job if build folder is empty diff --git a/ci/docker_build.sh b/ci/docker_build.sh new file mode 100644 index 0000000..3088dfa --- /dev/null +++ b/ci/docker_build.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e + +docker run \ + -e BUILD_NAME \ + -e UNITY_LICENSE \ + -e BUILD_TARGET \ + -e UNITY_USERNAME \ + -e UNITY_PASSWORD \ + -w /project/ \ + -v $UNITY_DIR:/project/ \ + $IMAGE_NAME \ + /bin/bash -c "/project/ci/before_script.sh && /project/ci/build.sh" diff --git a/ci/docker_test.sh b/ci/docker_test.sh new file mode 100644 index 0000000..1b846af --- /dev/null +++ b/ci/docker_test.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +docker run \ + -e UNITY_LICENSE \ + -e TEST_PLATFORM \ + -e UNITY_USERNAME \ + -e UNITY_PASSWORD \ + -w /project/ \ + -v $UNITY_DIR:/project/ \ + $IMAGE_NAME \ + /bin/bash -c "/project/ci/before_script.sh && /project/ci/test.sh" diff --git a/ci/get_activation_file.sh b/ci/get_activation_file.sh new file mode 100644 index 0000000..2ace508 --- /dev/null +++ b/ci/get_activation_file.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +activation_file=${UNITY_ACTIVATION_FILE:-./unity3d.alf} + +if [[ -z "${UNITY_USERNAME}" ]] || [[ -z "${UNITY_PASSWORD}" ]]; then + echo "UNITY_USERNAME or UNITY_PASSWORD environment variables are not set, please refer to instructions in the readme and add these to your secret environment variables." + exit 1 +fi + +xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' \ + unity-editor \ + -logFile /dev/stdout \ + -batchmode \ + -nographics \ + -username "$UNITY_USERNAME" -password "$UNITY_PASSWORD" | + tee ./unity-output.log + +cat ./unity-output.log | + grep 'LICENSE SYSTEM .* Posting *' | + sed 's/.*Posting *//' > "${activation_file}" + +# Fail job if unity.alf is empty +ls "${UNITY_ACTIVATION_FILE:-./unity3d.alf}" +exit_code=$? + +if [[ ${exit_code} -eq 0 ]]; then + echo "" + echo "" + echo "### Congratulations! ###" + echo "${activation_file} was generated successfully!" + echo "" + echo "### Next steps ###" + echo "" + echo "Complete the activation process manually" + echo "" + echo " 1. Download the artifact which should contain ${activation_file}" + echo " 2. Visit https://license.unity3d.com/manual" + echo " 3. Upload ${activation_file} in the form" + echo " 4. Answer questions (unity pro vs personal edition, both will work, just pick the one you use)" + echo " 5. Download 'Unity_v2019.x.ulf' file (year should match your unity version here, 'Unity_v2018.x.ulf' for 2018, etc.)" + echo " 6. Copy the content of 'Unity_v2019.x.ulf' license file to your CI's environment variable 'UNITY_LICENSE'. (Open your project's parameters > CI/CD > Variables and add 'UNITY_LICENSE' as the key and paste the content of the license file into the value)" + echo "" + echo "Once you're done, hit retry on the pipeline where other jobs failed, or just push another commit. Things should be green" + echo "" + echo "(optional) For more details on why this is not fully automated, visit https://gitlab.com/gableroux/unity3d-gitlab-ci-example/issues/73" +else + echo "License file could not be found at ${UNITY_ACTIVATION_FILE:-./unity3d.alf}" +fi +exit $exit_code diff --git a/ci/nunit-transforms/LICENSE.txt b/ci/nunit-transforms/LICENSE.txt new file mode 100644 index 0000000..5aa1d8f --- /dev/null +++ b/ci/nunit-transforms/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2016 Paul Hicks + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ci/nunit-transforms/nunit3-junit.xslt b/ci/nunit-transforms/nunit3-junit.xslt new file mode 100644 index 0000000..08e046a --- /dev/null +++ b/ci/nunit-transforms/nunit3-junit.xslt @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ci/test.sh b/ci/test.sh new file mode 100644 index 0000000..e45251c --- /dev/null +++ b/ci/test.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -x + +echo "Testing for $TEST_PLATFORM, Unit Type: $TESTING_TYPE" + +CODE_COVERAGE_PACKAGE="com.unity.testtools.codecoverage" +PACKAGE_MANIFEST_PATH="Packages/manifest.json" + +${UNITY_EXECUTABLE:-xvfb-run --auto-servernum --server-args='-screen 0 640x480x24' unity-editor} \ + -projectPath $UNITY_DIR \ + -runTests \ + -testPlatform $TEST_PLATFORM \ + -testResults $UNITY_DIR/$TEST_PLATFORM-results.xml \ + -logFile /dev/stdout \ + -batchmode \ + -nographics \ + -enableCodeCoverage \ + -coverageResultsPath $UNITY_DIR/$TEST_PLATFORM-coverage \ + -coverageOptions "generateAdditionalMetrics;generateHtmlReport;generateHtmlReportHistory;generateBadgeReport;" \ + -debugCodeOptimization + +UNITY_EXIT_CODE=$? + +if [ $UNITY_EXIT_CODE -eq 0 ]; then + echo "Run succeeded, no failures occurred"; +elif [ $UNITY_EXIT_CODE -eq 2 ]; then + echo "Run succeeded, some tests failed"; + if [ $TESTING_TYPE == 'JUNIT' ]; then + echo "Converting results to JUNit for analysis"; + saxonb-xslt -s $UNITY_DIR/$TEST_PLATFORM-results.xml -xsl $CI_PROJECT_DIR/ci/nunit-transforms/nunit3-junit.xslt >$UNITY_DIR/$TEST_PLATFORM-junit-results.xml + fi +elif [ $UNITY_EXIT_CODE -eq 3 ]; then + echo "Run failure (other failure)"; + if [ $TESTING_TYPE == 'JUNIT' ]; then + echo "Not converting results to JUNit"; + fi +else + echo "Unexpected exit code $UNITY_EXIT_CODE"; + if [ $TESTING_TYPE == 'JUNIT' ]; then + echo "Not converting results to JUNit"; + fi +fi + +if grep $CODE_COVERAGE_PACKAGE $PACKAGE_MANIFEST_PATH; then + cat $UNITY_DIR/$TEST_PLATFORM-coverage/Report/Summary.xml | grep Linecoverage + mv $UNITY_DIR/$TEST_PLATFORM-coverage/$CI_PROJECT_NAME-opencov/*Mode/TestCoverageResults_*.xml $UNITY_DIR/$TEST_PLATFORM-coverage/coverage.xml + rm -r $UNITY_DIR/$TEST_PLATFORM-coverage/$CI_PROJECT_NAME-opencov/ +else + { + echo -e "\033[33mCode Coverage package not found in $PACKAGE_MANIFEST_PATH. Please install the package \"Code Coverage\" through Unity's Package Manager to enable coverage reports.\033[0m" + } 2> /dev/null +fi + +cat $UNITY_DIR/$TEST_PLATFORM-results.xml | grep test-run | grep Passed +exit $UNITY_EXIT_CODE