|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +const{ inspect }=require('util'); |
| 4 | +const{codes: { |
| 5 | +ERR_INVALID_ARG_TYPE |
| 6 | +}}=require('internal/errors'); |
| 7 | + |
| 8 | +letblue=''; |
| 9 | +letgreen=''; |
| 10 | +letred=''; |
| 11 | +letwhite=''; |
| 12 | + |
| 13 | +constREADABLE_OPERATOR={ |
| 14 | +deepStrictEqual: 'Input A expected to strictly deep-equal input B', |
| 15 | +notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B', |
| 16 | +strictEqual: 'Input A expected to strictly equal input B', |
| 17 | +notStrictEqual: 'Input A expected to strictly not equal input B' |
| 18 | +}; |
| 19 | + |
| 20 | +functioncopyError(source){ |
| 21 | +constkeys=Object.keys(source); |
| 22 | +consttarget=Object.create(Object.getPrototypeOf(source)); |
| 23 | +for(constkeyofkeys){ |
| 24 | +target[key]=source[key]; |
| 25 | +} |
| 26 | +Object.defineProperty(target,'message',{value: source.message}); |
| 27 | +returntarget; |
| 28 | +} |
| 29 | + |
| 30 | +functioninspectValue(val){ |
| 31 | +// The util.inspect default values could be changed. This makes sure the |
| 32 | +// error messages contain the necessary information nevertheless. |
| 33 | +returninspect( |
| 34 | +val, |
| 35 | +{ |
| 36 | +compact: false, |
| 37 | +customInspect: false, |
| 38 | +depth: 1000, |
| 39 | +maxArrayLength: Infinity, |
| 40 | +// Assert compares only enumerable properties (with a few exceptions). |
| 41 | +showHidden: false, |
| 42 | +// Having a long line as error is better than wrapping the line for |
| 43 | +// comparison. |
| 44 | +breakLength: Infinity, |
| 45 | +// Assert does not detect proxies currently. |
| 46 | +showProxy: false |
| 47 | +} |
| 48 | +).split('\n'); |
| 49 | +} |
| 50 | + |
| 51 | +functioncreateErrDiff(actual,expected,operator){ |
| 52 | +varother=''; |
| 53 | +varres=''; |
| 54 | +varlastPos=0; |
| 55 | +varend=''; |
| 56 | +varskipped=false; |
| 57 | +constactualLines=inspectValue(actual); |
| 58 | +constexpectedLines=inspectValue(expected); |
| 59 | +constmsg=READABLE_OPERATOR[operator]+ |
| 60 | +`:\n${green}+ expected${white}${red}- actual${white}`; |
| 61 | +constskippedMsg=` ${blue}...${white} Lines skipped`; |
| 62 | + |
| 63 | +// Remove all ending lines that match (this optimizes the output for |
| 64 | +// readability by reducing the number of total changed lines). |
| 65 | +vara=actualLines[actualLines.length-1]; |
| 66 | +varb=expectedLines[expectedLines.length-1]; |
| 67 | +vari=0; |
| 68 | +while(a===b){ |
| 69 | +if(i++<2){ |
| 70 | +end=`\n ${a}${end}`; |
| 71 | +}else{ |
| 72 | +other=a; |
| 73 | +} |
| 74 | +actualLines.pop(); |
| 75 | +expectedLines.pop(); |
| 76 | +if(actualLines.length===0||expectedLines.length===0) |
| 77 | +break; |
| 78 | +a=actualLines[actualLines.length-1]; |
| 79 | +b=expectedLines[expectedLines.length-1]; |
| 80 | +} |
| 81 | +if(i>3){ |
| 82 | +end=`\n${blue}...${white}${end}`; |
| 83 | +skipped=true; |
| 84 | +} |
| 85 | +if(other!==''){ |
| 86 | +end=`\n ${other}${end}`; |
| 87 | +other=''; |
| 88 | +} |
| 89 | + |
| 90 | +constmaxLines=Math.max(actualLines.length,expectedLines.length); |
| 91 | +varprintedLines=0; |
| 92 | +varidentical=0; |
| 93 | +for(i=0;i<maxLines;i++){ |
| 94 | +// Only extra expected lines exist |
| 95 | +constcur=i-lastPos; |
| 96 | +if(actualLines.length<i+1){ |
| 97 | +if(cur>1&&i>2){ |
| 98 | +if(cur>4){ |
| 99 | +res+=`\n${blue}...${white}`; |
| 100 | +skipped=true; |
| 101 | +}elseif(cur>3){ |
| 102 | +res+=`\n ${expectedLines[i-2]}`; |
| 103 | +printedLines++; |
| 104 | +} |
| 105 | +res+=`\n ${expectedLines[i-1]}`; |
| 106 | +printedLines++; |
| 107 | +} |
| 108 | +lastPos=i; |
| 109 | +other+=`\n${green}+${white}${expectedLines[i]}`; |
| 110 | +printedLines++; |
| 111 | +// Only extra actual lines exist |
| 112 | +}elseif(expectedLines.length<i+1){ |
| 113 | +if(cur>1&&i>2){ |
| 114 | +if(cur>4){ |
| 115 | +res+=`\n${blue}...${white}`; |
| 116 | +skipped=true; |
| 117 | +}elseif(cur>3){ |
| 118 | +res+=`\n ${actualLines[i-2]}`; |
| 119 | +printedLines++; |
| 120 | +} |
| 121 | +res+=`\n ${actualLines[i-1]}`; |
| 122 | +printedLines++; |
| 123 | +} |
| 124 | +lastPos=i; |
| 125 | +res+=`\n${red}-${white}${actualLines[i]}`; |
| 126 | +printedLines++; |
| 127 | +// Lines diverge |
| 128 | +}elseif(actualLines[i]!==expectedLines[i]){ |
| 129 | +if(cur>1&&i>2){ |
| 130 | +if(cur>4){ |
| 131 | +res+=`\n${blue}...${white}`; |
| 132 | +skipped=true; |
| 133 | +}elseif(cur>3){ |
| 134 | +res+=`\n ${actualLines[i-2]}`; |
| 135 | +printedLines++; |
| 136 | +} |
| 137 | +res+=`\n ${actualLines[i-1]}`; |
| 138 | +printedLines++; |
| 139 | +} |
| 140 | +lastPos=i; |
| 141 | +res+=`\n${red}-${white}${actualLines[i]}`; |
| 142 | +other+=`\n${green}+${white}${expectedLines[i]}`; |
| 143 | +printedLines+=2; |
| 144 | +// Lines are identical |
| 145 | +}else{ |
| 146 | +res+=other; |
| 147 | +other=''; |
| 148 | +if(cur===1||i===0){ |
| 149 | +res+=`\n ${actualLines[i]}`; |
| 150 | +printedLines++; |
| 151 | +} |
| 152 | +identical++; |
| 153 | +} |
| 154 | +// Inspected object to big (Show ~20 rows max) |
| 155 | +if(printedLines>20&&i<maxLines-2){ |
| 156 | +return`${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n`+ |
| 157 | +`${blue}...${white}`; |
| 158 | +} |
| 159 | +} |
| 160 | + |
| 161 | +// Strict equal with identical objects that are not identical by reference. |
| 162 | +if(identical===maxLines){ |
| 163 | +// E.g., assert.deepStrictEqual(Symbol(), Symbol()) |
| 164 | +constbase=operator==='strictEqual' ? |
| 165 | +'Input objects identical but not reference equal:' : |
| 166 | +'Input objects not identical:'; |
| 167 | + |
| 168 | +// We have to get the result again. The lines were all removed before. |
| 169 | +constactualLines=inspectValue(actual); |
| 170 | + |
| 171 | +// Only remove lines in case it makes sense to collapse those. |
| 172 | +// TODO: Accept env to always show the full error. |
| 173 | +if(actualLines.length>30){ |
| 174 | +actualLines[26]=`${blue}...${white}`; |
| 175 | +while(actualLines.length>27){ |
| 176 | +actualLines.pop(); |
| 177 | +} |
| 178 | +} |
| 179 | + |
| 180 | +return`${base}\n\n${actualLines.join('\n')}\n`; |
| 181 | +} |
| 182 | +return`${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`; |
| 183 | +} |
| 184 | + |
| 185 | +classAssertionErrorextendsError{ |
| 186 | +constructor(options){ |
| 187 | +if(typeofoptions!=='object'||options===null){ |
| 188 | +thrownewERR_INVALID_ARG_TYPE('options','Object',options); |
| 189 | +} |
| 190 | +var{ |
| 191 | + actual, |
| 192 | + expected, |
| 193 | + message, |
| 194 | + operator, |
| 195 | + stackStartFn |
| 196 | +}=options; |
| 197 | + |
| 198 | +if(message!=null){ |
| 199 | +super(message); |
| 200 | +}else{ |
| 201 | +if(process.stdout.isTTY){ |
| 202 | +// Reset on each call to make sure we handle dynamically set environment |
| 203 | +// variables correct. |
| 204 | +if(process.stdout.getColorDepth()!==1){ |
| 205 | +blue='\u001b[34m'; |
| 206 | +green='\u001b[32m'; |
| 207 | +white='\u001b[39m'; |
| 208 | +red='\u001b[31m'; |
| 209 | +}else{ |
| 210 | +blue=''; |
| 211 | +green=''; |
| 212 | +white=''; |
| 213 | +red=''; |
| 214 | +} |
| 215 | +} |
| 216 | +// Prevent the error stack from being visible by duplicating the error |
| 217 | +// in a very close way to the original in case both sides are actually |
| 218 | +// instances of Error. |
| 219 | +if(typeofactual==='object'&&actual!==null&& |
| 220 | +typeofexpected==='object'&&expected!==null&& |
| 221 | +'stack'inactual&&actualinstanceofError&& |
| 222 | +'stack'inexpected&&expectedinstanceofError){ |
| 223 | +actual=copyError(actual); |
| 224 | +expected=copyError(expected); |
| 225 | +} |
| 226 | + |
| 227 | +if(operator==='deepStrictEqual'||operator==='strictEqual'){ |
| 228 | +super(createErrDiff(actual,expected,operator)); |
| 229 | +}elseif(operator==='notDeepStrictEqual'|| |
| 230 | +operator==='notStrictEqual'){ |
| 231 | +// In case the objects are equal but the operator requires unequal, show |
| 232 | +// the first object and say A equals B |
| 233 | +constres=inspectValue(actual); |
| 234 | +constbase=`Identical input passed to ${operator}:`; |
| 235 | + |
| 236 | +// Only remove lines in case it makes sense to collapse those. |
| 237 | +// TODO: Accept env to always show the full error. |
| 238 | +if(res.length>30){ |
| 239 | +res[26]=`${blue}...${white}`; |
| 240 | +while(res.length>27){ |
| 241 | +res.pop(); |
| 242 | +} |
| 243 | +} |
| 244 | + |
| 245 | +// Only print a single input. |
| 246 | +if(res.length===1){ |
| 247 | +super(`${base}${res[0]}`); |
| 248 | +}else{ |
| 249 | +super(`${base}\n\n${res.join('\n')}\n`); |
| 250 | +} |
| 251 | +}else{ |
| 252 | +letres=inspect(actual); |
| 253 | +letother=inspect(expected); |
| 254 | +if(res.length>128) |
| 255 | +res=`${res.slice(0,125)}...`; |
| 256 | +if(other.length>128) |
| 257 | +other=`${other.slice(0,125)}...`; |
| 258 | +super(`${res}${operator}${other}`); |
| 259 | +} |
| 260 | +} |
| 261 | + |
| 262 | +this.generatedMessage=!message; |
| 263 | +this.name='AssertionError [ERR_ASSERTION]'; |
| 264 | +this.code='ERR_ASSERTION'; |
| 265 | +this.actual=actual; |
| 266 | +this.expected=expected; |
| 267 | +this.operator=operator; |
| 268 | +Error.captureStackTrace(this,stackStartFn); |
| 269 | +} |
| 270 | +} |
| 271 | + |
| 272 | +module.exports={ |
| 273 | + AssertionError, |
| 274 | +errorCache: newMap() |
| 275 | +}; |
0 commit comments