/*
 *  Object.defineProperty() is pretty complex and intricate object property
 *  manipulation function.  The test cases here try to exercise all paths
 *  of the algorithm in E5.1 Section 8.12.9.
 *
 *  There is a separate brute force combination test.
 */

// FIXME: util
function formatValue(v) {
    if (typeof v === 'function') {
        // avoid implementation dependent string formatting
        if (v.funcName) {
            return '[function ' + v.funcName + ']';
        } else {
            return Object.prototype.toString.call(v);
        }
    }
    if (typeof v === 'number') {
        if (v === 0) {
            if (1/v === Number.NEGATIVE_INFINITY) {
                // format negative zero specially to detect them in the output
                return '-0';
            }
        }
    }
    return String(v);
}

function getDesc(obj, prop) {
    var pd;

    if (typeof obj !== 'object' || obj === null) {
        // valueOf()
        return prop + ': non-object (' + Object.prototype.toString.call(obj) + ')';
    }

    // ToString(prop) coercion
    pd = Object.getOwnPropertyDescriptor(obj, prop);

    if (pd === undefined) {
        // valueOf()
        return prop + ': undefined';
    }

    // ToPrimitive(prop) coercion without hint -> valueOf()
    return prop + ': ' +
           'value=' + formatValue(pd.value) +
           ', writable=' + formatValue(pd.writable) +
           ', enumerable=' + formatValue(pd.enumerable) +
           ', configurable=' + formatValue(pd.configurable) +
           ', typeof(get)=' + formatValue(pd.get) +
           ', typeof(set)=' + formatValue(pd.set);
}

function printDesc(obj, prop) {
    print(getDesc(obj, prop));
}

function testDef(obj, prop, attrs, arg_count) {
    var t;

    // coercion side effects
    print('pre:  ' + getDesc(obj, prop));

    try {
        if (arg_count === 0) {
            t = Object.defineProperty();
        } else if (arg_count === 1) {
            t = Object.defineProperty(obj);
        } else if (arg_count === 2) {
            t = Object.defineProperty(obj, prop);
        } else {
            t = Object.defineProperty(obj, prop, attrs);
        }
        print(typeof t, formatValue(t));
    } catch (e) {
        print(e.name);
    }

    // coercion side effects
    print('post: ' + getDesc(obj, prop));
}


/*===
coercion
pre:  undefined: non-object ([object Undefined])
TypeError
post: undefined: non-object ([object Undefined])
pre:  foo: non-object ([object Undefined])
TypeError
post: foo: non-object ([object Undefined])
pre:  foo: non-object ([object Null])
TypeError
post: foo: non-object ([object Null])
pre:  foo: non-object ([object Boolean])
TypeError
post: foo: non-object ([object Boolean])
pre:  foo: non-object ([object Boolean])
TypeError
post: foo: non-object ([object Boolean])
pre:  foo: non-object ([object Number])
TypeError
post: foo: non-object ([object Number])
pre:  foo: non-object ([object String])
TypeError
post: foo: non-object ([object String])
pre:  foo: undefined
object 1,2
post: foo: value=undefined, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
toString() prop
valueOf() prop
pre:  bar: undefined
toString() prop
object [object Object]
toString() prop
valueOf() prop
post: bar: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
bar: undefined
pre:  123: undefined
object [object Object]
post: 123: value=2, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
123: value=2, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
valueOf() prop
pre:  bar: non-object ([object Number])
TypeError
valueOf() prop
post: bar: non-object ([object Number])
===*/

/* Coercion of 'this' and property name. */

print('coercion');

