diff --git a/rapanui-samples/multilayer/rn-multilayer-hud.lua b/rapanui-samples/multilayer/rn-multilayer-hud.lua new file mode 100644 index 0000000..d776898 --- /dev/null +++ b/rapanui-samples/multilayer/rn-multilayer-hud.lua @@ -0,0 +1,40 @@ +------------------------------------------------------------------------- +-- Date: 12/05/2012 +-- @Author: Marko Pukari +-- Multilayer is used to create HUD layer which stays stationary. +-- all objects added to he HUD layer will move with the camera movement +-- +-------------------------------------------------------------------------- + +-- variables to make our work easier +local screen = RNFactory.screen +local viewport = screen.viewport +local layers = screen.layers +local mainlayer = layers:get(RNFactory.MAIN_LAYER) + +-- create camera and place it to the main layer +local camera = MOAICamera2D.new () + +layers:get(RNLayer.MAIN_LAYER):setCamera(camera) + +--create new layer for hud +local hudlayer = layers:createLayerWithPartition(RNLayer.HUD_LAYER,viewport) + +--create backgroud +--if you don't give the layer to the create, the image will be added to the mainlayer +local background = RNFactory.createImageFrom("images/background-landscape-hd.png",mainlayer) +local gamegroup = RNGroup:new() + +--create new image to the game/mainlayer +local gameobject = RNFactory.createImageFrom("images/tile0.png",mainlayer) +gameobject.x = 100 +gameobject.y = 100 + +--create new image to the hudlayer +local hudobject = RNFactory.createImageFrom("images/tile1.png",hudlayer) +hudobject.x = 200 +hudobject.y = 300 + +--move camera. Hud image stays on the same place while the camera moves acros the +--background. Images on game/mainlayer stays where they where placed +camera:seekLoc(100,100,5) diff --git a/rapanui-sdk/RNFactory.lua b/rapanui-sdk/RNFactory.lua index bc67523..4f0971d 100644 --- a/rapanui-sdk/RNFactory.lua +++ b/rapanui-sdk/RNFactory.lua @@ -262,6 +262,10 @@ function RNFactory.createPageSwipe(name, params) end function RNFactory.createImage(image, params) + return RNFactory.createImageFrom(image,RNFactory.screen.layers:get(RNLayer.MAIN_LAYER),params) +end + +function RNFactory.createImageFrom(image, layer, params) local parentGroup, left, top @@ -295,7 +299,7 @@ function RNFactory.createImage(image, params) o.x = o.originalWidth / 2 + left o.y = o.originalHeight / 2 + top - RNFactory.screen:addRNObject(o) + RNFactory.screen:addRNObject(o,nil,layer) if parentGroup ~= nil then parentGroup:insert(o) diff --git a/rapanui-sdk/RNLayer.lua b/rapanui-sdk/RNLayer.lua new file mode 100644 index 0000000..c49d281 --- /dev/null +++ b/rapanui-sdk/RNLayer.lua @@ -0,0 +1,84 @@ +--[[ +-- +-- RapaNui +-- +-- by Ymobe ltd (http://ymobe.co.uk) +-- +-- LICENSE: +-- +-- RapaNui uses the Common Public Attribution License Version 1.0 (CPAL) http://www.opensource.org/licenses/cpal_1.0. +-- CPAL is an Open Source Initiative approved +-- license based on the Mozilla Public License, with the added requirement that you attribute +-- Moai (http://getmoai.com/) and RapaNui in the credits of your program. +]] + +-- Author: niom +-- Date: 11/25/12 + +RNLayer = { + LAYER_WITH_SAME_NAME_EXISTS = "layer with same name already exists", + MAIN_LAYER = "mainlayer" +} + +function RNLayer:new() + local layers = {} + setmetatable(layers,self) + self.__index = self + return layers +end + +function RNLayer:createLayer(name,viewport) + local layer = MOAILayer2D.new() + local index = table.getn(self) + 1 + + if self:get(name) then + return nil, RNLayer.LAYER_WITH_SAME_NAME_EXISTS + end + + self[index] = {} + self[index].layer = layer + self[index].name = name + layer:setViewport(viewport) + MOAISim.pushRenderPass(layer) + + return layer +end + +function RNLayer:get(name) + for i,container in pairs(self) do + if container.name == name then + return container.layer + end + end +end + +function RNLayer:createLayerWithPartition(name,viewport) + local layer = self:createLayer(name,viewport) + local partition = MOAIPartition.new() + layer:setPartition(partition) + return layer,partition +end + +function RNLayer:remove(layer) + for i, container in pairs(self) do + if container.layer == layer then + self:clearContainer(container) + table.remove(self, i) + end + end +end + +function RNLayer:removeAll() + while table.getn(self) > 0 do + self:clearContainer(self[1]) + table.remove(self, 1) + end +end + +function RNLayer:clearContainer(container) + container.layer:clear() + container.layer = nil + container.name = nil +end + +return RNLayer \ No newline at end of file diff --git a/rapanui-sdk/RNScreen.lua b/rapanui-sdk/RNScreen.lua index 9ef944f..4c0ecbb 100644 --- a/rapanui-sdk/RNScreen.lua +++ b/rapanui-sdk/RNScreen.lua @@ -27,6 +27,7 @@ function RNScreen:new(o) spriteIndex = 0, viewport = nil, layer = nil, + layers = nil, visible = true } @@ -52,24 +53,31 @@ function RNScreen:initWith(width, height, screenWidth, screenHeight) self.viewport:setSize(screenWidth, screenHeight) self.viewport:setScale(width, -height) self.viewport:setOffset(-1, 1) - self.layer = MOAILayer2D.new() + self.layers = RNLayer:new() + self.layer,self.mainPartition = self.layers:createLayerWithPartition(RNLayer.MAIN_LAYER,self.viewport) self.layer:setViewport(self.viewport) - self.mainPartition = MOAIPartition.new() self.layer:setPartition(self.mainPartition) MOAISim.pushRenderPass(self.layer) end - -function RNScreen:addRNObject(object, mode) +--[[ + layer parameter can be either partition or layer since + both MOAIObjects have the insertProp function. +--]] +function RNScreen:addRNObject(object, mode, layer) if object == nil then return end + if layer == nil then + layer = self.mainPartition + end + object:setLocatingMode(mode) - self.mainPartition:insertProp(object:getProp()) + layer:insertProp(object:getProp()) object:setParentScene(self) object:updateLocation() @@ -82,8 +90,13 @@ function RNScreen:addRNObject(object, mode) object:getProp().RNObject = object end -function RNScreen:removeRNObject(object) - self.layer:removeProp(object:getProp()) +function RNScreen:removeRNObject(object, layer) + + if(layer == nil) then + layer = self.layers:get(RNLayer.MAIN_LAYER) + end + + layer:removeProp(object:getProp()) -- local id = object.idInScreen -- local len = table.getn(self.sprites) -- local ind = id diff --git a/rapanui-sdk/rapanui.lua b/rapanui-sdk/rapanui.lua index 5ce81ac..83cb90a 100644 --- a/rapanui-sdk/rapanui.lua +++ b/rapanui-sdk/rapanui.lua @@ -23,7 +23,7 @@ RNButton = require("rapanui-sdk/RNButton") RNEvent = require("rapanui-sdk/RNEvent") RNScreen = require("rapanui-sdk/RNScreen") RNWrappedEventListener = require("rapanui-sdk/RNWrappedEventListener") - +RNLayer = require("rapanui-sdk/RNLayer") -- Touch Listeners requires diff --git a/rapanui-test/RNFactory-hooks.lua b/rapanui-test/RNFactory-hooks.lua new file mode 100644 index 0000000..e7d4b9e --- /dev/null +++ b/rapanui-test/RNFactory-hooks.lua @@ -0,0 +1,13 @@ +-- Author: Marko Pukari +-- Date: 12/2/12 + +function suite_setup() + require("RNFactory") +end + +function suite_teardown() +end + +function test_ok() + assert_true(true) +end diff --git a/rapanui-test/RNFactoryTest.lua b/rapanui-test/RNFactoryTest.lua new file mode 100644 index 0000000..b193255 --- /dev/null +++ b/rapanui-test/RNFactoryTest.lua @@ -0,0 +1,102 @@ +-- Author: Marko Pukari +-- Date: 12/2/12 + +package.path = package.path .. ";../?.lua;lunatest/?.lua;mockobjects/?.lua;../rapanui-sdk/?.lua" +require('lunatest') +require('lunahamcrest') +require('MockRNLayer') +require('MockRNScreen') +require('MockRNGroup') +require('MockMOAISim') +require('MockRNInputManager') +require('MockConstants') +require('MockRNObject') +require('MockLayer') + +lunatest.suite("RNFactory-hooks") + + +RNLayer = createMockRNLayer(MockConstants.MAIN_LAYER) +RNGroup = createMockRNGroup() +RNScreen = createMockRNScreen() +MOAISim = createMockMOAISim() +RNInputManager = createMockRNInputManager() +RNObject = createRNObject() + +MOAIEnvironment = { + screenWidth = MockConstants.SCREENWIDTH, + screenHeight = MockConstants.SCREENHEIGHT +} + +config = { + stretch = {} +} +function init() + RNObject:reset() +end + +--RNFactory default creation +function testRNScreenIsCreatedWhenRNFactoryIsCreated() + assert_that(RNScreen.newCalled,is(greater_than(0))) +end + +function testThatRNGroupIsCreateWhenRNFactoryIsCreated() + assert_that(RNGroup.newCalled,is(greater_than(0))) +end + +function testThatCreateRNFactoryOpensNewMOAISimWindow() + assert_that(MOAISim.openWindowCalled,is(greater_than(0))) +end + +function testThatRNScreenInitWithIsCalled() + assert_that(RNScreen.initWithCalled,is(greater_than(0))) + local params = RNScreen.initWithParams +end + +function testThatSetGlobalRNScreenIsCalled() + assert_that(MockRNInputManager.setGlobalRNScreenCalled,is(greater_than(0))) +end + +--RNFactory:createImage() + +function testThatCreateImageCreatesNewObject() + init() + RNFactory.createImage("img") + assert_that(RNObject.newCalled,is(greater_than(0))) +end + +function testThatCreateImageInitsTheCreatedObject() + init() + RNFactory.createImage("img") + assert_that(RNObject.initWithImage2Called,is(greater_than(0))) +end + +function testThatCreateImageAddsObjectToTheScreen() + init() + RNFactory.createImage("img") + assert_that(RNScreen.addRNObjectCalled,is(greater_than(0))) +end + +function testThatCreateImageAddsObjectToTheMainGroup() + init() + RNFactory.createImage("img") + assert_that(RNGroup.insertCalled,is(greater_than(0))) +end + +function testThatCreateImageReturnsProperDeckAndRNObject() + init() + local rnobject,deck = RNFactory.createImage("img") + assert_not_nil(rnobject) + assert_not_nil(deck) + assert_true(rnobject == RNObject) +end + +function testThatCreateImageFromReturnsProperDeckAndRNObject() + init() + local rnobject,deck = RNFactory.createImageFrom("img") + assert_not_nil(rnobject) + assert_not_nil(deck) + assert_true(rnobject == RNObject) +end + +lunatest.run() diff --git a/rapanui-test/RNLayerTest.lua b/rapanui-test/RNLayerTest.lua new file mode 100644 index 0000000..3e9704b --- /dev/null +++ b/rapanui-test/RNLayerTest.lua @@ -0,0 +1,149 @@ +-- Author: Marko Pukari +-- Date: 11/25/12 + +package.path = package.path .. ";../?.lua;lunatest/?.lua;mockobjects/?.lua;../rapanui-sdk/?.lua" +require('lunatest') +require('lunahamcrest') +require('RNLayer') +require('MockPartition') +require('MockViewport') +require('MockLayer') +require('MockMOAILayer2D') +require('MockMOAISim') +require('MockMOAIPartition') + +--MOCK OBJECTS +VIEWPORT = createViewport("viewport") +TEST_PARTITION = createPartition("TEST_PARTITION") +TEST_LAYER = createTestLayer("TEST_LAYER",VIEWPORT,TEST_PARTITION) +TEST_LAYER2 = createTestLayer("TEST_LAYER2",VIEWPORT,TEST_PARTITION) + +--Mocked MOIA classes +MOAILayer2D = createMockMOAILayer2D(TEST_LAYER,TEST_LAYER2) +MOAISim = createMockMOAISim() +MOAIPartition = createMockMOAIPartition(TEST_PARTITION) + +local function init() + MOAILayer2D:reset() + MOAISim:reset() + MOAIPartition:reset() + return RNLayer:new() +end + +function testThatCreateShouldCreateNewLayer() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + assert_that(MOAILayer2D.newCalled,is(greater_than(0))) + assert_that(table.getn(rnlayer),equal_to(1)) +end + +function testThatCreatingMultipleLayersIncreaseContainerSize() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + rnlayer:createLayer("test2",VIEWPORT) + assert_that(table.getn(rnlayer),equal_to(2)) +end + +function testThatCreateShouldReturnTheNewLayer() + local rnlayer = init() + returnedLayer = rnlayer:createLayer("test",VIEWPORT) + assert_that(returnedLayer.name,is(equal_to(TEST_LAYER.name))) +end + +function testThatViewportIsAddedToTheCreatedLayer() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + assert_that(TEST_LAYER.setViewportCalled,is(greater_than(0))) +end + +function testThatCreatedLayerIsPushedToTheMOAISim() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + assert_that(MOAISim.pushRenderPassCalled,is(greater_than(0))) +end + +function testThatCreatedLayerIsFoundByName() + local rnlayer = init() + returnedLayer = rnlayer:createLayer("test",VIEWPORT) + assert_that(rnlayer:get("test").name,is(equal_to(TEST_LAYER.name))) +end + +function testThatAllCreatedLayersAreFoundByName() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + rnlayer:createLayer("test2",VIEWPORT) + assert_that(rnlayer:get("test").name,is(equal_to(TEST_LAYER.name))) + assert_that(rnlayer:get("test2").name,is(equal_to(TEST_LAYER2.name))) +end + +function testThatCannotCreateLayerWithSameName() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + rnlayer:createLayer("test",VIEWPORT) + assert_that(table.getn(rnlayer),equal_to(1)) +end + +function testThatCreateLayerWithSameNameReturnsNilAndErrorMessage() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + returnedlayer,msg = rnlayer:createLayer("test",VIEWPORT) + assert_nil(returnedlayer) + assert_that(msg,is(equal_to(rnlayer.LAYER_WITH_SAME_NAME_EXISTS))) +end + +function testThatLayerCanBeCreatedWithPartition() + local rnlayer = init() + returnnedLayer,returnnedPartition = rnlayer:createLayerWithPartition("test",VIEWPORT) + assert_that(MOAILayer2D.newCalled,is(greater_than(0))) + assert_that(MOAISim.pushRenderPassCalled,is(greater_than(0))) + assert_that(MOAIPartition.newCalled,is(greater_than(0))) + assert_that(TEST_LAYER.setPartitionCalled ,is(greater_than(0))) + assert_that(returnedLayer.name,is(equal_to(TEST_LAYER.name))) + assert_that(returnnedPartition.name,is(equal_to(TEST_PARTITION.name))) +end + +function testThatLayerCanBeRemoved() + local rnlayer = init() + returnedLayer = rnlayer:createLayer("test",VIEWPORT) + returnedLayer2 = rnlayer:createLayer("test2",VIEWPORT) + assert_that(table.getn(rnlayer),equal_to(2)) + + rnlayer:remove(returnedLayer2) + assert_that(table.getn(rnlayer),equal_to(1)) + assert_nil(rnlayer:get("test2")) + assert_not_nil(rnlayer:get("test")) +end + +function testThatLayerContainerIsProperlyCleared() + local rnlayer = init() + returnedLayer = rnlayer:createLayer("test",VIEWPORT) + container = rnlayer[1] + + assert_not_nil(container.name) + assert_not_nil(container.layer) + + rnlayer:remove(returnedLayer) + + assert_nil(container.name) + assert_nil(container.layer) +end + +function testThatLayerIsClearedWhenLayerIsRemoved() + local rnlayer = init() + returnedLayer = rnlayer:createLayer("test",VIEWPORT) + rnlayer:remove(returnedLayer) + assert_that(TEST_LAYER.clearCalled,is(greater_than(0))) +end + +function testThatAllLayersAreDeletedAtOnce() + local rnlayer = init() + rnlayer:createLayer("test",VIEWPORT) + rnlayer:createLayer("test2",VIEWPORT) + assert_that(table.getn(rnlayer),equal_to(2)) + + rnlayer:removeAll() + assert_that(table.getn(rnlayer),equal_to(0)) + +end + +lunatest.run() \ No newline at end of file diff --git a/rapanui-test/RNScreenTest.lua b/rapanui-test/RNScreenTest.lua new file mode 100644 index 0000000..9da8e6f --- /dev/null +++ b/rapanui-test/RNScreenTest.lua @@ -0,0 +1,221 @@ +-- Author: Marko Pukari +-- Date: 11/25/12 + +package.path = package.path .. ";../?.lua;lunatest/?.lua;mockobjects/?.lua;../rapanui-sdk/?.lua" +require('lunatest') +require('lunahamcrest') +require('RNScreen') +require('RNLayer') + +require('MockPartition') +require('MockViewport') +require('MockLayer') +require('MockRNObject') +require('MockMOAILayer2D') +require('MockMOAISim') +require('MockMOAIViewport') +require('MockMOAIPartition') + +local MVC = MockConstants + +--Mocked Objects +local PROP = {name="prop1"} +local PROP2 = {name="prop2"} + +local VIEWPORT = createViewport("viewport") +local TEST_PARTITION=createPartition("TEST_PARTITION") +local TEST_PARTITION2=createPartition("TEST_PARTITION2") +local TEST_LAYER=createTestLayer("TEST_LAYER",VIEWPORT,TEST_PARTITION) +local TEST_LAYER2=createTestLayer("TEST_LAYER2",VIEWPORT,TEST_PARTITION2) +local RNOBJECT = createRNObject("RNOBJECT",PROP) +local RNOBJECT2 = createRNObject("RNOBJECT2",PROP2) + +--Mocked MOIA classes +MOAILayer2D = createMockMOAILayer2D(TEST_LAYER,TEST_LAYER2) +MOAISim = createMockMOAISim() +MOAIViewport = createMockMOAIViewport(VIEWPORT) +MOAIPartition = createMockMOAIPartition(TEST_PARTITION) + +--Initialization + +RNLayer:new() + +local function init() + VIEWPORT:reset() + TEST_PARTITION:reset() + TEST_LAYER:reset() + TEST_LAYER2:reset() + + MOAILayer2D:reset() + MOAISim:reset() + MOAIViewport:reset() + MOAIPartition:reset() + + return RNScreen:new() +end + +--TESTS + +--RNScreen:initWith +function testThatScreenSizeIsSetCorrectly() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(rnscreen.width,is(equal_to(MockConstants.WIDTH))) + assert_that(rnscreen.height,is(equal_to(MockConstants.HEIGHT))) +end + +function testThatViewportIsCreated() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(MOAIViewport.newCalled,is(greater_than(0))) +end + +function testThatViewportSizeIsSet() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(VIEWPORT.setSizeCalled,is(greater_than(0))) +end + +function testThatViewportScaleIsSet() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(VIEWPORT.setScaleCalled,is(greater_than(0))) +end + +function testThatViewportOffsetIsSet() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(VIEWPORT.setOffsetCalled,is(greater_than(0))) +end + +function testThatLayerIsCreated() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(MOAILayer2D.newCalled,is(greater_than(0))) +end + +function testThatViewportIsSetToLayer() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(TEST_LAYER.setViewportCalled,is(greater_than(0))) +end + +function testThatNewPartitionIsCreated() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(MOAIPartition.newCalled,is(greater_than(0))) +end + +function testThatNewPartitionIsSetToScreenMainPartitiob() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(rnscreen.mainPartition.name,is(equal_to(TEST_PARTITION.name))) +end + +function testThatPartitionIsSetToLayer() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(TEST_LAYER.setPartitionCalled,is(greater_than(0))) +end + +function testThatLayerIsPushedToMoaiSim() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_that(MOAISim.pushRenderPassCalled,is(greater_than(0))) +end + +function testThatLayersAreStoredToScreen() + local rnscreen = init() + assert_nil(rnscreen.layers) + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_not_nil(rnscreen.layers) +end + +function testThatMainPartitionIsFoundFromLayers() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + assert_not_nil(rnscreen.layers:get(RNLayer.MAIN_LAYER)) +end + +--RNScreen:addRNObject +function testThatObjectLocationModeIsSet() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + rnscreen:addRNObject(RNOBJECT,nil,TEST_LAYER) + assert_that(RNOBJECT.setLocatingModeCalled,is(greater_than(0))) +end + +function testThatTheObjectIsAddedToPartition() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + rnscreen:addRNObject(RNOBJECT, nil, TEST_PARTITION) + assert_that(TEST_PARTITION.insertPropCalled,is(greater_than(0))) +end + +function testThatTheObjectIsAddedToMainPartitionIfNoLayerIsGiven() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + rnscreen:addRNObject(RNOBJECT) + assert_that(TEST_PARTITION.insertPropCalled,is(greater_than(0))) +end + +function testThatTheObjectIsAddedToGivenLayer() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + + rnscreen:addRNObject(RNOBJECT, nil, TEST_LAYER) + assert_that(TEST_LAYER.insertPropCalled,is(greater_than(0))) + + rnscreen:addRNObject(RNOBJECT2, nil, TEST_LAYER2) + assert_that(TEST_LAYER2.insertPropCalled,is(greater_than(0))) + +end + +function testThatTheObjectParentSceneIsSet() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + rnscreen:addRNObject(RNOBJECT, nil, TEST_LAYER) + assert_that(RNOBJECT.setParentSceneCalled,is(greater_than(0))) +end + +function testThatObjectUpdateLocationIsCalled() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + rnscreen:addRNObject(RNOBJECT, nil, TEST_LAYER) + assert_that(RNOBJECT.updateLocationCalled,is(greater_than(0))) +end + +--RNScreen:removeRNObject() +function testThatRemovedPropIsCalled() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + rnscreen:addRNObject(RNOBJECT) + rnscreen:addRNObject(RNOBJECT2) + rnscreen:removeRNObject(RNOBJECT) + assert_that(rnscreen.mainPartition.removePropCalled,is(greater_than(0))) +end + +function testThatObjectIsRemovedFromMainLayerIfNoLayerIsGivenToRemove() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + local mainlayer = rnscreen.layers:get(RNLayer.MAIN_LAYER) + local mainpartition = mainlayer.MOAIPARTITION + rnscreen:removeRNObject(RNOBJECT) + assert_that(mainpartition.removePropCalled,is(greater_than(0))) +end + +function testThatObjectIsRemovedFromGivenLayer() + local rnscreen = init() + rnscreen:initWith(MockConstants.WIDTH, MockConstants.HEIGHT, MockConstants.SCREENWIDTH, MockConstants.SCREENHEIGHT) + local mainlayer = rnscreen.layers:get(RNLayer.MAIN_LAYER) + local mainpartition = mainlayer.MOAIPARTITION + local otherpartition = TEST_LAYER2.MOAIPARTITION + rnscreen:addRNObject(RNOBJECT) + rnscreen:addRNObject(RNOBJECT2,nil,TEST_LAYER2) + rnscreen:removeRNObject(RNOBJECT2, TEST_LAYER2) + assert_that(mainpartition.removePropCalled,is(equal_to(0))) + assert_that(otherpartition.removePropCalled,is(greater_than(0))) + +end + +lunatest.run() \ No newline at end of file diff --git a/rapanui-test/lunatest/.gitignore b/rapanui-test/lunatest/.gitignore new file mode 100644 index 0000000..5a2f6e0 --- /dev/null +++ b/rapanui-test/lunatest/.gitignore @@ -0,0 +1,4 @@ +*.iml +.idea/ +out/ + diff --git a/rapanui-test/lunatest/README.md b/rapanui-test/lunatest/README.md new file mode 100644 index 0000000..715a075 --- /dev/null +++ b/rapanui-test/lunatest/README.md @@ -0,0 +1,28 @@ +Lunatest is an xUnit-style, Lua-based unit testing framework with +additional support for randomized testing (a la QuickCheck). + +It's largely upwardly compatible from [lunit][], with the following changes: + +* Where lunit uses assert(), lunatest uses assert_true(). lunatest does + not change any functions from the standard library. +* If running tests in only one file, no module declaration is necessary. +* For multiple suites, register them with lunatest.suite("file"). + This uses require to load the suite, and uses the same methods to + match filenames with modules. +* It doesn't have any dependencies except Lua, though if present, it + will use lhf's [lrandom][] module (for consistent pseudorandom numbers + across operating systems) and [luasocket][]'s gettime() for timestamps). + +The main (or only) test file should end in lunatest.run(), and can be +run as a normal lua script. The following command-line arguments are +supported: + +* -v: verbose mode, which lists every test's name, result, and runtime. +* -s / --suite *pattern*: Only run suite(s) with names matching the pattern. +* -t / --test *pattern*: Only run test(s) with names matching the pattern. + +[lunit]: http://www.nessie.de/mroth/lunit/ +[lrandom]: http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lrandom +[luasocket]: http://luaforge.net/projects/luasocket/ + +For further examples, see the API documentation and included test suite. diff --git a/rapanui-test/lunatest/files/lunatest.html b/rapanui-test/lunatest/files/lunatest.html new file mode 100644 index 0000000..a5045e2 --- /dev/null +++ b/rapanui-test/lunatest/files/lunatest.html @@ -0,0 +1,1505 @@ + + + + Reference + + + + + +
+ +
+ +
+
+
+ +
+ + + +
+ +

