1 module dmocks.arguments; 2 3 import dmocks.dynamic; 4 import dmocks.util; 5 import std.algorithm; 6 import std.conv; 7 import std.range; 8 9 interface ArgumentsMatch 10 { 11 bool matches(Dynamic[] args); 12 string toString(); 13 string diffToString(Dynamic[] args); 14 } 15 16 //TODO: allow richer specification of arguments 17 class StrictArgumentsMatch : ArgumentsMatch 18 { 19 private Dynamic[] _arguments; 20 this(Dynamic[] args) 21 { 22 _arguments = args; 23 } 24 25 override bool matches(Dynamic[] args) 26 { 27 return _arguments == args; 28 } 29 30 override string toString() 31 { 32 return _arguments.formatArguments(); 33 } 34 35 override string diffToString(Dynamic[] args) 36 { 37 import std.format : format; 38 39 if (args.length != _arguments.length) 40 { 41 return toString ~ " " ~ yellow("(length mismatch)"); 42 } 43 string[] argInfos; 44 foreach (e; zip(_arguments, args)) 45 { 46 string info = e[0].typename ~ " " ~ e[0].toString(); 47 if (e[0] != e[1]) 48 { 49 if (e[0].toString() != e[1].toString()) 50 { 51 info ~= " " ~ yellow(format!"(but got %s)"(e[1])); 52 } 53 else 54 { 55 info ~= " " ~ yellow("(same toString but unequal)"); 56 } 57 } 58 argInfos ~= info; 59 } 60 return "(" ~ argInfos.join(", ") ~ ")"; 61 } 62 } 63 64 class ArgumentsTypeMatch : ArgumentsMatch 65 { 66 private Dynamic[] _arguments; 67 private bool delegate(Dynamic, Dynamic) _del; 68 this(Dynamic[] args, bool delegate(Dynamic, Dynamic) del) 69 { 70 _arguments = args; 71 _del = del; 72 } 73 74 override bool matches(Dynamic[] args) 75 { 76 import std.range; 77 78 if (args.length != _arguments.length) 79 return false; 80 81 foreach (e; zip(_arguments, args)) 82 { 83 if (e[0].type != e[1].type) 84 return false; 85 if (!_del(e[0], e[1])) 86 return false; 87 } 88 return true; 89 } 90 91 override string toString() 92 { 93 return "(" ~ _arguments.map!(a => a.typename).join(", ") ~ ")"; 94 } 95 96 override string diffToString(Dynamic[] args) 97 { 98 import std.format : format; 99 100 if (args.length != _arguments.length) 101 { 102 return toString ~ " " ~ yellow("(length mismatch)"); 103 } 104 string[] argInfos = null; 105 foreach (e; zip(_arguments, args)) 106 { 107 string info = e[0].typename; 108 if (e[0].type != e[1].type) 109 info ~= " " ~ yellow(format!"(but got %s)"(e[1].type)); 110 if (!_del(e[0], e[1])) 111 { 112 if (e[0].toString() != e[1].toString()) 113 { 114 import dshould.stringcmp : oneLineDiff; 115 116 auto diff = oneLineDiff(e[0].toString(), e[1].toString()); 117 info ~= " " 118 ~ yellow("(but comparator failed. ") 119 ~ format!"Info: expected.toString %s, got.toString %s"( 120 diff.original, diff.target) 121 ~ yellow(")"); 122 } 123 else 124 { 125 info ~= " " ~ yellow("(but comparator failed. Info: expected.toString == got.toString)"); 126 } 127 } 128 argInfos ~= info; 129 } 130 return "(" ~ argInfos.join(", ") ~ ")"; 131 } 132 } 133 134 interface IArguments 135 { 136 string toString(); 137 bool opEquals(Object other); 138 } 139 140 auto arguments(ARGS...)(ARGS args) 141 { 142 Dynamic[] res = new Dynamic[](ARGS.length); 143 foreach (i, arg; args) 144 { 145 res[i] = dynamic(arg); 146 } 147 return res; 148 } 149 150 auto formatArguments(Dynamic[] _arguments) 151 { 152 return "(" ~ _arguments.map!(a => a.typename ~ " " ~ a.toString()).join(", ") ~ ")"; 153 } 154 155 @("argument equality") 156 unittest 157 { 158 auto a = arguments!(int, real)(5, 9.7); 159 auto b = arguments!(int, real)(5, 9.7); 160 auto c = arguments!(int, real)(9, 1.1); 161 auto d = arguments!(int, float)(5, 9.7f); 162 163 assert(a == b); 164 assert(a != c); 165 assert(a != d); 166 } 167 168 @("argument toString") 169 unittest 170 { 171 auto a = arguments!(int, real)(5, 9.7); 172 a.formatArguments(); 173 }