Skip to content
This repository
Browse code

Added default assert() message using v8's callsites

commit 007b7181e307a8d52970947d2b6432a2addd9ef0 1 parent 4b3824b
TJ Holowaychuk authored December 30, 2011
18  lib/assert.js
@@ -22,7 +22,7 @@
22 22
 // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 23
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 24
 
25  
-// UTILITY
  25
+var fs = require('fs');
26 26
 var util = require('util');
27 27
 var pSlice = Array.prototype.slice;
28 28
 
@@ -121,7 +121,21 @@ assert.fail = fail;
121 121
 // assert.strictEqual(true, guard, message_opt);.
122 122
 
123 123
 function ok(value, message) {
124  
-  if (!!!value) fail(value, true, message, '==', assert.ok);
  124
+  if (!!!value) {
  125
+    if (!message) {
  126
+      var call = util.stack()[1],
  127
+        file = call.getFileName(),
  128
+        lineno = call.getLineNumber(),
  129
+        src = fs.readFileSync(file, 'utf8'),
  130
+        line = src.split('\n')[lineno-1];
  131
+
  132
+      if (line.match(/assert\((.*)\)/)) {
  133
+        message = RegExp.$1;
  134
+      }
  135
+    }
  136
+
  137
+    fail(value, true, message, '==', assert.ok);
  138
+  }
125 139
 }
126 140
 assert.ok = ok;
127 141
 
11  lib/util.js
@@ -512,13 +512,22 @@ exports.inherits = function(ctor, superCtor) {
512 512
   ctor.prototype = Object.create(superCtor.prototype, {
513 513
     constructor: {
514 514
       value: ctor,
515  
-      enumerable: false,
516 515
       writable: true,
517 516
       configurable: true
518 517
     }
519 518
   });
520 519
 };
521 520
 
  521
+exports.stack = function(){
  522
+  var orig = Error.prepareStackTrace;
  523
+  Error.prepareStackTrace = function(_, stack){ return stack; };
  524
+  var err = new Error;
  525
+  Error.captureStackTrace(err, arguments.callee);
  526
+  var stack = err.stack;
  527
+  Error.prepareStackTrace = orig;
  528
+  return stack;
  529
+};
  530
+
522 531
 var deprecationWarnings;
523 532
 