function coercionTest() {
    var test = testDef;

    // Type(O) checks

    test(undefined, undefined, undefined, 0);
    test(undefined, 'foo', {});
    test(null, 'foo', {});
    test(true, 'foo', {});
    test(false, 'foo', {});
    test(123, 'foo', {});
    test('foo', 'foo', {});
    test([1,2], 'foo', {});
    test({ foo: 1, bar: 2 }, 'foo', {});

    // ToString(P)

    obj = {};
    test(obj, {
        toString: function() { print('toString() prop'); return 'foo'; },
        valueOf: function() { print('valueOf() prop'); return 'bar'; }
    }, { value: 1 }, 3);
    printDesc(obj, 'foo');
    printDesc(obj, 'bar');

    // ToString(123) -> '123'

    obj = {};
    test(obj, 123, { value: 2 });
    printDesc(obj, '123');

    // Type(O) check precedes ToString(P) coercion

    test(123, {
        toString: function() { print('toString() prop'); return 'foo'; },
        valueOf: function() { print('valueOf() prop'); return 'bar'; }
    }, { value: 1 });
}

try {
    coercionTest();
} catch (e) {
    print(e);
}

/*===
topropertydescriptor
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=undefined, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=null, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=true, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=false, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=123, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=1,2, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=[object Object], writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=[object Function], writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=[object Function]
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
TypeError
post: foo: undefined
pre:  foo: undefined
object [object Object]
post: foo: value=foo, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
===*/

/* ToPropertyDescriptor call (after this and property name coercion).
 * Detecting invalid descriptors.  E5.1 Section 8.10.5.
 */

print('topropertydescriptor');

function toPropertyDescriptorTest() {
    function test(desc) {
        var obj = {};
        testDef(obj, 'foo', desc);
    }

    // TypeError if not object
    test(undefined);
    test(null);
    test(true);
    test(false);
    test(123);
    test('foo');

    // Steps 3-8: ToBoolean() coercions.
    //
    // Note that ToBoolean() has no side effects so we can't verify that
    // it happens except by checking the resulting descriptor results.
    //
    //   undefined, null, NaN, +/-0, '' -> false
    //   otherwise -> true

    test({ value: 'foo', writable: undefined, enumerable: null, configurable: Number.NaN });
    test({ value: 'foo', writable: -0, enumerable: +0, configurable: '' });
    test({ value: 'foo', writable: Number.NEGATIVE_INFINITY, enumerable: Number.POSITIVE_INFINITY, configurable: 1 });
    test({ value: 'foo', writable: true, enumerable: false, configurable: 'x' });
    test({ value: 'foo', writable: '\u0000', enumerable: {}, configurable: [] });
    test({ value: 'foo', writable: function(){}, enumerable: /foo/, configurable: new Date() });

    // Step 5: no Value coercion.

    test({ value: undefined, writable: true, enumerable: true, configurable: true });
    test({ value: null, writable: true, enumerable: true, configurable: true });
    test({ value: true, writable: true, enumerable: true, configurable: true });
    test({ value: false, writable: true, enumerable: true, configurable: true });
    test({ value: 123, writable: true, enumerable: true, configurable: true });
    test({ value: 'foo', writable: true, enumerable: true, configurable: true });
    test({ value: [1,2], writable: true, enumerable: true, configurable: true });
    test({ value: { foo: 1, bar: 2 }, writable: true, enumerable: true, configurable: true });
    test({ value: function(){}, writable: true, enumerable: true, configurable: true });

    // Steps 7-8: get/set are checked to be callable.
 
    test({ get: undefined, set: function(){}, enumerable: true, configurable: true });
    test({ get: null, set: function(){}, enumerable: true, configurable: true });
    test({ get: true, set: function(){}, enumerable: true, configurable: true });
    test({ get: false, set: function(){}, enumerable: true, configurable: true });
    test({ get: 123, set: function(){}, enumerable: true, configurable: true });
    test({ get: 'foo', set: function(){}, enumerable: true, configurable: true });
    test({ get: [1,2], set: function(){}, enumerable: true, configurable: true });
    test({ get: { foo: 1, bar: 2 }, set: function(){}, enumerable: true, configurable: true });
    test({ get: function(){}, set: function(){}, enumerable: true, configurable: true });

    test({ set: undefined, get: function(){}, enumerable: true, configurable: true });
    test({ set: null, get: function(){}, enumerable: true, configurable: true });
    test({ set: true, get: function(){}, enumerable: true, configurable: true });
    test({ set: false, get: function(){}, enumerable: true, configurable: true });
    test({ set: 123, get: function(){}, enumerable: true, configurable: true });
    test({ set: 'foo', get: function(){}, enumerable: true, configurable: true });
    test({ set: [1,2], get: function(){}, enumerable: true, configurable: true });
    test({ set: { foo: 1, bar: 2 }, get: function(){}, enumerable: true, configurable: true });
    test({ set: function(){}, get: function(){}, enumerable: true, configurable: true });

    // Step 10, set/get vs. writable/value conflicts
    test({ set: function(){}, writable: true });
    test({ set: function(){}, value: 1 });
    test({ set: function(){}, writable: true, value: 1 });
    test({ get: function(){}, writable: true });
    test({ get: function(){}, value: 1 });
    test({ get: function(){}, writable: true, value: 1 });
    test({ get: function(){}, set: function(){}, writable: true });
    test({ get: function(){}, set: function(){}, value: 1 });
    test({ get: function(){}, set: function(){}, writable: true, value: 1 });

    // Additional properties are ignored
    test({ value: 'foo', writable: true, enumerable: false, configurable: true, unknown: 'property' });
}

