summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/doc/lualatex/lua-physical/test
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/doc/lualatex/lua-physical/test')
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua2759
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua36
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua238
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua964
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua314
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua515
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua609
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua95
8 files changed, 5530 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua
new file mode 100644
index 00000000000..d6e5f90fd2b
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua
@@ -0,0 +1,2759 @@
+--[[
+ luaunit.lua
+
+Description: A unit testing framework
+Homepage: https://github.com/bluebird75/luaunit
+Development by Philippe Fremy <phil@freehackers.org>
+Based on initial work of Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit)
+License: BSD License, see LICENSE.txt
+Version: 3.2
+]]--
+
+require("math")
+local M={}
+
+-- private exported functions (for testing)
+M.private = {}
+
+M.VERSION='3.2'
+M._VERSION=M.VERSION -- For LuaUnit v2 compatibility
+
+--[[ Some people like assertEquals( actual, expected ) and some people prefer
+assertEquals( expected, actual ).
+]]--
+M.ORDER_ACTUAL_EXPECTED = true
+M.PRINT_TABLE_REF_IN_ERROR_MSG = false
+M.TABLE_EQUALS_KEYBYCONTENT = true
+M.LINE_LENGTH = 80
+M.TABLE_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items
+M.LIST_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items
+
+--[[ M.EPSILON is meant to help with Lua's floating point math in simple corner
+cases like almostEquals(1.1-0.1, 1), which may not work as-is (e.g. on numbers
+with rational binary representation) if the user doesn't provide some explicit
+error margin.
+
+The default margin used by almostEquals() in such cases is M.EPSILON; and since
+Lua may be compiled with different numeric precisions (single vs. double), we
+try to select a useful default for it dynamically. Note: If the initial value
+is not acceptable, it can be changed by the user to better suit specific needs.
+
+See also: https://en.wikipedia.org/wiki/Machine_epsilon
+]]
+M.EPSILON = 2^-52 -- = machine epsilon for "double", ~2.22E-16
+if math.abs(1.1 - 1 - 0.1) > M.EPSILON then
+ -- rounding error is above EPSILON, assume single precision
+ M.EPSILON = 2^-23 -- = machine epsilon for "float", ~1.19E-07
+end
+
+-- set this to false to debug luaunit
+local STRIP_LUAUNIT_FROM_STACKTRACE = true
+
+M.VERBOSITY_DEFAULT = 10
+M.VERBOSITY_LOW = 1
+M.VERBOSITY_QUIET = 0
+M.VERBOSITY_VERBOSE = 20
+M.DEFAULT_DEEP_ANALYSIS = nil
+M.FORCE_DEEP_ANALYSIS = true
+M.DISABLE_DEEP_ANALYSIS = false
+
+-- set EXPORT_ASSERT_TO_GLOBALS to have all asserts visible as global values
+-- EXPORT_ASSERT_TO_GLOBALS = true
+
+-- we need to keep a copy of the script args before it is overriden
+local cmdline_argv = rawget(_G, "arg")
+
+M.FAILURE_PREFIX = 'LuaUnit test FAILURE: ' -- prefix string for failed tests
+
+M.USAGE=[[Usage: lua <your_test_suite.lua> [options] [testname1 [testname2] ... ]
+Options:
+ -h, --help: Print this help
+ --version: Print version information
+ -v, --verbose: Increase verbosity
+ -q, --quiet: Set verbosity to minimum
+ -e, --error: Stop on first error
+ -f, --failure: Stop on first failure or error
+ -r, --random Run tests in random order
+ -o, --output OUTPUT: Set output type to OUTPUT
+ Possible values: text, tap, junit, nil
+ -n, --name NAME: For junit only, mandatory name of xml file
+ -c, --count NUM: Execute all tests NUM times, e.g. to trig the JIT
+ -p, --pattern PATTERN: Execute all test names matching the Lua PATTERN
+ May be repeated to include severals patterns
+ Make sure you escape magic chars like +? with %
+ -x, --exclude PATTERN: Exclude all test names matching the Lua PATTERN
+ May be repeated to include severals patterns
+ Make sure you escape magic chars like +? with %
+ testname1, testname2, ... : tests to run in the form of testFunction,
+ TestClass or TestClass.testMethod
+]]
+
+local is_equal -- defined here to allow calling from mismatchFormattingPureList
+
+----------------------------------------------------------------
+--
+-- general utility functions
+--
+----------------------------------------------------------------
+
+local function pcall_or_abort(func, ...)
+ -- unpack is a global function for Lua 5.1, otherwise use table.unpack
+ local unpack = rawget(_G, "unpack") or table.unpack
+ local result = {pcall(func, ...)}
+ if not result[1] then
+ -- an error occurred
+ print(result[2]) -- error message
+ print()
+ print(M.USAGE)
+ os.exit(-1)
+ end
+ return unpack(result, 2)
+end
+
+local crossTypeOrdering = {
+ number = 1, boolean = 2, string = 3, table = 4, other = 5
+}
+local crossTypeComparison = {
+ number = function(a, b) return a < b end,
+ string = function(a, b) return a < b end,
+ other = function(a, b) return tostring(a) < tostring(b) end,
+}
+
+local function crossTypeSort(a, b)
+ local type_a, type_b = type(a), type(b)
+ if type_a == type_b then
+ local func = crossTypeComparison[type_a] or crossTypeComparison.other
+ return func(a, b)
+ end
+ type_a = crossTypeOrdering[type_a] or crossTypeOrdering.other
+ type_b = crossTypeOrdering[type_b] or crossTypeOrdering.other
+ return type_a < type_b
+end
+
+local function __genSortedIndex( t )
+ -- Returns a sequence consisting of t's keys, sorted.
+ local sortedIndex = {}
+
+ for key,_ in pairs(t) do
+ table.insert(sortedIndex, key)
+ end
+
+ table.sort(sortedIndex, crossTypeSort)
+ return sortedIndex
+end
+M.private.__genSortedIndex = __genSortedIndex
+
+local function sortedNext(state, control)
+ -- Equivalent of the next() function of table iteration, but returns the
+ -- keys in sorted order (see __genSortedIndex and crossTypeSort).
+ -- The state is a temporary variable during iteration and contains the
+ -- sorted key table (state.sortedIdx). It also stores the last index (into
+ -- the keys) used by the iteration, to find the next one quickly.
+ local key
+
+ --print("sortedNext: control = "..tostring(control) )
+ if control == nil then
+ -- start of iteration
+ state.count = #state.sortedIdx
+ state.lastIdx = 1
+ key = state.sortedIdx[1]
+ return key, state.t[key]
+ end
+
+ -- normally, we expect the control variable to match the last key used
+ if control ~= state.sortedIdx[state.lastIdx] then
+ -- strange, we have to find the next value by ourselves
+ -- the key table is sorted in crossTypeSort() order! -> use bisection
+ local lower, upper = 1, state.count
+ repeat
+ state.lastIdx = math.modf((lower + upper) / 2)
+ key = state.sortedIdx[state.lastIdx]
+ if key == control then
+ break -- key found (and thus prev index)
+ end
+ if crossTypeSort(key, control) then
+ -- key < control, continue search "right" (towards upper bound)
+ lower = state.lastIdx + 1
+ else
+ -- key > control, continue search "left" (towards lower bound)
+ upper = state.lastIdx - 1
+ end
+ until lower > upper
+ if lower > upper then -- only true if the key wasn't found, ...
+ state.lastIdx = state.count -- ... so ensure no match in code below
+ end
+ end
+
+ -- proceed by retrieving the next value (or nil) from the sorted keys
+ state.lastIdx = state.lastIdx + 1
+ key = state.sortedIdx[state.lastIdx]
+ if key then
+ return key, state.t[key]
+ end
+
+ -- getting here means returning `nil`, which will end the iteration
+end
+
+local function sortedPairs(tbl)
+ -- Equivalent of the pairs() function on tables. Allows to iterate in
+ -- sorted order. As required by "generic for" loops, this will return the
+ -- iterator (function), an "invariant state", and the initial control value.
+ -- (see http://www.lua.org/pil/7.2.html)
+ return sortedNext, {t = tbl, sortedIdx = __genSortedIndex(tbl)}, nil
+end
+M.private.sortedPairs = sortedPairs
+
+-- seed the random with a strongly varying seed
+math.randomseed(os.clock()*1E11)
+
+local function randomizeTable( t )
+ -- randomize the item orders of the table t
+ for i = #t, 2, -1 do
+ local j = math.random(i)
+ if i ~= j then
+ t[i], t[j] = t[j], t[i]
+ end
+ end
+end
+M.private.randomizeTable = randomizeTable
+
+local function strsplit(delimiter, text)
+-- Split text into a list consisting of the strings in text, separated
+-- by strings matching delimiter (which may _NOT_ be a pattern).
+-- Example: strsplit(", ", "Anna, Bob, Charlie, Dolores")
+ if delimiter == "" then -- this would result in endless loops
+ error("delimiter matches empty string!")
+ end
+ local list, pos, first, last = {}, 1
+ while true do
+ first, last = text:find(delimiter, pos, true)
+ if first then -- found?
+ table.insert(list, text:sub(pos, first - 1))
+ pos = last + 1
+ else
+ table.insert(list, text:sub(pos))
+ break
+ end
+ end
+ return list
+end
+M.private.strsplit = strsplit
+
+local function hasNewLine( s )
+ -- return true if s has a newline
+ return (string.find(s, '\n', 1, true) ~= nil)
+end
+M.private.hasNewLine = hasNewLine
+
+local function prefixString( prefix, s )
+ -- Prefix all the lines of s with prefix
+ return prefix .. string.gsub(s, '\n', '\n' .. prefix)
+end
+M.private.prefixString = prefixString
+
+local function strMatch(s, pattern, start, final )
+ -- return true if s matches completely the pattern from index start to index end
+ -- return false in every other cases
+ -- if start is nil, matches from the beginning of the string
+ -- if final is nil, matches to the end of the string
+ start = start or 1
+ final = final or string.len(s)
+
+ local foundStart, foundEnd = string.find(s, pattern, start, false)
+ return foundStart == start and foundEnd == final
+end
+M.private.strMatch = strMatch
+
+local function patternFilter(patterns, expr, nil_result)
+ -- Check if any of `patterns` is contained in `expr`. If so, return `true`.
+ -- Return `false` if none of the patterns are contained in expr. If patterns
+ -- is `nil` (= unset), return default value passed in `nil_result`.
+ if patterns ~= nil then
+
+ for _, pattern in ipairs(patterns) do
+ if string.find(expr, pattern) then
+ return true
+ end
+ end
+
+ return false -- no match from patterns
+ end
+
+ return nil_result
+end
+M.private.patternFilter = patternFilter
+
+local function xmlEscape( s )
+ -- Return s escaped for XML attributes
+ -- escapes table:
+ -- " &quot;
+ -- ' &apos;
+ -- < &lt;
+ -- > &gt;
+ -- & &amp;
+
+ return string.gsub( s, '.', {
+ ['&'] = "&amp;",
+ ['"'] = "&quot;",
+ ["'"] = "&apos;",
+ ['<'] = "&lt;",
+ ['>'] = "&gt;",
+ } )
+end
+M.private.xmlEscape = xmlEscape
+
+local function xmlCDataEscape( s )
+ -- Return s escaped for CData section, escapes: "]]>"
+ return string.gsub( s, ']]>', ']]&gt;' )
+end
+M.private.xmlCDataEscape = xmlCDataEscape
+
+local function stripLuaunitTrace( stackTrace )
+ --[[
+ -- Example of a traceback:
+ <<stack traceback:
+ example_with_luaunit.lua:130: in function 'test2_withFailure'
+ ./luaunit.lua:1449: in function <./luaunit.lua:1449>
+ [C]: in function 'xpcall'
+ ./luaunit.lua:1449: in function 'protectedCall'
+ ./luaunit.lua:1508: in function 'execOneFunction'
+ ./luaunit.lua:1596: in function 'runSuiteByInstances'
+ ./luaunit.lua:1660: in function 'runSuiteByNames'
+ ./luaunit.lua:1736: in function 'runSuite'
+ example_with_luaunit.lua:140: in main chunk
+ [C]: in ?>>
+
+ Other example:
+ <<stack traceback:
+ ./luaunit.lua:545: in function 'assertEquals'
+ example_with_luaunit.lua:58: in function 'TestToto.test7'
+ ./luaunit.lua:1517: in function <./luaunit.lua:1517>
+ [C]: in function 'xpcall'
+ ./luaunit.lua:1517: in function 'protectedCall'
+ ./luaunit.lua:1578: in function 'execOneFunction'
+ ./luaunit.lua:1677: in function 'runSuiteByInstances'
+ ./luaunit.lua:1730: in function 'runSuiteByNames'
+ ./luaunit.lua:1806: in function 'runSuite'
+ example_with_luaunit.lua:140: in main chunk
+ [C]: in ?>>
+
+ <<stack traceback:
+ luaunit2/example_with_luaunit.lua:124: in function 'test1_withFailure'
+ luaunit2/luaunit.lua:1532: in function <luaunit2/luaunit.lua:1532>
+ [C]: in function 'xpcall'
+ luaunit2/luaunit.lua:1532: in function 'protectedCall'
+ luaunit2/luaunit.lua:1591: in function 'execOneFunction'
+ luaunit2/luaunit.lua:1679: in function 'runSuiteByInstances'
+ luaunit2/luaunit.lua:1743: in function 'runSuiteByNames'
+ luaunit2/luaunit.lua:1819: in function 'runSuite'
+ luaunit2/example_with_luaunit.lua:140: in main chunk
+ [C]: in ?>>
+
+
+ -- first line is "stack traceback": KEEP
+ -- next line may be luaunit line: REMOVE
+ -- next lines are call in the program under testOk: REMOVE
+ -- next lines are calls from luaunit to call the program under test: KEEP
+
+ -- Strategy:
+ -- keep first line
+ -- remove lines that are part of luaunit
+ -- kepp lines until we hit a luaunit line
+ ]]
+
+ local function isLuaunitInternalLine( s )
+ -- return true if line of stack trace comes from inside luaunit
+ return s:find('[/\\]luaunit%.lua:%d+: ') ~= nil
+ end
+
+ -- print( '<<'..stackTrace..'>>' )
+
+ local t = strsplit( '\n', stackTrace )
+ -- print( prettystr(t) )
+
+ local idx = 2
+
+ -- remove lines that are still part of luaunit
+ while t[idx] and isLuaunitInternalLine( t[idx] ) do
+ -- print('Removing : '..t[idx] )
+ table.remove(t, idx)
+ end
+
+ -- keep lines until we hit luaunit again
+ while t[idx] and (not isLuaunitInternalLine(t[idx])) do
+ -- print('Keeping : '..t[idx] )
+ idx = idx + 1
+ end
+
+ -- remove remaining luaunit lines
+ while t[idx] do
+ -- print('Removing : '..t[idx] )
+ table.remove(t, idx)
+ end
+
+ -- print( prettystr(t) )
+ return table.concat( t, '\n')
+
+end
+M.private.stripLuaunitTrace = stripLuaunitTrace
+
+
+local function prettystr_sub(v, indentLevel, keeponeline, printTableRefs, recursionTable )
+ local type_v = type(v)
+ if "string" == type_v then
+ if keeponeline then
+ v = v:gsub("\n", "\\n") -- escape newline(s)
+ end
+
+ -- use clever delimiters according to content:
+ -- enclose with single quotes if string contains ", but no '
+ if v:find('"', 1, true) and not v:find("'", 1, true) then
+ return "'" .. v .. "'"
+ end
+ -- use double quotes otherwise, escape embedded "
+ return '"' .. v:gsub('"', '\\"') .. '"'
+
+ elseif "table" == type_v then
+ --if v.__class__ then
+ -- return string.gsub( tostring(v), 'table', v.__class__ )
+ --end
+ return M.private._table_tostring(v, indentLevel, keeponeline,
+ printTableRefs, recursionTable)
+
+ elseif "number" == type_v then
+ -- eliminate differences in formatting between various Lua versions
+ if v ~= v then
+ return "#NaN" -- "not a number"
+ end
+ if v == math.huge then
+ return "#Inf" -- "infinite"
+ end
+ if v == -math.huge then
+ return "-#Inf"
+ end
+ if _VERSION == "Lua 5.3" then
+ local i = math.tointeger(v)
+ if i then
+ return tostring(i)
+ end
+ end
+ end
+
+ return tostring(v)
+end
+
+local function prettystr( v, keeponeline )
+ --[[ Better string conversion, to display nice variable content:
+ For strings, if keeponeline is set to true, string is displayed on one line, with visible \n
+ * string are enclosed with " by default, or with ' if string contains a "
+ * if table is a class, display class name
+ * tables are expanded
+ ]]--
+ local recursionTable = {}
+ local s = prettystr_sub(v, 1, keeponeline, M.PRINT_TABLE_REF_IN_ERROR_MSG, recursionTable)
+ if recursionTable.recursionDetected and not M.PRINT_TABLE_REF_IN_ERROR_MSG then
+ -- some table contain recursive references,
+ -- so we must recompute the value by including all table references
+ -- else the result looks like crap
+ recursionTable = {}
+ s = prettystr_sub(v, 1, keeponeline, true, recursionTable)
+ end
+ return s
+end
+M.prettystr = prettystr
+
+local function tryMismatchFormatting( table_a, table_b, doDeepAnalysis )
+ --[[
+ Prepares a nice error message when comparing tables, performing a deeper
+ analysis.
+
+ Arguments:
+ * table_a, table_b: tables to be compared
+ * doDeepAnalysis:
+ M.DEFAULT_DEEP_ANALYSIS: (the default if not specified) perform deep analysis only for big lists and big dictionnaries
+ M.FORCE_DEEP_ANALYSIS : always perform deep analysis
+ M.DISABLE_DEEP_ANALYSIS: never perform deep analysis
+
+ Returns: {success, result}
+ * success: false if deep analysis could not be performed
+ in this case, just use standard assertion message
+ * result: if success is true, a multi-line string with deep analysis of the two lists
+ ]]
+
+ -- check if table_a & table_b are suitable for deep analysis
+ if type(table_a) ~= 'table' or type(table_b) ~= 'table' then
+ return false
+ end
+
+ if doDeepAnalysis == M.DISABLE_DEEP_ANALYSIS then
+ return false
+ end
+
+ local len_a, len_b, isPureList = #table_a, #table_b, true
+
+ for k1, v1 in pairs(table_a) do
+ if type(k1) ~= 'number' or k1 > len_a then
+ -- this table a mapping
+ isPureList = false
+ break
+ end
+ end
+
+ if isPureList then
+ for k2, v2 in pairs(table_b) do
+ if type(k2) ~= 'number' or k2 > len_b then
+ -- this table a mapping
+ isPureList = false
+ break
+ end
+ end
+ end
+
+ if isPureList and math.min(len_a, len_b) < M.LIST_DIFF_ANALYSIS_THRESHOLD then
+ if not (doDeepAnalysis == M.FORCE_DEEP_ANALYSIS) then
+ return false
+ end
+ end
+
+ if isPureList then
+ return M.private.mismatchFormattingPureList( table_a, table_b )
+ else
+ -- only work on mapping for the moment
+ -- return M.private.mismatchFormattingMapping( table_a, table_b, doDeepAnalysis )
+ return false
+ end
+end
+M.private.tryMismatchFormatting = tryMismatchFormatting
+
+local function getTaTbDescr()
+ if not M.ORDER_ACTUAL_EXPECTED then
+ return 'expected', 'actual'
+ end
+ return 'actual', 'expected'
+end
+
+local function extendWithStrFmt( res, ... )
+ table.insert( res, string.format( ... ) )
+end
+
+local function mismatchFormattingMapping( table_a, table_b, doDeepAnalysis )
+ --[[
+ Prepares a nice error message when comparing tables which are not pure lists, performing a deeper
+ analysis.
+
+ Returns: {success, result}
+ * success: false if deep analysis could not be performed
+ in this case, just use standard assertion message
+ * result: if success is true, a multi-line string with deep analysis of the two lists
+ ]]
+
+ -- disable for the moment
+ --[[
+ local result = {}
+ local descrTa, descrTb = getTaTbDescr()
+
+ local keysCommon = {}
+ local keysOnlyTa = {}
+ local keysOnlyTb = {}
+ local keysDiffTaTb = {}
+
+ local k, v
+
+ for k,v in pairs( table_a ) do
+ if is_equal( v, table_b[k] ) then
+ table.insert( keysCommon, k )
+ else
+ if table_b[k] == nil then
+ table.insert( keysOnlyTa, k )
+ else
+ table.insert( keysDiffTaTb, k )
+ end
+ end
+ end
+
+ for k,v in pairs( table_b ) do
+ if not is_equal( v, table_a[k] ) and table_a[k] == nil then
+ table.insert( keysOnlyTb, k )
+ end
+ end
+
+ local len_a = #keysCommon + #keysDiffTaTb + #keysOnlyTa
+ local len_b = #keysCommon + #keysDiffTaTb + #keysOnlyTb
+ local limited_display = (len_a < 5 or len_b < 5)
+
+ if math.min(len_a, len_b) < M.TABLE_DIFF_ANALYSIS_THRESHOLD then
+ return false
+ end
+
+ if not limited_display then
+ if len_a == len_b then
+ extendWithStrFmt( result, 'Table A (%s) and B (%s) both have %d items', descrTa, descrTb, len_a )
+ else
+ extendWithStrFmt( result, 'Table A (%s) has %d items and table B (%s) has %d items', descrTa, len_a, descrTb, len_b )
+ end
+
+ if #keysCommon == 0 and #keysDiffTaTb == 0 then
+ table.insert( result, 'Table A and B have no keys in common, they are totally different')
+ else
+ local s_other = 'other '
+ if #keysCommon then
+ extendWithStrFmt( result, 'Table A and B have %d identical items', #keysCommon )
+ else
+ table.insert( result, 'Table A and B have no identical items' )
+ s_other = ''
+ end
+
+ if #keysDiffTaTb ~= 0 then
+ result[#result] = string.format( '%s and %d items differing present in both tables', result[#result], #keysDiffTaTb)
+ else
+ result[#result] = string.format( '%s and no %sitems differing present in both tables', result[#result], s_other, #keysDiffTaTb)
+ end
+ end
+
+ extendWithStrFmt( result, 'Table A has %d keys not present in table B and table B has %d keys not present in table A', #keysOnlyTa, #keysOnlyTb )
+ end
+
+ local function keytostring(k)
+ if "string" == type(k) and k:match("^[_%a][_%w]*$") then
+ return k
+ end
+ return prettystr(k)
+ end
+
+ if #keysDiffTaTb ~= 0 then
+ table.insert( result, 'Items differing in A and B:')
+ for k,v in sortedPairs( keysDiffTaTb ) do
+ extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) )
+ extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) )
+ end
+ end
+
+ if #keysOnlyTa ~= 0 then
+ table.insert( result, 'Items only in table A:' )
+ for k,v in sortedPairs( keysOnlyTa ) do
+ extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) )
+ end
+ end
+
+ if #keysOnlyTb ~= 0 then
+ table.insert( result, 'Items only in table B:' )
+ for k,v in sortedPairs( keysOnlyTb ) do
+ extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) )
+ end
+ end
+
+ if #keysCommon ~= 0 then
+ table.insert( result, 'Items common to A and B:')
+ for k,v in sortedPairs( keysCommon ) do
+ extendWithStrFmt( result, ' = A and B [%s]: %s', keytostring(v), prettystr(table_a[v]) )
+ end
+ end
+
+ return true, table.concat( result, '\n')
+ ]]
+end
+M.private.mismatchFormattingMapping = mismatchFormattingMapping
+
+local function mismatchFormattingPureList( table_a, table_b )
+ --[[
+ Prepares a nice error message when comparing tables which are lists, performing a deeper
+ analysis.
+
+ Returns: {success, result}
+ * success: false if deep analysis could not be performed
+ in this case, just use standard assertion message
+ * result: if success is true, a multi-line string with deep analysis of the two lists
+ ]]
+ local result, descrTa, descrTb = {}, getTaTbDescr()
+
+ local len_a, len_b, refa, refb = #table_a, #table_b, '', ''
+ if M.PRINT_TABLE_REF_IN_ERROR_MSG then
+ refa, refb = string.format( '<%s> ', tostring(table_a)), string.format('<%s> ', tostring(table_b) )
+ end
+ local longest, shortest = math.max(len_a, len_b), math.min(len_a, len_b)
+ local deltalv = longest - shortest
+
+ local commonUntil = longest
+ for i = 1, longest do
+ if not is_equal(table_a[i], table_b[i]) then
+ commonUntil = i - 1
+ break
+ end
+ end
+
+ local commonBackTo = shortest - 1
+ for i = 0, shortest - 1 do
+ if not is_equal(table_a[len_a-i], table_b[len_b-i]) then
+ commonBackTo = i - 1
+ break
+ end
+ end
+
+
+ table.insert( result, 'List difference analysis:' )
+ if len_a == len_b then
+ -- TODO: handle expected/actual naming
+ extendWithStrFmt( result, '* lists %sA (%s) and %sB (%s) have the same size', refa, descrTa, refb, descrTb )
+ else
+ extendWithStrFmt( result, '* list sizes differ: list %sA (%s) has %d items, list %sB (%s) has %d items', refa, descrTa, len_a, refb, descrTb, len_b )
+ end
+
+ extendWithStrFmt( result, '* lists A and B start differing at index %d', commonUntil+1 )
+ if commonBackTo >= 0 then
+ if deltalv > 0 then
+ extendWithStrFmt( result, '* lists A and B are equal again from index %d for A, %d for B', len_a-commonBackTo, len_b-commonBackTo )
+ else
+ extendWithStrFmt( result, '* lists A and B are equal again from index %d', len_a-commonBackTo )
+ end
+ end
+
+ local function insertABValue(ai, bi)
+ bi = bi or ai
+ if is_equal( table_a[ai], table_b[bi]) then
+ return extendWithStrFmt( result, ' = A[%d], B[%d]: %s', ai, bi, prettystr(table_a[ai]) )
+ else
+ extendWithStrFmt( result, ' - A[%d]: %s', ai, prettystr(table_a[ai]))
+ extendWithStrFmt( result, ' + B[%d]: %s', bi, prettystr(table_b[bi]))
+ end
+ end
+
+ -- common parts to list A & B, at the beginning
+ if commonUntil > 0 then
+ table.insert( result, '* Common parts:' )
+ for i = 1, commonUntil do
+ insertABValue( i )
+ end
+ end
+
+ -- diffing parts to list A & B
+ if commonUntil < shortest - commonBackTo - 1 then
+ table.insert( result, '* Differing parts:' )
+ for i = commonUntil + 1, shortest - commonBackTo - 1 do
+ insertABValue( i )
+ end
+ end
+
+ -- display indexes of one list, with no match on other list
+ if shortest - commonBackTo <= longest - commonBackTo - 1 then
+ table.insert( result, '* Present only in one list:' )
+ for i = shortest - commonBackTo, longest - commonBackTo - 1 do
+ if len_a > len_b then
+ extendWithStrFmt( result, ' - A[%d]: %s', i, prettystr(table_a[i]) )
+ -- table.insert( result, '+ (no matching B index)')
+ else
+ -- table.insert( result, '- no matching A index')
+ extendWithStrFmt( result, ' + B[%d]: %s', i, prettystr(table_b[i]) )
+ end
+ end
+ end
+
+ -- common parts to list A & B, at the end
+ if commonBackTo >= 0 then
+ table.insert( result, '* Common parts at the end of the lists' )
+ for i = longest - commonBackTo, longest do
+ if len_a > len_b then
+ insertABValue( i, i-deltalv )
+ else
+ insertABValue( i-deltalv, i )
+ end
+ end
+ end
+
+ return true, table.concat( result, '\n')
+end
+M.private.mismatchFormattingPureList = mismatchFormattingPureList
+
+local function prettystrPairs(value1, value2, suffix_a, suffix_b)
+ --[[
+ This function helps with the recurring task of constructing the "expected
+ vs. actual" error messages. It takes two arbitrary values and formats
+ corresponding strings with prettystr().
+
+ To keep the (possibly complex) output more readable in case the resulting
+ strings contain line breaks, they get automatically prefixed with additional
+ newlines. Both suffixes are optional (default to empty strings), and get
+ appended to the "value1" string. "suffix_a" is used if line breaks were
+ encountered, "suffix_b" otherwise.
+
+ Returns the two formatted strings (including padding/newlines).
+ ]]
+ local str1, str2 = prettystr(value1), prettystr(value2)
+ if hasNewLine(str1) or hasNewLine(str2) then
+ -- line break(s) detected, add padding
+ return "\n" .. str1 .. (suffix_a or ""), "\n" .. str2
+ end
+ return str1 .. (suffix_b or ""), str2
+end
+M.private.prettystrPairs = prettystrPairs
+
+local TABLE_TOSTRING_SEP = ", "
+local TABLE_TOSTRING_SEP_LEN = string.len(TABLE_TOSTRING_SEP)
+
+
+local function _table_tostring( tbl, indentLevel, keeponeline, printTableRefs, recursionTable )
+ printTableRefs = printTableRefs or M.PRINT_TABLE_REF_IN_ERROR_MSG
+ recursionTable = recursionTable or {}
+ recursionTable[tbl] = true
+
+ local result, dispOnMultLines = {}, false
+
+ -- like prettystr but do not enclose with "" if the string is just alphanumerical
+ -- this is better for displaying table keys who are often simple strings
+ local function keytostring(k)
+ if "string" == type(k) and k:match("^[_%a][_%w]*$") then
+ return k
+ end
+ return prettystr_sub(k, indentLevel+1, true, printTableRefs, recursionTable)
+ end
+
+ local entry, count, seq_index = nil, 0, 1
+ for k, v in sortedPairs( tbl ) do
+ if k == seq_index then
+ -- for the sequential part of tables, we'll skip the "<key>=" output
+ entry = ''
+ seq_index = seq_index + 1
+ elseif recursionTable[k] then
+ -- recursion in the key detected
+ recursionTable.recursionDetected = true
+ entry = "<"..tostring(k)..">="
+ else
+ entry = keytostring(k) .. "="
+ end
+ if recursionTable[v] then
+ -- recursion in the value detected!
+ recursionTable.recursionDetected = true
+ entry = entry .. "<"..tostring(v)..">"
+ else
+ entry = entry ..
+ prettystr_sub( v, indentLevel+1, keeponeline, printTableRefs, recursionTable )
+ end
+ count = count + 1
+ result[count] = entry
+ end
+
+ if not keeponeline then
+ -- set dispOnMultLines if the maximum LINE_LENGTH would be exceeded
+ local totalLength = 0
+ for k, v in ipairs( result ) do
+ totalLength = totalLength + string.len( v )
+ if totalLength >= M.LINE_LENGTH then
+ dispOnMultLines = true
+ break
+ end
+ end
+
+ if not dispOnMultLines then
+ -- adjust with length of separator(s):
+ -- two items need 1 sep, three items two seps, ... plus len of '{}'
+ if count > 0 then
+ totalLength = totalLength + TABLE_TOSTRING_SEP_LEN * (count - 1)
+ end
+ dispOnMultLines = totalLength + 2 >= M.LINE_LENGTH
+ end
+ end
+
+ -- now reformat the result table (currently holding element strings)
+ if dispOnMultLines then
+ local indentString = string.rep(" ", indentLevel - 1)
+ result = {"{\n ", indentString,
+ table.concat(result, ",\n " .. indentString), "\n",
+ indentString, "}"}
+ else
+ result = {"{", table.concat(result, TABLE_TOSTRING_SEP), "}"}
+ end
+ if printTableRefs then
+ table.insert(result, 1, "<"..tostring(tbl).."> ") -- prepend table ref
+ end
+ return table.concat(result)
+end
+M.private._table_tostring = _table_tostring -- prettystr_sub() needs it
+
+local function _table_contains(t, element)
+ if type(t) == "table" then
+ local type_e = type(element)
+ for _, value in pairs(t) do
+ if type(value) == type_e then
+ if value == element then
+ return true
+ end
+ if type_e == 'table' then
+ -- if we wanted recursive items content comparison, we could use
+ -- _is_table_items_equals(v, expected) but one level of just comparing
+ -- items is sufficient
+ if M.private._is_table_equals( value, element ) then
+ return true
+ end
+ end
+ end
+ end
+ end
+ return false
+end
+
+local function _is_table_items_equals(actual, expected )
+ local type_a, type_e = type(actual), type(expected)
+
+ if (type_a == 'table') and (type_e == 'table') then
+ for k, v in pairs(actual) do
+ if not _table_contains(expected, v) then
+ return false
+ end
+ end
+ for k, v in pairs(expected) do
+ if not _table_contains(actual, v) then
+ return false
+ end
+ end
+ return true
+
+ elseif type_a ~= type_e then
+ return false
+
+ elseif actual ~= expected then
+ return false
+ end
+
+ return true
+end
+
+--[[
+This is a specialized metatable to help with the bookkeeping of recursions
+in _is_table_equals(). It provides an __index table that implements utility
+functions for easier management of the table. The "cached" method queries
+the state of a specific (actual,expected) pair; and the "store" method sets
+this state to the given value. The state of pairs not "seen" / visited is
+assumed to be `nil`.
+]]
+local _recursion_cache_MT = {
+ __index = {
+ -- Return the cached value for an (actual,expected) pair (or `nil`)
+ cached = function(t, actual, expected)
+ local subtable = t[actual] or {}
+ return subtable[expected]
+ end,
+
+ -- Store cached value for a specific (actual,expected) pair.
+ -- Returns the value, so it's easy to use for a "tailcall" (return ...).
+ store = function(t, actual, expected, value, asymmetric)
+ local subtable = t[actual]
+ if not subtable then
+ subtable = {}
+ t[actual] = subtable
+ end
+ subtable[expected] = value
+
+ -- Unless explicitly marked "asymmetric": Consider the recursion
+ -- on (expected,actual) to be equivalent to (actual,expected) by
+ -- default, and thus cache the value for both.
+ if not asymmetric then
+ t:store(expected, actual, value, true)
+ end
+
+ return value
+ end
+ }
+}
+
+local function _is_table_equals(actual, expected, recursions)
+ local type_a, type_e = type(actual), type(expected)
+ recursions = recursions or setmetatable({}, _recursion_cache_MT)
+
+ if type_a ~= type_e then
+ return false -- different types won't match
+ end
+
+ if (type_a == 'table') --[[ and (type_e == 'table') ]] then
+ if actual == expected then
+ -- Both reference the same table, so they are actually identical
+ return recursions:store(actual, expected, true)
+ end
+
+ -- If we've tested this (actual,expected) pair before: return cached value
+ local previous = recursions:cached(actual, expected)
+ if previous ~= nil then
+ return previous
+ end
+
+ -- Mark this (actual,expected) pair, so we won't recurse it again. For
+ -- now, assume a "false" result, which we might adjust later if needed.
+ recursions:store(actual, expected, false)
+
+ -- Tables must have identical element count, or they can't match.
+ if (#actual ~= #expected) then
+ return false
+ end
+
+ local actualKeysMatched, actualTableKeys = {}, {}
+
+ for k, v in pairs(actual) do
+ if M.TABLE_EQUALS_KEYBYCONTENT and type(k) == "table" then
+ -- If the keys are tables, things get a bit tricky here as we
+ -- can have _is_table_equals(t[k1], t[k2]) despite k1 ~= k2. So
+ -- we first collect table keys from "actual", and then later try
+ -- to match each table key from "expected" to actualTableKeys.
+ table.insert(actualTableKeys, k)
+ else
+ if not _is_table_equals(v, expected[k], recursions) then
+ return false -- Mismatch on value, tables can't be equal
+ end
+ actualKeysMatched[k] = true -- Keep track of matched keys
+ end
+ end
+
+ for k, v in pairs(expected) do
+ if M.TABLE_EQUALS_KEYBYCONTENT and type(k) == "table" then
+ local found = false
+ -- Note: DON'T use ipairs() here, table may be non-sequential!
+ for i, candidate in pairs(actualTableKeys) do
+ if _is_table_equals(candidate, k, recursions) then
+ if _is_table_equals(actual[candidate], v, recursions) then
+ found = true
+ -- Remove the candidate we matched against from the list
+ -- of table keys, so each key in actual can only match
+ -- one key in expected.
+ actualTableKeys[i] = nil
+ break
+ end
+ -- keys match but values don't, keep searching
+ end
+ end
+ if not found then
+ return false -- no matching (key,value) pair
+ end
+ else
+ if not actualKeysMatched[k] then
+ -- Found a key that we did not see in "actual" -> mismatch
+ return false
+ end
+ -- Otherwise actual[k] was already matched against v = expected[k].
+ end
+ end
+
+ if next(actualTableKeys) then
+ -- If there is any key left in actualTableKeys, then that is
+ -- a table-type key in actual with no matching counterpart
+ -- (in expected), and so the tables aren't equal.
+ return false
+ end
+
+ -- The tables are actually considered equal, update cache and return result
+ return recursions:store(actual, expected, true)
+
+ elseif actual ~= expected then
+ return false
+ end
+
+ return true
+end
+M.private._is_table_equals = _is_table_equals
+is_equal = _is_table_equals
+
+local function failure(msg, level)
+ -- raise an error indicating a test failure
+ -- for error() compatibility we adjust "level" here (by +1), to report the
+ -- calling context
+ error(M.FAILURE_PREFIX .. msg, (level or 1) + 1)
+end
+
+local function fail_fmt(level, ...)
+ -- failure with printf-style formatted message and given error level
+ failure(string.format(...), (level or 1) + 1)
+end
+M.private.fail_fmt = fail_fmt
+
+local function error_fmt(level, ...)
+ -- printf-style error()
+ error(string.format(...), (level or 1) + 1)
+end
+
+----------------------------------------------------------------
+--
+-- assertions
+--
+----------------------------------------------------------------
+
+local function errorMsgEquality(actual, expected, doDeepAnalysis)
+
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected, actual = actual, expected
+ end
+ if type(expected) == 'string' or type(expected) == 'table' then
+ local strExpected, strActual = prettystrPairs(expected, actual)
+ local result = string.format("expected: %s\nactual: %s", strExpected, strActual)
+
+ -- extend with mismatch analysis if possible:
+ local success, mismatchResult
+ success, mismatchResult = tryMismatchFormatting( actual, expected, doDeepAnalysis )
+ if success then
+ result = table.concat( { result, mismatchResult }, '\n' )
+ end
+ return result
+ end
+ return string.format("expected: %s, actual: %s",
+ prettystr(expected), prettystr(actual))
+end
+
+function M.assertError(f, ...)
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ if pcall( f, ... ) then
+ failure( "Expected an error when calling function but no error generated", 2 )
+ end
+end
+
+function M.assertEvalToTrue(value)
+ if not value then
+ failure("expected: a value evaluating to true, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertEvalToFalse(value)
+ if value then
+ failure("expected: false or nil, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertIsTrue(value)
+ if value ~= true then
+ failure("expected: true, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsTrue(value)
+ if value == true then
+ failure("expected: anything but true, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertIsFalse(value)
+ if value ~= false then
+ failure("expected: false, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsFalse(value)
+ if value == false then
+ failure("expected: anything but false, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertIsNil(value)
+ if value ~= nil then
+ failure("expected: nil, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsNil(value)
+ if value == nil then
+ failure("expected non nil value, received nil", 2)
+ end
+end
+
+function M.assertIsNaN(value)
+ if type(value) ~= "number" or value == value then
+ failure("expected: nan, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsNaN(value)
+ if type(value) == "number" and value ~= value then
+ failure("expected non nan value, received nan", 2)
+ end
+end
+
+function M.assertIsInf(value)
+ if type(value) ~= "number" or math.abs(value) ~= math.huge then
+ failure("expected: inf, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsInf(value)
+ if type(value) == "number" and math.abs(value) == math.huge then
+ failure("expected non inf value, received ±inf", 2)
+ end
+end
+
+function M.assertEquals(actual, expected, doDeepAnalysis)
+ if type(actual) == 'table' and type(expected) == 'table' then
+ if not _is_table_equals(actual, expected) then
+ failure( errorMsgEquality(actual, expected, doDeepAnalysis), 2 )
+ end
+ elseif type(actual) ~= type(expected) then
+ failure( errorMsgEquality(actual, expected), 2 )
+ elseif actual ~= expected then
+ failure( errorMsgEquality(actual, expected), 2 )
+ end
+end
+
+function M.almostEquals( actual, expected, margin, margin_boost )
+ if type(actual) ~= 'number' or type(expected) ~= 'number' or type(margin) ~= 'number' then
+ error_fmt(3, 'almostEquals: must supply only number arguments.\nArguments supplied: %s, %s, %s',
+ prettystr(actual), prettystr(expected), prettystr(margin))
+ end
+ if margin < 0 then
+ error('almostEquals: margin must not be negative, current value is ' .. margin, 3)
+ end
+ local realmargin = margin + (margin_boost or M.EPSILON)
+ return math.abs(expected - actual) <= realmargin
+end
+
+function M.assertAlmostEquals( actual, expected, margin )
+ -- check that two floats are close by margin
+ if not M.almostEquals(actual, expected, margin) then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected, actual = actual, expected
+ end
+ fail_fmt(2, 'Values are not almost equal\nExpected: %s with margin of %s, received: %s',
+ expected, margin, actual)
+ end
+end
+
+function M.assertNotEquals(actual, expected)
+ if type(actual) ~= type(expected) then
+ return
+ end
+
+ if type(actual) == 'table' and type(expected) == 'table' then
+ if not _is_table_equals(actual, expected) then
+ return
+ end
+ elseif actual ~= expected then
+ return
+ end
+ fail_fmt(2, 'Received the not expected value: %s', prettystr(actual))
+end
+
+function M.assertNotAlmostEquals( actual, expected, margin )
+ -- check that two floats are not close by margin
+ if M.almostEquals(actual, expected, margin) then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected, actual = actual, expected
+ end
+ fail_fmt(2, 'Values are almost equal\nExpected: %s with a difference above margin of %s, received: %s',
+ expected, margin, actual)
+ end
+end
+
+function M.assertStrContains( str, sub, useRe )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if not string.find(str, sub, 1, not useRe) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, %s %s was not found in string %s',
+ useRe and 'regexp' or 'substring', sub, str)
+ end
+end
+
+function M.assertStrIContains( str, sub )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if not string.find(str:lower(), sub:lower(), 1, true) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, substring %s was not found (case insensitively) in string %s',
+ sub, str)
+ end
+end
+
+function M.assertNotStrContains( str, sub, useRe )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if string.find(str, sub, 1, not useRe) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, %s %s was found in string %s',
+ useRe and 'regexp' or 'substring', sub, str)
+ end
+end
+
+function M.assertNotStrIContains( str, sub )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if string.find(str:lower(), sub:lower(), 1, true) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, substring %s was found (case insensitively) in string %s',
+ sub, str)
+ end
+end
+
+function M.assertStrMatches( str, pattern, start, final )
+ -- Verify a full match for the string
+ -- for a partial match, simply use assertStrContains with useRe set to true
+ if not strMatch( str, pattern, start, final ) then
+ pattern, str = prettystrPairs(pattern, str, '\n')
+ fail_fmt(2, 'Error, pattern %s was not matched by string %s',
+ pattern, str)
+ end
+end
+
+function M.assertErrorMsgEquals( expectedMsg, func, ... )
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ local no_error, error_msg = pcall( func, ... )
+ if no_error then
+ failure( 'No error generated when calling function but expected error: "'..expectedMsg..'"', 2 )
+ end
+
+ if error_msg ~= expectedMsg then
+ error_msg, expectedMsg = prettystrPairs(error_msg, expectedMsg)
+ fail_fmt(2, 'Exact error message expected: %s\nError message received: %s\n',
+ expectedMsg, error_msg)
+ end
+end
+
+function M.assertErrorMsgContains( partialMsg, func, ... )
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ local no_error, error_msg = pcall( func, ... )
+ if no_error then
+ failure( 'No error generated when calling function but expected error containing: '..prettystr(partialMsg), 2 )
+ end
+ if not string.find( error_msg, partialMsg, nil, true ) then
+ error_msg, partialMsg = prettystrPairs(error_msg, partialMsg)
+ fail_fmt(2, 'Error message does not contain: %s\nError message received: %s\n',
+ partialMsg, error_msg)
+ end
+end
+
+function M.assertErrorMsgMatches( expectedMsg, func, ... )
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ local no_error, error_msg = pcall( func, ... )
+ if no_error then
+ failure( 'No error generated when calling function but expected error matching: "'..expectedMsg..'"', 2 )
+ end
+ if not strMatch( error_msg, expectedMsg ) then
+ expectedMsg, error_msg = prettystrPairs(expectedMsg, error_msg)
+ fail_fmt(2, 'Error message does not match: %s\nError message received: %s\n',
+ expectedMsg, error_msg)
+ end
+end
+
+--[[
+Add type assertion functions to the module table M. Each of these functions
+takes a single parameter "value", and checks that its Lua type matches the
+expected string (derived from the function name):
+
+M.assertIsXxx(value) -> ensure that type(value) conforms to "xxx"
+]]
+for _, funcName in ipairs(
+ {'assertIsNumber', 'assertIsString', 'assertIsTable', 'assertIsBoolean',
+ 'assertIsFunction', 'assertIsUserdata', 'assertIsThread'}
+) do
+ local typeExpected = funcName:match("^assertIs([A-Z]%a*)$")
+ -- Lua type() always returns lowercase, also make sure the match() succeeded
+ typeExpected = typeExpected and typeExpected:lower()
+ or error("bad function name '"..funcName.."' for type assertion")
+
+ M[funcName] = function(value)
+ if type(value) ~= typeExpected then
+ fail_fmt(2, 'Expected: a %s value, actual: type %s, value %s',
+ typeExpected, type(value), prettystrPairs(value))
+ end
+ end
+end
+
+--[[
+Add shortcuts for verifying type of a variable, without failure (luaunit v2 compatibility)
+M.isXxx(value) -> returns true if type(value) conforms to "xxx"
+]]
+for _, typeExpected in ipairs(
+ {'Number', 'String', 'Table', 'Boolean',
+ 'Function', 'Userdata', 'Thread', 'Nil' }
+) do
+ local typeExpectedLower = typeExpected:lower()
+ local isType = function(value)
+ return (type(value) == typeExpectedLower)
+ end
+ M['is'..typeExpected] = isType
+ M['is_'..typeExpectedLower] = isType
+end
+
+--[[
+Add non-type assertion functions to the module table M. Each of these functions
+takes a single parameter "value", and checks that its Lua type differs from the
+expected string (derived from the function name):
+
+M.assertNotIsXxx(value) -> ensure that type(value) is not "xxx"
+]]
+for _, funcName in ipairs(
+ {'assertNotIsNumber', 'assertNotIsString', 'assertNotIsTable', 'assertNotIsBoolean',
+ 'assertNotIsFunction', 'assertNotIsUserdata', 'assertNotIsThread'}
+) do
+ local typeUnexpected = funcName:match("^assertNotIs([A-Z]%a*)$")
+ -- Lua type() always returns lowercase, also make sure the match() succeeded
+ typeUnexpected = typeUnexpected and typeUnexpected:lower()
+ or error("bad function name '"..funcName.."' for type assertion")
+
+ M[funcName] = function(value)
+ if type(value) == typeUnexpected then
+ fail_fmt(2, 'Not expected: a %s type, actual: value %s',
+ typeUnexpected, prettystrPairs(value))
+ end
+ end
+end
+
+function M.assertIs(actual, expected)
+ if actual ~= expected then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ actual, expected = expected, actual
+ end
+ expected, actual = prettystrPairs(expected, actual, '\n', ', ')
+ fail_fmt(2, 'Expected object and actual object are not the same\nExpected: %sactual: %s',
+ expected, actual)
+ end
+end
+
+function M.assertNotIs(actual, expected)
+ if actual == expected then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected = actual
+ end
+ fail_fmt(2, 'Expected object and actual object are the same object: %s',
+ prettystrPairs(expected))
+ end
+end
+
+function M.assertItemsEquals(actual, expected)
+ -- checks that the items of table expected
+ -- are contained in table actual. Warning, this function
+ -- is at least O(n^2)
+ if not _is_table_items_equals(actual, expected ) then
+ expected, actual = prettystrPairs(expected, actual)
+ fail_fmt(2, 'Contents of the tables are not identical:\nExpected: %s\nActual: %s',
+ expected, actual)
+ end
+end
+
+----------------------------------------------------------------
+-- Compatibility layer
+----------------------------------------------------------------
+
+-- for compatibility with LuaUnit v2.x
+function M.wrapFunctions()
+ -- In LuaUnit version <= 2.1 , this function was necessary to include
+ -- a test function inside the global test suite. Nowadays, the functions
+ -- are simply run directly as part of the test discovery process.
+ -- so just do nothing !
+ io.stderr:write[[Use of WrapFunctions() is no longer needed.
+Just prefix your test function names with "test" or "Test" and they
+will be picked up and run by LuaUnit.
+]]
+end
+
+local list_of_funcs = {
+ -- { official function name , alias }
+
+ -- general assertions
+ { 'assertEquals' , 'assert_equals' },
+ { 'assertItemsEquals' , 'assert_items_equals' },
+ { 'assertNotEquals' , 'assert_not_equals' },
+ { 'assertAlmostEquals' , 'assert_almost_equals' },
+ { 'assertNotAlmostEquals' , 'assert_not_almost_equals' },
+ { 'assertEvalToTrue' , 'assert_eval_to_true' },
+ { 'assertEvalToFalse' , 'assert_eval_to_false' },
+ { 'assertStrContains' , 'assert_str_contains' },
+ { 'assertStrIContains' , 'assert_str_icontains' },
+ { 'assertNotStrContains' , 'assert_not_str_contains' },
+ { 'assertNotStrIContains' , 'assert_not_str_icontains' },
+ { 'assertStrMatches' , 'assert_str_matches' },
+ { 'assertError' , 'assert_error' },
+ { 'assertErrorMsgEquals' , 'assert_error_msg_equals' },
+ { 'assertErrorMsgContains' , 'assert_error_msg_contains' },
+ { 'assertErrorMsgMatches' , 'assert_error_msg_matches' },
+ { 'assertIs' , 'assert_is' },
+ { 'assertNotIs' , 'assert_not_is' },
+ { 'wrapFunctions' , 'WrapFunctions' },
+ { 'wrapFunctions' , 'wrap_functions' },
+
+ -- type assertions: assertIsXXX -> assert_is_xxx
+ { 'assertIsNumber' , 'assert_is_number' },
+ { 'assertIsString' , 'assert_is_string' },
+ { 'assertIsTable' , 'assert_is_table' },
+ { 'assertIsBoolean' , 'assert_is_boolean' },
+ { 'assertIsNil' , 'assert_is_nil' },
+ { 'assertIsTrue' , 'assert_is_true' },
+ { 'assertIsFalse' , 'assert_is_false' },
+ { 'assertIsNaN' , 'assert_is_nan' },
+ { 'assertIsInf' , 'assert_is_inf' },
+ { 'assertIsFunction' , 'assert_is_function' },
+ { 'assertIsThread' , 'assert_is_thread' },
+ { 'assertIsUserdata' , 'assert_is_userdata' },
+
+ -- type assertions: assertIsXXX -> assertXxx
+ { 'assertIsNumber' , 'assertNumber' },
+ { 'assertIsString' , 'assertString' },
+ { 'assertIsTable' , 'assertTable' },
+ { 'assertIsBoolean' , 'assertBoolean' },
+ { 'assertIsNil' , 'assertNil' },
+ { 'assertIsTrue' , 'assertTrue' },
+ { 'assertIsFalse' , 'assertFalse' },
+ { 'assertIsNaN' , 'assertNaN' },
+ { 'assertIsInf' , 'assertInf' },
+ { 'assertIsFunction' , 'assertFunction' },
+ { 'assertIsThread' , 'assertThread' },
+ { 'assertIsUserdata' , 'assertUserdata' },
+
+ -- type assertions: assertIsXXX -> assert_xxx (luaunit v2 compat)
+ { 'assertIsNumber' , 'assert_number' },
+ { 'assertIsString' , 'assert_string' },
+ { 'assertIsTable' , 'assert_table' },
+ { 'assertIsBoolean' , 'assert_boolean' },
+ { 'assertIsNil' , 'assert_nil' },
+ { 'assertIsTrue' , 'assert_true' },
+ { 'assertIsFalse' , 'assert_false' },
+ { 'assertIsNaN' , 'assert_nan' },
+ { 'assertIsInf' , 'assert_inf' },
+ { 'assertIsFunction' , 'assert_function' },
+ { 'assertIsThread' , 'assert_thread' },
+ { 'assertIsUserdata' , 'assert_userdata' },
+
+ -- type assertions: assertNotIsXXX -> assert_not_is_xxx
+ { 'assertNotIsNumber' , 'assert_not_is_number' },
+ { 'assertNotIsString' , 'assert_not_is_string' },
+ { 'assertNotIsTable' , 'assert_not_is_table' },
+ { 'assertNotIsBoolean' , 'assert_not_is_boolean' },
+ { 'assertNotIsNil' , 'assert_not_is_nil' },
+ { 'assertNotIsTrue' , 'assert_not_is_true' },
+ { 'assertNotIsFalse' , 'assert_not_is_false' },
+ { 'assertNotIsNaN' , 'assert_not_is_nan' },
+ { 'assertNotIsInf' , 'assert_not_is_inf' },
+ { 'assertNotIsFunction' , 'assert_not_is_function' },
+ { 'assertNotIsThread' , 'assert_not_is_thread' },
+ { 'assertNotIsUserdata' , 'assert_not_is_userdata' },
+
+ -- type assertions: assertNotIsXXX -> assertNotXxx (luaunit v2 compat)
+ { 'assertNotIsNumber' , 'assertNotNumber' },
+ { 'assertNotIsString' , 'assertNotString' },
+ { 'assertNotIsTable' , 'assertNotTable' },
+ { 'assertNotIsBoolean' , 'assertNotBoolean' },
+ { 'assertNotIsNil' , 'assertNotNil' },
+ { 'assertNotIsTrue' , 'assertNotTrue' },
+ { 'assertNotIsFalse' , 'assertNotFalse' },
+ { 'assertNotIsNaN' , 'assertNotNaN' },
+ { 'assertNotIsInf' , 'assertNotInf' },
+ { 'assertNotIsFunction' , 'assertNotFunction' },
+ { 'assertNotIsThread' , 'assertNotThread' },
+ { 'assertNotIsUserdata' , 'assertNotUserdata' },
+
+ -- type assertions: assertNotIsXXX -> assert_not_xxx
+ { 'assertNotIsNumber' , 'assert_not_number' },
+ { 'assertNotIsString' , 'assert_not_string' },
+ { 'assertNotIsTable' , 'assert_not_table' },
+ { 'assertNotIsBoolean' , 'assert_not_boolean' },
+ { 'assertNotIsNil' , 'assert_not_nil' },
+ { 'assertNotIsTrue' , 'assert_not_true' },
+ { 'assertNotIsFalse' , 'assert_not_false' },
+ { 'assertNotIsNaN' , 'assert_not_nan' },
+ { 'assertNotIsInf' , 'assert_not_inf' },
+ { 'assertNotIsFunction' , 'assert_not_function' },
+ { 'assertNotIsThread' , 'assert_not_thread' },
+ { 'assertNotIsUserdata' , 'assert_not_userdata' },
+
+ -- all assertions with Coroutine duplicate Thread assertions
+ { 'assertIsThread' , 'assertIsCoroutine' },
+ { 'assertIsThread' , 'assertCoroutine' },
+ { 'assertIsThread' , 'assert_is_coroutine' },
+ { 'assertIsThread' , 'assert_coroutine' },
+ { 'assertNotIsThread' , 'assertNotIsCoroutine' },
+ { 'assertNotIsThread' , 'assertNotCoroutine' },
+ { 'assertNotIsThread' , 'assert_not_is_coroutine' },
+ { 'assertNotIsThread' , 'assert_not_coroutine' },
+}
+
+-- Create all aliases in M
+for _,v in ipairs( list_of_funcs ) do
+ local funcname, alias = v[1], v[2]
+ M[alias] = M[funcname]
+
+ if EXPORT_ASSERT_TO_GLOBALS then
+ _G[funcname] = M[funcname]
+ _G[alias] = M[funcname]
+ end
+end
+
+----------------------------------------------------------------
+--
+-- Outputters
+--
+----------------------------------------------------------------
+
+-- A common "base" class for outputters
+-- For concepts involved (class inheritance) see http://www.lua.org/pil/16.2.html
+
+local genericOutput = { __class__ = 'genericOutput' } -- class
+local genericOutput_MT = { __index = genericOutput } -- metatable
+M.genericOutput = genericOutput -- publish, so that custom classes may derive from it
+
+function genericOutput.new(runner, default_verbosity)
+ -- runner is the "parent" object controlling the output, usually a LuaUnit instance
+ local t = { runner = runner }
+ if runner then
+ t.result = runner.result
+ t.verbosity = runner.verbosity or default_verbosity
+ t.fname = runner.fname
+ else
+ t.verbosity = default_verbosity
+ end
+ return setmetatable( t, genericOutput_MT)
+end
+
+-- abstract ("empty") methods
+function genericOutput:startSuite() end
+function genericOutput:startClass(className) end
+function genericOutput:startTest(testName) end
+function genericOutput:addStatus(node) end
+function genericOutput:endTest(node) end
+function genericOutput:endClass() end
+function genericOutput:endSuite() end
+
+
+----------------------------------------------------------------
+-- class TapOutput
+----------------------------------------------------------------
+
+local TapOutput = genericOutput.new() -- derived class
+local TapOutput_MT = { __index = TapOutput } -- metatable
+TapOutput.__class__ = 'TapOutput'
+
+ -- For a good reference for TAP format, check: http://testanything.org/tap-specification.html
+
+ function TapOutput.new(runner)
+ local t = genericOutput.new(runner, M.VERBOSITY_LOW)
+ return setmetatable( t, TapOutput_MT)
+ end
+ function TapOutput:startSuite()
+ print("1.."..self.result.testCount)
+ print('# Started on '..self.result.startDate)
+ end
+ function TapOutput:startClass(className)
+ if className ~= '[TestFunctions]' then
+ print('# Starting class: '..className)
+ end
+ end
+
+ function TapOutput:addStatus( node )
+ io.stdout:write("not ok ", self.result.currentTestNumber, "\t", node.testName, "\n")
+ if self.verbosity > M.VERBOSITY_LOW then
+ print( prefixString( ' ', node.msg ) )
+ end
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( prefixString( ' ', node.stackTrace ) )
+ end
+ end
+
+ function TapOutput:endTest( node )
+ if node:isPassed() then
+ io.stdout:write("ok ", self.result.currentTestNumber, "\t", node.testName, "\n")
+ end
+ end
+
+ function TapOutput:endSuite()
+ print( '# '..M.LuaUnit.statusLine( self.result ) )
+ return self.result.notPassedCount
+ end
+
+
+-- class TapOutput end
+
+----------------------------------------------------------------
+-- class JUnitOutput
+----------------------------------------------------------------
+
+-- See directory junitxml for more information about the junit format
+local JUnitOutput = genericOutput.new() -- derived class
+local JUnitOutput_MT = { __index = JUnitOutput } -- metatable
+JUnitOutput.__class__ = 'JUnitOutput'
+
+ function JUnitOutput.new(runner)
+ local t = genericOutput.new(runner, M.VERBOSITY_LOW)
+ t.testList = {}
+ return setmetatable( t, JUnitOutput_MT )
+ end
+
+ function JUnitOutput:startSuite()
+ -- open xml file early to deal with errors
+ if self.fname == nil then
+ error('With Junit, an output filename must be supplied with --name!')
+ end
+ if string.sub(self.fname,-4) ~= '.xml' then
+ self.fname = self.fname..'.xml'
+ end
+ self.fd = io.open(self.fname, "w")
+ if self.fd == nil then
+ error("Could not open file for writing: "..self.fname)
+ end
+
+ print('# XML output to '..self.fname)
+ print('# Started on '..self.result.startDate)
+ end
+ function JUnitOutput:startClass(className)
+ if className ~= '[TestFunctions]' then
+ print('# Starting class: '..className)
+ end
+ end
+ function JUnitOutput:startTest(testName)
+ print('# Starting test: '..testName)
+ end
+
+ function JUnitOutput:addStatus( node )
+ if node:isFailure() then
+ print('# Failure: ' .. node.msg)
+ -- print('# ' .. node.stackTrace)
+ elseif node:isError() then
+ print('# Error: ' .. node.msg)
+ -- print('# ' .. node.stackTrace)
+ end
+ end
+
+ function JUnitOutput:endSuite()
+ print( '# '..M.LuaUnit.statusLine(self.result))
+
+ -- XML file writing
+ self.fd:write('<?xml version="1.0" encoding="UTF-8" ?>\n')
+ self.fd:write('<testsuites>\n')
+ self.fd:write(string.format(
+ ' <testsuite name="LuaUnit" id="00001" package="" hostname="localhost" tests="%d" timestamp="%s" time="%0.3f" errors="%d" failures="%d">\n',
+ self.result.runCount, self.result.startIsodate, self.result.duration, self.result.errorCount, self.result.failureCount ))
+ self.fd:write(" <properties>\n")
+ self.fd:write(string.format(' <property name="Lua Version" value="%s"/>\n', _VERSION ) )
+ self.fd:write(string.format(' <property name="LuaUnit Version" value="%s"/>\n', M.VERSION) )
+ -- XXX please include system name and version if possible
+ self.fd:write(" </properties>\n")
+
+ for i,node in ipairs(self.result.tests) do
+ self.fd:write(string.format(' <testcase classname="%s" name="%s" time="%0.3f">\n',
+ node.className, node.testName, node.duration ) )
+ if node:isNotPassed() then
+ self.fd:write(node:statusXML())
+ end
+ self.fd:write(' </testcase>\n')
+ end
+
+ -- Next two lines are needed to validate junit ANT xsd, but really not useful in general:
+ self.fd:write(' <system-out/>\n')
+ self.fd:write(' <system-err/>\n')
+
+ self.fd:write(' </testsuite>\n')
+ self.fd:write('</testsuites>\n')
+ self.fd:close()
+ return self.result.notPassedCount
+ end
+
+
+-- class TapOutput end
+
+----------------------------------------------------------------
+-- class TextOutput
+----------------------------------------------------------------
+
+--[[
+
+-- Python Non verbose:
+
+For each test: . or F or E
+
+If some failed tests:
+ ==============
+ ERROR / FAILURE: TestName (testfile.testclass)
+ ---------
+ Stack trace
+
+
+then --------------
+then "Ran x tests in 0.000s"
+then OK or FAILED (failures=1, error=1)
+
+-- Python Verbose:
+testname (filename.classname) ... ok
+testname (filename.classname) ... FAIL
+testname (filename.classname) ... ERROR
+
+then --------------
+then "Ran x tests in 0.000s"
+then OK or FAILED (failures=1, error=1)
+
+-- Ruby:
+Started
+ .
+ Finished in 0.002695 seconds.
+
+ 1 tests, 2 assertions, 0 failures, 0 errors
+
+-- Ruby:
+>> ruby tc_simple_number2.rb
+Loaded suite tc_simple_number2
+Started
+F..
+Finished in 0.038617 seconds.
+
+ 1) Failure:
+test_failure(TestSimpleNumber) [tc_simple_number2.rb:16]:
+Adding doesn't work.
+<3> expected but was
+<4>.
+
+3 tests, 4 assertions, 1 failures, 0 errors
+
+-- Java Junit
+.......F.
+Time: 0,003
+There was 1 failure:
+1) testCapacity(junit.samples.VectorTest)junit.framework.AssertionFailedError
+ at junit.samples.VectorTest.testCapacity(VectorTest.java:87)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
+
+FAILURES!!!
+Tests run: 8, Failures: 1, Errors: 0
+
+
+-- Maven
+
+# mvn test
+-------------------------------------------------------
+ T E S T S
+-------------------------------------------------------
+Running math.AdditionTest
+Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:
+0.03 sec <<< FAILURE!
+
+Results :
+
+Failed tests:
+ testLireSymbole(math.AdditionTest)
+
+Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
+
+
+-- LuaUnit
+---- non verbose
+* display . or F or E when running tests
+---- verbose
+* display test name + ok/fail
+----
+* blank line
+* number) ERROR or FAILURE: TestName
+ Stack trace
+* blank line
+* number) ERROR or FAILURE: TestName
+ Stack trace
+
+then --------------
+then "Ran x tests in 0.000s (%d not selected, %d skipped)"
+then OK or FAILED (failures=1, error=1)
+
+
+]]
+
+local TextOutput = genericOutput.new() -- derived class
+local TextOutput_MT = { __index = TextOutput } -- metatable
+TextOutput.__class__ = 'TextOutput'
+
+ function TextOutput.new(runner)
+ local t = genericOutput.new(runner, M.VERBOSITY_DEFAULT)
+ t.errorList = {}
+ return setmetatable( t, TextOutput_MT )
+ end
+
+ function TextOutput:startSuite()
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( 'Started on '.. self.result.startDate )
+ end
+ end
+
+ function TextOutput:startTest(testName)
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ io.stdout:write( " ", self.result.currentNode.testName, " ... " )
+ end
+ end
+
+ function TextOutput:endTest( node )
+ if node:isPassed() then
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ io.stdout:write("Ok\n")
+ else
+ io.stdout:write(".")
+ end
+ else
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( node.status )
+ print( node.msg )
+ --[[
+ -- find out when to do this:
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( node.stackTrace )
+ end
+ ]]
+ else
+ -- write only the first character of status
+ io.stdout:write(string.sub(node.status, 1, 1))
+ end
+ end
+ end
+
+ function TextOutput:displayOneFailedTest( index, fail )
+ print(index..") "..fail.testName )
+ print( fail.msg )
+ print( fail.stackTrace )
+ print()
+ end
+
+ function TextOutput:displayFailedTests()
+ if self.result.notPassedCount ~= 0 then
+ print("Failed tests:")
+ print("-------------")
+ for i, v in ipairs(self.result.notPassed) do
+ self:displayOneFailedTest(i, v)
+ end
+ end
+ end
+
+ function TextOutput:endSuite()
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print("=========================================================")
+ else
+ print()
+ end
+ self:displayFailedTests()
+ print( M.LuaUnit.statusLine( self.result ) )
+ if self.result.notPassedCount == 0 then
+ print('OK')
+ end
+ end
+
+-- class TextOutput end
+
+
+----------------------------------------------------------------
+-- class NilOutput
+----------------------------------------------------------------
+
+local function nopCallable()
+ --print(42)
+ return nopCallable
+end
+
+local NilOutput = { __class__ = 'NilOuptut' } -- class
+local NilOutput_MT = { __index = nopCallable } -- metatable
+
+function NilOutput.new(runner)
+ return setmetatable( { __class__ = 'NilOutput' }, NilOutput_MT )
+end
+
+----------------------------------------------------------------
+--
+-- class LuaUnit
+--
+----------------------------------------------------------------
+
+M.LuaUnit = {
+ outputType = TextOutput,
+ verbosity = M.VERBOSITY_DEFAULT,
+ __class__ = 'LuaUnit'
+}
+local LuaUnit_MT = { __index = M.LuaUnit }
+
+if EXPORT_ASSERT_TO_GLOBALS then
+ LuaUnit = M.LuaUnit
+end
+
+ function M.LuaUnit.new()
+ return setmetatable( {}, LuaUnit_MT )
+ end
+
+ -----------------[[ Utility methods ]]---------------------
+
+ function M.LuaUnit.asFunction(aObject)
+ -- return "aObject" if it is a function, and nil otherwise
+ if 'function' == type(aObject) then
+ return aObject
+ end
+ end
+
+ function M.LuaUnit.splitClassMethod(someName)
+ --[[
+ Return a pair of className, methodName strings for a name in the form
+ "class.method". If no class part (or separator) is found, will return
+ nil, someName instead (the latter being unchanged).
+
+ This convention thus also replaces the older isClassMethod() test:
+ You just have to check for a non-nil className (return) value.
+ ]]
+ local separator = string.find(someName, '.', 1, true)
+ if separator then
+ return someName:sub(1, separator - 1), someName:sub(separator + 1)
+ end
+ return nil, someName
+ end
+
+ function M.LuaUnit.isMethodTestName( s )
+ -- return true is the name matches the name of a test method
+ -- default rule is that is starts with 'Test' or with 'test'
+ return string.sub(s, 1, 4):lower() == 'test'
+ end
+
+ function M.LuaUnit.isTestName( s )
+ -- return true is the name matches the name of a test
+ -- default rule is that is starts with 'Test' or with 'test'
+ return string.sub(s, 1, 4):lower() == 'test'
+ end
+
+ function M.LuaUnit.collectTests()
+ -- return a list of all test names in the global namespace
+ -- that match LuaUnit.isTestName
+
+ local testNames = {}
+ for k, _ in pairs(_G) do
+ if type(k) == "string" and M.LuaUnit.isTestName( k ) then
+ table.insert( testNames , k )
+ end
+ end
+ table.sort( testNames )
+ return testNames
+ end
+
+ function M.LuaUnit.parseCmdLine( cmdLine )
+ -- parse the command line
+ -- Supported command line parameters:
+ -- --verbose, -v: increase verbosity
+ -- --quiet, -q: silence output
+ -- --error, -e: treat errors as fatal (quit program)
+ -- --output, -o, + name: select output type
+ -- --pattern, -p, + pattern: run test matching pattern, may be repeated
+ -- --exclude, -x, + pattern: run test not matching pattern, may be repeated
+ -- --random, -r, : run tests in random order
+ -- --name, -n, + fname: name of output file for junit, default to stdout
+ -- --count, -c, + num: number of times to execute each test
+ -- [testnames, ...]: run selected test names
+ --
+ -- Returns a table with the following fields:
+ -- verbosity: nil, M.VERBOSITY_DEFAULT, M.VERBOSITY_QUIET, M.VERBOSITY_VERBOSE
+ -- output: nil, 'tap', 'junit', 'text', 'nil'
+ -- testNames: nil or a list of test names to run
+ -- exeCount: num or 1
+ -- pattern: nil or a list of patterns
+ -- exclude: nil or a list of patterns
+
+ local result, state = {}, nil
+ local SET_OUTPUT = 1
+ local SET_PATTERN = 2
+ local SET_EXCLUDE = 3
+ local SET_FNAME = 4
+ local SET_XCOUNT = 5
+
+ if cmdLine == nil then
+ return result
+ end
+
+ local function parseOption( option )
+ if option == '--help' or option == '-h' then
+ result['help'] = true
+ return
+ elseif option == '--version' then
+ result['version'] = true
+ return
+ elseif option == '--verbose' or option == '-v' then
+ result['verbosity'] = M.VERBOSITY_VERBOSE
+ return
+ elseif option == '--quiet' or option == '-q' then
+ result['verbosity'] = M.VERBOSITY_QUIET
+ return
+ elseif option == '--error' or option == '-e' then
+ result['quitOnError'] = true
+ return
+ elseif option == '--failure' or option == '-f' then
+ result['quitOnFailure'] = true
+ return
+ elseif option == '--random' or option == '-r' then
+ result['randomize'] = true
+ return
+ elseif option == '--output' or option == '-o' then
+ state = SET_OUTPUT
+ return state
+ elseif option == '--name' or option == '-n' then
+ state = SET_FNAME
+ return state
+ elseif option == '--count' or option == '-c' then
+ state = SET_XCOUNT
+ return state
+ elseif option == '--pattern' or option == '-p' then
+ state = SET_PATTERN
+ return state
+ elseif option == '--exclude' or option == '-x' then
+ state = SET_EXCLUDE
+ return state
+ end
+ error('Unknown option: '..option,3)
+ end
+
+ local function setArg( cmdArg, state )
+ if state == SET_OUTPUT then
+ result['output'] = cmdArg
+ return
+ elseif state == SET_FNAME then
+ result['fname'] = cmdArg
+ return
+ elseif state == SET_XCOUNT then
+ result['exeCount'] = tonumber(cmdArg)
+ or error('Malformed -c argument: '..cmdArg)
+ return
+ elseif state == SET_PATTERN then
+ if result['pattern'] then
+ table.insert( result['pattern'], cmdArg )
+ else
+ result['pattern'] = { cmdArg }
+ end
+ return
+ elseif state == SET_EXCLUDE then
+ if result['exclude'] then
+ table.insert( result['exclude'], cmdArg )
+ else
+ result['exclude'] = { cmdArg }
+ end
+ return
+ end
+ error('Unknown parse state: '.. state)
+ end
+
+
+ for i, cmdArg in ipairs(cmdLine) do
+ if state ~= nil then
+ setArg( cmdArg, state, result )
+ state = nil
+ else
+ if cmdArg:sub(1,1) == '-' then
+ state = parseOption( cmdArg )
+ else
+ if result['testNames'] then
+ table.insert( result['testNames'], cmdArg )
+ else
+ result['testNames'] = { cmdArg }
+ end
+ end
+ end
+ end
+
+ if result['help'] then
+ M.LuaUnit.help()
+ end
+
+ if result['version'] then
+ M.LuaUnit.version()
+ end
+
+ if state ~= nil then
+ error('Missing argument after '..cmdLine[ #cmdLine ],2 )
+ end
+
+ return result
+ end
+
+ function M.LuaUnit.help()
+ print(M.USAGE)
+ os.exit(0)
+ end
+
+ function M.LuaUnit.version()
+ print('LuaUnit v'..M.VERSION..' by Philippe Fremy <phil@freehackers.org>')
+ os.exit(0)
+ end
+
+----------------------------------------------------------------
+-- class NodeStatus
+----------------------------------------------------------------
+
+ local NodeStatus = { __class__ = 'NodeStatus' } -- class
+ local NodeStatus_MT = { __index = NodeStatus } -- metatable
+ M.NodeStatus = NodeStatus
+
+ -- values of status
+ NodeStatus.PASS = 'PASS'
+ NodeStatus.FAIL = 'FAIL'
+ NodeStatus.ERROR = 'ERROR'
+
+ function NodeStatus.new( number, testName, className )
+ local t = { number = number, testName = testName, className = className }
+ setmetatable( t, NodeStatus_MT )
+ t:pass()
+ return t
+ end
+
+ function NodeStatus:pass()
+ self.status = self.PASS
+ -- useless but we know it's the field we want to use
+ self.msg = nil
+ self.stackTrace = nil
+ end
+
+ function NodeStatus:fail(msg, stackTrace)
+ self.status = self.FAIL
+ self.msg = msg
+ self.stackTrace = stackTrace
+ end
+
+ function NodeStatus:error(msg, stackTrace)
+ self.status = self.ERROR
+ self.msg = msg
+ self.stackTrace = stackTrace
+ end
+
+ function NodeStatus:isPassed()
+ return self.status == NodeStatus.PASS
+ end
+
+ function NodeStatus:isNotPassed()
+ -- print('hasFailure: '..prettystr(self))
+ return self.status ~= NodeStatus.PASS
+ end
+
+ function NodeStatus:isFailure()
+ return self.status == NodeStatus.FAIL
+ end
+
+ function NodeStatus:isError()
+ return self.status == NodeStatus.ERROR
+ end
+
+ function NodeStatus:statusXML()
+ if self:isError() then
+ return table.concat(
+ {' <error type="', xmlEscape(self.msg), '">\n',
+ ' <![CDATA[', xmlCDataEscape(self.stackTrace),
+ ']]></error>\n'})
+ elseif self:isFailure() then
+ return table.concat(
+ {' <failure type="', xmlEscape(self.msg), '">\n',
+ ' <![CDATA[', xmlCDataEscape(self.stackTrace),
+ ']]></failure>\n'})
+ end
+ return ' <passed/>\n' -- (not XSD-compliant! normally shouldn't get here)
+ end
+
+ --------------[[ Output methods ]]-------------------------
+
+ local function conditional_plural(number, singular)
+ -- returns a grammatically well-formed string "%d <singular/plural>"
+ local suffix = ''
+ if number ~= 1 then -- use plural
+ suffix = (singular:sub(-2) == 'ss') and 'es' or 's'
+ end
+ return string.format('%d %s%s', number, singular, suffix)
+ end
+
+ function M.LuaUnit.statusLine(result)
+ -- return status line string according to results
+ local s = {
+ string.format('Ran %d tests in %0.3f seconds',
+ result.runCount, result.duration),
+ conditional_plural(result.passedCount, 'success'),
+ }
+ if result.notPassedCount > 0 then
+ if result.failureCount > 0 then
+ table.insert(s, conditional_plural(result.failureCount, 'failure'))
+ end
+ if result.errorCount > 0 then
+ table.insert(s, conditional_plural(result.errorCount, 'error'))
+ end
+ else
+ table.insert(s, '0 failures')
+ end
+ if result.nonSelectedCount > 0 then
+ table.insert(s, string.format("%d non-selected", result.nonSelectedCount))
+ end
+ return table.concat(s, ', ')
+ end
+
+ function M.LuaUnit:startSuite(testCount, nonSelectedCount)
+ self.result = {
+ testCount = testCount,
+ nonSelectedCount = nonSelectedCount,
+ passedCount = 0,
+ runCount = 0,
+ currentTestNumber = 0,
+ currentClassName = "",
+ currentNode = nil,
+ suiteStarted = true,
+ startTime = os.clock(),
+ startDate = os.date(os.getenv('LUAUNIT_DATEFMT')),
+ startIsodate = os.date('%Y-%m-%dT%H:%M:%S'),
+ patternIncludeFilter = self.patternIncludeFilter,
+ patternExcludeFilter = self.patternExcludeFilter,
+ tests = {},
+ failures = {},
+ errors = {},
+ notPassed = {},
+ }
+
+ self.outputType = self.outputType or TextOutput
+ self.output = self.outputType.new(self)
+ self.output:startSuite()
+ end
+
+ function M.LuaUnit:startClass( className )
+ self.result.currentClassName = className
+ self.output:startClass( className )
+ end
+
+ function M.LuaUnit:startTest( testName )
+ self.result.currentTestNumber = self.result.currentTestNumber + 1
+ self.result.runCount = self.result.runCount + 1
+ self.result.currentNode = NodeStatus.new(
+ self.result.currentTestNumber,
+ testName,
+ self.result.currentClassName
+ )
+ self.result.currentNode.startTime = os.clock()
+ table.insert( self.result.tests, self.result.currentNode )
+ self.output:startTest( testName )
+ end
+
+ function M.LuaUnit:addStatus( err )
+ -- "err" is expected to be a table / result from protectedCall()
+ if err.status == NodeStatus.PASS then
+ return
+ end
+
+ local node = self.result.currentNode
+
+ --[[ As a first approach, we will report only one error or one failure for one test.
+
+ However, we can have the case where the test is in failure, and the teardown is in error.
+ In such case, it's a good idea to report both a failure and an error in the test suite. This is
+ what Python unittest does for example. However, it mixes up counts so need to be handled carefully: for
+ example, there could be more (failures + errors) count that tests. What happens to the current node ?
+
+ We will do this more intelligent version later.
+ ]]
+
+ -- if the node is already in failure/error, just don't report the new error (see above)
+ if node.status ~= NodeStatus.PASS then
+ return
+ end
+
+ if err.status == NodeStatus.FAIL then
+ node:fail( err.msg, err.trace )
+ table.insert( self.result.failures, node )
+ elseif err.status == NodeStatus.ERROR then
+ node:error( err.msg, err.trace )
+ table.insert( self.result.errors, node )
+ end
+
+ if node:isFailure() or node:isError() then
+ -- add to the list of failed tests (gets printed separately)
+ table.insert( self.result.notPassed, node )
+ end
+ self.output:addStatus( node )
+ end
+
+ function M.LuaUnit:endTest()
+ local node = self.result.currentNode
+ -- print( 'endTest() '..prettystr(node))
+ -- print( 'endTest() '..prettystr(node:isNotPassed()))
+ node.duration = os.clock() - node.startTime
+ node.startTime = nil
+ self.output:endTest( node )
+
+ if node:isPassed() then
+ self.result.passedCount = self.result.passedCount + 1
+ elseif node:isError() then
+ if self.quitOnError or self.quitOnFailure then
+ -- Runtime error - abort test execution as requested by
+ -- "--error" option. This is done by setting a special
+ -- flag that gets handled in runSuiteByInstances().
+ print("\nERROR during LuaUnit test execution:\n" .. node.msg)
+ self.result.aborted = true
+ end
+ elseif node:isFailure() then
+ if self.quitOnFailure then
+ -- Failure - abort test execution as requested by
+ -- "--failure" option. This is done by setting a special
+ -- flag that gets handled in runSuiteByInstances().
+ print("\nFailure during LuaUnit test execution:\n" .. node.msg)
+ self.result.aborted = true
+ end
+ end
+ self.result.currentNode = nil
+ end
+
+ function M.LuaUnit:endClass()
+ self.output:endClass()
+ end
+
+ function M.LuaUnit:endSuite()
+ if self.result.suiteStarted == false then
+ error('LuaUnit:endSuite() -- suite was already ended' )
+ end
+ self.result.duration = os.clock()-self.result.startTime
+ self.result.suiteStarted = false
+
+ -- Expose test counts for outputter's endSuite(). This could be managed
+ -- internally instead, but unit tests (and existing use cases) might
+ -- rely on these fields being present.
+ self.result.notPassedCount = #self.result.notPassed
+ self.result.failureCount = #self.result.failures
+ self.result.errorCount = #self.result.errors
+
+ self.output:endSuite()
+ end
+
+ function M.LuaUnit:setOutputType(outputType)
+ -- default to text
+ -- tap produces results according to TAP format
+ if outputType:upper() == "NIL" then
+ self.outputType = NilOutput
+ return
+ end
+ if outputType:upper() == "TAP" then
+ self.outputType = TapOutput
+ return
+ end
+ if outputType:upper() == "JUNIT" then
+ self.outputType = JUnitOutput
+ return
+ end
+ if outputType:upper() == "TEXT" then
+ self.outputType = TextOutput
+ return
+ end
+ error( 'No such format: '..outputType,2)
+ end
+
+ --------------[[ Runner ]]-----------------
+
+ function M.LuaUnit:protectedCall(classInstance, methodInstance, prettyFuncName)
+ -- if classInstance is nil, this is just a function call
+ -- else, it's method of a class being called.
+
+ local function err_handler(e)
+ -- transform error into a table, adding the traceback information
+ return {
+ status = NodeStatus.ERROR,
+ msg = e,
+ trace = string.sub(debug.traceback("", 3), 2)
+ }
+ end
+
+ local ok, err
+ if classInstance then
+ -- stupid Lua < 5.2 does not allow xpcall with arguments so let's use a workaround
+ ok, err = xpcall( function () methodInstance(classInstance) end, err_handler )
+ else
+ ok, err = xpcall( function () methodInstance() end, err_handler )
+ end
+ if ok then
+ return {status = NodeStatus.PASS}
+ end
+
+ -- determine if the error was a failed test:
+ -- We do this by stripping the failure prefix from the error message,
+ -- while keeping track of the gsub() count. A non-zero value -> failure
+ local failed, iter_msg
+ iter_msg = self.exeCount and 'iteration: '..self.currentCount..', '
+ err.msg, failed = err.msg:gsub(M.FAILURE_PREFIX, iter_msg or '', 1)
+ if failed > 0 then
+ err.status = NodeStatus.FAIL
+ end
+
+ -- reformat / improve the stack trace
+ if prettyFuncName then -- we do have the real method name
+ err.trace = err.trace:gsub("in (%a+) 'methodInstance'", "in %1 '"..prettyFuncName.."'")
+ end
+ if STRIP_LUAUNIT_FROM_STACKTRACE then
+ err.trace = stripLuaunitTrace(err.trace)
+ end
+
+ return err -- return the error "object" (table)
+ end
+
+
+ function M.LuaUnit:execOneFunction(className, methodName, classInstance, methodInstance)
+ -- When executing a test function, className and classInstance must be nil
+ -- When executing a class method, all parameters must be set
+
+ if type(methodInstance) ~= 'function' then
+ error( tostring(methodName)..' must be a function, not '..type(methodInstance))
+ end
+
+ local prettyFuncName
+ if className == nil then
+ className = '[TestFunctions]'
+ prettyFuncName = methodName
+ else
+ prettyFuncName = className..'.'..methodName
+ end
+
+ if self.lastClassName ~= className then
+ if self.lastClassName ~= nil then
+ self:endClass()
+ end
+ self:startClass( className )
+ self.lastClassName = className
+ end
+
+ self:startTest(prettyFuncName)
+
+ local node = self.result.currentNode
+ for iter_n = 1, self.exeCount or 1 do
+ if node:isNotPassed() then
+ break
+ end
+ self.currentCount = iter_n
+
+ -- run setUp first (if any)
+ if classInstance then
+ local func = self.asFunction( classInstance.setUp ) or
+ self.asFunction( classInstance.Setup ) or
+ self.asFunction( classInstance.setup ) or
+ self.asFunction( classInstance.SetUp )
+ if func then
+ self:addStatus(self:protectedCall(classInstance, func, className..'.setUp'))
+ end
+ end
+
+ -- run testMethod()
+ if node:isPassed() then
+ self:addStatus(self:protectedCall(classInstance, methodInstance, prettyFuncName))
+ end
+
+ -- lastly, run tearDown (if any)
+ if classInstance then
+ local func = self.asFunction( classInstance.tearDown ) or
+ self.asFunction( classInstance.TearDown ) or
+ self.asFunction( classInstance.teardown ) or
+ self.asFunction( classInstance.Teardown )
+ if func then
+ self:addStatus(self:protectedCall(classInstance, func, className..'.tearDown'))
+ end
+ end
+ end
+
+ self:endTest()
+ end
+
+ function M.LuaUnit.expandOneClass( result, className, classInstance )
+ --[[
+ Input: a list of { name, instance }, a class name, a class instance
+ Ouptut: modify result to add all test method instance in the form:
+ { className.methodName, classInstance }
+ ]]
+ for methodName, methodInstance in sortedPairs(classInstance) do
+ if M.LuaUnit.asFunction(methodInstance) and M.LuaUnit.isMethodTestName( methodName ) then
+ table.insert( result, { className..'.'..methodName, classInstance } )
+ end
+ end
+ end
+
+ function M.LuaUnit.expandClasses( listOfNameAndInst )
+ --[[
+ -- expand all classes (provided as {className, classInstance}) to a list of {className.methodName, classInstance}
+ -- functions and methods remain untouched
+
+ Input: a list of { name, instance }
+
+ Output:
+ * { function name, function instance } : do nothing
+ * { class.method name, class instance }: do nothing
+ * { class name, class instance } : add all method names in the form of (className.methodName, classInstance)
+ ]]
+ local result = {}
+
+ for i,v in ipairs( listOfNameAndInst ) do
+ local name, instance = v[1], v[2]
+ if M.LuaUnit.asFunction(instance) then
+ table.insert( result, { name, instance } )
+ else
+ if type(instance) ~= 'table' then
+ error( 'Instance must be a table or a function, not a '..type(instance)..', value '..prettystr(instance))
+ end
+ local className, methodName = M.LuaUnit.splitClassMethod( name )
+ if className then
+ local methodInstance = instance[methodName]
+ if methodInstance == nil then
+ error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) )
+ end
+ table.insert( result, { name, instance } )
+ else
+ M.LuaUnit.expandOneClass( result, name, instance )
+ end
+ end
+ end
+
+ return result
+ end
+
+ function M.LuaUnit.applyPatternFilter( patternIncFilter, patternExcFilter, listOfNameAndInst )
+ local included, excluded = {}, {}
+ for i, v in ipairs( listOfNameAndInst ) do
+ -- local name, instance = v[1], v[2]
+ if patternFilter( patternIncFilter, v[1], true ) and
+ not patternFilter( patternExcFilter, v[1], false ) then
+ table.insert( included, v )
+ else
+ table.insert( excluded, v )
+ end
+ end
+ return included, excluded
+ end
+
+ function M.LuaUnit:runSuiteByInstances( listOfNameAndInst )
+ --[[ Run an explicit list of tests. All test instances and names must be supplied.
+ each test must be one of:
+ * { function name, function instance }
+ * { class name, class instance }
+ * { class.method name, class instance }
+ ]]
+
+ local expandedList = self.expandClasses( listOfNameAndInst )
+ if self.randomize then
+ randomizeTable( expandedList )
+ end
+ local filteredList, filteredOutList = self.applyPatternFilter(
+ self.patternIncludeFilter, self.patternExcludeFilter, expandedList )
+
+ self:startSuite( #filteredList, #filteredOutList )
+
+ for i,v in ipairs( filteredList ) do
+ local name, instance = v[1], v[2]
+ if M.LuaUnit.asFunction(instance) then
+ self:execOneFunction( nil, name, nil, instance )
+ else
+ -- expandClasses() should have already taken care of sanitizing the input
+ assert( type(instance) == 'table' )
+ local className, methodName = M.LuaUnit.splitClassMethod( name )
+ assert( className ~= nil )
+ local methodInstance = instance[methodName]
+ assert(methodInstance ~= nil)
+ self:execOneFunction( className, methodName, instance, methodInstance )
+ end
+ if self.result.aborted then
+ break -- "--error" or "--failure" option triggered
+ end
+ end
+
+ if self.lastClassName ~= nil then
+ self:endClass()
+ end
+
+ self:endSuite()
+
+ if self.result.aborted then
+ print("LuaUnit ABORTED (as requested by --error or --failure option)")
+ os.exit(-2)
+ end
+ end
+
+ function M.LuaUnit:runSuiteByNames( listOfName )
+ --[[ Run LuaUnit with a list of generic names, coming either from command-line or from global
+ namespace analysis. Convert the list into a list of (name, valid instances (table or function))
+ and calls runSuiteByInstances.
+ ]]
+
+ local instanceName, instance
+ local listOfNameAndInst = {}
+
+ for i,name in ipairs( listOfName ) do
+ local className, methodName = M.LuaUnit.splitClassMethod( name )
+ if className then
+ instanceName = className
+ instance = _G[instanceName]
+
+ if instance == nil then
+ error( "No such name in global space: "..instanceName )
+ end
+
+ if type(instance) ~= 'table' then
+ error( 'Instance of '..instanceName..' must be a table, not '..type(instance))
+ end
+
+ local methodInstance = instance[methodName]
+ if methodInstance == nil then
+ error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) )
+ end
+
+ else
+ -- for functions and classes
+ instanceName = name
+ instance = _G[instanceName]
+ end
+
+ if instance == nil then
+ error( "No such name in global space: "..instanceName )
+ end
+
+ if (type(instance) ~= 'table' and type(instance) ~= 'function') then
+ error( 'Name must match a function or a table: '..instanceName )
+ end
+
+ table.insert( listOfNameAndInst, { name, instance } )
+ end
+
+ self:runSuiteByInstances( listOfNameAndInst )
+ end
+
+ function M.LuaUnit.run(...)
+ -- Run some specific test classes.
+ -- If no arguments are passed, run the class names specified on the
+ -- command line. If no class name is specified on the command line
+ -- run all classes whose name starts with 'Test'
+ --
+ -- If arguments are passed, they must be strings of the class names
+ -- that you want to run or generic command line arguments (-o, -p, -v, ...)
+
+ local runner = M.LuaUnit.new()
+ return runner:runSuite(...)
+ end
+
+ function M.LuaUnit:runSuite( ... )
+
+ local args = {...}
+ if type(args[1]) == 'table' and args[1].__class__ == 'LuaUnit' then
+ -- run was called with the syntax M.LuaUnit:runSuite()
+ -- we support both M.LuaUnit.run() and M.LuaUnit:run()
+ -- strip out the first argument
+ table.remove(args,1)
+ end
+
+ if #args == 0 then
+ args = cmdline_argv
+ end
+
+ local options = pcall_or_abort( M.LuaUnit.parseCmdLine, args )
+
+ -- We expect these option fields to be either `nil` or contain
+ -- valid values, so it's safe to always copy them directly.
+ self.verbosity = options.verbosity
+ self.quitOnError = options.quitOnError
+ self.quitOnFailure = options.quitOnFailure
+ self.fname = options.fname
+
+ self.exeCount = options.exeCount
+ self.patternIncludeFilter = options.pattern
+ self.patternExcludeFilter = options.exclude
+ self.randomize = options.randomize
+
+ if options.output then
+ if options.output:lower() == 'junit' and options.fname == nil then
+ print('With junit output, a filename must be supplied with -n or --name')
+ os.exit(-1)
+ end
+ pcall_or_abort(self.setOutputType, self, options.output)
+ end
+
+ self:runSuiteByNames( options.testNames or M.LuaUnit.collectTests() )
+
+ return self.result.notPassedCount
+ end
+-- class LuaUnit
+
+-- For compatbility with LuaUnit v2
+M.run = M.LuaUnit.run
+M.Run = M.LuaUnit.run
+
+function M:setVerbosity( verbosity )
+ M.LuaUnit.verbosity = verbosity
+end
+M.set_verbosity = M.setVerbosity
+M.SetVerbosity = M.setVerbosity
+
+
+return M
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua
new file mode 100644
index 00000000000..001b97231b9
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua
@@ -0,0 +1,36 @@
+--[[
+This file contains the unit tests for the physical module.
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+TestDimension = require("testDimension")
+TestUnit = require("testUnit")
+TestQuantity = require("testQuantity")
+TestNumber = require("testNumber")
+TestDefinition = require("testDefinition")
+TestData = require("testData")
+
+
+lu.LuaUnit.verbosity = 2
+os.exit( lu.LuaUnit.run() )
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua
new file mode 100644
index 00000000000..96d6201aa45
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua
@@ -0,0 +1,238 @@
+--[[
+This file contains the unit tests for the physical.Data object.
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+package.path = "../?.lua;" .. package.path
+
+local physical = require("physical")
+
+local Data = physical.Data
+local D = physical.Dimension
+local Q = physical.Quantitiy
+local N = physical.Number
+
+local N = physical.Number
+N.compact = true
+
+function dump(o)
+ if type(o) == 'table' then
+ local s = '{ '
+ for k,v in pairs(o) do
+ if type(k) ~= 'number' then k = '"'..k..'"' end
+ if getmetatable(v) == physical.Unit then
+ s = s .. '['..k..'] = ' .. tostring(v) .. ','
+ else
+ s = s .. '['..k..'] = ' .. dump(v) .. ','
+ end
+ end
+ return s .. '}\n'
+ else
+ return tostring(o)
+ end
+end
+
+
+local function contains(table, val)
+ for i=1,#table do
+ if table[i] == val then
+ return true
+ end
+ end
+ return false
+end
+
+local function count(table)
+ local n = 0
+ for k,v in pairs(table) do
+ n = n + 1
+ end
+ return n
+end
+
+
+TestData = {}
+
+
+-- Test astronomical data
+
+function TestData:testAstronomicalBodies()
+ local bodies = Data.Astronomical()
+ lu.assertTrue( contains(bodies,"Sun") )
+ lu.assertTrue( contains(bodies,"Earth") )
+ lu.assertTrue( contains(bodies,"Ceres") )
+end
+
+function TestData:testAstronomicalKeys()
+ local keys = Data.Astronomical("Earth")
+
+ lu.assertTrue( keys["Mass"] ~= nil )
+ lu.assertTrue( keys["EquatorialRadius"] ~= nil )
+end
+
+function TestData:testSun()
+ local m_s = N(1.9884e30, 2e26) * _kg
+ lu.assertEquals( tostring(Data.Astronomical("Sun","Mass")), tostring(m_s) )
+end
+
+function TestData:testMercury()
+ local m = (N(2.2031870799e13, 8.6e5) * _m^3 * _s^(-2) / _Gc ):to()
+
+ lu.assertEquals( tostring(Data.Astronomical("Mercury","Mass")), tostring(m) )
+end
+
+function TestData:testVenus()
+ local m = Data.Astronomical("Sun","Mass") / N(4.08523719e5,8e-3)
+ lu.assertEquals( tostring(Data.Astronomical("Venus","Mass")), tostring(m) )
+end
+
+function TestData:testEarth()
+ local m = N("5.97220(60)e24") * _kg
+ lu.assertEquals( tostring(Data.Astronomical("Earth","Mass")), tostring(m) )
+end
+
+function TestData:testMoon()
+ local m = Data.Astronomical("Earth","Mass") * N(1.23000371e-2,4e-10)
+ lu.assertEquals( tostring(Data.Astronomical("Moon","Mass")), tostring(m) )
+end
+
+function TestData:testMars()
+ local m = Data.Astronomical("Sun","Mass") / N(3.09870359e6,2e-2)
+
+ lu.assertEquals( tostring(Data.Astronomical("Mars","Mass")), tostring(m) )
+end
+
+function TestData:testJupiter()
+ local m = Data.Astronomical("Sun","Mass") / N(1.047348644e3,1.7e-5)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Jupiter","Mass")) )
+end
+
+function TestData:testSaturn()
+ local m = Data.Astronomical("Sun","Mass") / N(3.4979018e3,1e-4)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Saturn","Mass")) )
+end
+
+function TestData:testUranus()
+ local m = Data.Astronomical("Sun","Mass") / N(2.290298e4,3e-2)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Uranus","Mass")) )
+end
+
+function TestData:testNeptune()
+ local m = Data.Astronomical("Sun","Mass") / N(1.941226e4,3e-2)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Neptune","Mass")) )
+end
+
+function TestData:testPluto()
+ local m = Data.Astronomical("Sun","Mass") / N(1.36566e8,2.8e4)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Pluto","Mass")) )
+end
+
+function TestData:testEris()
+ local m = Data.Astronomical("Sun","Mass") / N(1.191e8,1.4e6)
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Eris","Mass")) )
+end
+
+function TestData:testCeres()
+ local m = N(4.72e-10,3e-12) * Data.Astronomical("Sun","Mass")
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Ceres","Mass")) )
+end
+
+function TestData:testPallas()
+ local m = N(1.03e-10,3e-12) * Data.Astronomical("Sun","Mass")
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Pallas","Mass")) )
+end
+
+function TestData:testVesta()
+ local m = N(1.35e-10,3e-12) * Data.Astronomical("Sun","Mass")
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Vesta","Mass")) )
+end
+
+
+
+-- Test isotope data
+function TestQuantity.isotopeGetByAZError()
+ local Z = Data.Isotope({56,55},"Z")
+end
+function TestData:testIsotopeGetByAZ()
+ lu.assertEquals( Data.Isotope({4,2},"A"), 4 )
+ lu.assertEquals( Data.Isotope({4,2},"Z"), 2 )
+
+ lu.assertTrue( (28667 * _keV):isclose(Data.Isotope({3,3},"MassExcess"),1e-9) )
+
+ lu.assertEquals( Data.Isotope({56,25},"A"), 56 )
+ lu.assertEquals( Data.Isotope({56,25},"Z"), 25 )
+
+ lu.assertTrue( (-2267 * _keV):isclose(Data.Isotope({3,3},"BindingEnergyPerNucleon"),1e-9) )
+
+ lu.assertError( TestQuantity.isotopeGetByAZError )
+end
+
+function TestData:testIsotopeGetAllKeys()
+ local row = Data.Isotope({4,2})
+ lu.assertTrue( row["MassExcess"] ~= nil )
+end
+
+
+function TestData:testIsotopeGetByIsotopeName()
+ lu.assertEquals( Data.Isotope("Helium-5","A"), 5 )
+
+
+ lu.assertEquals( Data.Isotope("Helium-5","Z"), 2 )
+
+ lu.assertEquals( Data.Isotope("Lithium5","A"), 5 )
+ lu.assertEquals( Data.Isotope("Lithium5","Z"), 3 )
+
+ lu.assertEquals( Data.Isotope("5He","A"), 5 )
+ lu.assertEquals( Data.Isotope("5He","Z"), 2 )
+end
+
+function TestData:testIsotopeGetByElementName()
+ local list = Data.Isotope("Fe","symbol")
+
+ lu.assertEquals( count(list), 31 )
+ lu.assertTrue( list["47Fe"] ~= nil )
+ lu.assertTrue( list["56Fe"] ~= nil )
+ lu.assertTrue( list["66Fe"] ~= nil )
+
+ --print(dump(Data.Isotope("He",{"DecayModes","DecayModesIntensity"})))
+end
+
+function TestData:testIsotopeGetAll()
+ local isotopes = Data.Isotope()
+
+ lu.assertTrue( contains(isotopes,"6He") )
+ lu.assertTrue( contains(isotopes,"55K") )
+ lu.assertTrue( contains(isotopes,"209Rn") )
+ lu.assertTrue( contains(isotopes,"295Ei") )
+end
+
+
+return TestData
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua
new file mode 100644
index 00000000000..08efc8bb9df
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua
@@ -0,0 +1,964 @@
+--[[
+This file contains the unit tests for the physical units defined in "defition.lua".
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+package.path = "../?.lua;" .. package.path
+local physical = require("physical")
+local N = physical.Number
+
+
+function dump(o)
+ if type(o) == 'table' then
+ local s = '{ '
+ for k,v in pairs(o) do
+ if type(k) ~= 'number' then k = '"'..k..'"' end
+ if getmetatable(v) == physical.Unit then
+ s = s .. '['..k..'] = ' .. v.symbol .. ','
+ else
+ s = s .. '['..k..'] = ' .. dump(v) .. ','
+ end
+ end
+ return s .. '}\n'
+ else
+ return tostring(o)
+ end
+end
+
+
+TestDefinition = {}
+
+-- SI Derived Units
+function TestDefinition:testGram()
+ lu.assertTrue( 5000 * _g == 5 * _kg)
+ lu.assertTrue( 5000 * _mg == 5 * _g)
+ lu.assertTrue( (1200 * _ng):isclose(1.2 * _ug,1e-10))
+end
+
+function TestDefinition:testHertz()
+ local f = (1.24e6*_Hz):to(_kHz)
+ lu.assertTrue(f == 1.24e3 * _kHz)
+
+ local f = (30/_s):to(_Hz)
+ lu.assertTrue(f == 30*_Hz)
+end
+
+function TestDefinition:testNewton()
+ local F = (30*_kg*5*_m/(2 * _s^2)):to(_N)
+ lu.assertTrue(F == 75 * _N)
+end
+
+function TestDefinition:testPascal()
+ local F = 400000 * _N
+ local A = 2 * _m^2
+ local P = (F/A):to(_Pa)
+ lu.assertTrue(P == 200000 * _Pa)
+end
+
+function TestDefinition:testJoule()
+ local F = 2 * _kN
+ local s = 5 * _mm
+ local W = (F*s):to(_J)
+ lu.assertTrue(W == 10*_J)
+end
+
+function TestDefinition:testWatt()
+ local W = 2 * _MJ
+ local t = 2 * _h
+ local P = (W/t):to(_W)
+ lu.assertTrue(P:isclose(277.777777777777778 * _W, 1e-10))
+end
+
+
+function TestDefinition:testCoulomb()
+ local I = 3.4 * _pA
+ local t = 1 * _min
+ local Q = (I*t):to(_C)
+ lu.assertTrue(Q:isclose(2.04e-10 * _C, 1e-10))
+end
+
+function TestDefinition:testVolt()
+ local E = 45 * _J
+ local Q = 4 * _C
+ local U = (E/Q):to(_V)
+ lu.assertTrue(U:isclose(11.25 * _V, 1e-10))
+end
+
+
+function TestDefinition:testFarad()
+ local Q = 40 * _mC
+ local U = 12 * _V
+ local C = (Q/U):to(_F)
+ lu.assertTrue(C:isclose(0.003333333333333 * _F, 1e-10))
+end
+
+function TestDefinition:testOhm()
+ local I = 30 * _mA
+ local U = 5 * _V
+ local R = (U/I):to(_Ohm)
+ lu.assertTrue(R:isclose(166.666666666666667 * _Ohm, 1e-10))
+end
+
+function TestDefinition:testSiemens()
+ local R = 5 * _kOhm
+ local G = (1/R):to(_S)
+ lu.assertTrue(G:isclose(0.0002 * _S, 1e-10))
+end
+
+function TestDefinition:testWeber()
+ local B = 2.3 * _mT
+ local A = 23 * _cm^2
+ local Phi = (B*A):to(_Wb)
+ lu.assertTrue(Phi:isclose(0.00000529 * _Wb, 1e-10))
+end
+
+function TestDefinition:testTesla()
+ local Phi = 40 * _uWb
+ local A = 78 * _mm^2
+ local B = (Phi/A):to(_T)
+ lu.assertTrue(B:isclose(0.512820512820513 * _T, 1e-10))
+end
+
+function TestDefinition:testHenry()
+ local U = 45 * _mV
+ local I = 20 * _mA
+ local t = 100 * _ms
+ local L = (U/(I/t)):to(_H)
+ lu.assertTrue(L:isclose(0.225*_H, 1e-10))
+end
+
+function TestDefinition:testLumen()
+ local I = 400 * _cd
+ local r = 2.4 * _m
+ local A = 1.2 * _m^2
+ local F = (I*A/r^2):to(_lm)
+ lu.assertTrue(F:isclose(83.333333333333*_lm, 1e-10))
+end
+
+function TestDefinition:testLux()
+ local I = 400 * _cd
+ local r = 2.4 * _m
+ local A = 1.2 * _m^2
+ local E = (I/r^2):to(_lx)
+ lu.assertTrue(E:isclose(69.444444444444*_lx, 1e-10))
+end
+
+function TestDefinition:testBecquerel()
+ local N = 12000
+ local t = 6 * _s
+ local A = (N/t):to(_Bq)
+
+ lu.assertTrue(A:isclose(2000*_Bq, 1e-10))
+end
+
+function TestDefinition:testGray()
+ local E = 12 * _mJ
+ local m = 60 * _kg
+ local D = (E/m):to(_Gy)
+ lu.assertTrue(D:isclose(0.0002*_Gy, 1e-10))
+end
+
+function TestDefinition:testSievert()
+ local E = 20 * _mJ
+ local m = 50 * _kg
+ local H = (E/m):to(_Sv)
+ lu.assertTrue(H:isclose(0.0004*_Sv, 1e-10))
+end
+
+function TestDefinition:testKatal()
+ local n = 20 * _mol
+ local t = 34 * _min
+ local a = (n/t):to(_kat)
+ lu.assertTrue(a:isclose(0.0098039215686275 * _kat, 1e-10))
+end
+
+function TestDefinition:testDegreeCelsius()
+ local theta = 0 * _degC
+ lu.assertTrue( (theta + _degC_0):to(_K) == 273.15 * _K )
+
+ theta = 5*_degC
+ lu.assertTrue( (theta + _degC_0):to(_K) == 278.15*_K )
+
+ theta = _degC*5
+ lu.assertTrue( (theta + _degC_0):to(_K) == 278.15*_K )
+
+ theta = 5*_degC + 3*_degC
+ lu.assertTrue( (theta + _degC_0):to(_K,true) == 281.15*_K )
+
+ local dT = (5 * _degC) / _cm
+ lu.assertTrue( dT == 5 * _K / _cm )
+
+
+ local T_1 = 0 * _degC
+ local T_2 = 1 * _K
+
+ local r = ((T_1 + _degC_0):to(_K)-T_2)/(T_1 + _degC_0)
+
+ r = r:to(_percent)
+ lu.assertTrue( r:isclose(99.63*_percent, 0.1) )
+
+ local c = 1000 * _J/(_kg*_degC)
+ local m = 1 * _g
+ local dT = 20 * _degC
+
+ local Q = ( c * m * dT ):to(_J)
+ lu.assertTrue( Q == 20 * _J )
+
+
+ theta_1 = 110 * _degC
+ T_1 = ( theta_1 + _degC_0 ):to(_K)
+ theta_1 = T_1:to(_degC) - _degC_0
+
+ lu.assertTrue( T_1 == 383.15 * _K )
+ lu.assertTrue( theta_1 == 110 * _degC )
+end
+
+
+-- PHYSICAL CONSTANTS
+function TestDefinition:testSpeedOfLight()
+ local lns = _c * 1 * _ns
+ local l = lns:to(_cm)
+ lu.assertTrue(l:isclose(29.9792458 * _cm, 1e-10))
+end
+
+function TestDefinition:testGravitationalConstant()
+ local m_1 = 20 * _kg
+ local m_2 = 40 * _kg
+ local r = 2 * _m
+ local F_G = (_Gc*m_1*m_2/r^2):to(_N)
+ lu.assertTrue(F_G:isclose(0.0000000133482 * _N, 1e-5))
+end
+
+function TestDefinition:testGravitationalConstant()
+ local m_1 = 20 * _kg
+ local m_2 = 40 * _kg
+ local r = 2 * _m
+ local F_G = (_Gc*m_1*m_2/r^2):to(_N)
+ lu.assertTrue(F_G:isclose(0.0000000133482 * _N, 1e-5))
+end
+
+function TestDefinition:testPlanckConstant()
+ local p = 4.619e-23 * _kg * _m / _s
+ local lambda = (_h_P/p):to(_m)
+ lu.assertTrue(lambda:isclose(0.01434 * _nm, 1e-3))
+end
+
+function TestDefinition:testReducedPlanckConstant()
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+ local hbar = _h_P/(2*Pi)
+ lu.assertTrue(hbar:isclose(_h_Pbar, 1e-3))
+end
+
+function TestDefinition:testElementaryCharge()
+ local N = (150 * _C / _e):to(_1)
+ lu.assertTrue(N:isclose(9.3622e20*_1, 1e-4))
+end
+
+function TestDefinition:testVacuumPermeability()
+ local I = 1.3 * _A
+ local r = 20 * _cm
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local B = (_u_0*I/(2*Pi*r)):to(_T)
+ lu.assertTrue(B:isclose(1.3*_uT, 1e-4))
+end
+
+function TestDefinition:testVacuumPermitivity()
+ local Q_1 = 3 * _nC
+ local Q_2 = 5.5 * _uC
+ local r = 20 * _cm
+
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local F_C = (1/(4*Pi*_e_0)) * Q_1 * Q_2 / r^2
+ F_C = F_C:to(_N)
+ lu.assertTrue(F_C:isclose(0.003707365112549*_N, 1e-4))
+end
+
+function TestDefinition:testAtomicMass()
+ local m = (12*_u):to(_yg)
+ lu.assertTrue(m:isclose(19.926468 * _yg, 1e-4))
+end
+
+function TestDefinition:testElectronMass()
+ local k = (_u/_m_e):to(_1)
+ lu.assertTrue(k:isclose(1822.888 * _1, 1e-4))
+end
+
+function TestDefinition:testProtonMass()
+ local k = (_m_p/_u):to(_1)
+ lu.assertTrue(k:isclose(1.007276 * _1, 1e-4))
+end
+
+function TestDefinition:testNeutronMass()
+ local k = (_m_n/_u):to(_1)
+ lu.assertTrue(k:isclose(1.008665 * _1, 1e-4))
+end
+
+function TestDefinition:testBohrMagneton()
+ local dE = (0.5*2*_u_B*1.2*_T):to(_eV)
+ lu.assertTrue(dE:isclose(6.95e-5 * _eV, 1e-3))
+end
+
+function TestDefinition:testNuclearMagneton()
+ local m_p = 0.5*_e*_h_Pbar/_u_N
+ lu.assertTrue(m_p:isclose(_m_p, 1e-10))
+end
+
+function TestDefinition:testProtonmagneticmoment()
+ lu.assertTrue((2.7928473508*_u_N):isclose(_u_p, 1e-6))
+end
+
+function TestDefinition:testNeutronMagneticMoment()
+ lu.assertTrue((-1.91304272*_u_N):isclose(_u_n, 1e-6))
+end
+
+function TestDefinition:testFineStructureConstant()
+ lu.assertTrue(_alpha:isclose(N(7.2973525664e-3,0.0000000017e-3)*_1, 1e-9))
+end
+
+function TestDefinition:testRydbergConstant()
+ lu.assertTrue(_Ry:isclose(N(1.097373139e7,0.000065e7)/_m, 1e-9))
+end
+
+function TestDefinition:testAvogadrosNumber()
+ lu.assertTrue(_N_A:isclose(N(6.02214076e23,0.00000001e23)/_mol, 1e-9))
+end
+
+function TestDefinition:testBoltzmannConstant()
+ lu.assertTrue(_k_B:isclose(N(1.380649e-23,0.000001e-23)*_J/_K, 1e-9))
+end
+
+function TestDefinition:testGasConstant()
+ lu.assertTrue(_R:isclose(N(8.3144598,0.0000048)*_J/(_K*_mol), 1e-9))
+end
+
+function TestDefinition:testStefanBoltzmannConstant()
+ lu.assertTrue(_sigma:isclose(N(5.6703744191844e-8,0.000013e-8)*_W/(_m^2*_K^4), 1e-6))
+end
+
+function TestDefinition:testStandardGravity()
+ lu.assertTrue(_g_0:isclose(9.80665*_m/_s^2, 1e-6))
+end
+
+function TestDefinition:testNominalSolarRadius()
+ lu.assertTrue(_R_S_nom:isclose(695700*_km, 1e-6))
+end
+
+
+
+-- NON-SI UNITS BUT ACCEPTED FOR USE WITH THE SI
+
+-- Length
+function TestDefinition:testAngstrom()
+ lu.assertTrue( (1e10*_angstrom):isclose(1*_m,1e-6) )
+end
+
+function TestDefinition:testFermi()
+ lu.assertTrue( (23444 * _fermi):isclose(0.23444*_angstrom,1e-6) )
+end
+
+-- Area
+function TestDefinition:testBarn()
+ lu.assertTrue( (38940*_am^2):isclose(0.3894*_mbarn,1e-6) )
+end
+
+function TestDefinition:testAre()
+ lu.assertTrue( (200*_m^2):isclose(2*_are,1e-6) )
+end
+
+function TestDefinition:testHectare()
+ lu.assertTrue( (56000*_m^2):isclose(5.6*_hectare,1e-6) )
+end
+
+-- Volume
+function TestDefinition:testLiter()
+ lu.assertTrue((1000*_L):isclose(1*_m^3, 1e-6))
+ lu.assertTrue((1*_mL):isclose(1*_cm^3, 1e-6))
+end
+
+function TestDefinition:testMetricTeaspoon()
+ lu.assertTrue((_L):isclose(200*_tsp, 1e-6))
+end
+
+function TestDefinition:testMetricTablespoon()
+ lu.assertTrue((3*_L):isclose(200*_Tbsp, 1e-6))
+end
+
+-- Time
+function TestDefinition:testSvedberg()
+ lu.assertTrue((0.56*_ns):isclose(5600*_svedberg, 1e-6))
+end
+function TestDefinition:testMinute()
+ lu.assertTrue((60*_s):isclose(_min, 1e-6))
+end
+
+function TestDefinition:testHour()
+ lu.assertTrue((60*_min):isclose(_h, 1e-6))
+end
+
+function TestDefinition:testDay()
+ lu.assertTrue((24*_h):isclose(_d, 1e-6))
+end
+
+function TestDefinition:testWeek()
+ lu.assertTrue((7*_d):isclose(_wk, 1e-6))
+end
+
+function TestDefinition:testYear()
+ lu.assertTrue((_a):isclose(365*_d+6*_h, 1e-6))
+end
+
+-- Angular
+
+function TestDefinition:testRadian()
+ lu.assertTrue((_rad):isclose(57.295779513082321*_deg, 1e-6))
+end
+
+function TestDefinition:testSteradian()
+ lu.assertTrue((_sr):isclose(_rad^2, 1e-6))
+end
+
+function TestDefinition:testGrad()
+ lu.assertTrue(((5*_deg):to()):isclose(0.087266462599716*_rad, 1e-6))
+end
+
+function TestDefinition:testArcMinute()
+ lu.assertTrue(((2*_deg):to(_arcmin)):isclose(120*_arcmin, 1e-6))
+end
+
+function TestDefinition:testArcSecond()
+ lu.assertTrue(((2*_deg):to(_arcmin)):isclose(120*60*_arcsec, 1e-6))
+end
+
+function TestDefinition:testGon()
+ lu.assertTrue((34.3*_deg):isclose(38.111111111111*_gon, 1e-6))
+end
+
+function TestDefinition:testTurn()
+ lu.assertTrue((720*_deg):isclose(2*_tr, 1e-6))
+end
+
+function TestDefinition:testSpat()
+ lu.assertTrue((_sp):isclose(12.566370614359173*_sr, 1e-6))
+end
+
+
+
+-- Astronomical
+function TestDefinition:testAstronomicalUnit()
+ lu.assertTrue((34.3*_au):isclose(5.131e9*_km, 1e-4))
+end
+
+function TestDefinition:testLightYear()
+ lu.assertTrue(_ly:isclose(63241*_au, 1e-4))
+end
+
+function TestDefinition:testParsec()
+ lu.assertTrue(_pc:isclose(3.262*_ly, 1e-3))
+end
+
+
+--force
+function TestDefinition:testKiloPond()
+ lu.assertTrue(_kp:isclose(9.8*_kg*_m/_s^2, 1e-2))
+end
+
+
+-- Pressure
+function TestDefinition:testBar()
+ lu.assertTrue((1013*_hPa):isclose(1.013*_bar, 1e-6))
+end
+
+function TestDefinition:testStandardAtmosphere()
+ lu.assertTrue((2*_atm):isclose(2.0265*_bar, 1e-6))
+end
+
+function TestDefinition:testTechnicalAtmosphere()
+ lu.assertTrue((2*_at):isclose(1.96*_bar, 1e-3))
+end
+
+function TestDefinition:testMillimeterOfMercury()
+ lu.assertTrue((120*_mmHg):isclose(15999*_Pa, 1e-4))
+end
+
+function TestDefinition:testTorr()
+ lu.assertTrue((120*_mmHg):isclose(120*_Torr, 1e-4))
+end
+
+
+-- Heat
+function TestDefinition:testCalorie()
+ lu.assertTrue((120*_cal):isclose(502.08*_J, 1e-6))
+end
+
+function TestDefinition:testCalorieInternational()
+ lu.assertTrue((120*_cal_IT):isclose(502.416*_J, 1e-6))
+end
+
+function TestDefinition:testGramOfTNT()
+ lu.assertTrue((5*_kg_TNT):isclose(2.092e7*_J, 1e-3))
+end
+
+function TestDefinition:testTonsOfTNT()
+ lu.assertTrue((2*_Mt_TNT):isclose(8.368e15*_J, 1e-3))
+end
+
+
+-- Electrical
+
+function TestDefinition:testVoltAmpere()
+ lu.assertTrue((23*_VA):isclose(23*_W, 1e-3))
+end
+
+function TestDefinition:testAmpereHour()
+ lu.assertTrue((850*_mAh):isclose(3060*_C, 1e-3))
+end
+
+
+-- Information units
+
+function TestDefinition:testBit()
+ lu.assertTrue( (100e12 * _bit):isclose(12500000000*_kB,1e-6) )
+ lu.assertTrue( (_KiB/_s):isclose(8192*_bit/_s,1e-6) )
+end
+
+function TestDefinition:testBitsPerSecond()
+ lu.assertTrue( (_MiB/_s):isclose(8388608*_bps,1e-6) )
+end
+
+function TestDefinition:testByte()
+ local d = 12500000000 * _kB
+ lu.assertTrue( d:isclose(12207031250*_KiB,1e-6) )
+ lu.assertTrue( d:isclose(100000000000*_kbit,1e-6) )
+end
+
+
+-- Others
+
+function TestDefinition:testPercent()
+ local k = (80 * _percent):to()
+ lu.assertEquals( k.value, 0.8)
+end
+
+function TestDefinition:testPermille()
+ local k = (80 * _permille):to()
+ lu.assertEquals( k.value, 0.08)
+end
+
+function TestDefinition:testTonne()
+ lu.assertTrue( (2.3*_t):isclose(2.3e6*_g,1e-6) )
+end
+
+function TestDefinition:testElectronVolt()
+ lu.assertTrue( (200 * _MeV):isclose(3.20435466e-11*_J,1e-6) )
+end
+
+function TestDefinition:testWattHour()
+ lu.assertTrue( (20 * _mWh):isclose(20*3.6*_J,1e-6) )
+end
+
+function TestDefinition:testMetricHorsePower()
+ lu.assertTrue( (200 * _PS):isclose(147100 * _W,1e-4) )
+end
+
+function TestDefinition:testCurie()
+ lu.assertTrue( (3e9 * _Bq):isclose(0.081081081 * _Ci,1e-4) )
+end
+
+function TestDefinition:testRad()
+ lu.assertTrue( (100 * _Rad):isclose(1 * _Gy,1e-4) )
+end
+
+function TestDefinition:testRad()
+ lu.assertTrue( (100 * _rem):isclose(1 * _Sv,1e-4) )
+end
+
+function TestDefinition:testPoiseuille()
+ local r = 2 * _mm
+ local l = 10 * _cm
+ local p = 400 * _Pa
+ local eta = 0.0027 * _Pl
+
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local Q = Pi*p*r^4/(8*eta*l)
+ lu.assertTrue( Q:isclose(9.3084226773031 * _mL/_s,1e-4) )
+end
+
+
+
+
+
+-- IMPERIAL UNITS
+
+-- Length
+function TestDefinition:testInch()
+ lu.assertTrue( (2.2*_m):isclose(86.6 * _in,1e-3) )
+end
+
+function TestDefinition:testThou()
+ lu.assertTrue( (4.3*_th):isclose(0.0043 * _in,1e-3) )
+end
+
+function TestDefinition:testPica()
+ lu.assertTrue( (5*_pica):isclose(0.8333 * _in,1e-3) )
+end
+
+function TestDefinition:testPoint()
+ lu.assertTrue( (72*_pt):isclose(25.4 * _mm,1e-3) )
+end
+
+function TestDefinition:testHand()
+ lu.assertTrue( (3*_hh):isclose(12 * _in,1e-3) )
+end
+
+function TestDefinition:testFoot()
+ lu.assertTrue( (2*_ft):isclose(60.96 * _cm,1e-3) )
+end
+
+function TestDefinition:testYard()
+ lu.assertTrue( (1.5*_yd):isclose(1.3716 * _m,1e-3) )
+end
+
+function TestDefinition:testRod()
+ lu.assertTrue( (22*_rd):isclose(1.106 * _hm,1e-3) )
+end
+
+function TestDefinition:testChain()
+ lu.assertTrue( (0.5*_ch):isclose(0.0100584 * _km,1e-3) )
+end
+
+function TestDefinition:testFurlong()
+ lu.assertTrue( (69*_fur):isclose(13.88 * _km,1e-3) )
+end
+
+function TestDefinition:testMile()
+ lu.assertTrue( (2*_mi):isclose(3.219 * _km,1e-3) )
+end
+
+function TestDefinition:testLeague()
+ lu.assertTrue( (2.2*_lea):isclose(6.6 * _mi,1e-3) )
+end
+
+
+-- International Nautical Units
+function TestDefinition:testNauticalMile()
+ lu.assertTrue( (200*_nmi):isclose(370.4 * _km,1e-4) )
+end
+
+function TestDefinition:testNauticalLeague()
+ lu.assertTrue( (200*_nlea):isclose(1111 * _km,1e-3) )
+end
+
+function TestDefinition:testCables()
+ lu.assertTrue( (23*_cbl):isclose(4262 * _m,1e-3) )
+end
+
+function TestDefinition:testFathom()
+ lu.assertTrue( (12*_ftm):isclose(21.95 * _m,1e-3) )
+end
+
+function TestDefinition:testKnot()
+ lu.assertTrue( (24*_kn):isclose(44.45 * _km/_h,1e-3) )
+end
+
+
+-- Area
+
+function TestDefinition:testAcre()
+ lu.assertTrue( (300*_ac):isclose(1.214e6 * _m^2,1e-3) )
+end
+
+
+
+
+-- Volume
+
+function TestDefinition:testGallon()
+ lu.assertTrue( (23*_L):isclose(5.059 * _gal,1e-3) )
+end
+
+function TestDefinition:testQuart()
+ lu.assertTrue( (3*_hL):isclose(264 * _qt,1e-3) )
+end
+
+function TestDefinition:testPint()
+ lu.assertTrue( (2*_daL):isclose(35.2 * _pint,1e-3) )
+end
+
+function TestDefinition:testCup()
+ lu.assertTrue( (5.5*_cL):isclose(0.194 * _cup,1e-2) )
+end
+
+function TestDefinition:testGill()
+ lu.assertTrue( (3.4*_cL):isclose(0.239 * _gi,1e-2) )
+end
+
+function TestDefinition:testFluidOunce()
+ lu.assertTrue( (23*_mL):isclose(0.8095 * _fl_oz,1e-2) )
+end
+
+function TestDefinition:testFluidDram()
+ lu.assertTrue( (800*_uL):isclose(0.2252 * _fl_dr,1e-2) )
+end
+
+
+
+
+-- Mass (Avoirdupois)
+
+function TestDefinition:testGrain()
+ lu.assertTrue( (34*_g):isclose(524.7 * _gr,1e-3) )
+end
+
+function TestDefinition:testPound()
+ lu.assertTrue( (0.5*_kg):isclose(1.102311310925 * _lb,1e-6) )
+end
+
+function TestDefinition:testOunce()
+ lu.assertTrue( (0.3*_kg):isclose(10.582188584879999 * _oz,1e-6) )
+end
+
+function TestDefinition:testDram()
+ lu.assertTrue( (45*_g):isclose(25.397252603684997 * _dr,1e-6) )
+end
+
+function TestDefinition:testStone()
+ lu.assertTrue( (34*_kg):isclose(5.354083510212 * _st,1e-6) )
+end
+
+function TestDefinition:testQuarter()
+ lu.assertTrue( (300*_kg):isclose(23.62095666267 * _qtr,1e-6) )
+end
+
+function TestDefinition:testHundredWeight()
+ lu.assertTrue( (0.5*_t):isclose(9.8420652761 * _cwt,1e-6) )
+end
+
+function TestDefinition:testLongTon()
+ lu.assertTrue( (45*_t):isclose(44.289293742495* _ton,1e-6) )
+end
+
+
+-- Mass (Troy)
+
+function TestDefinition:testTroyPound()
+ lu.assertTrue( (0.6*_kg):isclose(1.607537328432 * _lb_t,1e-6) )
+end
+
+function TestDefinition:testTroyOunce()
+ lu.assertTrue( (0.2*_kg):isclose(6.43014931372 * _oz_t,1e-6) )
+end
+
+function TestDefinition:testPennyWeight()
+ lu.assertTrue( (78*_g):isclose(50.155164647094004 * _dwt,1e-6) )
+end
+
+
+-- Mass (Others)
+
+function TestDefinition:testFirkin()
+ lu.assertTrue( (3*_kg):isclose(0.11810478331319998 * _fir,1e-6) )
+end
+
+
+-- Time
+function TestDefinition:testSennight()
+ lu.assertTrue( (3*_sen):isclose(21 * _d,1e-6) )
+end
+
+function TestDefinition:testFortnight()
+ lu.assertTrue( (2*_ftn):isclose(28 * _d,1e-6) )
+end
+
+-- Temperature
+function TestDefinition:testFahrenheit()
+ local theta = -457.87*_degF
+ lu.assertTrue( (theta + _degF_0):to(_K):isclose(1*_K, 0.001) )
+
+ -- the isclose function treats temperature units as differences
+ local theta_1 = 32 * _degF
+ local theta_2 = 0 * _degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+
+ local theta_1 = 69.8*_degF
+ local theta_2 = 21*_degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+
+ local theta_1 = 98.6*_degF
+ local theta_2 = 37*_degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+
+
+ local theta_1 = 212.0*_degF
+ local theta_2 = 100*_degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+end
+
+
+-- Others
+
+function TestDefinition:testPoundForce()
+ lu.assertTrue( (12*_lbf):isclose(53.3787 * _N,1e-4) )
+end
+
+function TestDefinition:testPoundal()
+ lu.assertTrue( (99*_pdl):isclose(13.6872 * _N,1e-4) )
+end
+
+function TestDefinition:testSlug()
+ lu.assertTrue( (0.5*_slug):isclose(7296.95 * _g,1e-4) )
+end
+
+function TestDefinition:testPsi()
+ lu.assertTrue( (29.0075*_psi):isclose(2 * _bar,1e-4) )
+end
+
+function TestDefinition:testThermochemicalBritishThermalUnit()
+ lu.assertTrue( (_BTU):isclose( _kcal*_lb*_degF/(_kg*_K) ,1e-8) )
+end
+
+function TestDefinition:testInternationalBritishThermalUnit()
+ lu.assertTrue( (_BTU_it):isclose( _kcal_IT*_lb*_degF/(_kg*_K) ,1e-8) )
+end
+
+function TestDefinition:testHorsepower()
+ lu.assertTrue( (700*_hp):isclose( 521990*_W ,1e-4) )
+end
+
+
+-- US CUSTOMARY UNITS
+-- ******************
+
+-- Length
+function TestDefinition:testUSinch()
+ lu.assertTrue( (12333*_in_US):isclose( 12333.024666*_in ,1e-6) )
+end
+
+function TestDefinition:testUShand()
+ lu.assertTrue( (3*_hh_US):isclose( 12.00002400005*_in ,1e-9) )
+end
+
+function TestDefinition:testUSFoot()
+ lu.assertTrue( (12*_ft_US):isclose( 144.0002880006*_in ,1e-9) )
+end
+
+function TestDefinition:testUSLink()
+ lu.assertTrue( (56*_li_US):isclose( 11.265430530872*_m ,1e-7) )
+end
+
+function TestDefinition:testUSYard()
+ lu.assertTrue( (3937*_yd_US):isclose( 3600*_m ,1e-9) )
+end
+
+function TestDefinition:testUSRod()
+ lu.assertTrue( (74*_rd_US):isclose( 372.16154432*_m ,1e-10) )
+end
+
+function TestDefinition:testUSChain()
+ lu.assertTrue( (0.5*_ch_US):isclose( 100.58420117*_dm ,1e-10) )
+end
+
+function TestDefinition:testUSFurlong()
+ lu.assertTrue( (56*_fur_US):isclose( 11.265430531*_km ,1e-10) )
+end
+
+function TestDefinition:testUSMile()
+ lu.assertTrue( (2.4*_mi_US):isclose( 3.8624333249*_km ,1e-10) )
+end
+
+function TestDefinition:testUSLeague()
+ lu.assertTrue( (5.5*_lea_US):isclose( 26.554229108*_km ,1e-10) )
+end
+
+function TestDefinition:testUSFathom()
+ lu.assertTrue( (5.5*_ftm_US):isclose( 100.58420117*_dm ,1e-10) )
+end
+
+function TestDefinition:testUSCable()
+ lu.assertTrue( (45*_cbl_US):isclose( 987.55*_dam ,1e-5) )
+end
+
+-- Area
+function TestDefinition:testUSAcre()
+ lu.assertTrue( (45*_ac_US):isclose( 182109.26744*_m^2 ,1e-9) )
+end
+
+-- Volume
+function TestDefinition:testUSGallon()
+ lu.assertTrue( (2*_gal_US):isclose( 1.665348369257978 * _gal ,1e-9) )
+ lu.assertTrue( (2*_gal_US):isclose( 7.570823568*_L ,1e-9) )
+end
+
+function TestDefinition:testUSQuart()
+ lu.assertTrue( (78*_qt_US):isclose( 738.15529788 * _dL ,1e-9) )
+end
+
+function TestDefinition:testUSPint()
+ lu.assertTrue( (46*_pint_US):isclose( 2.1766117758 * _daL ,1e-9) )
+end
+
+function TestDefinition:testUSCup()
+ lu.assertTrue( (2*_cup_US):isclose( 31.5450982 * _Tbsp ,1e-9) )
+end
+
+function TestDefinition:testUSGill()
+ lu.assertTrue( (45*_gi_US):isclose( 5.3232353437499995 * _L ,1e-7) )
+end
+
+function TestDefinition:testUSFluidOunce()
+ lu.assertTrue( (22*_fl_oz_US):isclose( 650.617653125 * _mL ,1e-7) )
+end
+
+function TestDefinition:testUSTablespoon()
+ lu.assertTrue( (10*_Tbsp_US):isclose( 147.867648438 * _mL ,1e-7) )
+end
+
+function TestDefinition:testUSTeaspoon()
+ lu.assertTrue( (0.5*_tsp_US):isclose( 2464.46080729 * _uL ,1e-7) )
+end
+
+function TestDefinition:testUSFluidDram()
+ lu.assertTrue( (1*_fl_dr_US):isclose( 3.6966911953125 * _mL ,1e-7) )
+end
+
+-- Mass
+
+function TestDefinition:testUSQuarter()
+ lu.assertTrue( (23*_qtr_US):isclose( 260.81561275 * _kg ,1e-7) )
+end
+
+function TestDefinition:testUSHunderdWeight()
+ lu.assertTrue( (1.4*_cwt_US):isclose( 63.5029318 * _kg ,1e-7) )
+end
+
+function TestDefinition:testUSTon()
+ lu.assertTrue( (2*_ton_US):isclose( 1.81436948 * _t ,1e-7) )
+end
+
+return TestDefinition
+
+
+
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua
new file mode 100644
index 00000000000..44d2840322e
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua
@@ -0,0 +1,314 @@
+--[[
+This file contains the unit tests for the physical.Dimension class.
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+package.path = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local D = physical.Dimension
+
+local L = D("L")
+local M = D("M")
+local T = D("T")
+local I = D("I")
+local K = D("K")
+local N = D("N")
+local J = D("J")
+
+
+TestDimension = {} --class
+
+
+-- Dimension.new(o=nil)
+function TestDimension:testEmptyConstructor()
+ local d = D()
+ lu.assertTrue( d:iszero() )
+end
+
+function TestDimension:testConstructorByString()
+ local d = D("Dimensionless")
+ lu.assertTrue( d:iszero() )
+end
+
+function TestDimension:testCopyConstructor()
+ local d1, d2
+
+ d1 = D("Energy")
+ lu.assertEquals( d1, M*L^2/T^2 )
+
+ d2 = D(d1)
+ lu.assertEquals( d2, M*L^2/T^2 )
+end
+
+
+
+
+function TestDimension:testDefine()
+ local i1 = D("Force")^4
+ local i2 = D.define("Insanity",i1)
+ local i3 = D("Insanity")
+ lu.assertEquals( i1, L^4 * M^4 * T^-8)
+ lu.assertEquals( i2, L^4 * M^4 * T^-8)
+ lu.assertEquals( i3, L^4 * M^4 * T^-8)
+end
+
+function TestDimension:testToString()
+ local d = D("Force")
+ lu.assertEquals( tostring(d), "[Force]" )
+
+ local d = D(L^-1 * T * I^2 * K^3 * N^4 * J^5)
+ lu.assertEquals( tostring(d), "[L]^-1 [T] [I]^2 [K]^3 [N]^4 [J]^5" )
+end
+
+function TestDimension:testEqual()
+ local d1 = D("Energy")
+ local d2 = D("Force")
+ local d3 = D("Torque")
+
+ lu.assertEquals( d1==d2, false)
+ lu.assertEquals( d1==d3, true)
+end
+
+function TestDimension:testMultiply()
+ local d1 = D("Force")
+ local d2 = D("Energy")
+
+ local d3 = d1 * d2
+ local d4 = d2 * d1
+
+ lu.assertEquals( d3, L^3 * M^2 * T^-4)
+ lu.assertEquals( d3, d4)
+end
+
+function TestDimension:testDivide()
+ local d1 = D("Force")
+ local d2 = D("Energy")
+
+ lu.assertEquals( d1, {1,1,-2,0,0,0,0,0,0})
+ lu.assertEquals( d2, {2,1,-2,0,0,0,0,0,0})
+
+ local d3 = d1 / d2
+ local d4 = d2 / d1
+
+ lu.assertEquals( d3, {-1,0,0,0,0,0,0,0,0})
+ lu.assertNotEquals( d3, d4)
+end
+
+function TestDimension:testPow()
+ local d = D("Length")^3
+ lu.assertEquals( d, L^3)
+end
+
+function TestDimension:isequal()
+ local d = D("Length")^3
+ lu.assertTrue( d == L^3)
+
+ local d = D("Force")
+ lu.assertTrue( d == L * M / T^2)
+end
+
+
+-- Test Dimension Definitions
+
+function TestDimension:testLength()
+ lu.assertEquals(D("Length"), L)
+ lu.assertEquals(D("L"), L)
+end
+
+function TestDimension:testMass()
+ lu.assertEquals(D("Mass"), M)
+ lu.assertEquals(D("M"), M)
+end
+
+function TestDimension:testTime()
+ lu.assertEquals(D("Time"), T)
+ lu.assertEquals(D("T"), T)
+end
+
+function TestDimension:testCreateByNameForce()
+ lu.assertTrue( D("Force") == M*L/T^2 )
+end
+
+function TestDimension:testArea()
+ lu.assertEquals(D("Area"), L^2)
+end
+
+function TestDimension:testVolume()
+ lu.assertEquals(D("Volume"), L^3)
+end
+
+function TestDimension:testFrequency()
+ lu.assertEquals(D("Frequency"), T^-1)
+end
+
+function TestDimension:testFrequency()
+ lu.assertEquals(D("Frequency"), T^-1)
+end
+
+function TestDimension:testDensity()
+ lu.assertEquals(D("Density"), M / D("Volume"))
+end
+
+function TestDimension:testVelocity()
+ lu.assertEquals(D("Velocity"), L / T)
+end
+
+function TestDimension:testAcceleration()
+ lu.assertEquals(D("Acceleration"), D("Velocity") / T)
+end
+
+function TestDimension:testForce()
+ lu.assertEquals(D("Force"), M * D("Acceleration"))
+end
+
+function TestDimension:testEnergy()
+ lu.assertEquals(D("Energy"), D("Force") * L)
+end
+
+function TestDimension:testPower()
+ lu.assertEquals(D("Power"), D("Energy") / T)
+end
+
+function TestDimension:testPower()
+ lu.assertEquals(D("Power"), D("Energy") / T)
+end
+
+function TestDimension:testTorque()
+ lu.assertEquals(D("Energy"), D("Torque"))
+end
+
+function TestDimension:testTorque()
+ lu.assertEquals(D("Pressure"), D("Force") / D("Area"))
+end
+
+function TestDimension:testImpulse()
+ lu.assertEquals(D("Impulse"), M * D("Velocity"))
+end
+
+function TestDimension:testSpecificAbsorbedDose()
+ lu.assertEquals(D("Absorbed Dose"), D("Energy") / M)
+end
+
+function TestDimension:testHeatCapacity()
+ lu.assertEquals(D("Heat Capacity"), D("Energy") / K)
+end
+
+function TestDimension:testSpecificHeatCapacity()
+ lu.assertEquals(D("Specific Heat Capacity"), D("Energy") / (M * K) )
+end
+
+function TestDimension:testAngularMomentum()
+ lu.assertEquals(D("Angular Momentum"), L * D("Impulse") )
+end
+
+function TestDimension:testAngularMomentofInertia()
+ lu.assertEquals(D("Moment of Inertia"), D("Torque") * T^2 )
+end
+
+function TestDimension:testEntropy()
+ lu.assertEquals(D("Entropy"), D("Energy") / K )
+end
+
+function TestDimension:testThermalConductivity()
+ lu.assertEquals(D("Thermal Conductivity"), D("Power") / (L*K) )
+end
+
+function TestDimension:testElectricCharge()
+ lu.assertEquals(D("Electric Charge"), D("Electric Current") * T )
+end
+
+function TestDimension:testElectricPermittivity()
+ lu.assertEquals(D("Electric Permittivity"), D("Electric Charge")^2 / ( D("Force") * D("Area") ) )
+end
+
+function TestDimension:testElectricFieldStrength()
+ lu.assertEquals(D("Electric Field Strength"), D("Force") / D("Electric Charge") )
+end
+
+function TestDimension:testElectricPotential()
+ lu.assertEquals(D("Electric Potential"), D("Energy") / D("Electric Charge") )
+end
+
+function TestDimension:testElectricResistance()
+ lu.assertEquals(D("Electric Resistance"), D("Electric Potential") / D("Electric Current") )
+end
+
+function TestDimension:testElectricConductance()
+ lu.assertEquals(D("Electric Conductance"), 1 / D("Electric Resistance") )
+end
+
+function TestDimension:testElectricCapacitance()
+ lu.assertEquals(D("Electric Capacitance"), D("Electric Charge") / D("Electric Potential") )
+end
+
+function TestDimension:testElectricInductance()
+ lu.assertEquals(D("Inductance"), D("Electric Potential") * T / D("Electric Current") )
+end
+
+function TestDimension:testMagneticFluxDensity()
+ lu.assertEquals(D("Magnetic Flux Density"), D("Force") / (D("Electric Charge") * D("Velocity")) )
+end
+
+function TestDimension:testMagneticFlux()
+ lu.assertEquals(D("Magnetic Flux"), D("Magnetic Flux Density") * D("Area") )
+end
+
+function TestDimension:testMagneticPermeability()
+ lu.assertEquals(D("Magnetic Permeability"), D("Magnetic Flux Density") * L / D("Electric Current") )
+end
+
+function TestDimension:testMagneticFieldStrength()
+ lu.assertEquals(D("Magnetic Field Strength"), D("Magnetic Flux Density") / D("Magnetic Permeability") )
+end
+
+function TestDimension:testIntensity()
+ lu.assertEquals(D("Intensity"), D("Power") / D("Area") )
+end
+
+function TestDimension:testReactionRate()
+ lu.assertEquals(D("Reaction Rate"), N / (T * D("Volume")) )
+end
+
+function TestDimension:testCatalyticActivity()
+ lu.assertEquals(D("Catalytic Activity"), N / T )
+end
+
+function TestDimension:testChemicalPotential()
+ lu.assertEquals(D("Chemical Potential"), D("Energy") / N )
+end
+
+function TestDimension:testMolarConcentration()
+ lu.assertEquals(D("Molar Concentration"), N / D("Volume") )
+end
+
+function TestDimension:testMolarHeatCapacity()
+ lu.assertEquals(D("Molar Heat Capacity"), D("Energy") / (K * N) )
+end
+
+function TestDimension:testIlluminance()
+ lu.assertEquals(D("Illuminance"), J / D("Area") )
+end
+
+return TestDimension
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua
new file mode 100644
index 00000000000..1a5ec0791d3
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua
@@ -0,0 +1,515 @@
+--[[
+This file contains the unit tests for the physical.Number class.
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+package.path = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local N = physical.Number
+
+
+function defaultformat()
+ N.seperateUncertainty = true
+ N.omitUncertainty = false
+ N.format = N.DECIMAL
+end
+
+
+TestNumber = {}
+
+
+
+-- test the default constructor
+function TestNumber:testNewDefault()
+ local n = N()
+ lu.assertEquals( n._x, 0 )
+ lu.assertEquals( n._dx, 0 )
+end
+
+-- test if the constructor works with one or two numbers as arguments
+function TestNumber:testNewByNumber()
+ local n = N(122,0.022)
+ lu.assertEquals( n._x, 122 )
+ lu.assertEquals( n._dx, 0.022 )
+
+ local n = N(122,0)
+ lu.assertEquals( n._x, 122 )
+ lu.assertEquals( n._dx, 0 )
+
+ local n = N(0, 0.01)
+ lu.assertEquals( n._x, 0 )
+ lu.assertEquals( n._dx, 0.01 )
+end
+
+-- test the copy constructor
+function TestNumber:testNewCopyConstructor()
+ local n = N(122,0.022)
+ local m = N(n)
+ lu.assertEquals( m._x, 122 )
+ lu.assertEquals( m._dx, 0.022 )
+end
+
+-- test construction by string in plus minus format
+function TestNumber:testNewByStringPlusMinusNotation()
+ local n = N("2.2 +/- 0.3")
+ lu.assertEquals( n._x, 2.2 )
+ lu.assertEquals( n._dx, 0.3 )
+
+ local n = N("2e-3 +/- 0.0003")
+ lu.assertEquals( n._x, 0.002 )
+ lu.assertEquals( n._dx, 0.0003 )
+
+ local n = N("67 +/- 2e-2")
+ lu.assertEquals( n._x, 67 )
+ lu.assertEquals( n._dx, 0.02 )
+
+ local n = N("15.2e-3 +/- 10.4e-6")
+ lu.assertEquals( n._x, 0.0152 )
+ lu.assertEquals( n._dx, 0.0000104 )
+end
+
+-- test construction by string in compact format
+function TestNumber:testNewByString()
+ local n = N("2.32(5)")
+ lu.assertEquals( n._x, 2.32 )
+ lu.assertEquals( n._dx, 0.05 )
+
+ local n = N("2.32(51)")
+ lu.assertEquals( n._x, 2.32 )
+ lu.assertEquals( n._dx, 0.51 )
+
+ local n = N("4.566(5)e2")
+ lu.assertAlmostEquals( n._x, 456.6, 0.01 )
+ lu.assertEquals( n._dx, 0.5 )
+
+ local n = N("2.30(55)e3")
+ lu.assertAlmostEquals( n._x, 2300, 0.1)
+ lu.assertEquals( n._dx, 550 )
+
+ local n = N("255.30(55)e6")
+ lu.assertAlmostEquals( n._x, 255300000, 0.1)
+ lu.assertEquals( n._dx, 550000 )
+end
+
+-- test construction by string in compact format
+function TestNumber:testNewByStringNumber()
+ local n = N("1")
+ lu.assertEquals( n._x, 1 )
+ lu.assertEquals( n._dx, 0.5 )
+
+ local n = N("2.3")
+ lu.assertEquals( n._x, 2.3 )
+ lu.assertEquals( n._dx, 0.05 )
+
+ local n = N("2.3e-2")
+ lu.assertEquals( n._x, 2.3e-2 )
+ lu.assertEquals( n._dx, 0.05e-2 )
+
+ local n = N("123")
+ lu.assertEquals( n._x, 123 )
+ lu.assertEquals( n._dx, 0.5 )
+
+ local n = N("123.556")
+ lu.assertEquals( n._x, 123.556 )
+ lu.assertEquals( n._dx, 0.0005 )
+end
+
+
+
+-- test string conversion to plus-minus format
+function TestNumber:testToPlusMinusNotation()
+
+ N.seperateUncertainty = true
+
+ N.format = N.DECIMAL
+ lu.assertEquals( tostring(N(1,0.5)), "(1.0 +/- 0.5)" )
+ lu.assertEquals( tostring(N(7,1)), "(7.0 +/- 1.0)" )
+ lu.assertEquals( tostring(N(0.005,0.0001)), "(0.00500 +/- 0.00010)" )
+ lu.assertEquals( tostring(N(500,2)), "(500 +/- 2)" )
+ lu.assertEquals( tostring(N(1023453838.0039,0.06)), "(1023453838.00 +/- 0.06)" )
+ lu.assertEquals( tostring(N(10234.0039, 0.00000000012)), "(10234.00390000000 +/- 0.00000000012)" )
+ lu.assertEquals( tostring(N(10234.0039e12, 0.00000000012e12)), "(10234003900000000 +/- 120)" )
+ lu.assertEquals( tostring(N(0, 0)), "0" )
+ lu.assertEquals( tostring(N(0, 0.01)), "(0.000 +/- 0.010)" )
+
+ N.format = N.SCIENTIFIC
+ lu.assertEquals( tostring(N(7,1)), "(7 +/- 1)" )
+ lu.assertEquals( tostring(N(80,22)), "(8 +/- 2)e1" )
+ lu.assertEquals( tostring(N(40,100)), "(4 +/- 10)e1" )
+ lu.assertEquals( tostring(N(0.005,0.0001)), "(5.00 +/- 0.10)e-3" )
+ lu.assertEquals( tostring(N(500,2)), "(5.00 +/- 0.02)e2" )
+
+end
+
+-- test string conversion to plus-minus format
+function TestNumber:testToParenthesesNotation()
+
+ N.seperateUncertainty = false
+ N.omitUncertainty = false
+
+ N.format = N.DECIMAL
+ lu.assertEquals( tostring(N(1,0.5)), "1.0(5)" )
+ lu.assertEquals( tostring(N(1.25,0.5)), "1.3(5)" )
+ lu.assertEquals( tostring(N(100,13)), "100(13)" )
+ lu.assertEquals( tostring(N(26076,45)), "26076(45)" )
+ lu.assertEquals( tostring(N(26076,0.01)), "26076.000(10)" )
+ lu.assertEquals( tostring(N(1234.56789, 0.00011)), "1234.56789(11)" )
+ lu.assertEquals( tostring(N(15200000, 23000)), "15200000(23000)" )
+ lu.assertEquals( tostring(N(5, 0.01)), "5.000(10)" )
+ lu.assertEquals( tostring(N(100, 5)), "100(5)" )
+
+ N.format = N.SCIENTIFIC
+ lu.assertEquals( tostring(N(15.2e-6, 2.3e-8)), "1.520(2)e-5" )
+ lu.assertEquals( tostring(N(15.2e-6, 1.2e-8)), "1.5200(12)e-5" )
+ lu.assertEquals( tostring(N(5, 0.01)), "5.000(10)" )
+ lu.assertEquals( tostring(N(15.2e-6, 0)), "1.52e-05" )
+ lu.assertEquals( tostring(N(16.25e-6, 5e-7)), "1.62(5)e-5" )
+
+ lu.assertEquals( tostring(N(1.9884e30, 2e26)/N(1.191e8,1.4e6)), "1.67(2)e22" )
+end
+
+
+
+-- test string conversion to compact format
+function TestNumber:testToOmitUncertaintyNotation()
+ N.seperateUncertainty = false
+ N.omitUncertainty = true
+
+ N.format = N.DECIMAL
+ lu.assertEquals( tostring(N(1, 0.5)), "1" )
+ lu.assertEquals( tostring(N(1.2, 0.05)), "1.2" )
+ lu.assertEquals( tostring(N(1.2, 0.005)), "1.20" )
+ lu.assertEquals( tostring(N(1.25, 0.05)), "1.3" )
+
+
+
+ N.format = N.SCIENTIFIC
+ lu.assertEquals( tostring(N(1.2e27, 0.05e27)), "1.2e27" )
+
+end
+
+
+-- test string conversion from compact to plus-minus format
+function TestNumber:testParseParenthesesNotation()
+ defaultformat()
+
+ local n = N("2.32(5)")
+ lu.assertEquals( tostring(n), "(2.32 +/- 0.05)" )
+
+ local n = N("2.32(51)")
+ lu.assertEquals( tostring(n), "(2.3 +/- 0.5)" )
+
+ local n = N("4.566(5)e2")
+ lu.assertEquals( tostring(n), "(456.6 +/- 0.5)" )
+
+ local n = N("2.30(55)e3")
+ lu.assertEquals( tostring(n), "(2300 +/- 550)" )
+end
+
+-- test string conversion from and to plus-minus format
+function TestNumber:testParsePlusMinusNotation()
+ defaultformat()
+
+ local n = N("2.2 +/- 0.3")
+ lu.assertEquals( tostring(n), "(2.2 +/- 0.3)" )
+
+ local n = N("2.2+/-0.3")
+ lu.assertEquals( tostring(n), "(2.2 +/- 0.3)" )
+
+ local n = N("2e-3 +/- 0.0003")
+ lu.assertEquals( tostring(n), "(0.0020 +/- 0.0003)" )
+
+ local n = N("67 +/- 2e-2")
+ lu.assertEquals( tostring(n), "(67.00 +/- 0.02)" )
+end
+
+-- test the frexp function
+function TestNumber:testfrexp()
+ local m,e = N._frexp(123)
+
+ lu.assertEquals( m, 1.23 )
+ lu.assertEquals( e, 2 )
+end
+
+
+
+
+
+
+-- test widget
+-- http://www.wolframalpha.com/widgets/gallery/view.jsp?id=ff2d5fc3ab1932df3c00308bead36006
+
+-- article on
+-- https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3387884/
+
+-- test addition of numbers and other physical numbers
+function TestNumber:testAdd()
+ defaultformat()
+
+ local n1 = N(5, 0.5)
+ local n2 = N(10, 0.2)
+
+ lu.assertEquals( tostring(2+n2), "(12.0 +/- 0.2)" )
+ lu.assertEquals( tostring(n1+3), "(8.0 +/- 0.5)" )
+ lu.assertEquals( tostring(n1+n2), "(15.0 +/- 0.5)" )
+end
+
+-- test subtraction of numbers and other physical numbers
+function TestNumber:testSubtract()
+ defaultformat()
+
+ local n1 = N(5, 0.5)
+ local n2 = N(10, 0.2)
+
+ lu.assertEquals( tostring(2-n2), "(-8.0 +/- 0.2)" )
+ lu.assertEquals( tostring(n1-3), "(2.0 +/- 0.5)" )
+ lu.assertEquals( tostring(n1-n2), "(-5.0 +/- 0.5)" )
+end
+
+-- test mixed operations
+function TestNumber:testMixed()
+ defaultformat()
+
+ local d = N(5, 0.5)
+
+ l = d - d
+ m = l / d
+
+ lu.assertEquals( tostring(m), "0.0" )
+end
+
+-- test unary minus operation
+function TestNumber:testUnaryMinus()
+ defaultformat()
+
+ local n = N(5.68, 0.2)
+
+ lu.assertEquals( tostring(-n), "(-5.7 +/- 0.2)" )
+ lu.assertEquals( tostring(-(-n)), "(5.7 +/- 0.2)" )
+end
+
+-- test multiplication with numbers and other physical numbers
+function TestNumber:testMultiplication()
+ defaultformat()
+
+ local n1 = N(4.52, 0.02)
+ local n2 = N(2.0, 0.2)
+
+ lu.assertEquals( tostring(4*n2), "(8.0 +/- 0.8)" )
+ lu.assertEquals( tostring(n1*5), "(22.60 +/- 0.10)" )
+ lu.assertEquals( tostring(n1*n2), "(9.0 +/- 0.9)" )
+end
+
+
+-- test division with numbers and other physical numbers
+function TestNumber:testDivision()
+ defaultformat()
+
+ local n1 = N(2.0, 0.2)
+ local n2 = N(3.0, 0.6)
+
+ lu.assertEquals( tostring(5/n2), "(1.7 +/- 0.3)" )
+ lu.assertEquals( tostring(n1/6), "(0.33 +/- 0.03)" )
+ lu.assertEquals( tostring(n1/n2), "(0.67 +/- 0.15)" )
+end
+
+
+-- uncertainty calculator physics
+-- http://ollyfg.github.io/Uncertainty-Calculator/
+function TestNumber:testPower()
+ defaultformat()
+
+ local n1 = N(3.0, 0.2)
+ local n2 = N(2.5, 0.01)
+
+ lu.assertEquals( tostring(n1^2), "(9.0 +/- 1.2)" )
+ lu.assertEquals( tostring(3^n2), "(15.59 +/- 0.17)" )
+ lu.assertEquals( tostring(n1^n2), "(16 +/- 3)" )
+end
+
+-- test the absolute value function
+function TestNumber:testAbs()
+ defaultformat()
+
+ local n = N(-5.0, 0.2)
+ lu.assertEquals( tostring(n:abs()), "(5.0 +/- 0.2)" )
+
+ local n = N(100, 50)
+ lu.assertEquals( tostring(n:abs()), "(100 +/- 50)" )
+end
+
+-- test the logarithm function
+function TestNumber:testLog()
+ defaultformat()
+
+ local n = N(5.0, 0.2)
+ lu.assertEquals( tostring(n:log()), "(1.61 +/- 0.04)" )
+
+ local n = N(0.03, 0.003)
+ lu.assertEquals( tostring(n:log()), "(-3.51 +/- 0.10)" )
+
+ local n = N(0.03, 0.003)
+ lu.assertEquals( tostring(n:log(4)), "(-2.53 +/- 0.07)" )
+
+ local n = N(5.0, 0.2)
+ lu.assertEquals( tostring(n:log(10)), "(0.699 +/- 0.017)" )
+
+ local n = N(0.03, 0.003)
+ lu.assertEquals( tostring(n:log(10)), "(-1.52 +/- 0.04)" )
+end
+
+-- test the exponential function
+function TestNumber:testExp()
+ defaultformat()
+
+ local n = N(7.0, 0.06)
+ lu.assertEquals( tostring(n:exp()), "(1097 +/- 66)" )
+
+ local n = N(0.2, 0.01)
+ lu.assertEquals( tostring(n:exp()), "(1.221 +/- 0.012)" )
+end
+
+-- test the square root function
+function TestNumber:testSqrt()
+ defaultformat()
+
+ local n = N(104.2, 0.06)
+ lu.assertEquals( tostring(n:sqrt()), "(10.208 +/- 0.003)" )
+
+ local n = N(0.0004, 0.000005)
+ lu.assertEquals( tostring(n:sqrt()), "(0.02000 +/- 0.00013)" )
+end
+
+
+
+-- TRIGONOMETRIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Trigonometric_functions
+
+function TestNumber:testSin()
+ defaultformat()
+
+ local n = N(-math.pi/6, 0.02)
+ lu.assertEquals( tostring(n:sin()), "(-0.500 +/- 0.017)" )
+end
+
+
+function TestNumber:testCos()
+ defaultformat()
+
+ local n = N(math.pi/3, 0.01)
+ lu.assertEquals( tostring(n:cos()), "(0.500 +/- 0.009)" )
+
+ local n = N(math.pi/4, 0.1)
+ lu.assertEquals( tostring(n:cos()), "(0.71 +/- 0.07)" )
+
+ local n = N(-math.pi/6, 0.02)
+ lu.assertEquals( tostring(n:cos()), "(0.87 +/- 0.01)" )
+end
+
+function TestNumber:testTan()
+ defaultformat()
+
+ local n = N(0, 0.01)
+ lu.assertEquals( tostring(n:tan()), "(0.000 +/- 0.010)" )
+
+ local n = N(math.pi/3, 0.1)
+ lu.assertEquals( tostring(n:tan()), "(1.7 +/- 0.4)" )
+
+ local n = N(-math.pi/3, 0.02)
+ lu.assertEquals( tostring(n:tan()), "(-1.73 +/- 0.08)" )
+end
+
+
+-- INVERS TRIGONOMETRIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#arctan
+
+function TestNumber:testArcsin()
+ defaultformat()
+
+ local n = N(0.5, 0.01)
+ lu.assertEquals( tostring(n:asin()), "(0.524 +/- 0.012)" )
+end
+
+
+function TestNumber:testArccos()
+ defaultformat()
+
+ local n = N(0.5, 0.01)
+ lu.assertEquals( tostring(n:acos()), "(1.047 +/- 0.012)" )
+end
+
+function TestNumber:testArctan()
+ defaultformat()
+
+ local n = N(0.5, 0.01)
+ lu.assertEquals( tostring(n:atan()), "(0.464 +/- 0.008)" )
+end
+
+
+-- HYPERBOLIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Hyperbolic_function
+
+function TestNumber:testSinh()
+ defaultformat()
+
+ local n = N(10, 0.003)
+ lu.assertEquals( tostring(n:sinh()), "(11013 +/- 33)" )
+end
+
+function TestNumber:testCosh()
+ defaultformat()
+
+ local n = N(10, 0.003)
+ lu.assertEquals( tostring(n:cosh()), "(11013 +/- 33)" )
+end
+
+function TestNumber:testTanh()
+ defaultformat()
+
+ local n = N(1, 0.003)
+ lu.assertEquals( tostring(n:tanh()), "(0.7616 +/- 0.0013)" )
+end
+
+
+-- INVERS HYPERBOLIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Inverse_hyperbolic_function
+
+function TestNumber:testArcsinh()
+ local n = N(1000, 5)
+ lu.assertEquals( tostring(n:asinh()), "(7.601 +/- 0.005)" )
+end
+
+function TestNumber:testArccosh()
+ local n = N(1000, 5)
+ lu.assertEquals( tostring(n:acosh()), "(7.601 +/- 0.005)" )
+end
+
+function TestNumber:testArctanh()
+ local n = N(0.2, 0.01)
+ lu.assertEquals( tostring(n:atanh()), "(0.203 +/- 0.010)" )
+end
+
+
+return TestNumber
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua
new file mode 100644
index 00000000000..b27c853e7cc
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua
@@ -0,0 +1,609 @@
+--[[
+This file contains the unit tests for the physical.Quantity class.
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+package.path = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local N = physical.Number
+local Q = physical.Quantity
+
+function dump(o)
+ if type(o) == 'table' then
+ local s = '{ '
+ for k,v in pairs(o) do
+ if type(k) ~= 'number' then k = '"'..k..'"' end
+ if getmetatable(v) == physical.Unit then
+ s = s .. '['..k..'] = ' .. v.symbol .. ','
+ else
+ s = s .. '['..k..'] = ' .. dump(v) .. ','
+ end
+ end
+ return s .. '}\n'
+ else
+ return tostring(o)
+ end
+end
+
+
+TestQuantity = {}
+
+-- Quantity.new(o=nil)
+function TestQuantity:testEmptyConstructor()
+ local q = Q()
+ lu.assertEquals( q.value, 1 )
+end
+function TestQuantity:testNumberConstructor()
+ local q = Q(42)
+ lu.assertEquals( q.value, 42 )
+ lu.assertTrue( q.dimension:iszero() )
+end
+function TestQuantity:testCopyConstructor()
+ local q = Q(73*_m)
+ lu.assertEquals( q.value, 73 )
+ lu.assertTrue( q.dimension == _m.dimension )
+end
+
+
+-- Quantity.defineBase(symbol,name,dimension)
+
+
+
+
+
+-- Quantity.define(symbol, name, o, tobase, frombase)
+
+
+
+
+
+
+
+function TestQuantity:testToString()
+ N.seperateUncertainty = true
+
+ lu.assertEquals( tostring(5 * _m), "5 * _m" )
+ lu.assertEquals( tostring(5 * _m^2), "5.0 * _m^2" )
+ lu.assertEquals( tostring(5 * _km/_h), "5.0 * _km / _h" )
+
+ lu.assertEquals( tostring( N(2.7,0.04) * _g/_cm^3), "(2.70 +/- 0.04) * _g / _cm^3" )
+end
+
+
+function TestQuantity:testToSIUnitX()
+ N.seperateUncertainty = false
+
+ lu.assertEquals( (5 * _m):tosiunitx(), "\\SI{5}{\\meter}" )
+ lu.assertEquals( (5 * _m):tosiunitx("x=1"), "\\SI[x=1]{5}{\\meter}" )
+
+ lu.assertEquals( (5 * _m):tosiunitx("x=5",1), "\\num[x=5]{5}" )
+ lu.assertEquals( (5 * _m):tosiunitx("x=5",2), "\\si[x=5]{\\meter}" )
+
+ lu.assertEquals( (5 * _m^2):tosiunitx(), "\\SI{5.0}{\\meter\\tothe{2}}" )
+
+ lu.assertEquals( (56 * _km):tosiunitx(), "\\SI{56}{\\kilo\\meter}" )
+ lu.assertEquals( (5 * _km/_h):tosiunitx(), "\\SI{5.0}{\\kilo\\meter\\per\\hour}" )
+ lu.assertEquals( (4.81 * _J / (_kg * _K) ):tosiunitx(), "\\SI{4.81}{\\joule\\per\\kilogram\\per\\kelvin}" )
+
+ lu.assertEquals( (N(2.7,0.04) * _g/_cm^3):tosiunitx(), "\\SI{2.70(4)}{\\gram\\per\\centi\\meter\\tothe{3}}" )
+end
+
+
+
+
+
+function TestQuantity.addError()
+ local l = 5*_m + 10*_s
+end
+function TestQuantity:testAdd()
+ local l = 5*_m + 10*_m
+ lu.assertEquals( l.value, 15 )
+ lu.assertEquals( l.dimension, _m.dimension )
+
+ local msg = "Error: Cannot add '5 * _m' to '10 * _s', because they have different dimensions."
+ lu.assertErrorMsgContains(msg, TestQuantity.addError )
+end
+
+
+
+
+function TestQuantity.subtractError()
+ local l = 5*_m - 10*_s
+end
+function TestQuantity:testSubtract()
+ local l = 5*_m - 15*_m
+ lu.assertEquals( l.value, -10 )
+ lu.assertEquals( l.dimension, _m.dimension )
+
+ local msg = "Error: Cannot subtract '10 * _s' from '5 * _m', because they have different dimensions."
+ lu.assertErrorMsgContains(msg, TestQuantity.subtractError )
+end
+
+
+function TestQuantity:testUnaryMinus()
+ local l = -5*_m
+ lu.assertEquals( l.value, -5 )
+ lu.assertEquals( l.dimension, _m.dimension )
+end
+
+
+function TestQuantity:testMultiply()
+ local A = 5*_m * 10 * _m
+ lu.assertEquals( A.value, 50 )
+ lu.assertEquals( A.dimension, (_m^2).dimension )
+end
+
+
+function TestQuantity:testMultiplyOfTemperatures()
+ local m_1 = 5 * _kg
+ local m_2 = 3 * _kg
+ local T_1 = 20 * _degC
+ local T_2 = 40 * _degC
+
+ -- if one multiplies a temperature by another quantity
+ -- the temperature will be interpreted as a temperature difference
+ local T_m = ( (m_1*T_1 + m_2*T_2) / (m_1 + m_2) ):to(_degC, false)
+
+ lu.assertEquals( T_m.value, 27.5 )
+
+
+ local m_1 = 5 * _kg
+ local m_2 = 3 * _kg
+ local T_1 = 20 * _degF
+ local T_2 = 40 * _degF
+
+ -- if one multiplies a temperature by another quantity
+ -- the temperature will be interpreted as a temperature difference.
+ local T_m = ( (m_1*T_1 + m_2*T_2) / (m_1 + m_2) ):to(_degC, false)
+
+ lu.assertAlmostEquals( T_m.value, 15.277777777778, 1e-3 )
+end
+
+
+function TestQuantity:testMultiplyWithNumber()
+ local one = N(1,0.1) * _1
+
+ lu.assertEquals( one.value, N(1,0.1) )
+ lu.assertEquals( one.dimension, (_1).dimension )
+
+ local mu = ( N(1,0.1) * _u_0 ):to(_N/_A^2)
+ lu.assertAlmostEquals( mu.value._dx, 1.256e-6, 1e-3 )
+ lu.assertEquals( mu.dimension, (_N/_A^2).dimension )
+end
+
+
+function TestQuantity.divideError()
+ local l = 7*_m / ((2*6 - 12)*_s)
+end
+function TestQuantity:testDivide()
+ local v = 7*_m / (2*_s)
+ lu.assertEquals( v.value, 3.5 )
+ lu.assertEquals( v.dimension, (_m/_s).dimension )
+
+ lu.assertError( divideError )
+end
+
+-- test power function
+function TestQuantity:testPow()
+ local V = (5*_m)^3
+ lu.assertEquals( V.value, 125 )
+ lu.assertEquals( V.dimension, (_m^3).dimension )
+end
+
+function TestQuantity:testPowWithQuantityAsExponent()
+ local V = (5*_m)^(24*_m / (12*_m))
+ lu.assertAlmostEquals( V.value, 25, 0.00001 )
+ lu.assertEquals( V.dimension, (_m^2).dimension )
+end
+
+-- test isclose function
+function TestQuantity:testisclose()
+ local rho1 = 19.3 * _g / _cm^3
+ local rho2 = 19.2 * _g / _cm^3
+
+ lu.assertTrue( rho1:isclose(rho2,0.1) )
+ lu.assertTrue( rho1:isclose(rho2,10 * _percent) )
+ lu.assertFalse( rho1:isclose(rho2,0.1 * _percent) )
+end
+
+-- test min function
+function TestQuantity:testMin()
+ local l = Q.min(-2.5,5)
+ lu.assertEquals( l.value, -2.5 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (3 * _cm):min(5 * _dm)
+ lu.assertEquals( l.value, 3 )
+ lu.assertEquals( l.dimension, _m.dimension )
+
+ local q1 = 20 * _cm
+ local q2 = 10 * _dm
+ local q3 = 1 * _m
+ lu.assertEquals( Q.min(q1,q2,q3), q1 )
+
+ local q1 = 20 * _A
+ lu.assertEquals( Q.min(q1), q1 )
+
+ local q1 = N(10,1) * _s
+ local q2 = N(9,1) * _s
+ lu.assertEquals( q1:min(q2), q2 )
+end
+
+-- test max function
+function TestQuantity:testMax()
+ local l = Q.max(-2.5,5)
+ lu.assertEquals( l.value, 5 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (3 * _m):max(5 * _m)
+ lu.assertEquals( l.value, 5 )
+ lu.assertEquals( l.dimension, _m.dimension )
+
+ local q1 = 20 * _cm
+ local q2 = 10 * _dm
+ local q3 = 1 * _m
+ lu.assertEquals( Q.max(q1,q2,q3), q2 )
+
+ local q1 = 20 * _A
+ lu.assertEquals( Q.max(q1), q1 )
+
+ local q1 = N(10,1) * _s
+ local q2 = N(9,1) * _s
+ lu.assertEquals( q1:max(q2), q1 )
+end
+
+-- test absolute value function
+function TestQuantity:testAbs()
+ local l = Q.abs(-2.5)
+ lu.assertEquals( l.value, 2.5 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (-45.3 * _m):abs()
+ lu.assertEquals( l.value, 45.3 )
+ lu.assertEquals( l.dimension, _m.dimension )
+
+ local l = ( N(-233,3) * _m^2):abs()
+ lu.assertEquals( l.value, N(233,3) )
+ lu.assertEquals( l.dimension, (_m^2).dimension )
+end
+
+-- test square root function
+function TestQuantity:testSqrt()
+ local l = (103 * _cm^2):sqrt()
+ lu.assertEquals( l.value, 10.148891565092219 )
+ lu.assertEquals( l.dimension, _m.dimension )
+
+ local l = (N(103,2) * _cm^2):sqrt()
+ lu.assertAlmostEquals( l.value._x, 10.148891565092219, 0.0001 )
+ lu.assertAlmostEquals( l.value._dx, 0.098532927816429, 0.0001 )
+ lu.assertEquals( l.dimension, _m.dimension )
+end
+
+-- test logarithm function
+function TestQuantity.logError()
+ local l = (100 * _s):log()
+end
+function TestQuantity:testLog()
+ lu.assertError( logError )
+
+ local l = Q.log(2)
+ lu.assertAlmostEquals( l.value, 0.693147180559945, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = Q.log(3 * _1)
+ lu.assertAlmostEquals( l.value, 1.09861228866811, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = Q.log(2, 10)
+ lu.assertAlmostEquals( l.value, 0.301029995663981, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = Q.log(4, 10 * _1)
+ lu.assertAlmostEquals( l.value, 0.602059991327962, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = Q.log(4 * _1, 8 * _1)
+ lu.assertAlmostEquals( l.value, 0.666666666666666, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (100 * _1):log()
+ lu.assertAlmostEquals( l.value, 4.605170185988091, 0.0001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (600 * _m / (50000 * _cm)):log()
+ lu.assertAlmostEquals( l.value, 0.182321556793955, 0.0001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = ( N(100,0) * _1 ):log()
+ lu.assertAlmostEquals( l.value._x, 4.605170185988091, 0.0001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+end
+
+function TestQuantity:testLogAdvanced()
+ local L_I1 = N(125,0.1) * _dB
+ local I_0 = 1e-12 * _W/_m^2
+ local I_1 = ( I_0 * 10^(L_I1/10) ):to(_W/_m^2)
+
+ local l = (I_1/I_0):log(10)
+ lu.assertAlmostEquals( l.value._x, 12.5, 0.001 )
+end
+
+-- test exponential function
+function TestQuantity.expError()
+ local l = (100 * _s):log()
+end
+function TestQuantity:testExp()
+ lu.assertError( expError )
+
+ local l = (-2*_m/(5*_m)):exp()
+ lu.assertAlmostEquals( l.value, 0.670320046035639, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = Q.exp(2)
+ lu.assertAlmostEquals( l.value, 7.38905609893065, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+end
+
+-- test sine function
+function TestQuantity.sinError()
+ local l = (100 * _s):sin()
+end
+function TestQuantity:testSin()
+ lu.assertError( sinError )
+
+ local l = Q.sin(1.570796326794897)
+ lu.assertAlmostEquals( l.value, 1, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (45 * _deg):sin()
+ lu.assertAlmostEquals( l.value, 0.707106781186548, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+end
+
+-- test cosine function
+function TestQuantity.cosError()
+ local l = (100 * _s):cos()
+end
+function TestQuantity:testCos()
+ lu.assertError( cosError )
+
+ local l = Q.cos(0)
+ lu.assertAlmostEquals( l.value, 1, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (50 * _deg):cos()
+ lu.assertAlmostEquals( l.value, 0.642787609686539, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+end
+
+-- test tangens function
+function TestQuantity.tanError()
+ local l = (100 * _s):tan()
+end
+function TestQuantity:testTan()
+ lu.assertError( tanError )
+
+ local l = Q.tan(0.785398163397448)
+ lu.assertAlmostEquals( l.value, 1, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (50 * _deg):tan()
+ lu.assertAlmostEquals( l.value, 1.19175359259421, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+end
+
+
+-- test asin function
+function TestQuantity.asinError()
+ local l = (100 * _s):asin()
+end
+function TestQuantity:testAsin()
+ lu.assertError( asinError )
+
+ local l = Q.asin(0.785398163397448)
+ lu.assertAlmostEquals( l.value, 0.903339110766512, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.5 * _1):asin()
+ lu.assertAlmostEquals( l.value, 0.523598775598299, 0.000001 )
+ lu.assertEquals( l.dimension, _rad.dimension )
+end
+
+
+-- test acos function
+function TestQuantity.acosError()
+ local l = (100 * _s):acos()
+end
+function TestQuantity:testAcos()
+ lu.assertError( acosError )
+
+ local l = Q.acos(0.785398163397448)
+ lu.assertAlmostEquals( l.value, 0.667457216028384, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.5 * _1):acos()
+ lu.assertAlmostEquals( l.value, 1.047197551196598, 0.000001 )
+ lu.assertEquals( l.dimension, _rad.dimension )
+end
+
+
+-- test atan function
+function TestQuantity.atanError()
+ local l = (100 * _s):atan()
+end
+function TestQuantity:testAtan()
+ lu.assertError( atanError )
+
+ local l = Q.atan(0.785398163397448)
+ lu.assertAlmostEquals( l.value, 0.665773750028354, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.5 * _1):atan()
+ lu.assertAlmostEquals( l.value, 0.463647609000806, 0.000001 )
+ lu.assertEquals( l.dimension, _rad.dimension )
+end
+
+-- test sinh function
+function TestQuantity.sinhError()
+ local l = (100 * _s):sinh()
+end
+function TestQuantity:testSinh()
+ lu.assertError( sinhError )
+
+ local l = Q.sinh(2)
+ lu.assertAlmostEquals( l.value, 3.626860407847019, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.75 * _1):sinh()
+ lu.assertAlmostEquals( l.value, 0.82231673193583, 1e-9 )
+
+ local l = (N(0.75,0.01) * _1):sinh()
+ lu.assertAlmostEquals( l.value:__tonumber(), 0.82231673193583, 1e-9 )
+end
+
+-- test cosh function
+function TestQuantity.coshError()
+ local l = (100 * _s):cosh()
+end
+function TestQuantity:testCosh()
+ lu.assertError( coshError )
+
+ local l = Q.cosh(2)
+ lu.assertAlmostEquals( l.value, 3.762195691083631, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.25 * _1):cosh()
+ lu.assertAlmostEquals( l.value, 1.031413099879573, 1e-9 )
+
+ local l = (N(0.25,0.01) * _1):cosh()
+ lu.assertAlmostEquals( l.value:__tonumber(), 1.031413099879573, 1e-9 )
+end
+
+-- test tanh function
+function TestQuantity.tanhError()
+ local l = (100 * _s):tanh()
+end
+function TestQuantity:testTanh()
+ lu.assertError( tanhError )
+
+ local l = Q.tanh(2)
+ lu.assertAlmostEquals( l.value, 0.964027580075817, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.5 * _1):tanh()
+ lu.assertAlmostEquals( l.value, 0.46211715726001, 1e-9 )
+
+ local l = (N(0.5,0.01) * _1):tanh()
+ lu.assertAlmostEquals( l.value:__tonumber(), 0.46211715726001, 1e-9 )
+end
+
+-- test asinh function
+function TestQuantity.asinhError()
+ local l = (100 * _s):asinh()
+end
+function TestQuantity:testAsinh()
+ lu.assertError( asinhError )
+
+ local l = Q.asinh(1)
+ lu.assertAlmostEquals( l.value, 0.881373587019543, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (2 * _1):asinh()
+ lu.assertAlmostEquals( l.value, 1.44363547517881, 1e-9 )
+
+ local l = (N(2,0.01) * _1):asinh()
+ lu.assertAlmostEquals( l.value:__tonumber(), 1.44363547517881, 1e-9 )
+end
+
+-- test acosh function
+function TestQuantity.acoshError()
+ local l = (100 * _s):acosh()
+end
+function TestQuantity:testAcosh()
+ lu.assertError( acoshError )
+
+ local l = Q.acosh(1.5)
+ lu.assertAlmostEquals( l.value, 0.962423650119207, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (1.2 * _1):acosh()
+ lu.assertAlmostEquals( l.value, 0.622362503714779, 1e-9 )
+
+ local l = (N(1.2,0.01) * _1):acosh()
+ lu.assertAlmostEquals( l.value:__tonumber(), 0.622362503714779, 1e-9 )
+end
+
+-- test atanh function
+function TestQuantity.atanhError()
+ local l = (100 * _s):atanh()
+end
+function TestQuantity:testAtanh()
+ lu.assertError( atanhError )
+
+ local l = Q.atanh(0.5)
+ lu.assertAlmostEquals( l.value, 0.549306144334055, 0.000001 )
+ lu.assertEquals( l.dimension, _1.dimension )
+
+ local l = (0.9 * _1):atanh()
+ lu.assertAlmostEquals( l.value, 1.47221948958322, 1e-9 )
+
+ local l = (N(0.9,0.01) * _1):atanh()
+ lu.assertAlmostEquals( l.value:__tonumber(), 1.47221948958322, 1e-9 )
+end
+
+-- test less than
+function TestQuantity:testLessThan()
+
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local l_D = 5 * _m
+ local d_D = N(0.25,0.001) * _mm
+
+ local d = N(5,0.01) * _cm
+ local l = N(10,0.01) * _cm
+
+ local I_max = N(1,0.001) * _A
+ local B = N(0.5,0.001) * _mT
+
+ local Nw = ( B*l/(_u_0*I_max) ):to(_1)
+ local N_max = (l/d_D):to(_1)
+ local l_max = (Nw*Pi*d):to(_m)
+
+ lu.assertTrue(l_D < l_max)
+end
+
+-- test less than zero
+function TestQuantity:testLessThanZero()
+ lu.assertTrue(1*_1 > 0)
+ lu.assertTrue(0*_1 > -1)
+ lu.assertTrue(-1*_1 < 0)
+
+ lu.assertTrue(0 < 1*_1)
+ lu.assertTrue(-1 < 0*_1)
+ lu.assertTrue(0 > -1*_1)
+end
+
+
+return TestQuantity
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua
new file mode 100644
index 00000000000..eb0d67402be
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua
@@ -0,0 +1,95 @@
+--[[
+This file contains the unit tests for the physical.Unit class.
+
+Copyright (c) 2020 Thomas Jenni
+
+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.
+]]--
+
+local lu = require("luaunit")
+
+package.path = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local D = physical.Dimension
+local U = physical.Unit
+
+
+TestUnit = {}
+
+function TestUnit:testNewDefault()
+ local u = U.new()
+ lu.assertEquals(u._term, {{},{}})
+end
+
+function TestUnit:testNewBaseUnit()
+ local _m = U.new("m","meter")
+ lu.assertEquals(_m.symbol, "m")
+ lu.assertEquals(_m.name, "meter")
+end
+
+function TestUnit:testMultiply()
+ local _m = U.new("m","meter")
+
+ local _m2 = _m * _m
+
+ lu.assertEquals(_m2.symbol, nil)
+ lu.assertEquals(_m2.name, nil)
+ lu.assertEquals(_m2._term[1][1][1], _m)
+ lu.assertEquals(_m2._term[1][1][2], 1)
+ lu.assertEquals(_m2._term[1][2][1], _m)
+ lu.assertEquals(_m2._term[1][2][2], 1)
+end
+
+function TestUnit:testDivide()
+ local _m = U.new("m","meter")
+
+ local _m2 = _m / _m
+
+ lu.assertEquals(_m2.symbol, nil)
+ lu.assertEquals(_m2.name, nil)
+ lu.assertEquals(_m2._term[1][1][1], _m)
+ lu.assertEquals(_m2._term[1][1][2], 1)
+ lu.assertEquals(_m2._term[2][1][1], _m)
+ lu.assertEquals(_m2._term[2][1][2], 1)
+end
+
+function TestUnit:testPower()
+ local _m = U.new("m","meter")
+
+ local _m2 = _m^4
+
+ lu.assertEquals(_m2.symbol, nil)
+ lu.assertEquals(_m2.name, nil)
+ lu.assertEquals(_m2._term[1][1][1], _m)
+ lu.assertEquals(_m2._term[1][1][2], 4)
+end
+
+function TestUnit:testNewDerivedUnit()
+ local _m = U.new("m","meter")
+ local _m2 = U.new("m2","squaremeter")
+
+ lu.assertEquals(_m2.symbol, "m2")
+ lu.assertEquals(_m2.name, "squaremeter")
+ lu.assertEquals(_m2._term[1][1][1], _m2)
+ lu.assertEquals(_m2._term[1][1][2], 1)
+end
+
+
+return TestUnit