File lunatest.lua

+ + +

Copyright (c) 2009-12 Scott Vokes Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. This is a library for randomized testing with Lua. For usage and examples, see README and the test suite.

+ + + + + + +

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
assert_boolean (val, msg)Test that val is a boolean.
assert_equal (exp, got, tol, msg)exp == got.
assert_error (f, msg)Test that the function raises an error when called.
assert_false (got, msg)got == false.
assert_function (val, msg)Test that val is a function.
assert_gt (lim, val, msg)val > lim.
assert_gte (lim, val, msg)val >= lim.
assert_len (len, val, msg)#val == len.
assert_lt (lim, val, msg)val < lim.
assert_lte (lim, val, msg)val <= lim.
assert_match (pat, s, msg)Test that the string s matches the pattern exp.
assert_metatable (exp, val, msg)Test that a value has the expected metatable.
assert_not_boolean (val, msg)Test that val is not a boolean.
assert_not_equal (exp, got, msg)exp ~= got.
assert_not_function (val, msg)Test that val is not a function.
assert_not_len (len, val, msg)#val ~= len.
assert_not_match (pat, s, msg)Test that the string s doesn't match the pattern exp.
assert_not_metatable (exp, val, msg)Test that a value does not have a given metatable.
assert_not_number (val, msg)Test that val is not a number.
assert_not_string (val, msg)Test that val is not a string.
assert_not_table (val, msg)Test that val is not a table.
assert_not_thread (val, msg)Test that val is not a thread (coroutine).
assert_not_userdata (val, msg)Test that val is not a userdata (light or heavy).
assert_number (val, msg)Test that val is a number.
assert_random (opt, f, ...)Run a test case with randomly instantiated arguments, running the test function f opt.count (default: 100) times.
assert_string (val, msg)Test that val is a string.
assert_table (val, msg)Test that val is a table.
assert_thread (val, msg)Test that val is a thread (coroutine).
assert_true (got, msg)got == true.
assert_userdata (val, msg)Test that val is a userdata (light or heavy).
fail (msg, no_exit)Fail a test.
is_test_key (k)Check if a function name should be considered a test key.
random_bool ()Get a random bool.
random_float (low, high)Get a random float low <= x < high.
random_int (low, high)Get a random value low <= x <= high.
run (hooks, suite_filter)Run all known test suites, with given configuration hooks.
set_seed (s)Set random seed.
skip (msg)Skip a test, with a note, e.g.
suite (modname)Add a file as a test suite.
+ + + + + + +
+
+ + + + +

