Imported Upstream version 0.6

upstream upstream/0.6
Victor Seva 12 years ago
commit 2c9b8a671d

@ -0,0 +1,51 @@
#
# LuaDist Travis-CI Hook
#
# We assume C build environments
language: C
# Try using multiple Lua Implementations
env:
- TOOL="" # Use native compiler (GCC usually)
- COMPILER="clang" # Use clang
- TOOL="i686-w64-mingw32" # 32bit MinGW
- TOOL="x86_64-w64-mingw32" # 64bit MinGW
- TOOL="arm-linux-gnueabihf" # ARM hard-float (hf), linux
# Crosscompile builds may fail
matrix:
allow_failures:
- env: TOOL="i686-w64-mingw32"
- env: TOOL="x86_64-w64-mingw32"
- env: TOOL="arm-linux-gnueabihf"
# Install dependencies
install:
- git clone git://github.com/LuaDist/_util.git ~/_util
- ~/_util/travis install
# Bootstap
before_script:
- ~/_util/travis bootstrap
# Build the module
script:
- ~/_util/travis build
# Execute additional tests or commands
#after_script:
# - ~/_util/travis test
# Only watch the master branch
branches:
only:
- master
# Notify the LuaDist Dev group if needed
notifications:
recipients:
- luadist-dev@googlegroups.com
email:
on_success: change
on_failure: always

@ -0,0 +1,14 @@
# Copyright (C) 2011-2012 LuaDist.
# Created by Peter Kapec
# Redistribution and use of this file is allowed according to the terms of the MIT license.
# For details see the COPYRIGHT file distributed with LuaDist.
# Please note that the package source code is licensed under its own license.
project ( lemock NONE )
cmake_minimum_required ( VERSION 2.8 )
include ( cmake/dist.cmake )
include ( lua )
# Install all files and documentation
install_lua_module ( lemock build/lemock.lua )
install_doc ( build/htdocs/ )

@ -0,0 +1,28 @@
LeMock License
LeMock is licensed under the terms of the MIT license reproduced below. This
means that LeMock is free software and can be used for both academic and
commercial purposes at absolutely no cost.
--------------------------------------------------------------------------
Copyright (C) 2009 Tommy Petterson <ptp@lysator.liu.se>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--------------------------------------------------------------------------

57
DEVEL

