1818using System . Collections . Generic ;
1919using System . IO ;
2020using System . Reflection ;
21+ using System . Xml ;
2122
2223using 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