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 }