Unit Testing API, Issue

3 views
Skip to first unread message

Kris Kowal

unread,
Dec 3, 2009, 1:00:08 AM12/3/09
to comm...@googlegroups.com
Felix Geisendörfer seems to have caught a bug in our specification.
With a literal implementation of the specification, a string is
equivalent to an empty object.

assert.deepEqual("", {});

I'm proposing that we interpolate a new line between 7.3 and 7.4 that states:

> For other pairs for which the typeof either the expected or actual value is "string", equivalence is determined by ==.

Kris Kowal

Ash Berlin

unread,
Dec 3, 2009, 8:21:34 AM12/3/09
to comm...@googlegroups.com
There are lots of other traps to be aware of: http://philrathe.com/articles/equiv -- specifically the "traps to be aware of section". Most of these we are already aware of. The RegExp one and the Boxed types are particulrly interesting. Flusspferd is going to be using this implementation for deepEqual.

// Consider those problems. All statements return true.

// typeof operator
typeof {} === "object";
typeof [] === "object"; // Oops! It isn't "array"
typeof function(){} === "function";
typeof "myString" === "string";
typeof 5 === "number";
typeof true === "boolean";
typeof null === "object"; // Oops! It isn't null
typeof undefined === "undefined";


// Date
var d1 = new Date();
var d2 = new Date();
d1.setMilliseconds(0);
d2.setMilliseconds(0);
d1.valueOf() === d2.valueOf(); // Yes they are the same date
d1 !== d2; // Oops!


// RegExp
var a = /./;
a instanceof Object; // Oops
a instanceof RegExp;
typeof a === "function"; // Oops, false in IE and Opera, true in FF and Safari ("object")


// object literal
var o1 = {};
var o2 = {
foo: undefined
};
o1["foo"] === o2["foo"]; // Oops!


// Number
1/0 !== 1/0; // Oops! NaN doesn't equals itself!
// Well it may be ok, or not, it still confusing


// == operator
"1" == true; // Oops! use === not ==


// === operator
new Number(4) !== 4; // Oops!
new String('foo') !== 'foo'; // Oops!
new Boolean(true) !== true; // Oops! ...
// Must use the == operator or valueOf() instead
// being careful about comparing same type together

Also when are functions equal? should they be checked when they are properties of an object?

The sample implementation from narwhal also suffers from infinite recursion with:

var b1 = ByteString([1]),
b2 = ByteString([2]);
assert.deepEqual(b1,b2);

due to the Binary (B and D) spec that says [[Get]] should return another ByteString. This is one of the reasons why I don't like that behvaiour.



-ash

Karl Guertin

unread,
Dec 3, 2009, 12:46:17 PM12/3/09
to comm...@googlegroups.com
> For other pairs for which the typeof either the expected or actual value is "string", equivalence is determined by ==.

+1, added.

On Thu, Dec 3, 2009 at 8:21 AM, Ash Berlin
<ash_flu...@firemirror.com> wrote:
> There are lots of other traps to be aware of: http://philrathe.com/articles/equiv -- specifically the "traps to be aware of section". Most of these we are already aware of. The RegExp one and the Boxed types are particulrly interesting. Flusspferd is going to be using this implementation for deepEqual.

The boxed types are handled by == but Regexps are not, so a RegExp
check gets added to my implementation. Working through Phil's
implementation/gotchas, I believe the spec covers them all. The edge
case is NaN. I thought that NaNs never equal each other in an attempt
to avoid programming bugs but both underscore and Phil Rathé match
isNaNs.

Thanks for catching the missing commas in my implementation, ash. I've
pushed a new version to my fork covering both additions mentioned
above.

--
Looking for a job. Hire me!

Kris Kowal

unread,
Dec 3, 2009, 3:43:35 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 9:46 AM, Karl Guertin <gray...@gmail.com> wrote:
> The boxed types are handled by == but Regexps are not, so a RegExp
> check gets added to my implementation. Working through Phil's
> implementation/gotchas, I believe the spec covers them all. The edge
> case is NaN. I thought that NaNs never equal each other in an attempt
> to avoid programming bugs but both underscore and Phil Rathé match
> isNaNs.

