1
0

initial commit

This commit is contained in:
Rokas Puzonas 2023-05-11 21:50:02 +03:00
commit 8d1cdf7514
5 changed files with 656 additions and 0 deletions

6
.luarc.json Normal file
View File

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json",
"Lua.diagnostics.globals": [
"peripheral"
]
}

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Fake CC peripherals
Create fake CC peripherals, useful for testing libraries which directly interact with peripherals

221
init.lua Normal file
View File

@ -0,0 +1,221 @@
local PeripheralAPIFake = {}
PeripheralAPIFake.__index = PeripheralAPIFake
local expect = require("cc.expect").expect
local function isPeripheral(peripheral)
if type(peripheral) ~= "table" then return false end
local meta = getmetatable(peripheral)
if type(meta) ~= "table" then return false end
if meta.__name ~= "peripheral" then return false end
if type(meta.name) ~= "string" then return false end
if type(meta.type) ~= "string" then return false end
if type(meta.types) ~= "table" then return false end
for _, ty in ipairs(meta.types) do
if not meta.types[ty] then return false end
end
return true
end
-- TODO: Add descriptive error messages to asserts
local function expectPeripheral(index, peripheral)
if not isPeripheral(peripheral) then
error(("bad argument #%d (table is not a peripheral)"):format(index), 3)
end
end
--- Performs a deep clone of a table.
local function cloneTable(t)
local mt = getmetatable(t)
setmetatable(t, nil)
local cloned = {}
for k, v in pairs(t) do
if type(v) == "table" then
cloned[k] = cloneTable(v)
else
cloned[k] = v
end
end
setmetatable(t, mt)
return cloned
end
local function doesTableContain(t, value)
for _, v in pairs(t) do
if v == value then
return true
end
end
return false
end
local function getPeripheralName(peripheral)
return getmetatable(peripheral).name
end
local function getPeripheralTypes(peripheral)
return getmetatable(peripheral).types
end
function PeripheralAPIFake.new(peripherals)
expect(1, peripherals, "table", "nil")
local self = setmetatable({}, PeripheralAPIFake)
self.peripherals = {}
if peripherals then
for _, peripheral in ipairs(peripherals) do
self:addPeripehral(peripheral)
end
end
return self
end
function PeripheralAPIFake:newExternalAPI()
local api = {}
function api.call(name, method, ...)
expect(1, name, "string")
expect(2, method, "string")
local peripheral = self.peripherals[name]
if not peripheral then return end
local func = peripheral[method]
if type(func) ~= "function" then
error(("No such method %s"):format(method), 2)
end
return func(...)
end
function api.find(ty, filter)
expect(1, ty, "string")
expect(2, filter, "function", "nil")
local peripherals = {}
for name, peripheral in pairs(self.peripherals) do
local types = getPeripheralTypes(peripheral)
if doesTableContain(types, ty) then
local wrapped = api.wrap(name)
if not filter or filter(name, wrapped) then
table.insert(peripherals, wrapped)
end
end
end
return peripherals
end
function api.getMethods(name)
expect(1, name, "string")
local peripheral = self:getPeripheral(name)
if not peripheral then return end
local methods = {}
for methodName, method in pairs(peripheral) do
if type(method) == "function" then
table.insert(methods, methodName)
end
end
return methods
end
function api.getName(peripheral)
expect(1, peripheral, "table", "string")
expectPeripheral(1, peripheral)
return getPeripheralName(peripheral)
end
function api.getNames()
return self:listPeripherals()
end
function api.getType(peripheral)
expect(1, peripheral, "table", "string")
if type(peripheral) == "table" then
expectPeripheral(1, peripheral)
end
local p = self:getPeripheral(peripheral)
return table.unpack(getPeripheralTypes(p))
end
function api.hasType(peripheral, peripheral_type)
expect(1, peripheral, "table", "string")
if type(peripheral) == "table" then
expectPeripheral(1, peripheral)
end
expect(2, peripheral_type, "string")
local p = self:getPeripheral(peripheral)
if not p then return end
local types = getPeripheralTypes(peripheral)
return doesTableContain(types, peripheral_type)
end
function api.isPresent(name)
expect(1, name, "string")
return self:hasPeripheral(name)
end
function api.wrap(name)
expect(1, name, "string")
local peripheral = self.peripherals[name]
if not peripheral then return end
return cloneTable(peripheral)
end
return api
end
function PeripheralAPIFake:addPeripehral(peripheral)
expect(1, peripheral, "table")
expectPeripheral(1, peripheral)
local name = getPeripheralName(peripheral)
assert(not self.peripherals[name], "Expected peripheral name to be unique, it's not unique")
assert(not self:hasPeripheral(peripheral), "Peripheral already added to system")
self.peripherals[name] = peripheral
end
function PeripheralAPIFake:removePeripheral(peripheral)
expect(1, peripheral, "table", "string")
if type(peripheral) == "string" then
self.peripherals[peripheral] = nil
else
expectPeripheral(1, peripheral)
local name = getPeripheralName(peripheral)
self.peripherals[name] = nil
end
end
function PeripheralAPIFake:hasPeripheral(peripheral)
expect(1, peripheral, "table", "string")
return self:getPeripheral(peripheral) ~= nil
end
function PeripheralAPIFake:getPeripheral(peripheral)
expect(1, peripheral, "table", "string")
if type(peripheral) == "string" then
return self.peripherals[peripheral]
else
expectPeripheral(1, peripheral)
local name = getPeripheralName(peripheral)
return self.peripherals[name]
end
end
function PeripheralAPIFake:getPeripheralName(peripheral)
return getPeripheralName(self:getPeripheral(peripheral))
end
function PeripheralAPIFake:listPeripherals()
local names = {}
for _, peripheral in pairs(self.peripherals) do
table.insert(names, getPeripheralName(peripheral))
end
return names
end
return PeripheralAPIFake

