SERVER-101755 Add string substitution to assertion messages (#34437)

GitOrigin-RevId: 344c864a3ad16675ce9190e47ebfba143c741660
This commit is contained in:
Daniel Tabacaru 2025-04-04 15:51:03 +02:00 committed by MongoDB Bot
parent e3315cf7d7
commit a918fa85ba
15 changed files with 140 additions and 186 deletions

View File

@ -576,7 +576,7 @@ class TestExceptionExtraction(unittest.TestCase):
] ]
output = execute_resmoke(resmoke_args).stdout output = execute_resmoke(resmoke_args).stdout
expected = "The following tests failed (with exit code):\n buildscripts/tests/resmoke_end2end/failtestfiles/js_failure.js (253 Failure executing JS file)\n uncaught exception: Error: [true] != [false] are not equal" expected = "The following tests failed (with exit code):\n buildscripts/tests/resmoke_end2end/failtestfiles/js_failure.js (253 Failure executing JS file)\n uncaught exception: Error: [true] and [false] are not equal"
assert expected in output assert expected in output
def test_resmoke_fixture_error(self): def test_resmoke_fixture_error(self):

View File

@ -39,6 +39,7 @@ export default [
assert: true, assert: true,
doassert: true, doassert: true,
sortDoc: true, sortDoc: true,
formatErrorMsg: true,
// src/mongo/shell/bridge.d.ts // src/mongo/shell/bridge.d.ts
MongoBridge: true, MongoBridge: true,
@ -189,7 +190,6 @@ export default [
isString: true, isString: true,
printjson: true, printjson: true,
printjsononeline: true, printjsononeline: true,
stringifyErrorMessageAndAttributes: true,
toJsonForLog: true, toJsonForLog: true,
tojson: true, tojson: true,
tojsonObject: true, tojsonObject: true,

View File

@ -210,7 +210,7 @@ export const $config = (function() {
// become refined. Retrying swapping the zone range will allow us to target the // become refined. Retrying swapping the zone range will allow us to target the
// shard key in its refined state. // shard key in its refined state.
const newShardKeyField = this.newShardKeyFields[1]; const newShardKeyField = this.newShardKeyFields[1];
const errorMsg = stringifyErrorMessageAndAttributes(e); const errorMsg = formatErrorMsg(e.message, e.extraAttr);
if ((errorMsg.includes(newShardKeyField) && errorMsg.includes('are not equal')) || if ((errorMsg.includes(newShardKeyField) && errorMsg.includes('are not equal')) ||
(errorMsg.includes(newShardKeyField) && (errorMsg.includes(newShardKeyField) &&
errorMsg.includes('assert.eq() failed'))) { errorMsg.includes('assert.eq() failed'))) {

View File

@ -82,7 +82,7 @@ export const $config = extendWorkload($baseConfig, function($config, $super) {
// migrated back in. The particular error code is replaced with a more generic one, so this is // migrated back in. The particular error code is replaced with a more generic one, so this is
// identified by the failed migration's error message. // identified by the failed migration's error message.
$config.data.isMoveChunkErrorAcceptable = (err) => { $config.data.isMoveChunkErrorAcceptable = (err) => {
const errorMsg = stringifyErrorMessageAndAttributes(err); const errorMsg = formatErrorMsg(err.message, err.extraAttr);
return (errorMsg.includes("CommandFailed") || return (errorMsg.includes("CommandFailed") ||
errorMsg.includes("Documents in target range may still be in use") || errorMsg.includes("Documents in target range may still be in use") ||
// This error can occur when the test updates the shard key value of a document // This error can occur when the test updates the shard key value of a document

View File

@ -100,6 +100,7 @@ const expectedGlobalVars = [
"escape", "escape",
"eval", "eval",
"eventResumeTokenType", "eventResumeTokenType",
"formatErrorMsg",
"gc", "gc",
"getJSHeapLimitMB", "getJSHeapLimitMB",
"globalThis", "globalThis",
@ -110,7 +111,6 @@ const expectedGlobalVars = [
"isNumber", "isNumber",
"isObject", "isObject",
"isString", "isString",
"stringifyErrorMessageAndAttributes",
"parseFloat", "parseFloat",
"parseInt", "parseInt",
"print", "print",

View File

@ -176,7 +176,7 @@ ShardingTest.prototype.checkUUIDsConsistentAcrossCluster = function() {
} }
} }
} catch (e) { } catch (e) {
if (stringifyErrorMessageAndAttributes(e).indexOf("Unauthorized") < 0) { if (formatErrorMsg(e.message, e.extraAttr).indexOf("Unauthorized") < 0) {
throw e; throw e;
} }
jsTest.log.info("ignoring exception while checking UUID consistency across cluster", jsTest.log.info("ignoring exception while checking UUID consistency across cluster",

View File

@ -39,8 +39,9 @@ for (let config of invalidMixedVersionsToCheck) {
() => new ShardingTest({shouldFailInit: true, shards: config.shards, other: config.other})); () => new ShardingTest({shouldFailInit: true, shards: config.shards, other: config.other}));
assert.eq( assert.eq(
true, true,
err.message.includes( formatErrorMsg(err.message, err.extraAttr)
"Can only specify one of 'last-lts' and 'last-continuous' in binVersion, not both."), .includes(
"Can only specify one of 'last-lts' and 'last-continuous' in binVersion, not both."),
"Unexpected Error"); "Unexpected Error");
} }

View File

@ -793,17 +793,18 @@ tests.push(function assertJsonFormat() {
tests.push(function assertEqJsonFormat() { tests.push(function assertEqJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.eq(5, 2 + 2, "lorem ipsum"); assert.eq(5, 2 + 2, "lorem ipsum");
}, {msg: "assert.eq() failed : lorem ipsum", attr: {a: 5, b: 4}}); }, {msg: "[{a}] and [{b}] are not equal : lorem ipsum", attr: {a: 5, b: 4}});
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.eq(5, 2 + 2, "lorem ipsum", kAttr); assert.eq(5, 2 + 2, "lorem ipsum", kAttr);
}, {msg: "assert.eq() failed : lorem ipsum", attr: {a: 5, b: 4, ...kAttr}}); }, {msg: "[{a}] and [{b}] are not equal : lorem ipsum", attr: {a: 5, b: 4, ...kAttr}});
}); });
tests.push(function assertDocEqJsonFormat() { tests.push(function assertDocEqJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.docEq({msg: "hello"}, {msg: "goodbye"}, "lorem ipsum", kAttr); assert.docEq({msg: "hello"}, {msg: "goodbye"}, "lorem ipsum", kAttr);
}, { }, {
msg: "assert.docEq() failed : lorem ipsum", msg:
"expected document {expectedDoc} and actual document {actualDoc} are not equal : lorem ipsum",
attr: {expectedDoc: {msg: "hello"}, actualDoc: {msg: "goodbye"}, ...kAttr} attr: {expectedDoc: {msg: "hello"}, actualDoc: {msg: "goodbye"}, ...kAttr}
}); });
}); });
@ -812,7 +813,7 @@ tests.push(function assertSetEqJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.setEq(new Set([1, 2, 3]), new Set([4, 5]), "lorem ipsum", kAttr); assert.setEq(new Set([1, 2, 3]), new Set([4, 5]), "lorem ipsum", kAttr);
}, { }, {
msg: "assert.setEq() failed : lorem ipsum", msg: "expected set {expectedSet} and actual set {actualSet} are not equal : lorem ipsum",
attr: {expectedSet: [1, 2, 3], actualSet: [4, 5], ...kAttr} attr: {expectedSet: [1, 2, 3], actualSet: [4, 5], ...kAttr}
}); });
}); });
@ -821,7 +822,7 @@ tests.push(function assertSameMembersJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.sameMembers([1, 2], [1], "Oops!", assert._isDocEq, kAttr); assert.sameMembers([1, 2], [1], "Oops!", assert._isDocEq, kAttr);
}, { }, {
msg: "assert.sameMembers() failed : Oops!", msg: "{aArr} != {bArr} : Oops!",
attr: {aArr: [1, 2], bArr: [1], compareFn: "_isDocEq", ...kAttr} attr: {aArr: [1, 2], bArr: [1], compareFn: "_isDocEq", ...kAttr}
}); });
}); });
@ -830,7 +831,7 @@ tests.push(function assertFuzzySameMembersJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.fuzzySameMembers([{soccer: 42}], [{score: 42000}], ["score"], "Oops!", 4, kAttr); assert.fuzzySameMembers([{soccer: 42}], [{score: 42000}], ["score"], "Oops!", 4, kAttr);
}, { }, {
msg: "assert.sameMembers() failed : Oops!", msg: "{aArr} != {bArr} : Oops!",
attr: {aArr: [{soccer: 42}], bArr: [{score: 42000}], compareFn: "fuzzyCompare", ...kAttr} attr: {aArr: [{soccer: 42}], bArr: [{score: 42000}], compareFn: "fuzzyCompare", ...kAttr}
}); });
}); });
@ -838,14 +839,14 @@ tests.push(function assertFuzzySameMembersJsonFormat() {
tests.push(function assertNeqJsonFormat() { tests.push(function assertNeqJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.neq(42, 42, "Oops!", kAttr); assert.neq(42, 42, "Oops!", kAttr);
}, {msg: "assert.neq() failed : Oops!", attr: {a: 42, b: 42, ...kAttr}}); }, {msg: "[{a}] and [{b}] are equal : Oops!", attr: {a: 42, b: 42, ...kAttr}});
}); });
tests.push(function assertHasFieldsJsonFormat() { tests.push(function assertHasFieldsJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.hasFields({hello: "world"}, ["goodbye"], "Oops!", kAttr); assert.hasFields({hello: "world"}, ["goodbye"], "Oops!", kAttr);
}, { }, {
msg: "assert.hasFields() failed : Oops!", msg: "Not all of the values from {arr} were in {result} : Oops!",
attr: {result: {hello: "world"}, arr: ["goodbye"], ...kAttr} attr: {result: {hello: "world"}, arr: ["goodbye"], ...kAttr}
}); });
}); });
@ -853,20 +854,23 @@ tests.push(function assertHasFieldsJsonFormat() {
tests.push(function assertContainsJsonFormat() { tests.push(function assertContainsJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.contains(3, [14, 15, 926], "Oops!", kAttr); assert.contains(3, [14, 15, 926], "Oops!", kAttr);
}, {msg: "assert.contains() failed : Oops!", attr: {element: 3, arr: [14, 15, 926], ...kAttr}}); }, {
msg: "{element} was not in {arr} : Oops!",
attr: {element: 3, arr: [14, 15, 926], ...kAttr}
});
}); });
tests.push(function assertDoesNotContainJsonFormat() { tests.push(function assertDoesNotContainJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.doesNotContain(3, [3, 23], "Oops!", kAttr); assert.doesNotContain(3, [3, 23], "Oops!", kAttr);
}, {msg: "assert.doesNotContain() failed : Oops!", attr: {element: 3, arr: [3, 23], ...kAttr}}); }, {msg: "{element} is in {arr} : Oops!", attr: {element: 3, arr: [3, 23], ...kAttr}});
}); });
tests.push(function assertContainsPrefixJsonFormat() { tests.push(function assertContainsPrefixJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.containsPrefix("hello", ["hell", "help"], "Oops!", kAttr); assert.containsPrefix("hello", ["hell", "help"], "Oops!", kAttr);
}, { }, {
msg: "assert.containsPrefix() failed : Oops!", msg: "{prefix} was not a prefix in {arr} : Oops!",
attr: {prefix: "hello", arr: ["hell", "help"], ...kAttr} attr: {prefix: "hello", arr: ["hell", "help"], ...kAttr}
}); });
}); });
@ -888,7 +892,7 @@ tests.push(function assertSoonNoExceptJsonFormat() {
tests.push(function assertRetryJsonFormat() { tests.push(function assertRetryJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.retry(() => false, "Oops!", 2, 10, {runHangAnalyzer: false}, kAttr); assert.retry(() => false, "Oops!", 2, 10, {runHangAnalyzer: false}, kAttr);
}, {msg: "assert.retry() failed : Oops!", attr: {...kAttr}}); }, {msg: "Oops!", attr: {...kAttr}});
}); });
tests.push(function assertRetryNoExceptJsonFormat() { tests.push(function assertRetryNoExceptJsonFormat() {
@ -896,7 +900,7 @@ tests.push(function assertRetryNoExceptJsonFormat() {
assert.retryNoExcept(() => { assert.retryNoExcept(() => {
throw Error("disaster"); throw Error("disaster");
}, "Oops!", 2, 10, {runHangAnalyzer: false}, kAttr); }, "Oops!", 2, 10, {runHangAnalyzer: false}, kAttr);
}, {msg: "assert.retry() failed : Oops!", attr: {...kAttr}}); }, {msg: "Oops!", attr: {...kAttr}});
}); });
tests.push(function assertTimeJsonFormat() { tests.push(function assertTimeJsonFormat() {
@ -908,11 +912,16 @@ tests.push(function assertTimeJsonFormat() {
try { try {
assert.time(f, "Oops!", timeoutMS, {runHangAnalyzer: false}, kAttr); assert.time(f, "Oops!", timeoutMS, {runHangAnalyzer: false}, kAttr);
} catch (e) { } catch (e) {
// Override the 'timeMS' to make the test deterministic. // Override 'timeMS' to make the test deterministic.
e.extraAttr.timeMS = sleepTimeMS; e.extraAttr.timeMS = sleepTimeMS;
// Override 'diff' to make the test deterministic.
e.extraAttr.diff = sleepTimeMS;
throw e; throw e;
} }
}, {msg: "assert.time() failed : Oops!", attr: {timeMS: sleepTimeMS, timeoutMS, ...kAttr}}); }, {
msg: "assert.time failed : Oops!",
attr: {timeMS: sleepTimeMS, timeoutMS, function: f, diff: sleepTimeMS, ...kAttr}
});
}); });
tests.push(function assertThrowsJsonFormat() { tests.push(function assertThrowsJsonFormat() {
@ -929,7 +938,7 @@ tests.push(function assertThrowsWithCodeJsonFormat() {
throw err; throw err;
}, 42, [], "Oops!", kAttr); }, 42, [], "Oops!", kAttr);
}, { }, {
msg: "assert.throwsWithCode() failed : Oops!", msg: "[{code}] and [{expectedCode}] are not equal : Oops!",
attr: {code: 24, expectedCode: [42], ...kAttr} attr: {code: 24, expectedCode: [42], ...kAttr}
}); });
}); });
@ -941,7 +950,7 @@ tests.push(function assertDoesNotThrowJsonFormat() {
throw err; throw err;
}, [], "Oops!", kAttr); }, [], "Oops!", kAttr);
}, { }, {
msg: "assert.doesNotThrow() failed : Oops!", msg: "threw unexpected exception: {error} : Oops!",
attr: {error: {message: "disaster"}, ...kAttr} attr: {error: {message: "disaster"}, ...kAttr}
}); });
}); });
@ -950,8 +959,8 @@ tests.push(function assertCommandWorkedWrongArgumentTypeJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.commandWorked("cmd", "Oops!"); assert.commandWorked("cmd", "Oops!");
}, { }, {
msg: "expected result type 'object'" + msg:
" : unexpected result type given to assert.commandWorked()", "expected result type 'object', got '{resultType}', res='{result}' : unexpected result type given to assert.commandWorked()",
attr: {result: "cmd", resultType: "string"} attr: {result: "cmd", resultType: "string"}
}); });
}); });
@ -968,7 +977,8 @@ tests.push(function assertCommandWorkedJsonFormat() {
}; };
assert.commandWorked(res, "Oops!"); assert.commandWorked(res, "Oops!");
}, { }, {
msg: "command failed: unexpected error : Oops!", msg:
"command failed: {res} with original command request: {originalCommand} with errmsg: unexpected error : Oops!",
attr: { attr: {
res: { res: {
ok: 0, ok: 0,
@ -989,7 +999,7 @@ tests.push(function assertCommandFailedJsonFormat() {
const res = {ok: 1, _mongo: "connection to localhost:20000", _commandObj: {hello: 1}}; const res = {ok: 1, _mongo: "connection to localhost:20000", _commandObj: {hello: 1}};
assert.commandFailed(res, "Oops!"); assert.commandFailed(res, "Oops!");
}, { }, {
msg: "command worked when it should have failed : Oops!", msg: "command worked when it should have failed: {res} : Oops!",
attr: { attr: {
res: { res: {
ok: 1, ok: 1,
@ -1007,7 +1017,7 @@ tests.push(function assertCommandFailedWithCodeJsonFormat() {
const res = {ok: 1, _mongo: "connection to localhost:20000", _commandObj: {hello: 1}}; const res = {ok: 1, _mongo: "connection to localhost:20000", _commandObj: {hello: 1}};
assert.commandFailedWithCode(res, ErrorCodes.BadValue, "Oops!"); assert.commandFailedWithCode(res, ErrorCodes.BadValue, "Oops!");
}, { }, {
msg: "command worked when it should have failed : Oops!", msg: "command worked when it should have failed: {res} : Oops!",
attr: { attr: {
res: { res: {
ok: 1, ok: 1,
@ -1032,7 +1042,8 @@ tests.push(function assertCommandFailedWithWrongCodeJsonFormat() {
}; };
assert.commandFailedWithCode(res, ErrorCodes.NetworkTimeout, "Oops!"); assert.commandFailedWithCode(res, ErrorCodes.NetworkTimeout, "Oops!");
}, { }, {
msg: "command did not fail with any of the following codes [ 89 ] unexpected error : Oops!", msg:
"command did not fail with any of the following codes {expectedCode} {res}. errmsg: unexpected error : Oops!",
attr: { attr: {
res: { res: {
ok: 0, ok: 0,
@ -1053,7 +1064,7 @@ tests.push(function assertWriteOKJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
const res = {ok: 0}; const res = {ok: 0};
assert.writeOK(res, "Oops!"); assert.writeOK(res, "Oops!");
}, {msg: "unknown type of write result, cannot check ok : Oops!", attr: {res: {ok: 0}}}); }, {msg: "unknown type of write result, cannot check ok: {res} : Oops!", attr: {res: {ok: 0}}});
}); });
tests.push(function assertWriteErrorJsonFormat() { tests.push(function assertWriteErrorJsonFormat() {
@ -1062,7 +1073,7 @@ tests.push(function assertWriteErrorJsonFormat() {
new WriteResult({nRemoved: 0, writeErrors: [], upserted: []}, 3, {w: "majority"}); new WriteResult({nRemoved: 0, writeErrors: [], upserted: []}, 3, {w: "majority"});
assert.writeError(res, "Oops!"); assert.writeError(res, "Oops!");
}, { }, {
msg: "no write error : Oops!", msg: "no write error: {res} : Oops!",
attr: { attr: {
res: { res: {
ok: {"$undefined": true}, ok: {"$undefined": true},
@ -1083,7 +1094,8 @@ tests.push(function assertWriteErrorWithCodeJsonFormat() {
{nRemoved: 0, writeErrors: [writeError], upserted: []}, 3, {w: "majority"}); {nRemoved: 0, writeErrors: [writeError], upserted: []}, 3, {w: "majority"});
assert.writeErrorWithCode(res, ErrorCodes.BadValue, "Oops!"); assert.writeErrorWithCode(res, ErrorCodes.BadValue, "Oops!");
}, { }, {
msg: "found code(s) does not match any of the expected codes : Oops!", msg:
"found code(s) {writeErrorCodes} does not match any of the expected codes {expectedCode}. Original command response: {res} : Oops!",
attr: { attr: {
res: { res: {
ok: {"$undefined": true}, ok: {"$undefined": true},
@ -1102,20 +1114,20 @@ tests.push(function assertWriteErrorWithCodeJsonFormat() {
tests.push(function assertIsNullJsonFormat() { tests.push(function assertIsNullJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.isnull({ok: 1}, "Oops!", kAttr); assert.isnull({ok: 1}, "Oops!", kAttr);
}, {msg: "assert.isnull() failed : Oops!", attr: {value: {ok: 1}, ...kAttr}}); }, {msg: "supposed to be null, was: {value} : Oops!", attr: {value: {ok: 1}, ...kAttr}});
}); });
tests.push(function assertLTJsonFormat() { tests.push(function assertLTJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.lt(41, 18, "Oops!", kAttr); assert.lt(41, 18, "Oops!", kAttr);
}, {msg: "assert less than failed : Oops!", attr: {a: 41, b: 18, ...kAttr}}); }, {msg: "{a} is not less than {b} : Oops!", attr: {a: 41, b: 18, ...kAttr}});
}); });
tests.push(function assertBetweenJsonFormat() { tests.push(function assertBetweenJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.between(1, 15, 10, "Oops!", true, kAttr); assert.between(1, 15, 10, "Oops!", true, kAttr);
}, { }, {
msg: "assert.between() failed : Oops!", msg: "{b} is not between {a} and {c} : Oops!",
attr: {a: 1, b: 15, c: 10, inclusive: true, ...kAttr} attr: {a: 1, b: 15, c: 10, inclusive: true, ...kAttr}
}); });
}); });
@ -1123,18 +1135,20 @@ tests.push(function assertBetweenJsonFormat() {
tests.push(function assertCloseJsonFormat() { tests.push(function assertCloseJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.close(123.4567, 123.4678, "Oops!"); assert.close(123.4567, 123.4678, "Oops!");
}, {msg: "assert.close() failed : Oops!", attr: {a: 123.4567, b: 123.4678, places: 4}}); }, {
msg:
"123.4567 is not equal to 123.4678 within 4 places, absolute error: 0.011099999999999, relative error: 0.00008990198254118888 : Oops!"
});
}); });
tests.push(function assertCloseWithinMSJsonFormat() { tests.push(function assertCloseWithinMSJsonFormat() {
const dateForLog = (arg) => JSON.parse(JSON.stringify(arg)); const dateForLog = (arg) => JSON.parse(JSON.stringify(arg));
const date1 = new Date(); const date1 = Date.UTC(1970, 0, 1, 23, 59, 59, 999);
sleep(10); const date2 = date1 + 10;
const date2 = new Date();
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.closeWithinMS(date1, date2, "Oops!", 1, kAttr); assert.closeWithinMS(date1, date2, "Oops!", 1, kAttr);
}, { }, {
msg: "assert.closeWithinMS() failed : Oops!", msg: "86399999 is not equal to 86400009 within 1 millis, actual delta: 10 millis : Oops!",
attr: {a: dateForLog(date1), b: dateForLog(date2), deltaMS: 1, ...kAttr} attr: {a: dateForLog(date1), b: dateForLog(date2), deltaMS: 1, ...kAttr}
}); });
}); });
@ -1143,7 +1157,7 @@ tests.push(function assertIncludesJsonFormat() {
assertThrowsErrorWithJson(() => { assertThrowsErrorWithJson(() => {
assert.includes("farmacy", "ace", "Oops!", kAttr); assert.includes("farmacy", "ace", "Oops!", kAttr);
}, { }, {
msg: "assert.includes() failed : Oops!", msg: "string [{haystack}] does not include [{needle}] : Oops!",
attr: {haystack: "farmacy", needle: "ace", ...kAttr} attr: {haystack: "farmacy", needle: "ace", ...kAttr}
}); });
}); });

View File

@ -297,7 +297,7 @@ tests.push(function invalidResponsesAttemptToProvideInformationToCommandWorks()
const error = assert.throws(() => { const error = assert.throws(() => {
assert.commandWorked(invalidRes); assert.commandWorked(invalidRes);
}); });
const errorMsg = stringifyErrorMessageAndAttributes(error); const errorMsg = formatErrorMsg(error.message, error.extraAttr);
assert.gte(errorMsg.indexOf(invalidRes), 0); assert.gte(errorMsg.indexOf(invalidRes), 0);
assert.gte(errorMsg.indexOf(typeof invalidRes), 0); assert.gte(errorMsg.indexOf(typeof invalidRes), 0);
}); });
@ -310,7 +310,7 @@ tests.push(function invalidResponsesAttemptToProvideInformationCommandFailed() {
const error = assert.throws(() => { const error = assert.throws(() => {
assert.commandFailed(invalidRes); assert.commandFailed(invalidRes);
}); });
const errorMsg = stringifyErrorMessageAndAttributes(error); const errorMsg = formatErrorMsg(error.message, error.extraAttr);
assert.gte(errorMsg.indexOf(invalidRes), 0); assert.gte(errorMsg.indexOf(invalidRes), 0);
assert.gte(errorMsg.indexOf(typeof invalidRes), 0); assert.gte(errorMsg.indexOf(typeof invalidRes), 0);
}); });

View File

@ -45,5 +45,5 @@ const err = assert.throws(() => {
}); });
}); });
assert(/ResumableRangeDeleterDisabled/.test(stringifyErrorMessageAndAttributes(err)), err); assert(/ResumableRangeDeleterDisabled/.test(formatErrorMsg(err.message, err.extraAttr)), err);
reshardingTest.teardown(); reshardingTest.teardown();

