Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions .github/workflows/test-audience-sample-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,114 @@ jobs:
name: playmode-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}
path: ${{ steps.playmode.outputs.artifactsPath }}

linux-build-smoke:
needs: set-matrix
if: |
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
|| github.event_name == 'schedule'
|| github.event_name == 'workflow_dispatch'
name: smoke ${{ matrix.target }} / ${{ matrix.backend }} / Unity ${{ matrix.unity }}
runs-on: ubuntu-latest-8-cores
timeout-minutes: 30
env:
AUDIENCE_TEST_CELL_ID: ${{ matrix.target }}-${{ matrix.backend }}-${{ matrix.unity }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJSON(needs.set-matrix.outputs.playmode_linux) }}

steps:
- uses: actions/checkout@v4
with:
lfs: true

- name: Inject testables passthrough (no-op)
# The smoke build does not need test asmdefs; this step exists so
# the Library cache key shape stays identical to the playmode-linux
# job and we share warm caches.
run: |
jq '.' examples/audience/Packages/manifest.json > /dev/null

- uses: actions/cache@v4
with:
path: examples/audience/Library
key: Library-smoke-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}-${{ hashFiles('examples/audience/Assets/**', 'examples/audience/Packages/**', 'examples/audience/ProjectSettings/**', 'src/Packages/Audience/**') }}
restore-keys: |
Library-smoke-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}-
Library-smoke-${{ matrix.backend }}-${{ matrix.target }}-

- uses: game-ci/unity-builder@v4
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
AUDIENCE_SCRIPTING_BACKEND: ${{ matrix.backend }}
with:
unityVersion: ${{ matrix.unity }}
targetPlatform: ${{ matrix.target }}
projectPath: examples/audience
buildMethod: Immutable.Audience.Samples.SampleApp.Editor.LinuxSmokeBuilder.Build
buildName: LinuxSmokePlayer
customParameters: --buildPath Builds/LinuxSmoke/LinuxSmokePlayer.x86_64

- name: Run smoke
env:
AUDIENCE_TEST_PUBLISHABLE_KEY: ${{ secrets.AUDIENCE_TEST_PUBLISHABLE_KEY }}
AUDIENCE_TEST_RUN_ID: ${{ github.run_id }}-${{ github.run_attempt }}
shell: bash
run: |
set -uo pipefail
player="examples/audience/Builds/LinuxSmoke/LinuxSmokePlayer.x86_64"
if [ ! -x "$player" ]; then
echo "::error::Built player not found at $player"
ls -la "examples/audience/Builds/LinuxSmoke/" 2>/dev/null || true
exit 1
fi

mkdir -p artifacts

# xvfb-run guards against Linux player init failing without a
# display, even with -batchmode. Cheap insurance.
sudo apt-get update >/dev/null 2>&1 || true
sudo apt-get install -y xvfb >/dev/null 2>&1 || true

xvfb-run -a --server-args="-screen 0 320x240x24 -ac" -- \
"$player" -batchmode -nographics -logFile - \
> artifacts/smoke.log 2>&1 &
smoke_pid=$!

# Hard cap 60s. The runner itself enforces a 20s flush + 30s deadline;
# this is just to release the cell if Application.Quit hangs.
deadline=$((SECONDS + 60))
while kill -0 $smoke_pid 2>/dev/null; do
if [ "$SECONDS" -ge "$deadline" ]; then
echo "::error::Smoke runner did not exit within 60s; killing."
kill -KILL $smoke_pid 2>/dev/null || true
wait $smoke_pid 2>/dev/null || true
echo "----- smoke.log -----"
cat artifacts/smoke.log || true
exit 1
fi
sleep 1
done

wait $smoke_pid
rc=$?
echo "Smoke exit code: $rc"
echo "----- smoke.log (tail 80) -----"
tail -n 80 artifacts/smoke.log || true
exit $rc