Functions

+
+ + + +
assert_boolean (val, msg)
+
+Test that val is a boolean. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_equal (exp, got, tol, msg)
+
+exp == got. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + got: +
  • + +
  • + tol: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_error (f, msg)
+
+Test that the function raises an error when called. + + +

Parameters

+
    + +
  • + f: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_false (got, msg)
+
+got == false. + + +

Parameters

+
    + +
  • + got: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_function (val, msg)
+
+Test that val is a function. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_gt (lim, val, msg)
+
+val > lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_gte (lim, val, msg)
+
+val >= lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_len (len, val, msg)
+
+#val == len. + + +

Parameters

+
    + +
  • + len: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_lt (lim, val, msg)
+
+val < lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_lte (lim, val, msg)
+
+val <= lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_match (pat, s, msg)
+
+Test that the string s matches the pattern exp. + + +

Parameters

+
    + +
  • + pat: +
  • + +
  • + s: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_metatable (exp, val, msg)
+
+Test that a value has the expected metatable. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_boolean (val, msg)
+
+Test that val is not a boolean. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_equal (exp, got, msg)
+
+exp ~= got. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + got: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_function (val, msg)
+
+Test that val is not a function. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_len (len, val, msg)
+
+#val ~= len. + + +

Parameters

+
    + +
  • + len: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_match (pat, s, msg)
+
+Test that the string s doesn't match the pattern exp. + + +

Parameters

+
    + +
  • + pat: +
  • + +
  • + s: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_metatable (exp, val, msg)
+
+Test that a value does not have a given metatable. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_number (val, msg)
+
+Test that val is not a number. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_string (val, msg)
+
+Test that val is not a string. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_table (val, msg)
+
+Test that val is not a table. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_thread (val, msg)
+
+Test that val is not a thread (coroutine). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_userdata (val, msg)
+
+Test that val is not a userdata (light or heavy). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_number (val, msg)
+
+Test that val is a number. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_random (opt, f, ...)
+
+Run a test case with randomly instantiated arguments, running the test function f opt.count (default: 100) times. + + +

Parameters

+
    + +
  • + opt: A table with options, or just a test name string.
    opt.count: how many random trials to perform
    opt.seed: Start the batch of trials with a specific seed
    opt.always: Always test these seeds (for regressions)
    opt.show_progress: Whether to print a . after every opt.tick trials.
    opt.seed_limit: Max seed to allow.
    opt.max_failures, max_errors, max_skips: Give up after X of each.
    +
  • + +
  • + f: A test function, run as f(unpack(randomized_args(...))) +
  • + +
  • + ...: the arg specification. For each argument, creates a random instance of that type.
    boolean: return true or false
    number n: returns 0 <= x < n, or -n <= x < n if negative. If n has a decimal component, so will the result.
    string: Specifiedd as "(len[,maxlen]) (pattern)".
    "10 %l" means 10 random lowercase letters.
    "10,30 [aeiou]" means between 10-30 vowels.
    function: Just call (as f()) and return result.
    table or userdata: Call v.__random() and return result.
    @usage +
  • + +
+ + + + + + + + +
+ + + + +
assert_string (val, msg)
+
+Test that val is a string. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_table (val, msg)
+
+Test that val is a table. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_thread (val, msg)
+
+Test that val is a thread (coroutine). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_true (got, msg)
+
+got == true. (Named "assert_true" to not conflict with standard assert.) + + +

Parameters

+
    + +
  • + got: +
  • + +
  • + msg: Message to display with the result. +
  • + +
+ + + + + + + + +
+ + + + +
assert_userdata (val, msg)
+
+Test that val is a userdata (light or heavy). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
fail (msg, no_exit)
+
+Fail a test. + + +

Parameters

+
    + +
  • + msg: +
  • + +
  • + no_exit: Unless set to true, the presence of any failures causes the test suite to terminate with an exit status of 1. +
  • + +
+ + + + + + + + +
+ + + + +
is_test_key (k)
+
+Check if a function name should be considered a test key. Defaults to functions starting or ending with "test", with leading underscores allowed. + + +

Parameters

+
    + +
  • + k: +
  • + +
+ + + + + + + + +
+ + + + +
random_bool ()
+
+Get a random bool. + + + + + + + + + +
+ + + + +
random_float (low, high)
+
+Get a random float low <= x < high. + + +

Parameters

+
    + +
  • + low: +
  • + +
  • + high: +
  • + +
+ + + + + + + + +
+ + + + +
random_int (low, high)
+
+Get a random value low <= x <= high. + + +

Parameters

+
    + +
  • + low: +
  • + +
  • + high: +
  • + +
+ + + + + + + + +
+ + + + +
run (hooks, suite_filter)
+
+Run all known test suites, with given configuration hooks. + + +

Parameters

+
    + +
  • + hooks: Override the default hooks. +
  • + +
  • + suite_filter: If set, only run suite(s) with names matching this pattern. +
  • + +
+ + + + +

Usage:

+If no hooks are provided and arg[1] == "-v", the verbose_hooks will be used. + + + + + +
+ + + + +
set_seed (s)
+
+Set random seed. + + +

Parameters

+
    + +
  • + s: +
  • + +
+ + + + + + + + +
+ + + + +
skip (msg)
+
+Skip a test, with a note, e.g. "TODO". + + +

Parameters

+
    + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
suite (modname)
+
+Add a file as a test suite. + + +

Parameters

+
    + +
  • + modname: The module to load as a suite. The file is interpreted in the same manner as require "modname". Which functions are tests is determined by is_test_key(name). +
  • + +
+ + + + + + + + +
+ + +
+ + + + + + + +
+ +
+ +
+

Valid XHTML 1.0!

+
+ +
+ + diff --git a/rapanui-test/lunatest/index.html b/rapanui-test/lunatest/index.html new file mode 100644 index 0000000..a5c48de --- /dev/null +++ b/rapanui-test/lunatest/index.html @@ -0,0 +1,104 @@ + + + + Reference + + + + + +
+ +
+ +
+
+
+ +
+ + + +
+ + + +

Modules

+ + + + + + + + +
lunatestUnit testing module, with extensions for random testing.
+ + + + + +

Files

+ + + + + + + + +
lunatest.lua
+ + +
+ +
+ +
+

Valid XHTML 1.0!

