1 /++ 2 A module containing the single threaded runner 3 4 Copyright: © 2017 Szabo Bogdan 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Szabo Bogdan 7 +/ 8 module trial.executor.single; 9 10 public import trial.interfaces; 11 import trial.runner; 12 import std.datetime; 13 import trial.step; 14 import trial.stackresult; 15 16 /** 17 The default test executor runs test in sequential order in a single thread 18 */ 19 class DefaultExecutor : ITestExecutor, IStepLifecycleListener, IAttachmentListener 20 { 21 private 22 { 23 SuiteResult suiteResult; 24 TestResult testResult; 25 StepResult currentStep; 26 StepResult[] stepStack; 27 } 28 29 this() { 30 suiteResult = SuiteResult("unknown"); 31 } 32 33 /// Called when an attachment is ready 34 void attach(ref const Attachment attachment) { 35 if(currentStep is null) { 36 suiteResult.attachments ~= Attachment(attachment.name, attachment.file, attachment.mime); 37 return; 38 } 39 40 currentStep.attachments ~= Attachment(attachment.name, attachment.file, attachment.mime); 41 } 42 43 /// Add the step result and update the other listeners on every step 44 void begin(string suite, string test, ref StepResult step) 45 { 46 currentStep.steps ~= step; 47 stepStack ~= currentStep; 48 currentStep = step; 49 LifeCycleListeners.instance.update(); 50 } 51 52 /// Update the other listeners on every step 53 void end(string suite, string test, ref StepResult step) 54 { 55 currentStep = stepStack[stepStack.length - 1]; 56 stepStack = stepStack[0 .. $ - 1]; 57 LifeCycleListeners.instance.update(); 58 } 59 60 /// It does nothing 61 SuiteResult[] beginExecution(ref const(TestCase)[]) 62 { 63 return []; 64 } 65 66 /// Return the result for the last executed suite 67 SuiteResult[] endExecution() 68 { 69 if (suiteResult.begin == SysTime.fromUnixTime(0)) 70 { 71 return []; 72 } 73 74 LifeCycleListeners.instance.update(); 75 LifeCycleListeners.instance.end(suiteResult); 76 return [ suiteResult ]; 77 } 78 79 protected 80 { 81 /// Run a test case 82 void runTest(ref const(TestCase) testCase, TestResult testResult) { 83 try 84 { 85 testCase.func(); 86 testResult.status = TestResult.Status.success; 87 } 88 catch (PendingTestException) 89 { 90 testResult.status = TestResult.Status.pending; 91 } 92 catch (Throwable t) 93 { 94 testResult.status = TestResult.Status.failure; 95 testResult.throwable = t.toTestException; 96 } 97 } 98 99 /// Convert a test case to a test result 100 void createTestResult(const(TestCase) testCase) 101 { 102 testResult = testCase.toTestResult; 103 testResult.begin = Clock.currTime; 104 105 testResult.status = TestResult.Status.started; 106 currentStep = testResult; 107 108 stepStack = []; 109 110 Step.suite = testCase.suiteName; 111 Step.test = testCase.name; 112 113 LifeCycleListeners.instance.begin(testCase.suiteName, testResult); 114 115 runTest(testCase, testResult); 116 117 testResult.end = Clock.currTime; 118 119 LifeCycleListeners.instance.end(testCase.suiteName, testResult); 120 } 121 } 122 123 /// Execute a test case 124 SuiteResult[] execute(ref const(TestCase) testCase) 125 { 126 SuiteResult[] result; 127 LifeCycleListeners.instance.update(); 128 129 if (suiteResult.name != testCase.suiteName) 130 { 131 if (suiteResult.begin != SysTime.fromUnixTime(0)) 132 { 133 suiteResult.end = Clock.currTime; 134 LifeCycleListeners.instance.end(suiteResult); 135 result = [suiteResult]; 136 } 137 138 suiteResult = SuiteResult(testCase.suiteName, Clock.currTime, Clock.currTime); 139 LifeCycleListeners.instance.begin(suiteResult); 140 } 141 142 createTestResult(testCase); 143 suiteResult.tests ~= testResult; 144 currentStep = null; 145 LifeCycleListeners.instance.update(); 146 147 return result; 148 } 149 } 150 151 version(unittest) { 152 version(Have_fluent_asserts) { 153 import fluent.asserts; 154 } 155 } 156 157 /// Executing a test case that throws a PendingTestException should mark the test result 158 /// as pending instead of a failure 159 unittest { 160 auto old = LifeCycleListeners.instance; 161 LifeCycleListeners.instance = new LifeCycleListeners; 162 LifeCycleListeners.instance.add(new DefaultExecutor); 163 164 scope (exit) { 165 LifeCycleListeners.instance = old; 166 } 167 168 void test() { 169 throw new PendingTestException(); 170 } 171 172 auto testCase = const TestCase("Some.Suite", "test name", &test, []); 173 auto result = [testCase].runTests; 174 175 result.length.should.equal(1); 176 result[0].tests[0].status.should.equal(TestResult.Status.pending); 177 } 178 179 180 /// Executing a test case should set the right begin and end times 181 unittest { 182 import core.thread; 183 auto old = LifeCycleListeners.instance; 184 LifeCycleListeners.instance = new LifeCycleListeners; 185 LifeCycleListeners.instance.add(new DefaultExecutor); 186 187 scope (exit) { 188 LifeCycleListeners.instance = old; 189 } 190 191 void test() { 192 Thread.sleep(1.msecs); 193 } 194 195 auto testCase = const TestCase("Some.Suite", "test name", &test, []); 196 197 auto begin = Clock.currTime; 198 auto result = [ testCase ].runTests; 199 auto testResult = result[0].tests[0]; 200 201 testResult.begin.should.be.greaterThan(begin); 202 testResult.end.should.be.greaterThan(begin + 1.msecs); 203 }