summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml
diff options
context:
space:
mode:
Diffstat (limited to 'Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml')
-rw-r--r--Data/BuiltIn/Libraries/lua-stdlib/spec/table_spec.yaml589
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)