diff --git a/.luacov b/.luacov index fd844951..e2605fe8 100644 --- a/.luacov +++ b/.luacov @@ -38,12 +38,14 @@ return { 'luacov.runner$', 'luacov.stats$', 'luacov.tick$', - 'ansicolors$', + 'term$', + 'term.colors$', + 'term.cursor$', + 'term.init$', 'copas$', 'coxpcall$', 'mediator$', 'moonscript.*$', - 'socket$', }, diff --git a/.travis.yml b/.travis.yml index 4f9eeebb..c2125f70 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,23 @@ language: c env: - - LUA='Lua 5.1' - - LUA='Lua 5.2' - - LUA='LuaJIT 2.0' + global: + - LUAROCKS=2.3.0 + matrix: + - LUA=lua5.1 + - LUA=lua5.2 + - LUA=lua5.3 + - LUA=luajit + - LUA=luajit2.0 + - LUA=luajit2.1 before_install: - - bash .travis_setup.sh + - source .travis/setenv_lua.sh + - luarocks install luasec + - luarocks install moonscript install: - - sudo apt-get update -qq - - sudo luarocks install luasec - - sudo apt-get install -qq libev-dev - - sudo luarocks install lua-ev scm --server=http://luarocks.org/repositories/rocks-scm/ - - sudo luarocks install copas - - sudo luarocks install moonscript - - sudo luarocks make + - luarocks make busted-scm-0.rockspec script: busted diff --git a/.travis/platform.sh b/.travis/platform.sh new file mode 100644 index 00000000..7259a7d6 --- /dev/null +++ b/.travis/platform.sh @@ -0,0 +1,15 @@ +if [ -z "${PLATFORM:-}" ]; then + PLATFORM=$TRAVIS_OS_NAME; +fi + +if [ "$PLATFORM" == "osx" ]; then + PLATFORM="macosx"; +fi + +if [ -z "$PLATFORM" ]; then + if [ "$(uname)" == "Linux" ]; then + PLATFORM="linux"; + else + PLATFORM="macosx"; + fi; +fi diff --git a/.travis/setenv_lua.sh b/.travis/setenv_lua.sh new file mode 100644 index 00000000..8d8c8255 --- /dev/null +++ b/.travis/setenv_lua.sh @@ -0,0 +1,3 @@ +export PATH=${PATH}:$HOME/.lua:$HOME/.local/bin:${TRAVIS_BUILD_DIR}/install/luarocks/bin +bash .travis/setup_lua.sh +eval `$HOME/.lua/luarocks path` diff --git a/.travis/setup_lua.sh b/.travis/setup_lua.sh new file mode 100644 index 00000000..6dcc0c6e --- /dev/null +++ b/.travis/setup_lua.sh @@ -0,0 +1,122 @@ +#! /bin/bash + +# A script for setting up environment for travis-ci testing. +# Sets up Lua and Luarocks. +# LUA must be "lua5.1", "lua5.2" or "luajit". +# luajit2.0 - master v2.0 +# luajit2.1 - master v2.1 + +set -eufo pipefail + +LUAJIT_VERSION="2.0.4" +LUAJIT_BASE="LuaJIT-$LUAJIT_VERSION" + +source .travis/platform.sh + +LUA_HOME_DIR=$TRAVIS_BUILD_DIR/install/lua + +LR_HOME_DIR=$TRAVIS_BUILD_DIR/install/luarocks + +mkdir $HOME/.lua + +LUAJIT="no" + +if [ "$PLATFORM" == "macosx" ]; then + if [ "$LUA" == "luajit" ]; then + LUAJIT="yes"; + fi + if [ "$LUA" == "luajit2.0" ]; then + LUAJIT="yes"; + fi + if [ "$LUA" == "luajit2.1" ]; then + LUAJIT="yes"; + fi; +elif [ "$(expr substr $LUA 1 6)" == "luajit" ]; then + LUAJIT="yes"; +fi + +mkdir -p "$LUA_HOME_DIR" + +if [ "$LUAJIT" == "yes" ]; then + + if [ "$LUA" == "luajit" ]; then + curl --location https://github.com/LuaJIT/LuaJIT/archive/v$LUAJIT_VERSION.tar.gz | tar xz; + else + git clone https://github.com/LuaJIT/LuaJIT.git $LUAJIT_BASE; + fi + + cd $LUAJIT_BASE + + if [ "$LUA" == "luajit2.1" ]; then + git checkout v2.1; + # force the INSTALL_TNAME to be luajit + perl -i -pe 's/INSTALL_TNAME=.+/INSTALL_TNAME= luajit/' Makefile + fi + + make && make install PREFIX="$LUA_HOME_DIR" + + ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/luajit + ln -s $LUA_HOME_DIR/bin/luajit $HOME/.lua/lua; + +else + + if [ "$LUA" == "lua5.1" ]; then + curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz + cd lua-5.1.5; + elif [ "$LUA" == "lua5.2" ]; then + curl http://www.lua.org/ftp/lua-5.2.4.tar.gz | tar xz + cd lua-5.2.4; + elif [ "$LUA" == "lua5.3" ]; then + curl http://www.lua.org/ftp/lua-5.3.2.tar.gz | tar xz + cd lua-5.3.2; + fi + + # Build Lua without backwards compatibility for testing + perl -i -pe 's/-DLUA_COMPAT_(ALL|5_2)//' src/Makefile + make $PLATFORM + make INSTALL_TOP="$LUA_HOME_DIR" install; + + ln -s $LUA_HOME_DIR/bin/lua $HOME/.lua/lua + ln -s $LUA_HOME_DIR/bin/luac $HOME/.lua/luac; + +fi + +cd $TRAVIS_BUILD_DIR + +lua -v + +LUAROCKS_BASE=luarocks-$LUAROCKS + +curl --location http://luarocks.org/releases/$LUAROCKS_BASE.tar.gz | tar xz + +cd $LUAROCKS_BASE + +if [ "$LUA" == "luajit" ]; then + ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; +elif [ "$LUA" == "luajit2.0" ]; then + ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.0" --prefix="$LR_HOME_DIR"; +elif [ "$LUA" == "luajit2.1" ]; then + ./configure --lua-suffix=jit --with-lua-include="$LUA_HOME_DIR/include/luajit-2.1" --prefix="$LR_HOME_DIR"; +else + ./configure --with-lua="$LUA_HOME_DIR" --prefix="$LR_HOME_DIR" +fi + +make build && make install + +ln -s $LR_HOME_DIR/bin/luarocks $HOME/.lua/luarocks + +cd $TRAVIS_BUILD_DIR + +luarocks --version + +rm -rf $LUAROCKS_BASE + +if [ "$LUAJIT" == "yes" ]; then + rm -rf $LUAJIT_BASE; +elif [ "$LUA" == "lua5.1" ]; then + rm -rf lua-5.1.5; +elif [ "$LUA" == "lua5.2" ]; then + rm -rf lua-5.2.4; +elif [ "$LUA" == "lua5.3" ]; then + rm -rf lua-5.3.2; +fi diff --git a/.travis_setup.sh b/.travis_setup.sh deleted file mode 100644 index 16b02769..00000000 --- a/.travis_setup.sh +++ /dev/null @@ -1,37 +0,0 @@ -# A script for setting up environment for travis-ci testing. -# Sets up Lua and Luarocks. -# LUA must be "Lua 5.1", "Lua 5.2" or "LuaJIT 2.0". - -echo 'rocks_servers = { - "http://rocks.moonscript.org/", - "http://luarocks.org/repositories/rocks" -}' >> ~/config.lua - - -if [ "$LUA" == "LuaJIT 2.0" ]; then - curl http://luajit.org/download/LuaJIT-2.0.2.tar.gz | tar xz - cd LuaJIT-2.0.2 - make && sudo make install INSTALL_TSYMNAME=lua; -else - if [ "$LUA" == "Lua 5.1" ]; then - curl http://www.lua.org/ftp/lua-5.1.5.tar.gz | tar xz - cd lua-5.1.5; - elif [ "$LUA" == "Lua 5.2" ]; then - curl http://www.lua.org/ftp/lua-5.2.3.tar.gz | tar xz - cd lua-5.2.3; - fi - sudo make linux install; -fi - -cd .. -curl http://luarocks.org/releases/luarocks-2.1.2.tar.gz | tar xz -cd luarocks-2.1.2 - -if [ "$LUA" == "LuaJIT 2.0" ]; then - ./configure --with-lua-include=/usr/local/include/luajit-2.0; -else - ./configure; -fi - -make && sudo make install -cd .. diff --git a/README.md b/README.md index 43a2ff0e..232978e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ Busted ====== +[![Join the chat at https://gitter.im/Olivine-Labs/busted](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Olivine-Labs/busted?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + [![travis-ci status](https://secure.travis-ci.org/Olivine-Labs/busted.png)](http://travis-ci.org/#!/Olivine-Labs/busted/builds) busted is a unit testing framework with a focus on being **easy to diff --git a/bin/busted b/bin/busted index e4d9f3f9..706d530d 100755 --- a/bin/busted +++ b/bin/busted @@ -1,193 +1,3 @@ #!/usr/bin/env lua -- Busted command-line runner - -local cli = require 'cliargs' -local busted = require 'busted.core'() - -local configLoader = require 'busted.modules.configuration_loader'() -local outputHandlerLoader = require 'busted.modules.output_handler_loader'() - -local luacov = require 'busted.modules.luacov'() - -local path = require 'pl.path' -local utils = require 'pl.utils' - -require 'busted.init'(busted) - --- Default cli arg values -local defaultOutput = path.is_windows and 'plainTerminal' or 'utfTerminal' -local defaultLoaders = 'lua,moonscript' -local defaultPattern = '_spec' -local lpathprefix = './src/?.lua;./src/?/?.lua;./src/?/init.lua' -local cpathprefix = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;' - --- Load up the command-line interface options -cli:set_name('busted') -cli:add_flag('--version', 'prints the program version and exits') - -cli:optarg('ROOT', 'test script file/folder. Folders will be traversed for any file that matches the --pattern option.', 'spec', 1) - -cli:add_option('-o, --output=LIBRARY', 'output library to load', defaultOutput) -cli:add_option('-d, --cwd=cwd', 'path to current working directory', './') -cli:add_option('-p, --pattern=PATTERN', 'only run test files matching the Lua pattern', defaultPattern) -cli:add_option('-t, --tags=TAGS', 'only run tests with these #tags') -cli:add_option('--exclude-tags=TAGS', 'do not run tests with these #tags, takes precedence over --tags') -cli:add_option('-m, --lpath=PATH', 'optional path to be prefixed to the Lua module search path', lpathprefix) -cli:add_option('--cpath=PATH', 'optional path to be prefixed to the Lua C module search path', cpathprefix) -cli:add_option('-r, --run=RUN', 'config to run from .busted file') -cli:add_option('--lang=LANG', 'language for error messages', 'en') -cli:add_option('--loaders=NAME', 'test file loaders', defaultLoaders) - -cli:add_flag('-c, --coverage', 'do code coverage analysis (requires `LuaCov` to be installed)') - -cli:add_flag('-v, --verbose', 'verbose output of errors') -cli:add_flag('-s, --enable-sound', 'executes `say` command if available') -cli:add_flag('--suppress-pending', 'suppress `pending` test output') -cli:add_flag('--defer-print', 'defer print to when test suite is complete') - --- Parse the cli arguments -local cliArgs = cli:parse_args() - --- Return early if only asked for the version -if cliArgs.version then - return print(busted.version) -end - -local tags = {} -local excludeTags = {} - -if cliArgs.t ~= '' then - tags = utils.split(cliArgs.t, ',') -end - -if cliArgs['exclude-tags'] ~= '' then - excludeTags = utils.split(cliArgs['exclude-tags'], ',') -end - --- Load current working directory -local fpath = cliArgs.d - --- Load test directory -local rootFile = path.normpath(path.join(fpath, cliArgs.ROOT)) - -local pattern = cliArgs.pattern - --- Load busted config file if available -local bustedConfigFilePath = path.normpath(path.join(fpath, '.busted')) -local bustedConfigFile = pcall(function() tasks = loadfile(bustedConfigFilePath)() end) - -if bustedConfigFile then - local config, err = configLoader(bustedConfigFile, config, cliArgs) - - if err then - print(err) - end -end - --- If coverage arg is passed in, load LuaCovsupport -if cliArgs.coverage then - luacov() -end - --- Add additional package paths based on lpath and cpath cliArgs -if #cliArgs.lpath > 0 then - lpathprefix = cliArgs.lpath - lpathprefix = lpathprefix:gsub('^%.[/%\\]', fpath ) - lpathprefix = lpathprefix:gsub(';%.[/%\\]', ';' .. fpath) - package.path = (lpathprefix .. ';' .. package.path):gsub(';;',';') -end - -if #cliArgs.cpath > 0 then - cpathprefix = cliArgs.cpath - cpathprefix = cpathprefix:gsub('^%.[/%\\]', fpath ) - cpathprefix = cpathprefix:gsub(';%.[/%\\]', ';' .. fpath) - package.cpath = (cpathprefix .. ';' .. package.cpath):gsub(';;',';') -end - -local loaders = {} -if #cliArgs.loaders > 0 then - string.gsub(cliArgs.loaders, '([^,]+)', function(c) loaders[#loaders+1] = c end) -end - --- We report an error if the same tag appears in both `options.tags` --- and `options.excluded_tags` because it does not make sense for the --- user to tell Busted to include and exclude the same tests at the --- same time. -for _, excluded in pairs(excludeTags) do - for _, included in pairs(tags) do - if excluded == included then - print('Cannot use --tags and --exclude-tags for the same tags') - os.exit(1) - end - end -end - --- Set up output handler to listen to events -local outputHandlerOptions = { - verbose = cliArgs.verbose, - suppressPending = cliArgs['suppress-pending'], - language = cliArgs.lang, - deferPrint = cliArgs['defer-print'] -} - -outputHandler = outputHandlerLoader(cliArgs.output, cliArgs.o, outputHandlerOptions, busted) -outputHandler:subscribe(outputHandlerOptions) - -if cliArgs.s then - require 'busted.outputHandlers.sound'(outputHandlerOptions, busted) -end - -local checkTag = function(name, tag, modifier) - local found = name:find('#' .. tag) - return (modifier == (found ~= nil)) -end - -local checkTags = function(name) - for i, tag in pairs(tags) do - if not checkTag(name, tag, true) then - return nil, false - end - end - - for i, tag in pairs(excludeTags) do - if not checkTag(name, tag, false) then - return nil, false - end - end - - return nil, true -end - -if cliArgs.t ~= '' or cliArgs['exclude-tags'] ~= '' then - -- Watch for tags - busted.subscribe({ 'register', 'it' }, checkTags, { priority = 1 }) - busted.subscribe({ 'register', 'pending' }, checkTags, { priority = 1 }) -end - -local testFileLoader = require 'busted.modules.test_file_loader'(busted, loaders) -testFileLoader(rootFile, pattern) - --- watch for test errors -local failures = 0 -local errors = 0 - -busted.subscribe({ 'error' }, function() - errors = errors + 1 -end) - -busted.subscribe({ 'test', 'end' }, function(element, parent, status) - if status == 'failure' then - failures = failures + 1 - end -end) - -busted.publish({ 'suite', 'start' }) -busted.execute() -busted.publish({ 'suite', 'end' }) - -local exit = 0 -if failures > 0 or errors > 0 then - exit = 1 -end - -os.exit(exit) +require 'busted.runner'({ standalone = false }) diff --git a/busted-2.0.rc1-0.rockspec b/busted-2.0.rc12-1.rockspec similarity index 72% rename from busted-2.0.rc1-0.rockspec rename to busted-2.0.rc12-1.rockspec index c64c3981..08d86dda 100644 --- a/busted-2.0.rc1-0.rockspec +++ b/busted-2.0.rc12-1.rockspec @@ -1,8 +1,8 @@ package = 'busted' -version = '2.0.rc1-0' +version = '2.0.rc12-1' source = { - url = 'https://github.com/Olivine-Labs/busted/archive/v2.0.rc1.tar.gz', - dir = 'busted-2.0.rc1' + url = 'https://github.com/Olivine-Labs/busted/archive/v2.0.rc12-1.tar.gz', + dir = 'busted-2.0.rc12-1' } description = { summary = 'Elegant Lua unit testing.', @@ -19,15 +19,17 @@ description = { } dependencies = { 'lua >= 5.1', - 'lua_cliargs >= 2.0', + 'lua_cliargs = 3.0-1', 'luafilesystem >= 1.5.0', + 'luasystem >= 0.2.0-0', 'dkjson >= 2.1.0', - 'say >= 1.2-1', - 'luassert >= 1.7.0-0', - 'ansicolors >= 1.0-1', - 'penlight >= 1.0.0-1', - 'mediator_lua >= 1.1-3', + 'say >= 1.3-0', + 'luassert >= 1.7.8-0', + 'lua-term >= 0.1-1', + 'penlight >= 1.3.2-2', + 'mediator_lua >= 1.1.1-0', } + build = { type = 'builtin', modules = { @@ -35,13 +37,23 @@ build = { ['busted.context'] = 'busted/context.lua', ['busted.environment'] = 'busted/environment.lua', ['busted.compatibility'] = 'busted/compatibility.lua', + ['busted.options'] = 'busted/options.lua', ['busted.done'] = 'busted/done.lua', + ['busted.runner'] = 'busted/runner.lua', + ['busted.status'] = 'busted/status.lua', + ['busted.utils'] = 'busted/utils.lua', + ['busted.block'] = 'busted/block.lua', + ['busted.execute'] = 'busted/execute.lua', ['busted.init'] = 'busted/init.lua', ['busted.modules.configuration_loader'] = 'busted/modules/configuration_loader.lua', ['busted.modules.luacov'] = 'busted/modules/luacov.lua', + ['busted.modules.standalone_loader'] = 'busted/modules/standalone_loader.lua', ['busted.modules.test_file_loader'] = 'busted/modules/test_file_loader.lua', ['busted.modules.output_handler_loader'] = 'busted/modules/output_handler_loader.lua', + ['busted.modules.helper_loader'] = 'busted/modules/helper_loader.lua', + ['busted.modules.filter_loader'] = 'busted/modules/filter_loader.lua', + ['busted.modules.cli'] = 'busted/modules/cli.lua', ['busted.modules.files.lua'] = 'busted/modules/files/lua.lua', ['busted.modules.files.moonscript'] = 'busted/modules/files/moonscript.lua', @@ -53,11 +65,13 @@ build = { ['busted.outputHandlers.TAP'] = 'busted/outputHandlers/TAP.lua', ['busted.outputHandlers.json'] = 'busted/outputHandlers/json.lua', ['busted.outputHandlers.junit'] = 'busted/outputHandlers/junit.lua', + ['busted.outputHandlers.gtest'] = 'busted/outputHandlers/gtest.lua', ['busted.outputHandlers.sound'] = 'busted/outputHandlers/sound.lua', ['busted.languages.en'] = 'busted/languages/en.lua', ['busted.languages.ar'] = 'busted/languages/ar.lua', ['busted.languages.de'] = 'busted/languages/de.lua', + ['busted.languages.es'] = 'busted/languages/es.lua', ['busted.languages.fr'] = 'busted/languages/fr.lua', ['busted.languages.ja'] = 'busted/languages/ja.lua', ['busted.languages.nl'] = 'busted/languages/nl.lua', @@ -65,6 +79,7 @@ build = { ['busted.languages.th'] = 'busted/languages/th.lua', ['busted.languages.ua'] = 'busted/languages/ua.lua', ['busted.languages.zh'] = 'busted/languages/zh.lua', + ['busted.languages.it'] = 'busted/languages/it.lua', }, install = { bin = { diff --git a/busted-scm-0.rockspec b/busted-scm-0.rockspec new file mode 100644 index 00000000..4bb668f6 --- /dev/null +++ b/busted-scm-0.rockspec @@ -0,0 +1,89 @@ +package = 'busted' +version = 'scm-0' +source = { + url = "git://github.com/Olivine-Labs/busted", + branch = "master" +} +description = { + summary = 'Elegant Lua unit testing.', + detailed = [[ + An elegant, extensible, testing framework. + Ships with a large amount of useful asserts, + plus the ability to write your own. Output + in pretty or plain terminal format, JSON, + or TAP for CI integration. Great for TDD + and unit, integration, and functional tests. + ]], + homepage = 'http://olivinelabs.com/busted/', + license = 'MIT ' +} +dependencies = { + 'lua >= 5.1', + 'lua_cliargs = 3.0-1', + 'luafilesystem >= 1.5.0', + 'luasystem >= 0.2.0-0', + 'dkjson >= 2.1.0', + 'say >= 1.3-0', + 'luassert >= 1.7.8-0', + 'lua-term >= 0.1-1', + 'penlight >= 1.3.2-2', + 'mediator_lua >= 1.1.1-0', +} + +build = { + type = 'builtin', + modules = { + ['busted.core'] = 'busted/core.lua', + ['busted.context'] = 'busted/context.lua', + ['busted.environment'] = 'busted/environment.lua', + ['busted.compatibility'] = 'busted/compatibility.lua', + ['busted.options'] = 'busted/options.lua', + ['busted.done'] = 'busted/done.lua', + ['busted.runner'] = 'busted/runner.lua', + ['busted.status'] = 'busted/status.lua', + ['busted.utils'] = 'busted/utils.lua', + ['busted.block'] = 'busted/block.lua', + ['busted.execute'] = 'busted/execute.lua', + ['busted.init'] = 'busted/init.lua', + + ['busted.modules.configuration_loader'] = 'busted/modules/configuration_loader.lua', + ['busted.modules.luacov'] = 'busted/modules/luacov.lua', + ['busted.modules.standalone_loader'] = 'busted/modules/standalone_loader.lua', + ['busted.modules.test_file_loader'] = 'busted/modules/test_file_loader.lua', + ['busted.modules.output_handler_loader'] = 'busted/modules/output_handler_loader.lua', + ['busted.modules.helper_loader'] = 'busted/modules/helper_loader.lua', + ['busted.modules.filter_loader'] = 'busted/modules/filter_loader.lua', + ['busted.modules.cli'] = 'busted/modules/cli.lua', + + ['busted.modules.files.lua'] = 'busted/modules/files/lua.lua', + ['busted.modules.files.moonscript'] = 'busted/modules/files/moonscript.lua', + ['busted.modules.files.terra'] = 'busted/modules/files/terra.lua', + + ['busted.outputHandlers.base'] = 'busted/outputHandlers/base.lua', + ['busted.outputHandlers.utfTerminal'] = 'busted/outputHandlers/utfTerminal.lua', + ['busted.outputHandlers.plainTerminal'] = 'busted/outputHandlers/plainTerminal.lua', + ['busted.outputHandlers.TAP'] = 'busted/outputHandlers/TAP.lua', + ['busted.outputHandlers.json'] = 'busted/outputHandlers/json.lua', + ['busted.outputHandlers.junit'] = 'busted/outputHandlers/junit.lua', + ['busted.outputHandlers.gtest'] = 'busted/outputHandlers/gtest.lua', + ['busted.outputHandlers.sound'] = 'busted/outputHandlers/sound.lua', + + ['busted.languages.en'] = 'busted/languages/en.lua', + ['busted.languages.ar'] = 'busted/languages/ar.lua', + ['busted.languages.de'] = 'busted/languages/de.lua', + ['busted.languages.es'] = 'busted/languages/es.lua', + ['busted.languages.fr'] = 'busted/languages/fr.lua', + ['busted.languages.ja'] = 'busted/languages/ja.lua', + ['busted.languages.nl'] = 'busted/languages/nl.lua', + ['busted.languages.ru'] = 'busted/languages/ru.lua', + ['busted.languages.th'] = 'busted/languages/th.lua', + ['busted.languages.ua'] = 'busted/languages/ua.lua', + ['busted.languages.zh'] = 'busted/languages/zh.lua', + ['busted.languages.it'] = 'busted/languages/it.lua', + }, + install = { + bin = { + ['busted'] = 'bin/busted' + } + } +} diff --git a/busted.lua b/busted.lua new file mode 100644 index 00000000..917d21d8 --- /dev/null +++ b/busted.lua @@ -0,0 +1,3 @@ +-- This is a dummy file so it can be used in busted's specs +-- without adding ./?/init.lua to the lua path +return require 'busted.init' diff --git a/busted/block.lua b/busted/block.lua new file mode 100644 index 00000000..8ecc98ae --- /dev/null +++ b/busted/block.lua @@ -0,0 +1,164 @@ +local getfenv = require 'busted.compatibility'.getfenv +local unpack = require 'busted.compatibility'.unpack +local shuffle = require 'busted.utils'.shuffle + +local function sort(elements) + table.sort(elements, function(t1, t2) + if t1.name and t2.name then + return t1.name < t2.name + end + return t2.name ~= nil + end) + return elements +end + +return function(busted) + local block = {} + local root = busted.context.get() + + function block.reject(descriptor, element) + element.env[descriptor] = function(...) + error("'" .. descriptor .. "' not supported inside current context block", 2) + end + end + + function block.rejectAll(element) + local env = getfenv(element.run) + block.reject('randomize', element) + for descriptor, _ in pairs(busted.executors) do + if root.env[descriptor] and (env ~= _G and env[descriptor] or rawget(env, descriptor)) then + block.reject(descriptor, element) + end + end + end + + local function exec(descriptor, element) + if not element.env then element.env = {} end + block.rejectAll(element) + local ret = { busted.safe(descriptor, element.run, element) } + return unpack(ret) + end + + function block.execAllOnce(descriptor, current, err) + local parent = busted.context.parent(current) + + if parent then + local success = block.execAllOnce(descriptor, parent) + if not success then + return success + end + end + + if not current[descriptor] then + current[descriptor] = {} + end + local list = current[descriptor] + if list.success ~= nil then + return list.success + end + + local success = true + for _, v in ipairs(list) do + if not exec(descriptor, v):success() then + if err then err(descriptor) end + success = false + end + end + + list.success = success + + return success + end + + function block.execAll(descriptor, current, propagate, err) + local parent = busted.context.parent(current) + + if propagate and parent then + local success, ancestor = block.execAll(descriptor, parent, propagate) + if not success then + return success, ancestor + end + end + + local list = current[descriptor] or {} + + local success = true + for _, v in ipairs(list) do + if not exec(descriptor, v):success() then + if err then err(descriptor) end + success = nil + end + end + return success, current + end + + function block.dexecAll(descriptor, current, propagate, err) + local parent = busted.context.parent(current) + local list = current[descriptor] or {} + + local success = true + for _, v in ipairs(list) do + if not exec(descriptor, v):success() then + if err then err(descriptor) end + success = nil + end + end + + if propagate and parent then + if not block.dexecAll(descriptor, parent, propagate) then + success = nil + end + end + return success + end + + function block.lazySetup(element, err) + return block.execAllOnce('lazy_setup', element, err) + end + + function block.lazyTeardown(element, err) + if element.lazy_setup and element.lazy_setup.success ~= nil then + block.dexecAll('lazy_teardown', element, nil, err) + element.lazy_setup.success = nil + end + end + + function block.setup(element, err) + return block.execAll('strict_setup', element, nil, err) + end + + function block.teardown(element, err) + return block.dexecAll('strict_teardown', element, nil, err) + end + + function block.execute(descriptor, element) + if not element.env then element.env = {} end + + local randomize = busted.randomize + local randomseed = busted.randomseed + element.env.randomize = function(...) + randomize = (select('#', ...) == 0 or ...) + if randomize then + randomseed = tonumber(({...})[1]) or tonumber(({...})[2]) or randomseed + end + end + + if busted.safe(descriptor, element.run, element):success() then + if busted.sort then + sort(busted.context.children(element)) + elseif randomize then + element.randomseed = randomseed + shuffle(busted.context.children(element), randomseed) + end + + if block.setup(element) then + busted.execute(element) + end + + block.lazyTeardown(element) + block.teardown(element) + end + end + + return block +end diff --git a/busted/compatibility.lua b/busted/compatibility.lua index 30279b4a..39371ea6 100644 --- a/busted/compatibility.lua +++ b/busted/compatibility.lua @@ -1,4 +1,17 @@ return { + getfenv = getfenv or function(f) + f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) + local name, value + local up = 0 + + repeat + up = up + 1 + name, value = debug.getupvalue(f, up) + until name == '_ENV' or name == nil + + return name and value or _G + end, + setfenv = setfenv or function(f, t) f = (type(f) == 'function' and f or debug.getinfo(f + 1, 'f').func) local name @@ -10,7 +23,47 @@ return { until name == '_ENV' or name == nil if name then - debug.upvaluejoin(f, up, function() return t end, 1) + debug.upvaluejoin(f, up, function() return name end, 1) + debug.setupvalue(f, up, t) end - end + + if f ~= 0 then return f end + end, + + loadstring = loadstring or load, + unpack = table.unpack or unpack, + + exit = function(code, force) + if not force and code ~= 0 and _VERSION:match('^Lua 5%.[12]$') then + error() + elseif code ~= 0 then + code = 1 + end + if _VERSION == 'Lua 5.1' and + (type(jit) ~= 'table' or not jit.version or jit.version_num < 20000) then + -- From Lua 5.1 manual: + -- > The userdata itself is freed only in the next + -- > garbage-collection cycle. + -- Call collectgarbage() while collectgarbage('count') + -- changes + 3 times, at least 3 times, + -- at max 100 times (to prevent infinite loop). + local times_const = 0 + for i = 1, 100 do + local count_before = collectgarbage("count") + collectgarbage() + local count_after = collectgarbage("count") + if count_after == count_before then + times_const = times_const + 1 + if times_const > 3 then + break + end + else + times_const = 0 + end + end + end + os.exit(code, true) + end, + + execute = require 'pl.utils'.execute, } diff --git a/busted/context.lua b/busted/context.lua index 315e1c33..2a8bc6b9 100644 --- a/busted/context.lua +++ b/busted/context.lua @@ -1,14 +1,82 @@ +local tablex = require 'pl.tablex' + +local function save() + local g = {} + for k,_ in next, _G, nil do + g[k] = rawget(_G, k) + end + return { + gmt = debug.getmetatable(_G), + g = g, + loaded = tablex.copy(package.loaded) + } +end + +local function restore(state) + setmetatable(_G, state.gmt) + for k,_ in next, _G, nil do + rawset(_G, k, state.g[k]) + end + for k,_ in pairs(package.loaded) do + package.loaded[k] = state.loaded[k] + end +end + return function() local context = {} - local data = {} + local data = { descriptor = 'suite', attributes = {} } local parents = {} local children = {} + local stack = {} + local states = {} function context.ref() local ref = {} local ctx = data + local function unwrap(element, levels) + local levels = levels or 1 + local parent = element + for i = 1, levels do + parent = ref.parent(parent) + if not parent then break end + end + if not element.env then element.env = {} end + setmetatable(element.env, { + __newindex = function(self, key, value) + if not parent then + _G[key] = value + else + if not parent.env then parent.env = {} end + parent.env[key] = value + end + end + }) + end + + local function push_state(current) + local state = false + if current.attributes.envmode == 'insulate' then + state = save() + elseif current.attributes.envmode == 'unwrap' then + unwrap(current) + elseif current.attributes.envmode == 'expose' then + unwrap(current, 2) + end + table.insert(states, state) + end + + local function pop_state(current) + local state = table.remove(states) + if current.attributes.envmode == 'expose' then + states[#states] = states[#states] and save() + end + if state then + restore(state) + end + end + function ref.get(key) if not key then return ctx end return ctx[key] @@ -18,10 +86,19 @@ return function() ctx[key] = value end + function ref.clear() + data = { descriptor = 'suite', attributes = {} } + parents = {} + children = {} + stack = {} + states = {} + ctx = data + end + function ref.attach(child) if not children[ctx] then children[ctx] = {} end parents[child] = ctx - children[ctx][#children[ctx]+1] = child + table.insert(children[ctx], child) end function ref.children(parent) @@ -32,13 +109,18 @@ return function() return parents[child] end - function ref.push(child) - if not parents[child] then error('Detached child. Cannot push.') end - ctx = child + function ref.push(current) + if not parents[current] and current ~= data then error('Detached child. Cannot push.') end + if ctx ~= current then push_state(current) end + table.insert(stack, ctx) + ctx = current end function ref.pop() - ctx = parents[ctx] + local current = ctx + ctx = table.remove(stack) + if ctx ~= current then pop_state(current) end + if not ctx then error('Context stack empty. Cannot pop.') end end return ref diff --git a/busted/core.lua b/busted/core.lua index f574ec90..f9b869bf 100644 --- a/busted/core.lua +++ b/busted/core.lua @@ -1,32 +1,104 @@ +local getfenv = require 'busted.compatibility'.getfenv +local setfenv = require 'busted.compatibility'.setfenv +local unpack = require 'busted.compatibility'.unpack +local path = require 'pl.path' +local pretty = require 'pl.pretty' +local system = require 'system' +local throw = error + +local failureMt = { + __index = {}, + __tostring = function(e) return tostring(e.message) end, + __type = 'failure' +} + +local failureMtNoString = { + __index = {}, + __type = 'failure' +} + +local pendingMt = { + __index = {}, + __tostring = function(p) return p.message end, + __type = 'pending' +} + +local function errortype(obj) + local mt = debug.getmetatable(obj) + if mt == failureMt or mt == failureMtNoString then + return 'failure' + elseif mt == pendingMt then + return 'pending' + end + return 'error' +end + +local function hasToString(obj) + return type(obj) == 'string' or (debug.getmetatable(obj) or {}).__tostring +end + +local function isCallable(obj) + return type(obj) == 'function' or (debug.getmetatable(obj) or {}).__call +end + return function() local mediator = require 'mediator'() local busted = {} - busted.version = '2.0.rc1-0' + busted.version = '2.0.rc12-0' local root = require 'busted.context'() busted.context = root.ref() local environment = require 'busted.environment'(busted.context) + busted.api = {} busted.executors = {} local executors = {} - - busted.getTrace = function(element, level, msg) + local eattributes = {} + + busted.gettime = system.gettime + busted.monotime = system.monotime + busted.sleep = system.sleep + busted.status = require 'busted.status' + + function busted.getTrace(element, level, msg) + local function trimTrace(info) + local index = info.traceback:find('\n%s*%[C]') + info.traceback = info.traceback:sub(1, index) + return info + end level = level or 3 + local thisdir = path.dirname(debug.getinfo(1, 'Sl').source) local info = debug.getinfo(level, 'Sl') + while info.what == 'C' or info.short_src:match('luassert[/\\].*%.lua$') or + (info.source:sub(1,1) == '@' and thisdir == path.dirname(info.source)) do + level = level + 1 + info = debug.getinfo(level, 'Sl') + end + info.traceback = debug.traceback('', level) info.message = msg local file = busted.getFile(element) - return file.getTrace(file.name, info) + return file and file.getTrace(file.name, info) or trimTrace(info) end - busted.rewriteMessage = function(element, message) + function busted.rewriteMessage(element, message, trace) local file = busted.getFile(element) + local msg = hasToString(message) and tostring(message) + msg = msg or (message ~= nil and pretty.write(message) or 'Nil error') + msg = (file and file.rewriteMessage and file.rewriteMessage(file.name, msg) or msg) + + local hasFileLine = msg:match('^[^\n]-:%d+: .*') + if not hasFileLine then + local trace = trace or busted.getTrace(element, 3, message) + local fileline = trace.short_src .. ':' .. trace.currentline .. ': ' + msg = fileline .. msg + end - return file.rewriteMessage and file.rewriteMessage(file.name, message) or message + return msg end function busted.publish(...) @@ -37,8 +109,12 @@ return function() return mediator:subscribe(...) end + function busted.unsubscribe(...) + return mediator:removeSubscriber(...) + end + function busted.getFile(element) - local current, parent = element, busted.context.parent(element) + local parent = busted.context.parent(element) while parent do if parent.file then @@ -64,56 +140,158 @@ return function() return parent end - function busted.safe(descriptor, run, element, setenv) - if setenv and (type(run) == 'function' or getmetatable(run).__call) then + function busted.fail(msg, level) + local rawlevel = (type(level) ~= 'number' or level <= 0) and level + local level = level or 1 + local _, emsg = pcall(throw, msg, rawlevel or level+2) + local e = { message = emsg } + setmetatable(e, hasToString(msg) and failureMt or failureMtNoString) + throw(e, rawlevel or level+1) + end + + function busted.pending(msg) + local p = { message = msg } + setmetatable(p, pendingMt) + throw(p) + end + + function busted.bindfenv(callable, var, value) + local env = {} + local f = (debug.getmetatable(callable) or {}).__call or callable + setmetatable(env, { __index = getfenv(f) }) + env[var] = value + setfenv(f, env) + end + + function busted.wrap(callable) + if isCallable(callable) then -- prioritize __call if it exists, like in files - environment.wrap(getmetatable(run).__call or run) + environment.wrap((debug.getmetatable(callable) or {}).__call or callable) end + end + function busted.safe(descriptor, run, element) busted.context.push(element) local trace, message + local status = 'success' local ret = { xpcall(run, function(msg) - message = busted.rewriteMessage(element, msg) + status = errortype(msg) trace = busted.getTrace(element, 3, msg) + message = busted.rewriteMessage(element, msg, trace) end) } if not ret[1] then - busted.publish({ 'error', descriptor }, element, busted.context.parent(element), message, trace) + if status == 'success' then + status = 'error' + trace = busted.getTrace(element, 3, ret[2]) + message = busted.rewriteMessage(element, ret[2], trace) + elseif status == 'failure' and descriptor ~= 'it' then + -- Only 'it' blocks can generate test failures. Failures in all + -- other blocks are errors outside the test. + status = 'error' + end + -- Note: descriptor may be different from element.descriptor when + -- safe_publish is used (i.e. for test start/end). The safe_publish + -- descriptor needs to be different for 'it' blocks so that we can + -- detect that a 'failure' in a test start/end handler is not really + -- a test failure, but rather an error outside the test, much like a + -- failure in a support function (i.e. before_each/after_each or + -- setup/teardown). + busted.publish({ status, element.descriptor }, element, busted.context.parent(element), message, trace) end + ret[1] = busted.status(status) busted.context.pop() return unpack(ret) end - function busted.register(descriptor, executor) - executors[descriptor] = executor + function busted.safe_publish(descriptor, channel, element, ...) + local args = {...} + local n = select('#', ...) + if channel[2] == 'start' then + element.starttick = busted.monotime() + element.starttime = busted.gettime() + elseif channel[2] == 'end' then + element.endtime = busted.gettime() + element.endtick = busted.monotime() + element.duration = element.starttick and (element.endtick - element.starttick) + end + local status = busted.safe(descriptor, function() + busted.publish(channel, element, unpack(args, 1, n)) + end, element) + return status:success() + end + + function busted.exportApi(key, value) + busted.api[key] = value + end + + function busted.export(key, value) + busted.exportApi(key, value) + environment.set(key, value) + end + + function busted.hide(key, value) + busted.api[key] = nil + environment.set(key, nil) + end + + function busted.register(descriptor, executor, attributes) + local alias = nil + if type(executor) == 'string' then + alias = descriptor + descriptor = executor + executor = executors[descriptor] + attributes = attributes or eattributes[descriptor] + executors[alias] = executor + eattributes[alias] = attributes + else + if executor ~= nil and not isCallable(executor) then + attributes = executor + executor = nil + end + executors[descriptor] = executor + eattributes[descriptor] = attributes + end local publisher = function(name, fn) if not fn and type(name) == 'function' then fn = name - name = nil + name = alias end local trace - if descriptor ~= 'file' then - trace = busted.getTrace(busted.context.get(), 3, name) + local ctx = busted.context.get() + if busted.context.parent(ctx) then + trace = busted.getTrace(ctx, 3, name) + end + + local publish = function(f) + busted.publish({ 'register', descriptor }, name, f, trace, attributes) end - busted.publish({ 'register', descriptor }, name, fn, trace) + if fn then publish(fn) else return publish end end - busted.executors[descriptor] = publisher - environment.set(descriptor, publisher) + local edescriptor = alias or descriptor + busted.executors[edescriptor] = publisher + busted.export(edescriptor, publisher) - busted.subscribe({ 'register', descriptor }, function(name, fn, trace) + busted.subscribe({ 'register', descriptor }, function(name, fn, trace, attributes) local ctx = busted.context.get() local plugin = { descriptor = descriptor, + attributes = attributes or {}, name = name, run = fn, - trace = trace + trace = trace, + starttick = nil, + endtick = nil, + starttime = nil, + endtime = nil, + duration = nil, } busted.context.attach(plugin) @@ -130,8 +308,8 @@ return function() if not current then current = busted.context.get() end for _, v in pairs(busted.context.children(current)) do local executor = executors[v.descriptor] - if executor then - busted.safe(v.descriptor, function() return executor(v) end, v) + if executor and not busted.skipAll then + busted.safe(v.descriptor, function() executor(v) end, v) end end end diff --git a/busted/execute.lua b/busted/execute.lua new file mode 100644 index 00000000..8e0eea8c --- /dev/null +++ b/busted/execute.lua @@ -0,0 +1,72 @@ +local shuffle = require 'busted.utils'.shuffle +local urandom = require 'busted.utils'.urandom +local tablex = require 'pl.tablex' + +local function sort(elements) + table.sort(elements, function(t1, t2) + if t1.name and t2.name then + return t1.name < t2.name + end + return t2.name ~= nil + end) + return elements +end + +return function(busted) + local block = require 'busted.block'(busted) + + local function execute(runs, options) + local root = busted.context.get() + local children = tablex.copy(busted.context.children(root)) + + local function suite_reset() + local oldctx = busted.context.get() + + busted.context.clear() + local ctx = busted.context.get() + for k, v in pairs(oldctx) do + ctx[k] = v + end + + for _, child in ipairs(children) do + for descriptor, _ in pairs(busted.executors) do + child[descriptor] = nil + end + busted.context.attach(child) + end + + busted.randomseed = tonumber(options.seed) or urandom() or os.time() + end + + for i = 1, runs do + if i > 1 then + suite_reset() + root = busted.context.get() + busted.safe_publish('suite', { 'suite', 'reset' }, root, i, runs) + end + + if options.sort then + sort(busted.context.children(root)) + elseif options.shuffle then + root.randomseed = busted.randomseed + shuffle(busted.context.children(root), busted.randomseed) + end + + local seed = (busted.randomize and busted.randomseed or nil) + if busted.safe_publish('suite', { 'suite', 'start' }, root, i, runs, seed) then + if block.setup(root) then + busted.execute() + end + block.lazyTeardown(root) + block.teardown(root) + end + busted.safe_publish('suite', { 'suite', 'end' }, root, i, runs) + + if busted.skipAll then + break + end + end + end + + return execute +end diff --git a/busted/init.lua b/busted/init.lua index 5a62c20c..c8e0ca8e 100644 --- a/busted/init.lua +++ b/busted/init.lua @@ -1,133 +1,135 @@ -math.randomseed(os.time()) - -local function shuffle(t) - local n = #t - while n >= 2 do - local k = math.random(n) - t[n], t[k] = t[k], t[n] - n = n - 1 - end - return t -end - -return function(busted) - local function execAll(descriptor, current, propagate) - local parent = busted.context.parent(current) - - if propagate and parent then execAll(descriptor, parent, propagate) end - - local list = current[descriptor] +local function init(busted) + local block = require 'busted.block'(busted) - if list then - for _, v in pairs(list) do - busted.safe(descriptor, v.run, v) - end + local file = function(file) + busted.wrap(file.run) + if busted.safe_publish('file', { 'file', 'start' }, file) then + block.execute('file', file) end + busted.safe_publish('file', { 'file', 'end' }, file) end - local function dexecAll(descriptor, current, propagate) - local parent = busted.context.parent(current) - local list = current[descriptor] - - if list then - for _, v in pairs(list) do - busted.safe(descriptor, v.run, v) - end + local describe = function(describe) + local parent = busted.context.parent(describe) + if busted.safe_publish('describe', { 'describe', 'start' }, describe, parent) then + block.execute('describe', describe) end - - if propagate and parent then execAll(descriptor, parent, propagate) end + busted.safe_publish('describe', { 'describe', 'end' }, describe, parent) end - local file = function(file) - busted.publish({ 'file', 'start' }, file.name) + local it = function(element) + local parent = busted.context.parent(element) + local finally - if busted.safe('file', file.run, file, true) then - busted.execute(file) + if not block.lazySetup(parent) then + -- skip test if any setup failed + return end - busted.publish({ 'file', 'end' }, file.name) - end - - local describe = function(describe) - local parent = busted.context.parent(describe) + if not element.env then element.env = {} end - busted.publish({ 'describe', 'start' }, describe, parent) + block.rejectAll(element) + element.env.finally = function(fn) finally = fn end + element.env.pending = busted.pending - if not describe.env then describe.env = {} end - - local randomize = false - describe.env.randomize = function() - randomize = true - end + local pass, ancestor = block.execAll('before_each', parent, true) - if busted.safe('describe', describe.run, describe) then - if randomize then - shuffle(busted.context.children(describe)) + if pass then + local status = busted.status('success') + if busted.safe_publish('test', { 'test', 'start' }, element, parent) then + status:update(busted.safe('it', element.run, element)) + if finally then + block.reject('pending', element) + status:update(busted.safe('finally', finally, element)) + end + else + status = busted.status('error') end - execAll('setup', describe) - busted.execute(describe) - dexecAll('teardown', describe) + busted.safe_publish('test', { 'test', 'end' }, element, parent, tostring(status)) end - busted.publish({ 'describe', 'end' }, describe, parent) + block.dexecAll('after_each', ancestor, true) end - local it = function(it) - local finally + local pending = function(element) + local parent = busted.context.parent(element) + local status = 'pending' + if not busted.safe_publish('it', { 'test', 'start' }, element, parent) then + status = 'error' + end + busted.safe_publish('it', { 'test', 'end' }, element, parent, status) + end - if not it.env then it.env = {} end + busted.register('file', file, { envmode = 'insulate' }) - it.env.finally = function(fn) - finally = fn - end + busted.register('describe', describe) + busted.register('insulate', 'describe', { envmode = 'insulate' }) + busted.register('expose', 'describe', { envmode = 'expose' }) - local parent = busted.context.parent(it) + busted.register('it', it) - execAll('before_each', parent, true) - busted.publish({ 'test', 'start' }, it, parent) - local res = busted.safe('it', it.run, it) - if not it.env.done then - busted.publish({ 'test', 'end' }, it, parent, res and 'success' or 'failure') - if finally then busted.safe('finally', finally, it) end - dexecAll('after_each', parent, true) - end - end + busted.register('pending', pending) - local pending = function(pending) - local trace = busted.getTrace(pending, 3) - busted.publish({ 'test', 'end' }, pending, busted.context.parent(pending), 'pending', trace) - end + busted.register('before_each', { envmode = 'unwrap' }) + busted.register('after_each', { envmode = 'unwrap' }) - local async = function() - local parent = busted.context.get() - if not parent.env then parent.env = {} end + busted.register('lazy_setup', { envmode = 'unwrap' }) + busted.register('lazy_teardown', { envmode = 'unwrap' }) + busted.register('strict_setup', { envmode = 'unwrap' }) + busted.register('strict_teardown', { envmode = 'unwrap' }) - parent.env.done = require 'busted.done'.new(function() - busted.publish({ 'test', 'end' }, it, parent, 'success') - if finally then busted.safe('finally', finally, it) end - dexecAll('after_each', parent, true) - end) - end + busted.register('setup', 'strict_setup') + busted.register('teardown', 'strict_teardown') - busted.register('file', file) + busted.register('context', 'describe') + busted.register('spec', 'it') + busted.register('test', 'it') - busted.register('describe', describe) - busted.register('context', describe) + busted.hide('file') - busted.register('it', it) - busted.register('pending', pending) + local assert = require 'luassert' + local spy = require 'luassert.spy' + local mock = require 'luassert.mock' + local stub = require 'luassert.stub' + local match = require 'luassert.match' + + busted.export('assert', assert) + busted.export('spy', spy) + busted.export('mock', mock) + busted.export('stub', stub) + busted.export('match', match) - busted.context.get().env.async = async + busted.exportApi('publish', busted.publish) + busted.exportApi('subscribe', busted.subscribe) + busted.exportApi('unsubscribe', busted.unsubscribe) - busted.register('setup') - busted.register('teardown') - busted.register('before_each') - busted.register('after_each') + busted.exportApi('bindfenv', busted.bindfenv) + busted.exportApi('fail', busted.fail) + busted.exportApi('gettime', busted.gettime) + busted.exportApi('monotime', busted.monotime) + busted.exportApi('sleep', busted.sleep) + busted.exportApi('parent', busted.context.parent) + busted.exportApi('children', busted.context.children) + busted.exportApi('version', busted.version) - assert = require 'luassert' - spy = require 'luassert.spy' - mock = require 'luassert.mock' - stub = require 'luassert.stub' + busted.bindfenv(assert, 'error', busted.fail) + busted.bindfenv(assert.is_true, 'error', busted.fail) return busted end + +return setmetatable({}, { + __call = function(self, busted) + init(busted) + + return setmetatable(self, { + __index = function(self, key) + return busted.api[key] + end, + + __newindex = function(self, key, value) + error('Attempt to modify busted') + end + }) + end +}) diff --git a/busted/languages/en.lua b/busted/languages/en.lua index aa99b6bd..dcfb4cce 100644 --- a/busted/languages/en.lua +++ b/busted/languages/en.lua @@ -25,6 +25,9 @@ s:set('output.success_single', 'success') s:set('output.seconds', 'seconds') +s:set('output.no_test_files_match', 'No test files found matching Lua pattern: %s') +s:set('output.file_not_found', 'Cannot find file or directory: %s') + -- definitions following are not used within the 'say' namespace return { failure_messages = { diff --git a/busted/languages/es.lua b/busted/languages/es.lua new file mode 100644 index 00000000..4219e42e --- /dev/null +++ b/busted/languages/es.lua @@ -0,0 +1,49 @@ +local s = require('say') + +s:set_namespace('es') + +-- 'Pending: test.lua @ 12 \n description +s:set('output.pending', 'Pendiente') +s:set('output.failure', 'Fallo') +s:set('output.error', 'Error') +s:set('output.success', 'Éxito') + +s:set('output.pending_plural', 'pendientes') +s:set('output.failure_plural', 'fallos') +s:set('output.error_plural', 'errores') +s:set('output.success_plural', 'éxitos') + +s:set('output.pending_zero', 'pendientes') +s:set('output.failure_zero', 'fallos') +s:set('output.error_zero', 'errores') +s:set('output.success_zero', 'éxitos') + +s:set('output.pending_single', 'pendiente') +s:set('output.failure_single', 'fallo') +s:set('output.error_single', 'error') +s:set('output.success_single', 'éxito') + +s:set('output.seconds', 'segundos') + +s:set('output.no_test_files_match', 'Ningún fichero de prueba fue encontrado para el patrón de Lua: %s') + +-- definitions following are not used within the 'say' namespace +return { + failure_messages = { + 'Tienes %d especificaciones o pruebas con errores', + 'Tus especificaciones o pruebas están dañadas', + 'Tu código es malo y deberías sentirte mal', + 'Tu código está en la Zona de Peligro', + 'Juego extraño. La única forma de ganar es omitiendo las pruebas', + 'Mi abuela escribió mejores especificaciones en una 386', + 'Cada vez que encuentres un fallo, tómate otra cerveza', + 'Esto no está del todo bien amigo' + }, + success_messages = { + 'Ohhh si! Pasando todas las pruebas', + 'No importa, tenía especificaciones', + 'Esto está bien amigo', + 'Un exitazo', + 'Pasaron las pruebas, tómate otra cerveza', + } +} diff --git a/busted/languages/fr.lua b/busted/languages/fr.lua index 78a05ae5..236d87c8 100644 --- a/busted/languages/fr.lua +++ b/busted/languages/fr.lua @@ -21,6 +21,8 @@ s:set('output.success_single', 'reussite') s:set('output.seconds', 'secondes') +s:set('output.no_test_files_match', 'Aucun test n\'est pourrait trouvé qui corresponde au motif de Lua: %s') + -- definitions following are not used within the 'say' namespace return { failure_messages = { diff --git a/busted/languages/it.lua b/busted/languages/it.lua new file mode 100644 index 00000000..a073bfe3 --- /dev/null +++ b/busted/languages/it.lua @@ -0,0 +1,50 @@ +local s = require('say') + +s:set_namespace('it') + +-- 'Pending: test.lua @ 12 \n description +s:set('output.pending', 'In attesa') +s:set('output.failure', 'Fallimento') +s:set('output.error', 'Errore') +s:set('output.success', 'Successo') + +s:set('output.pending_plural', 'in attesa') +s:set('output.failure_plural', 'fallimenti') +s:set('output.error_plural', 'errori') +s:set('output.success_plural', 'successi') + +s:set('output.pending_zero', 'in attesa') +s:set('output.failure_zero', 'fallimenti') +s:set('output.error_zero', 'errori') +s:set('output.success_zero', 'successi') + +s:set('output.pending_single', 'in attesa') +s:set('output.failure_single', 'fallimento') +s:set('output.error_single', 'errore') +s:set('output.success_single', 'successo') + +s:set('output.seconds', 'secondi') + +s:set('output.no_test_files_match', 'Nessun file di test trovat che corrisponde al pattern Lua: %s') +s:set('output.file_not_found', 'Nessun file o cartella trovato: %s') + +-- definitions following are not used within the 'say' namespace +return { + failure_messages = { + "Hai %d specifiche non conformi", + "Le tue specifiche non sono conformi", + "Il tuo codice fa schifo e dovresti sentirti male per questo", + "Il tuo codice è in pericolo", + "Strano. Il solo modo per terminare con successo i tuoi test è fare nessun test", + "Mia nonna ha scritto migliori specifiche su un 3 86", + "Ogni volta che trovi un errore, bevi un'altra birra", + "I fallimenti fanno male alla salute" + }, + success_messages = { + "Ma andiamo! Specifiche Ok!", + "Non importa, avevi le specifiche", + "Bella zio", + "Gran successo", + "Test passato, hai vinto una birra" + } +} diff --git a/busted/languages/pt-BR.lua b/busted/languages/pt-BR.lua new file mode 100644 index 00000000..06f7a154 --- /dev/null +++ b/busted/languages/pt-BR.lua @@ -0,0 +1,49 @@ +local s = require('say') + +s:set_namespace('pt-BR') + +-- 'Pending: test.lua @ 12 \n description +s:set('output.pending', 'Pendente') +s:set('output.failure', 'Falha') +s:set('output.error', 'Erro') +s:set('output.success', 'Sucesso') + +s:set('output.pending_plural', 'pendentes') +s:set('output.failure_plural', 'falhas') +s:set('output.error_plural', 'erros') +s:set('output.success_plural', 'sucessos') + +s:set('output.pending_zero', 'pendentes') +s:set('output.failure_zero', 'falhas') +s:set('output.error_zero', 'erros') +s:set('output.success_zero', 'sucessos') + +s:set('output.pending_single', 'pendente') +s:set('output.failure_single', 'falha') +s:set('output.error_single', 'erro') +s:set('output.success_single', 'sucesso') + +s:set('output.seconds', 'segundos') + +s:set('output.no_test_files_match', 'Nenhum arquivo de teste encontrado com o padrão do Lua: %s') + +-- definitions following are not used within the 'say' namespace +return { + failure_messages = { + 'Você tem %d testes quebrados', + 'Seus testes estão quebrados', + 'Seu código está mal e você deveria sentir-se mal', + 'Seu código está na zona de perigo', + 'Jogo estranho. A única forma de ganhar é não testar', + 'Minha avó escreveu testes melhores em um 386', + 'Cada vez que encontrar uma falha, beba outra cerveja', + 'Isso não está poético' + }, + success_messages = { + 'Perfeito! Todos os testes estão passando', + 'Não se preocupe, tem testes', + 'Isso está poético', + 'Excelente', + 'Os testes passaram, beba outra cerveja', + } +} diff --git a/busted/modules/cli.lua b/busted/modules/cli.lua new file mode 100644 index 00000000..cbc1a8bd --- /dev/null +++ b/busted/modules/cli.lua @@ -0,0 +1,244 @@ +local utils = require 'busted.utils' +local path = require 'pl.path' +local tablex = require 'pl.tablex' +local exit = require 'busted.compatibility'.exit +local execute = require 'busted.compatibility'.execute + +return function(options) + local appName = '' + local options = options or {} + local cli = require 'cliargs.core'() + + local configLoader = require 'busted.modules.configuration_loader'() + + -- Default cli arg values + local defaultOutput = options.output or 'utfTerminal' + local defaultLoaders = 'lua,moonscript' + local defaultPattern = '_spec' + local defaultSeed = '/dev/urandom or os.time()' + local lpathprefix = './src/?.lua;./src/?/?.lua;./src/?/init.lua' + local cpathprefix = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;' + + local cliArgsParsed = {} + + local function makeList(values) + return type(values) == 'table' and values or { values } + end + + local function fixupList(values, sep) + local sep = sep or ',' + local list = type(values) == 'table' and values or { values } + local olist = {} + for _, v in ipairs(list) do + tablex.insertvalues(olist, utils.split(v, sep)) + end + return olist + end + + local function processOption(key, value, altkey, opt) + if altkey then cliArgsParsed[altkey] = value end + cliArgsParsed[key] = value + return true + end + + local function processArg(key, value) + cliArgsParsed[key] = value + return true + end + + local function processArgList(key, value) + local list = cliArgsParsed[key] or {} + tablex.insertvalues(list, utils.split(value, ',')) + processArg(key, list) + return true + end + + local function processNumber(key, value, altkey, opt) + local number = tonumber(value) + if not number then + return nil, 'argument to ' .. opt:gsub('=.*', '') .. ' must be a number' + end + if altkey then cliArgsParsed[altkey] = number end + cliArgsParsed[key] = number + return true + end + + local function processList(key, value, altkey, opt) + local list = cliArgsParsed[key] or {} + tablex.insertvalues(list, utils.split(value, ',')) + processOption(key, list, altkey, opt) + return true + end + + local function processMultiOption(key, value, altkey, opt) + local list = cliArgsParsed[key] or {} + table.insert(list, value) + processOption(key, list, altkey, opt) + return true + end + + local function append(s1, s2, sep) + local sep = sep or '' + if not s1 then return s2 end + return s1 .. sep .. s2 + end + + local function processLoaders(key, value, altkey, opt) + local loaders = append(cliArgsParsed[key], value, ',') + processOption(key, loaders, altkey, opt) + return true + end + + local function processPath(key, value, altkey, opt) + local lpath = append(cliArgsParsed[key], value, ';') + processOption(key, lpath, altkey, opt) + return true + end + + local function processDir(key, value, altkey, opt) + local dpath = path.normpath(path.join(cliArgsParsed[key] or '', value)) + processOption(key, dpath, altkey, opt) + return true + end + + local function processShuffle(key, value, altkey, opt) + processOption('shuffle-files', value, nil, opt) + processOption('shuffle-tests', value, nil, opt) + end + + local function processSort(key, value, altkey, opt) + processOption('sort-files', value, nil, opt) + processOption('sort-tests', value, nil, opt) + end + + -- Load up the command-line interface options + cli:flag('--version', 'prints the program version and exits', false, processOption) + + if not options.standalone then + cli:splat('ROOT', 'test script file/folder. Folders will be traversed for any file that matches the --pattern option.', 'spec', 999, processArgList) + + cli:option('-p, --pattern=PATTERN', 'only run test files matching the Lua pattern', defaultPattern, processMultiOption) + cli:option('--exclude-pattern=PATTERN', 'do not run test files matching the Lua pattern, takes precedence over --pattern', nil, processMultiOption) + end + + cli:option('-e STATEMENT', 'execute statement STATEMENT', nil, processMultiOption) + cli:option('-o, --output=LIBRARY', 'output library to load', defaultOutput, processOption) + cli:option('-C, --directory=DIR', 'change to directory DIR before running tests. If multiple options are specified, each is interpreted relative to the previous one.', './', processDir) + cli:option('-f, --config-file=FILE', 'load configuration options from FILE', nil, processOptions) + cli:option('-t, --tags=TAGS', 'only run tests with these #tags', {}, processList) + cli:option('--exclude-tags=TAGS', 'do not run tests with these #tags, takes precedence over --tags', {}, processList) + cli:option('--filter=PATTERN', 'only run test names matching the Lua pattern', {}, processMultiOption) + cli:option('--filter-out=PATTERN', 'do not run test names matching the Lua pattern, takes precedence over --filter', {}, processMultiOption) + cli:option('-m, --lpath=PATH', 'optional path to be prefixed to the Lua module search path', lpathprefix, processPath) + cli:option('--cpath=PATH', 'optional path to be prefixed to the Lua C module search path', cpathprefix, processPath) + cli:option('-r, --run=RUN', 'config to run from .busted file', nil, processOption) + cli:option('--repeat=COUNT', 'run the tests repeatedly', '1', processNumber) + cli:option('--seed=SEED', 'random seed value to use for shuffling test order', defaultSeed, processNumber) + cli:option('--lang=LANG', 'language for error messages', 'en', processOption) + cli:option('--loaders=NAME', 'test file loaders', defaultLoaders, processLoaders) + cli:option('--helper=PATH', 'A helper script that is run before tests', nil, processOption) + cli:option('--lua=LUA', 'The path to the lua interpreter busted should run under', nil, processOption) + + cli:option('-Xoutput OPTION', 'pass `OPTION` as an option to the output handler. If `OPTION` contains commas, it is split into multiple options at the commas.', {}, processList) + cli:option('-Xhelper OPTION', 'pass `OPTION` as an option to the helper script. If `OPTION` contains commas, it is split into multiple options at the commas.', {}, processList) + + cli:flag('-c, --[no-]coverage', 'do code coverage analysis (requires `LuaCov` to be installed)', false, processOption) + cli:flag('-v, --[no-]verbose', 'verbose output of errors', false, processOption) + cli:flag('-s, --[no-]enable-sound', 'executes `say` command if available', false, processOption) + cli:flag('-l, --list', 'list the names of all tests instead of running them', false, processOption) + cli:flag('--ignore-lua', 'Whether or not to ignore the lua directive', false, processOption) + cli:flag('--[no-]lazy', 'use lazy setup/teardown as the default', false, processOption) + cli:flag('--[no-]auto-insulate', 'enable file insulation', true, processOption) + cli:flag('-k, --[no-]keep-going', 'continue as much as possible after an error or failure', true, processOption) + cli:flag('-R, --[no-]recursive', 'recurse into subdirectories', true, processOption) + cli:flag('--[no-]shuffle', 'randomize file and test order, takes precedence over --sort (--shuffle-test and --shuffle-files)', processShuffle) + cli:flag('--[no-]shuffle-files', 'randomize file execution order, takes precedence over --sort-files', processOption) + cli:flag('--[no-]shuffle-tests', 'randomize test order within a file, takes precedence over --sort-tests', processOption) + cli:flag('--[no-]sort', 'sort file and test order (--sort-tests and --sort-files)', processSort) + cli:flag('--[no-]sort-files', 'sort file execution order', processOption) + cli:flag('--[no-]sort-tests', 'sort test order within a file', processOption) + cli:flag('--[no-]suppress-pending', 'suppress `pending` test output', false, processOption) + cli:flag('--[no-]defer-print', 'defer print to when test suite is complete', false, processOption) + + local function parse(args) + -- Parse the cli arguments + local cliArgs, cliErr = cli:parse(args) + if not cliArgs then + return nil, appName .. ': error: ' .. cliErr .. '; re-run with --help for usage.' + end + + -- Load busted config file if available + local bustedConfigFilePath = cliArgs.f or path.normpath(path.join(cliArgs.directory, '.busted')) + local bustedConfigFile = loadfile(bustedConfigFilePath) + if bustedConfigFile then + local ok, config = pcall(function() + local conf, err = configLoader(bustedConfigFile(), cliArgsParsed, cliArgs) + return conf or error(err, 0) + end) + if not ok then + return nil, appName .. ': error: ' .. config + else + cliArgs = config + end + else + cliArgs = tablex.merge(cliArgs, cliArgsParsed, true) + end + + -- Switch lua, we should rebuild this feature once luarocks changes how it + -- handles executeable lua files. + if cliArgs['lua'] and not cliArgs['ignore-lua'] then + local _, code = execute( + cliArgs['lua']..' '..args[0]..' --ignore-lua '..table.concat(args, ' ') + ) + exit(code) + end + + -- Ensure multi-options are in a list + cliArgs.e = makeList(cliArgs.e) + cliArgs.pattern = makeList(cliArgs.pattern) + cliArgs.p = cliArgs.pattern + cliArgs['exclude-pattern'] = makeList(cliArgs['exclude-pattern']) + cliArgs.filter = makeList(cliArgs.filter) + cliArgs['filter-out'] = makeList(cliArgs['filter-out']) + + -- Fixup options in case options from config file are not of the right form + cliArgs.tags = fixupList(cliArgs.tags) + cliArgs.t = cliArgs.tags + cliArgs['exclude-tags'] = fixupList(cliArgs['exclude-tags']) + cliArgs.loaders = fixupList(cliArgs.loaders) + cliArgs.Xoutput = fixupList(cliArgs.Xoutput) + cliArgs.Xhelper = fixupList(cliArgs.Xhelper) + + -- We report an error if the same tag appears in both `options.tags` + -- and `options.excluded_tags` because it does not make sense for the + -- user to tell Busted to include and exclude the same tests at the + -- same time. + for _, excluded in pairs(cliArgs['exclude-tags']) do + for _, included in pairs(cliArgs.tags) do + if excluded == included then + return nil, appName .. ': error: Cannot use --tags and --exclude-tags for the same tags' + end + end + end + + cliArgs['repeat'] = tonumber(cliArgs['repeat']) + + return cliArgs + end + + return { + set_name = function(self, name) + appName = name + return cli:set_name(name) + end, + + set_silent = function(self, name) + appName = name + return cli:set_silent(name) + end, + + parse = function(self, args) + return parse(args) + end + } +end diff --git a/busted/modules/configuration_loader.lua b/busted/modules/configuration_loader.lua index d5a3fe96..c2504dce 100644 --- a/busted/modules/configuration_loader.lua +++ b/busted/modules/configuration_loader.lua @@ -1,31 +1,35 @@ -return function() - local path = require 'pl.path' - local tablex = require 'pl.tablex' +local tablex = require 'pl.tablex' +return function() -- Function to load the .busted configuration file if available - local loadBustedConfigurationFile = function(configFile, config, run) - if run and run ~= '' then - if type(configFile) ~= 'table' then - return config, '.busted file does not return a table.' - end + local loadBustedConfigurationFile = function(configFile, config, defaults) + if type(configFile) ~= 'table' then + return nil, '.busted file does not return a table.' + end + local defaults = defaults or {} + local run = config.run or defaults.run + + if run and run ~= '' then local runConfig = configFile[run] if type(runConfig) == 'table' then - config = tablex.merge(config, runConfig, true) - return config + config = tablex.merge(runConfig, config, true) else - return config, 'Task `' .. run .. '` not found, or not a table.' + return nil, 'Task `' .. run .. '` not found, or not a table.' end + elseif type(configFile.default) == 'table' then + config = tablex.merge(configFile.default, config, true) end - if configFile and type(configFile.default) == 'table' then - return tablex.merge(config, configFile.default, true) + if type(configFile._all) == 'table' then + config = tablex.merge(configFile._all, config, true) end + config = tablex.merge(defaults, config, true) + return config end return loadBustedConfigurationFile end - diff --git a/busted/modules/files/lua.lua b/busted/modules/files/lua.lua index 5e0afda1..6396f0ca 100644 --- a/busted/modules/files/lua.lua +++ b/busted/modules/files/lua.lua @@ -1,35 +1,22 @@ +local path = require 'pl.path' + local ret = {} -local getTrace = function(filename, info) +local getTrace = function(filename, info) local index = info.traceback:find('\n%s*%[C]') info.traceback = info.traceback:sub(1, index) - return info, false + return info end ret.match = function(busted, filename) - local path, name, ext = filename:match('(.-)([^\\/\\\\]-%.?([^%.\\/]*))$') - if ext == 'lua' then - return true - end - return false + return path.extension(filename) == '.lua' end - ret.load = function(busted, filename) - local file - - local success, err = pcall(function() - file, err = loadfile(filename) - - if not file then - busted.publish({ 'error', 'file' }, filename, nil, nil, err) - end - end) - - if not success then - busted.publish({ 'error', 'file' }, filename, nil, nil, err) + local file, err = loadfile(filename) + if not file then + busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {}) end - return file, getTrace end diff --git a/busted/modules/files/moonscript.lua b/busted/modules/files/moonscript.lua index 497a5fbb..067d2859 100644 --- a/busted/modules/files/moonscript.lua +++ b/busted/modules/files/moonscript.lua @@ -1,4 +1,4 @@ -local utils = require 'pl.utils' +local path = require 'pl.path' local ok, moonscript, line_tables, util = pcall(function() return require 'moonscript', require 'moonscript.line_tables', require 'moonscript.util' @@ -18,7 +18,7 @@ local lookup_line = function(fname, pos) end local rewrite_linenumber = function(fname, lineno) - local tbl = line_tables[fname] + local tbl = line_tables['@' .. fname] if fname and tbl then for i = lineno, 0 ,-1 do if tbl[i] then @@ -30,44 +30,49 @@ local rewrite_linenumber = function(fname, lineno) return lineno end -local rewrite_traceback = function(fname, trace) - local lines = {} - local j = 0 +local rewrite_filename = function(filename) + -- sometimes moonscript gives files like [string "./filename.moon"], so + -- we'll chop it up to only get the filename. + return filename:match('string "(.+)"') or filename +end - local rewrite_one = function(line) - if line == nil then - return '' - end +local rewrite_traceback = function(fname, trace) + local rewrite_one = function(line, pattern, sub) + if line == nil then return '' end - local fname, lineno = line:match('[^"]+"([^:]+)".:(%d+):') + local fname, lineno = line:match(pattern) if fname and lineno then + fname = rewrite_filename(fname) local new_lineno = rewrite_linenumber(fname, tonumber(lineno)) if new_lineno then - line = line:gsub(':' .. lineno .. ':', ':' .. new_lineno .. ':') + line = line:gsub(sub:format(tonumber(lineno)), sub:format(tonumber(new_lineno))) end end + return line end + local lines = {} + local j = 0 + for line in trace:gmatch('[^\r\n]+') do j = j + 1 - lines[j] = rewrite_one(line) + line = rewrite_one(line, '%s*(.-):(%d+): ', ':%d:') + line = rewrite_one(line, '<(.*):(%d+)>', ':%d>') + lines[j] = line end - return table.concat(lines, trace:match('[\r\n]+')) + return '\n' .. table.concat(lines, trace:match('[\r\n]+')) .. '\n' end local ret = {} -local getTrace = function(filename, info) - local p = require 'pl.pretty' +local getTrace = function(filename, info) local index = info.traceback:find('\n%s*%[C]') info.traceback = info.traceback:sub(1, index) - -- sometimes moonscript gives files like [string "./filename.moon"], so - -- we'll chop it up to only get the filename. - info.short_src = info.short_src:match('string "(.+)"') or info.short_src + info.short_src = rewrite_filename(info.short_src) info.traceback = rewrite_traceback(filename, info.traceback) info.linedefined = rewrite_linenumber(filename, info.linedefined) info.currentline = rewrite_linenumber(filename, info.currentline) @@ -76,45 +81,26 @@ local getTrace = function(filename, info) end local rewriteMessage = function(filename, message) - local split = utils.split(message, ':', true, 3) - - if #split < 3 then + local fname, line, msg = message:match('^([^\n]-):(%d+): (.*)') + if not fname then return message end - local filename = split[1] - local line = split[2] - filename = filename:match('string "(.+)"') + fname = rewrite_filename(fname) + line = rewrite_linenumber(fname, tonumber(line)) - line = rewrite_linenumber(filename, line) - - return filename .. ':' .. tostring(line) + return fname .. ':' .. tostring(line) .. ': ' .. msg end ret.match = function(busted, filename) - local path, name, ext = filename:match('(.-)([^\\/\\\\]-%.?([^%.\\/]*))$') - if ok and ext == 'moon' then - return true - end - return false + return ok and path.extension(filename) == '.moon' end - ret.load = function(busted, filename) - local file - - local success, err = pcall(function() - file, err = moonscript.loadfile(filename) - - if not file then - busted.publish({ 'error', 'file' }, filename, nil, nil, err) - end - end) - - if not success then - busted.publish({ 'error', 'file' }, filename, nil, nil, err) + local file, err = moonscript.loadfile(filename) + if not file then + busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {}) end - return file, getTrace, rewriteMessage end diff --git a/busted/modules/files/terra.lua b/busted/modules/files/terra.lua index 62e9265e..2e13350b 100644 --- a/busted/modules/files/terra.lua +++ b/busted/modules/files/terra.lua @@ -1,37 +1,23 @@ +local path = require 'pl.path' + local ret = {} local ok, terralib = pcall(function() return require 'terralib' end) -local getTrace = function(filename, info) +local getTrace = function(filename, info) local index = info.traceback:find('\n%s*%[C]') info.traceback = info.traceback:sub(1, index) return info end ret.match = function(busted, filename) - if ok then - local path, name, ext = filename:match('(.-)([^\\/\\\\]-%.?([^%.\\/]*))$') - if ext == 't' then - return true - end - end - return false + return ok and path.extension(filename) == '.t' end ret.load = function(busted, filename) - local file - - local success, err = pcall(function() - file, err = terralib.loadfile(filename) - - if not file then - busted.publish({ 'error', 'file' }, filename, nil, nil, err) - end - end) - - if not success then - busted.publish({ 'error', 'file' }, filename, nil, nil, err) + local file, err = terralib.loadfile(filename) + if not file then + busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {}) end - return file, getTrace end diff --git a/busted/modules/filter_loader.lua b/busted/modules/filter_loader.lua new file mode 100644 index 00000000..7154ff2a --- /dev/null +++ b/busted/modules/filter_loader.lua @@ -0,0 +1,132 @@ +return function() + local function filter(busted, options) + local getFullName = function(name) + local parent = busted.context.get() + local names = { name } + + while parent and (parent.name or parent.descriptor) and + parent.descriptor ~= 'file' do + table.insert(names, 1, parent.name or parent.descriptor) + parent = busted.context.parent(parent) + end + + return table.concat(names, ' ') + end + + local hasTag = function(name, tag) + local found = name:find('#' .. tag) + return (found ~= nil) + end + + local filterExcludeTags = function(name) + for i, tag in pairs(options.excludeTags) do + if hasTag(name, tag) then + return nil, false + end + end + return nil, true + end + + local filterTags = function(name) + local fullname = getFullName(name) + for i, tag in pairs(options.tags) do + if hasTag(fullname, tag) then + return nil, true + end + end + return nil, (#options.tags == 0) + end + + local filterOutNames = function(name) + for _, filter in pairs(options.filterOut) do + if getFullName(name):find(filter) ~= nil then + return nil, false + end + end + return nil, true + end + + local filterNames = function(name) + for _, filter in pairs(options.filter) do + if getFullName(name):find(filter) ~= nil then + return nil, true + end + end + return nil, (#options.filter == 0) + end + + local printTestName = function(element, parent, status) + if not (options.suppressPending and status == 'pending') then + local fullname = getFullName() + local trace = element.trace + if trace and trace.what == 'Lua' then + fullname = trace.short_src .. ':' .. trace.currentline .. ': ' .. fullname + end + print(fullname) + end + return nil, false + end + + local ignoreAll = function() + return nil, false + end + + local noop = function() end + local stubOut = function(descriptor, name, fn, ...) + if fn == noop then + return nil, true + end + busted.publish({ 'register', descriptor }, name, noop, ...) + return nil, false + end + + local skipOnError = function() + return nil, not busted.skipAll + end + + local applyFilter = function(descriptors, name, fn) + if options[name] and options[name] ~= '' then + for _, descriptor in ipairs(descriptors) do + busted.subscribe({ 'register', descriptor }, fn, { priority = 1 }) + end + end + end + + local applyDescFilter = function(descriptors, name, fn) + if options[name] and options[name] ~= '' then + for _, descriptor in ipairs(descriptors) do + local f = function(...) return fn(descriptor, ...) end + busted.subscribe({ 'register', descriptor }, f, { priority = 1 }) + end + end + end + + if options.list then + busted.subscribe({ 'suite', 'start' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'suite', 'end' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'file', 'start' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'file', 'end' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'describe', 'start' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'describe', 'end' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'test', 'start' }, ignoreAll, { priority = 1 }) + busted.subscribe({ 'test', 'end' }, printTestName, { priority = 1 }) + applyDescFilter({ 'setup', 'teardown', 'before_each', 'after_each' }, 'list', stubOut) + applyDescFilter({ 'lazy_setup', 'lazy_teardown' }, 'list', stubOut) + applyDescFilter({ 'strict_setup', 'strict_teardown' }, 'list', stubOut) + applyDescFilter({ 'it', 'pending' }, 'list', stubOut) + end + + applyFilter({ 'lazy_setup', 'lazy_teardown' }, 'nokeepgoing', skipOnError) + applyFilter({ 'strict_setup', 'strict_teardown' }, 'nokeepgoing', skipOnError) + applyFilter({ 'setup', 'teardown', 'before_each', 'after_each' }, 'nokeepgoing', skipOnError) + applyFilter({ 'file', 'describe', 'it', 'pending' }, 'nokeepgoing', skipOnError) + + -- The following filters are applied in reverse order + applyFilter({ 'it', 'pending' } , 'filter' , filterNames ) + applyFilter({ 'describe', 'it', 'pending' }, 'filterOut' , filterOutNames ) + applyFilter({ 'it', 'pending' } , 'tags' , filterTags ) + applyFilter({ 'describe', 'it', 'pending' }, 'excludeTags', filterExcludeTags) + end + + return filter +end diff --git a/busted/modules/helper_loader.lua b/busted/modules/helper_loader.lua new file mode 100644 index 00000000..56ae6673 --- /dev/null +++ b/busted/modules/helper_loader.lua @@ -0,0 +1,28 @@ +local path = require 'pl.path' +local hasMoon, moonscript = pcall(require, 'moonscript') +local utils = require 'busted.utils' + +return function() + local loadHelper = function(busted, helper, options) + local old_arg = _G.arg + local success, err = pcall(function() + utils.copy_interpreter_args(options.arguments) + _G.arg = options.arguments + if helper:match('%.lua$') then + dofile(path.normpath(helper)) + elseif hasMoon and helper:match('%.moon$') then + moonscript.dofile(path.normpath(helper)) + else + require(helper) + end + end) + + arg = old_arg + + if not success then + busted.publish({ 'error', 'helper' }, { descriptor = 'helper', name = helper }, nil, err, {}) + end + end + + return loadHelper +end diff --git a/busted/modules/output_handler_loader.lua b/busted/modules/output_handler_loader.lua index df9a3de4..ab93e4c6 100644 --- a/busted/modules/output_handler_loader.lua +++ b/busted/modules/output_handler_loader.lua @@ -1,16 +1,36 @@ local path = require 'pl.path' +local hasMoon, moonscript = pcall(require, 'moonscript') +local utils = require 'busted.utils' return function() - local loadOutputHandler = function(output, opath, options, busted) + local loadOutputHandler = function(busted, output, options) local handler - if output:match('.lua$') or output:match('.moon$') then - handler = loadfile(path.normpath(opath))() - else - handler = require('busted.outputHandlers.'..output) + utils.copy_interpreter_args(options.arguments) + local success, err = pcall(function() + if output:match('%.lua$') then + handler = dofile(path.normpath(output)) + elseif hasMoon and output:match('%.moon$') then + handler = moonscript.dofile(path.normpath(output)) + else + handler = require('busted.outputHandlers.' .. output) + end + end) + + if not success and err:match("module '.-' not found:") then + success, err = pcall(function() handler = require(output) end) + end + + if not success then + busted.publish({ 'error', 'output' }, { descriptor = 'output', name = output }, nil, err, {}) + handler = require('busted.outputHandlers.' .. options.defaultOutput) + end + + if options.enableSound then + require 'busted.outputHandlers.sound'(options) end - return handler(options, busted) + handler(options):subscribe(options) end return loadOutputHandler diff --git a/busted/modules/standalone_loader.lua b/busted/modules/standalone_loader.lua new file mode 100644 index 00000000..6299aa5f --- /dev/null +++ b/busted/modules/standalone_loader.lua @@ -0,0 +1,28 @@ +local getTrace = function(filename, info) + local index = info.traceback:find('\n%s*%[C]') + info.traceback = info.traceback:sub(1, index) + return info +end + +return function(busted) + local loadCurrentFile = function(info, options) + local filename = 'string' + if info.source:sub(1,1) == '@' or info.source:sub(1,1) == '=' then + filename = info.source:sub(2) + end + + -- Setup test file to be compatible with live coding + if info.func then + local file = setmetatable({ + getTrace = getTrace, + rewriteMessage = nil + }, { + __call = info.func + }) + + busted.executors.file(filename, file) + end + end + + return loadCurrentFile +end diff --git a/busted/modules/test_file_loader.lua b/busted/modules/test_file_loader.lua index cb46f5a4..6f2e83d8 100644 --- a/busted/modules/test_file_loader.lua +++ b/busted/modules/test_file_loader.lua @@ -1,3 +1,5 @@ +local s = require 'say' + return function(busted, loaders) local path = require 'pl.path' local dir = require 'pl.dir' @@ -9,17 +11,28 @@ return function(busted, loaders) fileLoaders[#fileLoaders+1] = loader end - local getTestFiles = function(rootFile, pattern) + local getTestFiles = function(rootFile, patterns, options) local fileList if path.isfile(rootFile) then fileList = { rootFile } elseif path.isdir(rootFile) then - local pattern = pattern - fileList = dir.getallfiles(rootFile) + local getfiles = options.recursive and dir.getallfiles or dir.getfiles + fileList = getfiles(rootFile) fileList = tablex.filter(fileList, function(filename) - return path.basename(filename):find(pattern) + local basename = path.basename(filename) + for _, patt in ipairs(options.excludes) do + if patt ~= '' and basename:find(patt) then + return nil + end + end + for _, patt in ipairs(patterns) do + if basename:find(patt) then + return true + end + end + return #patterns == 0 end) fileList = tablex.filter(fileList, function(filename) @@ -30,9 +43,19 @@ return function(busted, loaders) end end) else + busted.publish({ 'error' }, {}, nil, s('output.file_not_found'):format(rootFile), {}) fileList = {} end + table.sort(fileList) + return fileList + end + + local getAllTestFiles = function(rootFiles, patterns, options) + local fileList = {} + for _, root in ipairs(rootFiles) do + tablex.insertvalues(fileList, getTestFiles(root, patterns, options)) + end return fileList end @@ -45,11 +68,11 @@ return function(busted, loaders) end end - local loadTestFiles = function(rootFile, pattern, loaders) - local fileList = getTestFiles(rootFile, pattern) + local loadTestFiles = function(rootFiles, patterns, options) + local fileList = getAllTestFiles(rootFiles, patterns, options) - for i, fileName in pairs(fileList) do - local testFile, getTrace, rewriteMessage = loadTestFile(busted, fileName, loaders) + for i, fileName in ipairs(fileList) do + local testFile, getTrace, rewriteMessage = loadTestFile(busted, fileName) if testFile then local file = setmetatable({ @@ -62,8 +85,18 @@ return function(busted, loaders) busted.executors.file(fileName, file) end end + + if #fileList == 0 then + local pattern = patterns[1] + if #patterns > 1 then + pattern = '\n\t' .. table.concat(patterns, '\n\t') + end + busted.publish({ 'error' }, {}, nil, s('output.no_test_files_match'):format(pattern), {}) + end + + return fileList end - return loadTestFiles, loadTestFile, getTestFiles + return loadTestFiles, loadTestFile, getAllTestFiles end diff --git a/busted/options.lua b/busted/options.lua new file mode 100644 index 00000000..c6f5d6cc --- /dev/null +++ b/busted/options.lua @@ -0,0 +1,4 @@ +return { + standalone = true, + output = nil, +} diff --git a/busted/outputHandlers/TAP.lua b/busted/outputHandlers/TAP.lua index 83ff8c87..72dc9c8f 100644 --- a/busted/outputHandlers/TAP.lua +++ b/busted/outputHandlers/TAP.lua @@ -1,56 +1,85 @@ local pretty = require 'pl.pretty' -local tablex = require 'pl.tablex' -return function(options, busted) - local handler = require 'busted.outputHandlers.base'(busted) +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() - local getFullName = function(context) - local parent = context.parent - local names = { (context.name or context.descriptor) } + local success = 'ok %u - %s' + local failure = 'not ' .. success + local skip = 'ok %u - # SKIP %s' + local counter = 0 - while parent and (parent.name or parent.descriptor) and - parent.descriptor ~= 'file' do + handler.suiteReset = function() + counter = 0 + return nil, true + end + + handler.suiteEnd = function() + print('1..' .. counter) + return nil, true + end + + local function showFailure(t) + local message = t.message + local trace = t.trace or {} - current_context = context.parent - table.insert(names, 1, parent.name or parent.descriptor) - parent = busted.context.parent(parent) + if message == nil then + message = 'Nil error' + elseif type(message) ~= 'string' then + message = pretty.write(message) end - return table.concat(names, ' ') + print(failure:format(counter, t.name)) + print('# ' .. t.element.trace.short_src .. ' @ ' .. t.element.trace.currentline) + if t.randomseed then print('# Random seed: ' .. t.randomseed) end + print('# Failure message: ' .. message:gsub('\n', '\n# ')) + if options.verbose and trace.traceback then + print('# ' .. trace.traceback:gsub('^\n', '', 1):gsub('\n', '\n# ')) + end end - handler.suiteEnd = function(name, parent) - local total = handler.successesCount + handler.errorsCount + handler.failuresCount - print('1..' .. total) + handler.testStart = function(element, parent) + local trace = element.trace + if options.verbose and trace and trace.short_src then + local fileline = trace.short_src .. ' @ ' .. trace.currentline .. ': ' + local testName = fileline .. handler.getFullName(element) + print('# ' .. testName) + end - local success = 'ok %u - %s' - local failure = 'not ' .. success - local counter = 0 + return nil, true + end - for i,t in pairs(handler.successes) do - counter = counter + 1 - print(counter .. ' ' .. handler.format(t).name) + handler.testEnd = function(element, parent, status, trace) + counter = counter + 1 + if status == 'success' then + local t = handler.successes[#handler.successes] + print(success:format(counter, t.name)) + elseif status == 'pending' then + local t = handler.pendings[#handler.pendings] + print(skip:format(counter, (t.message or t.name))) + elseif status == 'failure' then + showFailure(handler.failures[#handler.failures]) + elseif status == 'error' then + showFailure(handler.errors[#handler.errors]) end - for i,t in pairs(handler.failures) do - counter = counter + 1 - local message = t.message - - if message == nil then - message = 'Nil error' - elseif type(message) ~= 'string' then - message = pretty.write(message) - end + return nil, true + end - print(counter .. ' ' .. handler.format(t).name) - print('# ' .. t.trace.short_src .. ' @ ' .. t.trace.currentline) - print('# Failure message: ' .. message:gsub('\n', '\n# ' )) + handler.error = function(element, parent, message, debug) + if element.descriptor ~= 'it' then + counter = counter + 1 + showFailure(handler.errors[#handler.errors]) end return nil, true end + busted.subscribe({ 'suite', 'reset' }, handler.suiteReset) busted.subscribe({ 'suite', 'end' }, handler.suiteEnd) + busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'error' }, handler.error) return handler end diff --git a/busted/outputHandlers/base.lua b/busted/outputHandlers/base.lua index 61290af4..7b6f5df1 100644 --- a/busted/outputHandlers/base.lua +++ b/busted/outputHandlers/base.lua @@ -1,4 +1,5 @@ -return function(busted) +return function() + local busted = require 'busted' local handler = { successes = {}, successesCount = 0, @@ -11,65 +12,99 @@ return function(busted) inProgress = {} } + handler.cancelOnPending = function(element, parent, status) + return not ((element.descriptor == 'pending' or status == 'pending') and handler.options.suppressPending) + end + handler.subscribe = function(handler, options) require('busted.languages.en') + handler.options = options if options.language ~= 'en' then require('busted.languages.' .. options.language) end - busted.subscribe({ 'suite', 'start' }, handler.baseSuiteStart) - busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd) - busted.subscribe({ 'test', 'start' }, handler.baseTestStart) - busted.subscribe({ 'test', 'end' }, handler.baseTestEnd) - busted.subscribe({ 'error', 'it' }, handler.baseError) - busted.subscribe({ 'error', 'file' }, handler.baseError) - busted.subscribe({ 'error', 'pending' }, handler.baseError) + busted.subscribe({ 'suite', 'reset' }, handler.baseSuiteReset, { priority = 1 }) + busted.subscribe({ 'suite', 'start' }, handler.baseSuiteStart, { priority = 1 }) + busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd, { priority = 1 }) + busted.subscribe({ 'test', 'start' }, handler.baseTestStart, { priority = 1, predicate = handler.cancelOnPending }) + busted.subscribe({ 'test', 'end' }, handler.baseTestEnd, { priority = 1, predicate = handler.cancelOnPending }) + busted.subscribe({ 'pending' }, handler.basePending, { priority = 1, predicate = handler.cancelOnPending }) + busted.subscribe({ 'failure', 'it' }, handler.baseTestFailure, { priority = 1 }) + busted.subscribe({ 'error', 'it' }, handler.baseTestError, { priority = 1 }) + busted.subscribe({ 'failure' }, handler.baseError, { priority = 1 }) + busted.subscribe({ 'error' }, handler.baseError, { priority = 1 }) end handler.getFullName = function(context) - local parent = busted.context.parent(context) + local parent = busted.parent(context) local names = { (context.name or context.descriptor) } while parent and (parent.name or parent.descriptor) and parent.descriptor ~= 'file' do - current_context = context.parent table.insert(names, 1, parent.name or parent.descriptor) - parent = busted.context.parent(parent) + parent = busted.parent(parent) end return table.concat(names, ' ') end - handler.format = function(element, parent, message, debug, isError) + local function copyElement(e) + local copy = {} + for k,v in next, e do + if type(v) ~= 'function' and k ~= 'env' then + copy[k] = v + end + end + return copy + end + local formatted = { - trace = element.trace or debug, + trace = debug or element.trace, + element = copyElement(element), name = handler.getFullName(element), message = message, + randomseed = parent and parent.randomseed, isError = isError } + formatted.element.trace = element.trace or debug return formatted end handler.getDuration = function() - if not handler.endTime or not handler.startTime then + if not handler.endTick or not handler.startTick then return 0 end - return handler.endTime - handler.startTime + return handler.endTick - handler.startTick end - handler.baseSuiteStart = function(name, parent) - handler.startTime = os.clock() + handler.baseSuiteStart = function(suite) + handler.startTick = suite.starttick + handler.startTime = suite.starttime + return nil, true + end + + handler.baseSuiteReset = function() + handler.successes = {} + handler.successesCount = 0 + handler.pendings = {} + handler.pendingsCount = 0 + handler.failures = {} + handler.failuresCount = 0 + handler.errors = {} + handler.errorsCount = 0 + handler.inProgress = {} return nil, true end - handler.baseSuiteEnd = function(name, parent) - handler.endTime = os.clock() + handler.baseSuiteEnd = function(suite) + handler.endTick = suite.endtick + handler.endTime = suite.endtime return nil, true end @@ -80,7 +115,6 @@ return function(busted) handler.baseTestEnd = function(element, parent, status, debug) local insertTable - local id = tostring(element) if status == 'success' then insertTable = handler.successes @@ -89,27 +123,51 @@ return function(busted) insertTable = handler.pendings handler.pendingsCount = handler.pendingsCount + 1 elseif status == 'failure' then - insertTable = handler.failures + -- failure already saved in failure handler handler.failuresCount = handler.failuresCount + 1 + return nil, true + elseif status == 'error' then + -- error count already incremented and saved in error handler + insertTable = handler.errors + return nil, true end - insertTable[id] = handler.format(element, parent, nil, debug) + local formatted = handler.format(element, parent, element.message, debug) + local id = tostring(element) if handler.inProgress[id] then for k, v in pairs(handler.inProgress[id]) do - insertTable[id][k] = v + formatted[k] = v end handler.inProgress[id] = nil end + table.insert(insertTable, formatted) + + return nil, true + end + + handler.basePending = function(element, parent, message, debug) + local id = tostring(element) + handler.inProgress[id].message = message + handler.inProgress[id].trace = debug + return nil, true + end + + handler.baseTestFailure = function(element, parent, message, debug) + table.insert(handler.failures, handler.format(element, parent, message, debug)) + return nil, true + end + + handler.baseTestError = function(element, parent, message, debug) + handler.errorsCount = handler.errorsCount + 1 + table.insert(handler.errors, handler.format(element, parent, message, debug, true)) return nil, true end handler.baseError = function(element, parent, message, debug) - if element.descriptor == 'it' then - handler.inProgress[tostring(element)].message = message - else + if element.descriptor ~= 'it' then handler.errorsCount = handler.errorsCount + 1 table.insert(handler.errors, handler.format(element, parent, message, debug, true)) end diff --git a/busted/outputHandlers/gtest.lua b/busted/outputHandlers/gtest.lua new file mode 100644 index 00000000..1b940a4f --- /dev/null +++ b/busted/outputHandlers/gtest.lua @@ -0,0 +1,285 @@ +local s = require 'say' +local pretty = require 'pl.pretty' +local term = require 'term' + +local colors + +local isatty = io.type(io.stdout) == 'file' and term.isatty(io.stdout) +local isWindows = package.config:sub(1,1) == '\\' + +if isWindows and not os.getenv("ANSICON") or not isatty then + colors = setmetatable({}, {__index = function() return function(s) return s end end}) +else + colors = require 'term.colors' +end + +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() + + local repeatSuiteString = '\nRepeating all tests (run %u of %u) . . .\n\n' + local randomizeString = colors.yellow('Note: Randomizing test order with a seed of %u.\n') + local suiteStartString = colors.green ('[==========]') .. ' Running tests from scanned files.\n' + local globalSetup = colors.green ('[----------]') .. ' Global test environment setup.\n' + local fileStartString = colors.green ('[----------]') .. ' Running tests from %s\n' + local runString = colors.green ('[ RUN ]') .. ' %s\n' + local successString = colors.green ('[ OK ]') .. ' %s (%.2f ms)\n' + local skippedString = colors.yellow ('[ SKIPPED ]') .. ' %s (%.2f ms)\n' + local failureString = colors.red ('[ FAILED ]') .. ' %s (%.2f ms)\n' + local errorString = colors.magenta('[ ERROR ]') .. ' %s (%.2f ms)\n' + local fileEndString = colors.green ('[----------]') .. ' %u %s from %s (%.2f ms total)\n\n' + local globalTeardown = colors.green ('[----------]') .. ' Global test environment teardown.\n' + local suiteEndString = colors.green ('[==========]') .. ' %u %s from %u test %s ran. (%.2f ms total)\n' + local successStatus = colors.green ('[ PASSED ]') .. ' %u %s.\n' + + local summaryStrings = { + skipped = { + header = colors.yellow ('[ SKIPPED ]') .. ' %u %s, listed below:\n', + test = colors.yellow ('[ SKIPPED ]') .. ' %s\n', + footer = ' %u SKIPPED %s\n', + }, + + failure = { + header = colors.red ('[ FAILED ]') .. ' %u %s, listed below:\n', + test = colors.red ('[ FAILED ]') .. ' %s\n', + footer = ' %u FAILED %s\n', + }, + + error = { + header = colors.magenta('[ ERROR ]') .. ' %u %s, listed below:\n', + test = colors.magenta('[ ERROR ]') .. ' %s\n', + footer = ' %u %s\n', + }, + } + + local fileCount = 0 + local fileTestCount = 0 + local testCount = 0 + local successCount = 0 + local skippedCount = 0 + local failureCount = 0 + local errorCount = 0 + + local pendingDescription = function(pending) + local name = pending.name + local string = '' + + if type(pending.message) == 'string' then + string = string .. pending.message .. '\n' + elseif pending.message ~= nil then + string = string .. pretty.write(pending.message) .. '\n' + end + + return string + end + + local failureDescription = function(failure) + local string = failure.randomseed and ('Random seed: ' .. failure.randomseed .. '\n') or '' + if type(failure.message) == 'string' then + string = string .. failure.message + elseif failure.message == nil then + string = string .. 'Nil error' + else + string = string .. pretty.write(failure.message) + end + + string = string .. '\n' + + if options.verbose and failure.trace and failure.trace.traceback then + string = string .. failure.trace.traceback .. '\n' + end + + return string + end + + local getFileLine = function(element) + local fileline = '' + if element.trace or element.trace.short_src then + fileline = colors.cyan(element.trace.short_src) .. ' @ ' .. + colors.cyan(element.trace.currentline) .. ': ' + end + return fileline + end + + local getTestList = function(status, count, list, getDescription) + local string = '' + local header = summaryStrings[status].header + if count > 0 and header then + local tests = (count == 1 and 'test' or 'tests') + local errors = (count == 1 and 'error' or 'errors') + string = header:format(count, status == 'error' and errors or tests) + + local testString = summaryStrings[status].test + if testString then + for _, t in ipairs(list) do + local fullname = getFileLine(t.element) .. colors.bright(t.name) + string = string .. testString:format(fullname) + if options.deferPrint then + string = string .. getDescription(t) + end + end + end + end + return string + end + + local getSummary = function(status, count) + local string = '' + local footer = summaryStrings[status].footer + if count > 0 and footer then + local tests = (count == 1 and 'TEST' or 'TESTS') + local errors = (count == 1 and 'ERROR' or 'ERRORS') + string = footer:format(count, status == 'error' and errors or tests) + end + return string + end + + local getSummaryString = function() + local tests = (successCount == 1 and 'test' or 'tests') + local string = successStatus:format(successCount, tests) + + string = string .. getTestList('skipped', skippedCount, handler.pendings, pendingDescription) + string = string .. getTestList('failure', failureCount, handler.failures, failureDescription) + string = string .. getTestList('error', errorCount, handler.errors, failureDescription) + + string = string .. ((skippedCount + failureCount + errorCount) > 0 and '\n' or '') + string = string .. getSummary('skipped', skippedCount) + string = string .. getSummary('failure', failureCount) + string = string .. getSummary('error', errorCount) + + return string + end + + local getFullName = function(element) + return getFileLine(element) .. colors.bright(handler.getFullName(element)) + end + + handler.suiteReset = function() + fileCount = 0 + fileTestCount = 0 + testCount = 0 + successCount = 0 + skippedCount = 0 + failureCount = 0 + errorCount = 0 + + return nil, true + end + + handler.suiteStart = function(suite, count, total, randomseed) + if total > 1 then + io.write(repeatSuiteString:format(count, total)) + end + if randomseed then + io.write(randomizeString:format(randomseed)) + end + io.write(suiteStartString) + io.write(globalSetup) + io.flush() + + return nil, true + end + + handler.suiteEnd = function(suite, count, total) + local elapsedTime_ms = suite.duration * 1000 + local tests = (testCount == 1 and 'test' or 'tests') + local files = (fileCount == 1 and 'file' or 'files') + io.write(globalTeardown) + io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) + io.write(getSummaryString()) + io.flush() + + return nil, true + end + + handler.fileStart = function(file) + fileTestCount = 0 + io.write(fileStartString:format(file.name)) + io.flush() + return nil, true + end + + handler.fileEnd = function(file) + local elapsedTime_ms = file.duration * 1000 + local tests = (fileTestCount == 1 and 'test' or 'tests') + fileCount = fileCount + 1 + io.write(fileEndString:format(fileTestCount, tests, file.name, elapsedTime_ms)) + io.flush() + return nil, true + end + + handler.testStart = function(element, parent) + io.write(runString:format(getFullName(element))) + io.flush() + + return nil, true + end + + handler.testEnd = function(element, parent, status, debug) + local elapsedTime_ms = element.duration * 1000 + local string + + fileTestCount = fileTestCount + 1 + testCount = testCount + 1 + if status == 'success' then + successCount = successCount + 1 + string = successString + elseif status == 'pending' then + skippedCount = skippedCount + 1 + string = skippedString + elseif status == 'failure' then + failureCount = failureCount + 1 + string = failureString + elseif status == 'error' then + errorCount = errorCount + 1 + string = errorString + end + + io.write(string:format(getFullName(element), elapsedTime_ms)) + io.flush() + + return nil, true + end + + handler.testFailure = function(element, parent, message, debug) + if not options.deferPrint then + io.write(failureDescription(handler.failures[#handler.failures])) + io.flush() + end + return nil, true + end + + handler.testError = function(element, parent, message, debug) + if not options.deferPrint then + io.write(failureDescription(handler.errors[#handler.errors])) + io.flush() + end + return nil, true + end + + handler.error = function(element, parent, message, debug) + if element.descriptor ~= 'it' then + if not options.deferPrint then + io.write(failureDescription(handler.errors[#handler.errors])) + io.flush() + end + errorCount = errorCount + 1 + end + + return nil, true + end + + busted.subscribe({ 'suite', 'reset' }, handler.suiteReset) + busted.subscribe({ 'suite', 'start' }, handler.suiteStart) + busted.subscribe({ 'suite', 'end' }, handler.suiteEnd) + busted.subscribe({ 'file', 'start' }, handler.fileStart) + busted.subscribe({ 'file', 'end' }, handler.fileEnd) + busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'failure', 'it' }, handler.testFailure) + busted.subscribe({ 'error', 'it' }, handler.testError) + busted.subscribe({ 'failure' }, handler.error) + busted.subscribe({ 'error' }, handler.error) + + return handler +end diff --git a/busted/outputHandlers/json.lua b/busted/outputHandlers/json.lua index d315efa4..08b87e0a 100644 --- a/busted/outputHandlers/json.lua +++ b/busted/outputHandlers/json.lua @@ -1,10 +1,10 @@ -local pretty = require 'pl.pretty' -local tablex = require 'pl.tablex' local json = require 'dkjson' -return function(options, busted) - local handler = require 'busted.outputHandlers.base'(busted) - handler.suiteEnd = function(element, parent, status) +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() + + handler.suiteEnd = function() print(json.encode({ pendings = handler.pendings, successes = handler.successes, diff --git a/busted/outputHandlers/junit.lua b/busted/outputHandlers/junit.lua index 11bb201a..f1d28a5f 100644 --- a/busted/outputHandlers/junit.lua +++ b/busted/outputHandlers/junit.lua @@ -1,64 +1,155 @@ local xml = require 'pl.xml' -local hostname = assert(io.popen('uname -n')):read('*l') +local string = require("string") -return function(options, busted) - local handler = require 'busted.outputHandlers.base'(busted) - local node +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() + local top = { + start_tick = busted.monotime(), + xml_doc = xml.new('testsuites', { + tests = 0, + errors = 0, + failures = 0, + skip = 0, + }) + } + local stack = {} + local testcase_node + if 'table' == type(options.arguments) then + --the first argument should be the name of the xml file. + output_file_name = options.arguments[1] + end - handler.testEnd = function(element, parent, status) - node.attr.tests = node.attr.tests + 1 + handler.suiteStart = function(suite, count, total) + local suite_xml = { + start_tick = suite.starttick, + xml_doc = xml.new('testsuite', { + name = 'Run ' .. count .. ' of ' .. total, + tests = 0, + errors = 0, + failures = 0, + skip = 0, + timestamp = os.date('!%Y-%m-%dT%H:%M:%S'), + }) + } + top.xml_doc:add_direct_child(suite_xml.xml_doc) + table.insert(stack, top) + top = suite_xml - node:addtag('testcase', { - classname = element.trace.short_src .. ':' .. element.trace.currentline, - name = element.name - }) + return nil, true + end - if status == 'failure' then - node.attr.failures = node.attr.failures + 1 - end + local function formatDuration(duration) + return string.format("%.2f", duration) + end + + local function elapsed(start_time) + return formatDuration(busted.monotime() - start_time) + end + + handler.suiteEnd = function(suite, count, total) + local suite_xml = top + suite_xml.xml_doc.attr.time = formatDuration(suite.duration) + + top = table.remove(stack) + top.xml_doc.attr.tests = top.xml_doc.attr.tests + suite_xml.xml_doc.attr.tests + top.xml_doc.attr.errors = top.xml_doc.attr.errors + suite_xml.xml_doc.attr.errors + top.xml_doc.attr.failures = top.xml_doc.attr.failures + suite_xml.xml_doc.attr.failures + top.xml_doc.attr.skip = top.xml_doc.attr.skip + suite_xml.xml_doc.attr.skip return nil, true end - handler.suiteStart = function(name, parent) - node = xml.new('testsuite', { - tests = 0, - errors = 0, - failures = 0, - skip = 0, - header = 'Busted Suite', - hostname = hostname, - timestamp = os.time() + handler.exit = function() + top.xml_doc.attr.time = elapsed(top.start_tick) + local output_string = xml.tostring(top.xml_doc, '', '\t', nil, false) + local file + if 'string' == type(output_file_name) then + file = io.open(output_file_name, 'w+b' ) + end + if file then + file:write(output_string) + file:write('\n') + file:close() + else + print(output_string) + end + return nil, true + end + + local function testStatus(element, parent, message, status, trace) + if status ~= 'success' then + testcase_node:addtag(status) + if status ~= 'pending' and parent and parent.randomseed then + testcase_node:text('Random seed: ' .. parent.randomseed .. '\n') + end + if message then testcase_node:text(message) end + if trace and trace.traceback then testcase_node:text(trace.traceback) end + testcase_node:up() + end + end + + handler.testStart = function(element, parent) + testcase_node = xml.new('testcase', { + classname = element.trace.short_src .. ':' .. element.trace.currentline, + name = handler.getFullName(element), }) + top.xml_doc:add_direct_child(testcase_node) return nil, true end - handler.suiteEnd = function(name, parent) - local ms = handler.getDuration() - node.attr.time = ms + handler.testEnd = function(element, parent, status) + top.xml_doc.attr.tests = top.xml_doc.attr.tests + 1 + testcase_node:set_attrib("time", formatDuration(element.duration)) + + if status == 'success' then + testStatus(element, parent, nil, 'success') + elseif status == 'pending' then + top.xml_doc.attr.skip = top.xml_doc.attr.skip + 1 + local formatted = handler.pendings[#handler.pendings] + local trace = element.trace ~= formatted.trace and formatted.trace + testStatus(element, parent, formatted.message, 'skipped', trace) + end - print(xml.tostring(node, '', '\t')) + return nil, true + end + handler.failureTest = function(element, parent, message, trace) + top.xml_doc.attr.failures = top.xml_doc.attr.failures + 1 + testStatus(element, parent, message, 'failure', trace) + return nil, true + end + + handler.errorTest = function(element, parent, message, trace) + top.xml_doc.attr.errors = top.xml_doc.attr.errors + 1 + testStatus(element, parent, message, 'error', trace) return nil, true end handler.error = function(element, parent, message, trace) - if status == 'failure' then - node.attr.errors = node.attr.errors + 1 + if element.descriptor ~= 'it' then + top.xml_doc.attr.errors = top.xml_doc.attr.errors + 1 + top.xml_doc:addtag('error') + top.xml_doc:text(message) + if trace and trace.traceback then + top.xml_doc:text(trace.traceback) + end + top.xml_doc:up() end - node:addtag('failure', { - message = message - }):text(trace.traceback):up() - return nil, true end - busted.subscribe({ 'test', 'end' }, handler.testEnd) + busted.subscribe({ 'exit' }, handler.exit) busted.subscribe({ 'suite', 'start' }, handler.suiteStart) busted.subscribe({ 'suite', 'end' }, handler.suiteEnd) - busted.subscribe({ 'error', 'file' }, handler.error) + busted.subscribe({ 'test', 'start' }, handler.testStart, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'error', 'it' }, handler.errorTest) + busted.subscribe({ 'failure', 'it' }, handler.failureTest) + busted.subscribe({ 'error' }, handler.error) + busted.subscribe({ 'failure' }, handler.error) return handler end diff --git a/busted/outputHandlers/plainTerminal.lua b/busted/outputHandlers/plainTerminal.lua index 115247c6..e15f9879 100644 --- a/busted/outputHandlers/plainTerminal.lua +++ b/busted/outputHandlers/plainTerminal.lua @@ -1,8 +1,9 @@ local s = require 'say' local pretty = require 'pl.pretty' -return function(options, busted) - local handler = require 'busted.outputHandlers.base'(busted) +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() local successDot = '+' local failureDot = '-' @@ -10,41 +11,54 @@ return function(options, busted) local pendingDot = '.' local pendingDescription = function(pending) - local name = handler.getFullName(pending) + local name = pending.name - local string = s('output.pending') .. ' → ' .. + local string = s('output.pending') .. ' -> ' .. pending.trace.short_src .. ' @ ' .. pending.trace.currentline .. '\n' .. name + if type(pending.message) == 'string' then + string = string .. '\n' .. pending.message + elseif pending.message ~= nil then + string = string .. '\n' .. pretty.write(pending.message) + end + return string end - local failureDescription = function(failure, isError) - local string = s('output.failure') .. ' → ' + local failureMessage = function(failure) + local string = failure.randomseed and ('Random seed: ' .. failure.randomseed .. '\n') or '' + if type(failure.message) == 'string' then + string = string .. failure.message + elseif failure.message == nil then + string = string .. 'Nil error' + else + string = string .. pretty.write(failure.message) + end + return string + end + + local failureDescription = function(failure, isError) + local string = s('output.failure') .. ' -> ' if isError then - string = s('output.error') + string = s('output.error') .. ' -> ' + end - if failure.message then - string = string .. ' → ' .. failure.message .. '\n' - end + if not failure.element.trace or not failure.element.trace.short_src then + string = string .. + failureMessage(failure) .. '\n' .. + failure.name else string = string .. - failure.trace.short_src .. ' @ ' .. - failure.trace.currentline .. '\n' .. - handler.getFullName(failure) .. '\n' - - if type(failure.message) == 'string' then - string = string .. failure.message - elseif failure.message == nil then - string = string .. 'Nil error' - else - string = string .. pretty.write(failure.message) - end + failure.element.trace.short_src .. ' @ ' .. + failure.element.trace.currentline .. '\n' .. + failure.name .. '\n' .. + failureMessage(failure) end - if options.verbose then + if options.verbose and failure.trace and failure.trace.traceback then string = string .. '\n' .. failure.trace.traceback end @@ -57,7 +71,7 @@ return function(options, busted) local pendingString = s('output.pending_plural') local errorString = s('output.error_plural') - local ms = handler.getDuration() + local sec = handler.getDuration() local successes = handler.successesCount local pendings = handler.pendingsCount local failures = handler.failuresCount @@ -87,7 +101,7 @@ return function(options, busted) errorString = s('output.error_single') end - local formattedTime = ('%.6f'):format(ms):gsub('([0-9])0+$', '%1') + local formattedTime = ('%.6f'):format(sec):gsub('([0-9])0+$', '%1') return successes .. ' ' .. successString .. ' / ' .. failures .. ' ' .. failureString .. ' / ' .. @@ -104,6 +118,8 @@ return function(options, busted) string = pendingDot elseif status == 'failure' then string = failureDot + elseif status == 'error' then + string = errorDot end io.write(string) @@ -113,7 +129,15 @@ return function(options, busted) return nil, true end - handler.suiteEnd = function(name, parent) + handler.suiteStart = function(suite, count, total) + local runString = (total > 1 and '\nRepeating all tests (run %u of %u) . . .\n\n' or '') + io.write(runString:format(count, total)) + io.flush() + + return nil, true + end + + handler.suiteEnd = function() print('') print(statusString()) @@ -142,9 +166,13 @@ return function(options, busted) return nil, true end - busted.subscribe({ 'test', 'end' }, handler.testEnd) + busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'suite', 'start' }, handler.suiteStart) busted.subscribe({ 'suite', 'end' }, handler.suiteEnd) busted.subscribe({ 'error', 'file' }, handler.error) + busted.subscribe({ 'failure', 'file' }, handler.error) + busted.subscribe({ 'error', 'describe' }, handler.error) + busted.subscribe({ 'failure', 'describe' }, handler.error) return handler end diff --git a/busted/outputHandlers/sound.lua b/busted/outputHandlers/sound.lua index 762090fa..8a749693 100644 --- a/busted/outputHandlers/sound.lua +++ b/busted/outputHandlers/sound.lua @@ -1,11 +1,12 @@ -local pretty = require 'pl.pretty' - -return function(options, busted) - local handler = require 'busted.outputHandlers.base'(busted) +local app = require 'pl.app' +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() local language = require('busted.languages.' .. options.language) - handler.suiteEnd = function(name, parent) - local system, sayer_pre, sayer_post + handler.suiteEnd = function() + local system = app.platform() + local sayer_pre, sayer_post local messages if system == 'Linux' then diff --git a/busted/outputHandlers/utfTerminal.lua b/busted/outputHandlers/utfTerminal.lua index 96d4a580..fa38c958 100644 --- a/busted/outputHandlers/utfTerminal.lua +++ b/busted/outputHandlers/utfTerminal.lua @@ -1,51 +1,73 @@ -local ansicolors = require 'ansicolors' local s = require 'say' local pretty = require 'pl.pretty' -return function(options, busted) - local handler = require 'busted.outputHandlers.base'(busted) +local colors - local successDot = ansicolors('%{green}●') - local failureDot = ansicolors('%{red}●') - local errorDot = ansicolors('%{magenta}●') - local pendingDot = ansicolors('%{yellow}●') +if package.config:sub(1,1) == '\\' and not os.getenv("ANSICON") then + -- Disable colors on Windows. + colors = setmetatable({}, {__index = function() return function(s) return s end end}) +else + colors = require 'term.colors' +end + +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() + + local successDot = colors.green('●') + local failureDot = colors.red('◼') + local errorDot = colors.magenta('✱') + local pendingDot = colors.yellow('◌') local pendingDescription = function(pending) - local name = handler.getFullName(pending) + local name = pending.name - local string = ansicolors('%{yellow}' .. s('output.pending')) .. ' → ' .. - ansicolors('%{cyan}' .. pending.trace.short_src) .. ' @ ' .. - ansicolors('%{cyan}' .. pending.trace.currentline) .. - '\n' .. ansicolors('%{bright}' .. name) + local string = colors.yellow(s('output.pending')) .. ' → ' .. + colors.cyan(pending.trace.short_src) .. ' @ ' .. + colors.cyan(pending.trace.currentline) .. + '\n' .. colors.bright(name) + + if type(pending.message) == 'string' then + string = string .. '\n' .. pending.message + elseif pending.message ~= nil then + string = string .. '\n' .. pretty.write(pending.message) + end return string end - local failureDescription = function(failure, isError) - local string = ansicolors('%{red}' .. s('output.failure')) .. ' → ' + local failureMessage = function(failure) + local string = failure.randomseed and ('Random seed: ' .. failure.randomseed .. '\n') or '' + if type(failure.message) == 'string' then + string = string .. failure.message + elseif failure.message == nil then + string = string .. 'Nil error' + else + string = string .. pretty.write(failure.message) + end + + return string + end + local failureDescription = function(failure, isError) + local string = colors.red(s('output.failure')) .. ' → ' if isError then - string = ansicolors('%{magenta}' .. s('output.error')) + string = colors.magenta(s('output.error')) .. ' → ' + end - if failure.message then - string = string .. ' → ' .. ansicolors('%{cyan}' .. failure.message) .. '\n' - end + if not failure.element.trace or not failure.element.trace.short_src then + string = string .. + colors.cyan(failureMessage(failure)) .. '\n' .. + colors.bright(failure.name) else string = string .. - ansicolors('%{cyan}' .. failure.trace.short_src) .. ' @ ' .. - ansicolors('%{cyan}' .. failure.trace.currentline) .. '\n' .. - ansicolors('%{bright}' .. handler.getFullName(failure)) .. '\n' - - if type(failure.message) == 'string' then - string = string .. failure.message - elseif failure.message == nil then - string = string .. 'Nil error' - else - string = string .. pretty.write(failure.message) - end + colors.cyan(failure.element.trace.short_src) .. ' @ ' .. + colors.cyan(failure.element.trace.currentline) .. '\n' .. + colors.bright(failure.name) .. '\n' .. + failureMessage(failure) end - if options.verbose then + if options.verbose and failure.trace and failure.trace.traceback then string = string .. '\n' .. failure.trace.traceback end @@ -58,7 +80,7 @@ return function(options, busted) local pendingString = s('output.pending_plural') local errorString = s('output.error_plural') - local ms = handler.getDuration() + local sec = handler.getDuration() local successes = handler.successesCount local pendings = handler.pendingsCount local failures = handler.failuresCount @@ -88,13 +110,13 @@ return function(options, busted) errorString = s('output.error_single') end - local formattedTime = ('%.6f'):format(ms):gsub('([0-9])0+$', '%1') + local formattedTime = ('%.6f'):format(sec):gsub('([0-9])0+$', '%1') - return ansicolors('%{green}' .. successes) .. ' ' .. successString .. ' / ' .. - ansicolors('%{red}' .. failures) .. ' ' .. failureString .. ' / ' .. - ansicolors('%{magenta}' .. errors) .. ' ' .. errorString .. ' / ' .. - ansicolors('%{yellow}' .. pendings) .. ' ' .. pendingString .. ' : ' .. - ansicolors('%{bright}' .. formattedTime) .. ' ' .. s('output.seconds') + return colors.green(successes) .. ' ' .. successString .. ' / ' .. + colors.red(failures) .. ' ' .. failureString .. ' / ' .. + colors.magenta(errors) .. ' ' .. errorString .. ' / ' .. + colors.yellow(pendings) .. ' ' .. pendingString .. ' : ' .. + colors.bright(formattedTime) .. ' ' .. s('output.seconds') end handler.testEnd = function(element, parent, status, debug) @@ -105,6 +127,8 @@ return function(options, busted) string = pendingDot elseif status == 'failure' then string = failureDot + elseif status == 'error' then + string = errorDot end io.write(string) @@ -114,7 +138,15 @@ return function(options, busted) return nil, true end - handler.suiteEnd = function(name, parent) + handler.suiteStart = function(suite, count, total) + local runString = (total > 1 and '\nRepeating all tests (run %u of %u) . . .\n\n' or '') + io.write(runString:format(count, total)) + io.flush() + + return nil, true + end + + handler.suiteEnd = function(suite, count, total) print('') print(statusString()) @@ -143,9 +175,13 @@ return function(options, busted) return nil, true end - busted.subscribe({ 'test', 'end' }, handler.testEnd) + busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) + busted.subscribe({ 'suite', 'start' }, handler.suiteStart) busted.subscribe({ 'suite', 'end' }, handler.suiteEnd) busted.subscribe({ 'error', 'file' }, handler.error) + busted.subscribe({ 'failure', 'file' }, handler.error) + busted.subscribe({ 'error', 'describe' }, handler.error) + busted.subscribe({ 'failure', 'describe' }, handler.error) return handler end diff --git a/busted/runner.lua b/busted/runner.lua new file mode 100644 index 00000000..d5d4d499 --- /dev/null +++ b/busted/runner.lua @@ -0,0 +1,185 @@ +-- Busted command-line runner + +local path = require 'pl.path' +local tablex = require 'pl.tablex' +local term = require 'term' +local utils = require 'busted.utils' +local exit = require 'busted.compatibility'.exit +local loadstring = require 'busted.compatibility'.loadstring +local loaded = false + +return function(options) + if loaded then return function() end else loaded = true end + + local isatty = io.type(io.stdout) == 'file' and term.isatty(io.stdout) + options = tablex.update(require 'busted.options', options or {}) + options.output = options.output or (isatty and 'utfTerminal' or 'plainTerminal') + + local busted = require 'busted.core'() + + local cli = require 'busted.modules.cli'(options) + local filterLoader = require 'busted.modules.filter_loader'() + local helperLoader = require 'busted.modules.helper_loader'() + local outputHandlerLoader = require 'busted.modules.output_handler_loader'() + + local luacov = require 'busted.modules.luacov'() + + require 'busted'(busted) + + local level = 2 + local info = debug.getinfo(level, 'Sf') + local source = info.source + local fileName = source:sub(1,1) == '@' and source:sub(2) or nil + local forceExit = fileName == nil + + -- Parse the cli arguments + local appName = path.basename(fileName or 'busted') + cli:set_name(appName) + local cliArgs, err = cli:parse(arg) + if not cliArgs then + io.stderr:write(err .. '\n') + exit(1, forceExit) + end + + if cliArgs.version then + -- Return early if asked for the version + print(busted.version) + exit(0, forceExit) + end + + -- Load current working directory + local _, err = path.chdir(path.normpath(cliArgs.directory)) + if err then + io.stderr:write(appName .. ': error: ' .. err .. '\n') + exit(1, forceExit) + end + + -- If coverage arg is passed in, load LuaCovsupport + if cliArgs.coverage then + luacov() + end + + -- If auto-insulate is disabled, re-register file without insulation + if not cliArgs['auto-insulate'] then + busted.register('file', 'file', {}) + end + + -- If lazy is enabled, make lazy setup/teardown the default + if cliArgs.lazy then + busted.register('setup', 'lazy_setup') + busted.register('teardown', 'lazy_teardown') + end + + -- Add additional package paths based on lpath and cpath cliArgs + if #cliArgs.lpath > 0 then + package.path = (cliArgs.lpath .. ';' .. package.path):gsub(';;',';') + end + + if #cliArgs.cpath > 0 then + package.cpath = (cliArgs.cpath .. ';' .. package.cpath):gsub(';;',';') + end + + -- Load and execute commands given on the command-line + if cliArgs.e then + for k,v in ipairs(cliArgs.e) do + loadstring(v)() + end + end + + -- watch for test errors and failures + local failures = 0 + local errors = 0 + local quitOnError = not cliArgs['keep-going'] + + busted.subscribe({ 'error', 'output' }, function(element, parent, message) + io.stderr:write(appName .. ': error: Cannot load output library: ' .. element.name .. '\n' .. message .. '\n') + return nil, true + end) + + busted.subscribe({ 'error', 'helper' }, function(element, parent, message) + io.stderr:write(appName .. ': error: Cannot load helper script: ' .. element.name .. '\n' .. message .. '\n') + return nil, true + end) + + busted.subscribe({ 'error' }, function(element, parent, message) + errors = errors + 1 + busted.skipAll = quitOnError + return nil, true + end) + + busted.subscribe({ 'failure' }, function(element, parent, message) + if element.descriptor == 'it' then + failures = failures + 1 + else + errors = errors + 1 + end + busted.skipAll = quitOnError + return nil, true + end) + + -- Set up randomization options + busted.sort = cliArgs['sort-tests'] + busted.randomize = cliArgs['shuffle-tests'] + busted.randomseed = tonumber(cliArgs.seed) or utils.urandom() or os.time() + + -- Set up output handler to listen to events + outputHandlerLoader(busted, cliArgs.output, { + defaultOutput = options.output, + enableSound = cliArgs['enable-sound'], + verbose = cliArgs.verbose, + suppressPending = cliArgs['suppress-pending'], + language = cliArgs.lang, + deferPrint = cliArgs['defer-print'], + arguments = cliArgs.Xoutput, + }) + + -- Set up helper script + if cliArgs.helper and cliArgs.helper ~= '' then + helperLoader(busted, cliArgs.helper, { + verbose = cliArgs.verbose, + language = cliArgs.lang, + arguments = cliArgs.Xhelper + }) + end + + -- Load tag and test filters + filterLoader(busted, { + tags = cliArgs.tags, + excludeTags = cliArgs['exclude-tags'], + filter = cliArgs.filter, + filterOut = cliArgs['filter-out'], + list = cliArgs.list, + nokeepgoing = not cliArgs['keep-going'], + suppressPending = cliArgs['suppress-pending'], + }) + + if cliArgs.ROOT then + -- Load test directories/files + local rootFiles = cliArgs.ROOT + local patterns = cliArgs.pattern + local testFileLoader = require 'busted.modules.test_file_loader'(busted, cliArgs.loaders) + testFileLoader(rootFiles, patterns, { + excludes = cliArgs['exclude-pattern'], + verbose = cliArgs.verbose, + recursive = cliArgs['recursive'], + }) + else + -- Running standalone, use standalone loader + local testFileLoader = require 'busted.modules.standalone_loader'(busted) + testFileLoader(info, { verbose = cliArgs.verbose }) + end + + local runs = cliArgs['repeat'] + local execute = require 'busted.execute'(busted) + execute(runs, { + seed = cliArgs.seed, + shuffle = cliArgs['shuffle-files'], + sort = cliArgs['sort-files'], + }) + + busted.publish({ 'exit' }) + + if options.standalone or failures > 0 or errors > 0 then + exit(failures + errors, forceExit) + end +end diff --git a/busted/status.lua b/busted/status.lua new file mode 100644 index 00000000..c68ce7fc --- /dev/null +++ b/busted/status.lua @@ -0,0 +1,43 @@ +local function get_status(status) + local smap = { + ['success'] = 'success', + ['pending'] = 'pending', + ['failure'] = 'failure', + ['error'] = 'error', + ['true'] = 'success', + ['false'] = 'failure', + ['nil'] = 'error', + } + return smap[tostring(status)] or 'error' +end + +return function(inital_status) + local objstat = get_status(inital_status) + local obj = { + success = function(self) return (objstat == 'success') end, + pending = function(self) return (objstat == 'pending') end, + failure = function(self) return (objstat == 'failure') end, + error = function(self) return (objstat == 'error') end, + + get = function(self) + return objstat + end, + + set = function(self, status) + objstat = get_status(status) + end, + + update = function(self, status) + -- prefer current failure/error status over new status + status = get_status(status) + if objstat == 'success' or (objstat == 'pending' and status ~= 'success') then + objstat = status + end + end + } + + return setmetatable(obj, { + __index = {}, + __tostring = function(self) return objstat end + }) +end diff --git a/busted/utils.lua b/busted/utils.lua new file mode 100644 index 00000000..bf771487 --- /dev/null +++ b/busted/utils.lua @@ -0,0 +1,37 @@ +return { + copy_interpreter_args = function(arguments) + -- copy non-positive command-line args auto-inserted by Lua interpreter + if arguments and _G.arg then + local i = 0 + while _G.arg[i] do + arguments[i] = _G.arg[i] + i = i - 1 + end + end + end, + + split = require 'pl.utils'.split, + + shuffle = function(t, seed) + if seed then math.randomseed(seed) end + local n = #t + while n >= 2 do + local k = math.random(n) + t[n], t[k] = t[k], t[n] + n = n - 1 + end + return t + end, + + urandom = function() + local f = io.open('/dev/urandom', 'rb') + if not f then return nil end + local s = f:read(4) f:close() + local bytes = {s:byte(1, 4)} + local value = 0 + for _, v in ipairs(bytes) do + value = value * 256 + v + end + return value + end, +} diff --git a/completions/bash/busted.bash b/completions/bash/busted.bash new file mode 100644 index 00000000..fb2af435 --- /dev/null +++ b/completions/bash/busted.bash @@ -0,0 +1,207 @@ +# bash completion for busted +# + +_busted_comp() { + local opt IFS=' '$'\t'$'\n' + for opt in $1; do + case $opt in + --*=*) printf %s$'\n' "$opt" ;; + *.) printf %s$'\n' "$opt" ;; + *) printf %s$'\n' "$opt " ;; + esac + done +} + +_busted() { + COMPREPLY=() + local cur="${COMP_WORDS[COMP_CWORD]}" + local prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ "${cur}" == "=" ]]; then + cur= + fi + if [[ "${prev}" == "=" ]]; then + prev="${COMP_WORDS[COMP_CWORD-2]}" + fi + + case "${prev}" in + --lang) + local langs="ar de en fr ja nl ru th ua zh" + COMPREPLY=( $(compgen -W "${langs}" -- ${cur}) ) + return 0 + ;; + -o|--output) + local outputs="plainTerminal utfTerminal TAP json junit sound" + local -a toks + toks=( ${toks[@]-} $(compgen -W "${outputs}" -- ${cur} ) ) + toks=( ${toks[@]-} $(compgen -f -X "!*.lua" -- ${cur} ) ) + toks=( ${toks[@]-} $(compgen -f -X "!*.moon" -- ${cur} ) ) + toks=( ${toks[@]-} $(compgen -d -- ${cur} ) ) + + if declare -fF _compopt_o_filenames > /dev/null; then + _compopt_o_filenames + else + compopt -o filenames + fi + + COMPREPLY=( "${COMPREPLY[@]}" "${toks[@]}" ) + return 0 + ;; + -r|--run) + local d="." + local f + local i + local word + for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )); do + case "${COMP_WORDS[i]}" in + -C|-f) + word="${COMP_WORDS[i+1]}" + if [ "${COMP_WORDS[i]}" == "-f" ]; then + f="${word}" + else + if [ "${word:0:1}" == "/" ]; then + d="${word}" + else + d="${d}/${word}" + fi + fi + ;; + --directory|--config-file) + word="${COMP_WORDS[i+1]}" + if [ "${word}" == "=" ]; then + word="${COMP_WORDS[i+2]}" + fi + if [ "${COMP_WORDS[i]}" == "--config-file" ]; then + f="${word}" + else + if [ "${word:0:1}" == "/" ]; then + d="${word}" + else + d="${d}/${word}" + fi + fi + ;; + esac + done + local cfgs=$(lua -e "cfgs=dofile('${f:-${d}/.busted}')" \ + -e "for k,_ in pairs(cfgs) do print(k) end" 2> /dev/null) + COMPREPLY=( $(compgen -W "${cfgs}" -- ${cur}) ) + return 0 + ;; + --loaders) + local prefix=${cur%,*} + local cur_=${cur##*,} + local loaders="lua moonscript terra" + local -a toks + toks=( ${toks[@]-} $(compgen -W "${loaders[@]}" -- ${cur} ) ) + if [[ "${prefix}" != "${cur}" ]]; then + local mloaders="" + for l in ${loaders}; do + if ! [[ "${prefix}," =~ .*,$l,.* || "${prefix}," =~ ^$l,.* ]]; then + mloaders="${mloaders} $l" + fi + done + toks=( ${toks[@]-} $(compgen -P "${prefix}," -W "${mloaders}" -- ${cur_} ) ) + fi + compopt -o nospace + + COMPREPLY=( "${COMPREPLY[@]}" "${toks[@]}" ) + return 0 + ;; + -C|--directory) + _filedir -d + return 0 + ;; + -f|--config-file) + _filedir + return 0 + ;; + --lua) + _filedir + return 0 + ;; + --helper) + _filedir + return 0 + ;; + -e) + # no completion available + return 0 + ;; + -p|--pattern|--exclude-pattern) + # no completion available + return 0 + ;; + -t|--tags|--exclude-tags) + # no completion available + return 0 + ;; + --filter|--filter-out) + # no completion available + return 0 + ;; + -m|--lpath|--cpath) + _filedir -d + return 0 + ;; + -Xoutput|--Xhelper) + # no completion available + return 0 + ;; + --repeat) + # no completion available + return 0 + ;; + --seed) + # no completion available + return 0 + ;; + esac + + if [[ "${cur}" == -* ]] ; then + local opts=" + -h --help + -v --verbose --no-verbose + --version + -l --list + -o --output= + -p --pattern= --exclude-pattern= + -C --directory= + -f --config-file= + -t --tags= --exclude-tags= + -m --lpath= --cpath= + -r --run= + -e + --lua= + --ignore-lua + --filter= --filter-out= + --repeat= + --seed= + --lang= + --loaders= + --helper= + -c --coverage --no-coverage + -s --enable-sound --no-enable-sound + -Xoutput + -Xhelper + --lazy --no-lazy + --auto-insulate --no-auto-insulate + -k --keep-going --no-keep-going + -R --recursive --no-recursive + --shuffle --shuffle-tests --shuffle-files + --no-shuffle --no-shuffle-tests --no-shuffle-files + --sort --sort-tests --sort-files + --no-sort --no-sort-tests --no-sort-files + --supress-pending --no-supress-pending + --defer-print --no-defer-print" + compopt -o nospace + + local IFS=$'\n' + COMPREPLY=( $(compgen -W "$(_busted_comp "${opts-}")" -- "${cur}" )) + return 0 + else + _filedir + fi +} + +complete -F _busted busted diff --git a/completions/zsh/_busted b/completions/zsh/_busted index 7bf7ed08..8f100d16 100644 --- a/completions/zsh/_busted +++ b/completions/zsh/_busted @@ -3,25 +3,111 @@ #alias rbu="unfunction _busted; autoload -U _busted" -local -a _busted_args +local _busted_args _busted_args=( -"--cpath=:Optional path to be prefixed to the Lua C module search path (default: ./csrc/?.so;./csrc/?/?.so;)" -"--defer-print:Print to when test suite is complete" -"--exclude-tags=:Do not run tests with these #tags, takes precedence over --tags" -"--lang=:Language for error messages (default: en)" -"--suppress-pending:'pending' test output" -"--version:Prints the program's version and exits" -"-c:Do code coverage analysis (requires 'LuaCov' to be installed) --coverage " -"-d:Path to current working directory (default: ./) --cwd=cwd " -"-h:Help" -"-l:Path to the execution environment (luajit or lua), picks first available (default: luajit) --lua=luajit " -"-m:Optional path to be prefixed to the Lua module search path (default: ./src/?.lua;./src/?/?.lua;./src/?/init.lua) --lpath=path " -"-o:Output library to load (default: utf_terminal) --output=LIBRARY " -"-p:Only run test files matching the Lua pattern (default: _spec) --pattern=pattern " -"-r:Config to run from .busted file --run=run " -"-s:Executes 'say' command if available --enable-sound " -"-t:Only run tests with these #tags --tags=tags " -"-v:Verbose output of errors --verbose " +"--cpath=[Optional path to be prefixed to the Lua C module search path (default: ./csrc/?.so;./csrc/?/?.so;)]:directory:_dirs" +"(--defer-print --no-defer-print)--defer-print[Defer print to when test suite is complete]" +"(--defer-print --no-defer-print)--no-defer-print[Do not defer print to when test suite is complete]" +"--lang=[Language for error messages (default: en)]:languages:(ar de en fr ja nl ru th ua zh)" +"--repeat=[Run the tests repeatedly (default: 1)]: :" +"--seed=[Random seed value to use for shuffing test order (default: os.time())]: :" +"(--sort --shuffle --no-shuffle --no-sort)--shuffle[Randomize file and test order, takes precedence over --sort (--shuffle-test and --shuffle-files)]" +"(--sort --shuffle --no-shuffle --no-sort)--no-shuffle[Do not randomize file and test order (--no-shuffle-test and --no-shuffle-files)]" +"(--sort-files --shuffle-files --no-sort-files --no-shuffle-files)--shuffle-files[Randomize file execution order, takes precedence over --sort-files]" +"(--sort-files --shuffle-files --no-sort-files --no-shuffle-files)--no-shuffle-files[Do not randomize file execution order]" +"(--sort-tests --shuffle-tests --no-sort-tests --no-shuffle-tests)--shuffle-tests[Randomize test order within a file, takes precedence over --sort-tests]" +"(--sort-tests --shuffle-tests --no-sort-tests --no-shuffle-tests)--no-shuffle-tests[Do not randomize test order within a file]" +"(--sort --shuffle --no-shuffle --no-sort)--sort[Sort file and test order (--sort-tests and --sort-files)]" +"(--sort --shuffle --no-shuffle --no-sort)--no-sort[Do not sort file and test order (--no-sort-tests and --no-sort-files)]" +"(--sort-files --shuffle-files --no-sort-files --no-shuffle-files)--sort-files[Sort file execution order]" +"(--sort-files --shuffle-files --no-sort-files --no-shuffle-files)--no-sort-files[Do not sort file execution order]" +"(--sort-tests --shuffle-tests --no-sort-tests --no-shuffle-tests)--sort-tests[Sort test order within a file]" +"(--sort-tests --shuffle-tests --no-sort-tests --no-shuffle-tests)--no-sort-tests[Do not sort test order within a file]" +"(--suppress-pending --no-suppress-pending)--suppress-pending[Suppress 'pending' test output]" +"(--suppress-pending --no-suppress-pending)--no-suppress-pending[Do not suppress 'pending' test output]" +"--version[Prints the program version and exits]" +"(-l --list)"{-l,--list}"[List the names of all tests instead of running them]" +"(--lazy --no-lazy)--lazy[Use lazy setup/teardown as the default]" +"(--lazy --no-lazy)--no-lazy[Use strict setup/teardown as the default]" +"(--auto-insulate --no-auto-insulate)--auto-insulate[Enable file insulation]" +"(--auto-insulate --no-auto-insulate)--no-auto-insulate[Disable file insulation]" +"(-k --keep-going --no-keep-going)--keep-going[Continue as much as possible after an error or failure]" +"(-k --keep-going --no-keep-going)--no-keep-going[Quit after first error or failure]" +"(-R --recursive --no-recursive)--recursive[Recurse into subdirectories]" +"(-R --recursive --no-recursive)--no-recursive[Do not recurse into subdirectories]" +"--ignore-lua[Whether or not to ignore the lua directive]" +"--lua[The path to the lua interpreter busted should run under]:files:_files" +"--helper[A helper script that is run before tests]:files:_files" +"--loaders=[Test file loaders]:loaders:_values -s , loaders lua moonscript terra" +"-Xoutput[Pass 'OPTION' as an option to the output handler. If 'OPTION' contains commas, it is split into multiple options at the commas.]: :" +"-Xhelper[Pass 'OPTION' as an option to the helper script. If 'OPTION' contains commas, it is split into multiple options at the commas.]: :" +"(-c --coverage --no-coverage)"{-c,--coverage}"[Do code coverage analysis (requires 'LuaCov' to be installed)]" +"(-c --coverage --no-coverage)--no-coverage[Disable code coverage analysis]" +"(-C --directory)"{-C,--directory=}"[Change to directory DIR before running tests. If multiple options are specified, each is interpreted relative to the previous one. (default: ./)]:directory:_dirs" +"(-f --config-file)"{-f,--config-file=}"[Load configuration options from FILE]:files:_files" +"(-h --help)"{-h,--help}"[Help]" +"(-m --lpath)"{-m,--lpath=}"[Optional path to be prefixed to the Lua module search path (default: ./src/?.lua;./src/?/?.lua;./src/?/init.lua)]:directory:_dirs" +"(-o --output)"{-o,--output=}"[Output library to load (default: utfTerminal)]:output handlers:->output" +"(-p --pattern)"{-p,--pattern=}"[Only run test files matching the Lua pattern (default: _spec)]: :" +"--exclude-pattern=[Do not run test files matching the Lua pattern, takes precedence over --pattern]: :" +"(-r --run)"{-r,--run=}"[Config to run from .busted file]:run configurations:->run" +"(-s --enable-sound)"{-s,--enable-sound}"[Executes 'say' command if available]" +"(-t --tags)"{-t,--tags=}"[Only run tests with these #tags]: :" +"--exclude-tags=[Do not run tests with these #tags, takes precedence over --tags]: :" +"--filter=[Only run test names matching the Lua pattern]: :" +"--filter-out=[Do not run test names matching the Lua pattern, takes precedence over --filter]: :" +"-e[Execute Lua statement]: :" +"(-v --verbose --no-verbose)"{-v,--verbose}"[Verbose output of errors]" +"(-v --verbose --no-verbose)--no-verbose[Disable verbose output of errors]" +"*:files:_files" ) -_describe -t commands "busted args" _busted_args +local curcontext="${curcontext}" state line +typeset -A opt_args + +_arguments -s $_busted_args + +case "${state}" in + run) + local d="." + local f + local i + local word + for (( i=1; i < ${#words}; i++ )); do + case "${words[i]}" in + -C|--directory|-f|--config-file) + word="${words[i+1]}" + if [[ "${words[i]}" == "-f" ]]; then + f="${word}" + else + if [[ "${word[1,1]}" == "/" ]]; then + d="${word}" + else + d="${d}/${word}" + fi + fi + ;; + --directory=*|--config-file=*) + if [[ "${words[i][1,14]}" == "--config-file=" ]]; then + f="${words[i][15,-1]}" + else + word="${words[i][13,-1]}" + if [[ "${word[1,1]}" == "/" ]]; then + d="${word}" + else + d="${d}/${word}" + fi + fi + ;; + esac + done + local cfgs + cfgs=$(lua -e "cfgs=dofile('${f:-${d}/.busted}')" \ + -e "for k,_ in pairs(cfgs) do print(k) end" 2> /dev/null) + _arguments -s "*:run configurations:(${cfgs})" + ;; + output) + _alternative "*:output handlers:(plainTerminal utfTerminal TAP json junit sound)" + _arguments -s "*:output handlers:_files -g '*.lua *.moon'" + ;; +esac diff --git a/spec/.hidden/.busted b/spec/.hidden/.busted new file mode 100644 index 00000000..4184008f --- /dev/null +++ b/spec/.hidden/.busted @@ -0,0 +1,28 @@ +return { + _all = { + ['ROOT'] = {'tests'}, + }, + default = { + ['ROOT'] = {'specs'}, + ['tags'] = {'tag11','tag22','tag33'}, + ['exclude-tags'] = {'etag11','etag22','etag33'}, + ['pattern'] = '_spec%.lua$', + ['exclude-pattern'] = '_exclude', + ['filter'] = 'filt', + ['filter-out'] = 'filt-out', + ['loaders'] = 'terra,moonscript', + ['Xoutput'] = '-f,--flag', + ['Xhelper'] = '-v,--verbose', + }, + test = { + ['tags'] = 'test1,test2,test3', + ['exclude-tags'] = 'etest1,etest2,etest3', + ['pattern'] = {'_test1%.lua$', '_test2%.lua$'}, + ['exclude-pattern'] = {'_exclude1', '_exclude2'}, + ['filter'] = {'filt1', 'filt2'}, + ['filter-out'] = {'filt1-out', 'filt2-out'}, + ['loaders'] = {'lua','terra'}, + ['Xoutput'] = {'-s','--sound'}, + ['Xhelper'] = {'-t','--print'}, + } +} diff --git a/spec/.hidden/.busted_bad b/spec/.hidden/.busted_bad new file mode 100644 index 00000000..b1df1c77 --- /dev/null +++ b/spec/.hidden/.busted_bad @@ -0,0 +1,14 @@ +return { + _all = { + ['ROOT'] = {'tests'}, + }, + default = { + ['ROOT'] = {'specs'}, + ['pattern'] = '_spec%.lua$', + ['loaders'] = doesnotexist.loaders, + ['verbose'] = true, + }, + test = { + ['pattern'] = '_test%.lua$', + } +} diff --git a/spec/.hidden/.busted_empty b/spec/.hidden/.busted_empty new file mode 100644 index 00000000..e69de29b diff --git a/spec/async_spec.lua b/spec/async_spec.lua index 65284aa2..3b4209c7 100644 --- a/spec/async_spec.lua +++ b/spec/async_spec.lua @@ -1,4 +1,4 @@ -describe('testing the done callback with tokens', function() +pending('testing the done callback with tokens', function() it('Tests done call back ordered', function() async() @@ -15,6 +15,7 @@ describe('testing the done callback with tokens', function() assert.stub(done.done_cb).was.called(1) done.done_cb:revert() -- revert so test can complete + done() end) it('Tests done call back unordered', function() @@ -31,6 +32,7 @@ describe('testing the done callback with tokens', function() assert.stub(done.done_cb).was.called(1) done.done_cb:revert() -- revert so test can complete + done() end) it('Tests done call back defaulting to ordered', function() @@ -43,37 +45,43 @@ describe('testing the done callback with tokens', function() assert.has_no_error(function() done('2') end) done.done_cb:revert() -- revert so test can complete + done() end) end) -describe('testing done callbacks being provided for async tests', function() +pending('testing done callbacks being provided for async tests', function() setup(function() async() assert.is_table(done) assert.is_function(done.wait) + done() end) before_each(function() async() assert.is_table(done) assert.is_function(done.wait) + done() end) after_each(function() async() assert.is_table(done) assert.is_function(done.wait) + done() end) teardown(function() async() assert.is_table(done) assert.is_function(done.wait) + done() end) it('Tests done callbacks being provided for async tests', function() async() assert.is_table(done) assert.is_function(done.wait) + done() end) end) diff --git a/spec/cl_error_messages.lua b/spec/cl_error_messages.lua new file mode 100644 index 00000000..a76bcfd0 --- /dev/null +++ b/spec/cl_error_messages.lua @@ -0,0 +1,24 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Test error messages show file and line for', function() + it('table errors #table', function() + error({}) + end) + + it('nil errors #nil', function() + error() + end) + + it('string errors #string', function() + error('error message') + end) + + it('table errors #tostring', function() + error(setmetatable({}, { __tostring = function() return '{}' end})) + end) + + it('table errors #pcall', function() + pcall(error, 'error inside pcall') + error('error after pcall') + end) +end) diff --git a/spec/cl_errors.lua b/spec/cl_errors.lua new file mode 100644 index 00000000..29d4a360 --- /dev/null +++ b/spec/cl_errors.lua @@ -0,0 +1,14 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Tests the busted error detection through the commandline', function() + + it('is a test that throws an error #testerr', function() + error('force an error') + end) + + it('is a test with a Lua error #luaerr', function() + local foo + foo.bar = nil + end) +end) + diff --git a/spec/cl_failing_support.lua b/spec/cl_failing_support.lua index 44942075..21a85a5e 100644 --- a/spec/cl_failing_support.lua +++ b/spec/cl_failing_support.lua @@ -12,6 +12,11 @@ describe('bad support functions should fail, sync test', function() teardown(function() end) it('Tests nothing, should always fail due to failing support functions', function() + assert(false) + end) + + it('Tests nothing, should always fail due to failing support functions', function() + assert(false) end) end) @@ -29,6 +34,9 @@ describe('bad support functions should fail, sync test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) end) @@ -45,6 +53,9 @@ describe('bad support functions should fail, sync test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) end) describe('bad teardown should properly fail a test', function() @@ -60,6 +71,27 @@ describe('bad support functions should fail, sync test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) + end) + + describe('bad setup/teardown should properly fail a test', function() + setup(function() + error('failing a setup method') + end) + + before_each(function() end) + + after_each(function() end) + + teardown(function() + error('failing a teardown method') + end) + + it('Tests nothing, should always fail due to failing support functions', function() + assert(false) + end) end) end) @@ -78,6 +110,9 @@ describe('bad support functions should fail, async test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) end) describe('bad before_each should properly fail a test, async', function() @@ -94,6 +129,9 @@ describe('bad support functions should fail, async test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) end) describe('bad after_each should properly fail a test, async', function() @@ -110,6 +148,9 @@ describe('bad support functions should fail, async test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) end) describe('bad teardown should properly fail a test, async', function() @@ -126,6 +167,34 @@ describe('bad support functions should fail, async test', function() it('Tests nothing, should always fail due to failing support functions', function() end) + + it('Tests nothing, should always fail due to failing support functions', function() + end) end) + + describe('bad setup/teardown should properly fail a test, async', function() + setup(function() + async() + error('failing a setup method') + end) + + before_each(function() end) + + after_each(function() end) + + teardown(function() + async() + error('failing a teardown method') + end) + + it('Tests nothing, should always fail due to failing support functions', function() + assert(false) + end) + + it('Tests nothing, should always fail due to failing support functions', function() + assert(false) + end) + end) + end) diff --git a/spec/cl_filter.lua b/spec/cl_filter.lua new file mode 100644 index 00000000..57fd6aa6 --- /dev/null +++ b/spec/cl_filter.lua @@ -0,0 +1,49 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Tests the busted command-line options', function() + + it('is a test with pattern1', function() + -- works by counting failure + error('error 1 on pattern1') + end) + + it('is another test with pattern1', function() + -- works by counting failure + error('error 2 on pattern1') + end) + + it('is a test with pattern2', function() + -- works by counting failure + error('error on pattern2') + end) + + it('is a test with pattern3', function() + -- nothing here, makes it succeed + end) + + it('is a test with two pattern3 and pattern4', function () + -- Always succeed + end) +end) + +describe('Tests describe with patt1', function() + before_each(function() + error('error in before_each on patt1') + end) + + after_each(function() + error('error in after_each on patt1') + end) + + it('is a test inside describe', function() + end) + + it('is another test inside describe', function() + end) +end) + +context('Tests context with patt2', function() + setup(function() + error('error in setup on patt2') + end) +end) diff --git a/spec/cl_gc_error.lua b/spec/cl_gc_error.lua new file mode 100644 index 00000000..248ba835 --- /dev/null +++ b/spec/cl_gc_error.lua @@ -0,0 +1,9 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Runs test with garbage collection failure', function() + it('throws error in __gc metamethod', function() + setmetatable({}, { __gc = function() error('gc error') end}) + collectgarbage() + collectgarbage() + end) +end) diff --git a/spec/cl_helper_script.lua b/spec/cl_helper_script.lua new file mode 100644 index 00000000..f1af2995 --- /dev/null +++ b/spec/cl_helper_script.lua @@ -0,0 +1,87 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +local setup = require 'busted'.setup +local teardown = require 'busted'.teardown +local before_each = require 'busted'.before_each +local after_each = require 'busted'.after_each +local subscribe = require 'busted'.subscribe +local assert = require 'busted'.assert +local cli = require 'cliargs' + +cli:set_name('cl_helper_script') +cli:flag('--fail-setup', 'force setup to fail') +cli:flag('--fail-teardown', 'force teardown to fail') +cli:flag('--fail-before-each', 'force before each to fail') +cli:flag('--fail-after-each', 'force after each to fail') +cli:flag('--fail-suite-reset', 'force suite reset handler to fail') +cli:flag('--fail-suite-start', 'force suite start handler to fail') +cli:flag('--fail-suite-end', 'force suite end handler to fail') +cli:flag('--fail-file-start', 'force file start handler to fail') +cli:flag('--fail-file-end', 'force file end handler to fail') +cli:flag('--fail-describe-start', 'force describe start handler to fail') +cli:flag('--fail-describe-end', 'force describe end handler to fail') +cli:flag('--fail-test-start', 'force test start handler to fail') +cli:flag('--fail-test-end', 'force test end handler to fail') + +local cliArgs = cli:parse(arg) + +setup(function() + assert(not cliArgs['fail-setup']) +end) + +teardown(function() + assert(not cliArgs['fail-teardown']) +end) + +before_each(function() + assert(not cliArgs['fail-before-each']) +end) + +after_each(function() + assert(not cliArgs['fail-after-each']) +end) + +subscribe({'suite', 'reset'}, function() + assert(not cliArgs['fail-suite-reset']) + return nil, true +end) + +subscribe({'suite', 'start'}, function() + assert(not cliArgs['fail-suite-start']) + return nil, true +end) + +subscribe({'suite', 'end'}, function() + assert(not cliArgs['fail-suite-end']) + return nil, true +end) + +subscribe({'file', 'start'}, function() + assert(not cliArgs['fail-file-start']) + return nil, true +end) + +subscribe({'file', 'end'}, function() + assert(not cliArgs['fail-file-end']) + return nil, true +end) + +subscribe({'describe', 'start'}, function() + assert(not cliArgs['fail-describe-start']) + return nil, true +end) + +subscribe({'describe', 'end'}, function() + assert(not cliArgs['fail-describe-end']) + return nil, true +end) + +subscribe({'test', 'start'}, function() + assert(not cliArgs['fail-test-start']) + return nil, true +end) + +subscribe({'test', 'end'}, function() + assert(not cliArgs['fail-test-end']) + return nil, true +end) diff --git a/spec/cl_list.lua b/spec/cl_list.lua new file mode 100644 index 00000000..c376ce45 --- /dev/null +++ b/spec/cl_list.lua @@ -0,0 +1,12 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Tests list', function() + it('test 1', function() + end) + + it('test 2', function() + end) + + it('test 3', function() + end) +end) diff --git a/spec/cl_lua_path.lua b/spec/cl_lua_path.lua new file mode 100644 index 00000000..30ec6f8f --- /dev/null +++ b/spec/cl_lua_path.lua @@ -0,0 +1,8 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Tests --lpath prepends to package.path', function() + it('require test module', function() + local mod = require('cl_test_module') + assert.is_equal('test module', mod) + end) +end) diff --git a/spec/cl_moonscript_error_messages.moon b/spec/cl_moonscript_error_messages.moon new file mode 100644 index 00000000..3dcb3ea7 --- /dev/null +++ b/spec/cl_moonscript_error_messages.moon @@ -0,0 +1,22 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe 'Test moonscript errors show file and line for', -> + it 'failures #fail', -> + assert.is_equal true, false + return + + it 'table errors #table', -> + error {} + return + + it 'nil errors #nil', -> + error! + return + + it 'string errors #string', -> + error 'error message' + return + + return + +return diff --git a/spec/cl_output_handler.lua b/spec/cl_output_handler.lua new file mode 100644 index 00000000..8a253a4a --- /dev/null +++ b/spec/cl_output_handler.lua @@ -0,0 +1,26 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +return function(options) + local busted = require 'busted' + local handler = require 'busted.outputHandlers.base'() + local cli = require 'cliargs' + local args = options.arguments + + cli:set_name('cl_output_handler') + cli:flag('--time', 'show timestamps') + cli:option('--time-format=FORMAT', 'format string according to strftime', '!%a %b %d %H:%M:%S %Y') + + local cliArgs = cli:parse(args) + + handler.testEnd = function(element, parent, status, debug) + local showTime = cliArgs.time + local timeFormat = cliArgs['time-format'] + local timestamp = showTime and ('[' .. os.date(timeFormat, 123456) .. '] ') or '' + + print(string.format("%s[%8s] %s", timestamp, status, handler.getFullName(element))) + end + + busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending }) + + return handler +end diff --git a/spec/cl_pending.lua b/spec/cl_pending.lua new file mode 100644 index 00000000..00fa7255 --- /dev/null +++ b/spec/cl_pending.lua @@ -0,0 +1,16 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Tests the busted pending functions through the commandline', function() + + it('is a test with a pending', function() + pending('finish this test later') + error('should never get here') + end) + + pending('is a pending inside a describe', function() + it('this test does not run', function() + error('this should not run') + end) + end) +end) + diff --git a/spec/cl_random_seed.lua b/spec/cl_random_seed.lua new file mode 100644 index 00000000..9dca26ba --- /dev/null +++ b/spec/cl_random_seed.lua @@ -0,0 +1,33 @@ +-- supporting testfile; belongs to 'cl_spec.lua' +-- executed with --seed=12345 +local order = {} + +describe('Randomizing test order with pre-defined seed', function() + randomize() + + for i = 1, 10 do + it('does 10 its', function() + table.insert(order, i) + end) + end +end) + +describe('Order of tests ran', function() + randomize() + + it('randomized with known random seed', function() + math.randomseed(12345) + local t = {} + for i = 1, 10 do + table.insert(t, i) + end + local n = #t + while n >= 1 do + local k = math.random(n) + t[n], t[k] = t[k], t[n] + n = n - 1 + end + local expected = t + assert.are.same(expected, order) + end) +end) diff --git a/spec/cl_randomize.lua b/spec/cl_randomize.lua new file mode 100644 index 00000000..91e933b3 --- /dev/null +++ b/spec/cl_randomize.lua @@ -0,0 +1,18 @@ +-- supporting testfile; belongs to 'cl_spec.lua' +local unexpected = {} +local order = {} + +describe('Randomizing test order with --shuffle flag', function() + for i = 1, 100 do + table.insert(unexpected, i) + + it('does 100 its', function() + table.insert(order, i) + end) + end + + teardown('runs tests in randomized order', function() + assert.are_not.same(unexpected, order) + end) +end) + diff --git a/spec/cl_sort.lua b/spec/cl_sort.lua new file mode 100644 index 00000000..68ddfc92 --- /dev/null +++ b/spec/cl_sort.lua @@ -0,0 +1,29 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +describe('Randomizing test order with --sort flag', function() + local expected = {} + local scratch = {} + local order = {} + + for i = 1, 100 do + table.insert(expected, i) + table.insert(scratch, i) + end + + while #scratch > 0 do + local n = #scratch + local k = math.random(n) + local num = scratch[k] + + it(string.format('test number %03d', num), function() + table.insert(order, num) + end) + + scratch[k], scratch[n] = scratch[n], scratch[k] + table.remove(scratch) + end + + teardown('runs tests in sorted order', function() + assert.are.same(expected, order) + end) +end) diff --git a/spec/cl_spec.lua b/spec/cl_spec.lua index 565f4238..30e83040 100644 --- a/spec/cl_spec.lua +++ b/spec/cl_spec.lua @@ -1,26 +1,7 @@ local utils = require 'pl.utils' -local path = require('pl.path') -local ditch = ' > /dev/null 2>&1' -if path.is_windows then - ditch = ' 1> NUL 2>NUL' -end ---ditch = '' -- uncomment this line, to show output of failing commands, for debugging - -local error_started -local error_start = function() - if ditch ~= '' then return end - print('================================================') - print('== Error block follows ==') - print('================================================') - error_started = true -end -local error_end = function() - if ditch ~= '' then return end - print('================================================') - print('== Error block ended, all according to plan ==') - print('================================================') - error_started = false -end +local path = require 'pl.path' +local normpath = path.normpath +local busted_cmd = path.is_windows and 'lua bin/busted' or 'bin/busted' -- if exitcode >256, then take MSB as exit code local modexit = function(exitcode) @@ -32,151 +13,757 @@ local modexit = function(exitcode) end local execute = function(cmd) - local success, exitcode = utils.execute(cmd..ditch) - return not not success, modexit(exitcode) + local success, exitcode, out, err = utils.executeex(cmd) + return not not success, modexit(exitcode), out, err end +local executeBusted = function(args) + local success, exitcode, out, err = execute(busted_cmd .. ' ' .. args) + local count = 0 + for failures, errors in out:gmatch('(%d+) failures? / (%d+) errors?') do + count = count + failures + errors + end + return success, count, out, err +end -it('Tests the busted command-line options', function() +local executeLua = function(args) + local success, exitcode, out, err = execute('lua ' .. args) + local count = 0 + for failures, errors in out:gmatch('(%d+) failures? / (%d+) errors?') do + count = count + failures + errors + end + return success, count, out, err +end - setup(function() - require('pl') - end) - - after_each(function() - if error_started then - print('================================================') - print('== Error block ended, something was wrong ==') - print('================================================') - error_started = false - end - end) - +describe('Tests the busted command-line options', function() it('tests running with --tags specified', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=_tags.lua$') + local success, errcnt = executeBusted('--pattern=_tags.lua$') + assert.is_false(success) + assert.is_equal(8, errcnt) + success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=tag1') assert.is_false(success) - assert.is_equal(3, exitcode) - success, exitcode = execute('bin/busted --pattern=_tags.lua$ --tags=tag1') + assert.is_equal(3, errcnt) + success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=tag1,tag2') assert.is_false(success) - assert.is_equal(2, exitcode) - success, exitcode = execute('bin/busted --pattern=_tags.lua$ --tags=tag1,tag2') + assert.is_equal(4, errcnt) + success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=tag1 --tags=tag2') assert.is_false(success) - assert.is_equal(3, exitcode) - error_end() + assert.is_equal(4, errcnt) end) it('tests running with --exclude-tags specified', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=_tags.lua$ --exclude-tags=tag1,tag2') + local success, errcnt = executeBusted('--pattern=_tags.lua$ --exclude-tags=tag1,tag2,dtag1,dtag2') assert.is_true(success) - assert.is_equal(0, exitcode) - success, exitcode = execute('bin/busted --pattern=_tags.lua$ --exclude-tags=tag2') + assert.is_equal(0, errcnt) + success, errcnt = executeBusted('--pattern=_tags.lua$ --exclude-tags=tag2,dtag1,dtag2') assert.is_false(success) - assert.is_equal(2, exitcode) - error_end() + assert.is_equal(2, errcnt) + success, errcnt = executeBusted('--pattern=_tags.lua$ --exclude-tags=tag2 --exclude-tags=dtag1,dtag2') + assert.is_false(success) + assert.is_equal(2, errcnt) end) it('tests running with --tags and --exclude-tags specified', function () - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=_tags.lua$ --tags=tag1 --exclude-tags=tag1') + local success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=tag1 --exclude-tags=tag1') assert.is_false(success) - assert.is_equal(1, exitcode) - success, exitcode = execute('bin/busted --pattern=_tags.lua$ --tags=tag3 --exclude-tags=tag4') + success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=tag3 --exclude-tags=tag4') + assert.is_false(success) + end) + + it('tests running with --tags specified in describe', function () + local success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=dtag1') + assert.is_false(success) + assert.is_equal(5, errcnt) + success, errcnt = executeBusted('--pattern=_tags.lua$ --tags=dtag2') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) + + it('tests running with --exclude-pattern specified', function () + local success, errcnt = executeBusted('--pattern="^cl_.*fail.*.lua$" --exclude-pattern="failing"') + assert.is_false(success) + assert.is_equal(4, errcnt) + end) + + it('tests running with --exclude-pattern="" disables exclude-pattern', function () + local success, errcnt = executeBusted('--pattern="cl_two_failures.lua$" --exclude-pattern ""') + assert.is_false(success) + assert.is_equal(2, errcnt) + local success, errcnt = executeBusted('--pattern="cl_success.lua$" --exclude-pattern=') assert.is_true(success) - assert.is_equal(0, exitcode) - error_end() + end) + + it('tests running with the same --pattern and --exclude-pattern specified', function () + local success, errcnt = executeBusted('--pattern="^cl_.*fail.*.lua$" --exclude-pattern="fail"') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) + + it('tests running with --filter specified', function () + local success, errcnt = executeBusted('--pattern=_filter.lua$') + assert.is_false(success) + assert.is_equal(8, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter="pattern1"') + assert.is_false(success) + assert.is_equal(3, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter="pattern2"') + assert.is_false(success) + assert.is_equal(2, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter="pattern1" --filter="pattern2"') + assert.is_false(success) + assert.is_equal(4, errcnt) + end) + + it('tests running with --filter-out specified', function () + local success, errcnt = executeBusted('--pattern=_filter.lua$ --filter-out="pattern1"') + assert.is_false(success) + assert.is_equal(6, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter-out="pattern%d"') + assert.is_false(success) + assert.is_equal(5, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter-out="patt1" --filter-out="patt2"') + assert.is_false(success) + assert.is_equal(3, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter-out="patt.*(%d)"') + assert.is_true(success) + end) + + it('tests running with --filter and --filter-out specified', function () + local success, errcnt = executeBusted('--pattern=_filter.lua$ --filter="pattern3" --filter-out="patt.*[12]"') + assert.is_true(success) + end) + + it('tests running with --filter specified in describe', function () + local success, errcnt = executeBusted('--pattern=_filter.lua$ --filter="patt1"') + assert.is_false(success) + assert.is_equal(5, errcnt) + success, errcnt = executeBusted('--pattern=_filter.lua$ --filter="patt2"') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) + + it('tests running with --lazy specified', function() + local success, errcnt = executeBusted('--lazy --pattern=_tags.lua$') + assert.is_false(success) + assert.is_equal(7, errcnt) + success, errcnt = executeBusted('--lazy --pattern=_tags.lua$ --tags=tag1') + assert.is_false(success) + assert.is_equal(2, errcnt) + success, errcnt = executeBusted('--lazy --pattern=_tags.lua$ --tags=tag1,tag2') + assert.is_false(success) + assert.is_equal(3, errcnt) + success, errcnt = executeBusted('--lazy --pattern=_tags.lua$ --tags=tag1 --tags=tag2') + assert.is_false(success) + assert.is_equal(3, errcnt) + end) + + it('tests running with -l specified', function() + local _, _, result = executeBusted('-l --pattern=cl_list.lua$') + local expected = 'spec/cl_list.lua:4: Tests list test 1\n' .. + 'spec/cl_list.lua:7: Tests list test 2\n' .. + 'spec/cl_list.lua:10: Tests list test 3\n' + assert.is_equal(normpath(expected), result) + end) + + it('tests running with --list specified', function() + local _, _, result = executeBusted('--list --pattern=cl_list.lua$') + local expected = 'spec/cl_list.lua:4: Tests list test 1\n' .. + 'spec/cl_list.lua:7: Tests list test 2\n' .. + 'spec/cl_list.lua:10: Tests list test 3\n' + assert.is_equal(normpath(expected), result) + end) + + it('tests running with --lpath specified', function() + local success, errcnt = executeBusted('--lpath="spec/?.lua" spec/cl_lua_path.lua') + assert.is_true(success) + assert.is_equal(0, errcnt) end) it('tests running with --lang specified', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=cl_success.lua$ --lang=en') + local success, errcnt = executeBusted('--pattern=cl_success.lua$ --lang=en') assert.is_true(success) - assert.is_equal(0, exitcode) - success, exitcode = execute('bin/busted --pattern=cl_success --lang=not_found_here') + assert.is_equal(0, errcnt) + success, errcnt = executeBusted('--pattern=cl_success --lang=not_found_here') assert.is_false(success) - assert.is_equal(1, exitcode) -- busted errors out on non-available language - error_end() end) it('tests running with --version specified', function() - local success, exitcode - success, exitcode = execute('bin/busted --version') + local success, errcnt = executeBusted('--version') assert.is_true(success) - assert.is_equal(0, exitcode) + assert.is_equal(0, errcnt) end) it('tests running with --help specified', function() - local success, exitcode - success, exitcode = execute('bin/busted --help') - assert.is_true(success) - assert.is_equal(0, exitcode) + local success, errcnt = executeBusted('--help') + assert.is_false(success) end) it('tests running a non-compiling testfile', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=cl_compile_fail.lua$') + local success, errcnt = executeBusted('--pattern=cl_compile_fail.lua$') assert.is_false(success) - assert.is_equal(1, exitcode) - error_end() + assert.is_equal(1, errcnt) end) it('tests running a testfile throwing errors when being run', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=cl_execute_fail.lua$') + local success, errcnt = executeBusted('--pattern=cl_execute_fail.lua$') assert.is_false(success) - assert.is_equal(1, exitcode) - error_end() + assert.is_equal(1, errcnt) end) it('tests running with --output specified', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=cl_success.lua$ --output=TAP') + local success, errcnt = executeBusted('--pattern=cl_success.lua$ --output=TAP') assert.is_true(success) - assert.is_equal(0, exitcode) - success, exitcode = execute('bin/busted --pattern=cl_two_failures.lua$ --output=not_found_here') + assert.is_equal(0, errcnt) + success, errcnt = executeBusted('--pattern=cl_two_failures.lua$ --output=not_found_here') assert.is_false(success) - assert.is_equal(3, exitcode) -- outputter missing, defaults to default outputter +1 error - error_end() + assert.is_equal(2, errcnt) + end) + + it('tests running with --output specified with module in lua path', function() + local success, errcnt = executeBusted('--pattern=cl_success.lua$ --output=busted.outputHandlers.TAP') + assert.is_true(success) + assert.is_equal(0, errcnt) end) it('tests no tests to exit with a fail-exitcode', function() - local success, exitcode - error_start() - success, exitcode = execute('bin/busted --pattern=this_filename_does_simply_not_exist$') + local success, exitcode = execute(busted_cmd ..' --pattern=this_filename_does_simply_not_exist$') assert.is_false(success) assert.is_equal(1, exitcode) - error_end() end) - + it('can switch interpreters', function() + local lua_exe = normpath(path.is_windows and 'spec/lua.bat' or 'spec/lua.lua') + local success, errcnt, out = executeBusted('--lua=' .. lua_exe .. ' spec/cl_success.lua') + assert.is_true(success) + assert.is_equal(0, errcnt) + assert.equal('bin/busted --ignore-lua --lua=' .. lua_exe .. ' spec/cl_success.lua\n', out) + end) end) ---[[ --TODO: uncomment this failing test and fix it describe('Tests failing tests through the commandline', function() - local old_ditch - before_each(function() - old_ditch, ditch = ditch, '' -- dump this test output only + it('tests failing setup/before_each/after_each/teardown functions', function() + local success, errcnt = executeBusted('--pattern=cl_failing_support.lua$') + assert.is_false(success) + assert.is_equal(16, errcnt) end) - after_each(function() - ditch = old_ditch + + it('tests failing support functions as errors', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_failing_support.lua$') + local _, numErrors = result:gsub('Error %-> .-\n','') + assert.is_equal(16, numErrors) end) - - it('tests failing setup/before_each/after_each/teardown functions', function() - local success, exitcode - error_start() - success, exitcode = execute('busted --pattern=cl_failing_support.lua$') +end) + +describe('Test busted running standalone', function() + it('tests running with --tags specified', function() + local success, errcnt = executeLua('spec/cl_standalone.lua') + assert.is_false(success) + assert.is_equal(3, errcnt) + success, errcnt = executeLua('spec/cl_standalone.lua --tags=tag1') + assert.is_false(success) + assert.is_equal(2, errcnt) + success, errcnt = executeLua('spec/cl_standalone.lua --tags=tag1,tag2') + assert.is_false(success) + assert.is_equal(3, errcnt) + end) + + it('tests running with --exclude-tags specified', function() + local success, errcnt = executeLua('spec/cl_standalone.lua --exclude-tags=tag1,tag2') + assert.is_true(success) + assert.is_equal(0, errcnt) + success, errcnt = executeLua('spec/cl_standalone.lua --exclude-tags=tag2') + assert.is_false(success) + assert.is_equal(2, errcnt) + end) + + it('tests running with --tags and --exclude-tags specified', function () + local success, errcnt = executeLua('spec/cl_standalone.lua --tags=tag1 --exclude-tags=tag1') + assert.is_false(success) + success, errcnt = executeLua('spec/cl_standalone.lua --tags=tag3 --exclude-tags=tag4') + assert.is_true(success) + end) + + it('tests running with --helper specified', function () + local success, errcnt = executeLua('spec/cl_standalone.lua --helper=spec/cl_helper_script.lua -Xhelper "--fail-teardown,--fail-after-each"') + assert.is_false(success) + assert.is_equal(9, errcnt) + end) + + it('tests running with --version specified', function() + local success, errcnt = executeLua('spec/cl_standalone.lua --version') + assert.is_true(success) + assert.is_equal(0, errcnt) + end) + + it('tests running with --help specified', function() + local success, errcnt = executeLua('spec/cl_standalone.lua --help') + assert.is_false(success) + end) + + it('tests running via stdin', function() + local success, errcnt = executeLua('< spec/cl_standalone.lua') assert.is_false(success) - assert.is_equal(8, exitcode) - error_end() + assert.is_equal(3, errcnt) + end) +end) + +describe('Test busted command-line runner', function() + it('runs standalone spec', function() + local success, errcnt = executeBusted('spec/cl_standalone.lua') + assert.is_false(success) + assert.is_equal(3, errcnt) + success, errcnt = executeBusted('--tags=tag1 spec/cl_standalone.lua') + assert.is_false(success) + assert.is_equal(2, errcnt) + success, errcnt = executeBusted('--tags=tag1,tag2 spec/cl_standalone.lua') + assert.is_false(success) + assert.is_equal(3, errcnt) + end) +end) + +describe('Tests distinguish between errors and failures', function() + it('by detecting errors as test errors', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_errors.lua$ --tags=testerr') + local errmsg = result:match('(Error %-> .-)\n') + assert.is_truthy(errmsg) + end) + + it('by detecting assert failures as test failures', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_two_failures.lua$') + local failmsg = result:match('(Failure %-> .-)\n') + assert.is_truthy(failmsg) + end) + + it('by detecting Lua runtime errors as test errors', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_errors.lua$ --tags=luaerr') + local failmsg = result:match('(Error %-> .-)\n') + assert.is_truthy(failmsg) + end) +end) + +describe('Tests stack trackback', function() + it('when throwing an error', function() + local _, _, result = executeBusted('--verbose --pattern=cl_errors.lua$ --tags=testerr') + local errmsg = result:match('(stack traceback:.*)\n') + local expected = [[stack traceback: + spec/cl_errors.lua:6: in function +]] + assert.is_equal(normpath(expected), errmsg) + end) + + it('when assertion fails', function() + local _, _, result = executeBusted('--verbose --pattern=cl_two_failures.lua$ --tags=err1') + local errmsg = result:match('(stack traceback:.*)\n') + local expected = [[stack traceback: + spec/cl_two_failures.lua:6: in function +]] + assert.is_equal(normpath(expected), errmsg) + end) + + it('when Lua runtime error', function() + local _, _, result = executeBusted('--verbose --pattern=cl_errors.lua$ --tags=luaerr') + local errmsg = result:match('(stack traceback:.*)\n') + local expected = [[stack traceback: + spec/cl_errors.lua:11: in function +]] + assert.is_equal(normpath(expected), errmsg) + end) +end) + +describe('Tests error messages through the command line', function() + it('when throwing errors in a test', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_errors.lua$ --tags=testerr') + local err = result:match('(Error %-> .-)\n') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expectedErr = "Error -> spec/cl_errors.lua @ 5" + local expectedMsg = "spec/cl_errors.lua:6: force an error" + assert.is_equal(normpath(expectedErr), err) + assert.is_equal(normpath(expectedMsg), errmsg) + end) + + it('when throwing an error table', function() + local _, _, result = executeBusted('--output=plainTerminal --tags=table --pattern=cl_error_messages.lua$') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expected = 'spec/cl_error_messages.lua:5: {' + assert.is_equal(normpath(expected), errmsg) + end) + + it('when throwing a nil error', function() + local _, _, result = executeBusted('--output=plainTerminal --tags=nil --pattern=cl_error_messages.lua$') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expected = 'spec/cl_error_messages.lua:9: Nil error' + assert.is_equal(normpath(expected), errmsg) + end) + + it('when throwing an error table with __tostring', function() + local _, _, result = executeBusted('--output=plainTerminal --tags=tostring --pattern=cl_error_messages.lua$') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expected = 'spec/cl_error_messages.lua:17: {}' + assert.is_equal(normpath(expected), errmsg) + end) + + it('when throwing after a pcall', function() + local _, _, result = executeBusted('--output=plainTerminal --tags=pcall --pattern=cl_error_messages.lua$') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expected = 'spec/cl_error_messages.lua:22: error after pcall' + assert.is_equal(normpath(expected), errmsg) + end) + + it('when running a non-compiling testfile', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_compile_fail.lua$') + local errmsg = result:match('(Error %-> .-:%d+:) ') + local expected = "Error -> spec/cl_compile_fail.lua:3:" + assert.is_equal(normpath(expected), errmsg) + end) + + it('when a testfile throws errors', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_execute_fail.lua$') + local err = result:match('(Error %-> .-)\n') + local errmsg = result:match('\n(spec[/\\]cl_execute_fail%.lua:%d+:.-)\n') + local expectedErr = 'Error -> spec/cl_execute_fail.lua @ 4' + local expectedMsg = 'spec/cl_execute_fail.lua:4: This compiles fine, but throws an error when being run' + assert.is_equal(normpath(expectedErr), err) + assert.is_equal(normpath(expectedMsg), errmsg) + end) + + it('when output library not found', function() + local _, _, result, rerr = executeBusted('--pattern=cl_two_failures.lua$ --output=not_found_here 2>&1') + local errmsg = rerr:match('(.-)\n') + local expected = 'busted: error: Cannot load output library: not_found_here' + assert.is_equal(expected, errmsg) + end) + + it('when helper script not found', function() + local _, _, result, rerr = executeBusted('--output=plainTerminal --pattern=cl_two_failures.lua$ --helper=not_found_here 2>&1') + local err = result:match('Error %-> .-:%d+: (.-)\n') + local errmsg = rerr:match('(.-)\n') + local expectedErr = "module 'not_found_here' not found:" + local expectedMsg = 'busted: error: Cannot load helper script: not_found_here' + assert.is_equal(expectedErr, err) + assert.is_equal(expectedMsg, errmsg) + end) + + it('when helper lua script not found', function() + local _, _, result, rerr = executeBusted('--output=plainTerminal --pattern=cl_two_failures.lua$ --helper=not_found_here.lua 2>&1') + local err = result:match('Error %-> (.-)\n') + local errmsg = rerr:match('(.-)\n') + local expectedErr = 'cannot open not_found_here.lua: No such file or directory' + local expectedMsg = 'busted: error: Cannot load helper script: not_found_here.lua' + assert.is_equal(expectedErr, err) + assert.is_equal(expectedMsg, errmsg) + end) + + it('when test file not found', function() + local _, _, result = executeBusted('--output=plainTerminal does_not_exist.lua') + local errmsg = result:match('Error %-> (.-)\n') + local expected = 'Cannot find file or directory: does_not_exist.lua' + assert.is_equal(expected, errmsg) + end) + + it('when test directory not found', function() + local _, _, result = executeBusted('--output=plainTerminal does_not_exist') + local errmsg = result:match('Error %-> (.-)\n') + local expected = 'Cannot find file or directory: does_not_exist' + assert.is_equal(expected, errmsg) + end) + + it('when no test files matching Lua pattern', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=this_filename_does_simply_not_exist$') + local errmsg = result:match('Error %-> (.-)\n') + local expected = 'No test files found matching Lua pattern: this_filename_does_simply_not_exist$' + assert.is_equal(expected, errmsg) + end) + + it('when __gc metamethod throws error', function() + local noGC = xpcall(function() + setmetatable({}, { __gc = function() error('gc error') end}) + collectgarbage() + collectgarbage() + end, function() end) + if noGC then pending('no __gc metamethod support') end + + local success, errcnt, result = executeBusted('--pattern=cl_gc_error.lua$') + local err = result:match('Error %-> (.-)\n') + local errmsg = result:match('\n([^\n]-%(spec[/\\].-%))\n') + local expected = 'error in __gc metamethod (spec/cl_gc_error.lua:5: gc error)' + assert.is_false(success) + assert.is_equal(1, errcnt) + assert.is_truthy(err) + assert.is_equal(normpath(expected), errmsg) + end) +end) + +local has_moon = pcall(require, 'moonscript') +local describe_moon = (has_moon and describe or pending) + +describe_moon('Tests moonscript error messages through the command line', function() + it('when assertion fails', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_moonscript_error_messages.moon$ --tags=fail') + local err = result:match('(Failure %-> .-)\n') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expectedErr = "Failure -> spec/cl_moonscript_error_messages.moon @ 4" + local expectedMsg = "spec/cl_moonscript_error_messages.moon:5: Expected objects to be equal." + assert.is_equal(normpath(expectedErr), err) + assert.is_equal(normpath(expectedMsg), errmsg) + end) + + it('when throwing string errors', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_moonscript_error_messages.moon$ --tags=string') + local err = result:match('(Error %-> .-)\n') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expectedErr = "Error -> spec/cl_moonscript_error_messages.moon @ 16" + local expectedMsg = "spec/cl_moonscript_error_messages.moon:17: error message" + assert.is_equal(normpath(expectedErr), err) + assert.is_equal(normpath(expectedMsg), errmsg) + end) + + it('when throwing an error table', function() + local _, _, result = executeBusted('--output=plainTerminal --tags=table --pattern=cl_moonscript_error_messages.moon$') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expected = 'spec/cl_moonscript_error_messages.moon:9: {' + assert.is_equal(normpath(expected), errmsg) + end) + + it('when throwing a nil error', function() + local _, _, result = executeBusted('--output=plainTerminal --tags=nil --pattern=cl_moonscript_error_messages.moon$') + local errmsg = result:match('\n(spec[/\\].-)\n') + local expected = 'spec/cl_moonscript_error_messages.moon:13: Nil error' + assert.is_equal(normpath(expected), errmsg) + end) +end) + +describe('Tests pending through the commandline', function() + it('skips tests inside pending scope', function() + local success, errcnt + success, errcnt = executeBusted('--pattern=cl_pending.lua$') + assert.is_true(success) + assert.is_equal(0, errcnt) + end) + + it('detects tests as pending', function() + local _, _, result = executeBusted('--output=plainTerminal --pattern=cl_pending.lua$') + local line1 = result:match('.-\n') + local _, pendingDots = line1:gsub('%.', '') + local _, numPending = result:gsub('Pending %-> .-\n', '') + assert.is_equal(2, pendingDots) + assert.is_equal(2, numPending) + end) + + it('--suppress-pending option is honored', function() + local _, _, result = executeBusted('--output=plainTerminal --suppress-pending --pattern=cl_pending.lua$') + local line1 = result:match('.-\n') + local _, pendingDots = line1:gsub('%.', '') + local _, numPending = result:gsub('Pending %-> .-\n', '') + assert.is_equal(0, pendingDots) + assert.is_equal(0, numPending) + end) +end) + +describe('Tests random seed through the commandline', function() + it('test seed value', function() + local success, errcnt + success, errcnt = executeBusted('--seed=12345 --pattern=cl_random_seed.lua$') + assert.is_true(success) + assert.is_equal(0, errcnt) + end) + + it('test invalid seed value exits with error', function() + local success, errcnt + success, errcnt = executeBusted('--seed=abcd --pattern=cl_random_seed.lua$') + assert.is_false(success) + end) + + it('test failure outputs random seed value', function() + local _, _, result = executeBusted('--seed=789 --pattern=cl_random_seed.lua$') + local seed = result:match('Random seed: (%d+)\n') + assert.is_equal(789, tonumber(seed)) + end) + + it('test non-randomized failure does not output seed value', function() + local _, _, result = executeBusted('--seed=789 --pattern=cl_two_failures.lua$') + local seed = result:match('Random seed:') + assert.is_equal(nil, seed) + end) +end) + +describe('Tests shuffle commandline option', function() + for _, opt in ipairs({ '--shuffle', '--shuffle-tests' }) do + it('forces test shuffling for non-randomized tests, ' .. opt, function() + local success, errcnt = executeBusted(opt .. ' --pattern=cl_randomize.lua$') + assert.is_true(success) + assert.is_equal(0, errcnt) + end) + end +end) + +describe('Tests sort commandline option', function() + for _, opt in ipairs({ '--sort', '--sort-tests' }) do + it('sorts tests by name, ' .. opt, function() + local success, errcnt = executeBusted(opt .. ' --pattern=cl_sort.lua$') + assert.is_true(success) + assert.is_equal(0, errcnt) + end) + end +end) + +describe('Tests repeat commandline option', function() + it('forces tests to repeat n times', function() + local success, errcnt = executeBusted('--repeat=2 --pattern=cl_two_failures.lua$') + assert.is_false(success) + assert.is_equal(4, errcnt) + end) + + it('exits with error when repeat is invalid', function() + local success, errcnt + success, errcnt = executeBusted('--repeat=abc --pattern=cl_success.lua$') + assert.is_false(success) + end) +end) + +describe('Tests no-keep-going commandline option', function() + it('skips all tests after first error', function() + local success, errcnt = executeBusted('--no-keep-going --pattern=cl_two_failures.lua$') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) +end) + +describe('Tests no-recursive commandline option', function() + it('does not run any tests in subdirectories', function() + local success, errcnt = executeBusted('--no-recursive --pattern=cl_two_failures.lua$ .') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) +end) + +describe('Tests no-auto-insulate commandline option', function() + it('does not insulate test files', function() + local success, errcnt = executeBusted('--no-auto-insulate --pattern=insulate_file.*.lua$') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) +end) + +describe('Tests Xoutput commandline option', function() + it('forwards no options to output handler when no options specified', function() + local _, _, result = executeBusted('--output=spec/cl_output_handler.lua --pattern=cl_success.lua$') + local status = result:match('^%[(.-)]') + assert.is_equal(' success', status) + end) + + it('forwards single option to output handler', function() + local _, _, result = executeBusted('--output=spec/cl_output_handler.lua -Xoutput "--time" --pattern=cl_success.lua$') + local timestamp = result:match('^%[(.-)]') + assert.is_equal('Fri Jan 02 10:17:36 1970', timestamp) + end) + + it('forwards multiple options to output handler', function() + local _, _, result = executeBusted('--output=spec/cl_output_handler.lua -Xoutput "--time,--time-format=!%H:%M:%S" --pattern=cl_success.lua$') + local timestamp = result:match('^%[(.-)]') + assert.is_equal('10:17:36', timestamp) + end) + + it('forwards multiple options to output handler using multiple -Xoutput', function() + local _, _, result = executeBusted('--output=spec/cl_output_handler.lua -Xoutput "--time" -Xoutput "--time-format=!%H:%M:%S" --pattern=cl_success.lua$') + local timestamp = result:match('^%[(.-)]') + assert.is_equal('10:17:36', timestamp) + end) +end) + +describe('Tests Xhelper commandline option', function() + it('forwards no options to helper script when no options specified', function() + local success = executeBusted('--helper=spec/cl_helper_script.lua --pattern=cl_success.lua$') + assert.is_true(success) + end) + + it('forwards single option to helper script', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-before-each" --pattern=cl_success.lua$') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) + + it('forwards multiple options to helper script', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-before-each,--fail-after-each" --pattern=cl_success.lua$') + assert.is_false(success) + assert.is_equal(2, errcnt) + end) + + it('forwards multiple options to helper script using multiple -Xhelper', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-before-each" -Xhelper "--fail-after-each" --pattern=cl_success.lua$') + assert.is_false(success) + assert.is_equal(2, errcnt) + end) +end) + +describe('Tests helper script', function() + it('can add setup to test suite', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-setup" --pattern=cl_two_failures.lua$') + assert.is_false(success) + assert.is_equal(1, errcnt) + end) + + it('can add teardown to test suite', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-teardown" --pattern=cl_two_failures.lua$') + assert.is_false(success) + assert.is_equal(3, errcnt) + end) + + it('runs setup/teardown for mutiple runs', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-setup,--fail-teardown" --pattern=cl_success.lua$ --repeat=2') + assert.is_false(success) + assert.is_equal(4, errcnt) + end) + + it('runs setup/teardown for mutiple runs with --lazy', function() + local success, errcnt = executeBusted('--lazy --helper=spec/cl_helper_script.lua -Xhelper "--fail-setup,--fail-teardown" --pattern=cl_success.lua$ --repeat=2') + assert.is_false(success) + assert.is_equal(4, errcnt) + end) + + it('can subscribe to suite start/reset', function() + local success, errcnt, result = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-suite-start,--fail-suite-reset" --pattern=cl_success.lua$ --repeat=2') + assert.is_false(success) + assert.is_equal(3, errcnt) + end) + + it('can subscribe to suite end', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-suite-end" --pattern=cl_success.lua$') + assert.is_false(success) + assert.is_equal(0, errcnt) + end) + + it('can subscribe to file start/end', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-file-start,--fail-file-end" --pattern=cl_success.lua$') + assert.is_false(success) + assert.is_equal(2, errcnt) + end) + + it('can subscribe to describe start/end', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-describe-start,--fail-describe-end" --pattern=cl_success.lua$') + assert.is_false(success) + assert.is_equal(2, errcnt) + end) + + it('can subscribe to test start/end', function() + local success, errcnt = executeBusted('--helper=spec/cl_helper_script.lua -Xhelper "--fail-test-start,--fail-test-end" --pattern=cl_two_failures.lua$') + assert.is_false(success) + assert.is_equal(4, errcnt) + end) +end) + +describe('Tests strict with helper script', function() + it('does not detect any errors', function() + local success = executeBusted('--helper=spec/cl_helper_script.lua --pattern=strict_spec.lua$') + assert.is_true(success) + end) +end) + +describe('Tests execute option', function() + it('will run the specified statement', function() + local success, errcnt, result = executeBusted('-e "print(\\\"hello world\\\")" --pattern=cl_success.lua$') + assert.is_true(success) + assert.is_equal(0, errcnt) + assert.is_equal('hello world', result:match('(.-)\n')) end) end) ---]] diff --git a/spec/cl_standalone.lua b/spec/cl_standalone.lua new file mode 100644 index 00000000..883d605e --- /dev/null +++ b/spec/cl_standalone.lua @@ -0,0 +1,29 @@ +-- supporting testfile; belongs to 'cl_spec.lua' + +require 'busted.runner'() + +describe('Tests busted standalone with command-line options', function() + + it('is a test with a tag #tag1', function() + -- works by counting failure + error('error 1 on tag1') + end) + + it('is a test with a tag #tag1', function() + -- works by counting failure + error('error 2 on tag1') + end) + + it('is a test with a tag #tag2', function() + -- works by counting failure + error('error on tag2') + end) + + it('is a test with a tag #tag3', function() + -- nothing here, makes it succeed + end) + + it('is a test with two tags #tag3 #tag4', function () + -- Always succeed + end) +end) diff --git a/spec/cl_tags.lua b/spec/cl_tags.lua index 269e64c6..0aeb6fed 100644 --- a/spec/cl_tags.lua +++ b/spec/cl_tags.lua @@ -7,12 +7,12 @@ describe('Tests the busted command-line options', function() error('error 1 on tag1') end) - it('is a test with a tag #tag1', function() + spec('is a test with a tag #tag1', function() -- works by counting failure error('error 2 on tag1') end) - it('is a test with a tag #tag2', function() + test('is a test with a tag #tag2', function() -- works by counting failure error('error on tag2') end) @@ -26,3 +26,24 @@ describe('Tests the busted command-line options', function() end) end) +describe('Tests describe with a tag #dtag1', function() + before_each(function() + error('error in before_each on dtag1') + end) + + after_each(function() + error('error in after_each on dtag1') + end) + + it('is a test inside describe', function() + end) + + it('is another test inside describe', function() + end) +end) + +context('Tests context with a tag #dtag2', function() + setup(function() + error('error in setup on dtag2') + end) +end) diff --git a/spec/cl_test_module.lua b/spec/cl_test_module.lua new file mode 100644 index 00000000..5db7dc4e --- /dev/null +++ b/spec/cl_test_module.lua @@ -0,0 +1 @@ +return 'test module' diff --git a/spec/cl_two_failures.lua b/spec/cl_two_failures.lua index 500347c8..083ec844 100644 --- a/spec/cl_two_failures.lua +++ b/spec/cl_two_failures.lua @@ -2,12 +2,12 @@ describe('Runs 2 failing tests', function() - it('is failing test 1', function() - error('error on test 1') + it('is failing test 1 #err1', function() + assert(false, 'failed on test 1') end) - it('is failing test 2', function() - error('error on test 2') + it('is failing test 2 #err2', function() + assert(false, 'failed on test 2') end) end) diff --git a/spec/core_spec.lua b/spec/core_spec.lua index 1aff33d2..a7267408 100644 --- a/spec/core_spec.lua +++ b/spec/core_spec.lua @@ -1,12 +1,16 @@ --ensure environment is set up +assert(type(file) == 'nil') assert(type(describe) == 'function') assert(type(context) == 'function') assert(type(it) == 'function') +assert(type(spec) == 'function') +assert(type(test) == 'function') assert(type(before_each) == 'function') assert(type(after_each) == 'function') assert(type(spy) == 'table') assert(type(stub) == 'table') -assert(type(mock) == 'function') +assert(type(mock) == 'table') +assert(type(match) == 'table') assert(type(assert) == 'table') describe('Before each', function() @@ -228,8 +232,33 @@ end) describe('tests environment', function() global = 'global' + setup(function() + globalsetup = 'globalsetup' + end) + + teardown(function() + globalteardown = 'globalteardown' + end) + + before_each(function() + globalbefore = 'globalbefore' + end) + + after_each(function() + globalafter = 'globalafter' + end) + + it('cannot access globals which have not been created yet', function() + assert.equal(nil, globalafter) + assert.equal(nil, globalteardown) + notglobal = 'notglobal' + end) + it('can access globals', function() assert.equal('global', global) + assert.equal('globalsetup', globalsetup) + assert.equal('globalbefore', globalbefore) + assert.equal('globalafter', globalafter) notglobal = 'notglobal' end) @@ -237,6 +266,15 @@ describe('tests environment', function() assert.equal(nil, notglobal) end) + describe('can access parent globals', function() + it('from child', function() + assert.equal('global', global) + assert.equal('globalsetup', globalsetup) + assert.equal('globalbefore', globalbefore) + assert.equal('globalafter', globalafter) + end) + end) + describe('cannot access globals set in children', function() it('has a global', function() notglobal = 'notglobal' @@ -245,3 +283,256 @@ describe('tests environment', function() assert.are.equal(notglobal, nil) end) end) + +describe('tests clean environment', function() + it('globals in previous describe are not available', function() + assert.is_nil(global) + assert.is_nil(globalsetup) + assert.is_nil(globalbefore) + assert.is_nil(globalafter) + assert.is_nil(globalteardown) + end) +end) + +describe 'tests syntactic sugar' (function() + it 'works' (function() + assert(true) + end) +end) + +describe('tests aliases', function() + local test_val = 0 + + context('runs context alias', function() + setup(function() + test_val = test_val + 1 + end) + + before_each(function() + test_val = test_val + 1 + end) + + after_each(function() + test_val = test_val + 1 + end) + + teardown(function() + test_val = test_val + 1 + end) + + spec('runs spec alias', function() + test_val = test_val + 1 + end) + + test('runs test alias', function() + test_val = test_val + 1 + end) + end) + + it('checks aliases were executed', function() + assert.is_equal(8, test_val) + end) +end) + +describe('tests unsupported functions', function() + it('it block does not have file executor', function() + assert.is_nil(file) + end) + + it('it block throws error on describe/context', function() + assert.has_error(describe, "'describe' not supported inside current context block") + assert.has_error(context, "'context' not supported inside current context block") + end) + + it('it block throws error on insulate/expose', function() + assert.has_error(insulate, "'insulate' not supported inside current context block") + assert.has_error(expose, "'expose' not supported inside current context block") + end) + + it('it block throws error on it/spec/test', function() + assert.has_error(it, "'it' not supported inside current context block") + assert.has_error(spec, "'spec' not supported inside current context block") + assert.has_error(test, "'test' not supported inside current context block") + end) + + it('it block throws error on setup/before_each/after_each/teardown', function() + assert.has_error(setup, "'setup' not supported inside current context block") + assert.has_error(before_each, "'before_each' not supported inside current context block") + assert.has_error(after_each, "'after_each' not supported inside current context block") + assert.has_error(teardown, "'teardown' not supported inside current context block") + end) + + it('it block throws error on lazy/strict setup/teardown', function() + assert.has_error(lazy_setup, "'lazy_setup' not supported inside current context block") + assert.has_error(lazy_teardown, "'lazy_teardown' not supported inside current context block") + assert.has_error(strict_setup, "'strict_setup' not supported inside current context block") + assert.has_error(strict_teardown, "'strict_teardown' not supported inside current context block") + end) + + it('it block throws error on randomize', function() + assert.has_error(randomize, "'randomize' not supported inside current context block") + end) + + it('finaly block throws error on pending', function() + finally(function() + assert.has_error(pending, "'pending' not supported inside current context block") + end) + end) +end) + +describe('tests unsupported functions in setup/before_each/after_each/teardown', function() + local function testUnsupported() + assert.is_nil(file) + assert.is_nil(finally) + assert.has_error(randomize, "'randomize' not supported inside current context block") + + assert.has_error(describe, "'describe' not supported inside current context block") + assert.has_error(context, "'context' not supported inside current context block") + + assert.has_error(insulate, "'insulate' not supported inside current context block") + assert.has_error(expose, "'expose' not supported inside current context block") + + assert.has_error(pending, "'pending' not supported inside current context block") + + assert.has_error(it, "'it' not supported inside current context block") + assert.has_error(spec, "'spec' not supported inside current context block") + assert.has_error(test, "'test' not supported inside current context block") + + assert.has_error(setup, "'setup' not supported inside current context block") + assert.has_error(before_each, "'before_each' not supported inside current context block") + assert.has_error(after_each, "'after_each' not supported inside current context block") + assert.has_error(teardown, "'teardown' not supported inside current context block") + + assert.has_error(lazy_setup, "'lazy_setup' not supported inside current context block") + assert.has_error(lazy_teardown, "'lazy_teardown' not supported inside current context block") + assert.has_error(strict_setup, "'strict_setup' not supported inside current context block") + assert.has_error(strict_teardown, "'strict_teardown' not supported inside current context block") + end + + setup(testUnsupported) + teardown(testUnsupported) + before_each(testUnsupported) + after_each(testUnsupported) + + it('tests nothing, all tests performed by support functions', function() + end) +end) + +describe('tests strict setup/teardown', function() + local setup_count = 0 + local teardown_count = 0 + + describe('in describe with no tests', function() + strict_setup(function() + setup_count = setup_count + 1 + end) + + strict_teardown(function() + teardown_count = teardown_count + 1 + end) + end) + + it('executes setup/teardown in previous block with no tests', function() + assert.is_equal(1, setup_count) + assert.is_equal(1, teardown_count) + end) +end) + +describe('tests lazy setup/teardown not run if no tests found in block', function() + lazy_setup(function() + assert(false, 'setup should not execute since no tests') + end) + + lazy_teardown(function() + assert(false, 'teardown should not execute since no tests') + end) +end) + +describe('tests lazy setup/teardown in describe with no tests', function() + local setup_count = 0 + local teardown_count = 0 + + describe('with nested describe with no tests', function() + lazy_setup(function() + setup_count = setup_count + 1 + end) + + lazy_teardown(function() + teardown_count = teardown_count + 1 + end) + + describe('with inner nested describe with no tests', function() + lazy_setup(function() + setup_count = setup_count + 1 + end) + + lazy_teardown(function() + teardown_count = teardown_count + 1 + end) + end) + end) + + it('does not run setup/teardown', function() + assert.is_equal(0, setup_count) + assert.is_equal(0, teardown_count) + end) +end) + +describe('tests lazy setup/teardown with nested tests', function() + local setup_count = 0 + local teardown_count = 0 + + lazy_setup(function() + setup_count = setup_count + 1 + end) + + lazy_teardown(function() + teardown_count = teardown_count + 1 + end) + + describe('nested describe with tests', function() + lazy_setup(function() + setup_count = setup_count + 1 + end) + + lazy_teardown(function() + teardown_count = teardown_count + 1 + end) + + it('runs all setups', function() + assert.is_equal(2, setup_count) + end) + + it('runs setups only once', function() + assert.is_equal(2, setup_count) + end) + + it('runs teardown after all tests complete', function() + assert.is_equal(0, teardown_count) + end) + end) + + describe('second nested describe', function() + lazy_teardown(function() + teardown_count = teardown_count + 1 + end) + + it('verify teardown ran after previous describe completes', function() + assert.is_equal(1, teardown_count) + end) + end) + + describe('another nested describe with tests', function() + lazy_setup(function() + setup_count = setup_count + 1 + end) + + it('runs setup for new describe', function() + assert.is_equal(3, setup_count) + end) + + it('verify teardown ran after previous describe completes', function() + assert.is_equal(2, teardown_count) + end) + end) +end) diff --git a/spec/execution_order_sync_spec.lua b/spec/execution_order_sync_spec.lua index c062be7f..ecb475e4 100644 --- a/spec/execution_order_sync_spec.lua +++ b/spec/execution_order_sync_spec.lua @@ -35,15 +35,38 @@ describe('before_each after_each egg test', function() assert.equal(egg,'SbB1AabB') egg = egg..'2' end) + + describe('jkl', function() + setup(function() + egg = egg..'s' + end) + + teardown(function() + egg = egg..'t' + end) + + before_each(function() + egg = egg..'E' + end) + + after_each(function() + egg = egg..'F' + end) + + it('3', function() + assert.equal(egg,'SbB1AabB2AasbBE') + egg = egg..'3' + end) + end) end) - it('3', function() - assert.equal(egg,'SbB1AabB2Aab') - egg = egg..'3' + it('4', function() + assert.equal(egg,'SbB1AabB2AasbBE3FAatb') + egg = egg..'4' end) end) -it('4', function() - assert.equal(egg,'SbB1AabB2Aab3aT') +it('5', function() + assert.equal(egg,'SbB1AabB2AasbBE3FAatb4aT') end) diff --git a/spec/export_spec.lua b/spec/export_spec.lua new file mode 100644 index 00000000..366ecad5 --- /dev/null +++ b/spec/export_spec.lua @@ -0,0 +1,95 @@ + +describe('tests require "busted"', function() + local describe = describe + local context = context + local insulate = insulate + local expose = expose + local it = it + local pending = pending + local spec = spec + local test = test + local setup = setup + local teardown = teardown + local before_each = before_each + local after_each = after_each + local lazy_setup = lazy_setup + local lazy_teardown = lazy_teardown + local strict_setup = strict_setup + local strict_teardown = strict_teardown + + it('does not export init', function() + assert.is_nil(require 'busted'.init) + end) + + it('does not export file executor', function() + assert.is_nil(require 'busted'.file) + end) + + it('exports describe/it/pending', function() + assert.is_equal(describe, require 'busted'.describe) + assert.is_equal(it, require 'busted'.it) + assert.is_equal(pending, require 'busted'.pending) + end) + + it('exports aliases', function() + assert.is_equal(context, require 'busted'.context) + assert.is_equal(insulate, require 'busted'.insulate) + assert.is_equal(expose, require 'busted'.expose) + assert.is_equal(spec, require 'busted'.spec) + assert.is_equal(test, require 'busted'.test) + end) + + it('exports support functions', function() + assert.is_equal(setup, require 'busted'.setup) + assert.is_equal(teardown, require 'busted'.teardown) + assert.is_equal(lazy_setup, require 'busted'.lazy_setup) + assert.is_equal(lazy_teardown, require 'busted'.lazy_teardown) + assert.is_equal(strict_setup, require 'busted'.strict_setup) + assert.is_equal(strict_teardown, require 'busted'.strict_teardown) + assert.is_equal(before_each, require 'busted'.before_each) + assert.is_equal(after_each, require 'busted'.after_each) + end) + + it('exports assert, mocks, and matchers', function() + assert.is_equal(assert, require 'busted'.assert) + assert.is_equal(spy, require 'busted'.spy) + assert.is_equal(mock, require 'busted'.mock) + assert.is_equal(stub, require 'busted'.stub) + assert.is_equal(match, require 'busted'.match) + end) + + it('exports publish/subscribe', function() + local foo + local publish = require 'busted'.publish + local subscribe = require 'busted'.subscribe + local unsubscribe = require 'busted'.unsubscribe + local sub = subscribe({'export_test'}, function(...) foo = {...} end) + publish({'export_test'}, 'value1', 'value2' ) + local unsub = unsubscribe(sub.id, {'export_test'}) + publish({'export_test'}, 'new_value1', 'new_value2') + assert.is_same({'value1', 'value2'}, foo) + assert.is_equal(sub, unsub) + end) + + it('exports other functions/variables', function() + assert.is_function(require 'busted'.bindfenv) + assert.is_function(require 'busted'.fail) + assert.is_function(require 'busted'.gettime) + assert.is_function(require 'busted'.monotime) + assert.is_function(require 'busted'.sleep) + assert.is_function(require 'busted'.parent) + assert.is_function(require 'busted'.children) + assert.is_string(require 'busted'.version) + end) + + it('functions cannot be overwritten', function() + local foo = function() assert(false) end + assert.has_error(function() require 'busted'.it = foo end) + assert.is_equal(it, require 'busted'.it) + end) + + it('cannot add new fields', function() + local bar = function() assert(false) end + assert.has_error(function() require 'busted'.foo = bar end) + end) +end) diff --git a/spec/expose_file1.lua b/spec/expose_file1.lua new file mode 100644 index 00000000..4df74ee9 --- /dev/null +++ b/spec/expose_file1.lua @@ -0,0 +1,9 @@ +expose('Tests expose from file root', function() + pl = require 'pl' + _G.global_var = 'this global is in _G' + + it('loads global environment with "List"', function() + assert.is_not_nil(pl) + assert.is_not_nil(List) + end) +end) diff --git a/spec/expose_file2.lua b/spec/expose_file2.lua new file mode 100644 index 00000000..63fd5496 --- /dev/null +++ b/spec/expose_file2.lua @@ -0,0 +1,13 @@ +describe('Tests environment exposed from previous file', function() + it('global environment still has "List"', function() + assert.is_nil(_G.pl) + assert.is_not_nil(pl) + assert.is_equal('this global is in _G', _G.global_var) + assert.is_not_nil(List) + end) + + it('global environment still has "pl" packages loaded', function() + assert.is_not_nil(package.loaded['pl']) + assert.is_not_nil(package.loaded['pl.List']) + end) +end) diff --git a/spec/file_context_support_spec.lua b/spec/file_context_support_spec.lua new file mode 100644 index 00000000..16a12a35 --- /dev/null +++ b/spec/file_context_support_spec.lua @@ -0,0 +1,28 @@ +local egg = '' + +setup(function() + egg = egg..'S' +end) + +teardown(function() + egg = egg..'T' + assert.equal('Sb1ab2aT', egg) +end) + +before_each(function() + egg = egg..'b' +end) + +after_each(function() + egg = egg..'a' +end) + +it('file context before_each after_each egg test 1', function() + assert.equal('Sb', egg) + egg = egg..'1' +end) + +it('file context before_each after_each egg test 2', function() + assert.equal('Sb1ab', egg) + egg = egg..'2' +end) diff --git a/spec/file_randomize_spec.lua b/spec/file_randomize_spec.lua new file mode 100644 index 00000000..a7cd7fc5 --- /dev/null +++ b/spec/file_randomize_spec.lua @@ -0,0 +1,16 @@ +local unexpected = {} +local order = {} + +randomize() + +for i = 1, 100 do + table.insert(unexpected, i) + + it('does 100 its', function() + table.insert(order, i) + end) +end + +teardown(function() + assert.are_not.same(unexpected, order) +end) diff --git a/spec/insulate-expose_spec.lua b/spec/insulate-expose_spec.lua new file mode 100644 index 00000000..bba84bd6 --- /dev/null +++ b/spec/insulate-expose_spec.lua @@ -0,0 +1,150 @@ +assert.is_nil(package.loaded.pl) +assert.is_nil(package.loaded['pl.file']) + +describe('Tests insulation', function() + insulate('environment inside insulate', function() + pl = require 'pl' + _G.insuated_global = true + + it('updates insuated global table _G', function() + assert.is_not_nil(insuated_global) + assert.is_not_nil(_G.insuated_global) + end) + + it('updates package.loaded', function() + assert.is_not_nil(pl) + assert.is_not_nil(Date) + assert.is_not_nil(package.loaded.pl) + assert.is_not_nil(package.loaded['pl.Date']) + end) + end) + + describe('environment after insulate', function() + it('restores insuated global table _G', function() + assert.is_nil(insuated_global) + assert.is_nil(_G.insuated_global) + end) + + it('restores package.loaded', function() + assert.is_nil(pl) + assert.is_nil(Date) + assert.is_nil(package.loaded.pl) + assert.is_nil(package.loaded['pl.Date']) + end) + end) +end) + +insulate('', function() + describe('Tests expose', function() + insulate('inside insulate block', function() + expose('tests environment inside expose block', function() + pl = require 'pl' + exposed_global = true + _G.global = true + + it('creates exposed global', function() + assert.is_not_nil(exposed_global) + assert.is_nil(_G.exposed_global) + end) + + it('updates global table _G', function() + assert.is_not_nil(global) + assert.is_not_nil(_G.global) + end) + + it('updates package.loaded', function() + assert.is_not_nil(pl) + assert.is_not_nil(Date) + assert.is_not_nil(package.loaded.pl) + assert.is_not_nil(package.loaded['pl.Date']) + end) + end) + end) + + describe('neutralizes insulation', function() + it('creates exposed global in outer block', function() + assert.is_not_nil(exposed_global) + assert.is_nil(_G.exposed_global) + end) + + it('does not restore global table _G', function() + assert.is_not_nil(global) + assert.is_not_nil(_G.global) + end) + + it('does not restore package.loaded', function() + assert.is_not_nil(pl) + assert.is_not_nil(Date) + assert.is_not_nil(package.loaded.pl) + assert.is_not_nil(package.loaded['pl.Date']) + end) + end) + end) + + it('Tests exposed globals does not exist in outer most block', function() + assert.is_nil(pl) + assert.is_nil(exposed_global) + assert.is_nil(_G.exposed_global) + end) + + it('Tests global table _G persists without insulate', function() + assert.is_not_nil(global) + assert.is_not_nil(_G.global) + end) + + it('Tests package.loaded persists without insulate', function() + assert.is_not_nil(Date) + assert.is_not_nil(package.loaded.pl) + assert.is_not_nil(package.loaded['pl.Date']) + end) +end) + +describe('Tests after insulating an expose block', function() + it('restores global table _G', function() + assert.is_nil(global) + assert.is_nil(_G.global) + end) + + it('restores package.loaded', function() + assert.is_nil(pl) + assert.is_nil(Date) + assert.is_nil(package.loaded.pl) + assert.is_nil(package.loaded['pl.Date']) + end) +end) + +describe('Tests insulate/expose', function() + local path = require 'pl.path' + local utils = require 'pl.utils' + local busted_cmd = path.is_windows and 'lua bin/busted' or 'bin/busted' + + local executeBusted = function(args) + local success, exitcode, out, err = utils.executeex(busted_cmd .. ' ' .. args) + if exitcode > 255 then + exitcode = math.floor(exitcode/256), exitcode - math.floor(exitcode/256)*256 + end + return not not success, exitcode, out, err + end + + describe('file insulation', function() + it('works between files', function() + local success, exitcode = executeBusted('spec/insulate_file1.lua spec/insulate_file2.lua') + assert.is_true(success) + assert.is_equal(0, exitcode) + end) + + it('works between files independent of order', function() + local success, exitcode = executeBusted('spec/insulate_file2.lua spec/insulate_file1.lua') + assert.is_true(success) + assert.is_equal(0, exitcode) + end) + end) + + describe('expose from file context', function() + it('works between files', function() + local success, exitcode = executeBusted('spec/expose_file1.lua spec/expose_file2.lua') + assert.is_true(success) + assert.is_equal(0, exitcode) + end) + end) +end) diff --git a/spec/insulate_file1.lua b/spec/insulate_file1.lua new file mode 100644 index 00000000..1a7d6014 --- /dev/null +++ b/spec/insulate_file1.lua @@ -0,0 +1,14 @@ +require 'pl' + +describe('Tests require "pl" in this file', function() + it('loads global environment with "List"', function() + assert.is_not_nil(List) + end) +end) + +describe('Tests require "cl_test_module" in another file', function() + it('does not keep test_module in environment', function() + assert.is_nil(test_module) + assert.is_nil(package.loaded['spec.cl_test_module']) + end) +end) diff --git a/spec/insulate_file2.lua b/spec/insulate_file2.lua new file mode 100644 index 00000000..d2b168d7 --- /dev/null +++ b/spec/insulate_file2.lua @@ -0,0 +1,15 @@ +test_module = require 'spec.cl_test_module' + +describe('Tests require "cl_test_module" in this file', function() + it('loads environment with "cl_test_module"', function() + assert.is_not_nil(test_module) + assert.is_not_nil(package.loaded['spec.cl_test_module']) + end) +end) + +describe('Tests require "pl" in another file', function() + it('does not keep "List" in environment', function() + assert.is_nil(List) + assert.is_nil(package.loaded['pl.List']) + end) +end) diff --git a/spec/lua.bat b/spec/lua.bat new file mode 100644 index 00000000..d347ee38 --- /dev/null +++ b/spec/lua.bat @@ -0,0 +1,2 @@ +@echo off +lua "%~dp0\lua.lua" %* diff --git a/spec/lua.lua b/spec/lua.lua new file mode 100755 index 00000000..61e40e50 --- /dev/null +++ b/spec/lua.lua @@ -0,0 +1,4 @@ +#!/usr/bin/env lua +local exit = require 'busted.compatibility'.exit +print(table.concat(arg, ' ')) +exit(0) diff --git a/spec/modules/cli_spec.lua b/spec/modules/cli_spec.lua new file mode 100644 index 00000000..b70dfcb7 --- /dev/null +++ b/spec/modules/cli_spec.lua @@ -0,0 +1,522 @@ +local path = require 'pl.path' +local normpath = path.normpath + +describe('Tests command-line interface', function() + it('default options', function() + local defaultOutput = 'default_output_handler' + local lpath = './src/?.lua;./src/?/?.lua;./src/?/init.lua' + local cpath = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;' + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + local args = cli:parse({}) + assert.is_equal(defaultOutput, args.o) + assert.is_equal(defaultOutput, args.output) + assert.is_same({'spec'}, args.ROOT) + assert.is_equal('./', args.C) + assert.is_equal('./', args.directory) + assert.is_equal('/dev/urandom or os.time()', args.seed) + assert.is_equal('en', args.lang) + assert.is_equal(1, args['repeat']) + assert.is_equal(lpath, args.m) + assert.is_equal(lpath, args.lpath) + assert.is_equal(cpath, args.cpath) + assert.is_true(args['auto-insulate']) + assert.is_true(args.k) + assert.is_true(args['keep-going']) + assert.is_true(args.R) + assert.is_true(args['recursive']) + assert.is_false(args.c) + assert.is_false(args.coverage) + assert.is_false(args.version) + assert.is_false(args.v) + assert.is_false(args.verbose) + assert.is_false(args.l) + assert.is_false(args.list) + assert.is_false(args.lazy) + assert.is_false(args.s) + assert.is_false(args['enable-sound']) + assert.is_false(args['suppress-pending']) + assert.is_false(args['defer-print']) + assert.is_nil(args.f) + assert.is_nil(args['config-file']) + assert.is_nil(args.shuffle) + assert.is_nil(args['shuffle-files']) + assert.is_nil(args['shuffle-tests']) + assert.is_nil(args.sort) + assert.is_nil(args['sort-files']) + assert.is_nil(args['sort-tests']) + assert.is_nil(args.r) + assert.is_nil(args.run) + assert.is_nil(args.helper) + assert.is_same({}, args.e) + assert.is_same({'_spec'}, args.p) + assert.is_same({'_spec'}, args.pattern) + assert.is_same({}, args['exclude-pattern']) + assert.is_same({}, args.t) + assert.is_same({}, args.tags) + assert.is_same({}, args['exclude-tags']) + assert.is_same({}, args.filter) + assert.is_same({}, args['filter-out']) + assert.is_same({}, args.Xoutput) + assert.is_same({}, args.Xhelper) + assert.is_same({'lua', 'moonscript'}, args.loaders) + end) + + it('standalone options disables ROOT and --pattern', function() + local cli = require 'busted.modules.cli'({ standalone = true }) + local args = cli:parse({}) + assert.is_nil(args.ROOT) + assert.is_same({}, args.p) + assert.is_same({}, args.pattern) + assert.is_same({}, args['exclude-pattern']) + end) + + it('specify flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '-v', '--version', '--coverage', '--defer-print', '--suppress-pending' }) + assert.is_true(args.v) + assert.is_true(args.verbose) + assert.is_true(args.version) + assert.is_true(args.coverage) + assert.is_true(args['defer-print']) + assert.is_true(args['suppress-pending']) + end) + + it('specify more flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '-s', '--list', '-k', '--no-keep-going', '-R', '--no-recursive' }) + assert.is_true(args.s) + assert.is_true(args['enable-sound']) + assert.is_true(args.l) + assert.is_true(args.list) + assert.is_false(args['keep-going']) + assert.is_false(args['recursive']) + end) + + it('specify even more flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--lazy', '--no-auto-insulate', '-k', '-R' }) + assert.is_true(args.lazy) + assert.is_false(args['auto-insulate']) + assert.is_true(args.k) + assert.is_true(args['keep-going']) + assert.is_true(args.R) + assert.is_true(args.recursive) + end) + + it('specify no-flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--no-lazy', '--no-auto-insulate', '--no-coverage', '--no-verbose' }) + assert.is_false(args.lazy) + assert.is_false(args['auto-insulate']) + assert.is_false(args.coverage) + assert.is_false(args.verbose) + end) + + it('specify more no-flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--no-enable-sound', '--no-suppress-pending', '--no-defer-print' }) + assert.is_false(args['enable-sound']) + assert.is_false(args['suppress-pending']) + assert.is_false(args['defer-print']) + end) + + it('specify shuffle flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--shuffle-files', '--shuffle-tests' }) + assert.is_nil(args.shuffle) + assert.is_true(args['shuffle-files']) + assert.is_true(args['shuffle-tests']) + end) + + it('specify shuffle flag only', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--shuffle', }) + assert.is_true(args.shuffle) + assert.is_true(args['shuffle-files']) + assert.is_true(args['shuffle-tests']) + end) + + it('specify shuffle no-flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--no-shuffle', '--no-shuffle-files', '--no-shuffle-tests' }) + assert.is_false(args.shuffle) + assert.is_false(args['shuffle-files']) + assert.is_false(args['shuffle-tests']) + end) + + it('specify no-shuffle flag only', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--no-shuffle', }) + assert.is_false(args.shuffle) + assert.is_false(args['shuffle-files']) + assert.is_false(args['shuffle-tests']) + end) + + it('specify shuffle and no-shuffle flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--shuffle', '--no-shuffle-files' }) + assert.is_true(args.shuffle) + assert.is_false(args['shuffle-files']) + assert.is_true(args['shuffle-tests']) + end) + + it('specify sort flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--sort-files', '--sort-tests' }) + assert.is_nil(args.sort) + assert.is_true(args['sort-files']) + assert.is_true(args['sort-tests']) + end) + + it('specify sort flag only', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--sort', }) + assert.is_true(args.sort) + assert.is_true(args['sort-files']) + assert.is_true(args['sort-tests']) + end) + + it('specify sort no-flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--no-sort', '--no-sort-files', '--no-sort-tests' }) + assert.is_false(args.sort) + assert.is_false(args['sort-files']) + assert.is_false(args['sort-tests']) + end) + + it('specify no-sort flag only', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--no-sort', }) + assert.is_false(args.sort) + assert.is_false(args['sort-files']) + assert.is_false(args['sort-tests']) + end) + + it('specify sort and no-sort flags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--sort', '--no-sort-files' }) + assert.is_true(args.sort) + assert.is_false(args['sort-files']) + assert.is_true(args['sort-tests']) + end) + + it('specify ROOT arg and --pattern', function() + local cli = require 'busted.modules.cli'({ standalone = false }) + local args = cli:parse({ '-p', 'match_files', 'root_is_here' }) + assert.is_same({'root_is_here'}, args.ROOT) + assert.is_same({'match_files'}, args.p) + assert.is_same({'match_files'}, args.pattern) + end) + + it('specify ROOT arg and --exclude-pattern', function() + local cli = require 'busted.modules.cli'({ standalone = false }) + local args = cli:parse({ '--exclude-pattern', 'exclude_files', 'root_is_here' }) + assert.is_same({'root_is_here'}, args.ROOT) + assert.is_same({'exclude_files'}, args['exclude-pattern']) + end) + + it('specify multiple root paths', function() + local cli = require 'busted.modules.cli'({ standalone = false }) + local args = cli:parse({'root1_path', 'root2_path', 'root3_path'}) + assert.is_same({'root1_path', 'root2_path', 'root3_path'}, args.ROOT) + end) + + it('specify --directory', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--directory=/path/to/dir' }) + assert.is_equal(normpath('/path/to/dir'), args.C) + assert.is_equal(normpath('/path/to/dir'), args.directory) + end) + + it('specify --directory multiple times', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--directory=/path/to', '-C', 'dir', '--directory=subdir' }) + assert.is_equal(normpath('/path/to/dir/subdir'), args.C) + assert.is_equal(normpath('/path/to/dir/subdir'), args.directory) + end) + + it('specify --directory multiple times with multiple roots', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--directory=/path/to', '-C', 'dir', '--directory=/new/path' }) + assert.is_equal(normpath('/new/path'), args.C) + assert.is_equal(normpath('/new/path'), args.directory) + end) + + it('specify --run', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--run=task' }) + assert.is_equal('task', args.r) + assert.is_equal('task', args.run) + end) + + it('specify --lang', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--lang=fr' }) + assert.is_equal('fr', args.lang) + end) + + it('specify --repeat', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--repeat=23' }) + assert.is_equal(23, args['repeat']) + end) + + it('specify output library', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '-o', 'output_handler', '-Xoutput', '--flag,-f', '-Xoutput', '--opt=val' }) + assert.is_equal('output_handler', args.o) + assert.is_equal('output_handler', args.output) + assert.is_same({'--flag', '-f', '--opt=val'}, args.Xoutput) + end) + + it('specify helper script', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--helper=helper_script', '-Xhelper', '--flag,-f', '-Xhelper', '--opt=val' }) + assert.is_equal('helper_script', args.helper) + assert.is_same({'--flag', '-f', '--opt=val'}, args.Xhelper) + end) + + it('specify --tags and --exclude-tags', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--tags=tag1,tag2', '-t', 'tag3', '--exclude-tags=etag1', '--exclude-tags=etag2,etag3' }) + assert.is_same({'tag1', 'tag2', 'tag3'}, args.t) + assert.is_same({'tag1', 'tag2', 'tag3'}, args.tags) + assert.is_same({'etag1', 'etag2', 'etag3'}, args['exclude-tags']) + end) + + it('specify --filter and --filter-out', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--filter=_filt', '--filter-out=_filterout' }) + assert.is_same({'_filt'}, args.filter) + assert.is_same({'_filterout'}, args['filter-out']) + end) + + it('specify --filter and --filter-out multiple times', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--filter=_filt1', '--filter=_filt2', '--filter-out=_filterout1', '--filter-out=_filterout2' }) + assert.is_same({'_filt1', '_filt2'}, args.filter) + assert.is_same({'_filterout1', '_filterout2'}, args['filter-out']) + end) + + it('specify --loaders', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '--loaders=load1,load2', '--loaders=load3' }) + assert.is_same({'load1', 'load2', 'load3'}, args.loaders) + end) + + it('specify --lpath', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '-C', '/root', '--lpath=./path1/?.lua', '-m', './path2/?.lua' }) + assert.is_equal('./path1/?.lua;./path2/?.lua', args.m) + assert.is_equal('./path1/?.lua;./path2/?.lua', args.lpath) + end) + + it('specify --cpath', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '-C', '/croot', '--lpath=./path1/?.so', '-m', './path2/?.so' }) + assert.is_equal('./path1/?.so;./path2/?.so', args.m) + assert.is_equal('./path1/?.so;./path2/?.so', args.lpath) + end) + + it('specify -e statement', function() + local cli = require 'busted.modules.cli'() + local args = cli:parse({ '-e', 'statement1', '-e', 'statement2' }) + assert.is_same({'statement1', 'statement2'}, args.e) + end) +end) + +describe('Tests using .busted tasks', function() + it('default options', function() + local defaultOutput = 'default_output_handler' + local lpath = './src/?.lua;./src/?/?.lua;./src/?/init.lua' + local cpath = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;' + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + local args = cli:parse({ '--directory=spec/.hidden' }) + assert.is_equal(defaultOutput, args.o) + assert.is_equal(defaultOutput, args.output) + assert.is_same({'specs'}, args.ROOT) + assert.is_equal(normpath('spec/.hidden'), args.C) + assert.is_equal(normpath('spec/.hidden'), args.directory) + assert.is_equal('/dev/urandom or os.time()', args.seed) + assert.is_equal('en', args.lang) + assert.is_equal(1, args['repeat']) + assert.is_equal(lpath, args.m) + assert.is_equal(lpath, args.lpath) + assert.is_equal(cpath, args.cpath) + assert.is_true(args['auto-insulate']) + assert.is_true(args.k) + assert.is_true(args['keep-going']) + assert.is_true(args.R) + assert.is_true(args['recursive']) + assert.is_false(args.c) + assert.is_false(args.coverage) + assert.is_false(args.version) + assert.is_false(args.v) + assert.is_false(args.verbose) + assert.is_false(args.l) + assert.is_false(args.list) + assert.is_false(args.lazy) + assert.is_false(args.s) + assert.is_false(args['enable-sound']) + assert.is_false(args['suppress-pending']) + assert.is_false(args['defer-print']) + assert.is_nil(args.f) + assert.is_nil(args['config-file']) + assert.is_nil(args.shuffle) + assert.is_nil(args['shuffle-files']) + assert.is_nil(args['shuffle-tests']) + assert.is_nil(args.sort) + assert.is_nil(args['sort-files']) + assert.is_nil(args['sort-tests']) + assert.is_nil(args.r) + assert.is_nil(args.run) + assert.is_nil(args.helper) + assert.is_same({}, args.e) + assert.is_same({'_spec%.lua$'}, args.p) + assert.is_same({'_spec%.lua$'}, args.pattern) + assert.is_same({'_exclude'}, args['exclude-pattern']) + assert.is_same({'tag11', 'tag22', 'tag33'}, args.t) + assert.is_same({'tag11', 'tag22', 'tag33'}, args.tags) + assert.is_same({'etag11', 'etag22', 'etag33'}, args['exclude-tags']) + assert.is_same({'filt'}, args.filter) + assert.is_same({'filt-out'}, args['filter-out']) + assert.is_same({'-f', '--flag'}, args.Xoutput) + assert.is_same({'-v', '--verbose'}, args.Xhelper) + assert.is_same({'terra', 'moonscript'}, args.loaders) + end) + + it('default options with --config-file option', function() + local defaultOutput = 'default_output_handler' + local lpath = './src/?.lua;./src/?/?.lua;./src/?/init.lua' + local cpath = path.is_windows and './csrc/?.dll;./csrc/?/?.dll;' or './csrc/?.so;./csrc/?/?.so;' + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + local args = cli:parse({ '--config-file', 'spec/.hidden/.busted' }) + assert.is_equal(defaultOutput, args.o) + assert.is_equal(defaultOutput, args.output) + assert.is_same({'specs'}, args.ROOT) + assert.is_equal('./', args.C) + assert.is_equal('./', args.directory) + assert.is_equal('spec/.hidden/.busted', args.f) + assert.is_equal('spec/.hidden/.busted', args['config-file']) + assert.is_equal('/dev/urandom or os.time()', args.seed) + assert.is_equal('en', args.lang) + assert.is_equal(1, args['repeat']) + assert.is_equal(lpath, args.m) + assert.is_equal(lpath, args.lpath) + assert.is_equal(cpath, args.cpath) + assert.is_true(args['auto-insulate']) + assert.is_true(args.k) + assert.is_true(args['keep-going']) + assert.is_true(args.R) + assert.is_true(args['recursive']) + assert.is_false(args.c) + assert.is_false(args.coverage) + assert.is_false(args.version) + assert.is_false(args.v) + assert.is_false(args.verbose) + assert.is_false(args.l) + assert.is_false(args.list) + assert.is_false(args.lazy) + assert.is_false(args.s) + assert.is_false(args['enable-sound']) + assert.is_false(args['suppress-pending']) + assert.is_false(args['defer-print']) + assert.is_nil(args.shuffle) + assert.is_nil(args['shuffle-files']) + assert.is_nil(args['shuffle-tests']) + assert.is_nil(args.sort) + assert.is_nil(args['sort-files']) + assert.is_nil(args['sort-tests']) + assert.is_nil(args.r) + assert.is_nil(args.run) + assert.is_nil(args.helper) + assert.is_same({'_spec%.lua$'}, args.p) + assert.is_same({'_spec%.lua$'}, args.pattern) + assert.is_same({'_exclude'}, args['exclude-pattern']) + assert.is_same({'tag11', 'tag22', 'tag33'}, args.t) + assert.is_same({'tag11', 'tag22', 'tag33'}, args.tags) + assert.is_same({'etag11', 'etag22', 'etag33'}, args['exclude-tags']) + assert.is_same({'filt'}, args.filter) + assert.is_same({'filt-out'}, args['filter-out']) + assert.is_same({'-f', '--flag'}, args.Xoutput) + assert.is_same({'-v', '--verbose'}, args.Xhelper) + assert.is_same({'terra', 'moonscript'}, args.loaders) + end) + + it('load configuration options', function() + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + local args = cli:parse({ '--directory=spec/.hidden', '--run=test' }) + assert.is_same({'_test1%.lua$', '_test2%.lua$'}, args.pattern) + assert.is_same({'_exclude1', '_exclude2'}, args['exclude-pattern']) + assert.is_same({'filt1', 'filt2'}, args.filter) + assert.is_same({'filt1-out', 'filt2-out'}, args['filter-out']) + assert.is_same({'tests'}, args.ROOT) + assert.is_same({'test1', 'test2', 'test3'}, args.tags) + assert.is_same({'etest1', 'etest2', 'etest3'}, args['exclude-tags']) + assert.is_same({'-s','--sound'}, args.Xoutput) + assert.is_same({'-t', '--print'}, args.Xhelper) + assert.is_same({'lua', 'terra'}, args.loaders) + end) + + it('load configuration options and override with command-line', function() + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + local args = cli:parse({ '--directory=spec/.hidden', '--run=test', '-t', 'tag1', '-p', 'patt', '--filter=fin', '--filter-out=fout', '--exclude-pattern', '', '--loaders=moonscript' }) + assert.is_same({'patt'}, args.pattern) + assert.is_same({''}, args['exclude-pattern']) + assert.is_same({'fin'}, args.filter) + assert.is_same({'fout'}, args['filter-out']) + assert.is_same({'tag1'}, args.tags) + assert.is_same({'etest1', 'etest2', 'etest3'}, args['exclude-tags']) + assert.is_same({'-s','--sound'}, args.Xoutput) + assert.is_same({'-t', '--print'}, args.Xhelper) + assert.is_same({'moonscript'}, args.loaders) + end) + + it('detects error in configuration file', function() + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + cli:set_name('app') + local args, err = cli:parse({ '--config-file=spec/.hidden/.busted_bad', '--run=test' }) + assert.is_nil(args) + assert.has_match('^app: error: spec/.hidden/.busted_bad:8: ', err) + assert.has_match("'doesnotexist'", err) + assert.has_match("a nil value", err) + end) + + it('detects invalid configuration file', function() + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + cli:set_name('myapp') + local args, err = cli:parse({ '--config-file=spec/.hidden/.busted_empty' }) + assert.is_nil(args) + assert.is_equal('myapp: error: .busted file does not return a table.', err) + end) + + it('detects unknown/invalid task', function() + local cli = require 'busted.modules.cli'({ standalone = false, output = defaultOutput }) + cli:set_name('appname') + local args, err = cli:parse({ '--config-file=spec/.hidden/.busted', '--run=invalid' }) + assert.is_nil(args) + assert.is_equal('appname: error: Task `invalid` not found, or not a table.', err) + end) +end) + +describe('Tests command-line parse errors', function() + before_each(function() + package.loaded['cliargs'] = nil + end) + + it('with invalid --repeat value', function() + local cli = require 'busted.modules.cli'() + cli:set_name('myapp') + local args, err = cli:parse({ '--repeat=abc'}) + assert.is_nil(args) + assert.is_equal('myapp: error: argument to --repeat must be a number; re-run with --help for usage.', err) + end) + + it('with same tag for --tags and --exclude-tags', function() + local cli = require 'busted.modules.cli'() + cli:set_name('myapp') + local args, err = cli:parse({ '--tags=tag1', '--exclude-tags=tag1' }) + assert.is_nil(args) + assert.is_equal('myapp: error: Cannot use --tags and --exclude-tags for the same tags', err) + end) +end) diff --git a/spec/modules/configuration_loader_spec.lua b/spec/modules/configuration_loader_spec.lua index 5aabf60c..d25f18c3 100644 --- a/spec/modules/configuration_loader_spec.lua +++ b/spec/modules/configuration_loader_spec.lua @@ -1,18 +1,22 @@ describe('Config Loader', function() local configLoader = require 'busted.modules.configuration_loader'() - local testConfig, cliArgs + local testConfig, cliArgs, defaults before_each(function() testConfig = { + _all = { + other = 'stuff', + }, default = { - output = { 'utfTerminal' } + output = 'utfTerminal' }, windows = { - output = { 'plainTerminal' } + output = 'plainTerminal' } } cliArgs = { } + defaults = { } end) it('returns a valid config with no config file', function() @@ -26,24 +30,57 @@ describe('Config Loader', function() it('returns a valid config with default config', function() local config, err = configLoader(testConfig, cliArgs) - assert.are.same(testConfig.default, config) + assert.are.same(testConfig.default.output, config.output) + assert.are.same(testConfig._all.other, config.other) assert.are.equal(nil, err) end) it('returns a valid config with specified config', function() - local config, err = configLoader(testConfig, cliArgs, 'windows') + cliArgs.run = 'windows' + local config, err = configLoader(testConfig, cliArgs) + + assert.are.same(testConfig.windows.output, config.output) + assert.are.same(testConfig._all.other, config.other) + assert.are.equal(nil, err) + end) + + it('returns a valid config with specified config and defaults specified', function() + defaults = { output = 'TAP' } + cliArgs.run = 'windows' + local config, err = configLoader(testConfig, cliArgs, defaults) - assert.are.same(testConfig.windows, config) + assert.are.same(testConfig.windows.output, config.output) + assert.are.same(testConfig._all.other, config.other) + assert.are.equal(nil, err) + end) + + it('returns a valid config with cliArgs and defaults specified', function() + cliArgs = { output = 'TAP' } + local config, err = configLoader(testConfig, cliArgs, defaults) + + assert.are.same(cliArgs.output, config.output) + assert.are.same(testConfig._all.other, config.other) + assert.are.equal(nil, err) + end) + + it('returns a valid config with defaults if no configs present', function() + defaults = { output = 'TAP' } + local config, err = configLoader({}, {}, defaults) + + assert.are.same(defaults, config) assert.are.equal(nil, err) end) it('returns an error with an invalid config', function() - local config, err = configLoader('invalid', cliArgs, 'run') - assert.are_not.equal(nil, err) + local config, err = configLoader('invalid', cliArgs) + assert.is_nil(config) + assert.are.equal('.busted file does not return a table.', err) end) it('returns an error with an invalid run', function() - local config, err = configLoader(testConfig, cliArgs, 'run') - assert.are_not.equal(nil, err) + cliArgs.run = 'invalid' + local config, err = configLoader(testConfig, cliArgs) + assert.is_nil(config) + assert.are.equal('Task `invalid` not found, or not a table.', err) end) end) diff --git a/spec/moonscript_spec.moon b/spec/moonscript_spec.moon index 938afe46..954827df 100644 --- a/spec/moonscript_spec.moon +++ b/spec/moonscript_spec.moon @@ -1,4 +1,6 @@ describe 'moonscript tests', -> it 'works', -> assert.are.equal true, true - + return + return +return diff --git a/spec/randomize_spec.lua b/spec/randomize_spec.lua index 4d6bede2..d3b8d76c 100644 --- a/spec/randomize_spec.lua +++ b/spec/randomize_spec.lua @@ -1,5 +1,7 @@ local unexpected = {} local order = {} +local orderfixed1 = {} +local orderfixed2 = {} describe('Randomizing test order', function() randomize() @@ -7,14 +9,95 @@ describe('Randomizing test order', function() for i = 1, 100 do table.insert(unexpected, i) - it('does 1000 its', function() + it('does 100 its', function() table.insert(order, i) end) end end) +describe('Randomizing test order with fixed seed as first arg', function() + randomize(3210) + + for i = 1, 10 do + it('does 10 its', function() + table.insert(orderfixed1, i) + end) + end +end) + +describe('Randomizing test order with fixed seed as second arg', function() + randomize(true, 56789) + + for i = 1, 10 do + it('does 10 its', function() + table.insert(orderfixed2, i) + end) + end +end) + describe('Order of tests ran', function() + local function shuffle(t, seed) + math.randomseed(seed) + local n = #t + while n >= 1 do + local k = math.random(n) + t[n], t[k] = t[k], t[n] + n = n - 1 + end + return t + end + it('randomized', function() assert.are_not.same(unexpected, order) end) + + it('randomized with known random seed: 3210', function() + local t = {1,2,3,4,5,6,7,8,9,10} + assert.are.same(shuffle(t, 3210), orderfixed1) + end) + + it('randomized with known random seed: 56789', function() + local t = {1,2,3,4,5,6,7,8,9,10} + assert.are.same(shuffle(t, 56789), orderfixed2) + end) +end) + +describe('Disabling randomized test order with randomize(false)', function() + randomize() + randomize(false) + + local expected = {} + local order = {} + + for i = 1, 100 do + table.insert(expected, i) + + it('does 100 its', function() + table.insert(order, i) + end) + end + + it('does not randomize tests', function() + assert.are.same(expected, order) + end) +end) + +describe('Disabling randomized test order with randomize(nil)', function() + randomize() + randomize(nil) + + local expected = {} + local order = {} + + for i = 1, 100 do + table.insert(expected, i) + + it('does 100 its', function() + table.insert(order, i) + end) + end + + it('does not randomize tests', function() + assert.are.same(expected, order) + end) end) diff --git a/spec/strict.lua b/spec/strict.lua new file mode 100644 index 00000000..03660f28 --- /dev/null +++ b/spec/strict.lua @@ -0,0 +1,39 @@ +-- strict.lua +-- checks uses of undeclared global variables +-- All global variables must be 'declared' through a regular assignment +-- (even assigning nil will do) in a main chunk before being used +-- anywhere or assigned to inside a function. +-- distributed under the Lua license: http://www.lua.org/license.html + +local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget + +local mt = getmetatable(_G) +if mt == nil then + mt = {} + setmetatable(_G, mt) +end + +mt.__declared = {} + +local function what () + local d = getinfo(3, "S") + return d and d.what or "C" +end + +mt.__newindex = function (t, n, v) + if not mt.__declared[n] then + local w = what() + if w ~= "main" and w ~= "C" then + error("assign to undeclared variable '"..n.."'", 2) + end + mt.__declared[n] = true + end + rawset(t, n, v) +end + +mt.__index = function (t, n) + if not mt.__declared[n] and what() ~= "C" then + error("variable '"..n.."' is not declared", 2) + end + return rawget(t, n) +end diff --git a/spec/strict_spec.lua b/spec/strict_spec.lua new file mode 100644 index 00000000..e91dbce0 --- /dev/null +++ b/spec/strict_spec.lua @@ -0,0 +1,9 @@ +require 'spec.strict' + +describe('runs a single successful test with strict', function() + + it('is a succesful test with strict', function() + -- nothing here, makes it succeed + end) + +end) diff --git a/try b/try index f697bf5b..fe04aa13 100755 --- a/try +++ b/try @@ -1,4 +1,4 @@ #!/bin/sh sudo luarocks remove busted --force -sudo luarocks make +sudo luarocks make busted-scm-0.rockspec busted diff --git a/try.bat b/try.bat index 89510472..0d4dba36 100644 --- a/try.bat +++ b/try.bat @@ -1,4 +1,4 @@ call luarocks remove busted --force -call luarocks make +call luarocks make busted-scm-0.rockspec cls call busted