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.
|
||||
--------------------------------------------------------------------------
|
@ -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 <<A HREF="mailto:ptp@lysator.liu.se">ptp@lysator.liu.se</A>>
|
||||
</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,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,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,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…
Reference in new issue