Mercurial > projects > mde
comparison mde/setup/Init.d @ 85:56c0ddd90193
Intermediate commit (not stable). Changes to init system.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 11 Sep 2008 11:33:51 +0100 |
parents | 3dfd934100f7 |
children | 79d816b3e2d2 |
comparison
equal
deleted
inserted
replaced
84:e0f1ec7fe73a | 85:56c0ddd90193 |
---|---|
12 | 12 |
13 You should have received a copy of the GNU General Public License | 13 You should have received a copy of the GNU General Public License |
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ | 14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
15 | 15 |
16 /************************************************************************************************** | 16 /************************************************************************************************** |
17 * Initialisation setup and exit cleanup module. | 17 * Initialisation and cleanup (shutdown) module. |
18 * | 18 * |
19 * This module provides an infrastructure for handling much of the initialisation and | 19 * Program startup follows this sequence: static this() functions, pre-init, init. |
20 * deinitialisation of the program. It does not, however, provide much of the (de)initialisation | 20 * Shutdown consists of: cleanup, post-cleanup, static ~this() functions. |
21 * code; with the exception of that for the logger. | 21 * |
22 * static this and ~this functions should not use any external resources, such as dynamically | |
23 * loaded libraries or files, and should not interact with other modules. They should be almost | |
24 * guaranteed not to fail. Preferably, they shouldn't involve large amounts of memory or | |
25 * processing, but this is not a requirement. | |
26 * | |
27 * Pre-init: init code written in this module. Generally only prerequisets of most other stages | |
28 * go here. | |
29 * | |
30 * Init: This is where init code from external modules gets hooked in. Each stage consists of an | |
31 * initialization function, a corresponding cleanup function, a state, and any dependencies (other | |
32 * init functions which must be run before this one). Init functions are run according to these | |
33 * dependencies, potentially threaded simultaeneously with other init functions. | |
34 * | |
35 * Cleanup: Cleanup happens similarly to init for all stages requiring shutdown (according to their | |
36 * state). The init dependencies are applied in reverse order (so if X depended on Y, Y's cleanup | |
37 * will not run until X's cleanup has completed), and again the functions may be threaded. | |
38 * | |
39 * Post-cleanup: like pre-init, this is code written in Init. | |
22 *************************************************************************************************/ | 40 *************************************************************************************************/ |
23 module mde.setup.Init; | 41 module mde.setup.Init; |
24 | 42 |
25 import mde.setup.init2; // This module is responsible for setting up some init functions. | 43 import mde.setup.InitStage; // Controls external delegates run by init |
26 import mde.setup.initFunctions; | |
27 import mde.setup.exception; | 44 import mde.setup.exception; |
28 | 45 |
29 import mde.lookup.Options; | 46 import mde.lookup.Options; |
30 import paths = mde.setup.paths; | 47 import paths = mde.setup.paths; |
31 import mde.exception; | 48 import mde.exception; // optionsLoadException |
32 | 49 |
33 // tango imports | 50 // tango imports |
34 import tango.core.Thread; | 51 import tango.core.Thread; |
52 import tango.core.sync.Condition; | |
35 import tango.core.Exception; | 53 import tango.core.Exception; |
36 import tango.stdc.stringz : fromStringz; | 54 import tango.util.container.LinkedList; |
55 | |
56 //import tango.stdc.stringz : fromStringz; | |
37 import tango.io.Console; // for printing command-line usage | 57 import tango.io.Console; // for printing command-line usage |
38 import TimeStamp = tango.text.convert.TimeStamp, tango.time.WallClock; // output date in log file | 58 import TimeStamp = tango.text.convert.TimeStamp, tango.time.WallClock; // output date in log file |
39 import tango.util.Arguments; | 59 import tango.util.Arguments; |
40 import tango.util.log.Log : Log, Logger, Level; | 60 import tango.util.log.Log; |
41 import tango.util.log.AppendConsole; | 61 import tango.util.log.AppendConsole; |
42 import tango.util.log.AppendFiles; | 62 import tango.util.log.AppendFiles; |
43 | 63 |
44 // Derelict imports | 64 // Derelict imports |
45 import derelict.opengl.gl; | 65 import derelict.opengl.gl; |
46 import derelict.sdl.sdl; | 66 import derelict.sdl.sdl; |
47 //import derelict.sdl.image; Was to be used... for now isn't | 67 //import derelict.sdl.image; Was to be used... for now isn't |
48 import derelict.freetype.ft; | 68 import derelict.freetype.ft; |
49 import derelict.util.exception; | 69 import derelict.util.exception; |
50 | 70 |
51 /** | 71 |
52 * Static CTOR | 72 /************************************************************************************************** |
53 * | |
54 * This should handle a minimal amount of functionality where useful. For instance, configuring the | |
55 * logger here and not in Init allows unittests to use the logger. | |
56 */ | |
57 static this() | |
58 { | |
59 Logger root = Log.root; | |
60 // Set the level here, but set it again once options have been loaded: | |
61 debug root.level(Logger.Trace); | |
62 else root.level(Logger.Info); | |
63 // Temporarily log to the console (until we've found paths and loaded options): | |
64 root.add(new AppendConsole); | |
65 } | |
66 static ~this() | |
67 { | |
68 } | |
69 | |
70 /** | |
71 * Init class | 73 * Init class |
72 * | 74 * |
73 * A scope class created at beginning of the program and destroyed at the end; thus the CTOR | 75 * A scope class created at beginning of the program and destroyed at the end; thus the CTOR |
74 * handles program initialisation and the DTOR handles program cleanup. | 76 * handles program initialisation and the DTOR handles program cleanup. |
75 */ | 77 *************************************************************************************************/ |
76 scope class Init | 78 scope class Init |
77 { | 79 { |
78 //private bool failure = false; // set true on failure during init, so that clean | |
79 private static Logger logger; | |
80 static this() { | 80 static this() { |
81 // Set up the logger temporarily (until pre-init): | |
82 Logger root = Log.root; | |
83 debug root.level(Logger.Trace); | |
84 else root.level(Logger.Info); | |
85 root.add(new AppendConsole); | |
86 | |
81 logger = Log.getLogger ("mde.setup.Init"); | 87 logger = Log.getLogger ("mde.setup.Init"); |
82 } | 88 } |
83 | 89 |
84 /** this() − initialisation | 90 /** this() − pre-init and init */ |
85 * | |
86 * Runs general initialisation code, in a threaded manner where this isn't difficult. | |
87 * If any init fails, cleanup is still handled by ~this(). | |
88 * | |
89 * Init order: 1. Pre-init (loads components needed by most init functions). 2. Dynamic library | |
90 * loading (load any dynamic libraries first, so that if loading fails nothing else need be | |
91 * done). 3. Init functions (threaded functions handling the rest of initialisation). | |
92 */ | |
93 /* In a single-threaded function this could be done with: | |
94 * scope(failure) cleanup; | |
95 * This won't work with a threaded init function since any threads completing succesfully will | |
96 * not clean-up, and a fixed list of clean-up functions cannot be used since clean-up functions | |
97 * must not run where the initialisation functions have failed. | |
98 * Hence a list of clean-up functions is built similarly to scope(failure) --- see addCleanupFct. | |
99 */ | |
100 this(char[][] cmdArgs) | 91 this(char[][] cmdArgs) |
101 { | 92 { |
102 debug logger.trace ("Init: starting"); | 93 /****************************************************************************************** |
103 | 94 * Pre-init - init code written in this module. |
104 //BEGIN Pre-init (stage init0) | 95 *****************************************************************************************/ |
96 debug logger.trace ("Init: starting pre-init"); | |
105 //FIXME: warn on invalid arguments, including base-path on non-Windows | 97 //FIXME: warn on invalid arguments, including base-path on non-Windows |
106 // But Arguments doesn't support this (in tango 0.99.6 and in r3563). | 98 // But Arguments doesn't support this (in tango 0.99.6 and in r3563). |
107 Arguments args; | 99 Arguments args; |
108 try { | 100 try { |
109 args = new Arguments(); | 101 args = new Arguments(); |
208 | 200 |
209 throw new InitException ("Loading dynamic libraries failed (see above)."); | 201 throw new InitException ("Loading dynamic libraries failed (see above)."); |
210 } | 202 } |
211 debug logger.trace ("Init: dynamic libraries loaded"); | 203 debug logger.trace ("Init: dynamic libraries loaded"); |
212 //END Load dynamic libraries | 204 //END Load dynamic libraries |
213 //END Pre-init | 205 |
214 | 206 |
215 | 207 /****************************************************************************************** |
216 //BEGIN Init (stages init2, init4) | 208 * Init − where init code from external modules gets hooked in. |
217 /* Call init functions. | 209 *****************************************************************************************/ |
218 * | 210 debug logger.trace ("Init: done pre-init, starting init stages"); |
219 * Current method is to try using threads, and on failure assume no threads were actually | 211 |
220 * created and run functions in a non-threaded manner. */ | 212 // Calculate reverse dependencies of stages: |
221 | 213 foreach (key,stage_p; stages) |
222 try { | 214 foreach (name; stage_p.depends) |
223 if (runStageThreaded (init)) runStageForward (init); | 215 stages[name].rdepends ~= key; |
224 } | 216 if (miscOpts.numThreads < 1 || miscOpts.numThreads > 64) // limit to a sensible number of threads |
225 catch (InitStageException) { // This init stage failed. | 217 miscOpts.set!(int)("numThreads", 4); // FIXME enforce limit in Options |
226 // FIXME: check DTOR still runs | 218 |
227 throw new InitException ("An init function failed (see above message(s))"); | 219 runStages!(true); // startup delegates |
228 } | |
229 //END Init | |
230 | 220 |
231 debug logger.trace ("Init: done"); | 221 debug logger.trace ("Init: done"); |
232 } | 222 } |
233 | 223 |
234 /** DTOR - runs cleanup functions. */ | 224 /** DTOR - runs cleanup functions. */ |
235 ~this() | 225 ~this() |
236 { | 226 { |
237 debug logger.trace ("Cleanup: starting"); | 227 debug logger.trace ("Cleanup: starting"); |
238 | 228 |
229 runStages!(false); // cleanup delegates | |
230 | |
239 Options.save(); // save options... do so here for now | 231 Options.save(); // save options... do so here for now |
240 | 232 |
241 // General cleanup: | 233 debug logger.trace ("Cleanup: done"); |
234 } | |
235 | |
236 // run init stages or cleanup if startup is false | |
237 private static void runStages(bool startup) () { | |
238 LinkedList!(InitStage*) toRun = new LinkedList!(InitStage*); | |
239 foreach (v; stages) { | |
240 // Filter only stages with the relevant delegate. It is not checked later that the | |
241 // delegate exists! | |
242 // If no init/cleanup function exists, implicit activation/deactivation can occur so | |
243 // that dependents/dependencies may activate/deactivate. | |
244 static if (startup) { | |
245 if (v.state == StageState.INACTIVE) { | |
246 if ((*v).init) | |
247 toRun.append (v); | |
248 else | |
249 v.state = StageState.ACTIVE; | |
250 } | |
251 } else { | |
252 if (v.state == StageState.ACTIVE) { | |
253 if (v.cleanup) | |
254 toRun.append (v); | |
255 else | |
256 v.state = StageState.INACTIVE; | |
257 } | |
258 } | |
259 } | |
260 debug logger.trace ("Added {} init/cleanup stages", toRun.size); | |
261 int numWorking = miscOpts.numThreads; | |
262 bool abortInit = false; | |
263 Mutex toRunM = new Mutex; // synchronization on toRun, numWorking | |
264 Condition toRunC = new Condition(toRunM); // used by threads waiting for remaining stages' dependencies to be met | |
265 | |
266 /* This is a threadable member function to run init stages. | |
267 * Operation follows: | |
268 * 1 Look for a stage to run: | |
269 * if found: | |
270 * notify waiting threads work may be available | |
271 * init stage | |
272 * goto 1 | |
273 * if not found: | |
274 * if any other threads are working, wait (dependencies may not be met yet) | |
275 * if no other threads are working, notify waiting threads and terminate (finished) | |
276 * When notified, threads start at 1. */ | |
277 void initThreadFct () { | |
278 debug logger.trace ("initThreadFct: starting"); | |
279 try { // created as a thread - must not throw exceptions | |
280 InitStage* stage; | |
281 | |
282 threadLoop: while (true) { // thread loops until a problem occurs or nothing else can be done | |
283 // Look for a job: | |
284 synchronized (toRunM) { | |
285 --numWorking; // stopped working: looking/waiting for a job | |
286 if (abortInit) break threadLoop; // something went wrong in another thread | |
287 static if (startup) | |
288 int num_rdepends = (stage is null) ? 0 : stage.rdepends.length; | |
289 else | |
290 int num_rdepends = (stage is null) ? 0 : stage.depends.length; | |
291 | |
292 getStage: while (true) { | |
293 auto it = toRun.iterator; // asserts if toRun is empty | |
294 itStages: while (it.next (stage)) { // get next element of toRun | |
295 static if (startup) { | |
296 foreach (d; stage.depends) | |
297 if (stages[d].state != StageState.ACTIVE) | |
298 continue itStages; // dependency isn't met (yet) | |
299 } else { | |
300 foreach (d; stage.rdepends) | |
301 if (stages[d].state != StageState.INACTIVE) | |
302 continue itStages; // reverse dependency isn't unmet (yet) | |
303 } | |
304 | |
305 // All dependencies met | |
306 it.remove; | |
307 break getStage; | |
308 } | |
309 | |
310 // No stage remaining with all dependencies met | |
311 if (!toRun.isEmpty && numWorking) // still some working so more dependencies may be met later | |
312 toRunC.wait; // wait until another thread finishes | |
313 else // no thread is working, so none of what's left is doable, or nothing's left | |
314 break threadLoop; | |
315 } | |
316 ++numWorking; // got a job! | |
317 if (num_rdepends > 2) // how many stages depended on the last one run? | |
318 toRunC.notifyAll(); // tell all waiting threads there may be work now | |
319 else if (num_rdepends == 2) | |
320 toRunC.notify(); // there's potentially work for this thread and one other | |
321 // else there won't be additional work so don't notify | |
322 } | |
323 | |
324 // Do a job: | |
325 try { | |
326 // FIXME - old stage start&finish trace messages - we don't have a name! | |
327 debug logger.trace ("InitStage {}: starting", stage); | |
328 static if (startup) | |
329 stage.state = (*stage).init(); // init is a property of a pointer (oh no!) | |
330 else | |
331 stage.state = stage.cleanup(); | |
332 debug logger.trace ("InitStage {}: completed; state: {}", stage, stage.state); | |
333 } catch (InitStageException e) { | |
334 debug logger.trace ("InitStage {}: failed", stage); | |
335 stage.state = e.state; | |
336 abortInit = true; | |
337 break threadLoop; | |
338 } catch (Exception e) { | |
339 debug logger.trace ("InitStage {}: failed", stage); | |
340 abortInit = true; | |
341 break threadLoop; | |
342 } | |
343 } | |
344 } catch (Exception e) { | |
345 logger.fatal ("Exception in initThreadFct: "~e.msg); | |
346 abortInit = true; | |
347 } | |
348 toRunC.notifyAll(); // Most likely if we're exiting, we should make sure others aren't waiting. | |
349 debug logger.trace ("initThreadFct: returning"); | |
350 return; | |
351 } | |
352 | |
353 // Start miscOpts.NumThreads - 1 threads: | |
242 try { | 354 try { |
243 if (runStageThreaded (cleanup)) runStageReverse (cleanup); | 355 ThreadGroup g = new ThreadGroup; |
244 } | 356 for (int i = miscOpts.numThreads; i > 1; --i) |
245 catch (InitStageException) { | 357 g.create (&initThreadFct); |
246 // Nothing else to do but report: | 358 initThreadFct(); // also run in current thread |
247 logger.error ("One or more cleanup functions failed!"); | 359 g.joinAll (false); // don't rethrow exceptions - there SHOULD NOT be any |
248 } | 360 } catch (ThreadException e) { |
249 | 361 logger.error ("Exception while using threads: "~e.msg); |
250 debug logger.trace ("Cleanup: done"); | 362 logger.error ("Disabling threads and attempting to continue."); |
251 } | 363 miscOpts.set!(int)("NumThreads", 1); // count includes current thread |
252 | 364 initThreadFct(); // try with just this thread |
253 | 365 } // any other exception will be caught in main() and abort program |
254 //BEGIN runStage... | 366 |
367 if (abortInit) | |
368 throw new InitException ("An init/cleanup function failed (see above message(s))"); | |
369 | |
370 foreach (stage; toRun) | |
371 logger.warn ("InitStage {}: was not run due to unmet dependencies", stage); | |
372 } | |
373 | |
255 private static { | 374 private static { |
256 /* The following three functions, runStage*, each run all functions in a stage in some order, | 375 Logger logger; |
257 * catching any exceptions thrown by the functions (although this isn't guaranteed for threads), | |
258 * and throw an InitStageException on initFailure. */ | |
259 | |
260 const LOG_IF_MSG = "Init function "; | |
261 const LOG_CF_MSG = "Cleanup function "; | |
262 const LOG_F_START = " - running"; | |
263 const LOG_F_END = " - completed"; | |
264 const LOG_F_BAD = " - failed"; | |
265 const LOG_F_FAIL = " - exception: "; | |
266 /* Runs all functions consecutively, first-to-last. | |
267 * If any function fails, halts immediately. */ | |
268 void runStageForward (InitStage s) { | |
269 foreach (func; s.funcs) { | |
270 if (initFailure) break; | |
271 try { | |
272 debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START); | |
273 func.func(); | |
274 debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END)); | |
275 } catch (Exception e) { | |
276 logger.fatal (LOG_IF_MSG ~ func.name ~ LOG_F_FAIL ~ | |
277 ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) ); | |
278 | |
279 setInitFailure(); | |
280 } | |
281 } | |
282 | |
283 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. | |
284 } | |
285 /* Runs all functions consecutively, last-to-first. | |
286 * If any function fails, continue until all have been run. */ | |
287 void runStageReverse (InitStage s) { | |
288 foreach_reverse (func; s.funcs) { | |
289 try { | |
290 debug logger.trace (LOG_CF_MSG ~ func.name ~ LOG_F_START); | |
291 func.func(); | |
292 debug logger.trace (LOG_CF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END)); | |
293 } catch (Exception e) { | |
294 logger.fatal (LOG_CF_MSG ~ func.name ~ LOG_F_FAIL ~ | |
295 ((e.msg is null || e.msg == "") ? "(no failure message)" : e.msg) ); | |
296 | |
297 setInitFailure(); | |
298 } | |
299 } | |
300 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. | |
301 } | |
302 /* Tries running functions in a threaded way. Returns false if successful, true if not but | |
303 * functions should be run without threads. */ | |
304 bool runStageThreaded (InitStage s) { | |
305 if (!miscOpts.useThreads) return true; // Use unthreaded route instead | |
306 | |
307 ThreadGroup tg; | |
308 try { // creating/starting threads could fail | |
309 tg = new ThreadGroup; | |
310 foreach (func; s.funcs) { // Start all threads | |
311 debug logger.trace (LOG_IF_MSG ~ func.name ~ LOG_F_START); | |
312 tg.create(func.func); | |
313 debug logger.trace (LOG_IF_MSG ~ func.name ~ (initFailure ? LOG_F_BAD : LOG_F_END)); | |
314 } | |
315 } catch (ThreadException e) { // Problem with threading; try without threads | |
316 logger.error ("Caught ThreadException while trying to create threads:"); | |
317 logger.error (e.msg); | |
318 logger.info ("Will disable threads and continue, assuming no threads were created."); | |
319 | |
320 miscOpts.set!(bool)("useThreads", false); // Disable threads entirely | |
321 return true; // Try again without threads | |
322 } | |
323 | |
324 /* Wait for all threads to complete. | |
325 * | |
326 * If something went wrong, we still need to do this before cleaning up. | |
327 */ | |
328 foreach (t; tg) { | |
329 try { | |
330 t.join (true); | |
331 } catch (Exception e) { | |
332 // Relying on catching exceptions thrown by other threads is a bad idea. | |
333 // Hence all threads should catch their own exceptions and return safely. | |
334 | |
335 logger.fatal ("Unhandled exception from Init function:"); | |
336 logger.fatal (e.msg); | |
337 | |
338 setInitFailure (); // abort (but join other threads first) | |
339 } | |
340 } | |
341 | |
342 if (initFailure) throw new InitStageException; // Problem running; abort and cleanup from here. | |
343 return false; // Done successfully | |
344 } | |
345 //END runStage... | |
346 | 376 |
347 void printUsage (char[] progName) { | 377 void printUsage (char[] progName) { |
348 Cout ("mde [no version]").newline; | 378 Cout ("mde [no version]").newline; |
349 Cout ("Usage:").newline; | 379 Cout ("Usage:").newline; |
350 Cout (progName ~ ` [options]`).newline; | 380 Cout (progName ~ ` [options]`).newline; |
364 --help, -h Print this message.`).newline; | 394 --help, -h Print this message.`).newline; |
365 } | 395 } |
366 } | 396 } |
367 | 397 |
368 debug (mdeUnitTest) unittest { | 398 debug (mdeUnitTest) unittest { |
369 /* Fake init and cleanup. Use unittest-specific init and cleanup InitStages to avoid | 399 logger.trace ("Starting unittest"); |
370 * messing other init/cleanup up. */ | 400 |
371 static InitStage initUT, cleanupUT; | 401 auto realInit = stages; // backup the real init stages |
372 | 402 stages = new typeof(stages); // an empty test-bed |
373 static bool initialised = false; | 403 |
374 static void cleanupFunc1 () { | 404 bool init1, init2, init3 = true; |
375 initialised = false; | 405 bool failed = false; // set if anything goes wrong |
376 } | 406 StageState s1InitReturns = StageState.ACTIVE; |
377 static void cleanupFunc2 () { | 407 addInitStage ("stg1", delegate StageState() { |
378 assert (initialised == true); | 408 init1 = true; |
379 } | 409 return s1InitReturns; |
380 | 410 }, delegate StageState() { |
381 static void initFunc () { | 411 init1 = false; |
382 initialised = true; | 412 return StageState.INACTIVE; |
383 cleanupUT.addFunc (&cleanupFunc1, "UT cleanup 1"); | 413 }); |
384 cleanupUT.addFunc (&cleanupFunc2, "UT cleanup 2"); | 414 addInitStage ("stg2", delegate StageState() { |
385 } | 415 if (!init1) failed = true; |
386 | 416 init2 = true; |
387 initUT.addFunc (&initFunc, "UT init"); | 417 return StageState.ACTIVE; |
388 | 418 }, delegate StageState() { |
389 runStageForward (initUT); | 419 if (!init1) failed = true; |
390 assert (initialised); | 420 init2 = false; |
391 | 421 return StageState.INACTIVE; |
392 runStageReverse (cleanupUT); | 422 }, ["stg1"]); |
393 assert (!initialised); | 423 InitStage s3; |
394 | 424 s3.init = delegate StageState() { |
425 throw new InitStageException (cast(StageState)7); // not a normal state, but good for a test | |
426 return StageState.ERROR; | |
427 }; | |
428 s3.cleanup = delegate StageState() { | |
429 if (!init1) failed = true; | |
430 init3 = false; | |
431 return StageState.INACTIVE; | |
432 }; | |
433 s3.depends = [ toStageName("stg1") ]; | |
434 s3.state = StageState.ACTIVE; // already active, so s3.init should not run (first time) | |
435 addInitStage ("stg3", &s3); | |
436 | |
437 // Stuff normally done in Init.this(): | |
438 // Calculate reverse dependencies of stages: | |
439 foreach (key,stage_p; stages) | |
440 foreach (name; stage_p.depends) | |
441 stages[name].rdepends ~= key; | |
442 if (miscOpts.numThreads < 1 || miscOpts.numThreads > 64) // limit to a sensible number of threads | |
443 miscOpts.set!(int)("numThreads", 4); // FIXME enforce limit in Options | |
444 | |
445 | |
446 // Run the above. | |
447 runStages!(true); | |
448 assert (init1); | |
449 assert (init2); | |
450 assert (!failed); | |
451 foreach (s; stages) | |
452 assert (s.state == StageState.ACTIVE); | |
453 | |
454 runStages!(false); | |
455 assert (!init1); | |
456 assert (!init2); | |
457 assert (!init3); | |
458 assert (!failed); | |
459 foreach (s; stages) | |
460 assert (s.state == StageState.INACTIVE); | |
461 | |
462 s1InitReturns = StageState.ERROR; | |
463 // Run again. S2/S3 shouldn't run, S1 won't shut down | |
464 runStages!(true); | |
465 assert (init1); | |
466 assert (!init2); | |
467 assert (!init3); | |
468 assert (!failed); | |
469 runStages!(false); | |
470 assert (init1); // S1 cleanup won't run | |
471 | |
472 stages[toStageName("stg1")].state = StageState.INACTIVE; // hack it back so we can still test | |
473 s1InitReturns = StageState.ACTIVE; | |
474 init1 = false; | |
475 try { | |
476 runStages!(true); | |
477 assert (false); // runStages should throw because s3.init runs now | |
478 } catch (Exception) {} | |
479 assert (init1); // s1.init should run first | |
480 assert (stages[toStageName("stg3")].state == cast(StageState)7); // set by the exception | |
481 | |
482 stages = realInit; // restore the real init stages | |
395 logger.info ("Unittest complete."); | 483 logger.info ("Unittest complete."); |
396 } | 484 } |
397 } | 485 } |