Please propose verbiage for the spec. Thanks.

Kris Kowal

Karl Guertin

unread,
Dec 3, 2009, 4:18:24 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 3:43 PM, Kris Kowal <cowber...@gmail.com> wrote:
> Please propose verbiage for the spec.  Thanks.

I'm actually -0 on NaN being equal to itself, but the verbiage would be:

(before 7.3.) If both the expected and actual values are Numbers,
they are equivalent
if isNaN(actual) && isNaN(expected) === true.

Which leaves the definition of Number ambiguous. The two (good) tests
I know of for that are:

typeof x == 'number' || x instanceof Number

and (if you know x is not null/undefined):

x.constructor === Number

However, different implementations might have more direct ways of testing.

Mark Miller

unread,
Dec 3, 2009, 4:40:31 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 1:18 PM, Karl Guertin <gray...@gmail.com> wrote:
> On Thu, Dec 3, 2009 at 3:43 PM, Kris Kowal <cowber...@gmail.com> wrote:
>> Please propose verbiage for the spec.  Thanks.
>
> I'm actually -0 on NaN being equal to itself, but the verbiage would be:
>
>    (before 7.3.) If both the expected and actual values are Numbers,
> they are equivalent
>    if isNaN(actual) && isNaN(expected) === true.

isNaN is a lousy way to test for NaN since isNaN('') === true, and its
use should be discouraged.

As the ES5 spec (15.1.2.4) says, "A reliable way for ECMAScript code
to test if a value X is a NaN is an expression of the form X !== X.
The result will be true if and only if X is a NaN."



> Which leaves the definition of Number ambiguous. The two (good) tests
> I know of for that are:
>
>    typeof x == 'number' || x instanceof Number

"==" here is safe, but it is generally dangerous and its use should be
discouraged.

typeof x === 'number'

is a fine test. "x instanceof Number" is not, since anyone is free to
make new objects that inherit from Number.prototype, and all such
objects will pass that test.

Object.create(Number.prototype, ...);

Perhaps you are trying for a test that number wrappers pass? That
would be a bad idea since number wrappers are not numbers in JS.

> and (if you know x is not null/undefined):
>
>    x.constructor === Number

Doesn't work for ({constructor: Number}), for example.


> However, different implementations might have more direct ways of testing.

typeof x === 'number'

should work everywhere.

--
Text by me above is hereby placed in the public domain

Cheers,
--MarkM

Karl Guertin

unread,
Dec 3, 2009, 4:52:38 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 4:40 PM, Mark Miller <eri...@gmail.com> wrote:
> isNaN is a lousy way to test for NaN since isNaN('') === true, and its
> use should be discouraged.
>
> As the ES5 spec (15.1.2.4) says, "A reliable way for ECMAScript code
> to test if a value X is a NaN is an expression of the form X !== X.
> The result will be true if and only if X is a NaN."

That's a much better test. Thanks for mentioning it.

> Perhaps you are trying for a test that number wrappers pass? That
> would be a bad idea since number wrappers are not numbers in JS.
> <snip>
>
> typeof x === 'number'
>
> should work everywhere.

Do boxed numbers count or are those wrappers? I admit you'd probably
only ever see them as with a wrapper, but...

> x = new Number(3)
3
> typeof x
"object"

Mark Miller

unread,
Dec 3, 2009, 5:19:22 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 1:52 PM, Karl Guertin <gray...@gmail.com> wrote:
> Do boxed numbers count or are those wrappers? I admit you'd probably
> only ever see them as with a wrapper, but...

This thread is the first time I've heard "boxed" in a JS context. I
suspect that what people mean by "boxed" is indeed JS wrappers.


>> x = new Number(3)
> 3
>> typeof x
> "object"

x is not a number. Number(x) is a number.