- name: Upload smoke artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: linux-build-smoke-${{ matrix.backend }}-${{ matrix.target }}-${{ matrix.unity }}
if-no-files-found: ignore
path: |
artifacts/smoke.log
examples/audience/Builds/LinuxSmoke/**

# Mobile IL2CPP build validation — runs on GitHub-hosted Ubuntu via GameCI Docker
# containers so self-hosted macOS/Windows machines are not occupied.
# Scope: IL2CPP compile pipeline only. Runtime tests require a real device and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Immutable.Audience.Samples.SampleApp.Editor",
"rootNamespace": "Immutable.Audience.Samples.SampleApp.Editor",
"references": [],
"references": ["Immutable.Audience.Samples.SampleApp"],
"includePlatforms": [
"Editor"
],
Expand Down
94 changes: 94 additions & 0 deletions examples/audience/Assets/Editor/LinuxSmokeBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#nullable enable

using System;
using System.IO;
using Immutable.Audience.Samples.SampleApp;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Immutable.Audience.Samples.SampleApp.Editor
{
// Invoked by CI via:
// Unity -batchmode -buildTarget StandaloneLinux64 \
// -executeMethod Immutable.Audience.Samples.SampleApp.Editor.LinuxSmokeBuilder.Build \
// -quit
//
// Optional CLI arg:
// --buildPath <path> Output path for the player (default: Builds/LinuxSmoke/LinuxSmokePlayer.x86_64)
//
// Produces a single-scene Linux player whose only behaviour is the
// LinuxSmokeRunner MonoBehaviour. The scene is generated in memory at
// build time to avoid shipping a hand-written .unity asset.
internal static class LinuxSmokeBuilder
{
private const string DefaultBuildPath = "Builds/LinuxSmoke/LinuxSmokePlayer.x86_64";
private const string TempScenePath = "Assets/_LinuxSmokeBuild.unity";

public static void Build()
{
string buildPath = GetArgValue("--buildPath") ?? DefaultBuildPath;
string scenePath = TempScenePath;

try
{
CreateSmokeScene(scenePath);

Directory.CreateDirectory(Path.GetDirectoryName(buildPath)!);

var options = new BuildPlayerOptions
{
scenes = new[] { scenePath },
locationPathName = buildPath,
target = BuildTarget.StandaloneLinux64,
targetGroup = BuildTargetGroup.Standalone,
options = BuildOptions.None,
};

Debug.Log($"[LinuxSmokeBuilder] Building -> {buildPath}");
var report = BuildPipeline.BuildPlayer(options);
var summary = report.summary;

if (summary.result == BuildResult.Succeeded)
{
Debug.Log($"[LinuxSmokeBuilder] Build succeeded ({summary.totalSize / 1024 / 1024} MB).");
}
else
{
Debug.LogError($"[LinuxSmokeBuilder] Build failed: {summary.totalErrors} error(s).");
EditorApplication.Exit(1);
}
}
finally
{
if (AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath) != null)
{
AssetDatabase.DeleteAsset(scenePath);
}
}
}

private static void CreateSmokeScene(string scenePath)
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);

var go = new GameObject("SmokeRunner");
go.AddComponent<LinuxSmokeRunner>();

EditorSceneManager.SaveScene(scene, scenePath);
}

private static string? GetArgValue(string flag)
{
var args = Environment.GetCommandLineArgs();
for (int i = 0; i < args.Length - 1; i++)
{
if (args[i] == flag)
return args[i + 1];
}
return null;
}
}
}
11 changes: 11 additions & 0 deletions examples/audience/Assets/Editor/LinuxSmokeBuilder.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

114 changes: 114 additions & 0 deletions examples/audience/Assets/SampleApp/Scripts/LinuxSmokeRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

namespace Immutable.Audience.Samples.SampleApp
{
// Headless smoke runner used by the linux-build-smoke CI job. Boots the
// Audience SDK, sends one marker event, awaits a flush, then quits with
// exit code 0 on success or 1 on any failure. Attached to a runtime
// GameObject by LinuxSmokeBuilder; does not run during normal SampleApp
// execution.
public sealed class LinuxSmokeRunner : MonoBehaviour
{
private const string KeyEnv = "AUDIENCE_TEST_PUBLISHABLE_KEY";
private const string RunEnv = "AUDIENCE_TEST_RUN_ID";
private const string CellEnv = "AUDIENCE_TEST_CELL_ID";

private const string EventName = "linux_smoke_ci_marker";
private const int FlushTimeoutSeconds = 20;
private const int OverallTimeoutSeconds = 30;

private void Start()
{
StartCoroutine(RunSmoke());
}

private IEnumerator RunSmoke()
{
var deadline = Time.realtimeSinceStartup + OverallTimeoutSeconds;
AudienceError? capturedError = null;

string? key = Environment.GetEnvironmentVariable(KeyEnv);
if (string.IsNullOrEmpty(key))
{
Debug.LogError($"[LinuxSmoke] {KeyEnv} is unset. Cannot init.");
Application.Quit(1);
yield break;
}

var config = new AudienceConfig
{
PublishableKey = key,
Consent = ConsentLevel.Full,
Debug = true,
OnError = err => capturedError = err,
};

try
{
ImmutableAudience.Init(config);
}
catch (Exception ex)
{
Debug.LogError($"[LinuxSmoke] Init threw: {ex}");
Application.Quit(1);
yield break;
}

var props = new Dictionary<string, object>
{
["runId"] = Environment.GetEnvironmentVariable(RunEnv) ?? "(unset)",
["cellId"] = Environment.GetEnvironmentVariable(CellEnv) ?? "(unset)",
["host"] = "linux-build-smoke",
};

try
{
ImmutableAudience.Track(EventName, props);
}
catch (Exception ex)
{
Debug.LogError($"[LinuxSmoke] Track threw: {ex}");
Application.Quit(1);
yield break;
}

using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(FlushTimeoutSeconds));
var flushTask = ImmutableAudience.FlushAsync(cts.Token);

while (!flushTask.IsCompleted)
{
if (Time.realtimeSinceStartup > deadline)
{
Debug.LogError("[LinuxSmoke] Overall deadline reached before flush completed.");
Application.Quit(1);
yield break;
}
yield return null;
}

if (flushTask.IsFaulted)
{
Debug.LogError($"[LinuxSmoke] FlushAsync faulted: {flushTask.Exception}");
Application.Quit(1);
yield break;
}

if (capturedError != null)
{
Debug.LogError($"[LinuxSmoke] AudienceError fired during run: {capturedError}");
Application.Quit(1);
yield break;
}

Debug.Log("[LinuxSmoke] OK. Init + Track + FlushAsync completed without errors.");
Application.Quit(0);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading