18
18
using System . Collections . Generic ;
19
19
using System . IO ;
20
20
using System . Reflection ;
21
+ using System . Xml ;
21
22
22
23
using Google ;
23
24
@@ -71,6 +72,11 @@ public static class Runner {
71
72
/// </summary>
72
73
private static bool defaultTestCaseCalled = false ;
73
74
75
+ /// <summary>
76
+ /// File to store snapshot of test case results.
77
+ /// </summary>
78
+ private static string TestCaseResultsFilename = "Temp/GvhRunnerTestCaseResults.xml" ;
79
+
74
80
/// <summary>
75
81
/// Register a method to call when the Version Handler has enabled all plugins in the
76
82
/// project.
@@ -116,27 +122,12 @@ public static void ScheduleTestCase(TestCase test) {
116
122
testCases . Add ( test ) ;
117
123
}
118
124
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
-
132
125
/// <summary>
133
126
/// Called when the Version Handler has enabled all managed plugins in a project.
134
127
/// </summary>
135
128
public static void VersionHandlerReady ( ) {
136
129
UnityEngine . Debug . Log ( "VersionHandler is ready." ) ;
137
130
Google . VersionHandler . UpdateCompleteMethods = null ;
138
- // If this has already been initialized this session, do not start tests again.
139
- if ( SetInitialized ( ) ) return ;
140
131
// Start executing tests.
141
132
ConfigureTestCases ( ) ;
142
133
RunOnMainThread . Run ( ( ) => { ExecuteNextTestCase ( ) ; } , runNow : false ) ;
@@ -157,7 +148,16 @@ private static void ConfigureTestCases() {
157
148
var testCaseMethods = new List < MethodInfo > ( ) ;
158
149
foreach ( var assembly in AppDomain . CurrentDomain . GetAssemblies ( ) ) {
159
150
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 ) {
161
161
foreach ( var attribute in method . GetCustomAttributes ( true ) ) {
162
162
if ( attribute is InitializerAttribute ) {
163
163
initializerMethods . Add ( method ) ;
@@ -199,6 +199,22 @@ private static void ConfigureTestCases() {
199
199
}
200
200
}
201
201
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
+
202
218
if ( ! defaultInitializerCalled ) {
203
219
UnityEngine . Debug . Log ( "FAILED: Default Initializer not called." ) ;
204
220
initializationSuccessful = false ;
@@ -256,13 +272,120 @@ public static void LogSummaryAndExit() {
256
272
Exit ( passed ) ;
257
273
}
258
274
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
+
259
381
/// <summary>
260
382
/// Log a test case result with error details.
261
383
/// </summary>
262
384
/// <param name="testCaseResult">Result to log.</param>
263
385
public static void LogTestCaseResult ( TestCaseResult testCaseResult ) {
264
386
testCaseResults . Add ( testCaseResult ) ;
265
387
UnityEngine . Debug . Log ( testCaseResult . FormatString ( true ) ) ;
388
+ WriteTestCaseResult ( testCaseResult ) ;
266
389
}
267
390
268
391
/// <summary>
@@ -297,6 +420,7 @@ private static void ExecuteNextTestCase() {
297
420
bool executeNext ;
298
421
do {
299
422
executeNext = false ;
423
+ UnityEngine . Debug . Log ( String . Format ( "Remaining test cases {0}" , testCases . Count ) ) ;
300
424
if ( testCases . Count > 0 ) {
301
425
var testCase = testCases [ 0 ] ;
302
426
testCases . RemoveAt ( 0 ) ;
0 commit comments