try {
    toPropertyDescriptorTest();
} catch (e) {
    print(e);
}

/*===
return value
true
===*/

/* Return value of defineProperty() is the object itself.  Curiously,
 * this does not allow chained calls because defineProperty() is not
 * in Object.prototype.
 */

print('return value');

function returnValueTest() {
    var obj = {};

    print(Object.defineProperty(obj, 'foo', { value: 1 }) === obj);
}

try {
    returnValueTest();
} catch (e) {
    print(e);
}

/*===
no current, not extensible
pre:  foo: undefined
TypeError
post: foo: undefined
foo: undefined
===*/

/* Steps 1-3: no current value, object is not extensible. */

print('no current, not extensible');

function noCurrentNotExtensibleTest() {
    var obj = {};

    Object.seal(obj);

    testDef(obj, 'foo', { value: 1 });
    printDesc(obj, 'foo');
}

try {
    noCurrentNotExtensibleTest();
} catch (e) {
    print(e);
}

/*===
no current, is extensible
pre:  foo: undefined
object [object Object]
post: foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
===*/

print('no current, is extensible');

/* Steps 1-4: no current value, object is extensible. */

function noCurrentIsExtensibleTest() {
    var obj = {};

    testDef(obj, 'foo', { value: 1 });

    // All other property attributes (writable, enumerable, configurable)
    // will have default values, i.e., false

    printDesc(obj, 'foo');
}

try {
    noCurrentIsExtensibleTest();
} catch (e) {
    print(e);
}

/*===
has current, desc empty
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
===*/

/* Step 5: has current value, but descriptor is empty. */

print('has current, desc empty');

function hasCurrentDescEmptyTest() {
    var obj = { foo: 1 };

    testDef(obj, 'foo', {});

    // No changes; here property attributes will have default values
    // object literals (i.e. writable, configurable, enumerable are
    // all true).

    printDesc(obj, 'foo');
}

try {
    hasCurrentDescEmptyTest();
} catch (e) {
    print(e);
}

/*===
step6
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
===*/

/* Step 6: has current value, descriptor is identical to current values,
 * when compared using SameValue (which distinguishes zero sign).
 */

print('step6');

