Skip to content

Commit de23579

Browse files
author
Stewart Miles
committed
Added integration test checkpointing.
Some integration tests can cause the app domain to be reloaded which restarts integration tests. This change stores the state of executed tests so that if the app domain is reloaded test execution can be resumed from prior to the app domain reload. Bug: 150471207 Change-Id: I72a1a8c8d5e3c1e3a8b6a8b0f8e2a5770c4f5a01
1 parent 22502da commit de23579

File tree

1 file changed

+140
-16
lines changed

1 file changed

+140
-16
lines changed

source/IntegrationTester/src/Runner.cs

Lines changed: 140 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using System.Collections.Generic;
1919
using System.IO;
2020
using System.Reflection;
21+
using System.Xml;
2122

2223
using Google;
2324

@@ -71,6 +72,11 @@ public static class Runner {
7172
/// </summary>
7273
private static bool defaultTestCaseCalled = false;
7374

75+
/// <summary>
76+
/// File to store snapshot of test case results.
77+
/// </summary>
78+
private static string TestCaseResultsFilename = "Temp/GvhRunnerTestCaseResults.xml";
79+
7480
/// <summary>
7581
/// Register a method to call when the Version Handler has enabled all plugins in the
7682
/// project.
@@ -116,27 +122,12 @@ public static void ScheduleTestCase(TestCase test) {
116122
testCases.Add(test);
117123
}
118124

119-
/// <summary>
120-
/// This module can be executed multiple times when the Version Handler is enabling
121-
/// so this method uses a temporary file to determine whether the module has been executed
122-
/// once in a Unity session.
123-
/// </summary>
124-
/// <returns>true if the module was previously initialized, false otherwise.</returns>
125-
private static bool SetInitialized() {
126-
const string INITIALIZED_PATH = "Temp/TestEnabledCallbackInitialized";
127-
if (File.Exists(INITIALIZED_PATH)) return true;
128-
File.WriteAllText(INITIALIZED_PATH, "Ready");
129-
return false;
130-
}
131-
132125
/// <summary>
133126
/// Called when the Version Handler has enabled all managed plugins in a project.
134127
/// </summary>
135128
public static void VersionHandlerReady() {
136129
UnityEngine.Debug.Log("VersionHandler is ready.");
137130
Google.VersionHandler.UpdateCompleteMethods = null;
138-
// If this has already been initialized this session, do not start tests again.
139-
if (SetInitialized()) return;
140131
// Start executing tests.
141132
ConfigureTestCases();
142133
RunOnMainThread.Run(() => { ExecuteNextTestCase(); }, runNow: false);
@@ -157,7 +148,16 @@ private static void ConfigureTestCases() {
157148
var testCaseMethods = new List<MethodInfo>();
158149
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) {
159150
foreach (var type in assembly.GetTypes()) {
160-
foreach (var method in type.GetMethods()) {
151+
IEnumerable<MethodInfo> methods;
152+
try {
153+
methods = type.GetMethods();
154+
} catch (Exception) {
155+
// TargetInvocationException, TypeLoadException and others can be thrown
156+
// when retrieving the methods of some .NET assemblies
157+
// (e.g System.Web.UI.WebControls.ModelDataSourceView) so ignore them.
158+
continue;
159+
}
160+
foreach (var method in methods) {
161161
foreach (var attribute in method.GetCustomAttributes(true)) {
162162
if (attribute is InitializerAttribute) {
163163
initializerMethods.Add(method);
@@ -199,6 +199,22 @@ private static void ConfigureTestCases() {
199199
}
200200
}
201201

202+
// Restore for all executed test cases, restore results and remove all pending test
203+
// cases that are complete.
204+
var executedTestCaseNames = new HashSet<string>();
205+
foreach (var executedTestCase in ReadTestCaseResults()) {
206+
testCaseResults.Add(executedTestCase);
207+
executedTestCaseNames.Add(executedTestCase.TestCaseName);
208+
}
209+
var filteredTestCases = new List<TestCase>();
210+
foreach (var testCase in testCases) {
211+
if (!executedTestCaseNames.Contains(testCase.Name)) {
212+
filteredTestCases.Add(testCase);
213+
}
214+
}
215+
defaultTestCaseCalled = executedTestCaseNames.Contains("DefaultTestCase");
216+
testCases = filteredTestCases;
217+
202218
if (!defaultInitializerCalled) {
203219
UnityEngine.Debug.Log("FAILED: Default Initializer not called.");
204220
initializationSuccessful = false;
@@ -256,13 +272,120 @@ public static void LogSummaryAndExit() {
256272
Exit(passed);
257273
}
258274

275+
/// <summary>
276+
/// Read test case results from the journal.
277+
/// </summary>
278+
/// <returns>List of TestCaseResults.</returns>
279+
private static List<TestCaseResult> ReadTestCaseResults() {
280+
var readTestCaseResults = new List<TestCaseResult>();
281+
if (!File.Exists(TestCaseResultsFilename)) return readTestCaseResults;
282+
283+
bool successful = XmlUtilities.ParseXmlTextFileElements(
284+
TestCaseResultsFilename, new Logger(),
285+
(XmlTextReader reader, string elementName, bool isStart, string parentElementName,
286+
List<string> elementNameStack) => {
287+
TestCaseResult currentTestCaseResult = null;
288+
int testCaseResultsCount = readTestCaseResults.Count;
289+
if (testCaseResultsCount > 0) {
290+
currentTestCaseResult = readTestCaseResults[testCaseResultsCount - 1];
291+
}
292+
if (elementName == "TestCaseResults" && parentElementName == "") {
293+
if (isStart) {
294+
readTestCaseResults.Clear();
295+
}
296+
return true;
297+
} else if (elementName == "TestCaseResult" &&
298+
parentElementName == "TestCaseResults") {
299+
if (isStart) {
300+
readTestCaseResults.Add(new TestCaseResult(new TestCase()));
301+
}
302+
return true;
303+
} else if (elementName == "TestCaseName" &&
304+
parentElementName == "TestCaseResult") {
305+
if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) {
306+
currentTestCaseResult.TestCaseName = reader.ReadContentAsString();
307+
}
308+
return true;
309+
} else if (elementName == "Skipped" && parentElementName == "TestCaseResult") {
310+
if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) {
311+
currentTestCaseResult.Skipped = reader.ReadContentAsBoolean();
312+
}
313+
return true;
314+
} else if (elementName == "ErrorMessages" &&
315+
parentElementName == "TestCaseResult") {
316+
return true;
317+
} else if (elementName == "ErrorMessage" &&
318+
parentElementName == "ErrorMessages") {
319+
if (isStart && reader.Read() && reader.NodeType == XmlNodeType.Text) {
320+
currentTestCaseResult.ErrorMessages.Add(reader.ReadContentAsString());
321+
}
322+
return true;
323+
}
324+
return false;
325+
});
326+
if (!successful) {
327+
UnityEngine.Debug.LogWarning(
328+
String.Format("Failed while reading {0}, test execution will restart if the " +
329+
"app domain is reloaded.", TestCaseResultsFilename));
330+
}
331+
return readTestCaseResults;
332+
}
333+
334+
/// <summary>
335+
// Log a test case result to the journal so that it isn't executed again if the app
336+
// domain is reloaded.
337+
/// </summary>
338+
private static bool WriteTestCaseResult(TestCaseResult testCaseResult) {
339+
var existingTestCaseResults = ReadTestCaseResults();
340+
existingTestCaseResults.Add(testCaseResult);
341+
try {
342+
Directory.CreateDirectory(Path.GetDirectoryName(TestCaseResultsFilename));
343+
using (var writer = new XmlTextWriter(new StreamWriter(TestCaseResultsFilename)) {
344+
Formatting = Formatting.Indented
345+
}) {
346+
writer.WriteStartElement("TestCaseResults");
347+
foreach (var result in existingTestCaseResults) {
348+
writer.WriteStartElement("TestCaseResult");
349+
if (!String.IsNullOrEmpty(result.TestCaseName)) {
350+
writer.WriteStartElement("TestCaseName");
351+
writer.WriteValue(result.TestCaseName);
352+
writer.WriteEndElement();
353+
}
354+
writer.WriteStartElement("Skipped");
355+
writer.WriteValue(result.Skipped);
356+
writer.WriteEndElement();
357+
if (result.ErrorMessages.Count > 0) {
358+
writer.WriteStartElement("ErrorMessages");
359+
foreach (var errorMessage in result.ErrorMessages) {
360+
writer.WriteStartElement("ErrorMessage");
361+
writer.WriteValue(errorMessage);
362+
writer.WriteEndElement();
363+
}
364+
writer.WriteEndElement();
365+
}
366+
writer.WriteEndElement();
367+
}
368+
writer.WriteEndElement();
369+
writer.Flush();
370+
writer.Close();
371+
}
372+
} catch (Exception e) {
373+
UnityEngine.Debug.LogWarning(
374+
String.Format("Failed while writing {0} ({1}), test execution will restart " +
375+
"if the app domain is reloaded.", TestCaseResultsFilename, e));
376+
return false;
377+
}
378+
return true;
379+
}
380+
259381
/// <summary>
260382
/// Log a test case result with error details.
261383
/// </summary>
262384
/// <param name="testCaseResult">Result to log.</param>
263385
public static void LogTestCaseResult(TestCaseResult testCaseResult) {
264386
testCaseResults.Add(testCaseResult);
265387
UnityEngine.Debug.Log(testCaseResult.FormatString(true));
388+
WriteTestCaseResult(testCaseResult);
266389
}
267390

268391
/// <summary>
@@ -297,6 +420,7 @@ private static void ExecuteNextTestCase() {
297420
bool executeNext;
298421
do {
299422
executeNext = false;
423+
UnityEngine.Debug.Log(String.Format("Remaining test cases {0}", testCases.Count));
300424
if (testCases.Count > 0) {
301425
var testCase = testCases[0];
302426
testCases.RemoveAt(0);

0 commit comments

Comments
 (0)