Log In  

You are correct. The game loop stripping feature as documented isn't useful with module-style Lua files, and is probably just not a useful idea. I'll file an issue to clean that up.

My natural inclination is to build a traditional test binary in the form of a cart then execute it headless. You could even put pico-test in a library. Here's an adaptation of pico-test's example:


local Test = {
 test = function(title,f)
  local desc=function(msg,f)
  local it=function(msg,f)
   local xs={f()}
   for i=1,#xs do
    if xs[i] == true then

return Test


local Math={
  gt=function(a,b) return a>b end,
  lt=function(a,b) return a<b end,
  mul=function(a,b) return a*b end,
  div=function(a,b) return a/b end

return Math

math_test.lua (shortened):

Test = require("test")
Math = require("math")

Test.test('Math functions', function(desc,it)
  desc('Math.gt()', function()
    local gt = Math.gt
    it('should return type boolean', function()
      return 'boolean' == type(gt(1,0))
    it('should give same result as > operator', function()
      return gt(1,0)

  -- ...

To build and run the tests headless (using appropriate paths for p8tool and pico8, and appropriate shell syntax for your platform):

p8tool build lib_test.p8 --lua lib_test.lua && pico8 -x lib_test.p8 | pico-test

(I got this to work, but for what it's worth, Pico-8 0.1.11G on macOS printed warnings on startup and crashed after running in headless mode. These are unrelated to this example, as far as I can tell.)

P#49682 2018-02-25 20:40 ( Edited 2018-02-26 01:40)

Indeed, I had to move tests in separate files in a similar way, but it wasn't that bad in the end.
I even added a test registration so I can test modules invidually or all at once:

-- picotest.lua
local test = {
  // ...
 test_suite={}  -- just add this

return test
-- testmath.lua
picotest = require("picotest")
math = require("math")

function test_math(desc,it)
  desc('Math.gt()', function()
    local gt = Math.gt
    it('should return type boolean', function()
      return 'boolean' == type(gt(1,0))
    it('should give same result as > operator', function()
      return gt(1,0)

-- register in test suite
add(picotest.test_suite, test_math)

-- pico-8 functions must be placed at the end to be parsed by p8tool correctly

function _init()
  picotest.test('math', test_math)

-- empty update allows to close test window with ctrl+c on success
function _update()
-- testall.lua
picotest = require("picotest")

-- require all of test modules here to register them to the test suite
-- etc.

function run_all_tests()
  picotest.test('all', function(desc,it)
    for test_callback in all(picotest.test_suite) do

function _init()

-- empty update allows to close test window with ctrl+c
function _update()

Then to build and test:

p8tool build testmath.p8 --lua testmath.lua && pico8 -x testmath.p8 | pico-test
p8tool build testall.p8 --lua testall.lua && pico8 -x testall.p8 | pico-test

On Unix you can also make a shell script:

# test.sh
p8tool build --lua "test$1.lua" "test$1.p8"

and call test.sh module or test.sh all


You need to build a full p8 for any test you make, since you cannot refer to another file and have to package the whole program in one place. If you still want to have your executable and your tests in one file to avoid duplicating the whole game code between your game.p8 and testall.p8, you may try to add some branching in _init and _update to either run the game or test based on some criterion (environment variable? special input? what can pico8 receive as program input?)

Note on require

It is crucial to require your modules with the exact same name to avoid duplicated packages (as picotool's author mentioned on GitHub). Otherwise, you will reimport different versions of the same global variables or tables and your test suite as well as the values you test will be messed up (typically, avoid requiring game packages from other game packages with "require("module") but from test packages with "require("src/module")").

P#52423 2018-05-05 16:02 ( Edited 2018-05-05 20:02)

In the end, I switched to external testing with busted. Fortunately it uses a similar format (describe - it) which made the conversion easier, but it still took me a lot of time to mock PICO-8 api in native Lua (helped by pico-love).
Now I'm not using p8tool anymore for my tests, only Lua native require, so I won't talk about it further in this thread.

P#54053 2018-07-10 14:42 ( Edited 2018-07-10 18:42)

My next struggle is continuous integration, as png cartridge export is not fully supported with PICO-8 format version 16. This has already been reported on GitHub. For now, I have to export my png manually from PICO-8, which is OK since I only release cartridges for tagged versions (v0.1, v0.2, etc.) which doesn't happen too often.

My other WIP is adding pseudo-preprocessor directives to strip debug code before shipping for Release. This seems promising and will allow to reduce the byte size under the threshold allowed to build a png cartridge. I'm not at ease with AST/parsing though so I do this as a pre-build step rather than integrating it in p8tool. That may be an interesting feature for picotool but fixing reported bugs should be the priority for now.

P#54054 2018-07-10 14:50 ( Edited 2018-07-10 18:50)

I'm having difficulties. Running this, not sure why everything must be written in Python.

I downloaded and unpacked the, "picotool-master" file. From command mode (DOS) typed:


Get back,

Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

Traceback (most recent call last):
File "C:\David\picotool-master\run_tests.py", line 3, in <module>
import nose
ImportError: No module named 'nose'


P#57339 2018-10-01 12:04 ( Edited 2018-10-01 16:04)

You'll have to install the "nose" Python module to run the tests. It could probably have been written in the README file (like it does for PYPNG), but most Python users will probably install picotool as a Python package, which will automatically also install all dependencies like "nose".

If you want to run the tests, either install "nose" with "pip install nose", or install picotool's dependencies with "pip install ." inside picotool's directory (or similar), since picotool is not available online through pip.

This is assuming you have pip, of course. I have no idea how anything like this works in Windows, which if I recall correctly does not have a package manager. The one time I tried to set up a Python environment on Windows I'm pretty sure I gave up (and I'm a Python programmer in my day job).

And since I know you're not used to modern programming environments, dw187, think of "pip" as a command-line based SPLORE for Python packages/libraries :) The same as "luarocks" for (regular non-PICO-8) Lua.

P#57354 2018-10-01 15:24 ( Edited 2018-10-01 19:27)

Link to pip-nose, please, tobiasvl - or has someone made a catch-all PKZIP file that has everything ready to go ?

P#57357 2018-10-01 16:29 ( Edited 2018-10-01 20:29)
P#57361 2018-10-01 17:32 ( Edited 2018-10-01 21:32)

Success ! ... I hope ...

I merged those nose files directly into the "picotool-master" file and am reading the "--help" now.

P#57365 2018-10-01 18:33 ( Edited 2018-10-01 22:33)

See the "Developing picotool" section of the README.md file, which mentions installing the nose test runner.

Note that you don't have to run the tests to use picotool. They're only needed if you plan on modifying the library itself.

(Python for Windows has made great strides in the last few years. I was impressed the most recent time I tried it.)

P#57385 2018-10-02 02:46 ( Edited 2018-10-02 06:46)

I also find the Python Installer for Windows easier to use, after having issues with Chocolate to manage versions. But I use scoop when I need updates and handling different versions (https://github.com/lukesampson/scoop/wiki/Switching-Ruby-and-Python-Versions).

To come back to the topic, as dddaaannn said, you can skip the testing part for now if you're confident you got a stable version. You just need Python 3 to run p8tool itself.

Now, if you really need a package that is commonly used in projects, you can install it globally:
pip install nose

and if you miss some permissions, add the --user flag to install to a user local folder:
pip install --user nose

(you can also use sudo pip install but once you start using sudo, you'll have to keep using sudo...)

Or install it in a virtual environment if you want to avoid installing packages on your default python. But to be honest, if you're not a Python developer, you should probably not spend too much time on this and just install what you need globally to make things work. You can do things more properly when you get into development in Python.

P#59774 2018-12-06 19:08

[Please log in to post a comment]