function step6Test() {
    var obj;

    function fun1() {
    }

    function fun2() {
    }

    function fun3() {
    }

    var test = testDef;

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: +0, writable: false, enumerable: false, configurable: false
    });

    test(obj, 'foo', { value: +0, writable: false, enumerable: false, configurable: false });
    test(obj, 'foo', { value: -0, writable: false, enumerable: false, configurable: false });  // not allowed
    test(obj, 'foo', { value: +0, writable: true, enumerable: false, configurable: false });
    test(obj, 'foo', { value: +0, writable: false, enumerable: true, configurable: false });
    test(obj, 'foo', { value: +0, writable: false, enumerable: false, configurable: true });
    test(obj, 'foo', { value: +0, writable: '', enumerable: 0, configurable: Number.NaN });  // allowed, coercion happens before actual algorithm

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: fun1, set: fun2, enumerable: false, configurable: false
    });

    test(obj, 'foo', { get: fun1, set: fun2, enumerable: false, configurable: false });
    test(obj, 'foo', { get: fun3, set: fun2, enumerable: false, configurable: false });
    test(obj, 'foo', { get: fun1, set: fun3, enumerable: false, configurable: false });
    test(obj, 'foo', { get: fun1, set: fun2, enumerable: true, configurable: false });
    test(obj, 'foo', { get: fun1, set: fun2, enumerable: false, configurable: true });
}

try {
    step6Test();
} catch (e) {
    print(e);
}

/*===
step7
pre:  foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
===*/

/* Step 7: has current value, descriptor is not identical, current is not
 * configurable, and attempt to (7.a) change property to configurable; or
 * (7.b) change enumerability status.
 */

print('step7');

function step7Test() {
    var obj;

    var test = testDef;

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: false
    });
    test(obj, 'foo', { configurable: true });
    test(obj, 'foo', { enumerable: true });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: false
    });
    test(obj, 'foo', { configurable: true });
    test(obj, 'foo', { enumerable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: function(){}, set: function(){}, enumerable: false, configurable: false
    });
    test(obj, 'foo', { configurable: true });
    test(obj, 'foo', { enumerable: true });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: function(){}, set: function(){}, enumerable: true, configurable: false
    });
    test(obj, 'foo', { configurable: true });
    test(obj, 'foo', { enumerable: false });
}

try {
    step7Test();
} catch (e) {
    print(e);
}

/*===
step8
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
===*/

/* Step 8: has current value, descriptor is not identical, descriptor is
 * generic (has no value, writable, get, or set; i.e. may only contain
 * configurable and enumerable).
 *
 * Step 7 already covered some cases; cases left are:
 *
 *   - previous is configurable, change configurability from true->false
 *   - previous is configurable, change enumerability from true->false or
 *     false->true
 *   - previous is configurable, change configurability from true->false
 *     and enumerability from true->false or false->true.
 *
 * Non-configurable cases (for generic descriptors) are already covered
 * fully; step 7 rejects any attempts to change configurability and
 * enumerability, so previous and new configurable and enumerable must
 * have the same values -- but that case is caught by step 6.
 */

print('step8');

function step8Test() {
    var obj;

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { configurable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { enumerable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { enumerable: true });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { configurable: false, enumerable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { configurable: false, enumerable: true });
}

try {
    step8Test();
} catch (e) {
    print(e);
}

/*===
step9
pre:  foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=true, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[object Function], typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=[object Function]
object [object Object]
post: foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=[object Function]
pre:  foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=[object Function]
object [object Object]
post: foo: value=undefined, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[object Function], typeof(set)=[object Function]
object [object Object]
post: foo: value=123, writable=false, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
===*/

/* Step 9: converting an accessor property to data property, or vice versa.
 * If existing property non-configurable, always rejected.
 */

print('step9');

function step9Test() {
    var obj;

    // non-configurable cases

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: false
    });
    testDef(obj, 'foo', { get: function(){}, set: function(){} });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: function(){}, set: function(){}, enumerable: true, configurable: false
    });
    testDef(obj, 'foo', { value: 1 });

    // configurable case: all attributes specified

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { get: function(){}, set: function(){}, enumerable: false, configurable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: function(){}, set: function(){}, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { value: 1, writable: true, enumerable: false, configurable: false });

    // configurable case: attribute defaults

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { get: function(){} });  // set defaults to undefined

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { set: function(){} });  // get defaults to undefined

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: function(){}, set: function(){}, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { writable: true });  // value defaults to undefined

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: function(){}, set: function(){}, enumerable: true, configurable: true
    });
    testDef(obj, 'foo', { value: 123 });  // writable defaults to false
}