195
init.test.lua Normal file
View File

@ -0,0 +1,195 @@
local lust = require("lust").nocolor()
local PeripheralAPIMock = require("init")
local describe, it, expect = lust.describe, lust.it, lust.expect
local function newPeripheralStub(name, types, methodNames)
local peripheral = {}
local metaTypes = {}
for i, ty in ipairs(types) do
metaTypes[i] = ty
metaTypes[ty] = true
end
setmetatable(peripheral, {
__name = "peripheral",
name = name,
type = types[1],
types = metaTypes
})
for _, methodName in pairs(methodNames) do
peripheral[methodName] = function() end
end
return peripheral
end
local function newInventoryStub(type, id)
return newPeripheralStub(("%s_%d"):format(type, id), {type, "inventory"}, {
"size",
"list",
"getItemDetail",
"getItemLimit",
"pushItems",
"pullItems"
})
end
local function pprint(...)
local pretty = require("cc.pretty")
pretty.print(pretty.pretty(...))
end
describe("peripherals mock", function()
it("initialize with no peripherals by default", function()
local peripheral = PeripheralAPIMock.new()
expect(peripheral:listPeripherals()).to.equal{}
end)
it("initialize with initial peripherals", function()
local chest1 = newInventoryStub("minecraft:chest", 1)
local chest2 = newInventoryStub("minecraft:chest", 2)
local peripheral = PeripheralAPIMock.new{ chest1, chest2 }
expect(peripheral:listPeripherals()).to.equal{
"minecraft:chest_1",
"minecraft:chest_2",
}
end)
it("throw error if given table is not a valid peripheral", function()
local chest = { }
expect(function()
PeripheralAPIMock.new{ chest }
end).to.fail()
end)
it("check if peripheral is added", function()
local chest = newInventoryStub("minecraft:chest", 1)
local peripheral = PeripheralAPIMock.new{ chest }
expect(peripheral:hasPeripheral(chest)).to.be.truthy()
end)
it("check if peripheral is removed", function()
local chest = newInventoryStub("minecraft:chest", 1)
local peripheral = PeripheralAPIMock.new{ chest }
expect(peripheral:hasPeripheral(chest)).to.be.truthy()
peripheral:removePeripheral(chest)
expect(peripheral:hasPeripheral(chest)).to_not.be.truthy()
end)
it("listPeripherals returns a new list every time", function()
local peripheral = PeripheralAPIMock.new()
expect(peripheral:listPeripherals()).to_not.be(peripheral:listPeripherals())
end)
describe("external api", function()
local chest1, chest2, api
lust.before(function()
chest1 = newInventoryStub("minecraft:chest", 1)
chest2 = newInventoryStub("minecraft:chest", 2)
local peripheral = PeripheralAPIMock.new{ chest1, chest2 }
api = peripheral:newExternalAPI()
end)
it("has the same methods as the real api", function()
for methodName in pairs(peripheral) do
assert(type(api[methodName]) == "function", ("Missing method %s"):format(methodName))
end
expect(#api).to.be(#peripheral)
end)
it("using .call and .wrap, invoke the same function", function()
local spy = lust.spy(chest1, "size")
api.call("minecraft:chest_1", "size")
expect(#spy).to.be(1)
api.wrap("minecraft:chest_1").size()
expect(#spy).to.be(2)
end)
it(".call dosen't supply self to methods", function()
local spy = lust.spy(chest1, "size")
api.call("minecraft:chest_1", "size", 1, 2, 3)
expect(spy).to.equal{{1, 2, 3}}
end)
it(".call returns nil if peripheral was not found", function()
expect(api.call("foobar", "size")).to.be(nil)
end)
it(".call throws error if peripheral dosen't have requested method", function()
expect(function()
api.call("minecraft:chest_1", "foobar")
end).to.fail()
end)
it(".wrap returns a new table each time, but have the same contents", function()
local name = "minecraft:chest_1"
expect(api.wrap(name)).to.equal(api.wrap(name))
expect(api.wrap(name)).to_not.be(api.wrap(name))
end)
it(".wrap returns nil if peripheral was not found", function()
expect(api.wrap("foobar")).to.be(nil)
end)
it("check if peripheral exists if .isPresent", function()
expect(api.isPresent("minecraft:chest_1")).to.be(true)
expect(api.isPresent("foobar")).to.be(false)
end)
it(".getType throws error if given table is not a peripheral", function()
expect(function ()
api.getType({})
end).to.fail()
expect(function ()
api.getType(chest1)
end).to_not.fail()
end)
it(".getType returns multiple types if that peripheral has it", function()
local types = {api.getType("minecraft:chest_1")}
expect(types).to.equal{"minecraft:chest", "inventory"}
end)
it(".getNames returns a new table each time, but have the same contents", function()
expect(api.getNames()).to.equal(api.getNames())
expect(api.getNames()).to_not.be(api.getNames())
end)
it(".getName throws error if table is not a valid peripheral", function()
expect(function()
api.getName({})
end).to.fail()
expect(function()
api.getName(chest1)
end).to_not.fail()
end)
it(".getMethods returns nil if peripheral is not found", function()
expect(api.getMethods("foobar")).to.be(nil)
end)
it(".hasType returns nil if peripheral is not found", function()
expect(api.hasType("foobar", "inventory")).to.be(nil)
end)
it(".hasType returns true if peripheral has target type", function()
expect(api.hasType(chest1, "inventory")).to.be(true)
end)
it(".find returns a list of wrapped peripherals", function()
local peripherals = api.find("inventory")
expect(peripherals).to.equal{ chest1, chest2 }
end)
it(".find with filter returns a list of wrapped peripherals", function()
local peripherals = api.find("inventory", function(_, wrapped)
return wrapped.size ~= nil
end)
expect(peripherals).to.equal{ chest1, chest2 }
end)
end)
end)

231
lust.lua Normal file
View File

@ -0,0 +1,231 @@
-- lust v0.1.0 - Lua test framework
-- https://github.com/bjornbytes/lust
-- MIT LICENSE
local lust = {}
lust.level = 0
lust.passes = 0
lust.errors = 0
lust.befores = {}
lust.afters = {}
local red = string.char(27) .. '[31m'
local green = string.char(27) .. '[32m'
local normal = string.char(27) .. '[0m'
local function indent(level) return string.rep('\t', level or lust.level) end
function lust.nocolor()
red, green, normal = '', '', ''
return lust
end
function lust.describe(name, fn)
print(indent() .. name)
lust.level = lust.level + 1
fn()
lust.befores[lust.level] = {}
lust.afters[lust.level] = {}
lust.level = lust.level - 1
end
function lust.it(name, fn)
for level = 1, lust.level do
if lust.befores[level] then
for i = 1, #lust.befores[level] do
lust.befores[level][i](name)
end
end
end
local success, err = pcall(fn)
if success then lust.passes = lust.passes + 1
else lust.errors = lust.errors + 1 end
local color = success and green or red
local label = success and 'PASS' or 'FAIL'
print(indent() .. color .. label .. normal .. ' ' .. name)
if err then
print(indent(lust.level + 1) .. red .. err .. normal)
end
for level = 1, lust.level do
if lust.afters[level] then
for i = 1, #lust.afters[level] do
lust.afters[level][i](name)
end
end
end
end
function lust.before(fn)
lust.befores[lust.level] = lust.befores[lust.level] or {}
table.insert(lust.befores[lust.level], fn)
end
function lust.after(fn)
lust.afters[lust.level] = lust.afters[lust.level] or {}
table.insert(lust.afters[lust.level], fn)
end
-- Assertions
local function isa(v, x)
if type(x) == 'string' then
return type(v) == x,
'expected ' .. tostring(v) .. ' to be a ' .. x,
'expected ' .. tostring(v) .. ' to not be a ' .. x
elseif type(x) == 'table' then
if type(v) ~= 'table' then
return false,
'expected ' .. tostring(v) .. ' to be a ' .. tostring(x),
'expected ' .. tostring(v) .. ' to not be a ' .. tostring(x)
end
local seen = {}
local meta = v
while meta and not seen[meta] do
if meta == x then return true end
seen[meta] = true
meta = getmetatable(meta) and getmetatable(meta).__index
end
return false,
'expected ' .. tostring(v) .. ' to be a ' .. tostring(x),
'expected ' .. tostring(v) .. ' to not be a ' .. tostring(x)
end
error('invalid type ' .. tostring(x))
end
local function has(t, x)
for k, v in pairs(t) do
if v == x then return true end
end
return false
end
local function strict_eq(t1, t2)
if type(t1) ~= type(t2) then return false end
if type(t1) ~= 'table' then return t1 == t2 end
for k, _ in pairs(t1) do
if not strict_eq(t1[k], t2[k]) then return false end
end
for k, _ in pairs(t2) do
if not strict_eq(t2[k], t1[k]) then return false end
end
return true
end
local paths = {
[''] = { 'to', 'to_not' },
to = { 'have', 'equal', 'be', 'exist', 'fail' },
to_not = { 'have', 'equal', 'be', 'exist', 'fail', chain = function(a) a.negate = not a.negate end },
a = { test = isa },
an = { test = isa },
be = { 'a', 'an', 'truthy',
test = function(v, x)
return v == x,
'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to be equal',
'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to not be equal'
end
},
exist = {
test = function(v)
return v ~= nil,
'expected ' .. tostring(v) .. ' to exist',
'expected ' .. tostring(v) .. ' to not exist'
end
},
truthy = {
test = function(v)
return v,
'expected ' .. tostring(v) .. ' to be truthy',
'expected ' .. tostring(v) .. ' to not be truthy'
end
},
equal = {
test = function(v, x)
return strict_eq(v, x),
'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to be exactly equal',
'expected ' .. tostring(v) .. ' and ' .. tostring(x) .. ' to not be exactly equal'
end
},
have = {
test = function(v, x)
if type(v) ~= 'table' then
error('expected ' .. tostring(v) .. ' to be a table')
end
return has(v, x),
'expected ' .. tostring(v) .. ' to contain ' .. tostring(x),
'expected ' .. tostring(v) .. ' to not contain ' .. tostring(x)
end
},
fail = {
test = function(v)
return not pcall(v),
'expected ' .. tostring(v) .. ' to fail',
'expected ' .. tostring(v) .. ' to not fail'
end
}
}
function lust.expect(v)
local assertion = {}
assertion.val = v
assertion.action = ''
assertion.negate = false
setmetatable(assertion, {
__index = function(t, k)
if has(paths[rawget(t, 'action')], k) then
rawset(t, 'action', k)
local chain = paths[rawget(t, 'action')].chain
if chain then chain(t) end
return t
end
return rawget(t, k)
end,
__call = function(t, ...)
if paths[t.action].test then
local res, err, nerr = paths[t.action].test(t.val, ...)
if assertion.negate then
res = not res
err = nerr or err
end
if not res then
error(err or 'unknown failure', 2)
end
end
end
})
return assertion
end
function lust.spy(target, name, run)
local spy = {}
local subject
local function capture(...)
table.insert(spy, {...})
return subject(...)
end
if type(target) == 'table' then
subject = target[name]
target[name] = capture
else
run = name
subject = target or function() end
end
setmetatable(spy, {__call = function(_, ...) return capture(...) end})
if run then run() end
return spy
end
lust.test = lust.it
lust.paths = paths
return lust