The funny case is of course

typeof NaN === 'number'

Try explaining that to a novice programmer!

Wes Garland

unread,
Dec 3, 2009, 6:30:22 PM12/3/09
to comm...@googlegroups.com
>   typeof NaN === 'number'
> Try explaining that to a novice programmer

FWIW, I find that more intuitive than typeof null === 'object' !

Of course, neither is a big deal.  Somebody should collect a page with all these JavaScript facts somewhere. Maybe Ripley's would host it?

Wes

--
Wesley W. Garland
Director, Product Development
PageMail, Inc.
+1 613 542 2787 x 102

Kris Kowal

unread,
Dec 3, 2009, 6:57:01 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 3:30 PM, Wes Garland <w...@page.ca> wrote:
>>   typeof NaN === 'number'
>> Try explaining that to a novice programmer

The reason NaN != NaN is mathematical, and we should preserve it.
There are many ways to arrive at NaN that are not algebraically
equivalent. It's JavaScript's way of saying, "I have discarded the
mathematical information necessary to evaluate any arithmetic
operation on this number, even to test it for equivalence to a number
arrived at by the same means." I agree that NaN is erroneous though
("HuH‽" would probably be more appropriate); but that error sailed
with the first generation of digital calculators.

To simplify, should deepEqual test for strict deep equivalence with
=== or loosey-goosey deep equivalence with ==? We should preserve the
behavior of one operator or the other for comparisons of non-compound
types.

Kris Kowal

Louis Santillan

unread,
Dec 3, 2009, 6:40:47 PM12/3/09
to comm...@googlegroups.com
On Thu, Dec 3, 2009 at 3:30 PM, Wes Garland <w...@page.ca> wrote:
>>   typeof NaN === 'number'
>> Try explaining that to a novice programmer
>
> FWIW, I find that more intuitive than typeof null === 'object' !
>
> Of course, neither is a big deal.  Somebody should collect a page with all
> these JavaScript facts somewhere. Maybe Ripley's would host it?
>
> Wes

