Skip to content

Commit

Permalink
Merge pull request #36 from nowsprinting/feature/launch_from_test
Browse files Browse the repository at this point in the history
Support launch autopilot from Play Mode tests
  • Loading branch information
Kuniwak authored Apr 3, 2024
2 parents 17dad43 + a7f2c65 commit 3d5defb
Show file tree
Hide file tree
Showing 18 changed files with 278 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ dotnet_naming_symbols.all_members.applicable_kinds = *

dotnet_naming_style.pascal_case_style.capitalization = pascal_case

file_header_template = Copyright (c) ${CurrentDate.Year} DeNA Co., Ltd.\nThis software is released under the MIT License.
file_header_template = Copyright (c) 2023-${CurrentDate.Year} DeNA Co., Ltd.\nThis software is released under the MIT License.

# RS0016: Only enable if API files are present
dotnet_public_api_analyzer.require_api_files = true
Expand Down
2 changes: 1 addition & 1 deletion Editor/Commandline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private static void Bootstrap()

// Activate autopilot and enter play mode
var state = AutopilotState.Instance;
state.launchFrom = AutopilotState.LaunchType.Commandline;
state.launchFrom = LaunchType.Commandline;
state.settings = settings;
EditorApplication.isPlaying = true;

Expand Down
6 changes: 3 additions & 3 deletions Editor/UI/Settings/AutopilotSettingsEditor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,20 +170,20 @@ private static void DrawHeader(string label)
internal async UniTask Stop()
{
var autopilot = FindObjectOfType<Autopilot>();
await autopilot.TerminateAsync(Autopilot.ExitCode.Normally);
await autopilot.TerminateAsync(ExitCode.Normally);
}

