1 module mocked.error;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.format;
7 import std.typecons;
8 
9 /**
10  * Constructs an $(D_PSYMBOL UnexpectedCallError).
11  *
12  * Params:
13  *     T = Object type.
14  *     Args = $(D_PARAM name)'s arguments.
15  *     name = Unexpected call name.
16  *     arguments = $(D_PARAM name)'s arguments.
17  *     file = File.
18  *     line = Line number.
19  *     nextInChain = The next error.
20  *
21  * Returns: $(D_PSYMBOL UnexpectedCallError).
22  */
23 UnexpectedCallError unexpectedCallError(T, Args...)(
24         string name, Args arguments,
25         string file = __FILE__, size_t line = __LINE__,
26         Throwable nextInChain = null)
27 {
28     string[] formattedArguments;
29 
30     static foreach (i, Arg; Args)
31     {
32         formattedArguments ~= to!string(arguments[i]);
33     }
34     return new UnexpectedCallError(formatName!T(name),
35             formattedArguments, file, line, nextInChain);
36 }
37 
38 private string formatName(T)(string name)
39 {
40     return format!"%s.%s"(T.stringof, name);
41 }
42 
43 /**
44  * Thrown when an unexpected call occurs.
45  */
46 final class UnexpectedCallError : Error
47 {
48     private string name;
49     private string[] arguments;
50 
51     /**
52      * Constructs an $(D_PSYMBOL UnexpectedCallError).
53      *
54      * Params:
55      *     name = Unexpected call name.
56      *     arguments = $(D_PARAM name)'s arguments.
57      *     file = File.
58      *     line = Line number.
59      *     nextInChain = The next error.
60      */
61     this(string name, string[] arguments,
62             string file = __FILE__, size_t line = __LINE__,
63             Throwable nextInChain = null) nothrow pure @safe
64     {
65         this.name = name;
66         this.arguments = arguments;
67 
68         super("Unexpected call", file, line, nextInChain);
69     }
70 
71     public override string toString() const
72     {
73         return format!"%s: %s(%(%s, %))\n\n---\n%s"(this.msg,
74                 this.name, this.arguments, this.info);
75     }
76 }
77 
78 /**
79  * Constructs an $(D_PSYMBOL UnexpectedArgumentError).
80  *
81  * $(D_PARAM arguments) contains both actual and expected arguments. First the
82  * actual arguments are given. The last argument in $(D_PARAM Args) is a
83  * $(D_PSYMBOL Maybe) containing the expected arguments.
84  *
85  * Params:
86  *     T = Object type.
87  *     Args = $(D_PARAM name)'s actual and expected arguments.
88  *     name = Unexpected call name.
89  *     arguments = $(D_PARAM name)'s actual and expected arguments.
90  *     file = File.
91  *     line = Line number.
92  *     nextInChain = The next error.
93  */
94 UnexpectedArgumentError unexpectedArgumentError(T, Args...)(
95         string name, Args arguments,
96         string file = __FILE__, size_t line = __LINE__,
97         Throwable nextInChain = null)
98 {
99     ExpectationPair[] formattedArguments;
100 
101     static foreach (i, Arg; Args[0 .. $ - 1])
102     {{
103         auto expected = to!string(arguments[$ - 1].get!i);
104         auto actual = to!string(arguments[i]);
105 
106         formattedArguments ~= ExpectationPair(actual, expected);
107     }}
108 
109     return new UnexpectedArgumentError(formatName!T(name),
110             formattedArguments, file, line, nextInChain);
111 }
112 
113 /// Pair containing the expected argument and the argument from the actual call.
114 alias ExpectationPair = Tuple!(string, "actual", string, "expected");
115 
116 /**
117  * Error thrown when a method has been called with arguments that don't match
118  * the expected ones.
119  */
120 final class UnexpectedArgumentError : Error
121 {
122     private string name;
123     private ExpectationPair[] arguments;
124 
125     /**
126      * Constructs an $(D_PSYMBOL UnexpectedArgumentError).
127      *
128      * Params:
129      *     name = Unexpected call name.
130      *     arguments = $(D_PARAM name)'s actual and expected arguments.
131      *     file = File.
132      *     line = Line number.
133      *     nextInChain = The next error.
134      */
135     this(string name, ExpectationPair[] arguments,
136             string file, size_t line, Throwable nextInChain) nothrow pure @safe
137     {
138         this.name = name;
139         this.arguments = arguments;
140 
141         super("Expectation failure", file, line, nextInChain);
142     }
143 
144     public override string toString() const
145     {
146         auto message = appender!string();
147 
148         message ~= format!"%s\n"(this.msg);
149 
150         auto actual = map!(argument => argument.actual)(arguments);
151         auto expected = map!(argument => argument.expected)(arguments);
152 
153         message ~= format!"  Expected: %s(%(%s, %))\n"(this.name, expected);
154         message ~= format!"  but got: %s(%(%s, %))\n"(this.name, actual);
155         message ~= format!"\n\n---\n%s"(this.info);
156 
157         return message.data;
158     }
159 }
160 
161 ExpectationViolationException expectationViolationException(T, MaybeArgs)(
162         string name,
163         MaybeArgs arguments, string file = __FILE__, size_t line = __LINE__,
164         Throwable nextInChain = null)
165 {
166     string[] formattedArguments;
167 
168     static foreach (i; 0 .. MaybeArgs.length)
169     {
170         formattedArguments ~= to!string(arguments.get!i);
171     }
172     
173     return new ExpectationViolationException(formatName!T(name),
174             formattedArguments, file, line, nextInChain);
175 }
176 
177 // Expected the method to be called n times, but called m times,
178 // where m < n.
179 // Same as unexpected call, but with expected arguments instead of the actual ones.
180 /**
181  * Thrown if a method was expected to be called, but wasn't.
182  */
183 final class ExpectationViolationException : Exception
184 {
185     string name;
186     string[] arguments;
187 
188     /**
189      * Constructs an $(D_PSYMBOL UnexpectedArgumentError).
190      */
191     this(string name, string[] arguments, string file, size_t line, Throwable nextInChain)
192     {
193         this.name = name;
194         this.arguments = arguments;
195 
196         super("Expected method not called", file, line, nextInChain);
197     }
198 
199     public override string toString() const
200     {
201         return format!"%s: %s(%(%s, %))\n\n---\n%s"(this.msg,
202                 this.name, this.arguments, this.info);
203     }
204 }