@ -0,0 +1,57 @@
LeMock Developer Notes
LeMock is implemented in Lua 5.1, but its source code is written as
literate documents, and usees the tool noweb to generate the .lua files.
The distributed source archive includes all the tangled files in the build
directory, to avoid dependending on noweb for installation.
The source is contained in the src directory in the form of literate
documents. To extract (tangle) the final files from the sources, the tool
[noweb http://www.cs.tufts.edu/~nr/noweb/] is needed.
The source files are meant to be tangled together all at once, because the
contents of a target file can be spread over several source files. The
target files are all the chunk names that are roots. These can be found
with noroots in the noweb toolbox. To automate the tangle process, there is
a custom script named autotangle in the tools directory. This script finds
all the target file names and tangles them in the current directory,
creating subdirectories as needed. The script is written for yet another
obscure tool, [rc http://www.libra-aries-books.co.uk/software/rc] [1]. It
is probably easy to port the short rc script to your favourit language. It
is invoked (in rc syntax) as:
``` ../tools/autotangle `{find ../src -name '*.nw'}
which means it wants all (recursively) .nw files in the src directory as
arguments.
The documentation is written in [txt2tags http://txt2tags.sourceforge.net/],
which can generate HTML among many other formats. The README, HISTORY,
COPYRIGHT, and this DEVEL text file are written as simple txt2tags
documents to remain readable as is. The .nw source files define wrapper
txt2tags documents for the web pages, which use txt2tags' include mechanism
to include the actual txt2tags files. The user guide is defined in the .nw
sources as a separate txt2tags document, so it can be easily generated as a
LaTeX document or Unix man page, but it too is included in a wrapper
txt2tags document when generating the web pages.
The building of the web pages is done with
[mk http://en.wikipedia.org/wiki/Mk_%28software%29], a Unix port of the
Plan9 make tool. (The mk files uses rc syntax.)
A set of unit tests (defined in the .nw source) can be run with
[lunit http://www.nessie.de/mroth/lunit/index.html] with the command:
``` lunit unit/*.lua
A program like [luacov http://luacov.luaforge.net/] can be used to check
the coverage of the unit tests.
--------------------
== Footnotes ==
: [1]
I use Byron's Unix port, which syntax is extended and incompatible with
the original (and other ports).
:

@ -0,0 +1,22 @@
LeMock History
: 0.6
- Actions can be set to raise an error.
- //anytimes// and //atleastonce//.
- Fail immediately if an unsatisfied action is closed.
- Simplify semantics for Anyarg and Anyargs.
- Documentation.
: 0.5
- Anyarg and Anyargs.
- Third rewrite (major refactoring).
: 0.4
- Labels, dependencies, closes, and replay count limits.
- Allow attribute modifying controller methods to be chained.
: 0.3
- Initial import.
- This is the second rewrite of the initial prototype. It handles recording
and replaying of actions with returnvalues, and verifies replay
completeness.

@ -0,0 +1,44 @@
LeMock Readme
== What is Lua Easy Mock ==
LeMock (Lua Easy Mock) is a mock creation module intended for use together
with a unit test framework such as lunit or lunity. It is inspired by
EasyMock (for Java), and strives to be easy to use.
== Availability ==
LeMock is hosted at LuaForge at http://luaforge.net/projects/lemock/
== Installation ==
Copy the file build/lemock.lua to a Lua search path directory. This is
usually something like ``/usr/share/lua/5.1/`` or
``/usr/local/lib/lua/5.1/``. You can type ``print(package.path)`` at the
Lua prompt to see what search path your Lua installation is using.
Documentation in HTML format is available in build/htdocs/.
== License ==
LeMock is licensed under the MIT license, which is the same license that
Lua uses. See COPYRIGHT_ for full terms.
== User Documentation ==
See build/htdocs/userguide.html.
== Development ==
LeMock is implemented in Lua 5.1, but its source code is written as
literate documents, and uses the tool noweb to tangle the .lua files. The
distributed source archive includes all the tangled files, to avoid
depending on noweb for installation. See DEVEL_ for information about how
the source code is organized, and what tools are needed for the build
process.

@ -0,0 +1,58 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<META NAME="generator" CONTENT="http://txt2tags.sf.net">
<LINK REL="stylesheet" TYPE="text/css" HREF="style.css">
<TITLE>LeMock</TITLE>
</HEAD>
<BODY>
<DIV CLASS="header" ID="header">
<H1>LeMock</H1>
</DIV>
<DIV CLASS="body" ID="page-COPYRIGHT">
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
<H1>License</H1>
<P>
LeMock is licensed under the terms of the MIT license reproduced below. This
means that LeMock is free software and can be used for both academic and
commercial purposes at absolutely no cost.
</P>
<HR NOSHADE SIZE=1>
<P>
Copyright (C) 2009 Tommy Petterson &lt;<A HREF="mailto:ptp@lysator.liu.se">ptp@lysator.liu.se</A>&gt;
</P>
<P>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
</P>
<P>
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
</P>
<P>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
</P>
<HR NOSHADE SIZE=1>
</DIV>
<!-- html code generated by txt2tags 2.3 (http://txt2tags.sf.net) -->
<!-- cmdline: txt2tags -t html -i www/COPYRIGHT.t2t -o htdocs/COPYRIGHT.html -->
</BODY></HTML>

@ -0,0 +1,98 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<META NAME="generator" CONTENT="http://txt2tags.sf.net">
<LINK REL="stylesheet" TYPE="text/css" HREF="style.css">
<TITLE>LeMock</TITLE>
</HEAD>
<BODY>
<DIV CLASS="header" ID="header">
<H1>LeMock</H1>
</DIV>
<DIV CLASS="body" ID="page-DEVEL">
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
<H1>Developer Notes</H1>
<P>
LeMock is implemented in Lua 5.1, but its source code is written as
literate documents, and usees the tool noweb to generate the .lua files.
The distributed source archive includes all the tangled files in the build
directory, to avoid dependending on noweb for installation.
</P>
<P>
The source is contained in the src directory in the form of literate
documents. To extract (tangle) the final files from the sources, the tool
<A HREF="http://www.cs.tufts.edu/~nr/noweb/">noweb</A> is needed.
</P>
<P>
The source files are meant to be tangled together all at once, because the
contents of a target file can be spread over several source files. The
target files are all the chunk names that are roots. These can be found
with noroots in the noweb toolbox. To automate the tangle process, there is
a custom script named autotangle in the tools directory. This script finds
all the target file names and tangles them in the current directory,
creating subdirectories as needed. The script is written for yet another
obscure tool, <A HREF="http://www.libra-aries-books.co.uk/software/rc">rc</A> [1]. It
is probably easy to port the short rc script to your favourit language. It
is invoked (in rc syntax) as:
</P>
<PRE>
../tools/autotangle `{find ../src -name '*.nw'}
</PRE>
<P></P>
<P>
which means it wants all (recursively) .nw files in the src directory as
arguments.
</P>
<P>
The documentation is written in <A HREF="http://txt2tags.sourceforge.net/">txt2tags</A>,
which can generate HTML among many other formats. The README, HISTORY,
COPYRIGHT, and this DEVEL text file are written as simple txt2tags
documents to remain readable as is. The .nw source files define wrapper
txt2tags documents for the web pages, which use txt2tags' include mechanism
to include the actual txt2tags files. The user guide is defined in the .nw
sources as a separate txt2tags document, so it can be easily generated as a
LaTeX document or Unix man page, but it too is included in a wrapper
txt2tags document when generating the web pages.
</P>
<P>
The building of the web pages is done with
<A HREF="http://en.wikipedia.org/wiki/Mk_%28software%29">mk</A>, a Unix port of the
Plan9 make tool. (The mk files uses rc syntax.)
</P>
<P>
A set of unit tests (defined in the .nw source) can be run with
<A HREF="http://www.nessie.de/mroth/lunit/index.html">lunit</A> with the command:
</P>
<PRE>
lunit unit/*.lua
</PRE>
<P></P>
<P>
A program like <A HREF="http://luacov.luaforge.net/">luacov</A> can be used to check
the coverage of the unit tests.
</P>
<HR NOSHADE SIZE=1>
<H2>Footnotes</H2>
<DL>
<DT>[1]</DT><DD>
I use Byron's Unix port, which syntax is extended and incompatible with
the original (and other ports).
</DL>
<HR NOSHADE SIZE=1>
<P>
2009-05-31
</P>
</DIV>
<!-- html code generated by txt2tags 2.3 (http://txt2tags.sf.net) -->
<!-- cmdline: txt2tags -t html -i www/DEVEL.t2t -o htdocs/DEVEL.html -->
</BODY></HTML>

@ -0,0 +1,59 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<META NAME="generator" CONTENT="http://txt2tags.sf.net">
<LINK REL="stylesheet" TYPE="text/css" HREF="style.css">
<TITLE>LeMock</TITLE>
</HEAD>
<BODY>
<DIV CLASS="header" ID="header">
<H1>LeMock</H1>
</DIV>
<DIV CLASS="body" ID="page-HISTORY">
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
<H1>History</H1>
<DL>
<DT>0.6</DT><DD>
<UL>
<LI>Actions can be set to raise an error.
<LI><I>anytimes</I> and <I>atleastonce</I>.
<LI>Fail immediately if an unsatisfied action is closed.
<LI>Simplify semantics for Anyarg and Anyargs.
<LI>Documentation.
</UL>
<DT>0.5</DT><DD>
<UL>
<LI>Anyarg and Anyargs.
<LI>Third rewrite (major refactoring).
</UL>
<DT>0.4</DT><DD>
<UL>
<LI>Labels, dependencies, closes, and replay count limits.
<LI>Allow attribute modifying controller methods to be chained.
</UL>
<DT>0.3</DT><DD>
<UL>
<LI>Initial import.
<LI>This is the second rewrite of the initial prototype. It handles recording
and replaying of actions with returnvalues, and verifies replay
completeness.
</UL>
</DL>
<HR NOSHADE SIZE=1>
<P>
2009-05-31
</P>
</DIV>
<!-- html code generated by txt2tags 2.3 (http://txt2tags.sf.net) -->
<!-- cmdline: txt2tags -t html -i www/HISTORY.t2t -o htdocs/HISTORY.html -->
</BODY></HTML>

@ -0,0 +1,69 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<META NAME="generator" CONTENT="http://txt2tags.sf.net">
<LINK REL="stylesheet" TYPE="text/css" HREF="style.css">
<TITLE>LeMock</TITLE>
</HEAD>
<BODY>
<DIV CLASS="header" ID="header">
<H1>LeMock</H1>
</DIV>
<DIV CLASS="body" ID="page-README">
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
<H1>Readme</H1>
<H2>What is Lua Easy Mock</H2>
<P>
LeMock (Lua Easy Mock) is a mock creation module intended for use together
with a unit test framework such as lunit or lunity. It is inspired by
EasyMock (for Java), and strives to be easy to use.
</P>
<H2>Availability</H2>
<P>
LeMock is hosted at LuaForge at <A HREF="http://luaforge.net/projects/lemock/">http://luaforge.net/projects/lemock/</A>
</P>
<H2>Installation</H2>
<P>
Copy the file build/lemock.lua to a Lua search path directory. This is
usually something like <CODE>/usr/share/lua/5.1/</CODE> or
<CODE>/usr/local/lib/lua/5.1/</CODE>. You can type <CODE>print(package.path)</CODE> at the
Lua prompt to see what search path your Lua installation is using.
</P>
<P>
Documentation in HTML format is available in build/htdocs/.
</P>
<H2>License</H2>
<P>
LeMock is licensed under the MIT license, which is the same license that
Lua uses. See <A HREF="COPYRIGHT.html">COPYRIGHT</A> for full terms.
</P>
<H2>User Documentation</H2>
<P>
See <A HREF="userguide.html">the user guide</A>.
</P>
<H2>Development</H2>
<P>
LeMock is implemented in Lua 5.1, but its source code is written as
literate documents, and uses the tool noweb to tangle the .lua files. The
distributed source archive includes all the tangled files, to avoid
depending on noweb for installation. See <A HREF="DEVEL.html">DEVEL</A> for information about how
the source code is organized, and what tools are needed for the build
process.
</P>
<HR NOSHADE SIZE=1>
<P>
2009-05-31
</P>
</DIV>
<!-- html code generated by txt2tags 2.3 (http://txt2tags.sf.net) -->
<!-- cmdline: txt2tags -t html -i www/README.t2t -o htdocs/README.html -->
</BODY></HTML>

@ -0,0 +1,26 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<META NAME="generator" CONTENT="http://txt2tags.sf.net">
<LINK REL="stylesheet" TYPE="text/css" HREF="style.css">
<TITLE>LeMock</TITLE>
</HEAD>
<BODY>
<DIV CLASS="header" ID="header">
<H1>LeMock</H1>
</DIV>
<DIV CLASS="body" ID="page-index">
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
</DIV>
<!-- html code generated by txt2tags 2.3 (http://txt2tags.sf.net) -->
<!-- cmdline: txt2tags -t html -i www/index.t2t -o htdocs/index.html -->
</BODY></HTML>

@ -0,0 +1,120 @@
body {
color: #181818;
background-color: #E0E4F0;
font: normal 10pt sans-serif;
max-width: 30em;
margin: 25pt;
}
.body h1 {
margin: 2em 0em 0em 0em;
font-size: 14pt;
}
.body h2 {
margin: 1.5em 0em 0em 0em;
font-size: 12pt;
}
.body h3 {
margin: 1em 0em 0em 0em;
font-size: 10pt;
}
.body p, .body ul, .body ol {
margin-top: 0.5em;
}
.body li {
margin-top: 0.5em;
}
a {
text-decoration: none;
}
hr {
margin-top: 3em;
}
.header h1 {
text-align: center;
padding: 0.3em;
border: 1pt solid black;
}
code, pre {
font-family: fixed;
font-style: normal;
font-size: 9pt;
line-height: 9pt;
background-color: #E8ECF8;
}
pre {
padding: 2pt;
}
div.toc {
margin-top: 3em;
line-height: 6pt;
}
.toc ul {
padding-left: 1.6em;
margin: 0em;
line-height: 10pt;
}
.toc li {
margin: 0em;
padding: 0em;
list-style-type: none;
}
.toc a {
color: #091;
}
#page-HISTORY DL DT {
font-weight: bold;
margin-top: 2em;
}
#page-HISTORY DL DD UL {
margin-top: 0pt;
padding-left: 0pt;
}
#main_menu {
margin: 0;
padding: 0;
}
#main_menu li {
margin: 0;
padding: 0;
display: inline;
}
#main_menu a {
padding: 3px 3px 2px 4px;
text-decoration:none;
font:bold 8pt/8pt Arial, Helvetica, sans-serif;
border: 1px solid #000;
}
#main_menu a:link,
#main_menu a:visited {
color: #fff;
background: #777;
}
#main_menu a:hover {
color: #000;
background: #777;
}
#page-README #main_menu-README a,
#page-COPYRIGHT #main_menu-COPYRIGHT a,
#page-userguide #main_menu-userguide a,
#page-HISTORY #main_menu-HISTORY a,
#page-DEVEL #main_menu-DEVEL a {
color: #000;
background: #aaa;
}
#page-README #main_menu-README a:hover,
#page-COPYRIGHT #main_menu-COPYRIGHT a:hover,
#page-userguide #main_menu-userguide a:hover,
#page-HISTORY #main_menu-HISTORY a:hover,
#page-DEVEL #main_menu-DEVEL a:hover {
color: #000;
background: #aaa;
}
#nav a:active {
color: #000;
background: #aaa;
}

@ -0,0 +1,425 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<META NAME="generator" CONTENT="http://txt2tags.sf.net">
<LINK REL="stylesheet" TYPE="text/css" HREF="style.css">
<TITLE>LeMock</TITLE>
</HEAD>
<BODY>
<DIV CLASS="header" ID="header">
<H1>LeMock</H1>
</DIV>
<DIV CLASS="body" ID="page-userguide">
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
<DIV CLASS="toc" ID="toc">
<UL>
<LI><A HREF="#toc1">Introduction</A>
<LI><A HREF="#toc2">The Mock Object</A>
<UL>
<LI><A HREF="#toc3">Actions</A>
<LI><A HREF="#anyargs">Anyargs</A>
</UL>
<LI><A HREF="#toc5">The Controller</A>
<UL>
<LI><A HREF="#toc6">Returns &amp; Error</A>
<LI><A HREF="#toc7">Label &amp; Depend</A>
<LI><A HREF="#toc8">Times</A>
<LI><A HREF="#toc9">Close</A>
</UL>
<LI><A HREF="#toc10">Tricks</A>
<UL>
<LI><A HREF="#toc11">Method Overloading</A>
</UL>
</UL>
</DIV>
<A NAME="toc1"></A>
<H1>Introduction</H1>
<P>
Mock objects replace difficult external objects during unit testing by
simulating the behaviors of the replaced objects. This is done by first
recording actions and their responses with the mock objects, and then
switching to replay mode. During replay mode the mock objects simulate the
replaced objects by looking up actions and replaying the recorded
responses, and finally verifying that all expected actions where completely
replayed.
</P>
<P>
Actions are stored in a list in a special controller object. During replay
the list is searched in recording order for the first matching action that
can be replayed.
</P>
<P>
Restrictions on the actions can be inserted during the recording phase. An
action can have a maximum count of how many times it will be replayed, and
a minimum count of how many times it must be replayed to be satisfied. An
action can depend on any set of other actions, and can not be replayed
before all of its depended actions are satisfied. An action can close any
set of actions when it is replayed, which stops all further replaying of
the closed actions. This is good for simulating state changes.
</P>
<H3>Example</H3>
<P>
This example tests that the insert_data function of the foo module handles
a missing data base table gracefully.
</P>
<PRE>
-- Setup
require 'lemock'
local mc = lemock.controller()
local sqlite3 = mc:mock()
local env = mc:mock()
local con = mc:mock()
package.loaded.luasql = nil
package.preload['luasql.sqlite3'] = function ()
luasql = {}
luasql.sqlite3 = sqlite3
return sqlite3
end
-- Record
sqlite3() ;mc :returns(env)
env:connect('/data/base') ;mc :returns(con)
con:execute(mc.ANYARGS) ;mc :error('LuaSQL: no such table')
con:close()
env:close()
-- Replay
mc:replay()
require 'foo'
local res = foo.insert_data(17)
assert(res==false)
--Verify
mc:verify()
</PRE>
<P></P>
<P>
First a controller is created. Then three mock objects are created, one for
the sqlite3 module, and two for objects returned by the (simulated) module.
</P>
<P>
Then a preloader for the sqlite3 module is installed, which returns the
sqlite3 mock object instead of the actual sqlite3 module.
</P>
<P>
In the record phase the expected calls and their return values (or thrown
errors) are recorded. The order is not significant, so this simplified test
will not detect if the close method is called before the execute method.
</P>
<P>
In the replay phase the tested module is loaded and executed. It will use
the mock objects instead of the real data base, and if it makes any
unrecorded calls, an error is thrown.
</P>
<P>
The verify phase asserts that all recorded actions have been replayed. If
the foo module for example forgets to call the close method, verify throws
an error.
</P>
<A NAME="toc2"></A>
<H1>The Mock Object</H1>
<P>
Mock objects are empty objects with special Lua meta methods that detect
actions performed with the object. What happens depends on the state
(recording or replaying) of the controller which created the mock object.
During recording the mock object adds the action to the controller's list
of recorded actions. During replay the mock object looks for a matching
recorded action that can be replayed, and simulates the action.
</P>
<P>
Some action attributes can not be inferred by the mock objects, for example
return values. These attributes have to be added afterwards with special
controller methods, and always affect the last recorded action.
</P>
<A NAME="toc3"></A>
<H2>Actions</H2>
<P>
Mock objects detect four types of actions: assignment, indexing, method
call, and self call. During replay an action will only match if it is the
very same action, that is, the same type of action performed on the same
mock object with all the same arguments. There are however
<A HREF="#anyargs">special arguments</A> that can be used during recording.
</P>
<PRE>
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m.x = 17 -- assignment
r = m.x -- indexing
m.x(1,2,3) -- method call
m:x(1,2,3) -- method call
m(1,2,3) -- self call
</PRE>
<A NAME="anyargs"></A>
<H2>Anyargs</H2>
<P>
An <I>anyarg</I> is a special argument used when recording, that will match
any argument during replay. It can appear anywhere and any times in an
argument list, or as the argument in an assignment, to replace real
arguments. There is also <I>anyargs</I>, which will match any number
(including zero) of any arguments. Anyargs can only appear as the last
argument of an argument list. Anyarg and anyargs are handy when the actual
values of the arguments during replay are unimportant or unknown.
</P>
<P>
Anyarg and anyargs are constants defined in the controller object.
</P>
<H3>Example</H3>
<P>
This example tests that the fetch_data function of module foo waits a while
and retries when no data is immediately available, and that it updates the
value of lasttime.
</P>
<PRE>
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:poll() ;mc :returns(nil)
con:sleep(mc.ANYARG)
con:poll() ;mc :returns('123.45')
con.lasttime = mc.ANYARG
mc:replay()
require 'foo'
local res = foo.fetch_data(con)
assert( math.abs(res-123.45) &lt; 0.0005 )
mc:verify()
</PRE>
<A NAME="toc5"></A>
<H1>The Controller</H1>
<P>
The controller's main purpose is to store the recorded actions, create mock
objects, switch to replay mode, and verify the completion of the replay
phase. But it is also needed to set or change special action attributes
during recording.
</P>
<P>
It is possible, although doubtfully useful, to use several controllers in
parallel during a single unit test. Each controller maintains its own
action list and state, and mock objects remember which controller they
belong to.
</P>
<A NAME="toc6"></A>
<H2>Returns &amp; Error</H2>
<P>
The by far most useful special action attribute is the return value.
Indexing actions can return a single value, while call actions and self
call actions can return a list of values. The return value is set with the
<I>returns</I> method, and it is an error to set the return value twice for
the same action.
</P>
<P>
For purposes of unit testing it is often useful to simulate errors. All
actions can raise an error, and return an error value (usually a string).
The return value is set with the <I>error</I> method. An action can not have
both a return value and raise an error.
</P>
<H3>Example</H3>
<PRE>
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m:foo(17) ;mc :returns(nil, "index out of range")
m:bar(-1) ;mc :error("invalid index")
</PRE>
<A NAME="toc7"></A>
<H2>Label &amp; Depend</H2>
<P>
Dependencies block actions from replaying until other actions have replayed
first. They can be used to verify that actions are being replayed in a
valid order.
</P>
<P>
To add dependencies, actions must first be labeled with one or more
<I>labels</I>. The same label can be given to several actions. As long as some
action with the label remains unsatisfied, that label is blocked, and all
actions depending on that label will not replay.
</P>
<H3>Example</H3>
<P>
This (contrived) example tests that function draw_square in module foo
calls all the necessary drawing methods of a square object in a correct
order. Note that there can be more than one correct order.
</P>
<PRE>
require 'lemock'
local mc = lemock.controller()
local square = mc:mock()
square:topleft() ;mc :label('tl')
square:topright() ;mc :label('tr')
square:botleft() ;mc :label('bl')
square:botright() ;mc :label('br')
square:leftedge() ;mc :label('edge') :depend('tl', 'bl')
square:rightedge() ;mc :label('edge') :depend('tr', 'br')
square:topedge() ;mc :label('edge') :depend('tl', 'tr')
square:botedge() ;mc :label('edge') :depend('bl', 'br')
square:fill() ;mc :depend('edge')
mc:replay()
require 'foo'
foo.draw_square( square )
mc:verify()
</PRE>
<P></P>
<P>
This example demonstrates two different ways of using dependencies. All the
corners have unique labels, because each edge depend on a set of specific
corners. But all the edges have the same label, because the fill operation
only depends on <I>all</I> edges have been satisfied.
</P>
<A NAME="toc8"></A>
<H2>Times</H2>
<P>
The default for a recorded action is to be replayed exactly once.
<CODE>times(2)</CODE> changes that to exactly two times, and <CODE>times(1,2)</CODE> changes
it to at least one time and at most two times.
</P>
<P>
When the action has been replayed the least count times it is
<I>satisfied</I>, which means verify will not complain about it, and it no
longer blocks actions that depend on this action from being replayed. If
the least count is zero the action is automatically satisfied and need not
be replayed at all, i.e., it is optional.
</P>
<P>
When the action has been replayed the most count times it will not replay
any more. The most replay count can be set to infinity (<CODE>math.huge</CODE> or
<CODE>1/0</CODE>), in which case the action will never stop replaying.
</P>
<P>
<CODE>anytimes()</CODE> can be used as an alias for <CODE>times(0,1/0)</CODE>, and
<CODE>atleastonce()</CODE> can be used as an alias for <CODE>times(1,1/0)</CODE>.
</P>
<H3>Example</H3>
<P>
This example tests that method update is called at least once.
</P>
<PRE>
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:log(mc.ANYARGS) ;mc :anytimes()
con:update('x',3) ;mc :returns(true) :atleastonce()
mc:replay()
require 'foo'
local watcher = foo.mk_watcher( con )
watcher:set( 'x', 3 )
mc:verify()
</PRE>
<A NAME="toc9"></A>
<H2>Close</H2>
<P>
Close can be used to simulate state changes in a limited way. When an
action with a close statement is replayed for the first time, it will
permanently block all labels in its close statement, so that actions with
these labels no longer replays. This passes on matching to later actions in
the action list, which may for example have different return values.
</P>
<P>
The closing simply blocks the labels, and it has nothing to do with max
replay counts or if closed actions have been satisfied or not. Closing an
unsatisfied action however results in an immediate failure.
</P>
<H3>Example</H3>
<P>
This example tests that the dump function of module foo calls the myio
functions in a correct order. The read function can be called any number of
times, until it is closed by the close function.
</P>
<PRE>
require 'lemock'
local mc = lemock.controller()
local myio = mc:mock()
local fs = mc:mock()
myio.open('abc', 'r') ;mc :returns(fs)
mc :label('open')
fs:read(mc.ANYARG) ;mc :returns('data')
mc :atleastonce() :label('read') :depend('open')
fs:close() ;mc :returns(true)
mc :depend('open') :close('read')
mc:replay()
require 'foo'
foo.dump(myio, 'abc', 128)
mc:verify()
</PRE>
<A NAME="toc10"></A>
<H1>Tricks</H1>
<P>
Mock objects are completely empty, and do not contain any methods or
properties of their own. If they did, that would risk shadowing a name of a
simulated object's method or property. There is however nothing preventing
users from defining methods and properties in mock objects. This way mock
objects can be turned into stubs, or a kind of mockstub hybrid.
</P>
<A NAME="toc11"></A>
<H2>Method Overloading</H2>
<P>
Lua does not support method overloading, but it can be (and sometimes is)
implemented manually by testing of function arguments. This presents a
problem to LeMock, because it matches exact arguments, and anyargs in not
sufficient. In this case the mock object can be extended with a dispatcher
function.
</P>
<H3>Example</H3>
<P>
This example shows a mock object with an overloaded add function. The stub
function can not be defined in the usual way, because that would record an
assignment action; it needs to be defined with <I>rawset</I>.
</P>
<PRE>
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
do
local function add (a, b)
if type(a) == 'number' then
return m.add_number(a, b)
else
return m.add_string(a, b)
end
end
rawset( m, 'add', add ) -- not recorded
end -- do
m.add_number(1, 2) ;mc :returns(3)
m.add_string('foo', 'bar') ;mc :returns('foobar')
mc:replay()
assert_equal( 3, m.add(1, 2) )
assert_equal( 'foobar', m.add('foo', 'bar') )
mc:verify()
</PRE>
<HR NOSHADE SIZE=1>
<P>
2009-05-31
</P>
</DIV>
<!-- html code generated by txt2tags 2.3 (http://txt2tags.sf.net) -->
<!-- cmdline: txt2tags -t html -\-toc -\-toc-level 2 -i www/userguide.t2t -o htdocs/userguide.html -->
</BODY></HTML>

@ -0,0 +1,659 @@
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
module( 'lemock', package.seeall )
_VERSION = "LeMock 0.6"
_COPYRIGHT = "Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>"
local class, object, qtostring, sfmt, add_to_set
local elements_of_set, value_equal
function object (class)
return setmetatable( {}, class )
end
function class (parent)
local c = object(parent)
c.__index = c
return c
end
sfmt = string.format
function qtostring (v)
if type(v) == 'string' then
return sfmt( '%q', v )
else
return tostring( v )
end
end
function add_to_set (o, setname, element)
if not o[setname] then
o[setname] = {}
end
local l = o[setname]
for i = 1, #l do
if l[i] == element then return end
end
l[#l+1] = element
end
function elements_of_set (o, setname)
local l = o[setname]
local i = l and #l+1 or 0
return function ()
i = i - 1
if i > 0 then return l[i] end
end
end
function value_equal (a, b)
if a == b then return true end
if a ~= a and b ~= b then return true end -- NaN == NaN
return false
end
local mock_controller_map = setmetatable( {}, {__mode='k'} )
-- All the classes are private
local Action, Argv, Callable, Controller, Mock
Action = {}
-- abstract
Action.generic = class()
function Action.generic:add_close (label)
add_to_set( self, 'closelist', label )
end
function Action.generic:add_depend (d)
add_to_set( self, 'dependlist', d )
end
function Action.generic:add_label (label)
add_to_set( self, 'labellist', label )
end
function Action.generic:assert_satisfied ()
assert( self.replay_count <= self.max_replays, "lemock internal error" )
if not (
self.min_replays <= self.replay_count
) then
error( sfmt( "Wrong replay count %d (expected %d..%d) for %s"
, self.replay_count
, self.min_replays, self.max_replays
, self:tostring()
)
, 0
)
end
end
function Action.generic:blocks ()
if self:is_satisfied() then
return function () end
end
return elements_of_set( self, 'labellist' )
end
function Action.generic:closes ()
return elements_of_set( self, 'closelist' )
end
function Action.generic:depends ()
return elements_of_set( self, 'dependlist' )
end
function Action.generic:has_label (l)
for x in elements_of_set( self, 'labellist' ) do
if x == l then return true end
end
return false
end
function Action.generic:is_expected ()
return self.replay_count < self.max_replays
and not self.is_blocked
and not self.is_closed
end
function Action.generic:is_satisfied ()
return
self.min_replays <= self.replay_count
end
function Action.generic:match (key)
if getmetatable(self) ~= getmetatable(key) then return false end
if self.mock ~= key.mock then return false end
return self:is_expected()
end
function Action.generic:new (mock)
local a = object( self )
a.mock = mock
a.replay_count = 0
a.min_replays = 1
a.max_replays = 1
return a
end
function Action.generic:set_times (a, b)
min = a or 1
max = b or min
min, max = tonumber(min), tonumber(max)
if (not min) or (not max) or (min >= math.huge)
or (min ~= min) or (max ~= max) -- NaN
or (min < 0) or (max <= 0) or (min > max) then
error( sfmt( "Unrealistic time arguments (%s, %s)"
, qtostring( min )
, qtostring( max )
)
, 0
)
end
self.min_replays = min
self.max_replays = max
end
Action.generic_call = class( Action.generic )
Action.generic_call.can_return = true
function Action.generic_call:get_returnvalue ()
if self.has_returnvalue then
return self.returnvalue:unpack()
end
end
function Action.generic_call:set_returnvalue (...)
self.returnvalue = Argv:new(...)
self.has_returnvalue = true
end
function Action.generic_call:match (q)
if not Action.generic.match( self, q ) then return false end
if not self.argv:equal( q.argv ) then return false end
return true
end
function Action.generic_call:new (m, ...)
local a = Action.generic.new( self, m )
a.argv = Argv:new(...)
return a
end
-- concrete
Action.call = class( Action.generic_call )
function Action.call:match (q)
if not Action.generic_call.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
function Action.call:new (m, key, ...)
local a = Action.generic_call.new( self, m, ... )
a.key = key
return a
end
function Action.call:tostring ()
if self.has_returnvalue then
return sfmt( "call %s(%s) => %s"
, tostring(self.key)
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "call %s(%s)"
, tostring(self.key)
, self.argv:tostring()
)
end
end
Action.index = class( Action.generic )
Action.index.can_return = true
function Action.index:get_returnvalue ()
return self.returnvalue
end
function Action.index:set_returnvalue (v)
self.returnvalue = v
self.has_returnvalue = true
end
function Action.index:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
function Action.index:new (m, key)
local a = Action.generic.new( self, m )
a.key = key
return a
end
function Action.index:tostring ()
local key = 'index '..tostring( self.key )
if self.has_returnvalue then
return sfmt( "index %s => %s"
, tostring( self.key )
, qtostring( self.returnvalue )
)
elseif self.is_callable then
return sfmt( "index %s()"
, tostring( self.key )
)
else
return sfmt( "index %s"
, tostring( self.key )
)
end
end
Action.newindex = class( Action.generic )
function Action.newindex:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
if not value_equal( self.val, q.val )
and self.val ~= Argv.ANYARG
and q.val ~= Argv.ANYARG then return false end
return true
end
function Action.newindex:new (m, key, val)
local a = Action.generic.new( self, m )
a.key = key
a.val = val
return a
end
function Action.newindex:tostring ()
return sfmt( "newindex %s = %s"
, tostring(self.key)
, qtostring(self.val)
)
end
Action.selfcall = class( Action.generic_call )
function Action.selfcall:match (q)
return Action.generic_call.match( self, q )
end
function Action.selfcall:new (m, ...)
local a = Action.generic_call.new( self, m, ... )
return a
end
function Action.selfcall:tostring ()
if self.has_returnvalue then
return sfmt( "selfcall (%s) => %s"
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "selfcall (%s)"
, self.argv:tostring()
)
end
end
Argv = class()
Argv.ANYARGS = newproxy() local ANYARGS = Argv.ANYARGS
Argv.ANYARG = newproxy() local ANYARG = Argv.ANYARG
function Argv:equal (other)
local a1, n1 = self.v, self.len
local a2, n2 = other.v, other.len
if n1-1 <= n2 and a1[n1] == ANYARGS then
n1 = n1-1
n2 = n1
elseif n2-1 <= n1 and a2[n2] == ANYARGS then
n2 = n2-1
n1 = n2
end
if n1 ~= n2 then
return false
end
for i = 1, n1 do
local v1, v2 = a1[i], a2[i]
if not value_equal(v1,v2) and v1 ~= ANYARG and v2 ~= ANYARG then
return false
end
end
return true
end
function Argv:new (...)
local av = object( self )
av.v = {...}
av.len = select('#',...)
for i = 1, av.len - 1 do
if av.v[i] == Argv.ANYARGS then
error( "ANYARGS not at end.", 0 )
end
end
return av
end
function Argv:tostring ()
local res = {}
local function w (v)
res[#res+1] = qtostring( v )
end
local av, ac = self.v, self.len
for i = 1, ac do
if av[i] == Argv.ANYARG then
res[#res+1] = 'ANYARG'
elseif av[i] == Argv.ANYARGS then
res[#res+1] = 'ANYARGS'
else
w( av[i] )
end
if i < ac then
res[#res+1] = ',' -- can not use qtostring in w()
end
end
return table.concat( res )
end
function Argv:unpack ()
return unpack( self.v, 1, self.len )
end
Callable = {}
Callable.generic = class()
Callable.record = class( Callable.generic )
Callable.replay = class( Callable.generic )
function Callable.generic:new ( index_action )
local f = object( self )
f.action = index_action
return f
end
function Callable.record:__call (...)
local index_action = self.action
local m = index_action.mock
local mc = mock_controller_map[m]
assert( mc.is_recording, "client uses cached callable from recording" )
mc:make_callable( index_action )
mc:add_action( Action.call:new( m, index_action.key, ... ))
end
function Callable.replay:__call (...)
local index_action = self.action
local m = index_action.mock
local mc = mock_controller_map[m]
local call_action = mc:lookup( Action.call:new( m, index_action.key, ... ))
mc:replay_action( call_action )
if call_action.throws_error then
error( call_action.errorvalue, 2 )
end
return call_action:get_returnvalue()
end
Controller = class()
-- Exported methods
function Controller:close (...)
if not self.is_recording then
error( "Can not insert close in replay mode.", 2 )
end
local action = self:get_last_action()
for _, close in ipairs{ ... } do
action:add_close( close )
end
return self -- for chaining
end
function Controller:depend (...)
if not self.is_recording then
error( "Can not add dependency in replay mode.", 2 )
end
local action = self:get_last_action()
for _, dependency in ipairs{ ... } do
action:add_depend( dependency )
end
return self -- for chaining
end
function Controller:error (value)
if not self.is_recording then
error( "Error called during replay.", 2 )
end
local action = self:get_last_action()
if action.has_returnvalue or action.throws_error then
error( "Returns and/or Error called twice for same action.", 2 )
end
action.throws_error = true
action.errorvalue = value
return self -- for chaining
end
function Controller:label (...)
if not self.is_recording then
error( "Can not add labels in replay mode.", 2 )
end
local action = self:get_last_action()
for _, label in ipairs{ ... } do
action:add_label( label )
end
return self -- for chaining
end
function Controller:mock ()
if not self.is_recording then
error( "New mock during replay.", 2 )
end
local m = object( Mock.record )
mock_controller_map[m] = self
return m
end
function Controller:new ()
local mc = object( self )
mc.actionlist = {}
mc.is_recording = true
return mc
end
function Controller:replay ()
if not self.is_recording then
error( "Replay called twice.", 2 )
end
self.is_recording = false
for m, mc in pairs( mock_controller_map ) do
if mc == self then
setmetatable( m, Mock.replay )
end
end
self:update_dependencies()
self:assert_no_dependency_cycles()
end
function Controller:returns (...)
if not self.is_recording then
error( "Returns called during replay.", 2 )
end
local action = self:get_last_action()
assert( not action.is_callable, "lemock internal error" )
if not action.can_return then
error( "Previous action can not return anything.", 2 )
end
if action.has_returnvalue or action.throws_error then
error( "Returns and/or Error called twice for same action.", 2 )
end
action:set_returnvalue(...)
return self -- for chaining
end
function Controller:times (min, max)
if not self.is_recording then
error( "Can not set times in replay mode.", 0 )
end
self:get_last_action():set_times( min, max )
return self -- for chaining
end
-- convenience functions
function Controller:anytimes() return self:times( 0, math.huge ) end
function Controller:atleastonce() return self:times( 1, math.huge ) end
function Controller:verify ()
if self.is_recording then
error( "Verify called during record.", 2 )
end
for a in self:actions() do
a:assert_satisfied()
end
end
-- Protected methods
function Controller:actions (q)
local l = self.actionlist
local i = 0
return function ()
i = i + 1
return l[i]
end
end
function Controller:add_action (a)
assert( a ~= nil, "lemock internal error" ) -- breaks array property
table.insert( self.actionlist, a )
end
function Controller:assert_no_dependency_cycles ()
local function is_in_path (label, path)
if not path then return false end -- is root
for _, l in ipairs( path ) do
if l == label then return true end
end
if path.prev then return is_in_path( label, path.prev ) end
return false
end
local function can_block (action, node)
for _, label in ipairs( node ) do
if action:has_label( label ) then return true end
end
return false
end
local function step (action, path)
local new_head
for label in action:depends() do
if is_in_path( label, path ) then
error( "Detected dependency cycle", 0 )
end
-- only create table if needed to reduce garbage
if not new_head then new_head = { prev=path } end
new_head[#new_head+1] = label
end
return new_head
end
local function search_depth_first (path)
for action in self:actions() do
if can_block( action, path ) then
local new_head = step( action, path )
if new_head then
search_depth_first( new_head )
end
end
end
end
for action in self:actions() do
local root = step( action, nil )
if root then search_depth_first( root ) end
end
end
function Controller:close_actions( ... ) -- takes iterator
for label in ... do
for candidate in self:actions() do
if candidate:has_label( label ) then
if not candidate:is_satisfied() then
error( "Closes unsatisfied action: "..candidate:tostring(), 0 )
end
candidate.is_closed = true
end
end
end
end
function Controller:get_last_action ()
local l = self.actionlist
if #l == 0 then
error( "No action is recorded yet.", 0 )
end
return l[#l]
end
function Controller:lookup (actual)
for action in self:actions() do
if action:match( actual ) then
return action
end
end
local expected = {}
for _, a in ipairs( self.actionlist ) do
if a:is_expected() and not a.is_callable then
expected[#expected+1] = a:tostring()
end
end
table.sort( expected )
if #expected == 0 then
expected[1] = "(Nothing)"
end
error( sfmt( "Unexpected action %s, expected:\n%s\n"
, actual:tostring()
, table.concat(expected,'\n')
)
, 0
)
end
function Controller:make_callable (action)
if action.has_returnvalue then
error( "Can not call "..action.key..". It has a returnvalue.", 0 )
end
action.is_callable = true
action.min_replays = 0
action.max_replays = math.huge
end
function Controller:new ()
local mc = object( self )
mc.actionlist = {}
mc.is_recording = true
return mc
end
function Controller:replay_action ( action )
assert( action:is_expected(), "lemock internal error" )
assert( action.replay_count < action.max_replays, "lemock internal error" )
local was_satisfied = action:is_satisfied()
action.replay_count = action.replay_count + 1
if not was_satisfied and action.labellist and action:is_satisfied() then
self:update_dependencies()
end
if action.closelist then
self:close_actions( action:closes() )
end
end
function Controller:update_dependencies ()
local blocked = {}
for action in self:actions() do
for label in action:blocks() do
blocked[label] = true
end
end
local function is_blocked (action)
for label in action:depends() do
if blocked[label] then return true end
end
return false
end
for action in self:actions() do
action.is_blocked = is_blocked( action )
end
end
Mock = { record={}, replay={} } -- no self-referencing __index!
function Mock.record:__index (key)
local mc = mock_controller_map[self]
local action = Action.index:new( self, key )
mc:add_action( action )
return Callable.record:new( action )
end
function Mock.record:__newindex (key, val)
local mc = mock_controller_map[self]
mc:add_action( Action.newindex:new( self, key, val ))
end
function Mock.record:__call (...)
local mc = mock_controller_map[self]
mc:add_action( Action.selfcall:new( self, ... ))
end
function Mock.replay:__index (key)
local mc = mock_controller_map[self]
local index_action = mc:lookup( Action.index:new( self, key ))
mc:replay_action( index_action )
if index_action.throws_error then
error( index_action.errorvalue, 2 )
end
if index_action.is_callable then
return Callable.replay:new( index_action )
else
return index_action:get_returnvalue()
end
end
function Mock.replay:__newindex (key, val)
local mc = mock_controller_map[self]
local newindex_action = mc:lookup( Action.newindex:new( self, key, val ))
mc:replay_action( newindex_action )
if newindex_action.throws_error then
error( newindex_action.errorvalue, 2 )
end
end
function Mock.replay:__call (...)
local mc = mock_controller_map[self]
local selfcall_action = mc:lookup( Action.selfcall:new( self, ... ))
mc:replay_action( selfcall_action )
if selfcall_action.throws_error then
error( selfcall_action.errorvalue, 2 )
end
return selfcall_action:get_returnvalue()
end
function controller ()
local exported_methods = {
'anytimes',
'atleastonce',
'close',
'depend',
'error',
'label',
'mock',
'new',
'replay',
'returns',
'times',
'verify',
}
local mc = Controller:new()
local wrapper = {}
for _, method in ipairs( exported_methods ) do
wrapper[ method ] = function (self, ...)
return mc[ method ]( mc, ... )
end
end
wrapper.ANYARG = Argv.ANYARG
wrapper.ANYARGS = Argv.ANYARGS
return wrapper
end
return _M

@ -0,0 +1,20 @@
MKSHELL = rc
all:V: htdocs
wrappers = `{find www -name '*.t2t'}
htmls = ${wrappers:www/%.t2t=htdocs/%.html}
htdocs:V: $htmls
$htmls: www/menubar.html
htdocs/COPYRIGHT.html: ../COPYRIGHT
htdocs/DEVEL.html: ../DEVEL
htdocs/HISTORY.html: ../HISTORY
htdocs/README.html: ../README
htdocs/%.html: www/%.t2t
txt2tags -t html -i www/$stem.t2t -o $target
htdocs/userguide.html: www/userguide.t2t userguide.t2t
txt2tags -t html --toc --toc-level 2 -i www/userguide.t2t -o $target

@ -0,0 +1,603 @@
-- ../src/unittestfiles.nw:145
-- ../src/misc.nw:7
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
-- ../src/unittestfiles.nw:146
require 'lunit'
module( 'unit.action', lunit.testcase, package.seeall )
local class, object, qtostring, sfmt
-- ../src/helperfunctions.nw:12
function object (class)
return setmetatable( {}, class )
end
function class (parent)
local c = object(parent)
c.__index = c
return c
end
-- ../src/unittestfiles.nw:152
-- ../src/helperfunctions.nw:29
function value_equal (a, b)
if a == b then return true end
if a ~= a and b ~= b then return true end -- NaN == NaN
return false
end
-- ../src/unittestfiles.nw:153
-- ../src/tostring.nw:23
sfmt = string.format
function qtostring (v)
if type(v) == 'string' then
return sfmt( '%q', v )
else
return tostring( v )
end
end
-- ../src/unittestfiles.nw:154
local Action, Argv
-- ../src/class/action.nw:24
Action = {}
-- abstract
-- ../src/class/action.nw:41
Action.generic = class()
-- ../src/restrictions.nw:607
function Action.generic:add_close (label)
add_to_set( self, 'closelist', label )
end
-- ../src/class/action.nw:44
-- ../src/restrictions.nw:443
function Action.generic:add_depend (d)
add_to_set( self, 'dependlist', d )
end
-- ../src/class/action.nw:45
-- ../src/restrictions.nw:207
function Action.generic:add_label (label)
add_to_set( self, 'labellist', label )
end
-- ../src/class/action.nw:46
-- ../src/main.nw:338
function Action.generic:assert_satisfied ()
assert( self.replay_count <= self.max_replays, "lemock internal error" )
if not (
-- ../src/main.nw:330
self.min_replays <= self.replay_count
-- ../src/main.nw:340
) then
error( sfmt( "Wrong replay count %d (expected %d..%d) for %s"
, self.replay_count
, self.min_replays, self.max_replays
, self:tostring()
)
, 0
)
end
end
-- ../src/class/action.nw:47
-- ../src/restrictions.nw:220
function Action.generic:blocks ()
if self:is_satisfied() then
return function () end
end
return elements_of_set( self, 'labellist' )
end
-- ../src/class/action.nw:48
-- ../src/restrictions.nw:630
function Action.generic:closes ()
return elements_of_set( self, 'closelist' )
end
-- ../src/class/action.nw:49
-- ../src/restrictions.nw:448
function Action.generic:depends ()
return elements_of_set( self, 'dependlist' )
end
-- ../src/class/action.nw:50
-- ../src/restrictions.nw:212
function Action.generic:has_label (l)
for x in elements_of_set( self, 'labellist' ) do
if x == l then return true end
end
return false
end
-- ../src/class/action.nw:51
-- ../src/main.nw:247
function Action.generic:is_expected ()
return self.replay_count < self.max_replays
and not self.is_blocked
and not self.is_closed
end
-- ../src/class/action.nw:52
-- ../src/main.nw:333
function Action.generic:is_satisfied ()
return
-- ../src/main.nw:330
self.min_replays <= self.replay_count
-- ../src/main.nw:335
end
-- ../src/class/action.nw:53
-- ../src/main.nw:269
function Action.generic:match (key)
if getmetatable(self) ~= getmetatable(key) then return false end
if self.mock ~= key.mock then return false end
return self:is_expected()
end
-- ../src/class/action.nw:54
-- ../src/main.nw:219
function Action.generic:new (mock)
local a = object( self )
a.mock = mock
a.replay_count = 0
a.min_replays = 1
a.max_replays = 1
return a
end
-- ../src/class/action.nw:55
-- ../src/restrictions.nw:102
function Action.generic:set_times (a, b)
min = a or 1
max = b or min
min, max = tonumber(min), tonumber(max)
if (not min) or (not max) or (min >= math.huge)
or (min ~= min) or (max ~= max) -- NaN
or (min < 0) or (max <= 0) or (min > max) then
error( sfmt( "Unrealistic time arguments (%s, %s)"
, qtostring( min )
, qtostring( max )
)
, 0
)
end
self.min_replays = min
self.max_replays = max
end
-- ../src/class/action.nw:28
-- ../src/class/action.nw:59
Action.generic_call = class( Action.generic )
Action.generic_call.can_return = true
-- ../src/action/generic_call.nw:76
function Action.generic_call:get_returnvalue ()
if self.has_returnvalue then
return self.returnvalue:unpack()
end
end
-- ../src/class/action.nw:63
-- ../src/action/generic_call.nw:56
function Action.generic_call:set_returnvalue (...)
self.returnvalue = Argv:new(...)
self.has_returnvalue = true
end
-- ../src/class/action.nw:64
-- ../src/action/generic_call.nw:45
function Action.generic_call:match (q)
if not Action.generic.match( self, q ) then return false end
if not self.argv:equal( q.argv ) then return false end
return true
end
-- ../src/class/action.nw:66
-- ../src/action/generic_call.nw:32
function Action.generic_call:new (m, ...)
local a = Action.generic.new( self, m )
a.argv = Argv:new(...)
return a
end
-- ../src/class/action.nw:29
-- concrete
-- ../src/class/action.nw:93
Action.call = class( Action.generic_call )
-- ../src/action/call.nw:118
function Action.call:match (q)
if not Action.generic_call.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
-- ../src/class/action.nw:96
-- ../src/action/call.nw:82
function Action.call:new (m, key, ...)
local a = Action.generic_call.new( self, m, ... )
a.key = key
return a
end
-- ../src/class/action.nw:97
-- ../src/tostring.nw:101
function Action.call:tostring ()
if self.has_returnvalue then
return sfmt( "call %s(%s) => %s"
, tostring(self.key)
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "call %s(%s)"
, tostring(self.key)
, self.argv:tostring()
)
end
end
-- ../src/class/action.nw:32
-- ../src/class/action.nw:81
Action.index = class( Action.generic )
Action.index.can_return = true
-- ../src/action/index.nw:134
function Action.index:get_returnvalue ()
return self.returnvalue
end
-- ../src/class/action.nw:85
-- ../src/action/index.nw:85
function Action.index:set_returnvalue (v)
self.returnvalue = v
self.has_returnvalue = true
end
-- ../src/class/action.nw:86
-- ../src/action/index.nw:123
function Action.index:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
-- ../src/class/action.nw:88
-- ../src/action/index.nw:67
function Action.index:new (m, key)
local a = Action.generic.new( self, m )
a.key = key
return a
end
-- ../src/class/action.nw:89
-- ../src/tostring.nw:70
function Action.index:tostring ()
local key = 'index '..tostring( self.key )
if self.has_returnvalue then
return sfmt( "index %s => %s"
, tostring( self.key )
, qtostring( self.returnvalue )
)
elseif self.is_callable then
return sfmt( "index %s()"
, tostring( self.key )
)
else
return sfmt( "index %s"
, tostring( self.key )
)
end
end
-- ../src/class/action.nw:33
-- ../src/class/action.nw:73
Action.newindex = class( Action.generic )
-- ../src/action/newindex.nw:102
function Action.newindex:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
if not value_equal( self.val, q.val )
and self.val ~= Argv.ANYARG
and q.val ~= Argv.ANYARG then return false end
return true
end
-- ../src/class/action.nw:76
-- ../src/action/newindex.nw:54
function Action.newindex:new (m, key, val)
local a = Action.generic.new( self, m )
a.key = key
a.val = val
return a
end
-- ../src/class/action.nw:77
-- ../src/tostring.nw:45
function Action.newindex:tostring ()
return sfmt( "newindex %s = %s"
, tostring(self.key)
, qtostring(self.val)
)
end
-- ../src/class/action.nw:34
-- ../src/class/action.nw:101
Action.selfcall = class( Action.generic_call )
-- ../src/action/selfcall.nw:93
function Action.selfcall:match (q)
return Action.generic_call.match( self, q )
end
-- ../src/class/action.nw:104
-- ../src/action/selfcall.nw:61
function Action.selfcall:new (m, ...)
local a = Action.generic_call.new( self, m, ... )
return a
end
-- ../src/class/action.nw:105
-- ../src/tostring.nw:129
function Action.selfcall:tostring ()
if self.has_returnvalue then
return sfmt( "selfcall (%s) => %s"
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "selfcall (%s)"
, self.argv:tostring()
)
end
end
-- ../src/unittestfiles.nw:157
-- ../src/class/argv.nw:6
Argv = class()
-- ../src/argv.nw:119
Argv.ANYARGS = newproxy() local ANYARGS = Argv.ANYARGS
Argv.ANYARG = newproxy() local ANYARG = Argv.ANYARG
function Argv:equal (other)
local a1, n1 = self.v, self.len
local a2, n2 = other.v, other.len
if n1-1 <= n2 and a1[n1] == ANYARGS then
n1 = n1-1
n2 = n1
elseif n2-1 <= n1 and a2[n2] == ANYARGS then
n2 = n2-1
n1 = n2
end
if n1 ~= n2 then
return false
end
for i = 1, n1 do
local v1, v2 = a1[i], a2[i]
if not value_equal(v1,v2) and v1 ~= ANYARG and v2 ~= ANYARG then
return false
end
end
return true
end
-- ../src/class/argv.nw:9
-- ../src/argv.nw:46
function Argv:new (...)
local av = object( self )
av.v = {...}
av.len = select('#',...)
for i = 1, av.len - 1 do
if av.v[i] == Argv.ANYARGS then
error( "ANYARGS not at end.", 0 )
end
end
return av
end
-- ../src/class/argv.nw:10
-- ../src/tostring.nw:163
function Argv:tostring ()
local res = {}
local function w (v)
res[#res+1] = qtostring( v )
end
local av, ac = self.v, self.len
for i = 1, ac do
if av[i] == Argv.ANYARG then
res[#res+1] = 'ANYARG'
elseif av[i] == Argv.ANYARGS then
res[#res+1] = 'ANYARGS'
else
w( av[i] )
end
if i < ac then
res[#res+1] = ',' -- can not use qtostring in w()
end
end
return table.concat( res )
end
-- ../src/class/argv.nw:11
-- ../src/argv.nw:156
function Argv:unpack ()
return unpack( self.v, 1, self.len )
end
-- ../src/unittestfiles.nw:158
-- ../src/action/call.nw:106
function call_match_test ()
local m = {}
local a = Action.call:new( m, 'foo', 4, 'bb' )
assert_true( a:match( Action.call:new( m, 'foo', 4, 'bb' )))
assert_false( a:match( Action.call:new( {}, 'foo', 4, 'bb' )))
assert_false( a:match( Action.call:new( m, 'bar', 4, 'bb' )))
assert_false( a:match( Action.call:new( m, 'foo', 1, 'bb' )))
assert_false( a:match( Action.call:new( m, 'foo', 4, 'b' )))
assert_false( a:match( Action.call:new( m, 'foo', 4, 'bb', 'cc' )))
end
-- ../src/unittestfiles.nw:160
-- ../src/tostring.nw:93
function call_tostring_test ()
local a = Action.call:new( {}, 'foo', 1, '"', 3 )
assert_equal( 'call foo(1,"\\"",3)', a:tostring() )
a:set_returnvalue( 'false', false )
assert_equal( 'call foo(1,"\\"",3) => "false",false', a:tostring() )
end
-- ../src/unittestfiles.nw:161
-- ../src/action/generic_call.nw:66
function generic_call_set_and_get_returnvalue_test ()
local a = Action.generic_call:new()
assert_equal( 0, select('#', a:get_returnvalue() ))
a:set_returnvalue( nil, false )
local r1, r2 = a:get_returnvalue()
assert_equal( nil, r1 )
assert_equal( false, r2 )
end
-- ../src/unittestfiles.nw:162
-- ../src/action/index.nw:114
function index_match_test ()
local m = {}
local a = Action.index:new( m, -1 )
assert_true( a:match( Action.index:new( m, -1 )))
assert_false( a:match( Action.index:new( {}, -1 )))
assert_false( a:match( Action.index:new( m, 'a' )))
end
-- ../src/unittestfiles.nw:163
-- ../src/action/index.nw:59
function create_index_action_test ()
local m = {}
local a = Action.index:new( m, 'foo' )
assert_equal( m, a.mock )
assert_equal( 'foo', a.key )
end
-- ../src/unittestfiles.nw:164
-- ../src/action/index.nw:78
function index_returnvalue_test ()
local a = Action.index:new( {}, -3 )
a:set_returnvalue( 'foo' )
assert_equal( 'foo', a:get_returnvalue() )
end
-- ../src/unittestfiles.nw:165
-- ../src/tostring.nw:57
function index_tostring_test ()
local a = Action.index:new( {}, true )
assert_equal( 'index true', a:tostring() )
a:set_returnvalue('"false"')
assert_equal( 'index true => "\\"false\\""', a:tostring() )
end
function callable_index_tostring_test ()
local a = Action.index:new( {}, 'f' )
a.is_callable = true
assert_equal( 'index f()', a:tostring() )
end
-- ../src/unittestfiles.nw:166
-- ../src/action/newindex.nw:76
function newindex_match_test ()
local m = {}
local a = Action.newindex:new( m, 'foo', 17 )
assert_true( a:match( Action.newindex:new( m, 'foo', 17 )))
assert_false( a:match( Action.newindex:new( {}, 'foo', 17 )))
assert_false( a:match( Action.newindex:new( m, 'fo', 17 )))
assert_false( a:match( Action.newindex:new( m, 'foo', 7 )))
end
function newindex_anyarg_test ()
local m = {}
local a = Action.newindex:new( m, 'foo', Argv.ANYARG )
local b = Action.newindex:new( m, 'foo', 33 )
local c = Action.newindex:new( m, 'foo', nil )
assert_true( a:match(b) )
assert_true( b:match(a) )
assert_true( a:match(c) )
assert_true( c:match(a) )
end
function newindex_NaN_test ()
local m = {}
local nan = 0/0
local a = Action.newindex:new( m, m, nan )
assert_true( a:match( Action.newindex:new( m, m, nan )))
end
-- ../src/unittestfiles.nw:167
-- ../src/tostring.nw:37
function newindex_tostring_test ()
local a = Action.newindex:new( {}, 'key', 7 )
assert_equal( 'newindex key = 7', a:tostring() )
a = Action.newindex:new( {}, true, '7' )
assert_equal( 'newindex true = "7"', a:tostring() )
end
-- ../src/unittestfiles.nw:168
-- ../src/action/selfcall.nw:82
function selfcall_match_test ()
local m = {}
local a = Action.selfcall:new( m, 5, nil, false )
assert_true( a:match( Action.selfcall:new( m, 5, nil, false )))
assert_false( a:match( Action.selfcall:new( {}, 5, nil, false )))
assert_false( a:match( Action.selfcall:new( m, nil, nil, false )))
assert_false( a:match( Action.selfcall:new( m, 5, false, false )))
assert_false( a:match( Action.selfcall:new( m, 5, nil, nil )))
end
-- ../src/unittestfiles.nw:169
-- ../src/tostring.nw:121
function selfcall_tostring_test ()
local a = Action.selfcall:new( {}, 1, '"', nil )
assert_equal( 'selfcall (1,"\\"",nil)', a:tostring() )
a:set_returnvalue( 'false', false )
assert_equal( 'selfcall (1,"\\"",nil) => "false",false', a:tostring() )
end

@ -0,0 +1,598 @@
-- ../src/unittestfiles.nw:108
-- ../src/misc.nw:7
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
-- ../src/unittestfiles.nw:109
require 'lunit'
module( 'unit.action_generic', lunit.testcase, package.seeall )
local class, object, qtostring, sfmt, add_to_set, elements_of_set
-- ../src/helperfunctions.nw:12
function object (class)
return setmetatable( {}, class )
end
function class (parent)
local c = object(parent)
c.__index = c
return c
end
-- ../src/unittestfiles.nw:115
-- ../src/tostring.nw:23
sfmt = string.format
function qtostring (v)
if type(v) == 'string' then
return sfmt( '%q', v )
else
return tostring( v )
end
end
-- ../src/unittestfiles.nw:116
-- ../src/helperfunctions.nw:47
function add_to_set (o, setname, element)
if not o[setname] then
o[setname] = {}
end
local l = o[setname]
for i = 1, #l do
if l[i] == element then return end
end
l[#l+1] = element
end
function elements_of_set (o, setname)
local l = o[setname]
local i = l and #l+1 or 0
return function ()
i = i - 1
if i > 0 then return l[i] end
end
end
-- ../src/unittestfiles.nw:117
local Action, Argv
-- ../src/class/action.nw:24
Action = {}
-- abstract
-- ../src/class/action.nw:41
Action.generic = class()
-- ../src/restrictions.nw:607
function Action.generic:add_close (label)
add_to_set( self, 'closelist', label )
end
-- ../src/class/action.nw:44
-- ../src/restrictions.nw:443
function Action.generic:add_depend (d)
add_to_set( self, 'dependlist', d )
end
-- ../src/class/action.nw:45
-- ../src/restrictions.nw:207
function Action.generic:add_label (label)
add_to_set( self, 'labellist', label )
end
-- ../src/class/action.nw:46
-- ../src/main.nw:338
function Action.generic:assert_satisfied ()
assert( self.replay_count <= self.max_replays, "lemock internal error" )
if not (
-- ../src/main.nw:330
self.min_replays <= self.replay_count
-- ../src/main.nw:340
) then
error( sfmt( "Wrong replay count %d (expected %d..%d) for %s"
, self.replay_count
, self.min_replays, self.max_replays
, self:tostring()
)
, 0
)
end
end
-- ../src/class/action.nw:47
-- ../src/restrictions.nw:220
function Action.generic:blocks ()
if self:is_satisfied() then
return function () end
end
return elements_of_set( self, 'labellist' )
end
-- ../src/class/action.nw:48
-- ../src/restrictions.nw:630
function Action.generic:closes ()
return elements_of_set( self, 'closelist' )
end
-- ../src/class/action.nw:49
-- ../src/restrictions.nw:448
function Action.generic:depends ()
return elements_of_set( self, 'dependlist' )
end
-- ../src/class/action.nw:50
-- ../src/restrictions.nw:212
function Action.generic:has_label (l)
for x in elements_of_set( self, 'labellist' ) do
if x == l then return true end
end
return false
end
-- ../src/class/action.nw:51
-- ../src/main.nw:247
function Action.generic:is_expected ()
return self.replay_count < self.max_replays
and not self.is_blocked
and not self.is_closed
end
-- ../src/class/action.nw:52
-- ../src/main.nw:333
function Action.generic:is_satisfied ()
return
-- ../src/main.nw:330
self.min_replays <= self.replay_count
-- ../src/main.nw:335
end
-- ../src/class/action.nw:53
-- ../src/main.nw:269
function Action.generic:match (key)
if getmetatable(self) ~= getmetatable(key) then return false end
if self.mock ~= key.mock then return false end
return self:is_expected()
end
-- ../src/class/action.nw:54
-- ../src/main.nw:219
function Action.generic:new (mock)
local a = object( self )
a.mock = mock
a.replay_count = 0
a.min_replays = 1
a.max_replays = 1
return a
end
-- ../src/class/action.nw:55
-- ../src/restrictions.nw:102
function Action.generic:set_times (a, b)
min = a or 1
max = b or min
min, max = tonumber(min), tonumber(max)
if (not min) or (not max) or (min >= math.huge)
or (min ~= min) or (max ~= max) -- NaN
or (min < 0) or (max <= 0) or (min > max) then
error( sfmt( "Unrealistic time arguments (%s, %s)"
, qtostring( min )
, qtostring( max )
)
, 0
)
end
self.min_replays = min
self.max_replays = max
end
-- ../src/class/action.nw:28
-- ../src/class/action.nw:59
Action.generic_call = class( Action.generic )
Action.generic_call.can_return = true
-- ../src/action/generic_call.nw:76
function Action.generic_call:get_returnvalue ()
if self.has_returnvalue then
return self.returnvalue:unpack()
end
end
-- ../src/class/action.nw:63
-- ../src/action/generic_call.nw:56
function Action.generic_call:set_returnvalue (...)
self.returnvalue = Argv:new(...)
self.has_returnvalue = true
end
-- ../src/class/action.nw:64
-- ../src/action/generic_call.nw:45
function Action.generic_call:match (q)
if not Action.generic.match( self, q ) then return false end
if not self.argv:equal( q.argv ) then return false end
return true
end
-- ../src/class/action.nw:66
-- ../src/action/generic_call.nw:32
function Action.generic_call:new (m, ...)
local a = Action.generic.new( self, m )
a.argv = Argv:new(...)
return a
end
-- ../src/class/action.nw:29
-- concrete
-- ../src/class/action.nw:93
Action.call = class( Action.generic_call )
-- ../src/action/call.nw:118
function Action.call:match (q)
if not Action.generic_call.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
-- ../src/class/action.nw:96
-- ../src/action/call.nw:82
function Action.call:new (m, key, ...)
local a = Action.generic_call.new( self, m, ... )
a.key = key
return a
end
-- ../src/class/action.nw:97
-- ../src/tostring.nw:101
function Action.call:tostring ()
if self.has_returnvalue then
return sfmt( "call %s(%s) => %s"
, tostring(self.key)
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "call %s(%s)"
, tostring(self.key)
, self.argv:tostring()
)
end
end
-- ../src/class/action.nw:32
-- ../src/class/action.nw:81
Action.index = class( Action.generic )
Action.index.can_return = true
-- ../src/action/index.nw:134
function Action.index:get_returnvalue ()
return self.returnvalue
end
-- ../src/class/action.nw:85
-- ../src/action/index.nw:85
function Action.index:set_returnvalue (v)
self.returnvalue = v
self.has_returnvalue = true
end
-- ../src/class/action.nw:86
-- ../src/action/index.nw:123
function Action.index:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
-- ../src/class/action.nw:88
-- ../src/action/index.nw:67
function Action.index:new (m, key)
local a = Action.generic.new( self, m )
a.key = key
return a
end
-- ../src/class/action.nw:89
-- ../src/tostring.nw:70
function Action.index:tostring ()
local key = 'index '..tostring( self.key )
if self.has_returnvalue then
return sfmt( "index %s => %s"
, tostring( self.key )
, qtostring( self.returnvalue )
)
elseif self.is_callable then
return sfmt( "index %s()"
, tostring( self.key )
)
else
return sfmt( "index %s"
, tostring( self.key )
)
end
end
-- ../src/class/action.nw:33
-- ../src/class/action.nw:73
Action.newindex = class( Action.generic )
-- ../src/action/newindex.nw:102
function Action.newindex:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
if not value_equal( self.val, q.val )
and self.val ~= Argv.ANYARG
and q.val ~= Argv.ANYARG then return false end
return true
end
-- ../src/class/action.nw:76
-- ../src/action/newindex.nw:54
function Action.newindex:new (m, key, val)
local a = Action.generic.new( self, m )
a.key = key
a.val = val
return a
end
-- ../src/class/action.nw:77
-- ../src/tostring.nw:45
function Action.newindex:tostring ()
return sfmt( "newindex %s = %s"
, tostring(self.key)
, qtostring(self.val)
)
end
-- ../src/class/action.nw:34
-- ../src/class/action.nw:101
Action.selfcall = class( Action.generic_call )
-- ../src/action/selfcall.nw:93
function Action.selfcall:match (q)
return Action.generic_call.match( self, q )
end
-- ../src/class/action.nw:104
-- ../src/action/selfcall.nw:61
function Action.selfcall:new (m, ...)
local a = Action.generic_call.new( self, m, ... )
return a
end
-- ../src/class/action.nw:105
-- ../src/tostring.nw:129
function Action.selfcall:tostring ()
if self.has_returnvalue then
return sfmt( "selfcall (%s) => %s"
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "selfcall (%s)"
, self.argv:tostring()
)
end
end
-- ../src/unittestfiles.nw:120
-- ../src/class/argv.nw:6
Argv = class()
-- ../src/argv.nw:119
Argv.ANYARGS = newproxy() local ANYARGS = Argv.ANYARGS
Argv.ANYARG = newproxy() local ANYARG = Argv.ANYARG
function Argv:equal (other)
local a1, n1 = self.v, self.len
local a2, n2 = other.v, other.len
if n1-1 <= n2 and a1[n1] == ANYARGS then
n1 = n1-1
n2 = n1
elseif n2-1 <= n1 and a2[n2] == ANYARGS then
n2 = n2-1
n1 = n2
end
if n1 ~= n2 then
return false
end
for i = 1, n1 do
local v1, v2 = a1[i], a2[i]
if not value_equal(v1,v2) and v1 ~= ANYARG and v2 ~= ANYARG then
return false
end
end
return true
end
-- ../src/class/argv.nw:9
-- ../src/argv.nw:46
function Argv:new (...)
local av = object( self )
av.v = {...}
av.len = select('#',...)
for i = 1, av.len - 1 do
if av.v[i] == Argv.ANYARGS then
error( "ANYARGS not at end.", 0 )
end
end
return av
end
-- ../src/class/argv.nw:10
-- ../src/tostring.nw:163
function Argv:tostring ()
local res = {}
local function w (v)
res[#res+1] = qtostring( v )
end
local av, ac = self.v, self.len
for i = 1, ac do
if av[i] == Argv.ANYARG then
res[#res+1] = 'ANYARG'
elseif av[i] == Argv.ANYARGS then
res[#res+1] = 'ANYARGS'
else
w( av[i] )
end
if i < ac then
res[#res+1] = ',' -- can not use qtostring in w()
end
end
return table.concat( res )
end
-- ../src/class/argv.nw:11
-- ../src/argv.nw:156
function Argv:unpack ()
return unpack( self.v, 1, self.len )
end
-- ../src/unittestfiles.nw:121
local A = Action.generic
Action = nil -- only allow generic action
function A:tostring () return "<generic action>" end
local a
function setup ()
a = A:new()
end
-- ../src/restrictions.nw:422
function add_depend_test ()
local ls = { 0, 'foo', 1/0, a, {} }
local seen = {}
for _, l in ipairs( ls ) do
seen[l] = 0
a:add_depend( l )
end
for l in a:depends() do
seen[l] = seen[l] + 1
end
for _, l in ipairs( ls ) do
assert_equal( 1, seen[l], "Mismatch for "..qtostring(l) )
end
end
function dependencies_dont_iterate_on_empty_list_test ()
for _ in a:depends() do
fail( "unexpected dependency" )
end
end
-- ../src/unittestfiles.nw:133
-- ../src/restrictions.nw:178
function label_test ()
local ls = { 1/0, 0, false, {}, a, "foo", true }
for i = 1, #ls do
assert_false( a:has_label( ls[i] ))
end
for i = 1, #ls do
a:add_label( ls[i] )
for j = 1 , #ls do
if j <= i then
assert_true( a:has_label( ls[j] ))
else
assert_false( a:has_label( ls[j] ))
end
end
end
end
function add_label_twice_test ()
local l = 'foo'
a:add_label( l )
a:add_label( l )
local cnt = 0
for x in a:blocks() do
assert_equal( l, x )
cnt = cnt + 1
end
assert_equal( 1, cnt )
end
-- ../src/unittestfiles.nw:134
-- ../src/main.nw:242
function expect_unreplayed_action_test ()
assert_true( a:is_expected() )
end
-- ../src/unittestfiles.nw:135
-- ../src/main.nw:320
function unreplayed_action_is_not_satisfied_test ()
assert_false( a:is_satisfied() )
end
function assert_satisfied_unreplayed_action_fails_test ()
local ok, err = pcall( function() a:assert_satisfied() end )
assert_false( ok, "unreplayed action was satisfied" )
assert_match( "Wrong replay count 0", err )
end
-- ../src/unittestfiles.nw:136
-- ../src/main.nw:254
function match_unreplayed_test ()
assert_true( a:match( a ))
end
function match_rejects_replayed_action_test ()
a.replay_count = 1
assert_false( a:match( a ))
end
function match_rejects_wrong_action_type_test ()
-- Fake different type
local B = class( A )
local b = B:new()
assert_false( a:match( b ))
end
-- ../src/unittestfiles.nw:137
-- ../src/main.nw:212
function new_action_has_right_default_values_test ()
assert_equal( 0, a.replay_count )
assert_equal( 1, a.min_replays )
assert_equal( 1, a.max_replays )
end
-- ../src/unittestfiles.nw:138
-- ../src/restrictions.nw:90
function set_and_get_times_test ()
end
function unrealistic_times_fails_test ()
local ps = { {'foo'}, {8,'bar'}, {-1}, {3,2}, {1/0}, {0/0}, {0,0} }
for _, p in ipairs( ps ) do
local ok, err = pcall( function() a:set_times( unpack(p) ) end )
assert_false( ok, "unrealistic times "..table.concat(p,", ") )
assert_match( "Unrealistic time arguments ", err )
end
end

@ -0,0 +1,213 @@
-- ../src/unittestfiles.nw:85
-- ../src/misc.nw:7
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
-- ../src/unittestfiles.nw:86
require 'lunit'
module( 'unit.argv', lunit.testcase, package.seeall )
local class, object, value_equal, sfmt, qtostring
-- ../src/helperfunctions.nw:12
function object (class)
return setmetatable( {}, class )
end
function class (parent)
local c = object(parent)
c.__index = c
return c
end
-- ../src/unittestfiles.nw:92
-- ../src/helperfunctions.nw:29
function value_equal (a, b)
if a == b then return true end
if a ~= a and b ~= b then return true end -- NaN == NaN
return false
end
-- ../src/unittestfiles.nw:93
-- ../src/tostring.nw:23
sfmt = string.format
function qtostring (v)
if type(v) == 'string' then
return sfmt( '%q', v )
else
return tostring( v )
end
end
-- ../src/unittestfiles.nw:94
local Argv
-- ../src/class/argv.nw:6
Argv = class()
-- ../src/argv.nw:119
Argv.ANYARGS = newproxy() local ANYARGS = Argv.ANYARGS
Argv.ANYARG = newproxy() local ANYARG = Argv.ANYARG
function Argv:equal (other)
local a1, n1 = self.v, self.len
local a2, n2 = other.v, other.len
if n1-1 <= n2 and a1[n1] == ANYARGS then
n1 = n1-1
n2 = n1
elseif n2-1 <= n1 and a2[n2] == ANYARGS then
n2 = n2-1
n1 = n2
end
if n1 ~= n2 then
return false
end
for i = 1, n1 do
local v1, v2 = a1[i], a2[i]
if not value_equal(v1,v2) and v1 ~= ANYARG and v2 ~= ANYARG then
return false
end
end
return true
end
-- ../src/class/argv.nw:9
-- ../src/argv.nw:46
function Argv:new (...)
local av = object( self )
av.v = {...}
av.len = select('#',...)
for i = 1, av.len - 1 do
if av.v[i] == Argv.ANYARGS then
error( "ANYARGS not at end.", 0 )
end
end
return av
end
-- ../src/class/argv.nw:10
-- ../src/tostring.nw:163
function Argv:tostring ()
local res = {}
local function w (v)
res[#res+1] = qtostring( v )
end
local av, ac = self.v, self.len
for i = 1, ac do
if av[i] == Argv.ANYARG then
res[#res+1] = 'ANYARG'
elseif av[i] == Argv.ANYARGS then
res[#res+1] = 'ANYARGS'
else
w( av[i] )
end
if i < ac then
res[#res+1] = ',' -- can not use qtostring in w()
end
end
return table.concat( res )
end
-- ../src/class/argv.nw:11
-- ../src/argv.nw:156
function Argv:unpack ()
return unpack( self.v, 1, self.len )
end
-- ../src/unittestfiles.nw:97
-- ../src/argv.nw:63
local l = {}
local function p (...) l[#l+1] = { n=select('#',...), ... } end
p() p(nil) p(nil,nil) p(false) p({}) p(false,nil,{},nil) p(nil,p)
p(true) p(0.1,'','a') p(1/0,nil,0/0) p(0/0) p(0/0, true) p(0/0, false)
function equal_test ()
local a1, a2, f, op
for i = 1, #l do
ai = Argv:new( unpack( l[i], 1, l[i].n ))
for j = 1, #l do
aj = Argv:new( unpack( l[j], 1, l[j].n ))
if i == j then
f, op = assert_true, ') ~= ('
else
f, op = assert_false, ') == ('
end
f( ai:equal(aj), '('..ai:tostring()..op..aj:tostring()..')' )
end
end
end
function equal_anyargs_test ()
local a, b = {}, {}
a[1] = Argv:new( Argv.ANYARGS )
a[2] = Argv:new( 6, Argv.ANYARGS )
a[3] = Argv:new( 6, 5, Argv.ANYARGS )
for i = 1, #l do
b[1] = Argv:new( unpack( l[i], 1, l[i].n ))
b[2] = Argv:new( 6, unpack( l[i], 1, l[i].n ))
b[3] = Argv:new( 6, 5, unpack( l[i], 1, l[i].n ))
for j = 1, 3 do
local astr = '('..a[j]:tostring()..')'
local bstr = '('..b[j]:tostring()..')'
assert_true( a[j]:equal(b[j]), astr..' ~= '..bstr )
assert_true( b[j]:equal(a[j]), bstr..' ~= '..astr )
end
end
end
function equal_anyarg_test ()
local l = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
local a1 = Argv:new( unpack(l) )
for i = 1, 9 do
l[i] = Argv.ANYARG
local a2 = Argv:new( unpack(l) )
assert_true( a1:equal(a2) )
assert_true( a2:equal(a1) )
l[i] = i
end
end
-- ../src/unittestfiles.nw:99
-- ../src/argv.nw:27
function new_test ()
Argv:new( Argv.ANYARGS )
Argv:new( 1, Argv.ANYARGS )
Argv:new( 1, 2, Argv.ANYARGS )
end
function new_anyargs_with_extra_arguments_fails_test ()
local l = {}
l['ANYARGS,1'] = { Argv.ANYARGS, 1 }
l['ANYARGS,ANYARGS' ] = { Argv.ANYARGS, Argv.ANYARGS }
l['1,ANYARGS,1'] = { 1, Argv.ANYARGS, 1 }
l['1,ANYARGS,ANYARGS'] = { 1, Argv.ANYARGS, Argv.ANYARGS }
for msg, args in pairs( l ) do
local ok, err = pcall( function() Argv:new( unpack(args) ) end )
assert_false( ok, "Bad ANYARGS accepted for "..msg )
assert_match( "ANYARGS not at end", err )
end
end
-- ../src/unittestfiles.nw:100
-- ../src/tostring.nw:151
function tostring_test ()
assert_equal( '', Argv:new() :tostring() )
assert_equal( '""', Argv:new('') :tostring() )
assert_equal( 'nil,nil', Argv:new(nil,nil) :tostring() )
assert_equal( '"false",false', Argv:new('false',false) :tostring() )
assert_equal( '1,2,3', Argv:new(1,2,3) :tostring() )
assert_equal( '1,ANYARG,3', Argv:new(1,Argv.ANYARG,3):tostring() )
assert_equal( 'ANYARGS', Argv:new(Argv.ANYARGS) :tostring() )
assert_equal( '7,0,ANYARGS', Argv:new(7,0,Argv.ANYARGS):tostring() )
end
-- ../src/unittestfiles.nw:101
-- ../src/argv.nw:148
function unpack_test ()
local a, b, c = Argv:new( false, nil, 7 ):unpack()
assert_equal( false, a )
assert_equal( nil, b )
assert_equal( 7, c )
end

@ -0,0 +1,860 @@
-- ../src/unittestfiles.nw:46
-- ../src/misc.nw:7
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
-- ../src/unittestfiles.nw:47
require 'lunit'
module( 'unit.controller', lunit.testcase, package.seeall )
local class, object, qtostring, sfmt, add_to_set, elements_of_set
-- ../src/helperfunctions.nw:12
function object (class)
return setmetatable( {}, class )
end
function class (parent)
local c = object(parent)
c.__index = c
return c
end
-- ../src/unittestfiles.nw:53
-- ../src/tostring.nw:23
sfmt = string.format
function qtostring (v)
if type(v) == 'string' then
return sfmt( '%q', v )
else
return tostring( v )
end
end
-- ../src/unittestfiles.nw:54
-- ../src/helperfunctions.nw:47
function add_to_set (o, setname, element)
if not o[setname] then
o[setname] = {}
end
local l = o[setname]
for i = 1, #l do
if l[i] == element then return end
end
l[#l+1] = element
end
function elements_of_set (o, setname)
local l = o[setname]
local i = l and #l+1 or 0
return function ()
i = i - 1
if i > 0 then return l[i] end
end
end
-- ../src/unittestfiles.nw:55
-- ../src/main.nw:373
local mock_controller_map = setmetatable( {}, {__mode='k'} )
-- ../src/unittestfiles.nw:57
local Controller, Action
-- ../src/class/controller.nw:6
Controller = class()
-- Exported methods
-- ../src/restrictions.nw:595
function Controller:close (...)
if not self.is_recording then
error( "Can not insert close in replay mode.", 2 )
end
local action = self:get_last_action()
for _, close in ipairs{ ... } do
action:add_close( close )
end
return self -- for chaining
end
-- ../src/class/controller.nw:10
-- ../src/restrictions.nw:410
function Controller:depend (...)
if not self.is_recording then
error( "Can not add dependency in replay mode.", 2 )
end
local action = self:get_last_action()
for _, dependency in ipairs{ ... } do
action:add_depend( dependency )
end
return self -- for chaining
end
-- ../src/class/controller.nw:11
-- ../src/main.nw:617
function Controller:error (value)
if not self.is_recording then
error( "Error called during replay.", 2 )
end
local action = self:get_last_action()
if action.has_returnvalue or action.throws_error then
error( "Returns and/or Error called twice for same action.", 2 )
end
action.throws_error = true
action.errorvalue = value
return self -- for chaining
end
-- ../src/class/controller.nw:12
-- ../src/restrictions.nw:158
function Controller:label (...)
if not self.is_recording then
error( "Can not add labels in replay mode.", 2 )
end
local action = self:get_last_action()
for _, label in ipairs{ ... } do
action:add_label( label )
end
return self -- for chaining
end
-- ../src/class/controller.nw:13
-- ../src/main.nw:462
function Controller:mock ()
if not self.is_recording then
error( "New mock during replay.", 2 )
end
local m = object( Mock.record )
mock_controller_map[m] = self
return m
end
-- ../src/class/controller.nw:14
-- ../src/main.nw:421
function Controller:new ()
local mc = object( self )
mc.actionlist = {}
mc.is_recording = true
return mc
end
-- ../src/class/controller.nw:15
-- ../src/main.nw:671
function Controller:replay ()
if not self.is_recording then
error( "Replay called twice.", 2 )
end
self.is_recording = false
for m, mc in pairs( mock_controller_map ) do
if mc == self then
setmetatable( m, Mock.replay )
end
end
self:update_dependencies()
self:assert_no_dependency_cycles()
end
-- ../src/class/controller.nw:16
-- ../src/main.nw:571
function Controller:returns (...)
if not self.is_recording then
error( "Returns called during replay.", 2 )
end
local action = self:get_last_action()
assert( not action.is_callable, "lemock internal error" )
if not action.can_return then
error( "Previous action can not return anything.", 2 )
end
if action.has_returnvalue or action.throws_error then
error( "Returns and/or Error called twice for same action.", 2 )
end
action:set_returnvalue(...)
return self -- for chaining
end
-- ../src/class/controller.nw:17
-- ../src/restrictions.nw:74
function Controller:times (min, max)
if not self.is_recording then
error( "Can not set times in replay mode.", 0 )
end
self:get_last_action():set_times( min, max )
return self -- for chaining
end
-- convenience functions
function Controller:anytimes() return self:times( 0, math.huge ) end
function Controller:atleastonce() return self:times( 1, math.huge ) end
-- ../src/class/controller.nw:18
-- ../src/main.nw:754
function Controller:verify ()
if self.is_recording then
error( "Verify called during record.", 2 )
end
for a in self:actions() do
a:assert_satisfied()
end
end
-- ../src/class/controller.nw:19
-- Protected methods
-- ../src/main.nw:145
function Controller:actions (q)
local l = self.actionlist
local i = 0
return function ()
i = i + 1
return l[i]
end
end
-- ../src/class/controller.nw:22
-- ../src/main.nw:56
function Controller:add_action (a)
assert( a ~= nil, "lemock internal error" ) -- breaks array property
table.insert( self.actionlist, a )
end
-- ../src/class/controller.nw:23
-- ../src/restrictions.nw:489
function Controller:assert_no_dependency_cycles ()
local function is_in_path (label, path)
if not path then return false end -- is root
for _, l in ipairs( path ) do
if l == label then return true end
end
if path.prev then return is_in_path( label, path.prev ) end
return false
end
local function can_block (action, node)
for _, label in ipairs( node ) do
if action:has_label( label ) then return true end
end
return false
end
local function step (action, path)
local new_head
for label in action:depends() do
if is_in_path( label, path ) then
error( "Detected dependency cycle", 0 )
end
-- only create table if needed to reduce garbage
if not new_head then new_head = { prev=path } end
new_head[#new_head+1] = label
end
return new_head
end
local function search_depth_first (path)
for action in self:actions() do
if can_block( action, path ) then
local new_head = step( action, path )
if new_head then
search_depth_first( new_head )
end
end
end
end
for action in self:actions() do
local root = step( action, nil )
if root then search_depth_first( root ) end
end
end
-- ../src/class/controller.nw:24
-- ../src/restrictions.nw:616
function Controller:close_actions( ... ) -- takes iterator
for label in ... do
for candidate in self:actions() do
if candidate:has_label( label ) then
if not candidate:is_satisfied() then
error( "Closes unsatisfied action: "..candidate:tostring(), 0 )
end
candidate.is_closed = true
end
end
end
end
-- ../src/class/controller.nw:25
-- ../src/main.nw:177
function Controller:get_last_action ()
local l = self.actionlist
if #l == 0 then
error( "No action is recorded yet.", 0 )
end
return l[#l]
end
-- ../src/class/controller.nw:26
-- ../src/main.nw:88
function Controller:lookup (actual)
for action in self:actions() do
if action:match( actual ) then
return action
end
end
-- ../src/main.nw:111
local expected = {}
for _, a in ipairs( self.actionlist ) do
if a:is_expected() and not a.is_callable then
expected[#expected+1] = a:tostring()
end
end
table.sort( expected )
if #expected == 0 then
expected[1] = "(Nothing)"
end
-- ../src/main.nw:95
error( sfmt( "Unexpected action %s, expected:\n%s\n"
, actual:tostring()
, table.concat(expected,'\n')
)
, 0
)
end
-- ../src/class/controller.nw:27
-- ../src/main.nw:531
function Controller:make_callable (action)
if action.has_returnvalue then
error( "Can not call "..action.key..". It has a returnvalue.", 0 )
end
action.is_callable = true
action.min_replays = 0
action.max_replays = math.huge
end
-- ../src/class/controller.nw:28
-- ../src/main.nw:421
function Controller:new ()
local mc = object( self )
mc.actionlist = {}
mc.is_recording = true
return mc
end
-- ../src/class/controller.nw:29
-- ../src/main.nw:297
function Controller:replay_action ( action )
assert( action:is_expected(), "lemock internal error" )
assert( action.replay_count < action.max_replays, "lemock internal error" )
local was_satisfied = action:is_satisfied()
action.replay_count = action.replay_count + 1
if not was_satisfied and action.labellist and action:is_satisfied() then
self:update_dependencies()
end
if action.closelist then
self:close_actions( action:closes() )
end
end
-- ../src/class/controller.nw:30
-- ../src/restrictions.nw:457
function Controller:update_dependencies ()
local blocked = {}
for action in self:actions() do
for label in action:blocks() do
blocked[label] = true
end
end
local function is_blocked (action)
for label in action:depends() do
if blocked[label] then return true end
end
return false
end
for action in self:actions() do
action.is_blocked = is_blocked( action )
end
end
-- ../src/unittestfiles.nw:60
-- ../src/class/action.nw:24
Action = {}
-- abstract
-- ../src/class/action.nw:41
Action.generic = class()
-- ../src/restrictions.nw:607
function Action.generic:add_close (label)
add_to_set( self, 'closelist', label )
end
-- ../src/class/action.nw:44
-- ../src/restrictions.nw:443
function Action.generic:add_depend (d)
add_to_set( self, 'dependlist', d )
end
-- ../src/class/action.nw:45
-- ../src/restrictions.nw:207
function Action.generic:add_label (label)
add_to_set( self, 'labellist', label )
end
-- ../src/class/action.nw:46
-- ../src/main.nw:338
function Action.generic:assert_satisfied ()
assert( self.replay_count <= self.max_replays, "lemock internal error" )
if not (
-- ../src/main.nw:330
self.min_replays <= self.replay_count
-- ../src/main.nw:340
) then
error( sfmt( "Wrong replay count %d (expected %d..%d) for %s"
, self.replay_count
, self.min_replays, self.max_replays
, self:tostring()
)
, 0
)
end
end
-- ../src/class/action.nw:47
-- ../src/restrictions.nw:220
function Action.generic:blocks ()
if self:is_satisfied() then
return function () end
end
return elements_of_set( self, 'labellist' )
end
-- ../src/class/action.nw:48
-- ../src/restrictions.nw:630
function Action.generic:closes ()
return elements_of_set( self, 'closelist' )
end
-- ../src/class/action.nw:49
-- ../src/restrictions.nw:448
function Action.generic:depends ()
return elements_of_set( self, 'dependlist' )
end
-- ../src/class/action.nw:50
-- ../src/restrictions.nw:212
function Action.generic:has_label (l)
for x in elements_of_set( self, 'labellist' ) do
if x == l then return true end
end
return false
end
-- ../src/class/action.nw:51
-- ../src/main.nw:247
function Action.generic:is_expected ()
return self.replay_count < self.max_replays
and not self.is_blocked
and not self.is_closed
end
-- ../src/class/action.nw:52
-- ../src/main.nw:333
function Action.generic:is_satisfied ()
return
-- ../src/main.nw:330
self.min_replays <= self.replay_count
-- ../src/main.nw:335
end
-- ../src/class/action.nw:53
-- ../src/main.nw:269
function Action.generic:match (key)
if getmetatable(self) ~= getmetatable(key) then return false end
if self.mock ~= key.mock then return false end
return self:is_expected()
end
-- ../src/class/action.nw:54
-- ../src/main.nw:219
function Action.generic:new (mock)
local a = object( self )
a.mock = mock
a.replay_count = 0
a.min_replays = 1
a.max_replays = 1
return a
end
-- ../src/class/action.nw:55
-- ../src/restrictions.nw:102
function Action.generic:set_times (a, b)
min = a or 1
max = b or min
min, max = tonumber(min), tonumber(max)
if (not min) or (not max) or (min >= math.huge)
or (min ~= min) or (max ~= max) -- NaN
or (min < 0) or (max <= 0) or (min > max) then
error( sfmt( "Unrealistic time arguments (%s, %s)"
, qtostring( min )
, qtostring( max )
)
, 0
)
end
self.min_replays = min
self.max_replays = max
end
-- ../src/class/action.nw:28
-- ../src/class/action.nw:59
Action.generic_call = class( Action.generic )
Action.generic_call.can_return = true
-- ../src/action/generic_call.nw:76
function Action.generic_call:get_returnvalue ()
if self.has_returnvalue then
return self.returnvalue:unpack()
end
end
-- ../src/class/action.nw:63
-- ../src/action/generic_call.nw:56
function Action.generic_call:set_returnvalue (...)
self.returnvalue = Argv:new(...)
self.has_returnvalue = true
end
-- ../src/class/action.nw:64
-- ../src/action/generic_call.nw:45
function Action.generic_call:match (q)
if not Action.generic.match( self, q ) then return false end
if not self.argv:equal( q.argv ) then return false end
return true
end
-- ../src/class/action.nw:66
-- ../src/action/generic_call.nw:32
function Action.generic_call:new (m, ...)
local a = Action.generic.new( self, m )
a.argv = Argv:new(...)
return a
end
-- ../src/class/action.nw:29
-- concrete
-- ../src/class/action.nw:93
Action.call = class( Action.generic_call )
-- ../src/action/call.nw:118
function Action.call:match (q)
if not Action.generic_call.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
-- ../src/class/action.nw:96
-- ../src/action/call.nw:82
function Action.call:new (m, key, ...)
local a = Action.generic_call.new( self, m, ... )
a.key = key
return a
end
-- ../src/class/action.nw:97
-- ../src/tostring.nw:101
function Action.call:tostring ()
if self.has_returnvalue then
return sfmt( "call %s(%s) => %s"
, tostring(self.key)
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "call %s(%s)"
, tostring(self.key)
, self.argv:tostring()
)
end
end
-- ../src/class/action.nw:32
-- ../src/class/action.nw:81
Action.index = class( Action.generic )
Action.index.can_return = true
-- ../src/action/index.nw:134
function Action.index:get_returnvalue ()
return self.returnvalue
end
-- ../src/class/action.nw:85
-- ../src/action/index.nw:85
function Action.index:set_returnvalue (v)
self.returnvalue = v
self.has_returnvalue = true
end
-- ../src/class/action.nw:86
-- ../src/action/index.nw:123
function Action.index:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
-- ../src/class/action.nw:88
-- ../src/action/index.nw:67
function Action.index:new (m, key)
local a = Action.generic.new( self, m )
a.key = key
return a
end
-- ../src/class/action.nw:89
-- ../src/tostring.nw:70
function Action.index:tostring ()
local key = 'index '..tostring( self.key )
if self.has_returnvalue then
return sfmt( "index %s => %s"
, tostring( self.key )
, qtostring( self.returnvalue )
)
elseif self.is_callable then
return sfmt( "index %s()"
, tostring( self.key )
)
else
return sfmt( "index %s"
, tostring( self.key )
)
end
end
-- ../src/class/action.nw:33
-- ../src/class/action.nw:73
Action.newindex = class( Action.generic )
-- ../src/action/newindex.nw:102
function Action.newindex:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
if not value_equal( self.val, q.val )
and self.val ~= Argv.ANYARG
and q.val ~= Argv.ANYARG then return false end
return true
end
-- ../src/class/action.nw:76
-- ../src/action/newindex.nw:54
function Action.newindex:new (m, key, val)
local a = Action.generic.new( self, m )
a.key = key
a.val = val
return a
end
-- ../src/class/action.nw:77
-- ../src/tostring.nw:45
function Action.newindex:tostring ()
return sfmt( "newindex %s = %s"
, tostring(self.key)
, qtostring(self.val)
)
end
-- ../src/class/action.nw:34
-- ../src/class/action.nw:101
Action.selfcall = class( Action.generic_call )
-- ../src/action/selfcall.nw:93
function Action.selfcall:match (q)
return Action.generic_call.match( self, q )
end
-- ../src/class/action.nw:104
-- ../src/action/selfcall.nw:61
function Action.selfcall:new (m, ...)
local a = Action.generic_call.new( self, m, ... )
return a
end
-- ../src/class/action.nw:105
-- ../src/tostring.nw:129
function Action.selfcall:tostring ()
if self.has_returnvalue then
return sfmt( "selfcall (%s) => %s"
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "selfcall (%s)"
, self.argv:tostring()
)
end
end
-- ../src/unittestfiles.nw:61
local A = Action.generic
Action = nil -- only allow generic action
function A:tostring () return '<dummy>' end
local mc
function setup ()
mc = Controller:new()
end
-- ../src/main.nw:125
function actions_dont_iterate_empty_list_test ()
for a in mc:actions() do
fail( "iterates on empty list" )
end
end
function actions_iterate_over_entire_list_exactly_once_test ()
local l = { {},{},{} }
for _, a in ipairs( l ) do
mc:add_action( a )
end
for a in mc:actions() do
assert_nil( a.check )
a.check = true
end
for _, a in ipairs( l ) do
assert_true( a.check )
end
end
-- ../src/unittestfiles.nw:73
-- ../src/main.nw:48
function add_action_at_the_end_test ()
mc:add_action( 7 )
mc:add_action( mc )
assert_equal( 7, mc.actionlist[1] )
assert_equal( mc, mc.actionlist[2] )
end
-- ../src/unittestfiles.nw:74
-- ../src/main.nw:162
function get_last_action_returns_last_element_test ()
local l = { 'a', 'foo', 17 }
for i = 1, #l do
mc:add_action( l[i] )
local res = mc:get_last_action()
assert_equal( l[i], res )
end
end
function get_last_action_fails_on_empty_list_test ()
local ok, err = pcall( function() mc:get_last_action() end )
assert_false( ok, "Found last action in empty list" )
assert_match( "No action is recorded yet", err )
end
-- ../src/unittestfiles.nw:75
-- ../src/restrictions.nw:143
function label_test ()
mc:add_action( A:new() )
mc:label( 'a', 'b' ):label( 'c', 'b' )
local a = mc:get_last_action()
local seen = {}
for l in a:blocks() do
seen[l] = true
end
assert_true( seen['a'] )
assert_true( seen['b'] )
assert_true( seen['c'] )
assert_nil( seen['d'] )
end
-- ../src/unittestfiles.nw:76
-- ../src/main.nw:71
function lookup_returns_first_matching_action_test ()
local Fake_action
-- ../src/misc.nw:12
Fake_action = class()
function Fake_action:new (x)
local a = object(Fake_action)
a.x = x
return a
end
function Fake_action:match (q)
return self.x < q.x
end
function Fake_action:is_expected ()
return true
end
function Fake_action:tostring ()
return '<faked action>'
end
function Fake_action:blocks ()
return function () end
end
Fake_action.depends = Fake_action.blocks
-- ../src/main.nw:74
local a1 = Fake_action:new(1)
local a2 = Fake_action:new(2)
local a3 = Fake_action:new(1)
local ok, err = pcall( function() mc:lookup( a1 ) end )
assert_false( ok, "match in empty list" )
assert_match( "Unexpected action <faked action>", err )
mc:add_action( a1 ) mc:add_action( a2 ) mc:add_action( a3 )
local ok, err = pcall( function() mc:lookup( a1 ) end )
assert_false( ok, "should not match any action" )
assert_match( "Unexpected action <faked action>", err )
assert_equal( a1, mc:lookup( a2 ), "did not find first match" )
end
-- ../src/unittestfiles.nw:77
-- ../src/main.nw:664
function replay_test ()
assert_true( mc.is_recording )
mc:replay()
assert_false( mc.is_recording )
end
-- ../src/unittestfiles.nw:78
-- ../src/main.nw:285
function replay_action_test ()
local a = A:new()
mc:add_action( a )
assert_true( a:is_expected() )
assert_false( a:is_satisfied() )
mc:replay_action( a )
assert_false( a:is_expected() )
assert_true( a:is_satisfied() )
assert_equal( 1, a.replay_count )
end

@ -0,0 +1,601 @@
-- ../src/unittestfiles.nw:10
-- ../src/misc.nw:7
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
-- ../src/unittestfiles.nw:11
require 'lunit'
module( 'unit.module', lunit.testcase, package.seeall )
require 'lemock'
local mc, m
function setup ()
mc = lemock.controller()
m = mc:mock()
end
-- ../src/restrictions.nw:537
function close_test ()
local t
t = m.foo ;mc:times(0,1/0):returns( 1 ) :label(1)
t = m.foo ;mc:times(0,1/0):returns( 2 ) :label(2)
t = m.foo ;mc:times(0,1/0):returns( 3 )
m.bar(1) ;mc:close(1)
m.bar(2) ;mc:close(2)
mc:replay()
m.bar(1)
assert_equal( 2, m.foo )
assert_equal( 2, m.foo )
assert_equal( 2, m.foo )
m.bar(2)
assert_equal( 3, m.foo )
mc:verify()
end
function close_unsatisfied_action_fails_test ()
m.a = 1 ;mc:label(1)
m.b = 2 ;mc:close(1)
mc:replay()
local ok, err = pcall( function() m.b = 2 end )
assert_false( ok, "Undetected close of unsatisfied action" )
assert_match( "Closes unsatisfied action", err )
end
function close_multiple_test ()
m.foo(1) ;mc:label(1) :times(0,1)
m.foo(1) ;mc:label(2) :times(0,1)
m.foo(1)
m.bar() ;mc:close(1,2)
mc:replay()
m.bar()
m.foo(1)
mc:verify()
end
-- ../src/restrictions.nw:573
function close_chaining_test ()
m.a = 1 ;mc:label 'A'
m.b = 1 ;mc:label 'B'
m.c = 1 ;mc:close('A'):close('B')
end
function close_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:close( 'foo' ) end )
assert_false( ok, "accepted close in replay mode" )
assert_match( "Can not insert close in replay mode", err )
end
function close_on_empty_actionlist_fails_test ()
local ok, err = pcall( function() mc:close( 'bar' ) end )
assert_false( ok, "accepted close with empty action list" )
assert_match( "No action is recorded yet", err )
end
-- ../src/unittestfiles.nw:25
-- ../src/restrictions.nw:240
function depend_fulfilled_test ()
m.foo = 1 ;mc:label 'A'
m.bar = 2 ;mc:depend 'A'
mc:replay()
m.foo = 1
m.bar = 2
mc:verify()
end
function depend_unfulfilled_fails_test ()
m.foo = 1 ;mc:label 'A'
m.bar = 2 ;mc:depend 'A'
mc:replay()
local ok, err = pcall( function() m.bar = 2 end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action newindex", err )
end
function depend_fulfilled_any_order_test ()
local tmp
m.a = 1 ;mc:label 'A'
tmp = m.b ;mc:returns(2):depend 'A'
tmp = m.b ;mc:returns(3)
mc:replay()
assert_equal( 3, m.b, "replayed wrong b" )
m.a = 1
assert_equal( 2, m.b, "replayed wrong b" )
mc:verify()
end
function depend_serial_blocks_test ()
local tmp
tmp = m:a() ;mc:label 'a'
tmp = m:c() ;mc:label 'c' :depend 'b'
tmp = m:b() ;mc:label 'b' :depend 'a'
mc:replay()
local ok, err = pcall( function() tmp = m:b() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
function depend_on_many_labels_test ()
local tmp
tmp = m:b() ;mc:label 'b'
tmp = m:c() ;mc:label 'c' :depend( 'a', 'b' )
tmp = m:a() ;mc:label 'a'
mc:replay()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
function depend_on_many_labels_test2_test ()
-- swap order, in case whole list is not checked
local tmp
tmp = m:b() ;mc:label 'b'
tmp = m:c() ;mc:label 'c' :depend( 'b', 'a' )
tmp = m:a() ;mc:label 'a'
mc:replay()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
function depend_on_many_bloskers_with_same_label_test ()
tmp = m:c() ;mc:label 'c' :depend 'b'
tmp = m:a() ;mc:label 'b'
tmp = m:b() ;mc:label 'b'
mc:replay()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
-- ../src/restrictions.nw:344
function depend_ignors_unknown_label_test ()
m.foo = 1 ;mc:label 'A'
m.bar = 2 ;mc:depend 'B'
mc:replay()
m.foo = 1
m.bar = 2
mc:verify()
end
-- ../src/restrictions.nw:361
function depend_detect_cycle_test ()
local ok, err = pcall( function()
m.foo = 1 ;mc:label 'A' :depend 'B'
m.bar = 2 ;mc:label 'B' :depend 'A'
mc:replay()
m.foo = 1
end )
assert_false( ok, "replayed cyclically blocked action" )
assert_match( "dependency cycle", err )
end
-- ../src/restrictions.nw:373
function depend_chaining_test ()
m.a = 1 ;mc:label 'A'
m.b = 1 ;mc:label 'B'
m.c = 1 ;mc:depend('A'):depend('B')
end
function depend_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:depend( 'foo' ) end )
assert_false( ok, "set dependency in replay mode" )
assert_match( "Can not add dependency in replay mode", err )
end
function depend_on_empty_actionlist_fails_test ()
local ok, err = pcall( function() mc:depend( 'bar' ) end )
assert_false( ok, "set dependency with empty action list" )
assert_match( "No action is recorded yet", err )
end
function depend_reports_expected_actions_on_faliure_test ()
local tmp
tmp = m.foo ;mc:depend 'B'
tmp = m.bar ;mc:label 'B'
mc:replay()
local ok, err = pcall( function() tmp = m.foo end )
assert_false( ok, "replayed blocked action" )
assert_match( "expected:.*index bar", err )
assert_not_match( "expected:.*index foo", err )
tmp = m.bar
local ok, err = pcall( function() tmp = m.bar end )
assert_false( ok, "expected:.*replayed blocked action" )
assert_not_match( "expected:.*index bar", err )
assert_match( "index foo", err )
end
-- ../src/unittestfiles.nw:26
-- ../src/restrictions.nw:130
function label_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:label( 'foo' ) end )
assert_false( ok, "set label in replay mode" )
assert_match( "Can not add labels in replay mode", err )
end
function label_on_empty_actionlist_fails_test ()
local ok, err = pcall( function() mc:label( 'bar' ) end )
assert_false( ok, "set label with empty action list" )
assert_match( "No action is recorded yet", err )
end
-- ../src/unittestfiles.nw:27
-- ../src/main.nw:510
function returns_on_empty_list_fails_test ()
local ok, err = pcall( function() mc:returns(nil) end )
assert_false( ok, "returns called on nothing" )
assert_match( "No action is recorded yet.", err )
end
function returns_make_call_fail_test ()
local tmp = m.foo ;mc:returns(1)
local ok, err = pcall( function() tmp(2) end )
assert_false( ok, "called index with returnvalue" )
assert_match( "Can not call foo. It has a returnvalue.", err )
end
function callable_index_replays_anytimes_test ()
local tmp = m.foo()
mc:replay()
tmp = m.foo
tmp = m.foo
tmp = m.foo()
mc:verify()
end
-- ../src/unittestfiles.nw:28
-- ../src/main.nw:449
function create_completely_empty_mock_test ()
for k, v in pairs( m ) do
fail( "Mock should be empty but contains "..tostring(k) )
end
end
function create_mock_during_replay_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:mock() end )
assert_false( ok, "mock() succeeded" )
assert_match( "New mock during replay.", err )
end
-- ../src/unittestfiles.nw:29
-- ../src/main.nw:692
function replay_in_any_order_test ()
m.a = 1
m.b = 2
m.c = 3
mc:replay()
m.c = 3
m.a = 1
m.b = 2
mc:verify()
end
function replaying_unexpected_action_fails_test ()
mc:replay()
local ok, err = pcall( function() m:somethingelse() end )
assert_false( ok, "unexpected replay succeeded" )
assert_match( "Unexpected action index somethingelse", err )
end
-- ../src/main.nw:718
function cached_recording_callable_fails_during_replay_test ()
local tmp = m.foo ; tmp()
mc:replay()
local ok, err = pcall( function() tmp() end )
assert_false( ok, "Cached callable not detected" )
assert_match( "client uses cached callable from recording", err )
end
-- ../src/unittestfiles.nw:30
-- ../src/main.nw:642
function replay_twice_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:replay() end )
assert_false( ok, "replay succeeded twice" )
assert_match( "Replay called twice.", err )
end
function multiple_controllers_test ()
local mc2 = lemock.controller()
local m2 = mc2:mock()
-- m -- -- m2 --
m.foo = 1
mc:replay()
m2.bar = 2
m.foo = 1
mc2:replay()
mc:verify()
m2.bar = 2
mc2:verify()
end
-- ../src/unittestfiles.nw:31
-- ../src/restrictions.nw:38
function times_test ()
local tmp = m.foo ;mc:returns( 2 ):times( 2, 3 )
mc:replay()
-- 1
local tmp = m.foo
local ok, err = pcall( function() mc:verify() end )
assert_false( ok, "verified unsatisfied action" )
assert_match( "Wrong replay count 1 ", err )
-- 2
local tmp = m.foo
mc:verify()
-- 3
local tmp = m.foo
mc:verify()
-- 4
local ok, err = pcall( function() local tmp = m.foo end )
assert_false( ok, "replaied finished action" )
assert_match( "Unexpected action index foo", err )
end
function times_called_twice_test ()
m.foo = 1 ;mc:times( 0, math.huge ):times( 1 )
end
function times_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:times(1) end )
assert_false( ok, "changed times in replay mode" )
assert_match( "Can not set times in replay mode.", err )
end
function unrealistic_times_fails_with_message_test ()
m.a = 'a'
local ok, err = pcall( function() mc:times(0) end )
assert_false( ok, "accepted unrealistic time arguments" )
assert_match( "Unrealistic time arguments", err )
end
-- ../src/unittestfiles.nw:32
-- ../src/main.nw:736
function verify_during_record_phase_fails_test ()
local ok, err = pcall( function() mc:verify() end )
assert_false( ok, "Verify succeeded" )
assert_match( "Verify called during record.", err )
end
function verify_replayed_actionlist_test ()
mc:replay()
mc:verify()
end
function verify_unreplyed_actionlist_fails_test ()
local tmp = m.foo
mc:replay()
local ok, err = pcall( function() mc:verify() end )
assert_false( ok, "Verify succeeded" )
assert_match( "Wrong replay count 0 ", err )
end
-- ../src/unittestfiles.nw:33
-- ../src/action/call.nw:13
function call_test ()
m.foo(1,2,3)
mc:replay()
local tmp = m.foo(1,2,3)
assert_nil( tmp )
mc:verify()
end
function call_anyarg_test ()
m.foo(1,mc.ANYARG,3)
mc:replay()
local tmp = m.foo(1,2,3)
mc:verify()
end
function call_anyargs_test ()
m.foo(mc.ANYARGS)
mc:replay()
local tmp = m.foo(1,2,3)
mc:verify()
end
function call_anyargs_bad_fails_test ()
local ok, err = pcall( function() m.foo(mc.ANYARGS, 1) end )
assert_false( ok, "ANYARGS misused" )
assert_match( "ANYARGS not at end", err )
end
function call_return_test ()
m.foo(1,2,3) ;mc:returns( 0, 9 )
mc:replay()
local tmp1, tmp2 = m.foo(1,2,3)
assert_equal( 0, tmp1 )
assert_equal( 9, tmp2 )
mc:verify()
end
function call_wrong_name_fails_test ()
m.foo(1,2,3) ;mc:returns( 0 )
mc:replay()
local ok, err = pcall( function() m:bar(1,2,3) end )
assert_false( ok, "replay wrong index" )
assert_match( "Unexpected action index bar", err )
end
function call_wrong_arg_fails_test ()
m.foo(1,2,3) ;mc:returns( 0 )
mc:replay()
local ok, err = pcall( function() m.foo(1) end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action call foo", err )
end
function call_throws_error_test ()
m.boo('Ba') ;mc:error( "Call throws error" )
mc:replay()
local ok, err = pcall( function() m.boo('Ba') end )
assert_false( ok, "did not throw error" )
assert_match( "Call throws error", err )
end
-- ../src/unittestfiles.nw:35
-- ../src/main.nw:596
function error_during_replay_fails_test ()
local tmp = m.foo
mc:replay()
local ok, err = pcall( function() mc:error(1) end )
assert_false( ok, "error() succeeded during replay" )
assert_match( "Error called during replay.", err )
end
function error_twice_fails_test ()
local tmp = m.foo ;mc:error(1)
local ok, err = pcall( function() mc:error(2) end )
assert_false( ok, "duplicate error() succeeded" )
assert_match( "Returns and/or Error called twice for same action.", err )
end
function error_plus_returns_fails_test ()
local tmp = m.foo ;mc:returns(1)
local ok, err = pcall( function() mc:error(2) end )
assert_false( ok, "both error and returns succeeded" )
assert_match( "Returns and/or Error called twice for same action.", err )
end
-- ../src/unittestfiles.nw:36
-- ../src/action/index.nw:13
function index_test ()
local tmp = m.foo
mc:replay()
local tmp = m.foo
assert_nil( tmp )
mc:verify()
end
function index_returns_test ()
local tmp = m.foo ;mc:returns( 1 )
mc:replay()
local tmp = m.foo
assert_equal( 1, tmp )
mc:verify()
end
function index_wrong_key_fails_test ()
local tmp = m.foo ;mc:returns( 1 )
mc:replay()
local ok, err = pcall( function() local tmp = m.bar end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action index bar", err )
end
function index_throws_error_test ()
local tmp = m.foo ;mc:error( "Index throws error" )
mc:replay()
local ok, err = pcall( function() tmp = m.foo end )
assert_false( ok, "did not throw error" )
assert_match( "Index throws error", err )
end
-- ../src/unittestfiles.nw:37
-- ../src/action/newindex.nw:9
function newindex_test ()
m.foo = 1
mc:replay()
m.foo = 1
mc:verify()
end
function newindex_anyarg_test ()
m.foo = mc.ANYARG
mc:replay()
m.foo = 1
mc:verify()
end
function newindex_wrong_key_fails_test ()
m.foo = 1
mc:replay()
local ok, err = pcall( function() m.bar = 1 end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action newindex", err )
end
function newindex_wrong_value_fails_test ()
m.foo = 1
mc:replay()
local ok, err = pcall( function() m.foo = 0 end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action newindex foo", err )
end
function newindex_throws_error_test ()
m.foo = 1 ;mc:error( "newindex throws error" )
mc:replay()
local ok, err = pcall( function() m.foo = 1 end )
assert_false( ok, "did not throw error" )
assert_match( "newindex throws error", err )
end
-- ../src/unittestfiles.nw:38
-- ../src/main.nw:550
function returns_during_replay_fails_test ()
local tmp = m.foo
mc:replay()
local ok, err = pcall( function() mc:returns(1) end )
assert_false( ok, "returns() succeeded during replay" )
assert_match( "Returns called during replay.", err )
end
function returns_on_nonreturning_action_fails_test ()
m.foo = 1 -- assignments can't return
local ok, err = pcall( function() mc:returns(0) end )
assert_false( ok, "returns() succeeded on non-returning action" )
assert_match( "Previous action can not return anything.", err )
end
function returns_twice_fails_test ()
local tmp = m.foo ;mc:returns(1)
local ok, err = pcall( function() mc:returns(2) end )
assert_false( ok, "duplicate returns() succeeded" )
assert_match( "Returns and/or Error called twice for same action.", err )
end
-- ../src/unittestfiles.nw:39
-- ../src/action/selfcall.nw:12
function selfcall_test ()
m(11)
mc:replay()
local tmp = m(11)
assert_nil( tmp )
mc:verify()
end
function selfcall_returns_test ()
m(99) ;mc:returns(1,nil,'foo')
mc:replay()
local a,b,c = m(99)
assert_equal( 1, a )
assert_equal( nil, b )
assert_equal( 'foo', c )
mc:verify()
end
function selfcall_wrong_argument_fails_test ()
m(99) ;mc:returns('a','b','c')
mc:replay()
local ok, err = pcall( function() m(90) end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action selfcall", err )
end
function selfcall_wrong_number_of_arguments_fails_test ()
m(1,2,3)
mc:replay()
local ok, err = pcall( function() m(1,2,3,4) end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action selfcall", err )
end
function selfcall_throws_error_test ()
m('Ba') ;mc:error( "Selfcall throws error" )
mc:replay()
local ok, err = pcall( function() m('Ba') end )
assert_false( ok, "did not throw error" )
assert_match( "Selfcall throws error", err )
end

@ -0,0 +1,264 @@
-- ../src/doc/userguide/unittests.nw:7
-- ../src/misc.nw:7
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
-- ../src/doc/userguide/unittests.nw:8
require 'lunit'
module( 'unit.userguide', lunit.testcase, package.seeall )
-- ../src/doc/userguide/section_actions.nw:32
function actions_test ()
-- ../src/doc/userguide/section_actions.nw:20
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m.x = 17 -- assignment
r = m.x -- indexing
m.x(1,2,3) -- method call
m:x(1,2,3) -- method call
m(1,2,3) -- self call
-- ../src/doc/userguide/section_actions.nw:34
end
-- ../src/doc/userguide/unittests.nw:13
-- ../src/doc/userguide/section_anyargs.nw:42
function example_anyargs_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.fetch_data (con)
local res = con:poll()
while not res do
con:sleep( 10 )
res = con:poll()
end
con.lasttime = os.time()
return tonumber( res )
end
end
-- ../src/doc/userguide/section_anyargs.nw:24
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:poll() ;mc :returns(nil)
con:sleep(mc.ANYARG)
con:poll() ;mc :returns('123.45')
con.lasttime = mc.ANYARG
mc:replay()
require 'foo'
local res = foo.fetch_data(con)
assert( math.abs(res-123.45) < 0.0005 )
mc:verify()
-- ../src/doc/userguide/section_anyargs.nw:57
end
-- ../src/doc/userguide/unittests.nw:14
-- ../src/doc/userguide/section_close.nw:53
function close_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.dump (xio, name, len)
local f = xio.open( name, 'r' )
f:read( len )
f:close()
end
end
-- ../src/doc/userguide/section_close.nw:31
require 'lemock'
local mc = lemock.controller()
local myio = mc:mock()
local fs = mc:mock()
myio.open('abc', 'r') ;mc :returns(fs)
mc :label('open')
fs:read(mc.ANYARG) ;mc :returns('data')
mc :atleastonce() :label('read') :depend('open')
fs:close() ;mc :returns(true)
mc :depend('open') :close('read')
mc:replay()
require 'foo'
foo.dump(myio, 'abc', 128)
mc:verify()
-- ../src/doc/userguide/section_close.nw:64
end
-- ../src/doc/userguide/unittests.nw:15
-- ../src/doc/userguide/section_label_depend.nw:57
function example_depend_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.draw_square (sq)
sq:botright() sq:topright() sq:rightedge()
sq:botleft() sq:topleft() sq:leftedge()
sq:topedge() sq:botedge()
sq:fill()
end
end
-- ../src/doc/userguide/section_label_depend.nw:35
require 'lemock'
local mc = lemock.controller()
local square = mc:mock()
square:topleft() ;mc :label('tl')
square:topright() ;mc :label('tr')
square:botleft() ;mc :label('bl')
square:botright() ;mc :label('br')
square:leftedge() ;mc :label('edge') :depend('tl', 'bl')
square:rightedge() ;mc :label('edge') :depend('tr', 'br')
square:topedge() ;mc :label('edge') :depend('tl', 'tr')
square:botedge() ;mc :label('edge') :depend('bl', 'br')
square:fill() ;mc :depend('edge')
mc:replay()
require 'foo'
foo.draw_square( square )
mc:verify()
-- ../src/doc/userguide/section_label_depend.nw:69
end
-- ../src/doc/userguide/unittests.nw:16
-- ../src/doc/userguide/chapter_tricks.nw:65
function overloading_test ()
-- ../src/doc/userguide/chapter_tricks.nw:39
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
do
local function add (a, b)
if type(a) == 'number' then
return m.add_number(a, b)
else
return m.add_string(a, b)
end
end
rawset( m, 'add', add ) -- not recorded
end -- do
m.add_number(1, 2) ;mc :returns(3)
m.add_string('foo', 'bar') ;mc :returns('foobar')
mc:replay()
assert_equal( 3, m.add(1, 2) )
assert_equal( 'foobar', m.add('foo', 'bar') )
mc:verify()
-- ../src/doc/userguide/chapter_tricks.nw:67
end
-- ../src/doc/userguide/unittests.nw:17
-- ../src/doc/userguide/section_returns_error.nw:36
function returns_error_test ()
-- ../src/doc/userguide/section_returns_error.nw:27
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m:foo(17) ;mc :returns(nil, "index out of range")
m:bar(-1) ;mc :error("invalid index")
-- ../src/doc/userguide/section_returns_error.nw:38
end
-- ../src/doc/userguide/unittests.nw:18
-- ../src/doc/userguide/chapter_introduction.nw:71
function example_simple_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
q = require 'luasql.sqlite3'
function foo.insert_data()
local env = q()
local con = env:connect( '/data/base' )
local ok, err = pcall( con.execute, con, 'insert foo bar' )
con:close()
env:close()
return ok
end
return foo
end
-- ../src/doc/userguide/chapter_introduction.nw:40
-- Setup
require 'lemock'
local mc = lemock.controller()
local sqlite3 = mc:mock()
local env = mc:mock()
local con = mc:mock()
package.loaded.luasql = nil
package.preload['luasql.sqlite3'] = function ()
luasql = {}
luasql.sqlite3 = sqlite3
return sqlite3
end
-- Record
sqlite3() ;mc :returns(env)
env:connect('/data/base') ;mc :returns(con)
con:execute(mc.ANYARGS) ;mc :error('LuaSQL: no such table')
con:close()
env:close()
-- Replay
mc:replay()
require 'foo'
local res = foo.insert_data(17)
assert(res==false)
--Verify
mc:verify()
-- ../src/doc/userguide/chapter_introduction.nw:87
end
-- ../src/doc/userguide/unittests.nw:19
-- ../src/doc/userguide/section_times.nw:52
function example_times_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.mk_watcher ( con )
local o = {}
function o:set ( key, val )
con:update( key, val )
end
return o
end
end
-- ../src/doc/userguide/section_times.nw:36
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:log(mc.ANYARGS) ;mc :anytimes()
con:update('x',3) ;mc :returns(true) :atleastonce()
mc:replay()
require 'foo'
local watcher = foo.mk_watcher( con )
watcher:set( 'x', 3 )
mc:verify()
-- ../src/doc/userguide/section_times.nw:65
end

@ -0,0 +1,352 @@
LeMock User Guide
v 0.6
2009-05-30
%!postproc(html): &ndash;
%!postproc(tex): --
= Introduction =
Mock objects replace difficult external objects during unit testing by
simulating the behaviors of the replaced objects. This is done by first
recording actions and their responses with the mock objects, and then
switching to replay mode. During replay mode the mock objects simulate the
replaced objects by looking up actions and replaying the recorded
responses, and finally verifying that all expected actions where completely
replayed.
Actions are stored in a list in a special controller object. During replay
the list is searched in recording order for the first matching action that
can be replayed.
Restrictions on the actions can be inserted during the recording phase. An
action can have a maximum count of how many times it will be replayed, and
a minimum count of how many times it must be replayed to be satisfied. An
action can depend on any set of other actions, and can not be replayed
before all of its depended actions are satisfied. An action can close any
set of actions when it is replayed, which stops all further replaying of
the closed actions. This is good for simulating state changes.
=== Example ===
This example tests that the insert_data function of the foo module handles
a missing data base table gracefully.
```
-- Setup
require 'lemock'
local mc = lemock.controller()
local sqlite3 = mc:mock()
local env = mc:mock()
local con = mc:mock()
package.loaded.luasql = nil
package.preload['luasql.sqlite3'] = function ()
luasql = {}
luasql.sqlite3 = sqlite3
return sqlite3
end
-- Record
sqlite3() ;mc :returns(env)
env:connect('/data/base') ;mc :returns(con)
con:execute(mc.ANYARGS) ;mc :error('LuaSQL: no such table')
con:close()
env:close()
-- Replay
mc:replay()
require 'foo'
local res = foo.insert_data(17)
assert(res==false)
--Verify
mc:verify()
```
First a controller is created. Then three mock objects are created, one for
the sqlite3 module, and two for objects returned by the (simulated) module.
Then a preloader for the sqlite3 module is installed, which returns the
sqlite3 mock object instead of the actual sqlite3 module.
In the record phase the expected calls and their return values (or thrown
errors) are recorded. The order is not significant, so this simplified test
will not detect if the close method is called before the execute method.
In the replay phase the tested module is loaded and executed. It will use
the mock objects instead of the real data base, and if it makes any
unrecorded calls, an error is thrown.
The verify phase asserts that all recorded actions have been replayed. If
the foo module for example forgets to call the close method, verify throws
an error.
= The Mock Object =
Mock objects are empty objects with special Lua meta methods that detect
actions performed with the object. What happens depends on the state
(recording or replaying) of the controller which created the mock object.
During recording the mock object adds the action to the controller's list
of recorded actions. During replay the mock object looks for a matching
recorded action that can be replayed, and simulates the action.
Some action attributes can not be inferred by the mock objects, for example
return values. These attributes have to be added afterwards with special
controller methods, and always affect the last recorded action.
== Actions ==
Mock objects detect four types of actions: assignment, indexing, method
call, and self call. During replay an action will only match if it is the
very same action, that is, the same type of action performed on the same
mock object with all the same arguments. There are however
[special arguments #anyargs] that can be used during recording.
```
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m.x = 17 -- assignment
r = m.x -- indexing
m.x(1,2,3) -- method call
m:x(1,2,3) -- method call
m(1,2,3) -- self call
```
== Anyargs ==[anyargs]
An //anyarg// is a special argument used when recording, that will match
any argument during replay. It can appear anywhere and any times in an
argument list, or as the argument in an assignment, to replace real
arguments. There is also //anyargs//, which will match any number
(including zero) of any arguments. Anyargs can only appear as the last
argument of an argument list. Anyarg and anyargs are handy when the actual
values of the arguments during replay are unimportant or unknown.
Anyarg and anyargs are constants defined in the controller object.
=== Example ===
This example tests that the fetch_data function of module foo waits a while
and retries when no data is immediately available, and that it updates the
value of lasttime.
```
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:poll() ;mc :returns(nil)
con:sleep(mc.ANYARG)
con:poll() ;mc :returns('123.45')
con.lasttime = mc.ANYARG
mc:replay()
require 'foo'
local res = foo.fetch_data(con)
assert( math.abs(res-123.45) < 0.0005 )
mc:verify()
```
= The Controller =
The controller's main purpose is to store the recorded actions, create mock
objects, switch to replay mode, and verify the completion of the replay
phase. But it is also needed to set or change special action attributes
during recording.
It is possible, although doubtfully useful, to use several controllers in
parallel during a single unit test. Each controller maintains its own
action list and state, and mock objects remember which controller they
belong to.
== Returns & Error ==
The by far most useful special action attribute is the return value.
Indexing actions can return a single value, while call actions and self
call actions can return a list of values. The return value is set with the
//returns// method, and it is an error to set the return value twice for
the same action.
For purposes of unit testing it is often useful to simulate errors. All
actions can raise an error, and return an error value (usually a string).
The return value is set with the //error// method. An action can not have
both a return value and raise an error.
=== Example ===
```
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m:foo(17) ;mc :returns(nil, "index out of range")
m:bar(-1) ;mc :error("invalid index")
```
== Label & Depend ==
Dependencies block actions from replaying until other actions have replayed
first. They can be used to verify that actions are being replayed in a
valid order.
To add dependencies, actions must first be labeled with one or more
//labels//. The same label can be given to several actions. As long as some
action with the label remains unsatisfied, that label is blocked, and all
actions depending on that label will not replay.
=== Example ===
This (contrived) example tests that function draw_square in module foo
calls all the necessary drawing methods of a square object in a correct
order. Note that there can be more than one correct order.
```
require 'lemock'
local mc = lemock.controller()
local square = mc:mock()
square:topleft() ;mc :label('tl')
square:topright() ;mc :label('tr')
square:botleft() ;mc :label('bl')
square:botright() ;mc :label('br')
square:leftedge() ;mc :label('edge') :depend('tl', 'bl')
square:rightedge() ;mc :label('edge') :depend('tr', 'br')
square:topedge() ;mc :label('edge') :depend('tl', 'tr')
square:botedge() ;mc :label('edge') :depend('bl', 'br')
square:fill() ;mc :depend('edge')
mc:replay()
require 'foo'
foo.draw_square( square )
mc:verify()
```
This example demonstrates two different ways of using dependencies. All the
corners have unique labels, because each edge depend on a set of specific
corners. But all the edges have the same label, because the fill operation
only depends on //all// edges have been satisfied.
== Times ==
The default for a recorded action is to be replayed exactly once.
``times(2)`` changes that to exactly two times, and ``times(1,2)`` changes
it to at least one time and at most two times.
When the action has been replayed the least count times it is
//satisfied//, which means verify will not complain about it, and it no
longer blocks actions that depend on this action from being replayed. If
the least count is zero the action is automatically satisfied and need not
be replayed at all, i.e., it is optional.
When the action has been replayed the most count times it will not replay
any more. The most replay count can be set to infinity (``math.huge`` or
``1/0``), in which case the action will never stop replaying.
``anytimes()`` can be used as an alias for ``times(0,1/0)``, and
``atleastonce()`` can be used as an alias for ``times(1,1/0)``.
=== Example ===
This example tests that method update is called at least once.
```
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:log(mc.ANYARGS) ;mc :anytimes()
con:update('x',3) ;mc :returns(true) :atleastonce()
mc:replay()
require 'foo'
local watcher = foo.mk_watcher( con )
watcher:set( 'x', 3 )
mc:verify()
```
== Close ==
Close can be used to simulate state changes in a limited way. When an
action with a close statement is replayed for the first time, it will
permanently block all labels in its close statement, so that actions with
these labels no longer replays. This passes on matching to later actions in
the action list, which may for example have different return values.
The closing simply blocks the labels, and it has nothing to do with max
replay counts or if closed actions have been satisfied or not. Closing an
unsatisfied action however results in an immediate failure.
=== Example ===
This example tests that the dump function of module foo calls the myio
functions in a correct order. The read function can be called any number of
times, until it is closed by the close function.
```
require 'lemock'
local mc = lemock.controller()
local myio = mc:mock()
local fs = mc:mock()
myio.open('abc', 'r') ;mc :returns(fs)
mc :label('open')
fs:read(mc.ANYARG) ;mc :returns('data')
mc :atleastonce() :label('read') :depend('open')
fs:close() ;mc :returns(true)
mc :depend('open') :close('read')
mc:replay()
require 'foo'
foo.dump(myio, 'abc', 128)
mc:verify()
```
= Tricks =
Mock objects are completely empty, and do not contain any methods or
properties of their own. If they did, that would risk shadowing a name of a
simulated object's method or property. There is however nothing preventing
users from defining methods and properties in mock objects. This way mock
objects can be turned into stubs, or a kind of mockstub hybrid.
== Method Overloading ==
Lua does not support method overloading, but it can be (and sometimes is)
implemented manually by testing of function arguments. This presents a
problem to LeMock, because it matches exact arguments, and anyargs in not
sufficient. In this case the mock object can be extended with a dispatcher
function.
=== Example ===
This example shows a mock object with an overloaded add function. The stub
function can not be defined in the usual way, because that would record an
assignment action; it needs to be defined with //rawset//.
```
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
do
local function add (a, b)
if type(a) == 'number' then
return m.add_number(a, b)
else
return m.add_string(a, b)
end
end
rawset( m, 'add', add ) -- not recorded
end -- do
m.add_number(1, 2) ;mc :returns(3)
m.add_string('foo', 'bar') ;mc :returns('foobar')
mc:replay()
assert_equal( 3, m.add(1, 2) )
assert_equal( 'foobar', m.add('foo', 'bar') )
mc:verify()
```

@ -0,0 +1,11 @@
LeMock
%!includeconf: config.rc
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
%!postproc(html): 'ID="body"' 'ID="page-COPYRIGHT"'
%!include(html): ''menubar.html''
= License =
%!include: ../../COPYRIGHT

@ -0,0 +1,13 @@
LeMock
%!includeconf: config.rc
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
%!postproc(html): 'ID="body"' 'ID="page-DEVEL"'
%!include(html): ''menubar.html''
= Developer Notes =
%!include: ../../DEVEL
--------------------
%%Date(%Y-%m-%d)

@ -0,0 +1,13 @@
LeMock
%!includeconf: config.rc
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
%!postproc(html): 'ID="body"' 'ID="page-HISTORY"'
%!include(html): ''menubar.html''
= History =
%!include: ../../HISTORY
--------------------
%%Date(%Y-%m-%d)

@ -0,0 +1,13 @@
LeMock
%!includeconf: config.rc
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
%!postproc(html): 'ID="body"' 'ID="page-README"'
%!include(html): ''menubar.html''
= Readme =
%!include: ../../README
--------------------
%%Date(%Y-%m-%d)

@ -0,0 +1,3 @@
%!options: --no-rc
%!style(html): style.css
%!options(html): --css-sugar

@ -0,0 +1,9 @@
LeMock
%!includeconf: config.rc
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
%!postproc(html): 'ID="body"' 'ID="page-index"'
%!include(html): ''menubar.html''

@ -0,0 +1,7 @@
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>

@ -0,0 +1,13 @@
LeMock
%!includeconf: config.rc
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
%!postproc(html): 'ID="body"' 'ID="page-userguide"'
%!include(html): ''menubar.html''
%%toc
%!include: ../userguide.t2t
--------------------
%%Date(%Y-%m-%d)

@ -0,0 +1,118 @@
# Locate Lua library
# This module defines
# LUA_EXECUTABLE, if found
# LUA_FOUND, if false, do not try to link to Lua
# LUA_LIBRARIES
# LUA_INCLUDE_DIR, where to find lua.h
# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
#
# Note that the expected include convention is
# #include "lua.h"
# and not
# #include <lua/lua.h>
# This is because, the lua location is not standardized and may exist
# in locations other than lua/
#=============================================================================
# Copyright 2007-2009 Kitware, Inc.
# Modified to support Lua 5.2 by LuaDist 2012
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
#
# The required version of Lua can be specified using the
# standard syntax, e.g. FIND_PACKAGE(Lua 5.1)
# Otherwise the module will search for any available Lua implementation
# Always search for non-versioned lua first (recommended)
SET(_POSSIBLE_LUA_INCLUDE include include/lua)
SET(_POSSIBLE_LUA_EXECUTABLE lua)
SET(_POSSIBLE_LUA_LIBRARY lua)
# Determine possible naming suffixes (there is no standard for this)
IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}")
ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "52" "5.2" "-5.2" "51" "5.1" "-5.1")
ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
# Set up possible search names and locations
FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES})
LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}")
LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}")
LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}")
ENDFOREACH(_SUFFIX)
# Find the lua executable
FIND_PROGRAM(LUA_EXECUTABLE
NAMES ${_POSSIBLE_LUA_EXECUTABLE}
)
# Find the lua header
FIND_PATH(LUA_INCLUDE_DIR lua.h
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE}
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw # Fink
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt
)
# Find the lua library
FIND_LIBRARY(LUA_LIBRARY
NAMES ${_POSSIBLE_LUA_LIBRARY}
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES lib64 lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw
/opt/local
/opt/csw
/opt
)
IF(LUA_LIBRARY)
# include the math library for Unix
IF(UNIX AND NOT APPLE)
FIND_LIBRARY(LUA_MATH_LIBRARY m)
SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries")
# For Windows and Mac, don't need to explicitly include the math library
ELSE(UNIX AND NOT APPLE)
SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries")
ENDIF(UNIX AND NOT APPLE)
ENDIF(LUA_LIBRARY)
# Determine Lua version
IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h")
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"")
STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
UNSET(lua_version_str)
ENDIF()
INCLUDE(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if
# all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua
REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR
VERSION_VAR LUA_VERSION_STRING)
MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE)

@ -0,0 +1,321 @@
# LuaDist CMake utility library.
# Provides sane project defaults and macros common to LuaDist CMake builds.
#
# Copyright (C) 2007-2012 LuaDist.
# by David Manura, Peter Drahoš
# Redistribution and use of this file is allowed according to the terms of the MIT license.
# For details see the COPYRIGHT file distributed with LuaDist.
# Please note that the package source code is licensed under its own license.
## Extract information from dist.info
if ( NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/dist.info )
message ( FATAL_ERROR
"Missing dist.info file (${CMAKE_CURRENT_SOURCE_DIR}/dist.info)." )
endif ()
file ( READ ${CMAKE_CURRENT_SOURCE_DIR}/dist.info DIST_INFO )
if ( "${DIST_INFO}" STREQUAL "" )
message ( FATAL_ERROR "Failed to load dist.info." )
endif ()
# Reads field `name` from dist.info string `DIST_INFO` into variable `var`.
macro ( _parse_dist_field name var )
string ( REGEX REPLACE ".*${name}[ \t]?=[ \t]?[\"']([^\"']+)[\"'].*" "\\1"
${var} "${DIST_INFO}" )
if ( ${var} STREQUAL DIST_INFO )
message ( FATAL_ERROR "Failed to extract \"${var}\" from dist.info" )
endif ()
endmacro ()
#
_parse_dist_field ( name DIST_NAME )
_parse_dist_field ( version DIST_VERSION )
_parse_dist_field ( license DIST_LICENSE )
_parse_dist_field ( author DIST_AUTHOR )
_parse_dist_field ( maintainer DIST_MAINTAINER )
_parse_dist_field ( url DIST_URL )
_parse_dist_field ( desc DIST_DESC )
message ( "DIST_NAME: ${DIST_NAME}")
message ( "DIST_VERSION: ${DIST_VERSION}")
message ( "DIST_LICENSE: ${DIST_LICENSE}")
message ( "DIST_AUTHOR: ${DIST_AUTHOR}")
message ( "DIST_MAINTAINER: ${DIST_MAINTAINER}")
message ( "DIST_URL: ${DIST_URL}")
message ( "DIST_DESC: ${DIST_DESC}")
string ( REGEX REPLACE ".*depends[ \t]?=[ \t]?[\"']([^\"']+)[\"'].*" "\\1"
DIST_DEPENDS ${DIST_INFO} )
if ( DIST_DEPENDS STREQUAL DIST_INFO )
set ( DIST_DEPENDS "" )
endif ()
message ( "DIST_DEPENDS: ${DIST_DEPENDS}")
## 2DO: Parse DIST_DEPENDS and try to install Dependencies with automatically using externalproject_add
## INSTALL DEFAULTS (Relative to CMAKE_INSTALL_PREFIX)
# Primary paths
set ( INSTALL_BIN bin CACHE PATH "Where to install binaries to." )
set ( INSTALL_LIB lib CACHE PATH "Where to install libraries to." )
set ( INSTALL_INC include CACHE PATH "Where to install headers to." )
set ( INSTALL_ETC etc CACHE PATH "Where to store configuration files" )
set ( INSTALL_SHARE share CACHE PATH "Directory for shared data." )
# Secondary paths
option ( INSTALL_VERSION
"Install runtime libraries and executables with version information." OFF)
set ( INSTALL_DATA ${INSTALL_SHARE}/${DIST_NAME} CACHE PATH
"Directory the package can store documentation, tests or other data in.")
set ( INSTALL_DOC ${INSTALL_DATA}/doc CACHE PATH
"Recommended directory to install documentation into.")
set ( INSTALL_EXAMPLE ${INSTALL_DATA}/example CACHE PATH
"Recommended directory to install examples into.")
set ( INSTALL_TEST ${INSTALL_DATA}/test CACHE PATH
"Recommended directory to install tests into.")
set ( INSTALL_FOO ${INSTALL_DATA}/etc CACHE PATH
"Where to install additional files")
# Tweaks and other defaults
# Setting CMAKE to use loose block and search for find modules in source directory
set ( CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS true )
set ( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH} )
option ( BUILD_SHARED_LIBS "Build shared libraries" ON )
# In MSVC, prevent warnings that can occur when using standard libraries.
if ( MSVC )
add_definitions ( -D_CRT_SECURE_NO_WARNINGS )
endif ()
# RPath and relative linking
option ( USE_RPATH "Use relative linking." ON)
if ( USE_RPATH )
string ( REGEX REPLACE "[^!/]+" ".." UP_DIR ${INSTALL_BIN} )
set ( CMAKE_SKIP_BUILD_RPATH FALSE CACHE STRING "" FORCE )
set ( CMAKE_BUILD_WITH_INSTALL_RPATH FALSE CACHE STRING "" FORCE )
set ( CMAKE_INSTALL_RPATH $ORIGIN/${UP_DIR}/${INSTALL_LIB}
CACHE STRING "" FORCE )
set ( CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE CACHE STRING "" FORCE )
set ( CMAKE_INSTALL_NAME_DIR @executable_path/${UP_DIR}/${INSTALL_LIB}
CACHE STRING "" FORCE )
endif ()
## MACROS
# Parser macro
macro ( parse_arguments prefix arg_names option_names)
set ( DEFAULT_ARGS )
foreach ( arg_name ${arg_names} )
set ( ${prefix}_${arg_name} )
endforeach ()
foreach ( option ${option_names} )
set ( ${prefix}_${option} FALSE )
endforeach ()
set ( current_arg_name DEFAULT_ARGS )
set ( current_arg_list )
foreach ( arg ${ARGN} )
set ( larg_names ${arg_names} )
list ( FIND larg_names "${arg}" is_arg_name )
if ( is_arg_name GREATER -1 )
set ( ${prefix}_${current_arg_name} ${current_arg_list} )
set ( current_arg_name ${arg} )
set ( current_arg_list )
else ()
set ( loption_names ${option_names} )
list ( FIND loption_names "${arg}" is_option )
if ( is_option GREATER -1 )
set ( ${prefix}_${arg} TRUE )
else ()
set ( current_arg_list ${current_arg_list} ${arg} )
endif ()
endif ()
endforeach ()
set ( ${prefix}_${current_arg_name} ${current_arg_list} )
endmacro ()
# install_executable ( executable_targets )
# Installs any executables generated using "add_executable".
# USE: install_executable ( lua )
# NOTE: subdirectories are NOT supported
set ( CPACK_COMPONENT_RUNTIME_DISPLAY_NAME "${DIST_NAME} Runtime" )
set ( CPACK_COMPONENT_RUNTIME_DESCRIPTION
"Executables and runtime libraries. Installed into ${INSTALL_BIN}." )
macro ( install_executable )
foreach ( _file ${ARGN} )
if ( INSTALL_VERSION )
set_target_properties ( ${_file} PROPERTIES VERSION ${DIST_VERSION}
SOVERSION ${DIST_VERSION} )
endif ()
install ( TARGETS ${_file} RUNTIME DESTINATION ${INSTALL_BIN}
COMPONENT Runtime )
endforeach()
endmacro ()
# install_library ( library_targets )
# Installs any libraries generated using "add_library" into apropriate places.
# USE: install_library ( libexpat )
# NOTE: subdirectories are NOT supported
set ( CPACK_COMPONENT_LIBRARY_DISPLAY_NAME "${DIST_NAME} Development Libraries" )
set ( CPACK_COMPONENT_LIBRARY_DESCRIPTION
"Static and import libraries needed for development. Installed into ${INSTALL_LIB} or ${INSTALL_BIN}." )
macro ( install_library )
foreach ( _file ${ARGN} )
if ( INSTALL_VERSION )
set_target_properties ( ${_file} PROPERTIES VERSION ${DIST_VERSION}
SOVERSION ${DIST_VERSION} )
endif ()
install ( TARGETS ${_file}
RUNTIME DESTINATION ${INSTALL_BIN} COMPONENT Runtime
LIBRARY DESTINATION ${INSTALL_LIB} COMPONENT Runtime
ARCHIVE DESTINATION ${INSTALL_LIB} COMPONENT Library )
endforeach()
endmacro ()
# helper function for various install_* functions, for PATTERN/REGEX args.
macro ( _complete_install_args )
if ( NOT("${_ARG_PATTERN}" STREQUAL "") )
set ( _ARG_PATTERN PATTERN ${_ARG_PATTERN} )
endif ()
if ( NOT("${_ARG_REGEX}" STREQUAL "") )
set ( _ARG_REGEX REGEX ${_ARG_REGEX} )
endif ()
endmacro ()
# install_header ( files/directories [INTO destination] )
# Install a directories or files into header destination.
# USE: install_header ( lua.h luaconf.h ) or install_header ( GL )
# USE: install_header ( mylib.h INTO mylib )
# For directories, supports optional PATTERN/REGEX arguments like install().
set ( CPACK_COMPONENT_HEADER_DISPLAY_NAME "${DIST_NAME} Development Headers" )
set ( CPACK_COMPONENT_HEADER_DESCRIPTION
"Headers needed for development. Installed into ${INSTALL_INC}." )
macro ( install_header )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO}
COMPONENT Header ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_INC}/${_ARG_INTO}
COMPONENT Header )
endif ()
endforeach()
endmacro ()
# install_data ( files/directories [INTO destination] )
# This installs additional data files or directories.
# USE: install_data ( extra data.dat )
# USE: install_data ( image1.png image2.png INTO images )
# For directories, supports optional PATTERN/REGEX arguments like install().
set ( CPACK_COMPONENT_DATA_DISPLAY_NAME "${DIST_NAME} Data" )
set ( CPACK_COMPONENT_DATA_DESCRIPTION
"Application data. Installed into ${INSTALL_DATA}." )
macro ( install_data )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file}
DESTINATION ${INSTALL_DATA}/${_ARG_INTO}
COMPONENT Data ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_DATA}/${_ARG_INTO}
COMPONENT Data )
endif ()
endforeach()
endmacro ()
# INSTALL_DOC ( files/directories [INTO destination] )
# This installs documentation content
# USE: install_doc ( doc/ doc.pdf )
# USE: install_doc ( index.html INTO html )
# For directories, supports optional PATTERN/REGEX arguments like install().
set ( CPACK_COMPONENT_DOCUMENTATION_DISPLAY_NAME "${DIST_NAME} Documentation" )
set ( CPACK_COMPONENT_DOCUMENTATION_DESCRIPTION
"Application documentation. Installed into ${INSTALL_DOC}." )
macro ( install_doc )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO}
COMPONENT Documentation ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_DOC}/${_ARG_INTO}
COMPONENT Documentation )
endif ()
endforeach()
endmacro ()
# install_example ( files/directories [INTO destination] )
# This installs additional examples
# USE: install_example ( examples/ exampleA )
# USE: install_example ( super_example super_data INTO super)
# For directories, supports optional PATTERN/REGEX argument like install().
set ( CPACK_COMPONENT_EXAMPLE_DISPLAY_NAME "${DIST_NAME} Examples" )
set ( CPACK_COMPONENT_EXAMPLE_DESCRIPTION
"Examples and their associated data. Installed into ${INSTALL_EXAMPLE}." )
macro ( install_example )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO}
COMPONENT Example ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_EXAMPLE}/${_ARG_INTO}
COMPONENT Example )
endif ()
endforeach()
endmacro ()
# install_test ( files/directories [INTO destination] )
# This installs tests and test files, DOES NOT EXECUTE TESTS
# USE: install_test ( my_test data.sql )
# USE: install_test ( feature_x_test INTO x )
# For directories, supports optional PATTERN/REGEX argument like install().
set ( CPACK_COMPONENT_TEST_DISPLAY_NAME "${DIST_NAME} Tests" )
set ( CPACK_COMPONENT_TEST_DESCRIPTION
"Tests and associated data. Installed into ${INSTALL_TEST}." )
macro ( install_test )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO}
COMPONENT Test ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_TEST}/${_ARG_INTO}
COMPONENT Test )
endif ()
endforeach()
endmacro ()
# install_foo ( files/directories [INTO destination] )
# This installs optional or otherwise unneeded content
# USE: install_foo ( etc/ example.doc )
# USE: install_foo ( icon.png logo.png INTO icons)
# For directories, supports optional PATTERN/REGEX argument like install().
set ( CPACK_COMPONENT_OTHER_DISPLAY_NAME "${DIST_NAME} Unspecified Content" )
set ( CPACK_COMPONENT_OTHER_DESCRIPTION
"Other unspecified content. Installed into ${INSTALL_FOO}." )
macro ( install_foo )
parse_arguments ( _ARG "INTO;PATTERN;REGEX" "" ${ARGN} )
_complete_install_args()
foreach ( _file ${_ARG_DEFAULT_ARGS} )
if ( IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/${_file}" )
install ( DIRECTORY ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO}
COMPONENT Other ${_ARG_PATTERN} ${_ARG_REGEX} )
else ()
install ( FILES ${_file} DESTINATION ${INSTALL_FOO}/${_ARG_INTO}
COMPONENT Other )
endif ()
endforeach()
endmacro ()
## CTest defaults
## CPack defaults
set ( CPACK_GENERATOR "ZIP" )
set ( CPACK_STRIP_FILES TRUE )
set ( CPACK_PACKAGE_NAME "${DIST_NAME}" )
set ( CPACK_PACKAGE_VERSION "${DIST_VERSION}")
set ( CPACK_PACKAGE_VENDOR "LuaDist" )
set ( CPACK_COMPONENTS_ALL Runtime Library Header Data Documentation Example Other )
include ( CPack )

@ -0,0 +1,293 @@
# LuaDist CMake utility library for Lua.
#
# Copyright (C) 2007-2012 LuaDist.
# by David Manura, Peter Drahos
# Redistribution and use of this file is allowed according to the terms of the MIT license.
# For details see the COPYRIGHT file distributed with LuaDist.
# Please note that the package source code is licensed under its own license.
set ( INSTALL_LMOD ${INSTALL_LIB}/lua
CACHE PATH "Directory to install Lua modules." )
set ( INSTALL_CMOD ${INSTALL_LIB}/lua
CACHE PATH "Directory to install Lua binary modules." )
option ( SKIP_LUA_WRAPPER
"Do not build and install Lua executable wrappers." OFF)
# List of (Lua module name, file path) pairs.
# Used internally by add_lua_test. Built by add_lua_module.
set ( _lua_modules )
# utility function: appends path `path` to path `basepath`, properly
# handling cases when `path` may be relative or absolute.
macro ( _append_path basepath path result )
if ( IS_ABSOLUTE "${path}" )
set ( ${result} "${path}" )
else ()
set ( ${result} "${basepath}/${path}" )
endif ()
endmacro ()
# install_lua_executable ( target source )
# Automatically generate a binary if srlua package is available
# The application or its source will be placed into /bin
# If the application source did not have .lua suffix then it will be added
# USE: lua_executable ( sputnik src/sputnik.lua )
macro ( install_lua_executable _name _source )
get_filename_component ( _source_name ${_source} NAME_WE )
# Find srlua and glue
find_program( SRLUA_EXECUTABLE NAMES srlua )
find_program( GLUE_EXECUTABLE NAMES glue )
# Executable output
set ( _exe ${CMAKE_CURRENT_BINARY_DIR}/${_name}${CMAKE_EXECUTABLE_SUFFIX} )
if ( NOT SKIP_LUA_WRAPPER AND SRLUA_EXECUTABLE AND GLUE_EXECUTABLE )
# Generate binary gluing the lua code to srlua, this is a robuust approach for most systems
add_custom_command(
OUTPUT ${_exe}
COMMAND ${GLUE_EXECUTABLE}
ARGS ${SRLUA_EXECUTABLE} ${_source} ${_exe}
DEPENDS ${_source}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
# Make sure we have a target associated with the binary
add_custom_target(${_name} ALL
DEPENDS ${_exe}
)
# Install with run permissions
install ( PROGRAMS ${_exe} DESTINATION ${INSTALL_BIN} COMPONENT Runtime)
# Also install source as optional resurce
install ( FILES ${_source} DESTINATION ${INSTALL_FOO} COMPONENT Other )
else()
# Install into bin as is but without the lua suffix, we assume the executable uses UNIX shebang/hash-bang magic
install ( PROGRAMS ${_source} DESTINATION ${INSTALL_BIN}
RENAME ${_source_name}
COMPONENT Runtime
)
endif()
endmacro ()
macro ( _lua_module_helper is_install _name )
parse_arguments ( _MODULE "LINK;ALL_IN_ONE" "" ${ARGN} )
# _target is CMake-compatible target name for module (e.g. socket_core).
# _module is relative path of target (e.g. socket/core),
# without extension (e.g. .lua/.so/.dll).
# _MODULE_SRC is list of module source files (e.g. .lua and .c files).
# _MODULE_NAMES is list of module names (e.g. socket.core).
if ( _MODULE_ALL_IN_ONE )
string ( REGEX REPLACE "\\..*" "" _target "${_name}" )
string ( REGEX REPLACE "\\..*" "" _module "${_name}" )
set ( _target "${_target}_all_in_one")
set ( _MODULE_SRC ${_MODULE_ALL_IN_ONE} )
set ( _MODULE_NAMES ${_name} ${_MODULE_DEFAULT_ARGS} )
else ()
string ( REPLACE "." "_" _target "${_name}" )
string ( REPLACE "." "/" _module "${_name}" )
set ( _MODULE_SRC ${_MODULE_DEFAULT_ARGS} )
set ( _MODULE_NAMES ${_name} )
endif ()
if ( NOT _MODULE_SRC )
message ( FATAL_ERROR "no module sources specified" )
endif ()
list ( GET _MODULE_SRC 0 _first_source )
get_filename_component ( _ext ${_first_source} EXT )
if ( _ext STREQUAL ".lua" ) # Lua source module
list ( LENGTH _MODULE_SRC _len )
if ( _len GREATER 1 )
message ( FATAL_ERROR "more than one source file specified" )
endif ()
set ( _module "${_module}.lua" )
get_filename_component ( _module_dir ${_module} PATH )
get_filename_component ( _module_filename ${_module} NAME )
_append_path ( "${CMAKE_CURRENT_SOURCE_DIR}" "${_first_source}" _module_path )
list ( APPEND _lua_modules "${_name}" "${_module_path}" )
if ( ${is_install} )
install ( FILES ${_first_source} DESTINATION ${INSTALL_LMOD}/${_module_dir}
RENAME ${_module_filename}
COMPONENT Runtime
)
endif ()
else () # Lua C binary module
enable_language ( C )
find_package ( Lua REQUIRED )
include_directories ( ${LUA_INCLUDE_DIR} )
set ( _module "${_module}${CMAKE_SHARED_MODULE_SUFFIX}" )
get_filename_component ( _module_dir ${_module} PATH )
get_filename_component ( _module_filenamebase ${_module} NAME_WE )
foreach ( _thisname ${_MODULE_NAMES} )
list ( APPEND _lua_modules "${_thisname}"
"${CMAKE_CURRENT_BINARY_DIR}/\${CMAKE_CFG_INTDIR}/${_module}" )
endforeach ()
add_library( ${_target} MODULE ${_MODULE_SRC})
target_link_libraries ( ${_target} ${LUA_LIBRARY} ${_MODULE_LINK} )
set_target_properties ( ${_target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${_module_dir}" PREFIX "" OUTPUT_NAME "${_module_filenamebase}" )
if ( ${is_install} )
install ( TARGETS ${_target} DESTINATION ${INSTALL_CMOD}/${_module_dir} COMPONENT Runtime)
endif ()
endif ()
endmacro ()
# add_lua_module
# Builds a Lua source module into a destination locatable by Lua
# require syntax.
# Binary modules are also supported where this function takes sources and
# libraries to compile separated by LINK keyword.
# USE: add_lua_module ( socket.http src/http.lua )
# USE2: add_lua_module ( mime.core src/mime.c )
# USE3: add_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} )
# USE4: add_lua_module ( ssl.context ssl.core ALL_IN_ONE src/context.c src/ssl.c )
# This form builds an "all-in-one" module (e.g. ssl.so or ssl.dll containing
# both modules ssl.context and ssl.core). The CMake target name will be
# ssl_all_in_one.
# Also sets variable _module_path (relative path where module typically
# would be installed).
macro ( add_lua_module )
_lua_module_helper ( 0 ${ARGN} )
endmacro ()
# install_lua_module
# This is the same as `add_lua_module` but also installs the module.
# USE: install_lua_module ( socket.http src/http.lua )
# USE2: install_lua_module ( mime.core src/mime.c )
# USE3: install_lua_module ( socket.core ${SRC_SOCKET} LINK ${LIB_SOCKET} )
macro ( install_lua_module )
_lua_module_helper ( 1 ${ARGN} )
endmacro ()
# Builds string representing Lua table mapping Lua modules names to file
# paths. Used internally.
macro ( _make_module_table _outvar )
set ( ${_outvar} )
list ( LENGTH _lua_modules _n )
if ( ${_n} GREATER 0 ) # avoids cmake complaint
foreach ( _i RANGE 1 ${_n} 2 )
list ( GET _lua_modules ${_i} _path )
math ( EXPR _ii ${_i}-1 )
list ( GET _lua_modules ${_ii} _name )
set ( ${_outvar} "${_table} ['${_name}'] = '${_path}'\;\n")
endforeach ()
endif ()
set ( ${_outvar}
"local modules = {
${_table}}" )
endmacro ()
# add_lua_test ( _testfile [ WORKING_DIRECTORY _working_dir ] )
# Runs Lua script `_testfile` under CTest tester.
# Optional named argument `WORKING_DIRECTORY` is current working directory to
# run test under (defaults to ${CMAKE_CURRENT_BINARY_DIR}).
# Both paths, if relative, are relative to ${CMAKE_CURRENT_SOURCE_DIR}.
# Any modules previously defined with install_lua_module are automatically
# preloaded (via package.preload) prior to running the test script.
# Under LuaDist, set test=true in config.lua to enable testing.
# USE: add_lua_test ( test/test1.lua [args...] [WORKING_DIRECTORY dir])
macro ( add_lua_test _testfile )
if ( NOT SKIP_TESTING )
parse_arguments ( _ARG "WORKING_DIRECTORY" "" ${ARGN} )
include ( CTest )
find_program ( LUA NAMES lua lua.bat )
get_filename_component ( TESTFILEABS ${_testfile} ABSOLUTE )
get_filename_component ( TESTFILENAME ${_testfile} NAME )
get_filename_component ( TESTFILEBASE ${_testfile} NAME_WE )
# Write wrapper script.
# Note: One simple way to allow the script to find modules is
# to just put them in package.preload.
set ( TESTWRAPPER ${CMAKE_CURRENT_BINARY_DIR}/${TESTFILENAME} )
_make_module_table ( _table )
set ( TESTWRAPPERSOURCE
"local CMAKE_CFG_INTDIR = ... or '.'
${_table}
local function preload_modules(modules)
for name, path in pairs(modules) do
if path:match'%.lua' then
package.preload[name] = assert(loadfile(path))
else
local name = name:gsub('.*%-', '') -- remove any hyphen prefix
local symbol = 'luaopen_' .. name:gsub('%.', '_')
--improve: generalize to support all-in-one loader?
local path = path:gsub('%$%{CMAKE_CFG_INTDIR%}', CMAKE_CFG_INTDIR)
package.preload[name] = assert(package.loadlib(path, symbol))
end
end
end
preload_modules(modules)
arg[0] = '${TESTFILEABS}'
table.remove(arg, 1)
return assert(loadfile '${TESTFILEABS}')(unpack(arg))
" )
if ( _ARG_WORKING_DIRECTORY )
get_filename_component (
TESTCURRENTDIRABS ${_ARG_WORKING_DIRECTORY} ABSOLUTE )
# note: CMake 2.6 (unlike 2.8) lacks WORKING_DIRECTORY parameter.
set ( _pre ${CMAKE_COMMAND} -E chdir "${TESTCURRENTDIRABS}" )
endif ()
file ( WRITE ${TESTWRAPPER} ${TESTWRAPPERSOURCE})
add_test ( NAME ${TESTFILEBASE} COMMAND ${_pre} ${LUA}
${TESTWRAPPER} "${CMAKE_CFG_INTDIR}"
${_ARG_DEFAULT_ARGS} )
endif ()
# see also http://gdcm.svn.sourceforge.net/viewvc/gdcm/Sandbox/CMakeModules/UsePythonTest.cmake
# Note: ${CMAKE_CFG_INTDIR} is a command-line argument to allow proper
# expansion by the native build tool.
endmacro ()
# Converts Lua source file `_source` to binary string embedded in C source
# file `_target`. Optionally compiles Lua source to byte code (not available
# under LuaJIT2, which doesn't have a bytecode loader). Additionally, Lua
# versions of bin2c [1] and luac [2] may be passed respectively as additional
# arguments.
#
# [1] http://lua-users.org/wiki/BinToCee
# [2] http://lua-users.org/wiki/LuaCompilerInLua
function ( add_lua_bin2c _target _source )
find_program ( LUA NAMES lua lua.bat )
execute_process ( COMMAND ${LUA} -e "string.dump(function()end)"
RESULT_VARIABLE _LUA_DUMP_RESULT ERROR_QUIET )
if ( NOT ${_LUA_DUMP_RESULT} )
SET ( HAVE_LUA_DUMP true )
endif ()
message ( "-- string.dump=${HAVE_LUA_DUMP}" )
if ( ARGV2 )
get_filename_component ( BIN2C ${ARGV2} ABSOLUTE )
set ( BIN2C ${LUA} ${BIN2C} )
else ()
find_program ( BIN2C NAMES bin2c bin2c.bat )
endif ()
if ( HAVE_LUA_DUMP )
if ( ARGV3 )
get_filename_component ( LUAC ${ARGV3} ABSOLUTE )
set ( LUAC ${LUA} ${LUAC} )
else ()
find_program ( LUAC NAMES luac luac.bat )
endif ()
endif ( HAVE_LUA_DUMP )
message ( "-- bin2c=${BIN2C}" )
message ( "-- luac=${LUAC}" )
get_filename_component ( SOURCEABS ${_source} ABSOLUTE )
if ( HAVE_LUA_DUMP )
get_filename_component ( SOURCEBASE ${_source} NAME_WE )
add_custom_command (
OUTPUT ${_target} DEPENDS ${_source}
COMMAND ${LUAC} -o ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo
${SOURCEABS}
COMMAND ${BIN2C} ${CMAKE_CURRENT_BINARY_DIR}/${SOURCEBASE}.lo
">${_target}" )
else ()
add_custom_command (
OUTPUT ${_target} DEPENDS ${SOURCEABS}
COMMAND ${BIN2C} ${_source} ">${_target}" )
endif ()
endfunction()

@ -0,0 +1,14 @@
--- This file is part of LuaDist project
name = "lemock"
version = "0.6"
desc = "Mock creation module intended for use together with a unit test framework such as lunit or lunity."
author = "Tommy Petterson"
license = "MIT"
url = "http://lemock.luaforge.net"
maintainer = "Peter Drahos"
depends = {
"lua ~> 5.1"
}

@ -0,0 +1,122 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
The Call Action
###############
The index action returns a Callable object, which will catch calls. The
catching of the call will record a call action, and modify the index action
to record the fact that it should return a Callable object during replay.
<<Unit test for module mock call>>=
function call_test ()
m.foo(1,2,3)
mc:replay()
local tmp = m.foo(1,2,3)
assert_nil( tmp )
mc:verify()
end
function call_anyarg_test ()
m.foo(1,mc.ANYARG,3)
mc:replay()
local tmp = m.foo(1,2,3)
mc:verify()
end
function call_anyargs_test ()
m.foo(mc.ANYARGS)
mc:replay()
local tmp = m.foo(1,2,3)
mc:verify()
end
function call_anyargs_bad_fails_test ()
local ok, err = pcall( function() m.foo(mc.ANYARGS, 1) end )
assert_false( ok, "ANYARGS misused" )
assert_match( "ANYARGS not at end", err )
end
function call_return_test ()
m.foo(1,2,3) ;mc:returns( 0, 9 )
mc:replay()
local tmp1, tmp2 = m.foo(1,2,3)
assert_equal( 0, tmp1 )
assert_equal( 9, tmp2 )
mc:verify()
end
function call_wrong_name_fails_test ()
m.foo(1,2,3) ;mc:returns( 0 )
mc:replay()
local ok, err = pcall( function() m:bar(1,2,3) end )
assert_false( ok, "replay wrong index" )
assert_match( "Unexpected action index bar", err )
end
function call_wrong_arg_fails_test ()
m.foo(1,2,3) ;mc:returns( 0 )
mc:replay()
local ok, err = pcall( function() m.foo(1) end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action call foo", err )
end
function call_throws_error_test ()
m.boo('Ba') ;mc:error( "Call throws error" )
mc:replay()
local ok, err = pcall( function() m.boo('Ba') end )
assert_false( ok, "did not throw error" )
assert_match( "Call throws error", err )
end
@
Record Phase
============
<<Class Callable.record call>>=
function Callable.record:__call (...)
local index_action = self.action
local m = index_action.mock
local mc = mock_controller_map[m]
assert( mc.is_recording, "client uses cached callable from recording" )
mc:make_callable( index_action )
mc:add_action( Action.call:new( m, index_action.key, ... ))
end
<<Class Action.call method new>>=
function Action.call:new (m, key, ...)
local a = Action.generic_call.new( self, m, ... )
a.key = key
return a
end
@
Replay Phase
============
<<Class Callable.replay call>>=
function Callable.replay:__call (...)
local index_action = self.action
local m = index_action.mock
local mc = mock_controller_map[m]
local call_action = mc:lookup( Action.call:new( m, index_action.key, ... ))
mc:replay_action( call_action )
if call_action.throws_error then
error( call_action.errorvalue, 2 )
end
return call_action:get_returnvalue()
end
<<Unit test for class Action.call method match>>=
function call_match_test ()
local m = {}
local a = Action.call:new( m, 'foo', 4, 'bb' )
assert_true( a:match( Action.call:new( m, 'foo', 4, 'bb' )))
assert_false( a:match( Action.call:new( {}, 'foo', 4, 'bb' )))
assert_false( a:match( Action.call:new( m, 'bar', 4, 'bb' )))
assert_false( a:match( Action.call:new( m, 'foo', 1, 'bb' )))
assert_false( a:match( Action.call:new( m, 'foo', 4, 'b' )))
assert_false( a:match( Action.call:new( m, 'foo', 4, 'bb', 'cc' )))
end
<<Class Action.call method match>>=
function Action.call:match (q)
if not Action.generic_call.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end

@ -0,0 +1,80 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
Class Action.generic_call
#########################
The generic_call action class implements the common tasks of the two
different call action types.
There are two types of calls in Lua: a call of a function, and a call of
the [[__call]] meta method for something else (e.g., a table). It is
normally not necessary to differentiate the two types in the mock, because
only the behavior is simulated so it does not matter what the simulated
object would return. The mock always returns a Callable, which records a
call action. The exception is when the mock object itself is called, which
records a selfcall action.
Note: a special case is if the mock contains another mock that can be
called. This is awkward, but possible, to simulate by first referencing the
inner mock object from the outer (an index action), and then recording the
inner mock object as the returned value of that index action, and finally
calling the second mock object.
new
---
This method is extended by the concrete Action classes.
<<Class Action.generic_call method new>>=
function Action.generic_call:new (m, ...)
local a = Action.generic.new( self, m )
a.argv = Argv:new(...)
return a
end
@
match
-----
This method is extended by the concrete Action classes.
<<Class Action.generic_call method match>>=
function Action.generic_call:match (q)
if not Action.generic.match( self, q ) then return false end
if not self.argv:equal( q.argv ) then return false end
return true
end
@
set_returnvalue
---------------
<<Class Action.generic_call method set_returnvalue>>=
function Action.generic_call:set_returnvalue (...)
self.returnvalue = Argv:new(...)
self.has_returnvalue = true
end
@
get_returnvalue
---------------
<<Unit test for class Action.generic_call method get_returnvalue>>=
function generic_call_set_and_get_returnvalue_test ()
local a = Action.generic_call:new()
assert_equal( 0, select('#', a:get_returnvalue() ))
a:set_returnvalue( nil, false )
local r1, r2 = a:get_returnvalue()
assert_equal( nil, r1 )
assert_equal( false, r2 )
end
<<Class Action.generic_call method get_returnvalue>>=
function Action.generic_call:get_returnvalue ()
if self.has_returnvalue then
return self.returnvalue:unpack()
end
end

@ -0,0 +1,136 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
The Index Action
################
The index action can either be "called" (via the returned Callable) or be
given a return value, or neither in which case it will return nil during
replay.
<<Unit test for module mock index>>=
function index_test ()
local tmp = m.foo
mc:replay()
local tmp = m.foo
assert_nil( tmp )
mc:verify()
end
function index_returns_test ()
local tmp = m.foo ;mc:returns( 1 )
mc:replay()
local tmp = m.foo
assert_equal( 1, tmp )
mc:verify()
end
function index_wrong_key_fails_test ()
local tmp = m.foo ;mc:returns( 1 )
mc:replay()
local ok, err = pcall( function() local tmp = m.bar end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action index bar", err )
end
function index_throws_error_test ()
local tmp = m.foo ;mc:error( "Index throws error" )
mc:replay()
local ok, err = pcall( function() tmp = m.foo end )
assert_false( ok, "did not throw error" )
assert_match( "Index throws error", err )
end
@
Record Phase
============
<<Class Mock.record index>>=
function Mock.record:__index (key)
local mc = mock_controller_map[self]
local action = Action.index:new( self, key )
mc:add_action( action )
return Callable.record:new( action )
end
@
Action new
----------
<<Unit test for class Action.index method new>>=
function create_index_action_test ()
local m = {}
local a = Action.index:new( m, 'foo' )
assert_equal( m, a.mock )
assert_equal( 'foo', a.key )
end
<<Class Action.index method new>>=
function Action.index:new (m, key)
local a = Action.generic.new( self, m )
a.key = key
return a
end
@
Action set_returnvalue
----------------------
<<Unit test for class Action.index method set_returnvalue>>=
function index_returnvalue_test ()
local a = Action.index:new( {}, -3 )
a:set_returnvalue( 'foo' )
assert_equal( 'foo', a:get_returnvalue() )
end
<<Class Action.index method set_returnvalue>>=
function Action.index:set_returnvalue (v)
self.returnvalue = v
self.has_returnvalue = true
end
@
Replay Phase
============
<<Class Mock.replay index>>=
function Mock.replay:__index (key)
local mc = mock_controller_map[self]
local index_action = mc:lookup( Action.index:new( self, key ))
mc:replay_action( index_action )
if index_action.throws_error then
error( index_action.errorvalue, 2 )
end
if index_action.is_callable then
return Callable.replay:new( index_action )
else
return index_action:get_returnvalue()
end
end
@
Action match
------------
<<Unit test for class Action.index method match>>=
function index_match_test ()
local m = {}
local a = Action.index:new( m, -1 )
assert_true( a:match( Action.index:new( m, -1 )))
assert_false( a:match( Action.index:new( {}, -1 )))
assert_false( a:match( Action.index:new( m, 'a' )))
end
<<Class Action.index method match>>=
function Action.index:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
return true
end
@
Action get_returnvalue
----------------------
<<Class Action.index method get_returnvalue>>=
function Action.index:get_returnvalue ()
return self.returnvalue
end

@ -0,0 +1,109 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
The Newindex Action
###################
<<Unit test for module mock newindex>>=
function newindex_test ()
m.foo = 1
mc:replay()
m.foo = 1
mc:verify()
end
function newindex_anyarg_test ()
m.foo = mc.ANYARG
mc:replay()
m.foo = 1
mc:verify()
end
function newindex_wrong_key_fails_test ()
m.foo = 1
mc:replay()
local ok, err = pcall( function() m.bar = 1 end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action newindex", err )
end
function newindex_wrong_value_fails_test ()
m.foo = 1
mc:replay()
local ok, err = pcall( function() m.foo = 0 end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action newindex foo", err )
end
function newindex_throws_error_test ()
m.foo = 1 ;mc:error( "newindex throws error" )
mc:replay()
local ok, err = pcall( function() m.foo = 1 end )
assert_false( ok, "did not throw error" )
assert_match( "newindex throws error", err )
end
@
Record Phase
============
<<Class Mock.record newindex>>=
function Mock.record:__newindex (key, val)
local mc = mock_controller_map[self]
mc:add_action( Action.newindex:new( self, key, val ))
end
<<Class Action.newindex method new>>=
function Action.newindex:new (m, key, val)
local a = Action.generic.new( self, m )
a.key = key
a.val = val
return a
end
@
Replay Phase
============
<<Class Mock.replay newindex>>=
function Mock.replay:__newindex (key, val)
local mc = mock_controller_map[self]
local newindex_action = mc:lookup( Action.newindex:new( self, key, val ))
mc:replay_action( newindex_action )
if newindex_action.throws_error then
error( newindex_action.errorvalue, 2 )
end
end
<<Unit test for class Action.newindex method match>>=
function newindex_match_test ()
local m = {}
local a = Action.newindex:new( m, 'foo', 17 )
assert_true( a:match( Action.newindex:new( m, 'foo', 17 )))
assert_false( a:match( Action.newindex:new( {}, 'foo', 17 )))
assert_false( a:match( Action.newindex:new( m, 'fo', 17 )))
assert_false( a:match( Action.newindex:new( m, 'foo', 7 )))
end
function newindex_anyarg_test ()
local m = {}
local a = Action.newindex:new( m, 'foo', Argv.ANYARG )
local b = Action.newindex:new( m, 'foo', 33 )
local c = Action.newindex:new( m, 'foo', nil )
assert_true( a:match(b) )
assert_true( b:match(a) )
assert_true( a:match(c) )
assert_true( c:match(a) )
end
function newindex_NaN_test ()
local m = {}
local nan = 0/0
local a = Action.newindex:new( m, m, nan )
assert_true( a:match( Action.newindex:new( m, m, nan )))
end
<<Class Action.newindex method match>>=
function Action.newindex:match (q)
if not Action.generic.match( self, q ) then return false end
if self.key ~= q.key then return false end
if not value_equal( self.val, q.val )
and self.val ~= Argv.ANYARG
and q.val ~= Argv.ANYARG then return false end
return true
end

@ -0,0 +1,95 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
The Call Action
###############
The selfcall action is made to the Mock object itself, so no Callable or
index action is needed.
<<Unit test for module mock selfcall>>=
function selfcall_test ()
m(11)
mc:replay()
local tmp = m(11)
assert_nil( tmp )
mc:verify()
end
function selfcall_returns_test ()
m(99) ;mc:returns(1,nil,'foo')
mc:replay()
local a,b,c = m(99)
assert_equal( 1, a )
assert_equal( nil, b )
assert_equal( 'foo', c )
mc:verify()
end
function selfcall_wrong_argument_fails_test ()
m(99) ;mc:returns('a','b','c')
mc:replay()
local ok, err = pcall( function() m(90) end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action selfcall", err )
end
function selfcall_wrong_number_of_arguments_fails_test ()
m(1,2,3)
mc:replay()
local ok, err = pcall( function() m(1,2,3,4) end )
assert_false( ok, "replay succeeded" )
assert_match( "Unexpected action selfcall", err )
end
function selfcall_throws_error_test ()
m('Ba') ;mc:error( "Selfcall throws error" )
mc:replay()
local ok, err = pcall( function() m('Ba') end )
assert_false( ok, "did not throw error" )
assert_match( "Selfcall throws error", err )
end
@
Record Phase
============
<<Class Mock.record selfcall>>=
function Mock.record:__call (...)
local mc = mock_controller_map[self]
mc:add_action( Action.selfcall:new( self, ... ))
end
<<Class Action.selfcall method new>>=
function Action.selfcall:new (m, ...)
local a = Action.generic_call.new( self, m, ... )
return a
end
@
Replay Phase
============
<<Class Mock.replay selfcall>>=
function Mock.replay:__call (...)
local mc = mock_controller_map[self]
local selfcall_action = mc:lookup( Action.selfcall:new( self, ... ))
mc:replay_action( selfcall_action )
if selfcall_action.throws_error then
error( selfcall_action.errorvalue, 2 )
end
return selfcall_action:get_returnvalue()
end
<<Unit test for class Action.selfcall method match>>=
function selfcall_match_test ()
local m = {}
local a = Action.selfcall:new( m, 5, nil, false )
assert_true( a:match( Action.selfcall:new( m, 5, nil, false )))
assert_false( a:match( Action.selfcall:new( {}, 5, nil, false )))
assert_false( a:match( Action.selfcall:new( m, nil, nil, false )))
assert_false( a:match( Action.selfcall:new( m, 5, false, false )))
assert_false( a:match( Action.selfcall:new( m, 5, nil, nil )))
end
<<Class Action.selfcall method match>>=
function Action.selfcall:match (q)
return Action.generic_call.match( self, q )
end

@ -0,0 +1,158 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
Class Argv
##########
It is convenient to handle argument lists and return lists as an abstract
type.
<<Userguide Anyargs>>=
An //anyarg// is a special argument used when recording, that will match
any argument during replay. It can appear anywhere and any times in an
argument list, or as the argument in an assignment, to replace real
arguments. There is also //anyargs//, which will match any number
(including zero) of any arguments. Anyargs can only appear as the last
argument of an argument list. Anyarg and anyargs are handy when the actual
values of the arguments during replay are unimportant or unknown.
@
new
---
<<Unit test for class Argv method new>>=
function new_test ()
Argv:new( Argv.ANYARGS )
Argv:new( 1, Argv.ANYARGS )
Argv:new( 1, 2, Argv.ANYARGS )
end
function new_anyargs_with_extra_arguments_fails_test ()
local l = {}
l['ANYARGS,1'] = { Argv.ANYARGS, 1 }
l['ANYARGS,ANYARGS' ] = { Argv.ANYARGS, Argv.ANYARGS }
l['1,ANYARGS,1'] = { 1, Argv.ANYARGS, 1 }
l['1,ANYARGS,ANYARGS'] = { 1, Argv.ANYARGS, Argv.ANYARGS }
for msg, args in pairs( l ) do
local ok, err = pcall( function() Argv:new( unpack(args) ) end )
assert_false( ok, "Bad ANYARGS accepted for "..msg )
assert_match( "ANYARGS not at end", err )
end
end
<<Class Argv method new>>=
function Argv:new (...)
local av = object( self )
av.v = {...}
av.len = select('#',...)
for i = 1, av.len - 1 do
if av.v[i] == Argv.ANYARGS then
error( "ANYARGS not at end.", 0 )
end
end
return av
end
@
equal
-----
<<Unit test for class Argv method equal>>=
local l = {}
local function p (...) l[#l+1] = { n=select('#',...), ... } end
p() p(nil) p(nil,nil) p(false) p({}) p(false,nil,{},nil) p(nil,p)
p(true) p(0.1,'','a') p(1/0,nil,0/0) p(0/0) p(0/0, true) p(0/0, false)
function equal_test ()
local a1, a2, f, op
for i = 1, #l do
ai = Argv:new( unpack( l[i], 1, l[i].n ))
for j = 1, #l do
aj = Argv:new( unpack( l[j], 1, l[j].n ))
if i == j then
f, op = assert_true, ') ~= ('
else
f, op = assert_false, ') == ('
end
f( ai:equal(aj), '('..ai:tostring()..op..aj:tostring()..')' )
end
end
end
function equal_anyargs_test ()
local a, b = {}, {}
a[1] = Argv:new( Argv.ANYARGS )
a[2] = Argv:new( 6, Argv.ANYARGS )
a[3] = Argv:new( 6, 5, Argv.ANYARGS )
for i = 1, #l do
b[1] = Argv:new( unpack( l[i], 1, l[i].n ))
b[2] = Argv:new( 6, unpack( l[i], 1, l[i].n ))
b[3] = Argv:new( 6, 5, unpack( l[i], 1, l[i].n ))
for j = 1, 3 do
local astr = '('..a[j]:tostring()..')'
local bstr = '('..b[j]:tostring()..')'
assert_true( a[j]:equal(b[j]), astr..' ~= '..bstr )
assert_true( b[j]:equal(a[j]), bstr..' ~= '..astr )
end
end
end
function equal_anyarg_test ()
local l = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }
local a1 = Argv:new( unpack(l) )
for i = 1, 9 do
l[i] = Argv.ANYARG
local a2 = Argv:new( unpack(l) )
assert_true( a1:equal(a2) )
assert_true( a2:equal(a1) )
l[i] = i
end
end
@
The comparison with ANYARGS is a bit tricky, because it must match the
empty list. The list //excluding// the final ANYARGS argument (its length
minus one) must match the other list.
ANYARG and ANYARGS are created as unique identifiers, with local aliases
for speed.
<<Class Argv method equal>>=
Argv.ANYARGS = newproxy() local ANYARGS = Argv.ANYARGS
Argv.ANYARG = newproxy() local ANYARG = Argv.ANYARG
function Argv:equal (other)
local a1, n1 = self.v, self.len
local a2, n2 = other.v, other.len
if n1-1 <= n2 and a1[n1] == ANYARGS then
n1 = n1-1
n2 = n1
elseif n2-1 <= n1 and a2[n2] == ANYARGS then
n2 = n2-1
n1 = n2
end
if n1 ~= n2 then
return false
end
for i = 1, n1 do
local v1, v2 = a1[i], a2[i]
if not value_equal(v1,v2) and v1 ~= ANYARG and v2 ~= ANYARG then
return false
end
end
return true
end
@
unpack
------
<<Unit test for class Argv method unpack>>=
function unpack_test ()
local a, b, c = Argv:new( false, nil, 7 ):unpack()
assert_equal( false, a )
assert_equal( nil, b )
assert_equal( 7, c )
end
<<Class Argv method unpack>>=
function Argv:unpack ()
return unpack( self.v, 1, self.len )
end

@ -0,0 +1,105 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
Class Inheritance
=================
ABSTRACT CONCRETE
generic
|
`------------------ newindex
|
`------------------ index
|
`-- generic_call
|
`------------ call
|
`------------ selfcall
<<Class Action>>=
Action = {}
-- abstract
<<Class Action.generic>>
<<Class Action.generic_call>>
-- concrete
<<Class Action.call>>
<<Class Action.index>>
<<Class Action.newindex>>
<<Class Action.selfcall>>
@
Abstract Action Classes
=======================
<<Class Action.generic>>=
Action.generic = class()
<<Class Action.generic method add_close>>
<<Class Action.generic method add_depend>>
<<Class Action.generic method add_label>>
<<Class Action.generic method assert_satisfied>>
<<Class Action.generic method blocks>>
<<Class Action.generic method closes>>
<<Class Action.generic method depends>>
<<Class Action.generic method has_label>>
<<Class Action.generic method is_expected>>
<<Class Action.generic method is_satisfied>>
<<Class Action.generic method match>>
<<Class Action.generic method new>>
<<Class Action.generic method set_times>>
<<Class Action.generic_call>>=
Action.generic_call = class( Action.generic )
Action.generic_call.can_return = true
<<Class Action.generic_call method get_returnvalue>>
<<Class Action.generic_call method set_returnvalue>>
<<Class Action.generic_call method match>>
<<Class Action.generic_call method new>>
@
Concrete Action Classes
=======================
<<Class Action.newindex>>=
Action.newindex = class( Action.generic )
<<Class Action.newindex method match>>
<<Class Action.newindex method new>>
<<Class Action.newindex method tostring>>
<<Class Action.index>>=
Action.index = class( Action.generic )
Action.index.can_return = true
<<Class Action.index method get_returnvalue>>
<<Class Action.index method set_returnvalue>>
<<Class Action.index method match>>
<<Class Action.index method new>>
<<Class Action.index method tostring>>
<<Class Action.call>>=
Action.call = class( Action.generic_call )
<<Class Action.call method match>>
<<Class Action.call method new>>
<<Class Action.call method tostring>>
<<Class Action.selfcall>>=
Action.selfcall = class( Action.generic_call )
<<Class Action.selfcall method match>>
<<Class Action.selfcall method new>>
<<Class Action.selfcall method tostring>>

@ -0,0 +1,11 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
<<Class Argv>>=
Argv = class()
<<Class Argv method equal>>
<<Class Argv method new>>
<<Class Argv method tostring>>
<<Class Argv method unpack>>

@ -0,0 +1,13 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
<<Class Callable>>=
Callable = {}
Callable.generic = class()
Callable.record = class( Callable.generic )
Callable.replay = class( Callable.generic )
<<Class Callable.generic method new>>
<<Class Callable.record call>>
<<Class Callable.replay call>>

@ -0,0 +1,30 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
<<Class Controller>>=
Controller = class()
-- Exported methods
<<Class Controller method close>>
<<Class Controller method depend>>
<<Class Controller method error>>
<<Class Controller method label>>
<<Class Controller method mock>>
<<Class Controller method new>>
<<Class Controller method replay>>
<<Class Controller method returns>>
<<Class Controller method times>>
<<Class Controller method verify>>
-- Protected methods
<<Class Controller method actions>>
<<Class Controller method add_action>>
<<Class Controller method assert_no_dependency_cycles>>
<<Class Controller method close_actions>>
<<Class Controller method get_last_action>>
<<Class Controller method lookup>>
<<Class Controller method make_callable>>
<<Class Controller method new>>
<<Class Controller method replay_action>>
<<Class Controller method update_dependencies>>

@ -0,0 +1,13 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
@
<<Class Mock>>=
<<Class Mock meta tables Mock.record and Mock.replay>>
<<Class Mock.record index>>
<<Class Mock.record newindex>>
<<Class Mock.record selfcall>>
<<Class Mock.replay index>>
<<Class Mock.replay newindex>>
<<Class Mock.replay selfcall>>

@ -0,0 +1,23 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Chapter The Controller>>=
= The Controller =
The controller's main purpose is to store the recorded actions, create mock
objects, switch to replay mode, and verify the completion of the replay
phase. But it is also needed to set or change special action attributes
during recording.
It is possible, although doubtfully useful, to use several controllers in
parallel during a single unit test. Each controller maintains its own
action list and state, and mock objects remember which controller they
belong to.
<<Userguide Section Returns & Error>>
<<Userguide Section Label & Depend>>
<<Userguide Section Times>>
<<Userguide Section Close>>
@

@ -0,0 +1,87 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Chapter Introduction>>=
= Introduction =
<<Userguide Introduction>>
=== Example ===
This example tests that the insert_data function of the foo module handles
a missing data base table gracefully.
```
<<Userguide Example simple>>
```
First a controller is created. Then three mock objects are created, one for
the sqlite3 module, and two for objects returned by the (simulated) module.
Then a preloader for the sqlite3 module is installed, which returns the
sqlite3 mock object instead of the actual sqlite3 module.
In the record phase the expected calls and their return values (or thrown
errors) are recorded. The order is not significant, so this simplified test
will not detect if the close method is called before the execute method.
In the replay phase the tested module is loaded and executed. It will use
the mock objects instead of the real data base, and if it makes any
unrecorded calls, an error is thrown.
The verify phase asserts that all recorded actions have been replayed. If
the foo module for example forgets to call the close method, verify throws
an error.
@
<<Userguide Example simple>>=
-- Setup
require 'lemock'
local mc = lemock.controller()
local sqlite3 = mc:mock()
local env = mc:mock()
local con = mc:mock()
package.loaded.luasql = nil
package.preload['luasql.sqlite3'] = function ()
luasql = {}
luasql.sqlite3 = sqlite3
return sqlite3
end
-- Record
sqlite3() ;mc :returns(env)
env:connect('/data/base') ;mc :returns(con)
con:execute(mc.ANYARGS) ;mc :error('LuaSQL: no such table')
con:close()
env:close()
-- Replay
mc:replay()
require 'foo'
local res = foo.insert_data(17)
assert(res==false)
--Verify
mc:verify()
@
<<Unit test for Userguide Example simple>>=
function example_simple_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
q = require 'luasql.sqlite3'
function foo.insert_data()
local env = q()
local con = env:connect( '/data/base' )
local ok, err = pcall( con.execute, con, 'insert foo bar' )
con:close()
env:close()
return ok
end
return foo
end
<<Userguide Example simple>>
end

@ -0,0 +1,21 @@
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Chapter The Mock Object>>=
= The Mock Object =
Mock objects are empty objects with special Lua meta methods that detect
actions performed with the object. What happens depends on the state
(recording or replaying) of the controller which created the mock object.
During recording the mock object adds the action to the controller's list
of recorded actions. During replay the mock object looks for a matching
recorded action that can be replayed, and simulates the action.
Some action attributes can not be inferred by the mock objects, for example
return values. These attributes have to be added afterwards with special
controller methods, and always affect the last recorded action.
<<Userguide Section Actions>>
<<Userguide Section Anyargs>>
@

@ -0,0 +1,67 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Chapter Tricks>>=
= Tricks =
Mock objects are completely empty, and do not contain any methods or
properties of their own. If they did, that would risk shadowing a name of a
simulated object's method or property. There is however nothing preventing
users from defining methods and properties in mock objects. This way mock
objects can be turned into stubs, or a kind of mockstub hybrid.
<<Userguide Section Overloading>>
@
<<Userguide Section Overloading>>=
== Method Overloading ==
Lua does not support method overloading, but it can be (and sometimes is)
implemented manually by testing of function arguments. This presents a
problem to LeMock, because it matches exact arguments, and anyargs in not
sufficient. In this case the mock object can be extended with a dispatcher
function.
=== Example ===
This example shows a mock object with an overloaded add function. The stub
function can not be defined in the usual way, because that would record an
assignment action; it needs to be defined with //rawset//.
```
<<Userguide Example overloading>>
```
@
<<Userguide Example overloading>>=
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
do
local function add (a, b)
if type(a) == 'number' then
return m.add_number(a, b)
else
return m.add_string(a, b)
end
end
rawset( m, 'add', add ) -- not recorded
end -- do
m.add_number(1, 2) ;mc :returns(3)
m.add_string('foo', 'bar') ;mc :returns('foobar')
mc:replay()
assert_equal( 3, m.add(1, 2) )
assert_equal( 'foobar', m.add('foo', 'bar') )
mc:verify()
@
<<Unit test for Userguide Example overloading>>=
function overloading_test ()
<<Userguide Example overloading>>
end

@ -0,0 +1,23 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<userguide.t2t>>=
LeMock User Guide
v 0.6
2009-05-30
%!postproc(html): &ndash;
%!postproc(tex): --
<<Userguide Chapter Introduction>>
<<Userguide Chapter The Mock Object>>
<<Userguide Chapter The Controller>>
<<Userguide Chapter Tricks>>
@
TODO
method reference with syntax
errors and their messages
limitations & future work

@ -0,0 +1,34 @@
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Section Actions>>=
== Actions ==
Mock objects detect four types of actions: assignment, indexing, method
call, and self call. During replay an action will only match if it is the
very same action, that is, the same type of action performed on the same
mock object with all the same arguments. There are however
[special arguments #anyargs] that can be used during recording.
```
<<Userguide Example actions>>
```
@
<<Userguide Example actions>>=
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m.x = 17 -- assignment
r = m.x -- indexing
m.x(1,2,3) -- method call
m:x(1,2,3) -- method call
m(1,2,3) -- self call
@
<<Unit test for Userguide Example actions>>=
function actions_test ()
<<Userguide Example actions>>
end

@ -0,0 +1,57 @@
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Section Anyargs>>=
== Anyargs ==[anyargs]
<<Userguide Anyargs>>
Anyarg and anyargs are constants defined in the controller object.
=== Example ===
This example tests that the fetch_data function of module foo waits a while
and retries when no data is immediately available, and that it updates the
value of lasttime.
```
<<Userguide Example anyargs>>
```
@
<<Userguide Example anyargs>>=
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:poll() ;mc :returns(nil)
con:sleep(mc.ANYARG)
con:poll() ;mc :returns('123.45')
con.lasttime = mc.ANYARG
mc:replay()
require 'foo'
local res = foo.fetch_data(con)
assert( math.abs(res-123.45) < 0.0005 )
mc:verify()
@
<<Unit test for Userguide Example anyargs>>=
function example_anyargs_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.fetch_data (con)
local res = con:poll()
while not res do
con:sleep( 10 )
res = con:poll()
end
con.lasttime = os.time()
return tonumber( res )
end
end
<<Userguide Example anyargs>>
end

@ -0,0 +1,64 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Section Close>>=
== Close ==
Close can be used to simulate state changes in a limited way. When an
action with a close statement is replayed for the first time, it will
permanently block all labels in its close statement, so that actions with
these labels no longer replays. This passes on matching to later actions in
the action list, which may for example have different return values.
The closing simply blocks the labels, and it has nothing to do with max
replay counts or if closed actions have been satisfied or not. Closing an
unsatisfied action however results in an immediate failure.
=== Example ===
This example tests that the dump function of module foo calls the myio
functions in a correct order. The read function can be called any number of
times, until it is closed by the close function.
```
<<Userguide Example close>>
```
@
<<Userguide Example close>>=
require 'lemock'
local mc = lemock.controller()
local myio = mc:mock()
local fs = mc:mock()
myio.open('abc', 'r') ;mc :returns(fs)
mc :label('open')
fs:read(mc.ANYARG) ;mc :returns('data')
mc :atleastonce() :label('read') :depend('open')
fs:close() ;mc :returns(true)
mc :depend('open') :close('read')
mc:replay()
require 'foo'
foo.dump(myio, 'abc', 128)
mc:verify()
@
<<Unit test for Userguide Example close>>=
function close_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.dump (xio, name, len)
local f = xio.open( name, 'r' )
f:read( len )
f:close()
end
end
<<Userguide Example close>>
end

@ -0,0 +1,69 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Section Label & Depend>>=
== Label & Depend ==
Dependencies block actions from replaying until other actions have replayed
first. They can be used to verify that actions are being replayed in a
valid order.
To add dependencies, actions must first be labeled with one or more
//labels//. The same label can be given to several actions. As long as some
action with the label remains unsatisfied, that label is blocked, and all
actions depending on that label will not replay.
=== Example ===
This (contrived) example tests that function draw_square in module foo
calls all the necessary drawing methods of a square object in a correct
order. Note that there can be more than one correct order.
```
<<Userguide Example depend>>
```
This example demonstrates two different ways of using dependencies. All the
corners have unique labels, because each edge depend on a set of specific
corners. But all the edges have the same label, because the fill operation
only depends on //all// edges have been satisfied.
@
<<Userguide Example depend>>=
require 'lemock'
local mc = lemock.controller()
local square = mc:mock()
square:topleft() ;mc :label('tl')
square:topright() ;mc :label('tr')
square:botleft() ;mc :label('bl')
square:botright() ;mc :label('br')
square:leftedge() ;mc :label('edge') :depend('tl', 'bl')
square:rightedge() ;mc :label('edge') :depend('tr', 'br')
square:topedge() ;mc :label('edge') :depend('tl', 'tr')
square:botedge() ;mc :label('edge') :depend('bl', 'br')
square:fill() ;mc :depend('edge')
mc:replay()
require 'foo'
foo.draw_square( square )
mc:verify()
@
<<Unit test for Userguide Example depend>>=
function example_depend_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.draw_square (sq)
sq:botright() sq:topright() sq:rightedge()
sq:botleft() sq:topleft() sq:leftedge()
sq:topedge() sq:botedge()
sq:fill()
end
end
<<Userguide Example depend>>
end

@ -0,0 +1,38 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Section Returns & Error>>=
== Returns & Error ==
The by far most useful special action attribute is the return value.
Indexing actions can return a single value, while call actions and self
call actions can return a list of values. The return value is set with the
//returns// method, and it is an error to set the return value twice for
the same action.
For purposes of unit testing it is often useful to simulate errors. All
actions can raise an error, and return an error value (usually a string).
The return value is set with the //error// method. An action can not have
both a return value and raise an error.
=== Example ===
```
<<Userguide Example returns & error>>
```
@
<<Userguide Example returns & error>>=
require 'lemock'
local mc = lemock.controller()
local m = mc:mock()
m:foo(17) ;mc :returns(nil, "index out of range")
m:bar(-1) ;mc :error("invalid index")
@
<<Unit test for Userguide Example returns & error>>=
function returns_error_test ()
<<Userguide Example returns & error>>
end

@ -0,0 +1,65 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Userguide Section Times>>=
== Times ==
The default for a recorded action is to be replayed exactly once.
``times(2)`` changes that to exactly two times, and ``times(1,2)`` changes
it to at least one time and at most two times.
When the action has been replayed the least count times it is
//satisfied//, which means verify will not complain about it, and it no
longer blocks actions that depend on this action from being replayed. If
the least count is zero the action is automatically satisfied and need not
be replayed at all, i.e., it is optional.
When the action has been replayed the most count times it will not replay
any more. The most replay count can be set to infinity (``math.huge`` or
``1/0``), in which case the action will never stop replaying.
``anytimes()`` can be used as an alias for ``times(0,1/0)``, and
``atleastonce()`` can be used as an alias for ``times(1,1/0)``.
=== Example ===
This example tests that method update is called at least once.
```
<<Userguide Example times>>
```
@
<<Userguide Example times>>=
require 'lemock'
local mc = lemock.controller()
local con = mc:mock()
con:log(mc.ANYARGS) ;mc :anytimes()
con:update('x',3) ;mc :returns(true) :atleastonce()
mc:replay()
require 'foo'
local watcher = foo.mk_watcher( con )
watcher:set( 'x', 3 )
mc:verify()
@
<<Unit test for Userguide Example times>>=
function example_times_test ()
package.loaded.foo = nil
package.preload.foo = function ()
foo = {}
function foo.mk_watcher ( con )
local o = {}
function o:set ( key, val )
con:update( key, val )
end
return o
end
end
<<Userguide Example times>>
end

@ -0,0 +1,19 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<unit/userguide.lua>>=
<<Lua file blurb>>
require 'lunit'
module( 'unit.userguide', lunit.testcase, package.seeall )
<<Unit test for Userguide Example actions>>
<<Unit test for Userguide Example anyargs>>
<<Unit test for Userguide Example close>>
<<Unit test for Userguide Example depend>>
<<Unit test for Userguide Example overloading>>
<<Unit test for Userguide Example returns & error>>
<<Unit test for Userguide Example simple>>
<<Unit test for Userguide Example times>>

@ -0,0 +1,297 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
The sources for the web pages are written as independent t2t documents. To
form a cohesive web "site", the pages are included in special wrapper t2t
documents, which use a different set of configurations, and add a header, a
navigation menu bar, and a footer. The wrappers are tangled into the www
directory, the userguide is tangled into the build directory, and the other
sources are unique files in the distribution root directory.
<<mkfile>>=
MKSHELL = rc
all:V: htdocs
wrappers = `{find www -name '*.t2t'}
htmls = ${wrappers:www/%.t2t=htdocs/%.html}
htdocs:V: $htmls
$htmls: www/menubar.html
htdocs/COPYRIGHT.html: ../COPYRIGHT
htdocs/DEVEL.html: ../DEVEL
htdocs/HISTORY.html: ../HISTORY
htdocs/README.html: ../README
htdocs/%.html: www/%.t2t
txt2tags -t html -i www/$stem.t2t -o $target
htdocs/userguide.html: www/userguide.t2t userguide.t2t
txt2tags -t html --toc --toc-level 2 -i www/userguide.t2t -o $target
@
== Wrappers ==
The wrappers assume the source is tangled in a subdirectory, so ../../ is
the path to the root.
The ID of each page is changed to something unique, so the navigation menu
can identify them.
<<www/README.t2t>>=
<<Webpage head>>
%!postproc(html): 'ID="body"' 'ID="page-README"'
<<Webpage main menu>>
= Readme =
%!include: ../../README
<<Webpage foot>>
@
(No footer on copyright page!)
<<www/COPYRIGHT.t2t>>=
<<Webpage head>>
%!postproc(html): 'ID="body"' 'ID="page-COPYRIGHT"'
<<Webpage main menu>>
= License =
%!include: ../../COPYRIGHT
@
<<www/HISTORY.t2t>>=
<<Webpage head>>
%!postproc(html): 'ID="body"' 'ID="page-HISTORY"'
<<Webpage main menu>>
= History =
%!include: ../../HISTORY
<<Webpage foot>>
@
<<www/DEVEL.t2t>>=
<<Webpage head>>
%!postproc(html): 'ID="body"' 'ID="page-DEVEL"'
<<Webpage main menu>>
= Developer Notes =
%!include: ../../DEVEL
<<Webpage foot>>
@
(The user guide is tangled into the build directory.)
<<www/userguide.t2t>>=
<<Webpage head>>
%!postproc(html): 'ID="body"' 'ID="page-userguide"'
<<Webpage main menu>>
%%toc
%!include: ../userguide.t2t
<<Webpage foot>>
@
The index file is just an empty file with the navigation menu. This is
stupid. FIXME!
<<www/index.t2t>>=
<<Webpage head>>
%!postproc(html): 'ID="body"' 'ID="page-index"'
<<Webpage main menu>>
@
== Header and Footer ==
The header contains the page's title (all pages get the same title), and
sets up the configuration. The footer just contains the date for when the
page was generated.
<<Webpage head>>=
LeMock
%!includeconf: config.rc
<<Doc reference preprocs>>
@
<<Webpage foot>>=
--------------------
%%Date(%Y-%m-%d)
@
Use css for layout and navigation menu.
<<www/config.rc>>=
%!options: --no-rc
%!style(html): style.css
%!options(html): --css-sugar
@
<<htdocs/style.css>>=
<<Webpage css>>
<<Webpage css menu>>
@
Some t2t pages are written with the intent that they should work as plain
text documents, and so they refer to other files by name. In the web pages
real links are preferred, but the words README and DEVEL are too common, so
a special trick with a trailing underscore is used.
<<Doc reference preprocs>>=
%!preproc: COPYRIGHT_ "[COPYRIGHT COPYRIGHT.html]"
%!preproc: DEVEL_ "[DEVEL DEVEL.html]"
%!preproc: "build/htdocs/userguide.html" "[the user guide userguide.html]"
@
== Style Sheet ===
<<Webpage css>>=
<<Webpage css page>>
<<Webpage css title>>
<<Webpage css code>>
<<Webpage css toc>>
<<Webpage css history>>
<<Webpage css page>>=
body {
color: #181818;
background-color: #E0E4F0;
font: normal 10pt sans-serif;
max-width: 30em;
margin: 25pt;
}
.body h1 {
margin: 2em 0em 0em 0em;
font-size: 14pt;
}
.body h2 {
margin: 1.5em 0em 0em 0em;
font-size: 12pt;
}
.body h3 {
margin: 1em 0em 0em 0em;
font-size: 10pt;
}
.body p, .body ul, .body ol {
margin-top: 0.5em;
}
.body li {
margin-top: 0.5em;
}
a {
text-decoration: none;
}
hr {
margin-top: 3em;
}
<<Webpage css title>>=
.header h1 {
text-align: center;
padding: 0.3em;
border: 1pt solid black;
}
<<Webpage css code>>=
code, pre {
font-family: fixed;
font-style: normal;
font-size: 9pt;
line-height: 9pt;
background-color: #E8ECF8;
}
pre {
padding: 2pt;
}
<<Webpage css toc>>=
div.toc {
margin-top: 3em;
line-height: 6pt;
}
.toc ul {
padding-left: 1.6em;
margin: 0em;
line-height: 10pt;
}
.toc li {
margin: 0em;
padding: 0em;
list-style-type: none;
}
.toc a {
color: #091;
}
<<Webpage css history>>=
#page-HISTORY DL DT {
font-weight: bold;
margin-top: 2em;
}
#page-HISTORY DL DD UL {
margin-top: 0pt;
padding-left: 0pt;
}
@
== Navigation Menu Bar ==
The navigation menu bar is implemented with css.
<<Webpage main menu>>=
%!include(html): ''menubar.html''
@
<<www/menubar.html>>=
<ul id="main_menu">
<li id="main_menu-README"><a href="README.html" >Readme</a></li>
<li id="main_menu-COPYRIGHT"><a href="COPYRIGHT.html">License</a></li>
<li id="main_menu-userguide"><a href="userguide.html">Userguide</a></li>
<li id="main_menu-HISTORY"><a href="HISTORY.html" >History</a></li>
<li id="main_menu-DEVEL"><a href="DEVEL.html" >Devel</a></li>
</ul>
@
<<Webpage css menu>>=
#main_menu {
margin: 0;
padding: 0;
}
#main_menu li {
margin: 0;
padding: 0;
display: inline;
}
#main_menu a {
padding: 3px 3px 2px 4px;
text-decoration:none;
font:bold 8pt/8pt Arial, Helvetica, sans-serif;
border: 1px solid #000;
}
#main_menu a:link,
#main_menu a:visited {
color: #fff;
background: #777;
}
#main_menu a:hover {
color: #000;
background: #777;
}
#page-README #main_menu-README a,
#page-COPYRIGHT #main_menu-COPYRIGHT a,
#page-userguide #main_menu-userguide a,
#page-HISTORY #main_menu-HISTORY a,
#page-DEVEL #main_menu-DEVEL a {
color: #000;
background: #aaa;
}
#page-README #main_menu-README a:hover,
#page-COPYRIGHT #main_menu-COPYRIGHT a:hover,
#page-userguide #main_menu-userguide a:hover,
#page-HISTORY #main_menu-HISTORY a:hover,
#page-DEVEL #main_menu-DEVEL a:hover {
color: #000;
background: #aaa;
}
#nav a:active {
color: #000;
background: #aaa;
}

@ -0,0 +1,32 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<lemock.lua>>=
<<Lua file blurb>>
module( 'lemock', package.seeall )
_VERSION = "LeMock 0.6"
_COPYRIGHT = "Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>"
local class, object, qtostring, sfmt, add_to_set
local elements_of_set, value_equal
<<Helper function class and object>>
<<Helper function qtostring and sfmt>>
<<Helper function add_to_set and elements_of_set>>
<<Helper function value_equal>>
<<Module mock private data mock_controller_map>>
-- All the classes are private
local Action, Argv, Callable, Controller, Mock
<<Class Action>>
<<Class Argv>>
<<Class Callable>>
<<Class Controller>>
<<Class Mock>>
<<Module mock function controller>>
return _M

@ -0,0 +1,65 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
class and object
----------------
These functions are mostly to enhance the reading of the code.
<<Helper function class and object>>=
function object (class)
return setmetatable( {}, class )
end
function class (parent)
local c = object(parent)
c.__index = c
return c
end
@
value_equal
-----------
NaN is always numerically not-equal everything, but for arguments and
return values we want a symbolic comparison.
<<Helper function value_equal>>=
function value_equal (a, b)
if a == b then return true end
if a ~= a and b ~= b then return true end -- NaN == NaN
return false
end
@
Sets
----
Sets are used in Actions to store labels and dependencies, which are
usually very short, and often not used at all, so it is worth some extra
trouble to optimize for size.
The implementation uses arrays, and no array (nil) can represent the empty
set.
<<Helper function add_to_set and elements_of_set>>=
function add_to_set (o, setname, element)
if not o[setname] then
o[setname] = {}
end
local l = o[setname]
for i = 1, #l do
if l[i] == element then return end
end
l[#l+1] = element
end
function elements_of_set (o, setname)
local l = o[setname]
local i = l and #l+1 or 0
return function ()
i = i - 1
if i > 0 then return l[i] end
end
end

@ -0,0 +1,761 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
Introduction
############
<<Userguide Introduction>>=
Mock objects replace difficult external objects during unit testing by
simulating the behaviors of the replaced objects. This is done by first
recording actions and their responses with the mock objects, and then
switching to replay mode. During replay mode the mock objects simulate the
replaced objects by looking up actions and replaying the recorded
responses, and finally verifying that all expected actions where completely
replayed.
Actions are stored in a list in a special controller object. During replay
the list is searched in recording order for the first matching action that
can be replayed.
Restrictions on the actions can be inserted during the recording phase. An
action can have a maximum count of how many times it will be replayed, and
a minimum count of how many times it must be replayed to be satisfied. An
action can depend on any set of other actions, and can not be replayed
before all of its depended actions are satisfied. An action can close any
set of actions when it is replayed, which stops all further replaying of
the closed actions. This is good for simulating state changes.
@
Manage Actions
##############
The Action List
===============
Actions are stored in a list in a special Controller object. To keep it
simple, the list is a simple array table. The controller has an
[[add_action]] method, and a [[lookup]] method. Actions are added at the
end, and looked up from the beginning.
add_action
----------
<<Unit test for class Controller method add_action>>=
function add_action_at_the_end_test ()
mc:add_action( 7 )
mc:add_action( mc )
assert_equal( 7, mc.actionlist[1] )
assert_equal( mc, mc.actionlist[2] )
end
<<Class Controller method add_action>>=
function Controller:add_action (a)
assert( a ~= nil, "lemock internal error" ) -- breaks array property
table.insert( self.actionlist, a )
end
@
lookup and actions
------------------
It should be easy to add new action types, so the [[lookup]] method
delegates matching to the [[match]] method of the Action objects in the
list. If there is no match an error is thrown with a list of all actions
that could have been matched.
<<Unit test for class Controller method lookup>>=
function lookup_returns_first_matching_action_test ()
local Fake_action
<<Simple Fake_action class>>
local a1 = Fake_action:new(1)
local a2 = Fake_action:new(2)
local a3 = Fake_action:new(1)
local ok, err = pcall( function() mc:lookup( a1 ) end )
assert_false( ok, "match in empty list" )
assert_match( "Unexpected action <faked action>", err )
mc:add_action( a1 ) mc:add_action( a2 ) mc:add_action( a3 )
local ok, err = pcall( function() mc:lookup( a1 ) end )
assert_false( ok, "should not match any action" )
assert_match( "Unexpected action <faked action>", err )
assert_equal( a1, mc:lookup( a2 ), "did not find first match" )
end
<<Class Controller method lookup>>=
function Controller:lookup (actual)
for action in self:actions() do
if action:match( actual ) then
return action
end
end
<<Find all expected actions>>
error( sfmt( "Unexpected action %s, expected:\n%s\n"
, actual:tostring()
, table.concat(expected,'\n')
)
, 0
)
end
@
There is no point in listing index actions for callables, since they will
just mirror the call actions.
Sorting the list will make it easier for the user to conclude that an
expected action is "missing", because there will be a definite place in the
list where the action would have been.
<<Find all expected actions>>=
local expected = {}
for _, a in ipairs( self.actionlist ) do
if a:is_expected() and not a.is_callable then
expected[#expected+1] = a:tostring()
end
end
table.sort( expected )
if #expected == 0 then
expected[1] = "(Nothing)"
end
@
Many functions iterate over all the actions in the action list.
<<Unit test for class Controller method actions>>=
function actions_dont_iterate_empty_list_test ()
for a in mc:actions() do
fail( "iterates on empty list" )
end
end
function actions_iterate_over_entire_list_exactly_once_test ()
local l = { {},{},{} }
for _, a in ipairs( l ) do
mc:add_action( a )
end
for a in mc:actions() do
assert_nil( a.check )
a.check = true
end
for _, a in ipairs( l ) do
assert_true( a.check )
end
end
<<Class Controller method actions>>=
function Controller:actions (q)
local l = self.actionlist
local i = 0
return function ()
i = i + 1
return l[i]
end
end
@
The Controller object will allow the user to set return values or
restrictions on the last recorded action.
last
----
<<Unit test for class Controller method get_last_action>>=
function get_last_action_returns_last_element_test ()
local l = { 'a', 'foo', 17 }
for i = 1, #l do
mc:add_action( l[i] )
local res = mc:get_last_action()
assert_equal( l[i], res )
end
end
function get_last_action_fails_on_empty_list_test ()
local ok, err = pcall( function() mc:get_last_action() end )
assert_false( ok, "Found last action in empty list" )
assert_match( "No action is recorded yet", err )
end
<<Class Controller method get_last_action>>=
function Controller:get_last_action ()
local l = self.actionlist
if #l == 0 then
error( "No action is recorded yet.", 0 )
end
return l[#l]
end
@
Actions
=======
Each Action type is implemented as a specialized class so it is easy to add
new action types. The action class is responsible for storing information
and state. The Mock class and Callable class are responsible for catching
and recording/replaying the action with help from the information in the
Action object.
Action types differ in what information they need to store, and therefore
how they are constructed, matched by lookup, and printed as strings. The
concrete or extended implementations of the methods [[new]], [[match]], and
[[tostring]], are therefore implemented together with the corresponding
Mock and Callable meta methods responsible for catching the action,
arranged in separate source files in the action/ directory.
New
---
New Action objects are created with default [[min_replays]] and
[[max_replays]] equal to one, which means each recorded action is expected
to be replayed exactly once. This method will be extended by the the
concrete Action class types.
<<Unit test for class Action.generic method new>>=
function new_action_has_right_default_values_test ()
assert_equal( 0, a.replay_count )
assert_equal( 1, a.min_replays )
assert_equal( 1, a.max_replays )
end
<<Class Action.generic method new>>=
function Action.generic:new (mock)
local a = object( self )
a.mock = mock
a.replay_count = 0
a.min_replays = 1
a.max_replays = 1
return a
end
@
match and is_expected
---------------------
The [[match]] method takes an Action object as key for the search, because
it is a convenient way to pass all the needed properties. The Action
objects in the action list should compare themselves with the key to see if
they can replay such an action. The state properties of the key object are
of course not used, but the state of the actions in the list is important
for if the actions can be replayed or not. The method [[is_expected]]
examines if the state allows the action to be replayed. The [[match]]
method will be extended by the concrete Action class types.
<<Unit test for class Action.generic method is_expected>>=
function expect_unreplayed_action_test ()
assert_true( a:is_expected() )
end
<<Class Action.generic method is_expected>>=
function Action.generic:is_expected ()
return self.replay_count < self.max_replays
and not self.is_blocked
and not self.is_closed
end
<<Unit test for class Action.generic method match>>=
function match_unreplayed_test ()
assert_true( a:match( a ))
end
function match_rejects_replayed_action_test ()
a.replay_count = 1
assert_false( a:match( a ))
end
function match_rejects_wrong_action_type_test ()
-- Fake different type
local B = class( A )
local b = B:new()
assert_false( a:match( b ))
end
<<Class Action.generic method match>>=
function Action.generic:match (key)
if getmetatable(self) ~= getmetatable(key) then return false end
if self.mock ~= key.mock then return false end
return self:is_expected()
end
@
replay_action
-------------
The [[replay]] method updates the state of an Action. It has nothing to do
with performing the action, which is done by the Mock class and Callable
class. It is an internal error to call this method with an action that is
not expected.
<<Unit test for class Controller method replay_action>>=
function replay_action_test ()
local a = A:new()
mc:add_action( a )
assert_true( a:is_expected() )
assert_false( a:is_satisfied() )
mc:replay_action( a )
assert_false( a:is_expected() )
assert_true( a:is_satisfied() )
assert_equal( 1, a.replay_count )
end
<<Class Controller method replay_action>>=
function Controller:replay_action ( action )
assert( action:is_expected(), "lemock internal error" )
assert( action.replay_count < action.max_replays, "lemock internal error" )
local was_satisfied = action:is_satisfied()
action.replay_count = action.replay_count + 1
if not was_satisfied and action.labellist and action:is_satisfied() then
self:update_dependencies()
end
if action.closelist then
self:close_actions( action:closes() )
end
end
@
is_satisfied and assert_satisfied
---------------------------------
When the replay phase is finished the Controller needs to verify that all
the actions in the action list have been satisfied. If some action is not
satisfied it is an error, so [[assert_satisfied]] must also throw an
appropriate error.
<<Unit test for class Action.generic method is_satisfied>>=
function unreplayed_action_is_not_satisfied_test ()
assert_false( a:is_satisfied() )
end
function assert_satisfied_unreplayed_action_fails_test ()
local ok, err = pcall( function() a:assert_satisfied() end )
assert_false( ok, "unreplayed action was satisfied" )
assert_match( "Wrong replay count 0", err )
end
<<satisfied expression>>=
self.min_replays <= self.replay_count
<<Class Action.generic method is_satisfied>>=
function Action.generic:is_satisfied ()
return <<satisfied expression>>
end
<<Class Action.generic method assert_satisfied>>=
function Action.generic:assert_satisfied ()
assert( self.replay_count <= self.max_replays, "lemock internal error" )
if not (<<satisfied expression>>) then
error( sfmt( "Wrong replay count %d (expected %d..%d) for %s"
, self.replay_count
, self.min_replays, self.max_replays
, self:tostring()
)
, 0
)
end
end
@
Catching and Simulating Actions
###############################
Three classes work in tight connection to catch, record, and replay
actions. They are [[Controller]], [[Mock]], and [[Callable]].
The Controller is the main class, and it stores the action list with all
the Actions. The Controller is used to record explicit information that can
not be caught in an automatic manner by the Mock or Callable objects, such
as retur values. It is used to record meta information such as replay
limits and replay order dependencies. It is used to create the Mock
objects, to switch to replay mode, and to verify the completeness of the
replay phase.
The Mock must be completely empty of methods and properties, because we
want to catch all of them, without accidentally shadowing any names. So it
can only use Lua meta methods, like [[__index]] and [[__newindex]]. Because
of this limitation it is not possible to store a reference to the
Controller in the Mock object, so we need a map for this.
<<Module mock private data mock_controller_map>>=
local mock_controller_map = setmetatable( {}, {__mode='k'} )
@
When a method of a Mock object is called there are actually two actions.
First the method's name is indexed, and then the returned object is called.
So the Mock [[__index]] meta method must record the index part, *and* return
something with a [[__call]] meta method that can record the call part. This
something is a [[Callable]] object.
Setup Phase
===========
The Controller
--------------
To keep the user interface clean the Controller class itself is never
exported. The module function [[controller]] creates a new local controller
object, and creates and returns a wrapper for it with the exported methods.
<<Module mock function controller>>=
function controller ()
local exported_methods = {
'anytimes',
'atleastonce',
'close',
'depend',
'error',
'label',
'mock',
'new',
'replay',
'returns',
'times',
'verify',
}
local mc = Controller:new()
local wrapper = {}
for _, method in ipairs( exported_methods ) do
wrapper[ method ] = function (self, ...)
return mc[ method ]( mc, ... )
end
end
wrapper.ANYARG = Argv.ANYARG
wrapper.ANYARGS = Argv.ANYARGS
return wrapper
end
<<Class Controller method new>>=
function Controller:new ()
local mc = object( self )
mc.actionlist = {}
mc.is_recording = true
return mc
end
@
The Mock
--------
The Mock class has two modes, one for recording and one for replaying.
These two modes are implemented as two different metatables. The [[class]
helper functions is not used to create them, because it sets [[__index]] to
be a self reference. The [[__index]] meta method, and other meta methods,
of the two Mock metatables are used for catching actions, and are defined
in the corresponding action source files.
<<Class Mock meta tables Mock.record and Mock.replay>>=
Mock = { record={}, replay={} } -- no self-referencing __index!
@
Because the Mock must be completely empty it can not have a [[new]] method.
So the Controller has a [[mock]] method that creates and returns new mock
objects, and maps them to itself in [[mock_controller_map]]. The Mock is
created in record mode, but will be switched to replay mode later by the
Controller.
<<Unit test for module mock; mock creation>>=
function create_completely_empty_mock_test ()
for k, v in pairs( m ) do
fail( "Mock should be empty but contains "..tostring(k) )
end
end
function create_mock_during_replay_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:mock() end )
assert_false( ok, "mock() succeeded" )
assert_match( "New mock during replay.", err )
end
<<Class Controller method mock>>=
function Controller:mock ()
if not self.is_recording then
error( "New mock during replay.", 2 )
end
local m = object( Mock.record )
mock_controller_map[m] = self
return m
end
@
The Callable
------------
A Callable is only created as the result of an index action. It is not
known at the time of the index action if the index will be called, but if
it is, the call action must update the index action with this information,
and therefore needs a reference to it.
<<Class Callable.generic method new>>=
function Callable.generic:new ( index_action )
local f = object( self )
f.action = index_action
return f
end
@
Record Phase
============
Return Values and Callables
---------------------------
Index action can either have a return value or be callable, in which case
they return a Callable object. When the call action is recorded the
corresponding index action must be marked as [[is_callable]], so that it
knows it shall return a Callable object during replay mode.
Index actions that return callables have their replay count limits set to
any-times, because all control adjustments will be made to the call action,
and it should not matter how many times the callable is retrieved from the
mock, but only how many times and in what order it is called.
If the retrieval count and/or order is important it is still possible to
simulate such behavior with an index action returning a mock object. Then
the index action can be controlled, and the call action can instead be
recorded with the selfcall action of the returned mock object.
<<Unit test for module mock; make callable>>=
function returns_on_empty_list_fails_test ()
local ok, err = pcall( function() mc:returns(nil) end )
assert_false( ok, "returns called on nothing" )
assert_match( "No action is recorded yet.", err )
end
function returns_make_call_fail_test ()
local tmp = m.foo ;mc:returns(1)
local ok, err = pcall( function() tmp(2) end )
assert_false( ok, "called index with returnvalue" )
assert_match( "Can not call foo. It has a returnvalue.", err )
end
function callable_index_replays_anytimes_test ()
local tmp = m.foo()
mc:replay()
tmp = m.foo
tmp = m.foo
tmp = m.foo()
mc:verify()
end
<<Class Controller method make_callable>>=
function Controller:make_callable (action)
if action.has_returnvalue then
error( "Can not call "..action.key..". It has a returnvalue.", 0 )
end
action.is_callable = true
action.min_replays = 0
action.max_replays = math.huge
end
@
Action classes that can return a value have the [[can_return]] property
set, and they implement the [[set_returnvalue]] and [[get_returnvalue]]
methods.
If an index action is marked as [[is_callable]] it ought to be followed by
a call action responsible for making it callable, and since it is no longer
the last action it should be impossible to set a returnvalue for the index
action. Doing so is an internal error.
<<Unit test for module mock returnvalue>>=
function returns_during_replay_fails_test ()
local tmp = m.foo
mc:replay()
local ok, err = pcall( function() mc:returns(1) end )
assert_false( ok, "returns() succeeded during replay" )
assert_match( "Returns called during replay.", err )
end
function returns_on_nonreturning_action_fails_test ()
m.foo = 1 -- assignments can't return
local ok, err = pcall( function() mc:returns(0) end )
assert_false( ok, "returns() succeeded on non-returning action" )
assert_match( "Previous action can not return anything.", err )
end
function returns_twice_fails_test ()
local tmp = m.foo ;mc:returns(1)
local ok, err = pcall( function() mc:returns(2) end )
assert_false( ok, "duplicate returns() succeeded" )
assert_match( "Returns and/or Error called twice for same action.", err )
end
<<Class Controller method returns>>=
function Controller:returns (...)
if not self.is_recording then
error( "Returns called during replay.", 2 )
end
local action = self:get_last_action()
assert( not action.is_callable, "lemock internal error" )
if not action.can_return then
error( "Previous action can not return anything.", 2 )
end
if action.has_returnvalue or action.throws_error then
error( "Returns and/or Error called twice for same action.", 2 )
end
action:set_returnvalue(...)
return self -- for chaining
end
@
Throwing Errors
---------------
Any action can be made to throw a recorded error when replayed. For
example, a newindex action could simulate an assignment to a userdata och
table with a metatable that is supposed to throw an error on assignments.
<<Unit test for module mock error>>=
function error_during_replay_fails_test ()
local tmp = m.foo
mc:replay()
local ok, err = pcall( function() mc:error(1) end )
assert_false( ok, "error() succeeded during replay" )
assert_match( "Error called during replay.", err )
end
function error_twice_fails_test ()
local tmp = m.foo ;mc:error(1)
local ok, err = pcall( function() mc:error(2) end )
assert_false( ok, "duplicate error() succeeded" )
assert_match( "Returns and/or Error called twice for same action.", err )
end
function error_plus_returns_fails_test ()
local tmp = m.foo ;mc:returns(1)
local ok, err = pcall( function() mc:error(2) end )
assert_false( ok, "both error and returns succeeded" )
assert_match( "Returns and/or Error called twice for same action.", err )
end
<<Class Controller method error>>=
function Controller:error (value)
if not self.is_recording then
error( "Error called during replay.", 2 )
end
local action = self:get_last_action()
if action.has_returnvalue or action.throws_error then
error( "Returns and/or Error called twice for same action.", 2 )
end
action.throws_error = true
action.errorvalue = value
return self -- for chaining
end
@
Switching from Record Mode to Replay Mode
=========================================
Switching to replay mode is done by changing all Mock objects' matatables.
It is an error to call [[replay]] twice on the same Controller.
Dependency information (see restrictions.nw) is not calculated in real
time. It is updated when needed, for example before starting the replay
phase. This is also a good point to check for dependency cycles.
<<Unit test for module mock; switching to replay mode>>=
function replay_twice_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:replay() end )
assert_false( ok, "replay succeeded twice" )
assert_match( "Replay called twice.", err )
end
function multiple_controllers_test ()
local mc2 = lemock.controller()
local m2 = mc2:mock()
-- m -- -- m2 --
m.foo = 1
mc:replay()
m2.bar = 2
m.foo = 1
mc2:replay()
mc:verify()
m2.bar = 2
mc2:verify()
end
<<Unit test for class Controller method replay>>=
function replay_test ()
assert_true( mc.is_recording )
mc:replay()
assert_false( mc.is_recording )
end
<<Class Controller method replay>>=
function Controller:replay ()
if not self.is_recording then
error( "Replay called twice.", 2 )
end
self.is_recording = false
for m, mc in pairs( mock_controller_map ) do
if mc == self then
setmetatable( m, Mock.replay )
end
end
self:update_dependencies()
self:assert_no_dependency_cycles()
end
@
Replay Phase
============
The key point with the replay phase is that unexpected actions should fail.
<<Unit test for module mock; replay>>=
function replay_in_any_order_test ()
m.a = 1
m.b = 2
m.c = 3
mc:replay()
m.c = 3
m.a = 1
m.b = 2
mc:verify()
end
function replaying_unexpected_action_fails_test ()
mc:replay()
local ok, err = pcall( function() m:somethingelse() end )
assert_false( ok, "unexpected replay succeeded" )
assert_match( "Unexpected action index somethingelse", err )
end
@
There is an error that would be very hard to track down if it is not
detected. During record mode the Mock returns recording Callables, but
during replay mode it returns replaying Callables. If the client caches a
Callable in record mode and uses it during replay mode, it would not fail
straight away as desired (if undetected), but record a false action which
would mess up the action list and cause some later action or the final
verify to pass or fail with a perplexing error message.
<<Unit test for module mock; replay>>=
function cached_recording_callable_fails_during_replay_test ()
local tmp = m.foo ; tmp()
mc:replay()
local ok, err = pcall( function() tmp() end )
assert_false( ok, "Cached callable not detected" )
assert_match( "client uses cached callable from recording", err )
end
@
Verify Completeness Phase
=========================
Unknown or misplaced actions can be detected during the replay phase, but
expected actions that are not replayed can not be detected until the replay
phase is finished. That is why the verify phase is needed, and it simply
asserts that all recorded actions have been satisfied.
<<Unit test for module mock; verify>>=
function verify_during_record_phase_fails_test ()
local ok, err = pcall( function() mc:verify() end )
assert_false( ok, "Verify succeeded" )
assert_match( "Verify called during record.", err )
end
function verify_replayed_actionlist_test ()
mc:replay()
mc:verify()
end
function verify_unreplyed_actionlist_fails_test ()
local tmp = m.foo
mc:replay()
local ok, err = pcall( function() mc:verify() end )
assert_false( ok, "Verify succeeded" )
assert_match( "Wrong replay count 0 ", err )
end
<<Class Controller method verify>>=
function Controller:verify ()
if self.is_recording then
error( "Verify called during record.", 2 )
end
for a in self:actions() do
a:assert_satisfied()
end
end

@ -0,0 +1,30 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
<<Lua file blurb>>=
------ THIS FILE IS TANGLED FROM LITERATE SOURCE FILES ------
-- Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
-- See terms in file COPYRIGHT, or at http://lemock.luaforge.net
<<Simple Fake_action class>>=
Fake_action = class()
function Fake_action:new (x)
local a = object(Fake_action)
a.x = x
return a
end
function Fake_action:match (q)
return self.x < q.x
end
function Fake_action:is_expected ()
return true
end
function Fake_action:tostring ()
return '<faked action>'
end
function Fake_action:blocks ()
return function () end
end
Fake_action.depends = Fake_action.blocks

@ -0,0 +1,632 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
Action Controll
###############
Times
=====
Some Actions might need to be replayed an arbitrary number of times,
although they should be replayed at least once. Others might be optional
and not need to be replayed at all.
The [[min_replays]] and [[max_replays]] properties of the Action implements
this kind of control. The Action is satisfied if it is replayed at least
[[min_replay]] times, but it will still continue to match on lookups until
it has been replayed [[max_replays]] times.
Common values for min and max:
+--------+-------------------+
| 1, 1 | exactly once |
+--------+-------------------+
| 1, inf | at least once |
+--------+-------------------+
| 0, 1 | no more than once |
+--------+-------------------+
| 0, inf | any times |
+--------+-------------------+
It is no harm to change the times more than once; they will simply be
overwritten. Suppressing multiple changes would require an extra control
property.
<<Unit test for module mock; times>>=
function times_test ()
local tmp = m.foo ;mc:returns( 2 ):times( 2, 3 )
mc:replay()
-- 1
local tmp = m.foo
local ok, err = pcall( function() mc:verify() end )
assert_false( ok, "verified unsatisfied action" )
assert_match( "Wrong replay count 1 ", err )
-- 2
local tmp = m.foo
mc:verify()
-- 3
local tmp = m.foo
mc:verify()
-- 4
local ok, err = pcall( function() local tmp = m.foo end )
assert_false( ok, "replaied finished action" )
assert_match( "Unexpected action index foo", err )
end
function times_called_twice_test ()
m.foo = 1 ;mc:times( 0, math.huge ):times( 1 )
end
function times_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:times(1) end )
assert_false( ok, "changed times in replay mode" )
assert_match( "Can not set times in replay mode.", err )
end
function unrealistic_times_fails_with_message_test ()
m.a = 'a'
local ok, err = pcall( function() mc:times(0) end )
assert_false( ok, "accepted unrealistic time arguments" )
assert_match( "Unrealistic time arguments", err )
end
<<Class Controller method times>>=
function Controller:times (min, max)
if not self.is_recording then
error( "Can not set times in replay mode.", 0 )
end
self:get_last_action():set_times( min, max )
return self -- for chaining
end
-- convenience functions
function Controller:anytimes() return self:times( 0, math.huge ) end
function Controller:atleastonce() return self:times( 1, math.huge ) end
@
Action set_times
----------------
<<Unit test for class Action.generic method set_times>>=
function set_and_get_times_test ()
end
function unrealistic_times_fails_test ()
local ps = { {'foo'}, {8,'bar'}, {-1}, {3,2}, {1/0}, {0/0}, {0,0} }
for _, p in ipairs( ps ) do
local ok, err = pcall( function() a:set_times( unpack(p) ) end )
assert_false( ok, "unrealistic times "..table.concat(p,", ") )
assert_match( "Unrealistic time arguments ", err )
end
end
<<Class Action.generic method set_times>>=
function Action.generic:set_times (a, b)
min = a or 1
max = b or min
min, max = tonumber(min), tonumber(max)
if (not min) or (not max) or (min >= math.huge)
or (min ~= min) or (max ~= max) -- NaN
or (min < 0) or (max <= 0) or (min > max) then
error( sfmt( "Unrealistic time arguments (%s, %s)"
, qtostring( min )
, qtostring( max )
)
, 0
)
end
self.min_replays = min
self.max_replays = max
end
@
Label
=====
Labels help establishing relationships between Actions. The labels are
actually implemented as tags, in a many-to-many relationship, so that one
Action can have multiple labels, and the same label can be assigned to
multiple Actions.
<<Unit test for module mock; label>>=
function label_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:label( 'foo' ) end )
assert_false( ok, "set label in replay mode" )
assert_match( "Can not add labels in replay mode", err )
end
function label_on_empty_actionlist_fails_test ()
local ok, err = pcall( function() mc:label( 'bar' ) end )
assert_false( ok, "set label with empty action list" )
assert_match( "No action is recorded yet", err )
end
<<Unit test for class Controller method label>>=
function label_test ()
mc:add_action( A:new() )
mc:label( 'a', 'b' ):label( 'c', 'b' )
local a = mc:get_last_action()
local seen = {}
for l in a:blocks() do
seen[l] = true
end
assert_true( seen['a'] )
assert_true( seen['b'] )
assert_true( seen['c'] )
assert_nil( seen['d'] )
end
<<Class Controller method label>>=
function Controller:label (...)
if not self.is_recording then
error( "Can not add labels in replay mode.", 2 )
end
local action = self:get_last_action()
for _, label in ipairs{ ... } do
action:add_label( label )
end
return self -- for chaining
end
@
Action add_label, has_label, and blocks
---------------------------------------
There is no method for iterating through the labels, but there is a
[[blocks]] method, that iterates through the labels if the Action is not
satisfied. This method is used for updating the dependency information.
<<Unit test for class Action.generic label methods>>=
function label_test ()
local ls = { 1/0, 0, false, {}, a, "foo", true }
for i = 1, #ls do
assert_false( a:has_label( ls[i] ))
end
for i = 1, #ls do
a:add_label( ls[i] )
for j = 1 , #ls do
if j <= i then
assert_true( a:has_label( ls[j] ))
else
assert_false( a:has_label( ls[j] ))
end
end
end
end
function add_label_twice_test ()
local l = 'foo'
a:add_label( l )
a:add_label( l )
local cnt = 0
for x in a:blocks() do
assert_equal( l, x )
cnt = cnt + 1
end
assert_equal( 1, cnt )
end
<<Class Action.generic method add_label>>=
function Action.generic:add_label (label)
add_to_set( self, 'labellist', label )
end
<<Class Action.generic method has_label>>=
function Action.generic:has_label (l)
for x in elements_of_set( self, 'labellist' ) do
if x == l then return true end
end
return false
end
<<Class Action.generic method blocks>>=
function Action.generic:blocks ()
if self:is_satisfied() then
return function () end
end
return elements_of_set( self, 'labellist' )
end
@
Depend
======
A typical usage case is expected to have some tenths actions, so there is
no need to optimize dependency calculations for speed. The dependencies are
stored as lists of labels in the action objects. When an action changes
states the Controller's [[update_dependency]] method examines the action
list. If an action is not satisfied all its labels are collected in a
blocking list, and finally all actions that depend on a label in the
blocking list are blocked by setting the [[is_blocked]] property to true.
<<Unit test for module mock; depend>>=
function depend_fulfilled_test ()
m.foo = 1 ;mc:label 'A'
m.bar = 2 ;mc:depend 'A'
mc:replay()
m.foo = 1
m.bar = 2
mc:verify()
end
function depend_unfulfilled_fails_test ()
m.foo = 1 ;mc:label 'A'
m.bar = 2 ;mc:depend 'A'
mc:replay()
local ok, err = pcall( function() m.bar = 2 end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action newindex", err )
end
function depend_fulfilled_any_order_test ()
local tmp
m.a = 1 ;mc:label 'A'
tmp = m.b ;mc:returns(2):depend 'A'
tmp = m.b ;mc:returns(3)
mc:replay()
assert_equal( 3, m.b, "replayed wrong b" )
m.a = 1
assert_equal( 2, m.b, "replayed wrong b" )
mc:verify()
end
function depend_serial_blocks_test ()
local tmp
tmp = m:a() ;mc:label 'a'
tmp = m:c() ;mc:label 'c' :depend 'b'
tmp = m:b() ;mc:label 'b' :depend 'a'
mc:replay()
local ok, err = pcall( function() tmp = m:b() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
function depend_on_many_labels_test ()
local tmp
tmp = m:b() ;mc:label 'b'
tmp = m:c() ;mc:label 'c' :depend( 'a', 'b' )
tmp = m:a() ;mc:label 'a'
mc:replay()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
function depend_on_many_labels_test2_test ()
-- swap order, in case whole list is not checked
local tmp
tmp = m:b() ;mc:label 'b'
tmp = m:c() ;mc:label 'c' :depend( 'b', 'a' )
tmp = m:a() ;mc:label 'a'
mc:replay()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
function depend_on_many_bloskers_with_same_label_test ()
tmp = m:c() ;mc:label 'c' :depend 'b'
tmp = m:a() ;mc:label 'b'
tmp = m:b() ;mc:label 'b'
mc:replay()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:a()
local ok, err = pcall( function() tmp = m:c() end )
assert_false( ok, "replayed blocked action" )
assert_match( "Unexpected action", err )
m:b()
m:c()
mc:verify()
end
@
The algorithm implies that "unknown" labels are ignored, i.e. unless a
depended upon label is explicitly blocked by an unsatisfied action with
that a label, the dependency is considered fulfilled.
<<Unit test for module mock; depend>>=
function depend_ignors_unknown_label_test ()
m.foo = 1 ;mc:label 'A'
m.bar = 2 ;mc:depend 'B'
mc:replay()
m.foo = 1
m.bar = 2
mc:verify()
end
@
Cycles constitute a problem. A dependency cycle of actions will block
itself out and can not be replayed. Although this does not necessarily mean
replay will fail -- all the actions might for example have [[min_replays]]
set to zero, in which case they are automatically satisfied -- it is most
likely a user error to introduce a dependency cycle, and a helpful error
message is in place.
<<Unit test for module mock; depend>>=
function depend_detect_cycle_test ()
local ok, err = pcall( function()
m.foo = 1 ;mc:label 'A' :depend 'B'
m.bar = 2 ;mc:label 'B' :depend 'A'
mc:replay()
m.foo = 1
end )
assert_false( ok, "replayed cyclically blocked action" )
assert_match( "dependency cycle", err )
end
<<Unit test for module mock; depend>>=
function depend_chaining_test ()
m.a = 1 ;mc:label 'A'
m.b = 1 ;mc:label 'B'
m.c = 1 ;mc:depend('A'):depend('B')
end
function depend_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:depend( 'foo' ) end )
assert_false( ok, "set dependency in replay mode" )
assert_match( "Can not add dependency in replay mode", err )
end
function depend_on_empty_actionlist_fails_test ()
local ok, err = pcall( function() mc:depend( 'bar' ) end )
assert_false( ok, "set dependency with empty action list" )
assert_match( "No action is recorded yet", err )
end
function depend_reports_expected_actions_on_faliure_test ()
local tmp
tmp = m.foo ;mc:depend 'B'
tmp = m.bar ;mc:label 'B'
mc:replay()
local ok, err = pcall( function() tmp = m.foo end )
assert_false( ok, "replayed blocked action" )
assert_match( "expected:.*index bar", err )
assert_not_match( "expected:.*index foo", err )
tmp = m.bar
local ok, err = pcall( function() tmp = m.bar end )
assert_false( ok, "expected:.*replayed blocked action" )
assert_not_match( "expected:.*index bar", err )
assert_match( "index foo", err )
end
@
Addind dependencies
-------------------
<<Class Controller method depend>>=
function Controller:depend (...)
if not self.is_recording then
error( "Can not add dependency in replay mode.", 2 )
end
local action = self:get_last_action()
for _, dependency in ipairs{ ... } do
action:add_depend( dependency )
end
return self -- for chaining
end
<<Unit test for Class Action.generic method add_depend and depends>>=
function add_depend_test ()
local ls = { 0, 'foo', 1/0, a, {} }
local seen = {}
for _, l in ipairs( ls ) do
seen[l] = 0
a:add_depend( l )
end
for l in a:depends() do
seen[l] = seen[l] + 1
end
for _, l in ipairs( ls ) do
assert_equal( 1, seen[l], "Mismatch for "..qtostring(l) )
end
end
function dependencies_dont_iterate_on_empty_list_test ()
for _ in a:depends() do
fail( "unexpected dependency" )
end
end
<<Class Action.generic method add_depend>>=
function Action.generic:add_depend (d)
add_to_set( self, 'dependlist', d )
end
<<Class Action.generic method depends>>=
function Action.generic:depends ()
return elements_of_set( self, 'dependlist' )
end
@
Updating dependencies and detecting cycles
------------------------------------------
<<Class Controller method update_dependencies>>=
function Controller:update_dependencies ()
local blocked = {}
for action in self:actions() do
for label in action:blocks() do
blocked[label] = true
end
end
local function is_blocked (action)
for label in action:depends() do
if blocked[label] then return true end
end
return false
end
for action in self:actions() do
action.is_blocked = is_blocked( action )
end
end
@
A cycle is detected by a complete depth-first search of the dependency
tree, examining for each step if new labels are already present somewhere
in the path to the new node. The design with multiple labels and multiple
dependencies makes the algorithm a bit extra complicated, because each node
in the tree is a set of labels.
A *node* is represented as a table, where labels are stored as array
elements, and the [[prev]] property references the previous node in the
*path*.
The use of temporary "linked lists" in this function generates a lot of
garbage for the garbage collector, but it is only run once.
<<Class Controller method assert_no_dependency_cycles>>=
function Controller:assert_no_dependency_cycles ()
local function is_in_path (label, path)
if not path then return false end -- is root
for _, l in ipairs( path ) do
if l == label then return true end
end
if path.prev then return is_in_path( label, path.prev ) end
return false
end
local function can_block (action, node)
for _, label in ipairs( node ) do
if action:has_label( label ) then return true end
end
return false
end
local function step (action, path)
local new_head
for label in action:depends() do
if is_in_path( label, path ) then
error( "Detected dependency cycle", 0 )
end
-- only create table if needed to reduce garbage
if not new_head then new_head = { prev=path } end
new_head[#new_head+1] = label
end
return new_head
end
local function search_depth_first (path)
for action in self:actions() do
if can_block( action, path ) then
local new_head = step( action, path )
if new_head then
search_depth_first( new_head )
end
end
end
end
for action in self:actions() do
local root = step( action, nil )
if root then search_depth_first( root ) end
end
end
@
Close
=====
<<Unit test for module mock; close>>=
function close_test ()
local t
t = m.foo ;mc:times(0,1/0):returns( 1 ) :label(1)
t = m.foo ;mc:times(0,1/0):returns( 2 ) :label(2)
t = m.foo ;mc:times(0,1/0):returns( 3 )
m.bar(1) ;mc:close(1)
m.bar(2) ;mc:close(2)
mc:replay()
m.bar(1)
assert_equal( 2, m.foo )
assert_equal( 2, m.foo )
assert_equal( 2, m.foo )
m.bar(2)
assert_equal( 3, m.foo )
mc:verify()
end
function close_unsatisfied_action_fails_test ()
m.a = 1 ;mc:label(1)
m.b = 2 ;mc:close(1)
mc:replay()
local ok, err = pcall( function() m.b = 2 end )
assert_false( ok, "Undetected close of unsatisfied action" )
assert_match( "Closes unsatisfied action", err )
end
function close_multiple_test ()
m.foo(1) ;mc:label(1) :times(0,1)
m.foo(1) ;mc:label(2) :times(0,1)
m.foo(1)
m.bar() ;mc:close(1,2)
mc:replay()
m.bar()
m.foo(1)
mc:verify()
end
<<Unit test for module mock; close>>=
function close_chaining_test ()
m.a = 1 ;mc:label 'A'
m.b = 1 ;mc:label 'B'
m.c = 1 ;mc:close('A'):close('B')
end
function close_in_replay_mode_fails_test ()
mc:replay()
local ok, err = pcall( function() mc:close( 'foo' ) end )
assert_false( ok, "accepted close in replay mode" )
assert_match( "Can not insert close in replay mode", err )
end
function close_on_empty_actionlist_fails_test ()
local ok, err = pcall( function() mc:close( 'bar' ) end )
assert_false( ok, "accepted close with empty action list" )
assert_match( "No action is recorded yet", err )
end
@
Adding closes
-------------
<<Class Controller method close>>=
function Controller:close (...)
if not self.is_recording then
error( "Can not insert close in replay mode.", 2 )
end
local action = self:get_last_action()
for _, close in ipairs{ ... } do
action:add_close( close )
end
return self -- for chaining
end
<<Class Action.generic method add_close>>=
function Action.generic:add_close (label)
add_to_set( self, 'closelist', label )
end
@
Perform closes
--------------
<<Class Controller method close_actions>>=
function Controller:close_actions( ... ) -- takes iterator
for label in ... do
for candidate in self:actions() do
if candidate:has_label( label ) then
if not candidate:is_satisfied() then
error( "Closes unsatisfied action: "..candidate:tostring(), 0 )
end
candidate.is_closed = true
end
end
end
end
<<Class Action.generic method closes>>=
function Action.generic:closes ()
return elements_of_set( self, 'closelist' )
end

@ -0,0 +1,182 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
Convertng Objects to Strings
############################
When replay or verification fails there should be an explanation that shows
what the problem is. One way is to list the expected actions. Therefore
each Action type has a [[tostring]] method. The output tries to mimic what
the missing expression might have looked like in the failing code.
Helper functions qtostring and sfmt
===================================
[[sfmt]] is to make the code less verbose. [[qtostring]] is a wrapper for
[[tostring]] which adds quoting of string values to make error messages
easier to understand.
<<Helper function qtostring and sfmt>>=
sfmt = string.format
function qtostring (v)
if type(v) == 'string' then
return sfmt( '%q', v )
else
return tostring( v )
end
end
@
Action Newindex
================
<<Unit test for class Action.newindex method tostring>>=
function newindex_tostring_test ()
local a = Action.newindex:new( {}, 'key', 7 )
assert_equal( 'newindex key = 7', a:tostring() )
a = Action.newindex:new( {}, true, '7' )
assert_equal( 'newindex true = "7"', a:tostring() )
end
<<Class Action.newindex method tostring>>=
function Action.newindex:tostring ()
return sfmt( "newindex %s = %s"
, tostring(self.key)
, qtostring(self.val)
)
end
@
Action Index
============
<<Unit test for class Action.index method tostring>>=
function index_tostring_test ()
local a = Action.index:new( {}, true )
assert_equal( 'index true', a:tostring() )
a:set_returnvalue('"false"')
assert_equal( 'index true => "\\"false\\""', a:tostring() )
end
function callable_index_tostring_test ()
local a = Action.index:new( {}, 'f' )
a.is_callable = true
assert_equal( 'index f()', a:tostring() )
end
<<Class Action.index method tostring>>=
function Action.index:tostring ()
local key = 'index '..tostring( self.key )
if self.has_returnvalue then
return sfmt( "index %s => %s"
, tostring( self.key )
, qtostring( self.returnvalue )
)
elseif self.is_callable then
return sfmt( "index %s()"
, tostring( self.key )
)
else
return sfmt( "index %s"
, tostring( self.key )
)
end
end
@
Action Call
===========
<<Unit test for class Action.call method tostring>>=
function call_tostring_test ()
local a = Action.call:new( {}, 'foo', 1, '"', 3 )
assert_equal( 'call foo(1,"\\"",3)', a:tostring() )
a:set_returnvalue( 'false', false )
assert_equal( 'call foo(1,"\\"",3) => "false",false', a:tostring() )
end
<<Class Action.call method tostring>>=
function Action.call:tostring ()
if self.has_returnvalue then
return sfmt( "call %s(%s) => %s"
, tostring(self.key)
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "call %s(%s)"
, tostring(self.key)
, self.argv:tostring()
)
end
end
@
Action Selfcall
===============
<<Unit test for class Action.selfcall method tostring>>=
function selfcall_tostring_test ()
local a = Action.selfcall:new( {}, 1, '"', nil )
assert_equal( 'selfcall (1,"\\"",nil)', a:tostring() )
a:set_returnvalue( 'false', false )
assert_equal( 'selfcall (1,"\\"",nil) => "false",false', a:tostring() )
end
<<Class Action.selfcall method tostring>>=
function Action.selfcall:tostring ()
if self.has_returnvalue then
return sfmt( "selfcall (%s) => %s"
, self.argv:tostring()
, self.returnvalue:tostring()
)
else
return sfmt( "selfcall (%s)"
, self.argv:tostring()
)
end
end
@
Class Argv
==========
Argument lists are converted without surrounding parentheses, because they
can be used as multiple return values as well as call arguments. When they
are used as call arguments, the invoker will have to add the parentheses.
<<Unit test for class Argv method tostring>>=
function tostring_test ()
assert_equal( '', Argv:new() :tostring() )
assert_equal( '""', Argv:new('') :tostring() )
assert_equal( 'nil,nil', Argv:new(nil,nil) :tostring() )
assert_equal( '"false",false', Argv:new('false',false) :tostring() )
assert_equal( '1,2,3', Argv:new(1,2,3) :tostring() )
assert_equal( '1,ANYARG,3', Argv:new(1,Argv.ANYARG,3):tostring() )
assert_equal( 'ANYARGS', Argv:new(Argv.ANYARGS) :tostring() )
assert_equal( '7,0,ANYARGS', Argv:new(7,0,Argv.ANYARGS):tostring() )
end
<<Class Argv method tostring>>=
function Argv:tostring ()
local res = {}
local function w (v)
res[#res+1] = qtostring( v )
end
local av, ac = self.v, self.len
for i = 1, ac do
if av[i] == Argv.ANYARG then
res[#res+1] = 'ANYARG'
elseif av[i] == Argv.ANYARGS then
res[#res+1] = 'ANYARGS'
else
w( av[i] )
end
if i < ac then
res[#res+1] = ',' -- can not use qtostring in w()
end
end
return table.concat( res )
end

@ -0,0 +1,169 @@
Lua Easy Mock -- LeMock
Copyright (C) 2009 Tommy Pettersson <ptp@lysator.liu.se>
See terms in file COPYRIGHT, or at http://lemock.luaforge.net
@
module
------
<<unit/module.lua>>=
<<Lua file blurb>>
require 'lunit'
module( 'unit.module', lunit.testcase, package.seeall )
require 'lemock'
local mc, m
function setup ()
mc = lemock.controller()
m = mc:mock()
end
<<Unit test for module mock; close>>
<<Unit test for module mock; depend>>
<<Unit test for module mock; label>>
<<Unit test for module mock; make callable>>
<<Unit test for module mock; mock creation>>
<<Unit test for module mock; replay>>
<<Unit test for module mock; switching to replay mode>>
<<Unit test for module mock; times>>
<<Unit test for module mock; verify>>
<<Unit test for module mock call>>
<<Unit test for module mock error>>
<<Unit test for module mock index>>
<<Unit test for module mock newindex>>
<<Unit test for module mock returnvalue>>
<<Unit test for module mock selfcall>>
@
controller
----------
<<unit/controller.lua>>=
<<Lua file blurb>>
require 'lunit'
module( 'unit.controller', lunit.testcase, package.seeall )
local class, object, qtostring, sfmt, add_to_set, elements_of_set
<<Helper function class and object>>
<<Helper function qtostring and sfmt>>
<<Helper function add_to_set and elements_of_set>>
<<Module mock private data mock_controller_map>>
local Controller, Action
<<Class Controller>>
<<Class Action>>
local A = Action.generic
Action = nil -- only allow generic action
function A:tostring () return '<dummy>' end
local mc
function setup ()
mc = Controller:new()
end
<<Unit test for class Controller method actions>>
<<Unit test for class Controller method add_action>>
<<Unit test for class Controller method get_last_action>>
<<Unit test for class Controller method label>>
<<Unit test for class Controller method lookup>>
<<Unit test for class Controller method replay>>
<<Unit test for class Controller method replay_action>>
@
argv
----
<<unit/argv.lua>>=
<<Lua file blurb>>
require 'lunit'
module( 'unit.argv', lunit.testcase, package.seeall )
local class, object, value_equal, sfmt, qtostring
<<Helper function class and object>>
<<Helper function value_equal>>
<<Helper function qtostring and sfmt>>
local Argv
<<Class Argv>>
<<Unit test for class Argv method equal>>
<<Unit test for class Argv method new>>
<<Unit test for class Argv method tostring>>
<<Unit test for class Argv method unpack>>
@
action_generic
--------------
<<unit/action_generic.lua>>=
<<Lua file blurb>>
require 'lunit'
module( 'unit.action_generic', lunit.testcase, package.seeall )
local class, object, qtostring, sfmt, add_to_set, elements_of_set
<<Helper function class and object>>
<<Helper function qtostring and sfmt>>
<<Helper function add_to_set and elements_of_set>>
local Action, Argv
<<Class Action>>
<<Class Argv>>
local A = Action.generic
Action = nil -- only allow generic action
function A:tostring () return "<generic action>" end
local a
function setup ()
a = A:new()
end
<<Unit test for Class Action.generic method add_depend and depends>>
<<Unit test for class Action.generic label methods>>
<<Unit test for class Action.generic method is_expected>>
<<Unit test for class Action.generic method is_satisfied>>
<<Unit test for class Action.generic method match>>
<<Unit test for class Action.generic method new>>
<<Unit test for class Action.generic method set_times>>
@
action
------
<<unit/action.lua>>=
<<Lua file blurb>>
require 'lunit'
module( 'unit.action', lunit.testcase, package.seeall )
local class, object, qtostring, sfmt
<<Helper function class and object>>
<<Helper function value_equal>>
<<Helper function qtostring and sfmt>>
local Action, Argv
<<Class Action>>
<<Class Argv>>
<<Unit test for class Action.call method match>>
<<Unit test for class Action.call method tostring>>
<<Unit test for class Action.generic_call method get_returnvalue>>
<<Unit test for class Action.index method match>>
<<Unit test for class Action.index method new>>
<<Unit test for class Action.index method set_returnvalue>>
<<Unit test for class Action.index method tostring>>
<<Unit test for class Action.newindex method match>>
<<Unit test for class Action.newindex method tostring>>
<<Unit test for class Action.selfcall method match>>
<<Unit test for class Action.selfcall method tostring>>

@ -0,0 +1,28 @@
#!/usr/bin/env rc
nl='
'
tf=`tempfile
for (f in ``($nl){noroots $* |sed 's/^<<\(.*\)>>$/\1/'}) {
if (~ $f *' '*) {
echo >[1=2] 'Skipping bad root <<'^$^f^'>>'
} else {
d=`{dirname $f} if (! test -d $d) mkdir -p $d
switch (`{basename $f}) {
case *.c *.h; o=(-L'#line %L "%F"%N')
case *.lua; o=(-L'-- %F:%L%N')
case *.sh; o=(-L'# %F:%L%N')
case mkfile; o=(-t8)
case *; o=()
}
notangle $o -R$f $* >$tf
if (! cmp -s $tf $f) {
echo Updating $f
rm -f $f
cat $tf > $f
chmod a-w $f
}
}
}
rm $tf
Loading…
Cancel
Save