internal void Launch(AutopilotState state)
{
state.settings = _settings;
if (EditorApplication.isPlaying)
{
state.launchFrom = AutopilotState.LaunchType.EditorPlayMode;
state.launchFrom = LaunchType.EditorPlayMode;
Launcher.Run();
}
else
{
state.launchFrom = AutopilotState.LaunchType.EditorEditMode;
state.launchFrom = LaunchType.EditorEditMode;
EditorApplication.isPlaying = true;
}
}
Expand Down
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ You can choose from two typical installation methods.
![](Documentation~/PackageManager_Dark.png/#gh-dark-mode-only)
![](Documentation~/PackageManager_Light.png/#gh-light-mode-only)

> **Note**
> [!NOTE]
> Do not forget to add `com.cysharp` and `com.nowsprinting` into scopes. These are used within Anjin.
> **Note**
> [!NOTE]
> Required install [Unity Test Framework](https://docs.unity3d.com/Packages/com.unity.test-framework@latest) package v1.3 or later for running tests (when adding to the `testables` in package.json).
### Install via OpenUPM-CLI
Expand Down Expand Up @@ -77,7 +77,7 @@ After installing the UPM package in the game title project, configure and implem
- Implement a custom Agent if necessary
- Implement initialization process as needed

> **Note**
> [!NOTE]
> Set `UNITY_EDITOR || DENA_AUTOPILOT_ENABLE` in the Define Constraints of the Assembly Definition File to which they belong to exclude them from release builds.

Expand Down Expand Up @@ -169,7 +169,27 @@ Open the AutopilotSettings file you wish to run in the inspector and click the *
After the set run time has elapsed, or as in normal play mode, clicking the Play button will stop the program.


### 2. Run from commandline
### 2. Run from Play Mode test

Autopilot works within your test code using the async method `LauncherFromTest.AutopilotAsync(string)`.
Specify the `AutopilotSettings` file path as the argument.

```
[Test]
public async Task LaunchAutopilotFromTest()
{
await LauncherFromTest.AutopilotAsync("Assets/Path/To/AutopilotSettings.asset");
}
```

> [!NOTE]
> If an error is detected in running, it will be output to `LogError` and the test will fail.
> [!WARNING]
> The default timeout for tests is 3 minutes. If the autopilot execution time exceeds 3 minutes, please specify the timeout time with the `Timeout` attribute.

### 3. Run from commandline

To execute from the commandline, specify the following arguments.

Expand Down Expand Up @@ -241,7 +261,7 @@ See **Anjin Annotations** below for more information.

This is an Agent that playback uGUI operations with the Recorded Playback feature of the [Automated QA](https://docs.unity3d.com/Packages/com.unity.automated-testing@latest) package.

> **Note**
> [!NOTE]
> The Automated QA package is in the preview stage. Please note that destructive changes may occur, and the package itself may be discontinued or withdrawn.
The following can be set in an instance (.asset file) of this Agent.
Expand Down
30 changes: 25 additions & 5 deletions README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ Click [English](./README.md) for English page if you need.
![](Documentation~/PackageManager_Dark.png/#gh-dark-mode-only)
![](Documentation~/PackageManager_Light.png/#gh-light-mode-only)

> **Note**
> [!NOTE]
> scopesに `com.cysharp``com.nowsprinting` を忘れず追加してください。Anjin内で使用しています。
> **Note**
> [!NOTE]
> Anjinパッケージ内のテストを実行する場合(package.jsonの `testables` に追加するとき)は、[Unity Test Framework](https://docs.unity3d.com/Packages/com.unity.test-framework@latest) パッケージ v1.3以上が必要です。
### openupm-cli を使用する場合
Expand Down Expand Up @@ -76,7 +76,7 @@ Anjinを起動すると、次のファイルが自動生成されます。
- 必要に応じて専用Agentの実装
- 必要に応じて初期化処理の実装

> **Note**
> [!NOTE]
> ゲームタイトル固有のオートパイロット向けコードは、属するAssembly Definition FileのDefine Constraintsに `UNITY_EDITOR || DENA_AUTOPILOT_ENABLE` を設定することで、リリースビルドから除外できます

Expand Down Expand Up @@ -168,7 +168,27 @@ Slack通知に付与するメンションを設定します。
設定された実行時間が経過するか、通常の再生モードと同じく再生ボタンクリックで停止します。


### 2. コマンドラインから実行
### 2. Play Modeテストから実行

非同期メソッド `LauncherFromTest.AutopilotAsync(string)` を使用することで、テストコード内でオートパイロットが動作します。
引数には `AutopilotSettings` ファイルパスを指定します。

```
[Test]
public async Task LaunchAutopilotFromTest()
{
await LauncherFromTest.AutopilotAsync("Assets/Path/To/AutopilotSettings.asset");
}
```

> [!NOTE]
> 実行中にエラーを検知すると `LogError` が出力されるため、そのテストは失敗と判定されます。
> [!WARNING]
> テストのデフォルトタイムアウトは3分です。オートパイロットの実行時間が3分を超える場合は `Timeout` 属性でタイムアウト時間を指定してください。

### 3. コマンドラインから実行

コマンドラインから実行する場合、以下の引数を指定します。

Expand Down Expand Up @@ -242,7 +262,7 @@ uGUIのコンポーネントをランダムに操作するAgentです。

[Automated QA](https://docs.unity3d.com/Packages/com.unity.automated-testing@latest)パッケージのRecorded Playback機能でレコーディングしたuGUI操作を再生するAgentです。

> **Note**
> [!NOTE]
> Automated QAパッケージはプレビュー段階のため、破壊的変更や、パッケージ自体の開発中止・廃止もありえる点、ご注意ください。
このAgentのインスタンス(.assetファイル)には以下を設定できます。
Expand Down
32 changes: 9 additions & 23 deletions Runtime/Autopilot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,6 @@ namespace DeNA.Anjin
/// </summary>
public class Autopilot : MonoBehaviour
{
/// <summary>
/// Exit code for autopilot running
/// </summary>
public enum ExitCode
{
/// <summary>
/// Normally exit
/// </summary>
Normally = 0,

/// <summary>
/// Exit by un catch Exceptions
/// </summary>
UnCatchExceptions = 1,

/// <summary>
/// Exit by fault in log message
/// </summary>
AutopilotFailed = 2
}

private ILogger _logger;
private RandomFactory _randomFactory;
private IAgentDispatcher _dispatcher;
Expand Down Expand Up @@ -114,7 +93,8 @@ private IEnumerator Lifespan(int timeoutSec)
/// <param name="logString">Log message string when terminate by the log message</param>
/// <param name="stackTrace">Stack trace when terminate by the log message</param>
/// <returns>A task awaits termination get completed</returns>
public async UniTask TerminateAsync(ExitCode exitCode, string logString = null, string stackTrace = null, CancellationToken token = default)
public async UniTask TerminateAsync(ExitCode exitCode, string logString = null, string stackTrace = null,
CancellationToken token = default)
{
if (_dispatcher != null)
{
Expand All @@ -134,10 +114,16 @@ public async UniTask TerminateAsync(ExitCode exitCode, string logString = null,

Destroy(this.gameObject);

if (_state.launchFrom == AutopilotState.LaunchType.EditorPlayMode)
if (_state.IsLaunchFromPlayMode)
{
_logger.Log("Terminate autopilot");
_state.Reset();
#if UNITY_INCLUDE_TESTS
if (_state.launchFrom == LaunchType.PlayModeTests && exitCode != ExitCode.Normally)
{
throw new NUnit.Framework.AssertionException($"Autopilot failed with exit code {exitCode}");
}
#endif
return; // Only terminate autopilot run if starting from play mode.
}

Expand Down
26 changes: 26 additions & 0 deletions Runtime/ExitCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2023-2024 DeNA Co., Ltd.
// This software is released under the MIT License.

namespace DeNA.Anjin
{
/// <summary>
/// Exit code for autopilot running
/// </summary>
public enum ExitCode
{
/// <summary>
/// Normally exit
/// </summary>
Normally = 0,

/// <summary>
/// Exit by un catch Exceptions
/// </summary>
UnCatchExceptions = 1,

/// <summary>
/// Exit by fault in log message
/// </summary>
AutopilotFailed = 2
}
}
3 changes: 3 additions & 0 deletions Runtime/ExitCode.cs.meta

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

42 changes: 42 additions & 0 deletions Runtime/LaunchType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) 2023-2024 DeNA Co., Ltd.
// This software is released under the MIT License.

namespace DeNA.Anjin
{
/// <summary>
/// Define of what autopilot was launched by
/// </summary>
public enum LaunchType
{
/// <summary>
/// Not launch yet
/// </summary>
NotSet = 0,

/// <summary>
/// Launch via Edit mode
/// </summary>
EditorEditMode,

/// <summary>
/// Launch via Play mode
/// </summary>
EditorPlayMode,

/// <summary>
/// Launch via Play mode tests
/// </summary>
PlayModeTests,

/// <summary>
/// Launch from commandline interface
/// When autopilot is finished, Unity editor is also exit.
/// </summary>
Commandline,

/// <summary>
/// Launch on standalone platform player build (not support yet)
/// </summary>
Runtime,
}
}
3 changes: 3 additions & 0 deletions Runtime/LaunchType.cs.meta

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

6 changes: 3 additions & 3 deletions Runtime/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static void Run()
return; // Normally play mode (not run autopilot)
}

if (state.launchFrom != AutopilotState.LaunchType.EditorPlayMode)
if (!state.IsLaunchFromPlayMode)
{
EditorApplication.playModeStateChanged += OnChangePlayModeState;
}
Expand Down Expand Up @@ -83,12 +83,12 @@ private static void OnChangePlayModeState(PlayModeStateChange playModeStateChang
var state = AutopilotState.Instance;
switch (state.launchFrom)
{
case AutopilotState.LaunchType.EditorEditMode:
case LaunchType.EditorEditMode:
Debug.Log("Exit play mode");
state.Reset();
break;

case AutopilotState.LaunchType.Commandline:
case LaunchType.Commandline:
// Exit Unity when returning from play mode to edit mode.
// Because it may freeze when exiting without going through edit mode.
var exitCode = (int)state.exitCode;
Expand Down
55 changes: 55 additions & 0 deletions Runtime/LauncherFromTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) 2023-2024 DeNA Co., Ltd.
// This software is released under the MIT License.

using Cysharp.Threading.Tasks;
using DeNA.Anjin.Settings;
using NUnit.Framework;
#if UNITY_EDITOR
using UnityEditor;
#endif

namespace DeNA.Anjin
{
/// <summary>
/// Launch from Play Mode test interface.
/// </summary>
public static class LauncherFromTest
{
/// <summary>
/// Run autopilot from Play Mode test.
/// If an error is detected in running, it will be output to `LogError` and the test will fail.
/// </summary>
/// <param name="settings">Autopilot settings</param>
public static async UniTask AutopilotAsync(AutopilotSettings settings)
{
#if UNITY_EDITOR
if (!EditorApplication.isPlaying)
{
throw new AssertionException("Not support run on Edit Mode tests");
}
#endif
var state = AutopilotState.Instance;
if (state.IsRunning)
{
throw new AssertionException("Autopilot is already running");
}

state.launchFrom = LaunchType.PlayModeTests;
state.settings = settings;
Launcher.Run();

await UniTask.WaitUntil(() => !state.IsRunning);
}

/// <summary>
/// Run autopilot from Play Mode test.
/// If an error is detected in running, it will be output to `LogError` and the test will fail.
/// </summary>
/// <param name="autopilotSettingsPath">Asset file path for autopilot settings</param>
public static async UniTask AutopilotAsync(string autopilotSettingsPath)
{
var settings = AssetDatabase.LoadAssetAtPath<AutopilotSettings>(autopilotSettingsPath);
await AutopilotAsync(settings);
}
}
}
3 changes: 3 additions & 0 deletions Runtime/LauncherFromTest.cs.meta

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

Loading

0 comments on commit 3d5defb

Please sign in to comment.