[test] Pretty print object properties on assert failure

Because I don't get much out of "Object() != Object()"

Change-Id: I5a765b9cb0a272d30edcd834ec7b60d2fd03190b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3497352
Reviewed-by: Camillo Bruni <cbruni@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79370}
This commit is contained in:
Leszek Swirski 2022-03-04 14:39:17 +01:00 committed by V8 LUCI CQ
parent d05a38f9ad
commit 5704f86c58
3 changed files with 101 additions and 76 deletions

View File

@ -3,7 +3,7 @@ test/mjsunit/mjsunit.js:{NUMBER}: Failure: expected <not same as 1> found <1>
Stack: MjsUnitAssertionError
at assertNotSame *mjsunit.js {NUMBER}:{NUMBER}
at *%(basename)s 7:1
throw new MjsUnitAssertionError(message);
throw new MjsUnitAssertionError(
^
MjsUnitAssertionError
at assertNotSame *mjsunit.js {NUMBER}:{NUMBER}

View File

@ -3,7 +3,7 @@ test/mjsunit/mjsunit.js:{NUMBER}: Failure: expected <true> found <false>
Stack: MjsUnitAssertionError
at assertTrue *mjsunit.js {NUMBER}:{NUMBER}
at *%(basename)s 7:1
throw new MjsUnitAssertionError(message);
throw new MjsUnitAssertionError(
^
MjsUnitAssertionError
at assertTrue *mjsunit.js {NUMBER}:{NUMBER}

View File

@ -25,18 +25,34 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function MjsUnitAssertionError(message) {
this.message = message;
// Temporarily install a custom stack trace formatter and restore the
// previous value.
let prevPrepareStackTrace = Error.prepareStackTrace;
try {
Error.prepareStackTrace = MjsUnitAssertionError.prepareStackTrace;
// This allows fetching the stack trace using TryCatch::StackTrace.
this.stack = new Error("MjsUnitAssertionError").stack;
} finally {
Error.prepareStackTrace = prevPrepareStackTrace;
var MjsUnitAssertionError = class MjsUnitAssertionError {
#cached_message = undefined;
#message_func = undefined;
constructor(message_func) {
this.#message_func = message_func;
// Temporarily install a custom stack trace formatter and restore the
// previous value.
let prevPrepareStackTrace = Error.prepareStackTrace;
try {
Error.prepareStackTrace = MjsUnitAssertionError.prepareStackTrace;
// This allows fetching the stack trace using TryCatch::StackTrace.
this.stack = new Error("MjsUnitAssertionError").stack;
} finally {
Error.prepareStackTrace = prevPrepareStackTrace;
}
}
get message() {
if (this.#cached_message === undefined) {
this.#cached_message = this.#message_func();
}
return this.#cached_message
}
toString() {
return this.message + "\n\nStack: " + this.stack;
};
}
/*
@ -45,11 +61,6 @@ function MjsUnitAssertionError(message) {
* the f-word and ignore all other lines.
*/
MjsUnitAssertionError.prototype.toString = function () {
return this.message + "\n\nStack: " + this.stack;
};
// Expected and found values the same objects, or the same primitive
// values.
// For known primitive values, please use assertEquals.
@ -258,69 +269,82 @@ var prettyPrinted;
prettyPrinted = function prettyPrinted(value) {
switch (typeof value) {
case "string":
return JSONStringify(value);
case "bigint":
return String(value) + "n";
case "number":
if (value === 0 && (1 / value) < 0) return "-0";
// FALLTHROUGH.
case "boolean":
case "undefined":
case "function":
case "symbol":
return String(value);
case "object":
if (value === null) return "null";
var objectClass = classOf(value);
switch (objectClass) {
case "Number":
case "BigInt":
case "String":
case "Boolean":
case "Date":
return objectClass + "(" + prettyPrinted(ValueOf(value)) + ")";
case "RegExp":
return RegExpPrototypeToString.call(value);
case "Array":
var mapped = ArrayPrototypeMap.call(
value, prettyPrintedArrayElement);
var joined = ArrayPrototypeJoin.call(mapped, ",");
return "[" + joined + "]";
case "Uint8Array":
case "Int8Array":
case "Int16Array":
case "Uint16Array":
case "Uint32Array":
case "Int32Array":
case "Float32Array":
case "Float64Array":
var joined = ArrayPrototypeJoin.call(value, ",");
return objectClass + "([" + joined + "])";
case "Object":
break;
let visited = new Set();
function prettyPrint(value) {
try {
switch (typeof value) {
case "string":
return JSONStringify(value);
case "bigint":
return String(value) + "n";
case "number":
if (value === 0 && (1 / value) < 0) return "-0";
// FALLTHROUGH.
case "boolean":
case "undefined":
case "function":
case "symbol":
return String(value);
case "object":
if (value === null) return "null";
// Guard against re-visiting.
if (visited.has(value)) return "<...>";
visited.add(value);
var objectClass = classOf(value);
switch (objectClass) {
case "Number":
case "BigInt":
case "String":
case "Boolean":
case "Date":
return objectClass + "(" + prettyPrint(ValueOf(value)) + ")";
case "RegExp":
return RegExpPrototypeToString.call(value);
case "Array":
var mapped = ArrayPrototypeMap.call(
value, (v,i,array)=>{
if (v === undefined && !(i in array)) return "";
return prettyPrint(v, visited);
});
var joined = ArrayPrototypeJoin.call(mapped, ",");
return "[" + joined + "]";
case "Uint8Array":
case "Int8Array":
case "Int16Array":
case "Uint16Array":
case "Uint32Array":
case "Int32Array":
case "Float32Array":
case "Float64Array":
var joined = ArrayPrototypeJoin.call(value, ",");
return objectClass + "([" + joined + "])";
case "Object":
break;
default:
return objectClass + "(" + String(value) + ")";
}
// classOf() returned "Object".
var name = value.constructor?.name ?? "Object";
var pretty_properties = [];
for (let [k,v] of Object.entries(value)) {
ArrayPrototypePush.call(
pretty_properties, `${k}:${prettyPrint(v, visited)}`);
}
var joined = ArrayPrototypeJoin.call(pretty_properties, ",");
return `${name}({${joined}})`;
default:
return objectClass + "(" + String(value) + ")";
return "-- unknown value --";
}
// classOf() returned "Object".
var name = value.constructor.name;
if (name) return name + "()";
return "Object()";
default:
return "-- unknown value --";
} catch (e) {
// Guard against general exceptions (especially stack overflows).
return "<error>"
}
}
return prettyPrint(value);
}
function prettyPrintedArrayElement(value, index, array) {
if (value === undefined && !(index in array)) return "";
return prettyPrinted(value);
}
failWithMessage = function failWithMessage(message) {
throw new MjsUnitAssertionError(message);
throw new MjsUnitAssertionError(()=>message);
}
formatFailureText = function(expectedText, found, name_opt) {
@ -340,7 +364,8 @@ var prettyPrinted;
}
function fail(expectedText, found, name_opt) {
return failWithMessage(formatFailureText(expectedText, found, name_opt));
throw new MjsUnitAssertionError(
()=>formatFailureText(expectedText, found, name_opt));
}