-
Notifications
You must be signed in to change notification settings - Fork 13
Packages
Pedro R. Andrade, Raian V. Maretto
This tutorial works with TerraME version 2.0.0-RC4 or greater.
- Introduction
- General Structure
- Package Description
- File Types
- Tests
- Source Code Verification
- Documentation
- Building Package
Contributions to TerraME can be made in the form of packages. A package encapsulates a set of functionalities, models, and (or) data with a given purpose. This report describes the rules for building and validating packages in TerraME. A good way to learn about how to build packages in TerraME is by studying the code of some of the available packages. Additional information about packages can be found in the TerraME documentation, more specifically in UnitTest
and Package
.
A package is stored as a directory. It might contain the following internal directories and files:
- description.lua: Compulsory Lua file that describes the package. See section Package Description for more details.
- license.txt: A file with the license of the package. See some options in http://www.r-project.org/Licenses/.
-
lua: Folder with the source code of the package. The functions in the files of this directory will be available for the simulation when the user loads the package using
import()
orgetPackage()
. See section Types, Models, and Functions for more details. - load.lua: Optional file that describes the order that the files in lua directory will be loaded. If this file does not exist, the files will be loaded in alphabetical order. If lua directory contains internal directories with Lua files, they will not be loaded unless explicitly described in load.lua.
- tests: Folder with unit tests for the package. See section Tests for more details.
- examples: Folder with examples of the package. See section Tests and Documentation for more details.
- config.lua: A configuration file for testing the package. When running tests, if no configuration file is set, TerraME uses this file if it exists.
- data: Folder with files storing data that can be used by tests or examples of the package. These files are typically used to build CellularSpaces, Societies, Neighborhoods, SocialNetworks, or placements, but can have any other usage. It can also have Lua scripts to create TerraView projects.
- data.lua: A lua file that documents all data files. See section Documentation for more details.
- font: Folder with files storing fonts that can be used to draw Agents in Maps.
- font.lua: A lua file that documents all fonts of the package. See section Documentation for more details.
- images: A directory that store image files related to the documentation.
-
log: A directory where signatures for the outputs of
UnitTest:assertSnapshot()
,UnitTest:assertFile()
, and prints from examples are stored. The files can be stored directly within this directory or within internal directories with the name of the respective operational system ("windows"
,"linux"
, and/or"mac"
). When using internal directories, the files directly withinlog
are ignored. - logo.png: An optional image with the logo of the package to be shown in the HTML documentation. If this file does not exist, TerraME's logo will be shown in the HTML files.
All the information about the package is documented in file description.lua
. It is a common Lua file that defines some string variables, with the following semantics:
-
package
: Name of the package. -
title
: Optional title for the HTML documentation of the package. -
version
: Current version of the package. It must be in the form<number>[.<number>]*
. For example:1
,0.2
,2.5.2
. -
date
: Date of the current version. If this argument does not exist, then TerraME will automatically create it when building the package. -
authors
: Name of the author(s) of the package. -
depends
: A comma-separated list of package names which this package depends on. Those packages will be loaded before the current package when require is called. Each package name must be followed by a comment in parentheses specifying a version requirement. The comment must contain a comparison operator, whitespace and a valid version number. For example:"terrame (>= 1.2)"
. -
contact
: E-mail of one or more authors (optional). -
content
: A description of the package. -
license
: Name of the package's license. -
url
: An optional variable with a webpage of the package.
An example of description.lua
is shown below.
package = "base"
version = "2.0"
date = "17 October 2014"
license = "LGPL 3.0"
depends = "terrame (>= 1.2), tube (>= 0.1)"
title = "TerraME Types and Functions"
url = "http://www.terrame.org"
authors = "INPE and TerraLAB/UFOP"
contact = "pedro.andrade@inpe.br, tiago@iceb.ufop.br"
content = "Detailed description of the package"
In a package, source code files group functions with similar purposes. There are three types of files in TerraME:
- Model: File with the definition of a Model. The name of the file must have the same name of the model, plus ".lua". Each Model of a package must be implemented in a different file. Instances of the model can then be implemented in the examples of the package.
-
Type: Define a new type. Files with a function with the same name of the file will be considered as a new type by the package. This function is called constructor. All functions inside this file will be considered as functions related to the objects returned as result of calling the constructor. It means that the first argument of each function will be considered the instance of the new type. Every function belonging to a type must have the first argument named
self
for documentation purposes. - Functions: Describe a set of functions. The name of the file is independent from the names of the functions it contains, but in some way it should summarize its internal content.
In any of the files above, if it contains internal functions that should not be published by the package, such functions need to be declared as local
.
Every global function of the source code must be tested in order to validate the package. In the directory tests
, each file tests the functions of the file with the same name in the source code. Test files must return a table containing functions with names equal to the names of the functions of the respective file in the source code. Each of these functions get a UnitTest
as argument and must have at least one assert. For example, the code below implements tests for functions belong()
and levenshtein()
, declared in the source code.
return{
belong = function(unitTest)
local mvector = {"a", "b", "c", "d"}
unitTest:assert(belong("b", mvector))
unitTest:assert(not belong("e", mvector))
end,
levenshtein = function(unitTest)
unitTest:assertEquals(levenshtein("abv", "abc"), 1)
unitTest:assertEquals(levenshtein("bvaacc", "bcaacac"), 2)
unitTest:assertEquals(levenshtein("xwtaacc", "caacac"), 4)
end
}
Each test function takes as argument an instance of UnitTest
. This argument can be used to execute asserts, verifying results of the functions from the package. See the available asserts in the documentation of UnitTest
.
Functions usually require specific types or values in their arguments. If such requirements are not followed, they must stop the simulation with an error. In the documentation of Package
, there are some useful functions to verify arguments of functions and to produce errors and warnings, such as customError()
, verify()
, and invalidFileExtensionError()
.
It might be interesting to test errors in your package to ensure that the package produces the right error messages. The way to do so is by generating the error in the source code functions and then use UnitTest:assertError()
to check the error message. For example, the first argument of forEachElement()
is mandatory:
function forEachElement(obj, func)
if obj == nil then
mandatoryArgumentError(1)
--- ...
end
Every error function in Package
has one or more associated functions ending with Msg
that return the respective error message. Therefore, to test this error, we can use the code below:
return{
forEachElement = function(unitTest)
local error_func = function()
forEachElement()
end
unitTest:assertError(error_func, mandatoryArgumentMsg(1))
end
}
In the case of warnings, we can use UnitTest:assertWarning()
to capture them. The code executed along an assertWarning()
must execute without any error. Only a single warning must be produced.
If a warning is found along tests outside an assertWarning()
, it will be considered an error.
The internal random number generator of TerraME uses a seed with the current simulation time. It means that, as default, random numbers of two different simulations will not be the same. However, in the tests, it is necessary to have exactly the same random numbers to ensure that the asserts will not fail. Because of that, TerraME automatically sets the seed to zero before any test, to avoid that one test that uses random numbers affects the next ones. In any case, it is possible to set another seed along any test.
Data tests require external files or connections to a DBMS. When the tests use files available in directory data
, it is possible to implement tests using filePath()
, available in TerraME (see its documentation in Package
). For example, the following command creates a CellularSpace
from a CSV file available in the package base
, the core package of TerraME.
cs = CellularSpace{
file = filePath("cs.csv", "base"),
sep = ";"
}
unitTest:assertType(cs, "CellularSpace")
unitTest:assertEquals(2500, #cs)
When the tests use a database connection, they might use file config.lua through getConfig()
(see Utils
) to get information about how to connect to a database. Note that config.lua must be placed in the same directory TerraME will be executed for testing the package. Using this strategy guarantees that the tests could be run in different DBMSs or even in different machines without needing to change the test files. An example of config.lua is shown below:
password = ""
dbType = "mysql"
Using this file, one can create tests such as the one below:
CellularSpace = function(unitTest)
local config = getConfig()
local mdbType = config.dbType
local mhost = config.host
local muser = config.user
local mpassword = config.password
local mport = config.port
local mdatabase = "cabeca"
local cs = CellularSpace{
dbType = mdbType,
host = mhost,
user = muser,
password = mpassword,
port = mport,
database = mdatabase,
theme = "cells90x90"
}
}
The directory log
stores all files that can be used as signatures for some tests. Three situations use such directory:
UnitTest:assertSnapshot()
-
UnitTest:assertFile()
, or - examples that
print()
some output.
In each of these three cases, the tests verify if the file exist in the log/system
directory, where system
is the current operational system (windows
, linux
, or mac
). If yes, it will compare the
output of the test with the signature within the log
directory. Otherwise, it will copy the output to log
and
show an error indicating that the test should be executed again.
Examples describe the main functionalities of a package. They are scripts that must run stand alone, needing to import()
the package itself explicitly.
Examples can contain calls to print()
.
During the tests, some print call in an example, it redirects its output to a file with the same name of the example plus an extension .log
.
After that, it tries to find a file with the same name in the log directory.
If it exists, it checks if both have the same content (the argument tolerance
in the configuration file allows lines to be different).
Otherwise, it will copy the file to log directory and show an error in the report pointing out that the tests must be run again to check if the same output will be produced again.
When the example creates Charts
or Maps
, TerraME automatically uses UnitTest:assertSnapshot()
. In the case of Chart
, it calls assert
in the end of the simulation. For a Map
, it creates an assert when the Map
is created and another in the end of the simulation.
It is also possible to have some difference between the log created when the assert is executed for the first time and
the test by using tolerance
in the configuration file.
It is possible to run only part of the tests, or configure parts of its execution, using a file that might contain at lease one of the following optional variables:
-
directory
: When the package contains lots of functions, it might be interesting to have an internal structure of directories, grouping tests into classes. This argument contains a string or a vector of strings describing the directory(s) to be tested. These strings can describe the name or part of the name of the directory to be tested. For instance, if directory is "basic" then directories such as "basic/core" or "alternative/displaybasic" will be tested. If directory is nil, then all directories will be tested. -
file
: A string or vector of strings with the files to be tested. If nil, all files within the selected directories will be tested. -
test
: A string or vector of strings with the name of the function(s) to be tested. If nil, all tests within the selected files will be tested. It is also possible to set it asfalse
, to run only the examples of the package. -
notest
: A string or vector of strings with the name of the function(s) not to be tested. It cannot be used withtest
. -
examples
: A boolean value, indicating whether the examples should be executed. As default, examples are executed only when all tests are executed. -
lines
: A boolean indicating whether TerraME should check if all the source code lines are executed along the tests. As default, it will not execute this check because it adds too many verifications to the tests. In any case, if there is a test function that the modeler wants to avoid such verification, it is possible to writedebug.sethook()
to avoid source code verification along the execution of such function. TerraME verifies the lines of code only when executing the test functions. Everything declared as global in the test scripts will not be taken into account. -
time
: A boolean value indicating whether TerraME should show the time each test takes. Whentrue
, it adds one more output line for each test. Each test that takes more than five seconds is printed with a yellow background, and with red background when it takes more than 20 seconds. The default value isfalse
. -
tolerance
: A number between zero and one that indicates the error tolerance in the examples. It is valid for both the printed lines and the saved images. In the case of lines, each line has the given tolerance, which means that the number of characters that can be diffferent depends on the size of the line.
The code below shows an example of a config file:
examples = false
directory = "core"
file = {"CellularSpace.lua", "Society.lua"}
test = {"synchronize", "add"}
The command to test a package is:
terrame -package pkg -test
If package pkg
is installed then TerraME runs its tests. If not, then TerraME checks if there is a local directory with this name storing the package. If directory tests does not exist when this command is run then TerraME will automatically create the directory and files according to the source code of the package. It fills each file with test functions, indicating where the modeler should define the tests.
It is also possible to add -color
in order to have a coloured output, which helps to find errors:
terrame -color -package pkg -test
Under Mac and Linux, such colors are automatic in the terminal. Under Windows, you must install a software such as ansicon. To install it, just download, enter in the directory x64
or x86
, according to your computer spec, and then run ansicon -i
.
A configuration file can be used as optional argument, in the following way:
terrame [-color] -package pkg -test test-file.lua
The tool to test packages executes the following verifications:
- Check if the package can be loaded.
- Check if the package prints messages when loading.
- Check if every test function executes correctly, checking its asserts.
- Check if every test function has at least one assert.
- Check if every test function exists in the source code of the package. If the name of a file that contains a set of tests does not match any file in the source code, this verification is skipped for such file. In this sense, there can exist more functions in the tests than in the source code of the package.
- Check if any function creates global variables when executed. Global variables must be created only when the package is loaded.
- Check if the tests execute some
print()
. It is possible toprint()
only in the examples. - Check if error messages point to the test files. This kind of error can occur when the package uses Lua functions
error()
orassert()
directly, instead of the ones available inPackage
, or when there is some unexpected internal error in the package. - Check if all functions of the source code have at least one test.
- Check if every
assert
was executed at least once. It is possible to ignore this verification for specific asserts by adding the wordSKIP
within a comment in such line. Allasserts
within comments must also haveSKIP
to be ignored, as it is understood that in principle asserts should not be commented. - Check if all examples execute correctly. A package must have at least one example to be validated.
Some minor problems in the source code cannot be verified using test functionalities. For example, when one declares a variable and does not use it. As Lua is an interpreted language, it might be possible that the user declared a variable with a name and is using it with a different name, which might produce an error. TerraME allows a package to be verified to find this kind of bug. To execute this functionality just run:
terrame [-color] -package pkg -check
The documentation of TerraME is written using a slightly changed version of LuaDoc. Every non-local function of the package must be documented. LuaDoc looks for the sequence of three minus signs (---
) in the source code of the package. This sequence of characters indicates the beginning of a documented comment. The documentation ends with the first line of code found. The following code defines a function and its documentation.
--- Round a number given its value and a precision.
-- @arg num A number.
-- @arg idp The number of decimal places to be used.
-- @usage round(2.34566, 3)
function round(num, idp)
-- ...
end
The first sentence between ---
and the first period will be the resume. The other sentences until the first tag @
belong to the description of the function. It is possible to use character \
to separate paragraphs in the description of a function.
The documentation above uses two tags:
-- @arg <argument> <text>
Describe function arguments. It requires the name of the argument
and its description (<text>
).
The function above has two arguments, num
and idp
.
-- @usage <text>
Describe an example on how to use the function. It must be a piece of code that can be executed alone. For
instance, when writing a package, it must import the package in the beginning of each usage. It is possible
to avoid running such script when building the documentation if the source code contains the text DONTRUN
.
The usage must also call the documented function, otherwise
an error will be prompted while building the documentation. Usage is optional for deprecated functions.
The corresponding HTML documentation for this function will be:
Note that the function gets arguments using names in the source code, but the arguments are described as positions in the HTML documentation. It is documented this way because the user does not use the name of the arguments, only their position, to call a function.
In the case of functions with named arguments, they have a single table as argument. In this case, the way to document
the parameters is by using <table>.<attribute>
as name for the parameter. See an example below, where the
argument attrs
is a named table with four fields.
--- A second order function to numerically solve ordinary
-- differential equations with a given initial value.
-- @arg attrs.method the name of a numeric algorithm to
-- solve the ordinary differential equations.
-- @arg attrs.equation A differential equation or a vector of equations.
-- @arg attrs.a The beginning of the interval.
-- @arg attrs.b The end of the interval.
-- @usage v = integrate {
-- -- ...
-- }
function integrate(attrs)
-- ...
end
The documentation of this function is shown as follows. Note that attrs.
is not shown in the final documentation.
When a file defines a type, the function with the name of the type documents the type. It will be shown in the beginning of the webpage of the type, followed by the other functions in alphabetical order. For instance:
--- Type to generate random numbers.
-- @arg data.seed A number to generate the pseudo-random numbers.
-- Default is the current time of the system.
-- @usage random = Random()
--
-- random = Random{seed = 0}
function Random(data)
-- ...
end
The arguments are then described as <argument-name>
. In the HTML, we have the following documentation:
The first argument of all functions of the type must be named self
. It means that it will
not be documented, as it will come from the element in the left of the :
in the function call.
For example:
--- Reset the seed to generate random numbers.
-- @arg seed A positive integer number.
-- @usage value = random:reSeed(1)
reSeed = function(self, seed)
-- ...
end
will produce the following HTML documentation:
Files without type are documented with @header
:
-- @header <text>
The <text>
describes the content of a file. This argument does not require the ---
in the beginning of the comment.
Files with functions must never use self
as first argument in any of its functions, otherwise they will be ignored by the documentation.
Models are documented in the same way of named functions. All the
models of a package will be placed in a single webpage.
Functions init()
and check()
should not be documented, as well as any other user-defined function in the file.
Data files are documented in a file called data.lua
. This file must have only calls to data{}
(to document files) or directory{}
(to document directories). data{}
gets the following named arguments:
-
file
: A string (or vector of strings) with the name(s) of the documented file(s). -
summary
: A string describing the data. -
source
: Where the data was taken from. -
image
: A string with a file name (stored inimages
directory of the package) to be displayed in the documentation. -
reference
: A string describing a reference to be cited by those who want to use the file. -
attributes
: A named table, with names representing the attributes of the data and values describing describing the respective attributes. When documenting raster data, bands are described as attributes, using strings in their names. Note that, when the names cannot be used directly, such as1 = ...
(Lua does not allow attribute names starting with numbers), it is necessary to use["1"] = ...
, with the same semantics.
The files within a directory do not need to be documented. Function directory{}
gets the following named arguments:
-
name
: A string with the name of the directory. -
summary
: A string describing the directory. -
source
: Where the data within the directory was taken from. -
reference
: A string describing a reference to be cited by those who want to use the data within the directory.
An example of a data.lua
is shown below:
data{
file = "agents.csv",
summary = "A simple set of four agents",
attributes = {
name = "Name of the agent",
age = "Age of the agent"
}
}
data{
file = {"emas.tif"},
reference = "Almeida, Rodolfo M., et al. (2008) ...",
summary = "Land cover data on Parque Nacional das Emas, Brazil",
attributes = {
["1"] = "A band with the cover type. Zero means forest, one means deforested."
}
}
directory{
name = "test",
summary = "Directory with files used only for internal tests.",
source = "TerraME team"
}
When there is more than one attribute with the same description of a given data, it is possible to group them into a table. For example, a given file with attributes defor2000
, defor2005
, and defor2010
can be documented as follows:
data{
file = "defor.shp",
summary = "Deforestation data",
attributes = {
{["defor2000", "defor2005", "defor2010"]} = "Deforestation for the years 2000, 2005, and 2010."
}
}
TerraME will automatically add more information from such files to the HTML documentation. They are:
- The geometry of the data and the number of objects,
- The geospatial projection, and
- The type of each attribute or band available (for files), and
- The number of files, and
- The available file extensions (within directories).
In data directory, it is also possible to have lua files to create TerraView projects and cellular
layers using terralib
package. One can run all Lua scripts of a package using argument -project
,
on in the button Project
in the graphical interface.
The automatically created files will be documented by TerraME, and must
not belong to data.lua
. Lua files within data directory must also not be documented in
data.lua
.
Font files are documented in a file called font.lua
. This file must have only calls to font{}
, which gets the following named arguments:
-
name
: The name of the font to be used as argumentfont
forMap
. -
file
: A string with the name of the file for the font in directoryfont
. -
source
: Where the data was taken from. -
summary
: A description for the font. -
symbol
: A table mapping symbol names to their values in the font. The names can be used as value to argumentsymbol
forMap
.
An example of a font.lua is shown below:
font {
name = "Pet Animals",
file = "Pet Animals.ttf",
source = "http://www.dafont.com/pet-animals.font",
summary = "Pet animals by Zdravko Andreev, aka Z-Designs.",
symbol = {
fish = 66,
bird = 77,
horse = 78,
pig = 80
}
}
font {
name = "JLS Smiles Sampler",
file = "JLS Smiles Sampler.ttf",
source = "http://www.dafont.com/jls-smiles-sampler.font",
summary = "Font by Michael Adkins & James Stirling.",
symbol = {
smile = 65,
pirate = 74,
skeleton = 86,
mustache = 99
}
}
Every function with named arguments can have HTML tables. It is useful to describe arguments that represent strategies or options, described as strings.
-- @tabular <argument>
Tag @tabular
builds a table for a given <argument>
of a function with named arguments. It is possible to avoid referring to an argument by using NONE
instead of an argument name. Tables are placed in the same order of its declaration. Values of tables are separated by &
(columns) or \
(lines). The first line of a table is recognised as titles, so they will be displayed in bold font. If any of these names have the word arguments
(not case sensitive) then all the elements of the column need to be an argument of the function. Additionally, LuaDoc will check if all arguments of the function are used in this column at least once. The example below describes a @tabular
:
-- @tabular strategy
-- Strategy & Description & Compulsory Arguments &
-- Optional Arguments \
-- "coord" & A bidirected relation. & target & name \
-- "function" & A Neighborhood based on a function. &
-- filter & name \
-- "moore"(default) & Eight touching Cells. & &
-- name, self, wrap \
-- "vonneumann" & Rook Neighborhood. & & name, self, wrap
The table below will then be created:
It is possible to create internal links in the documentation. They can be created using <file-without-.lua>::<function-name>
. The types defined in the package are identified along the documentation and links are automatically created in the HTML documentation. It is also possible to create links in the end of each function:
-- @see <text>
Refers to the documentation of other functions. Every reference in @see
must also follow the standard <file-without-.lua>:<function-name>
.
It is possible to add images to the documentation of examples and models.
-- @image file.bmp
The file needs to be stored in the images
directory of the package.
Deprecated functions are going to be removed from the package in the next versions. It is useful to have them to guarantee back compatibility and warn the user without producing any error at all.
-- @deprecated <link>
The <link>
describes the function that can be used instead of the deprecated one. It can be an internal
link or a description of what the user should use instead of the deprecated function.
Examples are documented using the tag @example
.
-- @example <text>
The <text>
describes the example. This argument does not require the ---
in the beginning of the comment. It is possible to use character \
to separate paragraphs in the description of an example. Examples can also have tags @arg
, describing global values that could be used as arguments for the script.
The command to build the documentation of a package is:
terrame [-color] -package pkg -doc
If package pkg
is installed then TerraME builds its documentation. If not, then TerraME checks if there is a local directory with this name storing the package.
The tool to build the documentation executes the following verifications:
- Check if every global function of the package is documented, as well as functions inside of global tables.
- Check if every non-named argument is documented.
- Check if every
@tabular
uses all available arguments of the function, in the columns that containargument
, and if all elements of@tabular
are arguments of the function. - Check if there is any unnecessary tag in the documentation.
- Check if every global function or functions inside of global tables have an
@usage
, and if each of them contains a call to the function itself. - Check if all examples are documented.
- Check if all data files are documented.
- Check if every internal link is valid.
TerraME will automatically create a directory doc
in the respective package and save the HTML documentation in such directory. It will create the following HTML webpages:
- An index.html, the main webpage describing the package using the content of description.lua. It also contains links to the other webpages.
- One webpage for each file in the directory that contain a type. The beginning of such webpages will describe the constructor of the type. Such files will be put in the group "Types" in the menu bar.
- One webpage for each file that contains functions (with
@header
). - One webpage for the
Models
. - One webpage for all examples.
- One webpage for the data.
To build a package, we need to use the option -build:
terrame [-color] -package <pkg> -build <config> [-clean]
If package pkg
is installed then TerraME builds the package. If not, then TerraME checks if there is a local directory with this name storing the package. When -build
is executed, TerraME will first check if the directory contains the required information, then it builds the documentation, executes all the tests, and finally creates a zip file with the content of the package.
It is possible to have a config
file written in Lua to be used by the tests. This file can have a single value: lines
.
There is also an option in the command to build a package called -clean
, to remove unnecessary files in the output, such as the
directory log
.
If you have comments, doubts or suggestions related to this document, please write a feedback to pedro.andrade <at> inpe.br.
Back to wiki or terrame.org.