1 // -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 // Any copyright is dedicated to the Public Domain.
3 // http://creativecommons.org/licenses/publicdomain/
5 // Assert that cloning b does the right thing as far as we can tell.
6 // Caveat: getters in b must produce the same value each time they're
7 // called. We may call them several times.
9 // If desc is provided, then the very first thing we do to b is clone it.
10 // (The self-modifying object test counts on this.)
12 function check(b, desc) {
13 function classOf(obj) {
14 return Object.prototype.toString.call(obj);
17 function ownProperties(obj) {
18 return Object.getOwnPropertyNames(obj).
19 map(function (p) { return [p, Object.getOwnPropertyDescriptor(obj, p)]; });
22 function isCloneable(pair) {
23 return typeof pair[0] === 'string' && pair[1].enumerable;
26 function notIndex(p) {
28 return !("" + u == p && u != 0xffffffff);
31 function assertIsCloneOf(a, b, path) {
32 assertEq(a === b, false);
35 assertEq(ca, classOf(b), path);
37 assertEq(Object.getPrototypeOf(a),
38 ca == "[object Object]" ? Object.prototype : Array.prototype,
41 // 'b', the original object, may have non-enumerable or XMLName
42 // properties; ignore them. 'a', the clone, should not have any
43 // non-enumerable properties (except .length, if it's an Array) or
44 // XMLName properties.
45 var pb = ownProperties(b).filter(isCloneable);
46 var pa = ownProperties(a);
47 for (var i = 0; i < pa.length; i++) {
48 assertEq(typeof pa[i][0], "string", "clone should not have E4X properties " + path);
49 if (!pa[i][1].enumerable) {
50 if (Array.isArray(a) && pa[i][0] == "length") {
51 // remove it so that the comparisons below will work
55 throw new Error("non-enumerable clone property " + uneval(pa[i][0]) + " " + path);
60 // Check that, apart from properties whose names are array indexes,
61 // the enumerable properties appear in the same order.
62 var aNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex);
63 var bNames = pa.map(function (pair) { return pair[1]; }).filter(notIndex);
64 assertEq(aNames.join(","), bNames.join(","), path);
66 // Check that the lists are the same when including array indexes.
67 function byName(a, b) { a = a[0]; b = b[0]; return a < b ? -1 : a === b ? 0 : 1; }
70 assertEq(pa.length, pb.length, "should see the same number of properties " + path);
71 for (var i = 0; i < pa.length; i++) {
74 assertEq(aName, bName, path);
76 var path2 = path + "." + aName;
79 assertEq(da.configurable, true, path2);
80 assertEq(da.writable, true, path2);
81 assertEq("value" in da, true, path2);
84 if (typeof va === "object" && va !== null)
85 queue.push([va, vb, path2]);
87 assertEq(va, vb, path2);
91 var banner = "while testing clone of " + (desc || uneval(b));
92 var a = deserialize(serialize(b));
93 var queue = [[a, b, banner]];
94 while (queue.length) {
95 var triple = queue.shift();
96 assertIsCloneOf(triple[0], triple[1], triple[2]);
99 return a; // for further testing
106 check({x: 0.7, p: "forty-two", y: null, z: undefined});
107 check(Array.prototype);
108 check(Object.prototype);
122 // Check cloning properties other than basic data properties. (check()
123 // asserts that the properties of the clone are configurable, writable,
124 // enumerable data properties.)
126 Object.defineProperties(b, {
127 x: {enumerable: true, get: function () { return 12479; }},
128 y: {enumerable: true, configurable: true, writable: false, value: 0},
129 z: {enumerable: true, configurable: false, writable: true, value: 0},
130 hidden: {enumerable:false, value: 1334}});
133 // Check corner cases involving property names.
138 "\xff\x7f\u7fff\uffff\ufeff\ufffe": 1, // random unicode id
139 "\ud800 \udbff \udc00 \udfff": 2}; // busted surrogate pairs
144 b[0xffffffff] = null;
145 b[0x100000000] = null;
147 b["\xff\x7f\u7fff\uffff\ufeff\ufffe"] = 1;
148 b["\ud800 \udbff \udc00 \udfff"] = 2;
151 // An array's .length property is not enumerable, so it is not cloned.
153 assertEq(b.length, 5);
155 assertEq(a.length, 0);
159 assertEq(a.length, 2);
161 // Check that prototypes are not cloned, per spec.
162 b = Object.create({x:1});
167 // Check that cloning separates merge points in the tree, per spec.
169 b = {one: same, two: same};
171 assertEq(a.one === a.two, false);
175 assertEq(a[0] === a[1], false);
177 // Try cloning a deep object. Don't fail with "too much recursion".
180 for (var i = 0; i < 10000; i++) {
182 current['x' + i] = next;
185 check(b, "deepObject"); // takes 2 seconds :-\
188 XXX TODO spin this out into its own test
189 // This fails quickly with an OOM error. An exception would be nicer.
190 function Infinitree() {
191 return { get left() { return new Infinitree; },
192 get right() { return new Infinitree; }};
196 serialize(new Infinitree);
200 assertEq(threw, true);
203 // Clone an array with holes.
204 check([0, 1, 2, , 4, 5, 6]);
206 // Array holes should not take up space.
210 assertEq(serialize(b).length < 255, true);
212 // Self-modifying object.
213 // This should never read through to b's prototype.
214 b = Object.create({y: 2},
215 {x: {enumerable: true,
217 get: function() { if (this.hasOwnProperty("y")) delete this.y; return 1; }},
218 y: {enumerable: true,
222 check(b, "selfModifyingObject");
224 // Ignore properties with object-ids.
225 var uri = "http://example.net";
227 Object.defineProperty(b, QName(uri, "x"), {enumerable: true, value: 3});
228 Object.defineProperty(b, QName(uri, "y"), {enumerable: true, value: 5});
233 reportCompare(0, 0, 'ok');