Crockford has noted some these issues
(http://javascript.crockford.com/survey.html). comp.lang.javascript
also has some noted (http://www.jibbering.com/faq/).

-L

Mark Miller

unread,
Dec 3, 2009, 8:00:39 PM12/3/09
to comm...@googlegroups.com
It should definitely *not* use "==" for anything. "==" is really badly
broken. It isn't even transitive!

"===" is also broken on NaN and -0 as previously noted. In Caja's
runtime library, we have

Should use a deep adaptation of

function identical(x, y) {
if (x === y) {
// 0 === -0, but they are not identical
return x !== 0 || 1/x === 1/y;
} else {
// NaN !== NaN, but they are identical.
// NaNs are the only non-reflexive value, i.e., if x !== x,
// then x is a NaN.
return x !== x && y !== y;
}
}

which provides robust shallow equality. deppEqual should be a deep
adaptation of this.



> Kris Kowal
>
> --
>
> You received this message because you are subscribed to the Google Groups "CommonJS" group.
> To post to this group, send email to comm...@googlegroups.com.
> To unsubscribe from this group, send email to commonjs+u...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/commonjs?hl=en.

Wes Garland

unread,
Dec 6, 2009, 10:54:10 PM12/6/09
to comm...@googlegroups.com
Sorry to be jumping on this dicussion so late, but this isn't my area of expertise and it is not a current implementation effort here (happy to "go with the flow" for the most part").

I HAVE, however, had the opportunity to "overhear" discussions on IRC and as such popped into the wiki to read some of the deepEquals stuff.

Here's my thinking summarized on this issue:
  • I don't like discussing unqualified equality in the JavaScript context. I started not liking it when I got bitten by Netscape Navigator, er, a couple of years ago, when JavaScript 1.2 came out and introduced some really wild, subtle bugs in my code. Fortunately, things got straightened out in 1.3 but I was forcing language=JavaScript1.1 out of habit for at least 5 years after it was no longer necessary.  (Once bitten, twice shy)
  • I prefer to talk about the equivalence and strict-equality operators, == and ===
  • I would never name an API deepEquals for the reasons outlined above
  • I would rather see deepEquivalent and deepStrictlyEquals
  • In both of these, 'deep' would mean
    • traversal of enumerable ownProperties whose values are not intrinsics
    • by intrinsics, I mean anything with a typeof number, string, boolean, or undefined
    • Yes, undefined, I believe undefined is an under appreciated intrinsic type
  • All comparisons during deepStrictEqual use === (so this is a shallow deepness)
    • Except NaN which is special-cased (users need this)
    • Except -0 which is special-cased 
  • Comparison of intrinsics during deepEquivalent 
    • occur when either argument is an intrinsic
    • use ==
    • Except NaN which is special-cased (users need this)
    • Except -0 which is special-cased
    • Except we allow properties whose values are undefined to be equivalent to undefined properties
  • Comparisons of functions during deepEquivalent use strict equality
  • Comparisons of objects which are instanceof RegExp during deepEquivalent use strict equality  (this is important because RegExps have hidden internal state, are callable, and are maybe typeof function depending on your platform)
  • All other non-intrinsics are compared with deepEquivalent during deepEquivalent (so this is a bigger comparison graph)
  • This wording is important because it allows both these functions to meaningfully operate on e4x xml types without actually building CommonJS on E4X. 
  • The same point would be valid if additional base types were added JavaScript, although depending on the new types, so additional verbiage might be needed (i.e. what consititues an intrinsic)
Now, why am I special casing 0 / -0 in deepStrictlyEqual?  ES3 15.8.2.13, pow(x,-1) yields different answers when is x -0 or 0.  I suggest codifying this comparsing as the correct way to special-case it.

Incidentally, Mark Miller's posts to both this thread and various ES5 threads (especially "how can I tell if this thing I have is a function?" (callable regexps)) have helped me get my thinking more in order on this issue. Thanks, Mark.

Oh, Mark, is isNaN(x) enough for the NaN special casing? ISTR it's not, but can't remember the details.

Karl Guertin

unread,
Dec 7, 2009, 1:17:27 AM12/7/09
to comm...@googlegroups.com
On Sun, Dec 6, 2009 at 10:54 PM, Wes Garland <w...@page.ca> wrote:
> I would rather see deepEquivalent and deepStrictlyEquals

I had considered writing something suggesting this split but was
holding off since I wasn't confident I could cover everything for a
spec. I find the current system's equivalence to be somewhat bizarre,
so I'm +1 on this set of tests with the exception of:

> Except we allow properties whose values are undefined to be equivalent to undefined properties

Is there a use case for this other than {}['a'] == {a:undefined}['a']?
I'd consider this a hazard since it's not at all obvious unless you
know this particular wrinkle. In particular, ES5's Object.keys() will
return 'a' on the latter and nothing on the former and I'd consider
that a bigger difference than the RegExp internal position state.

> Oh, Mark, is isNaN(x) enough for the NaN special casing? ISTR it's not, but can't remember the details.

He corrected me a bit further up in the thread that the proper test is x !== x.

Mark Miller

unread,
Dec 7, 2009, 1:25:16 AM12/7/09
to comm...@googlegroups.com
You are quite welcome!



> Oh, Mark, is isNaN(x) enough for the NaN special casing? ISTR it's not, but
> can't remember the details.
>
> Wes
>
> --
> Wesley W. Garland
> Director, Product Development
> PageMail, Inc.
> +1 613 542 2787 x 102
>

Mark Miller

unread,
Dec 7, 2009, 1:38:33 AM12/7/09
to comm...@googlegroups.com
Yes. The problem with isNaN is that it first coerces its argument to a
number. As a result, isNaN('a') === 'true'.

(Earlier I said that isNaN('') === 'true', for which I was mistaken.)
Reply all
Reply to author
Forward
0 new messages