A game about forced loneliness, made by TACStudios
1using System;
2
3namespace UnityEditor.TestTools
4{
5 /// <summary>
6 /// You can use the ```TestPlayerBuildModifier``` attribute to accomplish a couple of different scenarios.
7 /// ## Modify the Player build options for Play Mode tests
8 ///
9 /// It is possible to change the [BuildPlayerOptions](https://docs.unity3d.com/ScriptReference/BuildPlayerOptions.html) for the test **Player**, to achieve custom behavior when running **Play Mode** tests. Modifying the build options allows for changing the target location of the build as well as changing [BuildOptions](https://docs.unity3d.com/ScriptReference/BuildOptions.html).
10 ///
11 /// To modify the `BuildPlayerOptions`, do the following:
12 ///
13 /// * Implement the `ITestPlayerBuildModifier`
14 /// * Reference the implementation type in a `TestPlayerBuildModifier` attribute on an assembly level.
15 ///
16 /// <example>
17 /// <code>
18 /// using UnityEditor;
19 /// using UnityEditor.TestTools;
20 ///
21 /// [assembly:TestPlayerBuildModifier(typeof(BuildModifier))]
22 /// public class BuildModifier : ITestPlayerBuildModifier
23 /// {
24 /// public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions)
25 /// {
26 /// if (playerOptions.target == BuildTarget.iOS)
27 /// {
28 /// playerOptions.options |= BuildOptions.SymlinkLibraries; // Enable symlink libraries when running on iOS
29 /// }
30 ///
31 /// playerOptions.options |= BuildOptions.AllowDebugging; // Enable allow Debugging flag on the test Player.
32 /// return playerOptions;
33 /// }
34 /// }
35 /// </code>
36 /// </example>
37 ///
38 ///
39 /// > **Note:** When building the Player, it includes all `TestPlayerBuildModifier` attributes across all loaded assemblies, independent of the currently used test filter. As the implementation references the `UnityEditor` namespace, the code is typically implemented in an Editor only assembly, as the `UnityEditor` namespace is not available otherwise.
40 ///
41 /// ## Split build and run
42 /// It is possible to use the Unity Editor for building the Player with tests, without [running the tests](./workflow-run-playmode-test-standalone.md). This allows for running the Player on e.g. another machine. In this case, it is necessary to modify the Player to build and implement a custom handling of the test result.
43 /// By using `TestPlayerBuildModifier`, you can alter the `BuildOptions` to not start the Player after the build as well as build the Player at a specific location. Combined with [PostBuildCleanup](./reference-setup-and-cleanup.md#prebuildsetup-and-postbuildcleanup), you can automatically exit the Editor on completion of the build.
44 ///
45 ///
46 /// <example>
47 /// <code>
48 /// using System;
49 /// using System.IO;
50 /// using System.Linq;
51 /// using Tests;
52 /// using UnityEditor;
53 /// using UnityEditor.TestTools;
54 /// using UnityEngine;
55 /// using UnityEngine.TestTools;
56 ///
57 /// [assembly:TestPlayerBuildModifier(typeof(HeadlessPlayModeSetup))]
58 /// [assembly:PostBuildCleanup(typeof(HeadlessPlayModeSetup))]
59 ///
60 /// namespace Tests
61 /// {
62 /// public class HeadlessPlayModeSetup : ITestPlayerBuildModifier, IPostBuildCleanup
63 /// {
64 /// private static bool s_RunningPlayerTests;
65 /// public BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions)
66 /// {
67 /// // Do not launch the player after the build completes.
68 /// playerOptions.options &= ~BuildOptions.AutoRunPlayer;
69 ///
70 /// // Set the headlessBuildLocation to the output directory you desire. It does not need to be inside the project.
71 /// var headlessBuildLocation = Path.GetFullPath(Path.Combine(Application.dataPath, ".//..//PlayModeTestPlayer"));
72 /// var fileName = Path.GetFileName(playerOptions.locationPathName);
73 /// if (!string.IsNullOrEmpty(fileName))
74 /// {
75 /// headlessBuildLocation = Path.Combine(headlessBuildLocation, fileName);
76 /// }
77 /// playerOptions.locationPathName = headlessBuildLocation;
78 ///
79 /// // Instruct the cleanup to exit the Editor if the run came from the command line.
80 /// // The variable is static because the cleanup is being invoked in a new instance of the class.
81 /// s_RunningPlayerTests = true;
82 /// return playerOptions;
83 /// }
84 ///
85 /// public void Cleanup()
86 /// {
87 /// if (s_RunningPlayerTests && IsRunningTestsFromCommandLine())
88 /// {
89 /// // Exit the Editor on the next update, allowing for other PostBuildCleanup steps to run.
90 /// EditorApplication.update += () => { EditorApplication.Exit(0); };
91 /// }
92 /// }
93 ///
94 /// private static bool IsRunningTestsFromCommandLine()
95 /// {
96 /// var commandLineArgs = Environment.GetCommandLineArgs();
97 /// return commandLineArgs.Any(value => value == "-runTests");
98 /// }
99 /// }
100 /// }
101 /// </code>
102 /// </example>
103 ///
104 /// If the Editor is still running after the Play Mode tests have run, the Player tries to report the results back, using [PlayerConnection](https://docs.unity3d.com/ScriptReference/Networking.PlayerConnection.PlayerConnection.html), which has a reference to the IP address of the Editor machine, when built.
105 ///
106 /// To implement a custom way of reporting the results of the test run, let one of the assemblies in the Player include a `TestRunCallbackAttribute`. At `RunFinished`, it is possible to get the full test report as XML from the [NUnit](http://www.nunit.org/) test result by calling `result.ToXml(true)`. You can save the result and then save it on the device or send it to another machine as needed.
107 ///
108 /// </summary>
109 [AttributeUsage(AttributeTargets.Assembly)]
110 public class TestPlayerBuildModifierAttribute : Attribute
111 {
112 private Type m_Type;
113
114 /// <summary>
115 /// Initializes and returns an instance of TestPlayerBuildModifierAttribute or throws an <see cref="ArgumentException"/>.
116 /// </summary>
117 /// <param name="type">A target type that implements ITestPlayerBuildModifier.</param>
118 /// <exception cref="ArgumentException">Throws a <see cref="ArgumentException"/> if the type provided does not implemented the `ITestPlayerBuildModifier` interface. </exception>
119 public TestPlayerBuildModifierAttribute(Type type)
120 {
121 var interfaceType = typeof(ITestPlayerBuildModifier);
122 if (!interfaceType.IsAssignableFrom(type))
123 {
124 throw new ArgumentException(string.Format("Type provided to {0} does not implement {1}", GetType().Name, interfaceType.Name));
125 }
126 m_Type = type;
127 }
128
129 internal ITestPlayerBuildModifier ConstructModifier()
130 {
131 return Activator.CreateInstance(m_Type) as ITestPlayerBuildModifier;
132 }
133 }
134}