View File

@ -56,7 +56,7 @@ function runStandaloneTest(stageRegex, pipeline, expectedCommand) {
const result = assert.throws(() => coll.aggregate(pipeline, {cursor: {batchSize: 2}})); const result = assert.throws(() => coll.aggregate(pipeline, {cursor: {batchSize: 2}}));
assert(isNetworkError(result)); assert(isNetworkError(result));
assert(stageRegex.test(stringifyErrorMessageAndAttributes(result)), assert(stageRegex.test(formatErrorMsg(result.message, result.extraAttr)),
`Error wasn't due to stage failing: ${result}`); `Error wasn't due to stage failing: ${result}`);
} }
} }

View File

@ -62,6 +62,17 @@ declare function assert(value: boolean | any, msg?: string | function | object,
*/ */
declare function sortDoc(doc: object): any declare function sortDoc(doc: object): any
/**
* Format error message by replacing occurrences of '{key}'s in 'msg' with 'value' in [key, value] pairs from 'attr'.
*
* @param msg Failure message.
* @param attr Additional attributes to be included in failure messages.
* @param serializeFn Additional function to serialize 'value' in [key, value] pairs from 'attr'.
*
* @returns Failure message.
*/
declare function formatErrorMsg(msg: string, attr?: object, serializeFn?: function): string
declare module assert { declare module assert {
/** /**

View File

@ -90,6 +90,13 @@ function _convertExceptionToReturnStatus(func, excMsg) {
return safeFunc; return safeFunc;
} }
function formatErrorMsg(msg, attr = {}, serializeFn = tojson) {
for (const [key, value] of Object.entries(attr)) {
msg = msg.replaceAll(`{${key}}`, serializeFn(value));
}
return msg;
}
assert = (function() { assert = (function() {
// Wrapping the helper function in an IIFE to avoid polluting the global namespace. // Wrapping the helper function in an IIFE to avoid polluting the global namespace.
@ -104,7 +111,7 @@ assert = (function() {
return msg; return msg;
} }
function _doassert(msg, prefix, attr, prefixOverride) { function _doassert(msg, prefix, attr) {
if (TestData?.logFormat === "json") { if (TestData?.logFormat === "json") {
if (attr?.res) { if (attr?.res) {
// Special handling for command reply a.k.a. 'res' parameters, as it might contain // Special handling for command reply a.k.a. 'res' parameters, as it might contain
@ -115,9 +122,9 @@ assert = (function() {
...(attr.res._mongo && {connection: attr.res._mongo}) ...(attr.res._mongo && {connection: attr.res._mongo})
}; };
} }
doassert(_buildAssertionMessage(msg, prefixOverride ?? prefix), attr); doassert(_buildAssertionMessage(msg, prefix), attr);
} }
doassert(_buildAssertionMessage(msg, prefix), attr?.res); doassert(_buildAssertionMessage(msg, formatErrorMsg(prefix, attr, tojson)), attr?.res);
} }
function _validateAssertionMessage(msg, attr) { function _validateAssertionMessage(msg, attr) {
@ -189,10 +196,7 @@ assert = (function() {
return; return;
} }
_doassert(msg, _doassert(msg, `[{a}] and [{b}] are not equal`, {a, b, ...attr});
`[${tojson(a)}] != [${tojson(b)}] are not equal`,
{a, b, ...attr},
"assert.eq() failed");
}; };
function _isDocEq(a, b) { function _isDocEq(a, b) {
@ -212,10 +216,8 @@ assert = (function() {
} }
_doassert(msg, _doassert(msg,
"expected document " + tojson(expectedDoc) + " and actual document " + "expected document {expectedDoc} and actual document {actualDoc} are not equal",
tojson(actualDoc) + " are not equal", {expectedDoc, actualDoc, ...attr});
{expectedDoc, actualDoc, ...attr},
"assert.docEq() failed");
}; };
/** /**
@ -228,10 +230,8 @@ assert = (function() {
const failAssertion = function() { const failAssertion = function() {
_doassert( _doassert(
msg, msg,
"expected set " + tojson(expectedSet) + " and actual set " + tojson(actualSet) + "expected set {expectedSet} and actual set {actualSet} are not equal",
" are not equal", {expectedSet: Array.from(expectedSet), actualSet: Array.from(actualSet), ...attr});
{expectedSet: Array.from(expectedSet), actualSet: Array.from(actualSet), ...attr},
"assert.setEq() failed");
}; };
if (expectedSet.size !== actualSet.size) { if (expectedSet.size !== actualSet.size) {
failAssertion(); failAssertion();
@ -253,10 +253,7 @@ assert = (function() {
_validateAssertionMessage(msg, attr); _validateAssertionMessage(msg, attr);
const failAssertion = function() { const failAssertion = function() {
_doassert(msg, _doassert(msg, "{aArr} != {bArr}", {aArr, bArr, compareFn: compareFn.name, ...attr});
tojson(aArr) + " != " + tojson(bArr),
{aArr, bArr, compareFn: compareFn.name, ...attr},
"assert.sameMembers() failed");
}; };
if (aArr.length !== bArr.length) { if (aArr.length !== bArr.length) {
@ -299,10 +296,7 @@ assert = (function() {
return; return;
} }
_doassert(msg, _doassert(msg, "[{a}] and [{b}] are equal", {a, b, ...attr});
`[${tojson(a)}] == [${tojson(b)}] are equal`,
{a, b, ...attr},
"assert.neq() failed");
}; };
assert.hasFields = function(result, arr, msg, attr) { assert.hasFields = function(result, arr, msg, attr) {
@ -318,10 +312,8 @@ assert = (function() {
} }
if (count != arr.length) { if (count != arr.length) {
_doassert(msg, _doassert(
"Not all of the values from " + tojson(arr) + " were in " + tojson(result), msg, "Not all of the values from {arr} were in {result}", {result, arr, ...attr});
{result, arr, ...attr},
"assert.hasFields() failed");
} }
}; };
@ -339,10 +331,7 @@ assert = (function() {
} }
} }
_doassert(msg, _doassert(msg, "{element} was not in {arr}", {element, arr, ...attr});
tojson(element) + " was not in " + tojson(arr),
{element, arr, ...attr},
"assert.contains() failed");
}; };
assert.doesNotContain = function(element, arr, msg, attr) { assert.doesNotContain = function(element, arr, msg, attr) {
@ -355,10 +344,7 @@ assert = (function() {
const match = comp == element || const match = comp == element ||
((comp != null && element != null) && friendlyEqual(comp, element)); ((comp != null && element != null) && friendlyEqual(comp, element));
if (match) { if (match) {
_doassert(msg, _doassert(msg, "{element} is in {arr}", {element, arr, ...attr});
tojson(element) + " is in " + tojson(arr),
{element, arr, ...attr},
"assert.doesNotContain() failed");
} }
} }
}; };
@ -382,10 +368,7 @@ assert = (function() {
} }
} }
_doassert(msg, _doassert(msg, "{prefix} was not a prefix in {arr}", {prefix, arr, ...attr});
tojson(prefix) + " was not a prefix in " + tojson(arr),
{prefix, arr, ...attr},
"assert.containsPrefix() failed");
}; };
/* /*
@ -500,7 +483,7 @@ assert = (function() {
print(msg + " Running hang analyzer from assert.retry."); print(msg + " Running hang analyzer from assert.retry.");
MongoRunner.runHangAnalyzer(); MongoRunner.runHangAnalyzer();
} }
_doassert(msg, null, attr, "assert.retry() failed"); _doassert(msg, null, attr);
}; };
/* /*
@ -556,8 +539,7 @@ assert = (function() {
return res; return res;
} }
const msgPrefix = const msgPrefix = "assert.time failed";
"assert.time failed timeout " + timeout + "ms took " + diff + "ms : " + f + ", msg";
msg = _buildAssertionMessage(msg); msg = _buildAssertionMessage(msg);
if (runHangAnalyzer) { if (runHangAnalyzer) {
msg = msg + " The hang analyzer is automatically called in assert.time functions. " + msg = msg + " The hang analyzer is automatically called in assert.time functions. " +
@ -567,8 +549,7 @@ assert = (function() {
print(msg + " Running hang analyzer from assert.time."); print(msg + " Running hang analyzer from assert.time.");
MongoRunner.runHangAnalyzer(); MongoRunner.runHangAnalyzer();
} }
_doassert( _doassert(msg, msgPrefix, {timeMS: diff, timeoutMS: timeout, function: f, diff, ...attr});
msg, msgPrefix, {timeMS: diff, timeoutMS: timeout, ...attr}, "assert.time() failed");
}; };
function assertThrowsHelper(func, params) { function assertThrowsHelper(func, params) {
@ -649,9 +630,8 @@ assert = (function() {
} }
if (!expectedCode.some((ec) => error.code == ec)) { if (!expectedCode.some((ec) => error.code == ec)) {
_doassert(msg, _doassert(msg,
`[${tojson(error.code)}] != [${tojson(expectedCode)}] are not equal`, `[{code}] and [{expectedCode}] are not equal`,
{code: error.code, expectedCode, ...attr}, {code: error.code, expectedCode, ...attr});
"assert.throwsWithCode() failed");
} }
return error; return error;
}; };
@ -666,9 +646,8 @@ assert = (function() {
if (error) { if (error) {
const {code, message} = error; const {code, message} = error;
_doassert(msg, _doassert(msg,
"threw unexpected exception: " + error, "threw unexpected exception: {error}",
{error: {...(code && {code}), ...(message && {message})}, ...attr}, {error: {...(code && {code}), ...(message && {message})}, ...attr});
"assert.doesNotThrow() failed");
} }
return res; return res;
@ -722,9 +701,8 @@ assert = (function() {
function _validateCommandResponse(res, assertionName) { function _validateCommandResponse(res, assertionName) {
if (typeof res !== "object") { if (typeof res !== "object") {
_doassert(`unexpected result type given to assert.${assertionName}()`, _doassert(`unexpected result type given to assert.${assertionName}()`,
`expected result type 'object', got '${typeof res}', res='${res}'`, `expected result type 'object', got '{resultType}', res='{result}'`,
{result: res, resultType: typeof res}, {result: res, resultType: typeof res});
"expected result type 'object'");
} }
} }
@ -776,21 +754,18 @@ assert = (function() {
// Keep this as a function so we don't call tojson if not necessary. // Keep this as a function so we don't call tojson if not necessary.
const makeFailPrefix = (res) => { const makeFailPrefix = (res) => {
let prefix = "command failed: " + tojson(res); let prefix = "command failed: {res}";
if (typeof res._commandObj === "object" && res._commandObj !== null) { if (typeof res._commandObj === "object" && res._commandObj !== null) {
prefix += " with original command request: " + tojson(res._commandObj); prefix += " with original command request: {originalCommand}";
} }
if (typeof res._mongo === "object" && res._mongo !== null) { if (typeof res._mongo === "object" && res._mongo !== null) {
prefix += " on connection: " + res._mongo; prefix += " on connection: {connection}";
}
if (res.hasOwnProperty("errmsg")) {
prefix += ` with errmsg: ${res.errmsg}`;
} }
return prefix; return prefix;
}; };
const makeFailPrefixOverride = (res) => {
if (res.hasOwnProperty("errmsg")) {
return `command failed: ${res.errmsg}`;
}
return "command failed";
};
if (_isWriteResultType(res)) { if (_isWriteResultType(res)) {
// These can only contain write errors, not command errors. // These can only contain write errors, not command errors.
@ -801,7 +776,9 @@ assert = (function() {
// A WriteCommandError implies ok:0. // A WriteCommandError implies ok:0.
// Error objects may have a `code` property added (e.g. // Error objects may have a `code` property added (e.g.
// DBCollection.prototype.mapReduce) without a `ok` property. // DBCollection.prototype.mapReduce) without a `ok` property.
_doassert(msg, makeFailPrefix(res), {res}, makeFailPrefixOverride(res)); _doassert(msg,
makeFailPrefix(res),
{res, originalCommand: res._commandObj, connection: res._mongo});
} else if (res.hasOwnProperty("ok")) { } else if (res.hasOwnProperty("ok")) {
// Handle raw command responses or cases like MapReduceResult which extend command // Handle raw command responses or cases like MapReduceResult which extend command
// response. // response.
@ -810,16 +787,15 @@ assert = (function() {
ignoreWriteConcernErrors: ignoreWriteConcernErrors ignoreWriteConcernErrors: ignoreWriteConcernErrors
})) { })) {
_runHangAnalyzerForSpecificFailureTypes(res); _runHangAnalyzerForSpecificFailureTypes(res);
_doassert(msg, makeFailPrefix(res), {res}, makeFailPrefixOverride(res)); _doassert(msg,
makeFailPrefix(res),
{res, originalCommand: res._commandObj, connection: res._mongo});
} }
} else if (res.hasOwnProperty("acknowledged")) { } else if (res.hasOwnProperty("acknowledged")) {
// CRUD api functions return plain js objects with an acknowledged property. // CRUD api functions return plain js objects with an acknowledged property.
// no-op. // no-op.
} else { } else {
_doassert(msg, _doassert(msg, "unknown type of result, cannot check ok: {res}", {res});
"unknown type of result, cannot check ok: " + tojson(res),
{res},
"unknown type of result");
} }
return res; return res;
} }
@ -836,30 +812,21 @@ assert = (function() {
// Keep this as a function so we don't call tojson if not necessary. // Keep this as a function so we don't call tojson if not necessary.
const makeFailPrefix = (res) => { const makeFailPrefix = (res) => {
return "command worked when it should have failed: " + tojson(res);
};
const makeFailPrefixOverride = (res) => {
if (res.hasOwnProperty("errmsg")) { if (res.hasOwnProperty("errmsg")) {
return `command worked when it should have failed: ${res.errmsg}`; return `command worked when it should have failed: {res}. errmsg: ${res.errMsg}`;
} }
return "command worked when it should have failed"; return "command worked when it should have failed: {res}";
}; };
const makeFailCodePrefix = (res, expectedCode) => { const makeFailCodePrefix = (res, expectedCode) => {
return (expectedCode !== assert._kAnyErrorCode)
? "command did not fail with any of the following codes " + tojson(expectedCode) +
" " + tojson(res)
: null;
};
const makeFailCodePrefixOverride = (res, expectedCode) => {
if (res.hasOwnProperty("errmsg")) { if (res.hasOwnProperty("errmsg")) {
return (expectedCode !== assert._kAnyErrorCode) return (expectedCode !== assert._kAnyErrorCode)
? "command did not fail with any of the following codes " + ? `command did not fail with any of the following codes {expectedCode} {res}. errmsg: ${
tojson(expectedCode) + " " + res.errmsg res.errmsg}`
: null; : null;
} }
return (expectedCode !== assert._kAnyErrorCode) return (expectedCode !== assert._kAnyErrorCode)
? "command did not fail with any of the following codes " + tojson(expectedCode) ? "command did not fail with any of the following codes {expectedCode} {res}"
: null; : null;
}; };
@ -872,20 +839,14 @@ assert = (function() {
// DBCollection.prototype.mapReduce) without a `ok` property. // DBCollection.prototype.mapReduce) without a `ok` property.
if (expectedCode !== assert._kAnyErrorCode) { if (expectedCode !== assert._kAnyErrorCode) {
if (!res.hasOwnProperty("code") || !expectedCode.includes(res.code)) { if (!res.hasOwnProperty("code") || !expectedCode.includes(res.code)) {
_doassert(msg, _doassert(msg, makeFailCodePrefix(res, expectedCode), {res, expectedCode});
makeFailCodePrefix(res, expectedCode),
{res, expectedCode},
makeFailCodePrefixOverride(res, expectedCode));
} }
} }
} else if (res.hasOwnProperty("ok")) { } else if (res.hasOwnProperty("ok")) {
// Handle raw command responses or cases like MapReduceResult which extend command // Handle raw command responses or cases like MapReduceResult which extend command
// response. // response.
if (_rawReplyOkAndNoWriteErrors(res)) { if (_rawReplyOkAndNoWriteErrors(res)) {
_doassert(msg, _doassert(msg, makeFailPrefix(res), {res});
makeFailPrefix(res, expectedCode),
{res},
makeFailPrefixOverride(res, expectedCode));
} }
if (expectedCode !== assert._kAnyErrorCode) { if (expectedCode !== assert._kAnyErrorCode) {
@ -900,23 +861,14 @@ assert = (function() {
if (!foundCode) { if (!foundCode) {
_runHangAnalyzerForSpecificFailureTypes(res); _runHangAnalyzerForSpecificFailureTypes(res);
_doassert(msg, _doassert(msg, makeFailCodePrefix(res, expectedCode), {res, expectedCode});
makeFailCodePrefix(res, expectedCode),
{res, expectedCode},
makeFailCodePrefixOverride(res, expectedCode));
} }
} }
} else if (res.hasOwnProperty("acknowledged")) { } else if (res.hasOwnProperty("acknowledged")) {
// CRUD api functions return plain js objects with an acknowledged property. // CRUD api functions return plain js objects with an acknowledged property.
_doassert(msg, _doassert(msg, makeFailPrefix(res), {res});
makeFailPrefix(res, expectedCode),
{res},
makeFailPrefixOverride(res, expectedCode));
} else { } else {
_doassert(msg, _doassert(msg, "unknown type of result, cannot check error: {res}", {res});
"unknown type of result, cannot check error: " + tojson(res),
{res},
"unknown type of result");
} }
return res; return res;
} }
@ -996,7 +948,7 @@ assert = (function() {
if (errMsg) { if (errMsg) {
_runHangAnalyzerForSpecificFailureTypes(res); _runHangAnalyzerForSpecificFailureTypes(res);
_doassert(msg, errMsg + ": " + tojson(res), {res}, errMsg); _doassert(msg, errMsg + ": {res}", {res}, errMsg);
} }
return res; return res;
@ -1047,7 +999,7 @@ assert = (function() {
if (errMsg) { if (errMsg) {
_runHangAnalyzerForSpecificFailureTypes(res); _runHangAnalyzerForSpecificFailureTypes(res);
_doassert(msg, errMsg + ": " + tojson(res), {res}, errMsg); _doassert(msg, errMsg + ": {res}", {res});
} }
if (expectedCode !== assert._kAnyErrorCode) { if (expectedCode !== assert._kAnyErrorCode) {
@ -1056,14 +1008,11 @@ assert = (function() {
} }
const found = expectedCode.some((ec) => writeErrorCodes.has(ec)); const found = expectedCode.some((ec) => writeErrorCodes.has(ec));
if (!found) { if (!found) {
errMsg = "found code(s) " + tojson(Array.from(writeErrorCodes)) + errMsg =
" does not match any of the expected codes " + tojson(expectedCode) + "found code(s) {writeErrorCodes} does not match any of the expected codes {expectedCode}. Original command response: {res}";
". Original command response: " + tojson(res);
_runHangAnalyzerForSpecificFailureTypes(res); _runHangAnalyzerForSpecificFailureTypes(res);
_doassert(msg, _doassert(
errMsg, msg, errMsg, {res, expectedCode, writeErrorCodes: Array.from(writeErrorCodes)});
{res, expectedCode, writeErrorCodes: Array.from(writeErrorCodes)},
"found code(s) does not match any of the expected codes");
} }
} }
@ -1077,10 +1026,7 @@ assert = (function() {
return; return;
} }
_doassert(msg, _doassert(msg, "supposed to be null, was: {value}", {value, ...attr});
"supposed to be null, was: " + tojson(value),
{value, ...attr},
"assert.isnull() failed");
}; };
function _shouldUseBsonWoCompare(a, b) { function _shouldUseBsonWoCompare(a, b) {
@ -1117,10 +1063,7 @@ assert = (function() {
return; return;
} }
_doassert(msg, _doassert(msg, "{a} is not " + description + " {b}", {a, b, ...attr});
a + " is not " + description + " " + b,
{a, b, ...attr},
`assert ${description} failed`);
} }
assert.lt = function(a, b, msg, attr) { assert.lt = function(a, b, msg, attr) {
@ -1158,10 +1101,7 @@ assert = (function() {
return; return;
} }
_doassert(msg, _doassert(msg, "{b} is not between {a} and {c}", {a, b, c, inclusive, ...attr});
b + " is not between " + a + " and " + c,
{a, b, c, inclusive, ...attr},
"assert.between() failed");
}; };
assert.betweenIn = function(a, b, c, msg, attr) { assert.betweenIn = function(a, b, c, msg, attr) {
@ -1193,7 +1133,7 @@ assert = (function() {
assert.close = function(a, b, msg, places = 4) { assert.close = function(a, b, msg, places = 4) {
const [isClose, errMsg] = _isClose(a, b, places); const [isClose, errMsg] = _isClose(a, b, places);
if (!isClose) { if (!isClose) {
_doassert(msg, errMsg, {a, b, places}, "assert.close() failed"); _doassert(msg, errMsg);
} }
}; };
@ -1240,10 +1180,7 @@ assert = (function() {
"actual delta: " + actualDelta + " millis"; "actual delta: " + actualDelta + " millis";
const forLog = (arg) => arg instanceof Date ? JSON.parse(JSON.stringify(arg)) : arg; const forLog = (arg) => arg instanceof Date ? JSON.parse(JSON.stringify(arg)) : arg;
_doassert(msg, _doassert(msg, msgPrefix, {a: forLog(a), b: forLog(b), deltaMS, ...attr});
msgPrefix,
{a: forLog(a), b: forLog(b), deltaMS, ...attr},
"assert.closeWithinMS() failed");
}; };
assert.includes = function(haystack, needle, msg, attr) { assert.includes = function(haystack, needle, msg, attr) {
@ -1251,8 +1188,8 @@ assert = (function() {
return; return;
} }
const prefix = `string [${haystack}] does not include [${needle}]`; const prefix = "string [{haystack}] does not include [{needle}]";
_doassert(msg, prefix, {haystack, needle, ...attr}, "assert.includes() failed"); _doassert(msg, prefix, {haystack, needle, ...attr});
}; };
assert.noAPIParams = function(cmdOptions) { assert.noAPIParams = function(cmdOptions) {

View File

@ -7,7 +7,6 @@ declare function isObject()
declare function isString() declare function isString()
declare function printjson() declare function printjson()
declare function printjsononeline() declare function printjsononeline()
declare function stringifyErrorMessageAndAttributes()
declare function toJsonForLog() declare function toJsonForLog()
declare function tojson() declare function tojson()
declare function tojsonObject() declare function tojsonObject()

View File

@ -925,11 +925,3 @@ isNumber = function(x) {
isObject = function(x) { isObject = function(x) {
return typeof (x) == "object"; return typeof (x) == "object";
}; };
stringifyErrorMessageAndAttributes = function(e) {
const escapedMsg = JSON.stringify(e.message);
if (!e.extraAttr) {
return escapedMsg;
}
return `${escapedMsg}: ${tojson(e.extraAttr)}`;
};