1 module cucumber.result; 2 3 import core.time : Duration; 4 import std..string : format; 5 import std.typecons : Nullable; 6 7 import gherkin : Feature, Scenario, Step; 8 9 /// 10 enum Result 11 { 12 FAILED = "failed", 13 SKIPPED = "skipped", 14 UNDEFINED = "undefined", 15 PASSED = "passed" 16 } 17 18 alias FAILED = Result.FAILED; 19 alias SKIPPED = Result.SKIPPED; 20 alias UNDEFINED = Result.UNDEFINED; 21 alias PASSED = Result.PASSED; 22 23 /// 24 mixin template isResult() 25 { 26 mixin template isResult(string result) 27 { 28 import std..string : capitalize; 29 import std.uni : toUpper; 30 31 mixin("bool is%s() const { return this.result == Result.%s; }".format(result.capitalize, 32 result.toUpper)); 33 } 34 35 mixin isResult!"failed"; 36 mixin isResult!"skipped"; 37 mixin isResult!"undefined"; 38 mixin isResult!"passed"; 39 } 40 41 /// 42 struct ResultSummary 43 { 44 /// 45 ulong failed, skipped, undefined, passed; 46 47 /// 48 ulong total() const 49 { 50 return failed + skipped + undefined + passed; 51 } 52 53 /// 54 this(Result result) 55 { 56 switch (result) 57 { 58 case Result.FAILED: 59 this.failed = 1; 60 break; 61 case Result.SKIPPED: 62 this.skipped = 1; 63 break; 64 case Result.UNDEFINED: 65 this.undefined = 1; 66 break; 67 case Result.PASSED: 68 this.passed = 1; 69 break; 70 default: 71 // do nothing 72 } 73 } 74 75 /// 76 ResultSummary opOpAssign(string operator)(ResultSummary val) 77 { 78 if (operator == "+") 79 { 80 this.failed += val.failed; 81 this.skipped += val.skipped; 82 this.undefined += val.undefined; 83 this.passed += val.passed; 84 return this; 85 } 86 assert(0); 87 } 88 } 89 90 /// 91 struct RunResult 92 { 93 /// 94 int exitCode = 0; 95 /// 96 Duration time; 97 /// 98 ResultSummary[string] resultSummaries; 99 /// 100 FeatureResult[] featureResults; 101 102 /// 103 RunResult opOpAssign(string operator)(FeatureResult val) 104 { 105 if (operator == "+") 106 { 107 this.featureResults ~= val; 108 this.time += val.time; 109 if (!val.isPassed) 110 { 111 exitCode = 1; 112 } 113 if (!("scenarios" in resultSummaries)) 114 resultSummaries["scenarios"] = ResultSummary(); 115 if (!("steps" in resultSummaries)) 116 resultSummaries["steps"] = ResultSummary(); 117 118 resultSummaries["scenarios"] += val.resultSummary; 119 foreach (scenarioResult; val.scenarioResults) 120 { 121 resultSummaries["steps"] += scenarioResult.resultSummary; 122 } 123 return this; 124 } 125 assert(0); 126 } 127 } 128 129 /// 130 struct FeatureResult 131 { 132 /// 133 Feature feature; 134 /// 135 Result result = Result.PASSED; 136 /// 137 ScenarioResult[] scenarioResults; 138 /// 139 Duration time; 140 /// 141 ResultSummary resultSummary; 142 143 mixin isResult; 144 145 /// 146 FeatureResult opOpAssign(string operator)(ScenarioResult val) 147 { 148 if (operator == "+") 149 { 150 this.scenarioResults ~= val; 151 this.time += val.time; 152 if (!val.isPassed && this.isPassed) 153 { 154 this.result = Result.FAILED; 155 } 156 this.resultSummary += ResultSummary(val.result); 157 return this; 158 } 159 assert(0); 160 } 161 } 162 163 /// 164 struct ScenarioResult 165 { 166 /// 167 Scenario scenario; 168 /// 169 string location; 170 /// 171 Result result = Result.PASSED; 172 /// 173 StepResult[] stepResults; 174 /// 175 Duration time; 176 /// 177 ResultSummary resultSummary; 178 /// 179 Nullable!ulong exampleNumber; 180 181 mixin isResult; 182 183 /// 184 ScenarioResult opOpAssign(string operator)(StepResult val) 185 { 186 if (operator == "+") 187 { 188 this.stepResults ~= val; 189 this.time += val.time; 190 if (!val.isPassed && this.isPassed) 191 { 192 this.result = val.result; 193 } 194 this.resultSummary += ResultSummary(val.result); 195 return this; 196 } 197 assert(0); 198 } 199 } 200 201 /// 202 struct StepResult 203 { 204 /// 205 Step step; 206 /// 207 string location; 208 /// 209 Result result = Result.PASSED; 210 /// 211 Duration time; 212 /// 213 Nullable!Exception exception; 214 215 mixin isResult; 216 }