524 533
 exports._deprecationWarning = function(moduleId, message) {
121  test/simple/test-assert.js
@@ -23,7 +23,7 @@ var common = require('../common');
23 23
 var assert = require('assert');
24 24
 var a = require('assert');
25 25
 
26  
-function makeBlock(f) {
  26
+function makeFunction(f) {
27 27
   var args = Array.prototype.slice.call(arguments, 1);
28 28
   return function() {
29 29
     return f.apply(this, args);
@@ -33,97 +33,97 @@ function makeBlock(f) {
33 33
 assert.ok(common.indirectInstanceOf(a.AssertionError.prototype, Error),
34 34
           'a.AssertionError instanceof Error');
35 35
 
36  
-assert.throws(makeBlock(a, false), a.AssertionError, 'ok(false)');
  36
+assert.throws(makeFunction(a, false), a.AssertionError, 'ok(false)');
37 37
 
38  
-assert.doesNotThrow(makeBlock(a, true), a.AssertionError, 'ok(true)');
  38
+assert.doesNotThrow(makeFunction(a, true), a.AssertionError, 'ok(true)');
39 39
 
40  
-assert.doesNotThrow(makeBlock(a, 'test', 'ok(\'test\')'));
  40
+assert.doesNotThrow(makeFunction(a, 'test', 'ok(\'test\')'));
41 41
 
42  
-assert.throws(makeBlock(a.ok, false),
  42
+assert.throws(makeFunction(a.ok, false),
43 43
               a.AssertionError, 'ok(false)');
44 44
 
45  
-assert.doesNotThrow(makeBlock(a.ok, true),
  45
+assert.doesNotThrow(makeFunction(a.ok, true),
46 46
                     a.AssertionError, 'ok(true)');
47 47
 
48  
-assert.doesNotThrow(makeBlock(a.ok, 'test'), 'ok(\'test\')');
  48
+assert.doesNotThrow(makeFunction(a.ok, 'test'), 'ok(\'test\')');
49 49
 
50  
-assert.throws(makeBlock(a.equal, true, false), a.AssertionError, 'equal');
  50
+assert.throws(makeFunction(a.equal, true, false), a.AssertionError, 'equal');
51 51
 
52  
-assert.doesNotThrow(makeBlock(a.equal, null, null), 'equal');
  52
+assert.doesNotThrow(makeFunction(a.equal, null, null), 'equal');
53 53
 
54  
-assert.doesNotThrow(makeBlock(a.equal, undefined, undefined), 'equal');
  54
+assert.doesNotThrow(makeFunction(a.equal, undefined, undefined), 'equal');
55 55
 
56  
-assert.doesNotThrow(makeBlock(a.equal, null, undefined), 'equal');
  56
+assert.doesNotThrow(makeFunction(a.equal, null, undefined), 'equal');
57 57
 
58  
-assert.doesNotThrow(makeBlock(a.equal, true, true), 'equal');
  58
+assert.doesNotThrow(makeFunction(a.equal, true, true), 'equal');
59 59
 
60  
-assert.doesNotThrow(makeBlock(a.equal, 2, '2'), 'equal');
  60
+assert.doesNotThrow(makeFunction(a.equal, 2, '2'), 'equal');
61 61
 
62  
-assert.doesNotThrow(makeBlock(a.notEqual, true, false), 'notEqual');
  62
+assert.doesNotThrow(makeFunction(a.notEqual, true, false), 'notEqual');
63 63
 
64  
-assert.throws(makeBlock(a.notEqual, true, true),
  64
+assert.throws(makeFunction(a.notEqual, true, true),
65 65
               a.AssertionError, 'notEqual');
66 66
 
67  
-assert.throws(makeBlock(a.strictEqual, 2, '2'),
  67
+assert.throws(makeFunction(a.strictEqual, 2, '2'),
68 68
               a.AssertionError, 'strictEqual');
69 69
 
70  
-assert.throws(makeBlock(a.strictEqual, null, undefined),
  70
+assert.throws(makeFunction(a.strictEqual, null, undefined),
71 71
               a.AssertionError, 'strictEqual');
72 72
 
73  
-assert.doesNotThrow(makeBlock(a.notStrictEqual, 2, '2'), 'notStrictEqual');
  73
+assert.doesNotThrow(makeFunction(a.notStrictEqual, 2, '2'), 'notStrictEqual');
74 74
 
75 75
 // deepEquals joy!
76 76
 // 7.2
77  
-assert.doesNotThrow(makeBlock(a.deepEqual, new Date(2000, 3, 14),
  77
+assert.doesNotThrow(makeFunction(a.deepEqual, new Date(2000, 3, 14),
78 78
                     new Date(2000, 3, 14)), 'deepEqual date');
79 79
 
80  
-assert.throws(makeBlock(a.deepEqual, new Date(), new Date(2000, 3, 14)),
  80
+assert.throws(makeFunction(a.deepEqual, new Date(), new Date(2000, 3, 14)),
81 81
               a.AssertionError,
82 82
               'deepEqual date');
83 83
 
84 84
 // 7.3
85  
-assert.doesNotThrow(makeBlock(a.deepEqual, /a/, /a/));
86  
-assert.doesNotThrow(makeBlock(a.deepEqual, /a/g, /a/g));
87  
-assert.doesNotThrow(makeBlock(a.deepEqual, /a/i, /a/i));
88  
-assert.doesNotThrow(makeBlock(a.deepEqual, /a/m, /a/m));
89  
-assert.doesNotThrow(makeBlock(a.deepEqual, /a/igm, /a/igm));
90  
-assert.throws(makeBlock(a.deepEqual, /ab/, /a/));
91  
-assert.throws(makeBlock(a.deepEqual, /a/g, /a/));
92  
-assert.throws(makeBlock(a.deepEqual, /a/i, /a/));
93  
-assert.throws(makeBlock(a.deepEqual, /a/m, /a/));
94  
-assert.throws(makeBlock(a.deepEqual, /a/igm, /a/im));
  85
+assert.doesNotThrow(makeFunction(a.deepEqual, /a/, /a/));
  86
+assert.doesNotThrow(makeFunction(a.deepEqual, /a/g, /a/g));
  87
+assert.doesNotThrow(makeFunction(a.deepEqual, /a/i, /a/i));
  88
+assert.doesNotThrow(makeFunction(a.deepEqual, /a/m, /a/m));
  89
+assert.doesNotThrow(makeFunction(a.deepEqual, /a/igm, /a/igm));
  90
+assert.throws(makeFunction(a.deepEqual, /ab/, /a/));
  91
+assert.throws(makeFunction(a.deepEqual, /a/g, /a/));
  92
+assert.throws(makeFunction(a.deepEqual, /a/i, /a/));
  93
+assert.throws(makeFunction(a.deepEqual, /a/m, /a/));
  94
+assert.throws(makeFunction(a.deepEqual, /a/igm, /a/im));
95 95
 
96 96
 var re1 = /a/;
97 97
 re1.lastIndex = 3;
98  
-assert.throws(makeBlock(a.deepEqual, re1, /a/));
  98
+assert.throws(makeFunction(a.deepEqual, re1, /a/));
99 99
 
100 100
 
101 101
 // 7.4
102  
-assert.doesNotThrow(makeBlock(a.deepEqual, 4, '4'), 'deepEqual == check');
103  
-assert.doesNotThrow(makeBlock(a.deepEqual, true, 1), 'deepEqual == check');
104  
-assert.throws(makeBlock(a.deepEqual, 4, '5'),
  102
+assert.doesNotThrow(makeFunction(a.deepEqual, 4, '4'), 'deepEqual == check');
  103
+assert.doesNotThrow(makeFunction(a.deepEqual, true, 1), 'deepEqual == check');
  104
+assert.throws(makeFunction(a.deepEqual, 4, '5'),
105 105
               a.AssertionError,
106 106
               'deepEqual == check');
107 107
 
108 108
 // 7.5
109 109
 // having the same number of owned properties && the same set of keys
110  
-assert.doesNotThrow(makeBlock(a.deepEqual, {a: 4}, {a: 4}));
111  
-assert.doesNotThrow(makeBlock(a.deepEqual, {a: 4, b: '2'}, {a: 4, b: '2'}));
112  
-assert.doesNotThrow(makeBlock(a.deepEqual, [4], ['4']));
113  
-assert.throws(makeBlock(a.deepEqual, {a: 4}, {a: 4, b: true}),
  110
+assert.doesNotThrow(makeFunction(a.deepEqual, {a: 4}, {a: 4}));
  111
+assert.doesNotThrow(makeFunction(a.deepEqual, {a: 4, b: '2'}, {a: 4, b: '2'}));
  112
+assert.doesNotThrow(makeFunction(a.deepEqual, [4], ['4']));
  113
+assert.throws(makeFunction(a.deepEqual, {a: 4}, {a: 4, b: true}),
114 114
               a.AssertionError);
115  
-assert.doesNotThrow(makeBlock(a.deepEqual, ['a'], {0: 'a'}));
  115
+assert.doesNotThrow(makeFunction(a.deepEqual, ['a'], {0: 'a'}));
116 116
 //(although not necessarily the same order),
117  
-assert.doesNotThrow(makeBlock(a.deepEqual, {a: 4, b: '1'}, {b: '1', a: 4}));
  117
+assert.doesNotThrow(makeFunction(a.deepEqual, {a: 4, b: '1'}, {b: '1', a: 4}));
118 118
 var a1 = [1, 2, 3];
119 119
 var a2 = [1, 2, 3];
120 120
 a1.a = 'test';
121 121
 a1.b = true;
122 122
 a2.b = true;
123 123
 a2.a = 'test';
124  
-assert.throws(makeBlock(a.deepEqual, Object.keys(a1), Object.keys(a2)),
  124
+assert.throws(makeFunction(a.deepEqual, Object.keys(a1), Object.keys(a2)),
125 125
               a.AssertionError);
126  
-assert.doesNotThrow(makeBlock(a.deepEqual, a1, a2));
  126
+assert.doesNotThrow(makeFunction(a.deepEqual, a1, a2));
127 127
 
128 128
 // having an identical prototype property
129 129
 var nbRoot = {
@@ -147,35 +147,35 @@ nameBuilder2.prototype = nbRoot;
147 147
 var nb1 = new nameBuilder('Ryan', 'Dahl');
148 148
 var nb2 = new nameBuilder2('Ryan', 'Dahl');
149 149
 
150  
-assert.doesNotThrow(makeBlock(a.deepEqual, nb1, nb2));
  150
+assert.doesNotThrow(makeFunction(a.deepEqual, nb1, nb2));
151 151
 
152 152
 nameBuilder2.prototype = Object;
153 153
 nb2 = new nameBuilder2('Ryan', 'Dahl');
154  
-assert.throws(makeBlock(a.deepEqual, nb1, nb2), a.AssertionError);
  154
+assert.throws(makeFunction(a.deepEqual, nb1, nb2), a.AssertionError);
155 155
 
156 156
 // String literal + object blew up my implementation...
157  
-assert.throws(makeBlock(a.deepEqual, 'a', {}), a.AssertionError);
  157
+assert.throws(makeFunction(a.deepEqual, 'a', {}), a.AssertionError);
158 158
 
159 159
 // Testing the throwing
160 160
 function thrower(errorConstructor) {
161 161
   throw new errorConstructor('test');
162 162
 }
163  
-var aethrow = makeBlock(thrower, a.AssertionError);
164  
-aethrow = makeBlock(thrower, a.AssertionError);
  163
+var aethrow = makeFunction(thrower, a.AssertionError);
  164
+aethrow = makeFunction(thrower, a.AssertionError);
165 165
 
166 166
 // the basic calls work
167  
-assert.throws(makeBlock(thrower, a.AssertionError),
  167
+assert.throws(makeFunction(thrower, a.AssertionError),
168 168
               a.AssertionError, 'message');
169  
-assert.throws(makeBlock(thrower, a.AssertionError), a.AssertionError);
170  
-assert.throws(makeBlock(thrower, a.AssertionError));
  169
+assert.throws(makeFunction(thrower, a.AssertionError), a.AssertionError);
  170
+assert.throws(makeFunction(thrower, a.AssertionError));
171 171
 
172 172
 // if not passing an error, catch all.
173  
-assert.throws(makeBlock(thrower, TypeError));
  173
+assert.throws(makeFunction(thrower, TypeError));
174 174
 
175 175
 // when passing a type, only catch errors of the appropriate type
176 176
 var threw = false;
177 177
 try {
178  
-  a.throws(makeBlock(thrower, TypeError), a.AssertionError);
  178
+  a.throws(makeFunction(thrower, TypeError), a.AssertionError);
179 179
 } catch (e) {
180 180
   threw = true;
181 181
   assert.ok(e instanceof TypeError, 'type');
@@ -187,7 +187,7 @@ threw = false;
187 187
 
188 188
 // doesNotThrow should pass through all errors
189 189
 try {
190  
-  a.doesNotThrow(makeBlock(thrower, TypeError), a.AssertionError);
  190
+  a.doesNotThrow(makeFunction(thrower, TypeError), a.AssertionError);
191 191
 } catch (e) {
192 192
   threw = true;
193 193
   assert.ok(e instanceof TypeError);
@@ -197,7 +197,7 @@ assert.equal(true, threw,
197 197
 
198 198
 // key difference is that throwing our correct error makes an assertion error
199 199
 try {
200  
-  a.doesNotThrow(makeBlock(thrower, TypeError), TypeError);
  200
+  a.doesNotThrow(makeFunction(thrower, TypeError), TypeError);
201 201
 } catch (e) {
202 202
   threw = true;
203 203
   assert.ok(e instanceof a.AssertionError);
@@ -224,15 +224,24 @@ try {
224 224
 assert.ok(threw, 'wrong constructor validation');
225 225
 
226 226
 // use a RegExp to validate error message
227  
-a.throws(makeBlock(thrower, TypeError), /test/);
  227
+a.throws(makeFunction(thrower, TypeError), /test/);
228 228
 
229 229
 // use a fn to validate error object
230  
-a.throws(makeBlock(thrower, TypeError), function(err) {
  230
+a.throws(makeFunction(thrower, TypeError), function(err) {
231 231
   if ((err instanceof TypeError) && /test/.test(err)) {
232 232
     return true;
233 233
   }
234 234
 });
235 235
 
  236
+var gotError = false;
  237
+try {
  238
+  assert('manny' == 'tobi');
  239
+} catch (err) {
  240
+  assert("'manny' == 'tobi'" == err.message);
  241
+  process.exit(1)
  242
+}
  243
+assert(gotError);
  244
+
236 245
 
237 246
 // GH-207. Make sure deepEqual doesn't loop forever on circular refs
238 247
 

7 notes on commit 007b718

JP Richardson

Any chance on getting this commit accepted? When tests fail, it'd be useful to have the line displayed, as I write my tests like the example without messages.

Ben Noordhuis

@isaacs: Opinions? I suppose it's kind of useful but it also looks like something that's prone to break after a V8 upgrade...

TJ Holowaychuk

@bnoordhuis it's been the same since I can remember in v8, not that it's necessarily stable but I cant recall it changing at all yet

looks like it's been the same since 2010 http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi

Isaac Z. Schlueter
Collaborator

I really like this idea a lot. Makes it more like a C-style assert. Yes, please, this would be crazy useful.

A few issues with this approach, though:

  1. The makeBlock -> makeFunction rename is unnecessary, and makes the commit more cluttered. Please revert that.
  2. The constructor attribute needs enumerable: false. Nothing about this should require touching util.inherits. Please revert.
  3. It should not be necessary to do any synchronous fs reading for this. Keep in mind, fs.readFileSync won't work so well if it's a native module or a random string passed to vm.runInThisContext().

Rather than reading the file, why not just pull the program text out of the callsite? https://gist.github.com/1784220 There may be a better way to do this. Really, what you want is the expression that is being passed to assert() as the first argument, which might span multiple lines. Eg:

assert(itIsAPony &&
       itHasASaddle &&
       sitOnSaddle() &&
       "pony saddles should be sittable" );

I think we can rely on this API remaining stable in v8, at least unless it becomes standardized across browsers somehow (which would be nice, but is highly unlikely.)

TJ Holowaychuk

haha sorry the rubyism of makeBlock was lame but yeah I can revert that. false is the default for enumerable: (they all default to false). We'll definitely need to tweak for multi-line stuff I overlooked that.

Isaac Z. Schlueter
Collaborator

Sure, it's more about keeping the noise down, and having commits that only do onething.

TJ Holowaychuk

definitely. agreed

Please sign in to comment.
Something went wrong with that request. Please try again.