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 }