diff options
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml')
-rw-r--r-- | Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml b/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml new file mode 100644 index 0000000..d9fed0c --- /dev/null +++ b/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml @@ -0,0 +1,589 @@ +# General Lua Libraries for Lua 5.1, 5.2 & 5.3 +# Copyright (C) 2011-2018 stdlib authors + +before: | + base_module = 'table' + this_module = 'std.table' + global_table = '_G' + + extend_base = {'clone', 'clone_select', 'depair', 'empty', + 'enpair', 'insert', 'invert', 'keys', 'maxn', + 'merge', 'merge_select', 'new', + 'pack', 'project', 'remove', 'size', 'sort', + 'unpack', 'values'} + + M = require(this_module) + + +specify std.table: +- context when required: + - context by name: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by=this_module}). + to_equal {} + - it does not touch the core table table: + expect(show_apis {added_to=base_module, by=this_module}). + to_equal {} + - it contains apis from the core table table: + expect(show_apis {from=base_module, not_in=this_module}). + to_contain.a_permutation_of(extend_base) + + - context via the std module: + - it does not touch the global table: + expect(show_apis {added_to=global_table, by='std'}).to_equal {} + - it does not touch the core table table: + expect(show_apis {added_to=base_module, by='std'}).to_equal {} + + +- describe clone: + - before: + subject = {k1={'v1'}, k2={'v2'}, k3={'v3'}} + withmt = setmetatable(M.clone(subject), {'meta!'}) + + f = M.clone + + - context with bad arguments: + badargs.diagnose(f, 'std.table.clone(table, [table], ?boolean|:nometa)') + + - it does not just return the subject: + expect(f(subject)).not_to_be(subject) + - it does copy the subject: + expect(f(subject)).to_equal(subject) + - it only makes a shallow copy of field values: + expect(f(subject).k1).to_be(subject.k1) + - it does not perturb the original subject: + target = {k1=subject.k1, k2=subject.k2, k3=subject.k3} + copy = f(subject) + expect(subject).to_equal(target) + expect(subject).to_be(subject) + + - context with metatables: + - it copies the metatable by default: + expect(getmetatable(f(withmt))).to_be(getmetatable(withmt)) + - it treats non-table arg2 as nometa parameter: + expect(getmetatable(f(withmt, ':nometa'))).to_be(nil) + - it treats table arg2 as a map parameter: + expect(getmetatable(f(withmt, {}))).to_be(getmetatable(withmt)) + - it supports 3 arguments with nometa as arg3: + expect(getmetatable(f(withmt, {}, ':nometa'))).to_be(nil) + + - context when renaming some keys: + - it renames during cloning: + target = {newkey=subject.k1, k2=subject.k2, k3=subject.k3} + expect(f(subject, {k1 = 'newkey'})).to_equal(target) + - it does not perturb the value in the renamed key field: + expect(f(subject, {k1 = 'newkey'}).newkey).to_be(subject.k1) + + +- describe clone_select: + - before: + subject = {k1={'v1'}, k2={'v2'}, k3={'v3'}} + withmt = setmetatable(M.clone(subject), {'meta!'}) + + f = M.clone_select + + - context with bad arguments: + badargs.diagnose(f, 'std.table.clone_select(table, [table], ?boolean|:nometa)') + + - it does not just return the subject: + expect(f(subject)).not_to_be(subject) + - it copies the keys selected: + expect(f(subject, {'k1', 'k2'})).to_equal({k1={'v1'}, k2={'v2'}}) + - it does copy the subject when supplied with a full list of keys: + expect(f(subject, {'k1', 'k2', 'k3'})).to_equal(subject) + - it only makes a shallow copy: + expect(f(subject, {'k1'}).k1).to_be(subject.k1) + - it does not perturb the original subject: + target = {k1=subject.k1, k2=subject.k2, k3=subject.k3} + copy = f(subject, {'k1', 'k2', 'k3'}) + expect(subject).to_equal(target) + expect(subject).to_be(subject) + + - context with metatables: + - it treats non-table arg2 as nometa parameter: + expect(getmetatable(f(withmt, ':nometa'))).to_be(nil) + - it treats table arg2 as a map parameter: + expect(getmetatable(f(withmt, {}))).to_be(getmetatable(withmt)) + expect(getmetatable(f(withmt, {'k1'}))).to_be(getmetatable(withmt)) + - it supports 3 arguments with nometa as arg3: + expect(getmetatable(f(withmt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f(withmt, {'k1'}, ':nometa'))).to_be(nil) + + +- describe depair: + - before: + t = {'first', 'second', third = 4} + l = M.enpair(t) + + f = M.depair + + - context with bad arguments: + badargs.diagnose(f, 'std.table.depair(list of lists)') + + - it returns a primitive table: + expect(objtype(f(l))).to_be 'table' + - it works with an empty table: + expect(f {}).to_equal {} + - it is the inverse of enpair: + expect(f(l)).to_equal(t) + + +- describe empty: + - before: + f = M.empty + + - context with bad arguments: + badargs.diagnose(f, 'std.table.empty(table)') + + - it returns true for an empty table: + expect(f {}).to_be(true) + expect(f {nil}).to_be(true) + - it returns false for a non-empty table: + expect(f {'stuff'}).to_be(false) + expect(f {{}}).to_be(false) + expect(f {false}).to_be(false) + + +- describe enpair: + - before: + t = {'first', 'second', third = 4} + l = M.enpair(t) + + f = M.enpair + + - context with bad arguments: + badargs.diagnose(f, 'std.table.enpair(table)') + + - it returns a table: + expect(objtype(f(t))).to_be 'table' + - it works for an empty table: + expect(f {}).to_equal {} + - it turns a table into a table of pairs: + expect(f(t)).to_equal {{1, 'first'}, {2, 'second'}, {'third', 4}} + - it is the inverse of depair: + expect(f(t)).to_equal(l) + + +- describe insert: + - before: + f, badarg = init(M, this_module, 'insert') + + - context with bad arguments: + badargs.diagnose(f, 'std.table.insert(table, [int], any)') + + examples { + ['it diagnoses more than 2 arguments with no pos'] = function() + pending '#issue 76' + expect(f({}, false, false)).to_raise(badarg(3)) + end + } + examples { + ['it diagnoses out of bounds pos arguments'] = function() + expect(f({}, 0, 'x')).to_raise 'position 0 out of bounds' + expect(f({}, 2, 'x')).to_raise 'position 2 out of bounds' + expect(f({1}, 5, 'x')).to_raise 'position 5 out of bounds' + end + } + + - it returns the modified table: + t = {} + expect(f(t, 1)).to_be(t) + - it append a new element at the end by default: + expect(f({1, 2}, 'x')).to_equal {1, 2, 'x'} + - it fills holes by default: + expect(f({1, 2, [5]=3}, 'x')).to_equal {1, 2, 'x', [5]=3} + - it respects __len when appending: + t = setmetatable({1, 2, [5]=3}, {__len = function() return 42 end}) + expect(f(t, 'x')).to_equal {1, 2, [5]=3, [43]='x'} + - it moves other elements up if necessary: + expect(f({1, 2}, 1, 'x')).to_equal {'x', 1, 2} + expect(f({1, 2}, 2, 'x')).to_equal {1, 'x', 2} + expect(f({1, 2}, 3, 'x')).to_equal {1, 2, 'x'} + - it inserts a new element according to pos argument: + expect(f({}, 1, 'x')).to_equal {'x'} + + +- describe invert: + - before: + subject = {k1=1, k2=2, k3=3} + + f = M.invert + + - context with bad arguments: + badargs.diagnose(f, 'std.table.invert(table)') + + - it returns a new table: + expect(f(subject)).not_to_be(subject) + - it inverts keys and values in the returned table: + expect(f(subject)).to_equal {'k1', 'k2', 'k3'} + - it is reversible: + expect(f(f(subject))).to_equal(subject) + - it seems to copy a list of 1..n numbers: + subject = {1, 2, 3} + expect(f(subject)).to_copy(subject) + + +- describe keys: + - before: + subject = {k1=1, k2=2, k3=3} + + f = M.keys + + - context with bad arguments: + badargs.diagnose(f, 'std.table.keys(table)') + + - it returns an empty list when subject is empty: + expect(f {}).to_equal {} + - it makes a list of table keys: + cmp = function(a, b) + return a < b + end + expect(M.sort(f(subject), cmp)).to_equal {'k1', 'k2', 'k3'} + - it does not guarantee stable ordering: + subject = {} + -- is this a good test? there is a vanishingly small possibility the + -- returned table will have all 10000 keys in the same order... + for i = 10000, 1, -1 do + table.insert(subject, i) + end + expect(f(subject)).not_to_equal(subject) + + +- describe maxn: + - before: + f = M.maxn + + - context with bad arguments: + badargs.diagnose(f, 'std.table.maxn(table)') + + - it returns the largest numeric key of a table: + expect(f {'a', 'b', 'c'}).to_be(3) + expect(f {1, 2, 5, a=10, 3}).to_be(4) + - it works with an empty table: + expect(f {}).to_be(0) + - it ignores holes: + expect(f {1, 2, [5]=3}).to_be(5) + - it ignores __len metamethod: + t = setmetatable({1, 2, [5]=3}, {__len = function() return 42 end}) + expect(f(t)).to_be(5) + + +- describe merge: + - before: | + -- Additional merge keys which are moderately unusual + t1 = {k1={'v1'}, k2='if', k3={'?'}} + t2 = {['if']=true, [{'?'}]=false, _='underscore', k3=t1.k1} + t1mt = setmetatable(M.clone(t1), {'meta!'}) + target = {} + for k, v in pairs(t1) do + target[k] = v + end + for k, v in pairs(t2) do + target[k] = v + end + + f, badarg = init(M, this_module, 'merge') + + - context with bad arguments: + badargs.diagnose(f, 'std.table.merge(table, table, [table], ?boolean|:nometa)') + + examples { + ['it diagnoses more than 2 arguments with no pos'] = function() + pending '#issue 76' + expect(f({}, {}, ':nometa', false)).to_raise(badarg(4)) + end + } + + - it does not create a whole new table: + expect(f(t1, t2)).to_be(t1) + - it does not change t1 when t2 is empty: + expect(f(t1, {})).to_be(t1) + - it copies t2 when t1 is empty: + expect(f({}, t1)).to_copy(t1) + - it merges keys from t2 into t1: + expect(f(t1, t2)).to_equal(target) + - it gives precedence to values from t2: + original = M.clone(t1) + m = f(t1, t2) -- Merge is destructive, do it once only. + expect(m.k3).to_be(t2.k3) + expect(m.k3).not_to_be(original.k3) + - it only makes a shallow copy of field values: + expect(f({}, t1).k1).to_be(t1.k1) + + - context with metatables: + - it copies the metatable by default: + expect(getmetatable(f({}, t1mt))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it treats non-table arg3 as nometa parameter: + expect(getmetatable(f({}, t1mt, ':nometa'))).to_be(nil) + - it treats table arg3 as a map parameter: + expect(getmetatable(f({}, t1mt, {}))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it supports 4 arguments with nometa as arg4: + expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil) + + - context when renaming some keys: + - it renames during merging: + target = {newkey=t1.k1, k2=t1.k2, k3=t1.k3} + expect(f({}, t1, {k1 = 'newkey'})).to_equal(target) + - it does not perturb the value in the renamed key field: + expect(f({}, t1, {k1 = 'newkey'}).newkey).to_be(t1.k1) + + +- describe merge_select: + - before: | + -- Additional merge keys which are moderately unusual + tablekey = {'?'} + t1 = {k1={'v1'}, k2='if', k3={'?'}} + t1mt = setmetatable(M.clone(t1), {'meta!'}) + t2 = {['if']=true, [tablekey]=false, _='underscore', k3=t1.k1} + t2keys = {'if', tablekey, '_', 'k3'} + target = {} + for k, v in pairs(t1) do + target[k] = v + end + for k, v in pairs(t2) do + target[k] = v + end + + f, badarg = init(M, this_module, 'merge_select') + + - context with bad arguments: + badargs.diagnose(f, 'std.table.merge_select(table, table, [table], ?boolean|:nometa)') + + examples { + ['it diagnoses more than 2 arguments with no pos'] = function() + pending '#issue 76' + expect(f({}, {}, ':nometa', false)).to_raise(badarg(4)) + end + } + + - it does not create a whole new table: + expect(f(t1, t2)).to_be(t1) + - it does not change t1 when t2 is empty: + expect(f(t1, {})).to_be(t1) + - it does not change t1 when key list is empty: + expect(f(t1, t2, {})).to_be(t1) + - it copies the named fields: + expect(f({}, t2, t2keys)).to_equal(t2) + - it makes a shallow copy: + expect(f({}, t1, {'k1'}).k1).to_be(t1.k1) + - it copies exactly named fields of t2 when t1 is empty: + expect(f({}, t1, {'k1', 'k2', 'k3'})).to_copy(t1) + - it merges keys from t2 into t1: + expect(f(t1, t2, t2keys)).to_equal(target) + - it gives precedence to values from t2: + original = M.clone(t1) + m = f(t1, t2, t2keys) -- Merge is destructive, do it once only. + expect(m.k3).to_be(t2.k3) + expect(m.k3).not_to_be(original.k3) + + - context with metatables: + - it copies the metatable by default: + expect(getmetatable(f({}, t1mt))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it treats non-table arg3 as nometa parameter: + expect(getmetatable(f({}, t1mt, ':nometa'))).to_be(nil) + - it treats table arg3 as a map parameter: + expect(getmetatable(f({}, t1mt, {}))).to_be(getmetatable(t1mt)) + expect(getmetatable(f({}, t1mt, {'k1'}))).to_be(getmetatable(t1mt)) + - it supports 4 arguments with nometa as arg4: + expect(getmetatable(f({}, t1mt, {}, ':nometa'))).to_be(nil) + expect(getmetatable(f({}, t1mt, {'k1'}, ':nometa'))).to_be(nil) + + +- describe new: + - before: + f = M.new + + - context with bad arguments: + badargs.diagnose(f, 'std.table.new(?any, ?table)') + + - context when not setting a default: + - before: default = nil + - it returns a new table when nil is passed: + expect(f(default, nil)).to_equal {} + - it returns any table passed in: + t = {'unique table'} + expect(f(default, t)).to_be(t) + + - context when setting a default: + - before: + default = 'default' + - it returns a new table when nil is passed: + expect(f(default, nil)).to_equal {} + - it returns any table passed in: + t = {'unique table'} + expect(f(default, t)).to_be(t) + + - it returns the stored value for existing keys: + t = f('default') + v = {'unique value'} + t[1] = v + expect(t[1]).to_be(v) + - it returns the constructor default for unset keys: + t = f('default') + expect(t[1]).to_be 'default' + - it returns the actual default object: + default = {'unique object'} + t = f(default) + expect(t[1]).to_be(default) + + +- describe pack: + - before: + unpack = unpack or table.unpack + t = {'one', 'two', 'five', n=3} + f = M.pack + - it creates an empty table with no arguments: + expect(f()).to_equal {n=0} + - it creates a table with arguments as elements: + expect(f('one', 'two', 'five')).to_equal(t) + - it is the inverse operation to unpack: + expect(f(unpack(t))).to_equal(t) + - it saves the tuple length in field n: + expect(f(1, 2, 5).n).to_be(3) + expect(f('', false, nil).n).to_be(3) + expect(f(nil, nil, nil).n).to_be(3) + + +- describe project: + - before: + l = { + {first = false, second = true, third = true}, + {first = 1, second = 2, third = 3}, + {first = '1st', second = '2nd', third = '3rd'}, + } + + f = M.project + + - context with bad arguments: + badargs.diagnose(f, 'std.table.project(any, list of tables)') + + - it returns a table: + expect(objtype(f('third', l))).to_be 'table' + - it works with an empty table: + expect(f('third', {})).to_equal {} + - it projects a table of fields from a table of tables: + expect(f('third', l)).to_equal {true, 3, '3rd'} + - it projects fields with a falsey value correctly: + expect(f('first', l)).to_equal {false, 1, '1st'} + + +- describe remove: + - before: + f = M.remove + + - context with bad arguments: + badargs.diagnose(f, 'std.table.remove(table, ?int)') + + examples { + ['it diagnoses out of bounds pos arguments'] = function() + expect(f({1}, 0)).to_raise 'position 0 out of bounds' + expect(f({1}, 3)).to_raise 'position 3 out of bounds' + expect(f({1}, 5)).to_raise 'position 5 out of bounds' + end + } + + - it returns the removed element: + t = {'one', 'two', 'five'} + expect(f({'one', 2, 5}, 1)).to_be 'one' + - it removes an element from the end by default: + expect(f {1, 2, 'five'}).to_be 'five' + - it ignores holes: + t = {'second', 'first', [5]='invisible'} + expect(f(t)).to_be 'first' + expect(f(t)).to_be 'second' + - it respects __len when defaulting pos: + t = setmetatable({1, 2, [43]='invisible'}, {__len = function() return 42 end}) + expect(f(t)).to_be(nil) + expect(f(t)).to_be(nil) + expect(t).to_equal {1, 2, [43]='invisible'} + - it moves other elements down if necessary: + t = {1, 2, 5, 'third', 'first', 'second', 42} + expect(f(t, 5)).to_be 'first' + expect(t).to_equal {1, 2, 5, 'third', 'second', 42} + expect(f(t, 5)).to_be 'second' + expect(t).to_equal {1, 2, 5, 'third', 42} + expect(f(t, 4)).to_be 'third' + expect(t).to_equal {1, 2, 5, 42} + + +- describe size: + - before: | + -- - 1 - ------- 2 ------- -- 3 -- + subject = {'one', {{'two'}, 'three'}, four=5} + + f = M.size + + - context with bad arguments: + badargs.diagnose(f, 'std.table.size(table)') + + - it counts the number of keys in a table: + expect(f(subject)).to_be(3) + - it counts no keys in an empty table: + expect(f {}).to_be(0) + + +- describe sort: + - before: + subject = {5, 2, 4, 1, 0, 3} + target = {0, 1, 2, 3, 4, 5} + cmp = function(a, b) return a < b end + + f = M.sort + + - context with bad arguments: + badargs.diagnose(f, 'std.table.sort(table, ?function)') + + - it sorts elements in place: + f(subject, cmp) + expect(subject).to_equal(target) + - it returns the sorted table: + expect(f(subject, cmp)).to_equal(target) + + +- describe unpack: + - before: + t = {'one', 'two', 'five'} + f = M.unpack + - it returns nil for an empty table: + expect(f {}).to_be(nil) + - it returns numeric indexed table elements: + expect({f(t)}).to_equal(t) + - it respects __len metamethod: + function two(t) + return setmetatable(t, {__len=function() return 2 end}) + end + expect(pack(f(two {})).n).to_be(2) + expect(pack(f(two(t))).n).to_be(2) + - it returns holes in numeric indices as nil: + expect({f {nil, 2}}).to_equal {[2] = 2} + expect({f {nil, nil, 3}}).to_equal {[3] = 3} + expect({f {1, nil, nil, 4}}).to_equal {1, [4] = 4} + - it is the inverse operation to pack: + expect({f(M.pack('one', 'two', 'five'))}).to_equal(t) + + +- describe values: + - before: + subject = {k1={1}, k2={2}, k3={3}} + + f = M.values + + - context with bad arguments: + badargs.diagnose(f, 'std.table.values(table)') + + - it returns an empty list when subject is empty: + expect(f {}).to_equal {} + - it makes a list of table values: + cmp = function(a, b) return a[1] < b[1] end + expect(M.sort(f(subject), cmp)).to_equal {{1}, {2}, {3}} + - it does guarantee stable ordering: + subject = {} + -- is this a good test? or just requiring an implementation quirk? + for i = 10000, 1, -1 do + table.insert(subject, i) + end + expect(f(subject)).to_equal(subject) |