diff options
author | Karl Berry <karl@freefriends.org> | 2020-09-06 21:19:54 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2020-09-06 21:19:54 +0000 |
commit | da539a5083a4e436ce339377122bf143859ab814 (patch) | |
tree | e57c25090a1763a3cf8028b42eeb23ad326ed580 /Master/texmf-dist/doc/lualatex/lua-physical/test | |
parent | ba5b524ffb7b31ed52f06d40fa7cd2877124e46a (diff) |
lua-physical (6sep20)
git-svn-id: svn://tug.org/texlive/trunk@56278 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc/lualatex/lua-physical/test')
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: + -- " " + -- ' ' + -- < < + -- > > + -- & & + + return string.gsub( s, '.', { + ['&'] = "&", + ['"'] = """, + ["'"] = "'", + ['<'] = "<", + ['>'] = ">", + } ) +end +M.private.xmlEscape = xmlEscape + +local function xmlCDataEscape( s ) + -- Return s escaped for CData section, escapes: "]]>" + return string.gsub( s, ']]>', ']]>' ) +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 |