try {
    step9Test();
} catch (e) {
    print(e);
}

/*===
step10
pre:  foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=1, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
TypeError
post: foo: value=0, writable=false, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=false, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=true, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=1, writable=true, enumerable=false, configurable=false, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=2, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
pre:  foo: value=1, writable=true, enumerable=false, configurable=true, typeof(get)=undefined, typeof(set)=undefined
object [object Object]
post: foo: value=2, writable=false, enumerable=true, configurable=false, typeof(get)=undefined, typeof(set)=undefined
===*/

/* Step 10: modifying a data property.  If existing property non-configurable,
 * allow change from writable to non-writable (but not vice versa), and no-op
 * if new and old values match with SameValue (otherwise reject).
 */

print('step10');

function step10Test() {
    var obj;

    // non-configurable: change from writable to non-writable (allowed)

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { writable: false });

    // non-configurable: attempt to change from non-writable to writable (rejected)

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: false, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { writable: true });

    // non-configurable: value is SameValue (allowed)

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: false, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { value: 1 });

    // non-configurable: value is not SameValue (rejected)

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: false, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { value: 2 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: +0, writable: false, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { value: -0 });  // rejected

    // configurable -> allow all changes

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { writable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { enumerable: true });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { configurable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { value: 2 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        value: 1, writable: true, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { value: 2, writable: false, enumerable: true, configurable: false }); // all at the same time

    // Note: enumerability changes handled in earlier cases (steps 7-8)
}

try {
    step10Test();
} catch (e) {
    print(e);
}

/*===
step11
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
TypeError
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f3], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f1], typeof(set)=[function f3]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=true, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=false, configurable=false, typeof(get)=[function f1], typeof(set)=[function f2]
pre:  foo: value=undefined, writable=undefined, enumerable=false, configurable=true, typeof(get)=[function f1], typeof(set)=[function f2]
object [object Object]
post: foo: value=undefined, writable=undefined, enumerable=true, configurable=false, typeof(get)=[function f3], typeof(set)=[function f3]
===*/

/* Step 11: modifying an accessor property.  If existing property
 * non-configurable, no-op if both set and/or get (whatever is present)
 * match existing values with SameValue (otherwise reject).
 */

print('step11');

function step11Test() {
    var obj;

    function f1() { }
    function f2() { }
    function f3() { }

    f1.funcName = 'f1';
    f2.funcName = 'f2';
    f3.funcName = 'f3';

    // non-configurable: allow get/set if they are the same

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { set: f2 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { get: f1 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { get: f1, set: f2 });

    // non-configurable: reject if get/set and not same

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { get: f2 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { set: f1 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { get: f1, set: f3 });  // get OK, set not

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { get: f3, set: f2 });  // set OK, get not

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: false
    });
    testDef(obj, 'foo', { get: f3, set: f3 });

    // configurable: accept all changes

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { get: f3 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { set: f3 });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { enumerable: true });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { configurable: false });

    obj = {};
    Object.defineProperty(obj, 'foo', {
        get: f1, set: f2, enumerable: false, configurable: true
    });
    testDef(obj, 'foo', { get: f3, set: f3, enumerable: true, configurable: false });
}

try {
    step11Test();
} catch (e) {
    print(e);
}

/*===
step12
===*/

/* Step 12-13: modify attributes, return true if not rejected. */

print('step12');

function step12Test() {
    var obj;

    // FIXME: anything to test here which hasn't been covered above?
}

try {
    step12Test();
} catch (e) {
    print(e);
}

// FIXME: test special behavior (like arrays, arguments object, etc)