+
+ +
+ + diff --git a/rapanui-test/lunatest/luadoc.css b/rapanui-test/lunatest/luadoc.css new file mode 100644 index 0000000..bc0f98a --- /dev/null +++ b/rapanui-test/lunatest/luadoc.css @@ -0,0 +1,286 @@ +body { + margin-left: 1em; + margin-right: 1em; + font-family: arial, helvetica, geneva, sans-serif; + background-color:#ffffff; margin:0px; +} + +code { + font-family: "Andale Mono", monospace; +} + +tt { + font-family: "Andale Mono", monospace; +} + +body, td, th { font-size: 11pt; } + +h1, h2, h3, h4 { margin-left: 0em; } + +textarea, pre, tt { font-size:10pt; } +body, td, th { color:#000000; } +small { font-size:0.85em; } +h1 { font-size:1.5em; } +h2 { font-size:1.25em; } +h3 { font-size:1.15em; } +h4 { font-size:1.06em; } + +a:link { font-weight:bold; color: #004080; text-decoration: none; } +a:visited { font-weight:bold; color: #006699; text-decoration: none; } +a:link:hover { text-decoration:underline; } +hr { color:#cccccc } +img { border-width: 0px; } + + +h3 { padding-top: 1em; } + +p { margin-left: 1em; } + +p.name { + font-family: "Andale Mono", monospace; + padding-top: 1em; + margin-left: 0em; +} + +blockquote { margin-left: 3em; } + +pre.example { + background-color: rgb(245, 245, 245); + border-top-width: 1px; + border-right-width: 1px; + border-bottom-width: 1px; + border-left-width: 1px; + border-top-style: solid; + border-right-style: solid; + border-bottom-style: solid; + border-left-style: solid; + border-top-color: silver; + border-right-color: silver; + border-bottom-color: silver; + border-left-color: silver; + padding: 1em; + margin-left: 1em; + margin-right: 1em; + font-family: "Andale Mono", monospace; + font-size: smaller; +} + + +hr { + margin-left: 0em; + background: #00007f; + border: 0px; + height: 1px; +} + +ul { list-style-type: disc; } + +table.index { border: 1px #00007f; } +table.index td { text-align: left; vertical-align: top; } +table.index ul { padding-top: 0em; margin-top: 0em; } + +table { + border: 1px solid black; + border-collapse: collapse; + margin-left: auto; + margin-right: auto; +} +th { + border: 1px solid black; + padding: 0.5em; +} +td { + border: 1px solid black; + padding: 0.5em; +} +div.header, div.footer { margin-left: 0em; } + +#container +{ + margin-left: 1em; + margin-right: 1em; + background-color: #f0f0f0; +} + +#product +{ + text-align: center; + border-bottom: 1px solid #cccccc; + background-color: #ffffff; +} + +#product big { + font-size: 2em; +} + +#product_logo +{ +} + +#product_name +{ +} + +#product_description +{ +} + +#main +{ + background-color: #f0f0f0; + border-left: 2px solid #cccccc; +} + +#navigation +{ + float: left; + width: 18em; + margin: 0; + vertical-align: top; + background-color: #f0f0f0; + overflow:visible; +} + +#navigation h1 { + background-color:#e7e7e7; + font-size:1.1em; + color:#000000; + text-align:left; + margin:0px; + padding:0.2em; + border-top:1px solid #dddddd; + border-bottom:1px solid #dddddd; +} + +#navigation ul +{ + font-size:1em; + list-style-type: none; + padding: 0; + margin: 1px; +} + +#navigation li +{ + text-indent: -1em; + margin: 0em 0em 0em 0.5em; + display: block; + padding: 3px 0px 0px 12px; +} + +#navigation li li a +{ + padding: 0px 3px 0px -1em; +} + +#content +{ + margin-left: 18em; + padding: 1em; + border-left: 2px solid #cccccc; + border-right: 2px solid #cccccc; + background-color: #ffffff; +} + +#about +{ + clear: both; + margin: 0; + padding: 5px; + border-top: 2px solid #cccccc; + background-color: #ffffff; +} + +@media print { + body { + font: 12pt "Times New Roman", "TimeNR", Times, serif; + } + a { font-weight:bold; color: #004080; text-decoration: underline; } + + #main { background-color: #ffffff; border-left: 0px; } + #container { margin-left: 2%; margin-right: 2%; background-color: #ffffff; } + + #content { margin-left: 0px; padding: 1em; border-left: 0px; border-right: 0px; background-color: #ffffff; } + + #navigation { display: none; + } + pre.example { + font-family: "Andale Mono", monospace; + font-size: 10pt; + page-break-inside: avoid; + } +} + +table.module_list td +{ + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.module_list td.name { background-color: #f0f0f0; } +table.module_list td.summary { width: 100%; } + +table.file_list +{ + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.file_list td +{ + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.file_list td.name { background-color: #f0f0f0; } +table.file_list td.summary { width: 100%; } + + +table.function_list +{ + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.function_list td +{ + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.function_list td.name { background-color: #f0f0f0; } +table.function_list td.summary { width: 100%; } + + +table.table_list +{ + border-width: 1px; + border-style: solid; + border-color: #cccccc; + border-collapse: collapse; +} +table.table_list td +{ + border-width: 1px; + padding: 3px; + border-style: solid; + border-color: #cccccc; +} +table.table_list td.name { background-color: #f0f0f0; } +table.table_list td.summary { width: 100%; } + +dl.function dt {border-top: 1px solid #ccc; padding-top: 1em;} +dl.function dd {padding-bottom: 1em;} +dl.function h3 {padding: 0; margin: 0; font-size: medium;} + +dl.table dt {border-top: 1px solid #ccc; padding-top: 1em;} +dl.table dd {padding-bottom: 1em;} +dl.table h3 {padding: 0; margin: 0; font-size: medium;} + +#TODO: make module_list, file_list, function_list, table_list inherit from a list + diff --git a/rapanui-test/lunatest/lunahamcrest.lua b/rapanui-test/lunatest/lunahamcrest.lua new file mode 100644 index 0000000..c80be3e --- /dev/null +++ b/rapanui-test/lunatest/lunahamcrest.lua @@ -0,0 +1,84 @@ +-- +--- @author Janne Sinivirta +-- + +-- required core global functions +local type = type +local string = string + +-- ############## +-- # Matchers # +-- ############## + +--- Matcher to check == equality +function equal_to(object) + return { + matches = function(value) return object == value end, + describe = "equal to " .. object + } +end + +--- Purely syntactic sugar. You can for example say assert_that(2, is(equal_to(2))) +function is(matcher) + return { + matches = function(value) return matcher.matches(value) end, + describe = matcher.describe + } +end + +--- Matcher to negate the result of enclosed Matcher +function is_not(matcher) + return { + matches = function(value) return not matcher.matches(value) end, + describe = "not " .. matcher.describe + } +end + +--- Matcher for nil. Should be named nil but that is of course a reserved word +function is_nil() + return { + matches = function(value) return value == nil end, + describe = "nil" + } +end + +--- Matcher to check == equality +function of_type(expected_type) + return { + matches = function(value) + return type(value) == expected_type, type(value) + end, + describe = "type " .. expected_type + } +end + +--- Matcher to check if string contains a given substring +function contains_string(substring) + return { + matches = function(value) return type(value) == "string" and type(substring) == "string" and string.find(value, substring) ~= nil end, + describe = "contains " .. substring + } +end + +--- Matcher to check if string starts with a given substring +function starts_with(substring) + return { + matches = function(value) return type(value) == "string" and type(substring) == "string" and string.find(value, substring) == 1 end, + describe = "starts with " .. substring + } +end + +--- Matcher to check == equality +function equals_ignoring_case(object) + return { + matches = function(value) return type(value) == "string" and type(object) == "string" and string.lower(object) == string.lower(value) end, + describe = "equal to " .. object .. " ignoring case" + } +end + +function greater_than(object) + return { + matches = function(value) return object < value end, + describe = "greater than " .. object + } +end diff --git a/rapanui-test/lunatest/lunatest-0.9.1-0.rockspec b/rapanui-test/lunatest/lunatest-0.9.1-0.rockspec new file mode 100644 index 0000000..93b3e14 --- /dev/null +++ b/rapanui-test/lunatest/lunatest-0.9.1-0.rockspec @@ -0,0 +1,29 @@ +package = "lunatest" +version = "0.9.1-0" +source = { + url = "git://github.com/silentbicycle/lunatest.git", + tag = "v0.9.1" +} +description = { + summary = "xUnit-style + randomized unit testing framework", + detailed = [[ + +Lunatest is an xUnit-style unit testing framework, with +additional support for randomized testing (a la QuickCheck). + +It's upwardly compatible from lunit, except it does not change any +functions in the standard library (by using assert_true() instead +of assert()). +]], + homepage = "http://github.com/silentbicycle/lunatest", + license = "MIT/X11" +} +dependencies = { + "lua >= 5.1" +} +build = { + type = "builtin", + modules = { + lunatest = "lunatest.lua" + } +} diff --git a/rapanui-test/lunatest/lunatest.lua b/rapanui-test/lunatest/lunatest.lua new file mode 100644 index 0000000..380f851 --- /dev/null +++ b/rapanui-test/lunatest/lunatest.lua @@ -0,0 +1,1142 @@ +----------------------------------------------------------------------- +-- +-- Copyright (c) 2009-12 Scott Vokes +-- +-- Permission is hereby granted, free of charge, to any person +-- obtaining a copy of this software and associated documentation +-- files (the "Software"), to deal in the Software without +-- restriction, including without limitation the rights to use, +-- copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the +-- Software is furnished to do so, subject to the following +-- conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +-- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +-- HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +-- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +-- OTHER DEALINGS IN THE SOFTWARE. +-- +------------------------------------------------------------------------ +-- +-- This is a library for randomized testing with Lua. +-- For usage and examples, see README and the test suite. +-- +------------------------------------------------------------------------ + +------------ +-- Module -- +------------ + +-- standard libraries used +local debug, io, math, os, string, table = + debug, io, math, os, string, table + +-- required core global functions +local assert, error, ipairs, pairs, pcall, print, setmetatable, tonumber = + assert, error, ipairs, pairs, pcall, print, setmetatable, tonumber +local fmt, tostring, type, unpack = string.format, tostring, type, unpack +local getmetatable, rawget, setmetatable, xpcall = + getmetatable, rawget, setmetatable, xpcall +local exit, next, require = os.exit, next, require + +-- Get containing env, Lua 5.1-style +local getenv = getfenv + +---Use lhf's random, if available. It provides an RNG with better +-- statistical properties, and it gives consistent values across OSs. +-- http://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lrandom +pcall(require, "random") +local random = random + +-- Use the debug API to get line numbers, if available. +pcall(require, "debug") +local debug = debug + +-- Use luasocket's gettime(), luaposix' gettimeofday(), or os.date for +-- timestamps +local now = pcall(require, "socket") and socket.gettime or + pcall(require, "posix") and posix.gettimeofday and + function () + local s, us = posix.gettimeofday() + return s + us / 1000000 + end or + function () return tonumber(os.date("%s")) end + +-- Get env immediately wrapping module, to put assert_ tests there. +local _importing_env = getenv() + +-- Check command line arguments: +-- -v / --verbose, default to verbose_hooks. +-- -s or --suite, only run the named suite(s). +-- -t or --test, only run tests matching the pattern. +local lt_arg = arg + +-- ##################### +-- # Utility functions # +-- ##################### + +local function printf(...) print(string.format(...)) end + +local function result_table(name) + return { name=name, pass={}, fail={}, skip={}, err={} } +end + +local function combine_results(to, from) + local s_name = from.name + for _,set in ipairs{"pass", "fail", "skip", "err" } do + local fs, ts = from[set], to[set] + for name,val in pairs(fs) do + ts[s_name .. "." .. name] = val + end + end +end + +local function is_func(v) return type(v) == "function" end + +local function count(t) + local ct = 0 + for _ in pairs(t) do ct = ct + 1 end + return ct +end + + +-- ########### +-- # Results # +-- ########### + +local function msec(t) + if t and type(t) == "number" then + return fmt(" (%.2fms)", t * 1000) + else + return "" + end +end + + +local RPass = {} +local passMT = {__index=RPass} +function RPass:tostring_char() return "." end +function RPass:add(s, name) s.pass[name] = self end +function RPass:type() return "pass" end +function RPass:tostring(name) + return fmt("PASS: %s%s%s", + name or "(unknown)", msec(self.elapsed), + self.msg and (": " .. tostring(self.msg)) or "") +end + + +local RFail = {} +local failMT = {__index=RFail} +function RFail:tostring_char() return "F" end +function RFail:add(s, name) s.fail[name] = self end +function RFail:type() return "fail" end +function RFail:tostring(name) + return fmt("FAIL: %s%s: %s%s%s", + name or "(unknown)", + msec(self.elapsed), + self.reason or "", + self.msg and (" - " .. tostring(self.msg)) or "", + self.line and (" (%d)"):format(self.line) or "") +end + + +local RSkip = {} +local skipMT = {__index=RSkip} +function RSkip:tostring_char() return "s" end +function RSkip:add(s, name) s.skip[name] = self end +function RSkip:type() return "skip" end +function RSkip:tostring(name) + return fmt("SKIP: %s()%s", name or "unknown", + self.msg and (" - " .. tostring(self.msg)) or "") +end + + +local RError = {} +local errorMT = {__index=RError} +function RError:tostring_char() return "E" end +function RError:add(s, name) s.err[name] = self end +function RError:type() return "error" end +function RError:tostring(name) + return self.msg or + fmt("ERROR (in %s%s, couldn't get traceback)", + msec(self.elapsed), name or "(unknown)") +end + + +local function Pass(t) return setmetatable(t or {}, passMT) end +local function Fail(t) return setmetatable(t, failMT) end +local function Skip(t) return setmetatable(t, skipMT) end +local function Error(t) return setmetatable(t, errorMT) end + + +-- ############## +-- # Assertions # +-- ############## + +---Renamed standard assert. +local checked = 0 +local TS = tostring + +local function wraptest(flag, msg, t) + checked = checked + 1 + t.msg = msg + if debug then + local info = debug.getinfo(3, "l") + t.line = info.currentline + end + if not flag then error(Fail(t)) end +end + +---Fail a test. +-- @param no_exit Unless set to true, the presence of any failures +-- causes the test suite to terminate with an exit status of 1. +function fail(msg, no_exit) + local line + if debug then + local info = debug.getinfo(2, "l") + line = info.currentline + end + error(Fail { msg=msg, reason="(Failed)", no_exit=no_exit, line=line }) +end + + +---Skip a test, with a note, e.g. "TODO". +function skip(msg) error(Skip { msg=msg }) end + +---Assert for use with Hamcrest Matchers +function assert_that(got, matcher) + local result, mismatch = matcher.matches(got) + wraptest(result, "", { reason=fmt("Expected %s but was %s.", matcher.describe, mismatch or TS(got)) }) +end + +---got == true. +-- (Named "assert_true" to not conflict with standard assert.) +-- @param msg Message to display with the result. +function assert_true(got, msg) + wraptest(got, msg, { reason=fmt("Expected success, got %s.", TS(got)) }) +end + +---got == false. +function assert_false(got, msg) + wraptest(not got, msg, + { reason=fmt("Expected false, got %s", TS(got)) }) +end + +--got == nil +function assert_nil(got, msg) + wraptest(got == nil, msg, + { reason=fmt("Expected nil, got %s", TS(got)) }) +end + +--got ~= nil +function assert_not_nil(got, msg) + wraptest(got ~= nil, msg, + { reason=fmt("Expected non-nil value, got %s", TS(got)) }) +end + +local function tol_or_msg(t, m) + if not t and not m then return 0, nil + elseif type(t) == "string" then return 0, t + elseif type(t) == "number" then return t, m + else error("Neither a numeric tolerance nor string") + end +end + + +---exp == got. +function assert_equal(exp, got, tol, msg) + tol, msg = tol_or_msg(tol, msg) + if type(exp) == "number" and type(got) == "number" then + wraptest(math.abs(exp - got) <= tol, msg, + { reason=fmt("Expected %s +/- %s, got %s", + TS(exp), TS(tol), TS(got)) }) + else + wraptest(exp == got, msg, + { reason=fmt("Expected %q, got %q", TS(exp), TS(got)) }) + end +end + +---exp ~= got. +function assert_not_equal(exp, got, msg) + wraptest(exp ~= got, msg, + { reason="Expected something other than " .. TS(exp) }) +end + +---val > lim. +function assert_gt(lim, val, msg) + wraptest(val > lim, msg, + { reason=fmt("Expected a value > %s, got %s", + TS(lim), TS(val)) }) +end + +---val >= lim. +function assert_gte(lim, val, msg) + wraptest(val >= lim, msg, + { reason=fmt("Expected a value >= %s, got %s", + TS(lim), TS(val)) }) +end + +---val < lim. +function assert_lt(lim, val, msg) + wraptest(val < lim, msg, + { reason=fmt("Expected a value < %s, got %s", + TS(lim), TS(val)) }) +end + +---val <= lim. +function assert_lte(lim, val, msg) + wraptest(val <= lim, msg, + { reason=fmt("Expected a value <= %s, got %s", + TS(lim), TS(val)) }) +end + +---#val == len. +function assert_len(len, val, msg) + wraptest(#val == len, msg, + { reason=fmt("Expected #val == %d, was %d", + len, #val) }) +end + +---#val ~= len. +function assert_not_len(len, val, msg) + wraptest(#val ~= len, msg, + { reason=fmt("Expected length other than %d", len) }) +end + +---Test that the string s matches the pattern exp. +function assert_match(pat, s, msg) + s = tostring(s) + wraptest(type(s) == "string" and s:match(pat), msg, + { reason=fmt("Expected string to match pattern %s, was %s", + pat, + (s:len() > 30 and (s:sub(1,30) .. "...")or s)) }) +end + +---Test that the string s doesn't match the pattern exp. +function assert_not_match(pat, s, msg) + wraptest(type(s) ~= "string" or not s:match(pat), msg, + { reason=fmt("Should not match pattern %s", pat) }) +end + +---Test that val is a boolean. +function assert_boolean(val, msg) + wraptest(type(val) == "boolean", msg, + { reason=fmt("Expected type boolean but got %s", + type(val)) }) +end + +---Test that val is not a boolean. +function assert_not_boolean(val, msg) + wraptest(type(val) ~= "boolean", msg, + { reason=fmt("Expected type other than boolean but got %s", + type(val)) }) +end + +---Test that val is a number. +function assert_number(val, msg) + wraptest(type(val) == "number", msg, + { reason=fmt("Expected type number but got %s", + type(val)) }) +end + +---Test that val is not a number. +function assert_not_number(val, msg) + wraptest(type(val) ~= "number", msg, + { reason=fmt("Expected type other than number but got %s", + type(val)) }) +end + +---Test that val is a string. +function assert_string(val, msg) + wraptest(type(val) == "string", msg, + { reason=fmt("Expected type string but got %s", + type(val)) }) +end + +---Test that val is not a string. +function assert_not_string(val, msg) + wraptest(type(val) ~= "string", msg, + { reason=fmt("Expected type other than string but got %s", + type(val)) }) +end + +---Test that val is a table. +function assert_table(val, msg) + wraptest(type(val) == "table", msg, + { reason=fmt("Expected type table but got %s", + type(val)) }) +end + +---Test that val is not a table. +function assert_not_table(val, msg) + wraptest(type(val) ~= "table", msg, + { reason=fmt("Expected type other than table but got %s", + type(val)) }) +end + +---Test that val is a function. +function assert_function(val, msg) + wraptest(type(val) == "function", msg, + { reason=fmt("Expected type function but got %s", + type(val)) }) +end + +---Test that val is not a function. +function assert_not_function(val, msg) + wraptest(type(val) ~= "function", msg, + { reason=fmt("Expected type other than function but got %s", + type(val)) }) +end + +---Test that val is a thread (coroutine). +function assert_thread(val, msg) + wraptest(type(val) == "thread", msg, + { reason=fmt("Expected type thread but got %s", + type(val)) }) +end + +---Test that val is not a thread (coroutine). +function assert_not_thread(val, msg) + wraptest(type(val) ~= "thread", msg, + { reason=fmt("Expected type other than thread but got %s", + type(val)) }) +end + +---Test that val is a userdata (light or heavy). +function assert_userdata(val, msg) + wraptest(type(val) == "userdata", msg, + { reason=fmt("Expected type userdata but got %s", + type(val)) }) +end + +---Test that val is not a userdata (light or heavy). +function assert_not_userdata(val, msg) + wraptest(type(val) ~= "userdata", msg, + { reason=fmt("Expected type other than userdata but got %s", + type(val)) }) +end + +---Test that a value has the expected metatable. +function assert_metatable(exp, val, msg) + local mt = getmetatable(val) + wraptest(mt == exp, msg, + { reason=fmt("Expected metatable %s but got %s", + TS(exp), TS(mt)) }) +end + +---Test that a value does not have a given metatable. +function assert_not_metatable(exp, val, msg) + local mt = getmetatable(val) + wraptest(mt ~= exp, msg, + { reason=fmt("Expected metatable other than %s", + TS(exp)) }) +end + +---Test that the function raises an error when called. +function assert_error(f, msg) + local ok, err = pcall(f) + wraptest(not ok, msg, + { exp="an error", got=ok or err, + reason=fmt("Expected an error, got %s", TS(got)) }) +end + + +---Run a test case with randomly instantiated arguments, +-- running the test function f opt.count (default: 100) times. +-- @param opt A table with options, or just a test name string.
+-- opt.count: how many random trials to perform
+-- opt.seed: Start the batch of trials with a specific seed
+-- opt.always: Always test these seeds (for regressions)
+-- opt.show_progress: Whether to print a . after every opt.tick trials.
+-- opt.seed_limit: Max seed to allow.
+-- opt.max_failures, max_errors, max_skips: Give up after X of each.
+-- @param f A test function, run as f(unpack(randomized_args(...))) +-- @param ... the arg specification. For each argument, creates a +-- random instance of that type.
+-- boolean: return true or false
+-- number n: returns 0 <= x < n, or -n <= x < n if negative. +-- If n has a decimal component, so will the result.
+-- string: Specifiedd as "(len[,maxlen]) (pattern)".
+-- "10 %l" means 10 random lowercase letters.
+-- "10,30 [aeiou]" means between 10-30 vowels.
+-- function: Just call (as f()) and return result.
+-- table or userdata: Call v.__random() and return result.
+-- @usage +function assert_random(opt, f, ...) + -- Stub. Exported to the same namespace, but code appears below. +end + + +-- #################### +-- # Module beginning # +-- #################### + +---Unit testing module, with extensions for random testing. +module("lunatest") + +VERSION = "0.94" + + +-- ######### +-- # Hooks # +-- ######### + +local dot_ct = 0 +local cols = 70 + +local iow = io.write + +-- Print a char ([.fEs], etc.), wrapping at 70 columns. +local function dot(c) + c = c or "." + io.write(c) + dot_ct = dot_ct + 1 + if dot_ct > cols then + io.write("\n ") + dot_ct = 0 + end + io.stdout:flush() +end + +local function print_totals(r) + local ps, fs = count(r.pass), count(r.fail) + local ss, es = count(r.skip), count(r.err) + local el, unit = r.t_post - r.t_pre, "s" + if el < 1 then unit = "ms"; el = el * 1000 end + local elapsed = fmt(" in %.2f %s", el, unit) + local buf = {"\n---- Testing finished%s, ", + "with %d assertion(s) ----\n", + " %d passed, %d failed, ", + "%d error(s), %d skipped."} + printf(table.concat(buf), elapsed, checked, ps, fs, es, ss) +end + + +---Default behavior. +default_hooks = { + begin = false, + begin_suite = function(s_env, tests) + iow(fmt("\n-- Starting suite %q, %d test(s)\n ", + s_env.name, count(tests))) + end, + end_suite = false, + pre_test = false, + post_test = function(name, res) + dot(res:tostring_char()) + end, + done = function(r) + print_totals(r) + for _,ts in ipairs{ r.fail, r.err, r.skip } do + for name,res in pairs(ts) do + printf("%s", res:tostring(name)) + end + end + end, +} + + +---Default verbose behavior. +verbose_hooks = { + begin = function(res, suites) + local s_ct = count(suites) + if s_ct > 0 then + printf("Starting tests, %d suite(s)", s_ct) + end + end, + begin_suite = function(s_env, tests) + dot_ct = 0 + printf("-- Starting suite %q, %d test(s)", + s_env.name, count(tests)) + end, + end_suite = + function(s_env) + local ps, fs = count(s_env.pass), count(s_env.fail) + local ss, es = count(s_env.skip), count(s_env.err) + dot_ct = 0 + printf(" Finished suite %q, +%d -%d E%d s%d", + s_env.name, ps, fs, es, ss) + end, + pre_test = false, + post_test = function(name, res) + printf("%s", res:tostring(name)) + dot_ct = 0 + end, + done = function(r) print_totals(r) end +} + +setmetatable(verbose_hooks, {__index = default_hooks }) + + +-- ################ +-- # Registration # +-- ################ + +local suites = {} +local failed_suites = {} + +---Check if a function name should be considered a test key. +-- Defaults to functions starting or ending with "test", with +-- leading underscores allowed. +function is_test_key(k) + return type(k) == "string" and k:match("_*test.*") +end + +local function get_tests(mod) + local ts = {} + if type(mod) == "table" then + for k,v in pairs(mod) do + if is_test_key(k) and type(v) == "function" then + ts[k] = v + end + end + ts.setup = rawget(mod, "setup") + ts.teardown = rawget(mod, "teardown") + ts.ssetup = rawget(mod, "suite_setup") + ts.steardown = rawget(mod, "suite_teardown") + return ts + end + return {} +end + +---Add a file as a test suite. +-- @param modname The module to load as a suite. The file is +-- interpreted in the same manner as require "modname". +-- Which functions are tests is determined by is_test_key(name). +function suite(modname) + local ok, err = pcall( + function() + local mod, r_err = require(modname) + suites[modname] = get_tests(mod) + end) + if not ok then + print(fmt(" * Error loading test suite %q:\n%s", + modname, tostring(err))) + failed_suites[#failed_suites+1] = modname + end +end + + +-- ########### +-- # Running # +-- ########### + +local ok_types = { pass=true, fail=true, skip=true } + +local function err_handler(name) + return function (e) + if type(e) == "table" and e.type and ok_types[e.type()] then return e end + local msg = fmt("ERROR in %s():\n\t%s", name, tostring(e)) + msg = debug.traceback(msg, 3) + return Error { msg=msg } + end +end + + +local function run_test(name, test, suite, hooks, setup, teardown) + local result + if is_func(hooks.pre_test) then hooks.pre_test(name) end + local t_pre, t_post, elapsed --timestamps. requires luasocket. + local ok, err + + if is_func(setup) then + ok, err = xpcall(function() setup(name) end, err_handler(name)) + else + ok = true + end + + if ok then + t_pre = now() + ok, err = xpcall(test, err_handler(name)) + t_post = now() + elapsed = t_post - t_pre + + if is_func(teardown) then + if ok then + ok, err = xpcall(function() teardown(name, elapsed) end, + err_handler(name)) + else + xpcall(function() teardown(name, elapsed) end, + function(info) + print "\n===============================================" + local msg = fmt("ERROR in teardown handler: %s", info) + print(msg) + os.exit(1) + end) + end + end + end + + if ok then err = Pass() end + result = err + result.elapsed = elapsed + + -- TODO: log tests w/ no assertions? + result:add(suite, name) + + if is_func(hooks.post_test) then hooks.post_test(name, result) end +end + + +local function cmd_line_switches(arg) + arg = arg or {} + local opts = {} + for i=1,#arg do + local v = arg[i] + if v == "-v" or v == "--verbose" then opts.verbose=true + elseif v == "-s" or v == "--suite" then + opts.suite_pat = arg[i+1] + elseif v == "-t" or v == "--test" then + opts.test_pat = arg[i+1] + end + end + return opts +end + + +local function failure_or_error_count(r) + local t = 0 + for k,f in pairs(r.err) do + t = t + 1 + end + for k,f in pairs(r.fail) do + if not f.no_exit then t = t + 1 end + end + return t +end + +local function run_suite(hooks, opts, results, suite_filter, sname, tests) + local ssetup, steardown = tests.ssetup, tests.steardown + tests.ssetup, tests.steardown = nil, nil + + if not suite_filter or sname:match(suite_filter) then + local run_suite = true + local res = result_table(sname) + + if ssetup then + local ok, err = pcall(ssetup) + if not ok or (ok and err == false) then + run_suite = false + local msg = fmt("Error in %s's suite_setup: %s", sname, tostring(err)) + failed_suites[#failed_suites+1] = sname + results.err[sname] = Error{msg=msg} + end + end + + if run_suite and count(tests) > 0 then + local setup, teardown = tests.setup, tests.teardown + tests.setup, tests.teardown = nil, nil + + if hooks.begin_suite then hooks.begin_suite(res, tests) end + res.tests = tests + for name, test in pairs(tests) do + if not opts.test_pat or name:match(opts.test_pat) then + run_test(name, test, res, hooks, setup, teardown) + end + end + if steardown then pcall(steardown) end + if hooks.end_suite then hooks.end_suite(res) end + combine_results(results, res) + end + end +end + +---Run all known test suites, with given configuration hooks. +-- @param hooks Override the default hooks. +-- @param suite_filter If set, only run suite(s) with names +-- matching this pattern. +-- @usage If no hooks are provided and arg[1] == "-v", the +-- verbose_hooks will be used. +function run(hooks, suite_filter) + -- also check the namespace it's run in + local opts = cmd_line_switches(lt_arg) + + -- Make stdout line-buffered for better interactivity when the output is + -- not going to the terminal, e.g. is piped to another program. + io.stdout:setvbuf("line") + + if hooks == true or (hooks == nil and opts.verbose) then + hooks = verbose_hooks + else + hooks = hooks or {} + end + + setmetatable(hooks, {__index = default_hooks}) + + local results = result_table("main") + results.t_pre = now() + + -- If it's all in one test file, check its environment, too. + local env = getenv(3) + if env then suites.main = get_tests(env) end + + if hooks.begin then hooks.begin(results, suites) end + + local suite_filter = opts.suite_pat or suite_filter + + for sname,suite in pairs(suites) do + run_suite(hooks, opts, results, suite_filter, sname, suite) + end + results.t_post = now() + if hooks.done then hooks.done(results) end + + local failures = failure_or_error_count(results) + if failures > 0 then os.exit(failures) end + if #failed_suites > 0 then os.exit(#failed_suites) end +end + + +-- ######################## +-- # Randomization basics # +-- ######################## + +local _r +if random then + _r = random.new() +end + +---Set random seed. +function set_seed(s) _r:seed(s) end + +---Get a random value low <= x <= high. +function random_int(low, high) + if not high then high = low; low = 0 end + return _r:value(low, high) +end + +---Get a random bool. +function random_bool() return random_int(0, 1) == 1 end + +---Get a random float low <= x < high. +function random_float(low, high) + return random_int(low, high - 1) + _r:value() +end + + +if not random then + set_seed = math.randomseed + random_bool = function() return math.random(0, 1) == 1 end + random_float = function(l, h) + return random_int(l, h - 1) + math.random() + end + random_int = function(l, h) + if not h then h = l; l = 0 end + return math.random(l, h) + end +end + +-- Lua_number's bits of precision. IEEE 754 doubles have 52. +local function determine_accuracy() + for i=1,128 do + if 2^i == (2^i + 1) then return i - 1 end + end + return 128 --long long ints? +end +local bits_of_accuracy = determine_accuracy() + + +-- ################## +-- # Random strings # +-- ################## + + +-- For valid char classes, see Lua Reference Manual 5.1, p. 77 +-- or http://www.lua.org/manual/5.1/manual.html#5.4.1 . +local function charclass(pat) + local m = {} + + local match, char = string.match, string.char + for i=0,255 do + local c = char(i) + if match(c, pat) then m[#m+1] = c end + end + + return table.concat(m) +end + + +-- Return a (() -> random char) iterator from a pattern. +local function parse_pattern(pattern) + local cs = {} --charset + local idx = 1 + local len = string.len(pattern) + assert(len > 0, "Cannot generate pattern from empty string.") + + local function at_either_end() return #cs == 0 or #cs == len end + local function slice(i) return string.sub(pattern, i, i) end + + while idx <= len do + local c = slice(idx) + + if c == "-" then + if at_either_end() then + cs[#cs+1] = c --literal - at start or end + else --range + local low = string.byte(slice(idx-1)) + 1 + local high = string.byte(slice(idx+1)) + assert(low < high, "Invalid character range: " .. pattern) + for asc=low,high do + cs[#cs+1] = string.char(asc) + end + idx = idx + 1 + end + + elseif c == "%" then + local nextc = slice(idx + 1) + cs[#cs+1] = charclass("%" .. nextc) + idx = idx + 1 + + else + cs[#cs+1] = c + end + idx = idx + 1 + end + + cs = table.concat(cs) + local len = string.len(cs) + assert(len > 0, "Empty charset") + + return function() + local idx = random_int(1, len) + return string.sub(cs, idx, idx) + end +end + + +-- Read a random string spec, return a config table. +local function parse_randstring(s) + local low, high, rest = string.match(s, "([0-9]+),?([0-9]*) (.*)") + if low then --any match + if high == "" then high = low end + return { low = tonumber(low), + high = tonumber(high), + gen = parse_pattern(rest) } + else + local err = "Invalid random string spec: " .. s + error(err, 2) + end +end + + +-- Generate a random string. +-- @usage e.g. "20 listoftwentycharstogenerate" or "10,20 %l". +function random_string(spec) + local info = parse_randstring(spec) + local ct, diff + diff = info.high - info.low + if diff == 0 then ct = info.low else + ct = random_int(diff) + info.low + end + + local acc = {} + for i=1,ct do + acc[i] = info.gen(self) + end + local res = table.concat(acc) + assert(res:len() == ct, "Bad string gen") + return res +end + + +-- ######################### +-- # General random values # +-- ######################### + +-- Generate a random number, according to arg. +local function gen_number(arg) + arg = arg or math.huge + local signed = (arg < 0) + local float + if signed then float = (math.ceil(arg) ~= arg) else + float = (math.floor(arg) ~= arg) + end + + local f = float and random_float or random_int + if signed then + return f(arg, -arg) + else + return f(0, arg) + end +end + + +-- Create an arbitrary instance of a value. +local function generate_arbitrary(arg) + local t = type(arg) + if t == "number" then + return gen_number(arg) + elseif t == "function" then + return arg(gen_number()) -- assume f(number) -> val + elseif t == "string" then + return random_string(arg) + elseif t == "table" or t == "userdata" then + assert(arg.__random, t .. " has no __random method") + -- assume arg.__random(number) -> val + return arg.__random(gen_number()) + elseif t == "boolean" then + return random_bool() + else + error("Cannot randomly generate values of type " .. t .. ".") + end +end + + +local random_test_defaults = { + count = 100, + max_failures = 10, + max_errors = 5, + max_skips = 50, + random_bound = 2^bits_of_accuracy, + seed_limit = math.min(1e13, 2^bits_of_accuracy), + always = {}, + seed = nil, + show_progress = true +} + + +local function random_args(args) + local as = {} + for i=1,#args do + as[i] = generate_arbitrary(args[i]) + end + return as +end + + +local function new_seed(limit) + limit = limit or 1e13 + return random_int(0, limit) +end + + +local function get_seeds_and_args(t) + local ss = {} + for _,r in ipairs(t) do + if r.seed then + ss[#ss+1] = fmt("%s %s\n Seed: %s", + r.reason or "", r.msg and ("\n " .. r.msg) or "", r.seed) + end + if r.args then + for i,arg in ipairs(r.args) do + ss[#ss+1] = " * " .. arg + end + end + ss[#ss+1] = "" + end + return ss +end + + +local function run_randtest(seed, f, args, r, limit) + local try_ct = 0 + while r.tried[seed] and try_ct < 50 do + seed = new_seed(limit) + try_ct = try_ct + 1 + end + if try_ct >= 50 then + error(Fail { reason = "Exhausted all seeds" }) + end + set_seed(seed) + r.tried[seed] = true + + local result + local r_args = random_args(args) + local ok, err = pcall(function() f(unpack(r_args)) end) + if ok then + result = Pass() + result.seed = seed + r.ps[#r.ps+1] = result + else + -- So errors in the suite itself get through... + if type(err) == "string" then error(err) end + result = err + result.seed = seed + local rt = result:type() + if rt == "pass" then r.ps[#r.ps+1] = result + elseif rt == "fail" then r.fs[#r.fs+1] = result + elseif rt == "error" then r.es[#r.es+1] = result + elseif rt == "skip" then r.ss[#r.ss+1] = result + else error("unmatched") + end + end + + seed = new_seed(limit) + r.ts = r.ts + 1 + local str_args = {} + -- Convert args to strs (for display) and add to result. + for i,v in ipairs(r_args) do + str_args[i] = tostring(v) + end + result.args = str_args + return seed +end + + +local function report_trial(r, opt) + if #r.es > 0 then + local seeds = get_seeds_and_args(r.es) + error(Fail { reason = fmt("%d tests, %d error(s).\n %s", + r.ts, #r.es, + table.concat(seeds, "\n ")), + seeds = seeds}) + elseif #r.fs > 0 then + local seeds = get_seeds_and_args(r.fs) + error(Fail { reason = fmt("%d tests, %d failure(s).\n %s", + r.ts, #r.fs, + table.concat(seeds, "\n ")), + seeds = seeds}) + elseif #r.ss >= opt.max_skips then + error(Fail { reason = fmt("Too many cases skipped.")}) + else + error(Pass { reason = fmt(": %d cases passed.", #r.ps) }) + end +end + + +local function assert_random(opt, f, ...) + local args = { ... } + if type(opt) == "string" then + opt = { name=opt } + elseif type(opt) == "function" then + table.insert(args, 1, f) + f = opt + opt = {} + end + + setmetatable(opt, { __index=random_test_defaults }) + + local seed = opt.seed or os.time() + local r = { ps={}, fs={}, es={}, ss={}, ts=0, tried={} } + + -- Run these seeds every time, for easy regression testing. + for _,s in ipairs(opt.always) do + run_randtest(s, f, args, r, opt.seed_limit) + end + + set_seed(seed) + local tick = opt.tick or opt.count / 10 + + for i=1,opt.count do + seed = run_randtest(seed, f, args, r, opt.seed_limit) + if #r.ss >= opt.max_skips or + #r.fs >= opt.max_failures or + #r.es >= opt.max_errors then break + end + if opt.show_progress and i % tick == 0 then + dot(".") + end + end + local overall_status = (passed == count and "PASS" or "FAIL") + + report_trial(r, opt) +end + + +-- Put it in the same namespace as the other assert_ functions. +_importing_env.assert_random = assert_random diff --git a/rapanui-test/lunatest/modules/lunatest.html b/rapanui-test/lunatest/modules/lunatest.html new file mode 100644 index 0000000..2b32f06 --- /dev/null +++ b/rapanui-test/lunatest/modules/lunatest.html @@ -0,0 +1,1501 @@ + + + + Reference + + + + + +
+ +
+ +
+
+
+ +
+ + + +
+ +

Module lunatest

+ +

Unit testing module, with extensions for random testing.

+ + + + + +

Functions

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
assert_boolean (val, msg)Test that val is a boolean.
assert_equal (exp, got, tol, msg)exp == got.
assert_error (f, msg)Test that the function raises an error when called.
assert_false (got, msg)got == false.
assert_function (val, msg)Test that val is a function.
assert_gt (lim, val, msg)val > lim.
assert_gte (lim, val, msg)val >= lim.
assert_len (len, val, msg)#val == len.
assert_lt (lim, val, msg)val < lim.
assert_lte (lim, val, msg)val <= lim.
assert_match (pat, s, msg)Test that the string s matches the pattern exp.
assert_metatable (exp, val, msg)Test that a value has the expected metatable.
assert_not_boolean (val, msg)Test that val is not a boolean.
assert_not_equal (exp, got, msg)exp ~= got.
assert_not_function (val, msg)Test that val is not a function.
assert_not_len (len, val, msg)#val ~= len.
assert_not_match (pat, s, msg)Test that the string s doesn't match the pattern exp.
assert_not_metatable (exp, val, msg)Test that a value does not have a given metatable.
assert_not_number (val, msg)Test that val is not a number.
assert_not_string (val, msg)Test that val is not a string.
assert_not_table (val, msg)Test that val is not a table.
assert_not_thread (val, msg)Test that val is not a thread (coroutine).
assert_not_userdata (val, msg)Test that val is not a userdata (light or heavy).
assert_number (val, msg)Test that val is a number.
assert_random (opt, f, ...)Run a test case with randomly instantiated arguments, running the test function f opt.count (default: 100) times.
assert_string (val, msg)Test that val is a string.
assert_table (val, msg)Test that val is a table.
assert_thread (val, msg)Test that val is a thread (coroutine).
assert_true (got, msg)got == true.
assert_userdata (val, msg)Test that val is a userdata (light or heavy).
fail (msg, no_exit)Fail a test.
is_test_key (k)Check if a function name should be considered a test key.
random_bool ()Get a random bool.
random_float (low, high)Get a random float low <= x < high.
random_int (low, high)Get a random value low <= x <= high.
run (hooks, suite_filter)Run all known test suites, with given configuration hooks.
set_seed (s)Set random seed.
skip (msg)Skip a test, with a note, e.g.
suite (modname)Add a file as a test suite.
+ + + + + + +
+
+ + + +

Functions

+
+ + + +
assert_boolean (val, msg)
+
+Test that val is a boolean. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_equal (exp, got, tol, msg)
+
+exp == got. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + got: +
  • + +
  • + tol: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_error (f, msg)
+
+Test that the function raises an error when called. + + +

Parameters

+
    + +
  • + f: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_false (got, msg)
+
+got == false. + + +

Parameters

+
    + +
  • + got: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_function (val, msg)
+
+Test that val is a function. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_gt (lim, val, msg)
+
+val > lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_gte (lim, val, msg)
+
+val >= lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_len (len, val, msg)
+
+#val == len. + + +

Parameters

+
    + +
  • + len: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_lt (lim, val, msg)
+
+val < lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_lte (lim, val, msg)
+
+val <= lim. + + +

Parameters

+
    + +
  • + lim: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_match (pat, s, msg)
+
+Test that the string s matches the pattern exp. + + +

Parameters

+
    + +
  • + pat: +
  • + +
  • + s: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_metatable (exp, val, msg)
+
+Test that a value has the expected metatable. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_boolean (val, msg)
+
+Test that val is not a boolean. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_equal (exp, got, msg)
+
+exp ~= got. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + got: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_function (val, msg)
+
+Test that val is not a function. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_len (len, val, msg)
+
+#val ~= len. + + +

Parameters

+
    + +
  • + len: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_match (pat, s, msg)
+
+Test that the string s doesn't match the pattern exp. + + +

Parameters

+
    + +
  • + pat: +
  • + +
  • + s: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_metatable (exp, val, msg)
+
+Test that a value does not have a given metatable. + + +

Parameters

+
    + +
  • + exp: +
  • + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_number (val, msg)
+
+Test that val is not a number. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_string (val, msg)
+
+Test that val is not a string. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_table (val, msg)
+
+Test that val is not a table. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_thread (val, msg)
+
+Test that val is not a thread (coroutine). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_not_userdata (val, msg)
+
+Test that val is not a userdata (light or heavy). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_number (val, msg)
+
+Test that val is a number. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_random (opt, f, ...)
+
+Run a test case with randomly instantiated arguments, running the test function f opt.count (default: 100) times. + + +

Parameters

+
    + +
  • + opt: A table with options, or just a test name string.
    opt.count: how many random trials to perform
    opt.seed: Start the batch of trials with a specific seed
    opt.always: Always test these seeds (for regressions)
    opt.show_progress: Whether to print a . after every opt.tick trials.
    opt.seed_limit: Max seed to allow.
    opt.max_failures, max_errors, max_skips: Give up after X of each.
    +
  • + +
  • + f: A test function, run as f(unpack(randomized_args(...))) +
  • + +
  • + ...: the arg specification. For each argument, creates a random instance of that type.
    boolean: return true or false
    number n: returns 0 <= x < n, or -n <= x < n if negative. If n has a decimal component, so will the result.
    string: Specifiedd as "(len[,maxlen]) (pattern)".
    "10 %l" means 10 random lowercase letters.
    "10,30 [aeiou]" means between 10-30 vowels.
    function: Just call (as f()) and return result.
    table or userdata: Call v.__random() and return result.
    @usage +
  • + +
+ + + + + + + + +
+ + + + +
assert_string (val, msg)
+
+Test that val is a string. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_table (val, msg)
+
+Test that val is a table. + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_thread (val, msg)
+
+Test that val is a thread (coroutine). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
assert_true (got, msg)
+
+got == true. (Named "assert_true" to not conflict with standard assert.) + + +

Parameters

+
    + +
  • + got: +
  • + +
  • + msg: Message to display with the result. +
  • + +
+ + + + + + + + +
+ + + + +
assert_userdata (val, msg)
+
+Test that val is a userdata (light or heavy). + + +

Parameters

+
    + +
  • + val: +
  • + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
fail (msg, no_exit)
+
+Fail a test. + + +

Parameters

+
    + +
  • + msg: +
  • + +
  • + no_exit: Unless set to true, the presence of any failures causes the test suite to terminate with an exit status of 1. +
  • + +
+ + + + + + + + +
+ + + + +
is_test_key (k)
+
+Check if a function name should be considered a test key. Defaults to functions starting or ending with "test", with leading underscores allowed. + + +

Parameters

+
    + +
  • + k: +
  • + +
+ + + + + + + + +
+ + + + +
random_bool ()
+
+Get a random bool. + + + + + + + + + +
+ + + + +
random_float (low, high)
+
+Get a random float low <= x < high. + + +

Parameters

+
    + +
  • + low: +
  • + +
  • + high: +
  • + +
+ + + + + + + + +
+ + + + +
random_int (low, high)
+
+Get a random value low <= x <= high. + + +

Parameters

+
    + +
  • + low: +
  • + +
  • + high: +
  • + +
+ + + + + + + + +
+ + + + +
run (hooks, suite_filter)
+
+Run all known test suites, with given configuration hooks. + + +

Parameters

+
    + +
  • + hooks: Override the default hooks. +
  • + +
  • + suite_filter: If set, only run suite(s) with names matching this pattern. +
  • + +
+ + + + +

Usage:

+If no hooks are provided and arg[1] == "-v", the verbose_hooks will be used. + + + + + +
+ + + + +
set_seed (s)
+
+Set random seed. + + +

Parameters

+
    + +
  • + s: +
  • + +
+ + + + + + + + +
+ + + + +
skip (msg)
+
+Skip a test, with a note, e.g. "TODO". + + +

Parameters

+
    + +
  • + msg: +
  • + +
+ + + + + + + + +
+ + + + +
suite (modname)
+
+Add a file as a test suite. + + +

Parameters

+
    + +
  • + modname: The module to load as a suite. The file is interpreted in the same manner as require "modname". Which functions are tests is determined by is_test_key(name). +
  • + +
+ + + + + + + + +
+ + +
+ + + + + + +
+ +
+ +
+

Valid XHTML 1.0!

+
+ +
+ + diff --git a/rapanui-test/lunatest/suite-hooks-fail.lua b/rapanui-test/lunatest/suite-hooks-fail.lua new file mode 100644 index 0000000..c71d3cf --- /dev/null +++ b/rapanui-test/lunatest/suite-hooks-fail.lua @@ -0,0 +1,13 @@ +module(..., package.seeall) + +-- Either returning false or erroring out in suite_setup() +-- will prevent the suite from running. +function suite_setup() + print "\n\n-- (about to fail and abort suite)" + if true then return false end + error("don't run this suite") +end + +function test_never_run() + assert_true(false, "this suite should never be run") +end diff --git a/rapanui-test/lunatest/suite-hooks.lua b/rapanui-test/lunatest/suite-hooks.lua new file mode 100644 index 0000000..514d838 --- /dev/null +++ b/rapanui-test/lunatest/suite-hooks.lua @@ -0,0 +1,13 @@ +module(..., package.seeall) + +function suite_setup() + print "\n\n-- running suite setup hook" +end + +function suite_teardown() + print "\n\n-- running suite teardown hook" +end + +function test_ok() + assert_true(true) +end diff --git a/rapanui-test/lunatest/suite-with-random-tests.lua b/rapanui-test/lunatest/suite-with-random-tests.lua new file mode 100644 index 0000000..5ba4103 --- /dev/null +++ b/rapanui-test/lunatest/suite-with-random-tests.lua @@ -0,0 +1,172 @@ +module(..., package.seeall) + +-- Several random tests, with arguments specified both +-- via functions and via consts. + +local seed = os.time() +lunatest.set_seed(seed) +print("Seed is ", seed) + +local random_bool, random_int = + lunatest.random_bool, lunatest.random_int +local random_float, random_string = + lunatest.random_float, lunatest.random_string + +function test_random_bool() + assert_random("bool is t or f", + function (b) + return b == true or b == false + end, true) +end + + +function test_random_bool2() + assert_random("bool is t or f", + function () + return random_bool() + end, true) +end + + +function test_random_int() + assert_random({name="pos int", count=1000 }, + function () + local ri = random_int(1, 10) + return ri >= 1 and ri <= 10 + end) +end + +function test_random_int2() + assert_random({ name="pos int", count=1000 }, + function (ri) + return ri >= 0 and ri <= 10 + end, 10) +end + +function test_neg_int() + assert_random({ name="neg int", count=1000 }, + function () + local ni = random_int(-math.huge, 0) + return ni < 0 + end) +end + +function test_neg_or_pos_int() + assert_random({ name="neg int", count=1000 }, + function (i) + assert_number(i) + assert_gte(-400, i) + assert_lte(400, i) + end, -400) +end + + +function test_random_int_trio() + assert_random({ name="three ints", count=1000 }, + function () + local ri = random_int + local x, y, z = ri(0, 5), ri(10, 50), ri(50, 100) + assert_gt(x, y) + assert_gte(y, z) + assert_lt(z, x) + end) +end + +function test_random_float() + assert_random({ name="float", count=1000 }, + function () + local rf = random_float + assert_gt(21.5, rf(30, 40)) + end) +end + +function test_random_float2() + assert_random({ name="float", count=1000 }, + function (x) + assert_gt(0, x) + end, 55.5) +end + + +function test_random_string() + assert_random("vowels", + function (x) + local len = string.len(x) + assert_lte(10, len) + assert_match("[aeiou]+", x) + end, "10 aeiou") +end + +function test_random_string2() + -- In the first argument, .always={...} is a list of seeds + -- that will be run every trial. + assert_random( { name="alpha", + always={ 6483877599982, 9948212639558 }}, + function (x) + local len = string.len(x) + assert_gte(10, len) + assert_lte(30, len) + assert_match(".+", x) + end, "10,30 %a") +end + +function test_coinflip() + local function rbool() return random_bool() end + -- typically, this would go in a metatable... + local coin = { __random = rbool } + assert_random("flip", + function(flip) + assert_boolean(flip) + end, coin) +end + + +function test_random_int_bounds() + for run, pair in ipairs{ {1, 2}, {2, 10}, {-1, 1}, + {-100, 100}, {-1, 0}, {0, 1} } do + assert_random("int_bounds", + function() + local ri = random_int(pair[1], pair[2]) + assert_gte(pair[1], ri) + assert_lte(pair[2], ri) + end) + end +end + +function test_random_int_bounds() + for run, pair in ipairs{ {1, 2}, {2, 10}, {-1, 1}, + {-100, 100}, {-1, 0}, {0, 1} } do + assert_random("float_bounds", + function() + local ri = random_float(pair[1], pair[2]) + assert_gte(pair[1], ri) + assert_lt(pair[2], ri) + end) + end +end + + +function test_random_str_len() + local fmt = string.format + for _,l in ipairs{ 1, 5, 10, 15, 30 } do + assert_random("strlen", + function() + local pat = fmt("%d %%l", l) + local rs = random_string(pat) + assert_len(l, rs) + assert_match("^%l+$", rs) + end) + end +end + + +function test_random_str_range() + local fmt = string.format + assert_random("strlen2", + function() + local pat = fmt("%d a-z", 15) + local rs = random_string(pat) + assert_len(15, rs) + assert_match("^%l+$", rs) + end) +end diff --git a/rapanui-test/lunatest/test-error_handler.lua b/rapanui-test/lunatest/test-error_handler.lua new file mode 100644 index 0000000..333bc29 --- /dev/null +++ b/rapanui-test/lunatest/test-error_handler.lua @@ -0,0 +1,9 @@ +require "lunatest" + +-- This should error out with a more meaningful error message +-- than "./lunatest.lua:608: attempt to index local 'e' (a function value)". +function test_foo() + error(function() return 2 end) +end + +lunatest.run() diff --git a/rapanui-test/lunatest/test-teardown_fail.lua b/rapanui-test/lunatest/test-teardown_fail.lua new file mode 100644 index 0000000..96b0ea7 --- /dev/null +++ b/rapanui-test/lunatest/test-teardown_fail.lua @@ -0,0 +1,39 @@ +require "lunatest" + +function busywait(n) + n = n or 10000 + for i=1,n do + for j=1,n do + -- no-op + end + end +end + +function setup(n) + -- show that test case time does not include setup + busywait() +end + +local teardown_count = 0 + +function teardown(n) + teardown_count = teardown_count + 1 + if teardown_count > 1 then error("*boom*") end +end + +function test_fail_but_expect_teardown() + error("fail whale") +end + +function test_fail_but_expect_teardown_2() + error("boom") +end + +local caught_teardown_error = false + +xpcall(lunatest.run(), function(x) + print("ARGH", x) + caught_teardown_error = true + end) + +assert(caught_teardown_error, "Didn't catch teardown failure.") diff --git a/rapanui-test/lunatest/test.lua b/rapanui-test/lunatest/test.lua new file mode 100644 index 0000000..71b4b77 --- /dev/null +++ b/rapanui-test/lunatest/test.lua @@ -0,0 +1,203 @@ +pcall(require, "luacov") --measure code coverage, if luacov is present +require "lunatest" + +print '==============================' +print('To test just the random suite, add "-s random" to the command line') +print('To run just some tests, add "-t [pattern]"') +print '==============================' + + +lunatest.suite("suite-with-random-tests") +lunatest.suite("suite-hooks") +lunatest.suite("suite-hooks-fail") + +function test_fail() + -- the true here is so the test run as a whole still succeeds. + fail("This one *should* fail.", true) +end + +function test_assert() + assert_true(true) +end + +function test_skip() + skip("(reason why this test was skipped)") +end + +function test_assert_false() + assert_false(false) +end + +function test_assert_nil() + assert_nil(nil) +end + + +function test_assert_not_nil() + assert_not_nil("foo") +end + +function test_assert_equal() + assert_equal(4, 4) +end + +function test_assert_equal_tolerance() + assert_equal(4, 4.0001, 0.0001, "Should approximately match") +end + +function test_assert_not_equal() + assert_not_equal("perl", "quality") +end + +function test_assert_gt() + assert_gt(8, 400) +end + +function test_assert_gte() + assert_gte(8, 400) + assert_gte(8, 8) +end + +function test_assert_lt() + assert_lt(8, -2) +end + +function test_assert_lte() + assert_lte(8, -2) + assert_lte(8, 8) +end + +function test_assert_len() + assert_len(3, { "foo", "bar", "baz" }) +end + +function test_assert_not_len() + assert_not_len(23, { "foo", "bar", "baz" }) +end + +function test_assert_match() + assert_match("oo", "string with foo in it") +end + +function test_assert_not_match() + assert_not_match("abba zabba", "foo") +end + +function test_assert_boolean() + assert_boolean(true) + assert_boolean(false) +end + +function test_assert_not_boolean() + assert_not_boolean("cheesecake") +end + +function test_assert_number() + assert_number(47) + assert_number(0) + assert_number(math.huge) + assert_number(-math.huge) +end + +function test_assert_not_number() + assert_not_number(_G) + assert_not_number("abc") + assert_not_number({1, 2, 3}) + assert_not_number(false) + assert_not_number(function () return 3 end) +end + +function test_assert_string() + assert_string("yarn") + assert_string("") +end + +function test_assert_not_string() + assert_not_string(23) + assert_not_string(true) + assert_not_string(false) + assert_not_string({"1", "2", "3"}) +end + +function test_assert_table() + assert_table({}) + assert_table({"1", "2", "3"}) + assert_table({ foo=true, bar=true, baz=true }) +end + +function test_assert_not_table() + assert_not_table(nil) + assert_not_table(23) + assert_not_table("lapdesk") + assert_not_table(false) + assert_not_table(function () return 3 end) +end + +function test_assert_function() + assert_function(function() return "*splat*" end) + assert_function(string.format) +end + +function test_assert_not_function() + assert_not_function(nil) + assert_not_function(23) + assert_not_function("lapdesk") + assert_not_function(false) + assert_not_function(coroutine.create(function () return 3 end)) + assert_not_function({"1", "2", "3"}) + assert_not_function({ foo=true, bar=true, baz=true }) +end + +function test_assert_thread() + assert_thread(coroutine.create(function () return 3 end)) +end + +function test_assert_not_thread() + assert_not_thread(nil) + assert_not_thread(23) + assert_not_thread("lapdesk") + assert_not_thread(false) + assert_not_thread(function () return 3 end) + assert_not_thread({"1", "2", "3"}) + assert_not_thread({ foo=true, bar=true, baz=true }) +end + +function test_assert_userdata() + assert_userdata(io.open("test.lua", "r")) +end + +function test_assert_not_userdata() + assert_not_userdata(nil) + assert_not_userdata(23) + assert_not_userdata("lapdesk") + assert_not_userdata(false) + assert_not_userdata(function () return 3 end) + assert_not_userdata({"1", "2", "3"}) + assert_not_userdata({ foo=true, bar=true, baz=true }) +end + +function test_assert_metatable() + assert_metatable(getmetatable("any string"), "foo") + local t = { __index=string } + local val = setmetatable( { 1 }, t) + assert_metatable(t, val) +end + +function test_assert_not_metatable() + assert_not_metatable(getmetatable("any string"), 23) +end + +function test_assert_error() + assert_error(function () + error("*crash!*") + end) +end + +-- This caused a crash when matching a string with invalid % escapes. +-- Thanks to Diab Jerius for the bugfix. +function test_failure_formatting() + local inv_esc = "str with invalid escape %( in it" + assert_match(inv_esc, inv_esc, "Should fail but not crash") +end + +lunatest.run() diff --git a/rapanui-test/lunatest/test_hamcrest.lua b/rapanui-test/lunatest/test_hamcrest.lua new file mode 100644 index 0000000..8090340 --- /dev/null +++ b/rapanui-test/lunatest/test_hamcrest.lua @@ -0,0 +1,48 @@ +-- +-- Author: Janne Sinivirta +-- Date: 9/19/12 +-- +require('lunatest') +require('lunahamcrest') + +function test_equal_to() + assert_that(4, equal_to(4)) + assert_that("ok", equal_to("ok")) +end + +function test_not() + assert_that(4, is_not(equal_to(5))) + assert_that(5, is_not(is_nil())) +end + +function test_syntactic_sugars() + assert_that(4, is(equal_to(4))) + assert_that(nil, is_nil()) +end + +function test_of_type() + assert_that("test", is(of_type("string"))) + assert_that(4, is(of_type("number"))) +end + +function test_contains_string() + assert_that("testable", contains_string("test")) + assert_that("testable", contains_string("tab")) + assert_that("testable", contains_string("testable")) +end + +function test_starts_with() + assert_that("testable", starts_with("tes")) + assert_that("testable", starts_with("testable")) +end + +function test_starts_with() + assert_that("testable", equals_ignoring_case("tesTaBlE")) +end + +function test_greater_than() + assert_that(3, greater_than(2)) + assert_that(-1, greater_than(-2)) + end + +lunatest.run() diff --git a/rapanui-test/mockobjects/MockConstants.lua b/rapanui-test/mockobjects/MockConstants.lua new file mode 100644 index 0000000..f470518 --- /dev/null +++ b/rapanui-test/mockobjects/MockConstants.lua @@ -0,0 +1,15 @@ +-- Author: Marko Pukari +-- Date: 12/2/12 + +MockConstants = { + WIDTH = 1, + HEIGHT = 2, + SCREENWIDTH = 3, + SCREENHEIGHT = 4, + OFFSET_X = -1, + OFFSET_Y = 1, + ORIGINALWIDTH = 4, + ORIGINALHEIGHT = 6, + WINDOWNAME = "mainwindow", + MAIN_LAYER = "mainlayer" +} \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockLayer.lua b/rapanui-test/mockobjects/MockLayer.lua new file mode 100644 index 0000000..f38bf0f --- /dev/null +++ b/rapanui-test/mockobjects/MockLayer.lua @@ -0,0 +1,49 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createTestLayer(name,viewport,partition) + local MockLayer = { + setPartitionCalled = 0, + setViewportCalled = 0, + clearCalled = 0, + insertPropCalled = 0, + MOAIVIEWPORT = viewport, + MOAIPARTITION = partition, + name = name + } + + function MockLayer:setViewport(viewport) + MockLayer.setViewportCalled = MockLayer.setViewportCalled + 1 + assert_that(viewport.name,is(equal_to(MockLayer.MOAIVIEWPORT.name))) + end + + function MockLayer:setPartition(partition) + assert_that(partition.name,is(equal_to(MockLayer.MOAIPARTITION.name))) + MockLayer.setPartitionCalled = MockLayer.setPartitionCalled + 1 + end + + function MockLayer:clear() + MockLayer.clearCalled=MockLayer.clearCalled + 1 + end + + function MockLayer:insertProp(prop) + self.insertPropCalled = self.insertPropCalled + 1 + self.MOAIPARTITION:insertProp(prop) + end + + function MockLayer:reset() + self.setPartitionCalled = 0 + self.setViewportCalled = 0 + self.clearCalled = 0 + self.insertPropCalled = 0 + self.removePropCalled = 0 + end + + function MockLayer:removeProp(prop) + self.removePropCalled = self.removePropCalled + 1 + self.MOAIPARTITION:removeProp(prop) + end + + + return MockLayer +end diff --git a/rapanui-test/mockobjects/MockMOAILayer2D.lua b/rapanui-test/mockobjects/MockMOAILayer2D.lua new file mode 100644 index 0000000..f87cc40 --- /dev/null +++ b/rapanui-test/mockobjects/MockMOAILayer2D.lua @@ -0,0 +1,24 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createMockMOAILayer2D(...) + + local MockMOAILayer2D = { + newCalled = 0, + i=0, + layers = arg + } + + MockMOAILayer2D.new = function() + MockMOAILayer2D.newCalled = MockMOAILayer2D.newCalled + 1 + MockMOAILayer2D.i = MockMOAILayer2D.i + 1 + return MockMOAILayer2D.layers[MockMOAILayer2D.i] + end + + function MockMOAILayer2D:reset() + self.newCalled = 0; + self.i=0 + end + + return MockMOAILayer2D +end \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockMOAIPartition.lua b/rapanui-test/mockobjects/MockMOAIPartition.lua new file mode 100644 index 0000000..a5ebed1 --- /dev/null +++ b/rapanui-test/mockobjects/MockMOAIPartition.lua @@ -0,0 +1,21 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createMockMOAIPartition(partition) + local MockMOAIPartition = { + newCalled = 0, + PARTITION = partition + } + + function MockMOAIPartition.new() + MockMOAIPartition.newCalled = MockMOAIPartition.newCalled + 1 + return MockMOAIPartition.PARTITION + end + + function MockMOAIPartition:reset() + self.newCalled = 0 + end + + return MockMOAIPartition +end + diff --git a/rapanui-test/mockobjects/MockMOAISim.lua b/rapanui-test/mockobjects/MockMOAISim.lua new file mode 100644 index 0000000..9cf87a2 --- /dev/null +++ b/rapanui-test/mockobjects/MockMOAISim.lua @@ -0,0 +1,29 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createMockMOAISim() + + local MockMOAISim = { + pushRenderPassCalled = 0, + openWindowCalled = 0 + } + + MockMOAISim.pushRenderPass = function(layer) + assert_not_nil(layer) + MockMOAISim.pushRenderPassCalled = MOAISim.pushRenderPassCalled + 1 + end + + function MockMOAISim.openWindow(name, screenlwidth, screenHeight) + assert_that(screenlwidth,is(equal_to(MockConstants.SCREENWIDTH))) + assert_that(screenHeight,is(equal_to(MockConstants.SCREENHEIGHT))) + assert_that(name,is(equal_to(MockConstants.WINDOWNAME))) + MockMOAISim.openWindowCalled = MockMOAISim.openWindowCalled + 1 + end + + function MockMOAISim:reset() + self.pushRenderPassCalled = 0 + self.openWindowCalled = 0 + end + + return MockMOAISim +end \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockMOAIViewport.lua b/rapanui-test/mockobjects/MockMOAIViewport.lua new file mode 100644 index 0000000..eb5ca2b --- /dev/null +++ b/rapanui-test/mockobjects/MockMOAIViewport.lua @@ -0,0 +1,22 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createMockMOAIViewport(viewport) + + MockMOAIViewport = { + newCalled = 0, + VIEWPORT = viewport, + name="testViewport" + } + + MockMOAIViewport.new = function() + MockMOAIViewport.newCalled = MockMOAIViewport.newCalled + 1 + return MockMOAIViewport.VIEWPORT + end + + function MockMOAIViewport:reset() + self.newCalled = 0 + end + + return MockMOAIViewport +end \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockPartition.lua b/rapanui-test/mockobjects/MockPartition.lua new file mode 100644 index 0000000..0f21243 --- /dev/null +++ b/rapanui-test/mockobjects/MockPartition.lua @@ -0,0 +1,28 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createPartition(name) + local MockPartition = { + insertPropCalled = 0, + removePropCalled = 0, + name="TEST_PARTITION" + } + + function MockPartition:insertProp(prop) + self.insertPropCalled = self.insertPropCalled + 1 + end + + function MockPartition:removeProp(prop) + self.removePropCalled = self.removePropCalled + 1 + end + + MockPartition.name = name + + function MockPartition:reset() + self.insertPropCalled = 0 + self.removePropCalled = 0 + end + + return MockPartition +end + diff --git a/rapanui-test/mockobjects/MockRNGroup.lua b/rapanui-test/mockobjects/MockRNGroup.lua new file mode 100644 index 0000000..fd6c390 --- /dev/null +++ b/rapanui-test/mockobjects/MockRNGroup.lua @@ -0,0 +1,26 @@ +-- Author: Marko Pukari +-- Date: 12/2/12 + +function createMockRNGroup() + MockRNGroup = { + newCalled = 0, + insertCalled = 0 + } + + function MockRNGroup:new() + self.newCalled = self.newCalled + 1 + return self + end + + function MockRNGroup:insert(rnobject) + assert_equal(rnobject,RNObject) + self.insertCalled = self.insertCalled + 1 + end + + function MockRNGroup:reset() + self.newCalled = 0 + self.insertCalled = 0 + end + + return MockRNGroup +end \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockRNInputManager.lua b/rapanui-test/mockobjects/MockRNInputManager.lua new file mode 100644 index 0000000..74fa6fc --- /dev/null +++ b/rapanui-test/mockobjects/MockRNInputManager.lua @@ -0,0 +1,20 @@ +-- Author: Marko Pukari +-- Date: 12/2/12 + +function createMockRNInputManager() + MockRNInputManager = { + setGlobalRNScreenCalled = 0 + } + + function MockRNInputManager.setGlobalRNScreen(screen) + assert_true(screen == RNScreen) + MockRNInputManager.setGlobalRNScreenCalled = MockRNInputManager.setGlobalRNScreenCalled + 1 + return self + end + + function MockRNInputManager:reset() + MockRNInputManager.setGlobalRNScreenCalled = 0 + end + + return MockRNInputManager +end \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockRNLayer.lua b/rapanui-test/mockobjects/MockRNLayer.lua new file mode 100644 index 0000000..943fa58 --- /dev/null +++ b/rapanui-test/mockobjects/MockRNLayer.lua @@ -0,0 +1,19 @@ +function createMockRNLayer(expectGetBytName) + + MockRNLayer = { + getCalled = 0, + expectName = expectGetBytName, + MAIN_LAYER = "mainlayer" + } + + function MockRNLayer.new() + return MockRNLayer + end + function MockRNLayer:get(name) + assert_that(name,is(equal_to(self.expectName))) + return createTestLayer("main",{},{}) + end + + return MockRNLayer +end + diff --git a/rapanui-test/mockobjects/MockRNObject.lua b/rapanui-test/mockobjects/MockRNObject.lua new file mode 100644 index 0000000..6a4e2e2 --- /dev/null +++ b/rapanui-test/mockobjects/MockRNObject.lua @@ -0,0 +1,52 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +function createRNObject(name,prop) + local MockRNObject = { + setLocatingModeCalled = 0, + setParentSceneCalled = 0, + updateLocationCalled = 0, + newCalled = 0, + initWithImage2Called = 0, + MOAIPROP = prop, + name = name, + originalWidth = MockConstants.ORIGINALWIDTH, + originalHeight = MockConstants.ORIGINALHEIGHT + } + + function MockRNObject:new() + self.newCalled = self.newCalled + 1 + return self + end + + function MockRNObject:initWithImage2(image) + self.initWithImage2Called = self.initWithImage2Called + 1 + return self,{} + + end + + function MockRNObject:getProp() + return self.MOAIPROP + end + + function MockRNObject:setLocatingMode(mode) + self.setLocatingModeCalled = self.setLocatingModeCalled + 1 + end + + function MockRNObject:setParentScene(object) + self.setParentSceneCalled = self.setParentSceneCalled + 1 + end + + function MockRNObject:updateLocation() + self.updateLocationCalled = self.updateLocationCalled + 1 + end + + function MockRNObject:reset() + self.setLocatingModeCalled = 0 + self.setParentSceneCalled = 0 + self.updateLocationCalled = 0 + self.newCalled = 0 + end + + return MockRNObject +end diff --git a/rapanui-test/mockobjects/MockRNScreen.lua b/rapanui-test/mockobjects/MockRNScreen.lua new file mode 100644 index 0000000..b3b0154 --- /dev/null +++ b/rapanui-test/mockobjects/MockRNScreen.lua @@ -0,0 +1,39 @@ +-- Author: Marko Pukari +-- Date: 12/2/12 + +function createMockRNScreen() + MockRNScreen = { + newCalled = 0, + initWithCalled = 0, + addRNObjectCalled = 0, + layers = MockRNLayer.new() + } + + function MockRNScreen:new() + self.newCalled = self.newCalled + 1 + return self + end + + function MockRNScreen:initWith(lwidth, lheight, screenlwidth, screenHeight) + assert_that(lwidth,is(equal_to(MockConstants.SCREENWIDTH))) + assert_that(lheight,is(equal_to(MockConstants.SCREENHEIGHT))) + assert_that(screenlwidth,is(equal_to(MockConstants.SCREENWIDTH))) + assert_that(screenHeight,is(equal_to(MockConstants.SCREENHEIGHT))) + + self.initWithCalled = self.initWithCalled + 1 + end + + function MockRNScreen:addRNObject(rnobject,mode,layer) + assert_true(rnobject == RNObject) + assert_nil(mode) + self.addRNObjectCalled = self.addRNObjectCalled + 1 + end + + function MockRNScreen:reset() + --MockRNScreen.newCalled = 0 + MockRNScreen.initWithCalled = 0 + self.addRNObjectCalled = 0 + end + + return MockRNScreen +end \ No newline at end of file diff --git a/rapanui-test/mockobjects/MockViewport.lua b/rapanui-test/mockobjects/MockViewport.lua new file mode 100644 index 0000000..8e9c396 --- /dev/null +++ b/rapanui-test/mockobjects/MockViewport.lua @@ -0,0 +1,39 @@ +-- Author: Marko Pukari +-- Date: 11/30/12 + +require('MockConstants') +function createViewport(name) + local MockViewport = { + setSizeCalled = 0, + setScaleCalled = 0, + setOffsetCalled = 0 + } + + function MockViewport:setSize(screenWidth,screenHeight) + assert_that(screenWidth,is(equal_to(MockConstants.SCREENWIDTH))) + assert_that(screenHeight,is(equal_to(MockConstants.SCREENHEIGHT))) + MockViewport.setSizeCalled = MockViewport.setSizeCalled + 1 + end + + function MockViewport:setScale(width,height) + assert_that(width,is(equal_to(MockConstants.WIDTH))) + assert_that(height,is(equal_to(-MockConstants.HEIGHT))) + MockViewport.setScaleCalled = MockViewport.setScaleCalled + 1 + end + + function MockViewport:setOffset(offset_x,offset_y) + assert_that(offset_x,is(equal_to(MockConstants.OFFSET_X))) + assert_that(offset_y,is(equal_to(MockConstants.OFFSET_Y))) + MockViewport.setOffsetCalled = MockViewport.setOffsetCalled + 1 + end + + function MockViewport:reset() + MockViewport.setSizeCalled = 0 + MockViewport.setScaleCalled = 0 + MockViewport.setOffsetCalled = 0 + end + + MockViewport.name=name + + return MockViewport +end \ No newline at end of file