Icon representing a recipe

Recipe: Safe Fun 1.3

created by LociOiling

Profile


Name
Safe Fun 1.3
ID
103265
Shared with
Public
Parent
None
Children
Created on
February 25, 2020 at 23:51 PM UTC
Updated on
February 25, 2020 at 23:51 PM UTC
Description

Safe Fun demonstrates the use of the pcall function to trap errors. SafeFun tests many of the Foldit Lua functions for parameter errors, such as segment out of range. SafeFun allows testing functions by group, such as band, behavior, selection, and structure. Version 1.2 adds a dialog to allow control over which tests run. Version 1.3 refines error testing.

Best for


Code


--[[ Safe Fun "Safe" wrappers for functions which may crash your recipe. Many of the foldit LUA functions terminate the recipe if called with an invalid parameter. For example, selection.Select terminates the recipe if the segment specified is outside of the valid range of segments. The wrapper functions are prefixed with "Safe" and added to the same namespace (table) as the original problem function. For example, selection.SafeSelect can be used in place of selection.Select. The wrapper functions catch the error using the LUA pcall function, and return an error code and an error message if the underlying function failed. A return code of zero indicate the underlying function was successful. The return values from the underlying function are returned after the return code. For example, structure.IsHydrophobic returns a boolean, so structure.SafeIsHydrophobic returns an number and a boolean: local rc local isPhobic rc, isPhobic = structure.SafeIsHydrophobic ( segIdx ) if rc ~= 0 then print ( "IsHydrophobic ( " .. segidx .. " ), rc = " .. rc .. ", errmsg = \"" .. errmsg .. "\"" ) else if isPhobic then ... end end A negative return code indicates the underlying function encountered an error. A return code of -1 is commonly used to indicate a bad segment index. Large negative return codes indicate a general error: * -997 = argument type error * -998 = argument count error * -999 = other unspecifed error For example, the call rc, errmsg = behavior.SafeSetClashImportance ( "very" ) would return rc = -997, and errmsg = "(number expected, got string)". On the other hand, with a otherwise valid numeric argument, the call rc, errmsg = behavior.SafeSetClashImportance ( 19 ) would return rc = -1, and errmsg = "(clashing importance value outside valid range - between 0 and 1))". Calling scripts should at least check for a zero return code, and possibly log information for debugging purposes. In most cases, scripts should be able to continue, perhaps after skipping the current work item. The most common errors seem to be a bad segment number, bad band parameters, and a bad rotamer number. This recipe consists of a "safe functions" section and a test routine. The safe functions can be copied into other recipes, in whole or in part. The safefun table and the CommonError function are required for the other functions to work. The test routine is table-driven, and tests each safe function. The test table includes the expected return code for each test. An error message is printed if the actual return code does not match the expected return code. The test cases generally cover specific error cases and one or more successful rc = 0 cases. In particular, one band.SafeAddBetweenSegments case should be successful. The band from this test case, which should have band index 1, is then used in later tests. version history 1.1 LociOiling 2020/02/23 + added dialog, break down tests by group 1.2 LociOiling 2020/02/24 + added "test all" option + more tests and functions: * band.SafeAddBetweenSegments - test added for new symmetric chain option * structure.InsertResidue and structure.DeleteResidue added 1.3 LociOiling 2020/02/25 + minor cosmetic fixes + add parameter type error as a test type + rework error checking, use CommonError instead of ParseError * new return code -997 for argument type error * new return code -998 for argument count error ]]-- Recipe = "Safe Fun" Version = "1.3" ReVersion = Recipe .. " " .. Version -- -- options -- local opts = {} opts.runall = true -- test all groups if true opts.ttpasses = true -- run all tests which are expected to pass opts.ttsegrng = false -- run all tests for segment range errors opts.ttpvalue = false -- run all tests for parameter value errors opts.ttargcnt = true -- run all tests for argument count errors opts.ttargtyp = true -- run all tests for argument type errors -- -- safe functions v 1.0 -- -- safe functions use pcall to invoke functions which -- may unexpectedly terminate the script -- -- safe functions are added to the existing Foldit psuedo-classes -- -- -- common section used by all safe functions -- safefun = {} -- -- CommonError -- common routine used by safe functions, -- checks for common errors -- -- checks for errors like bad segment and bad band index -- even for functions where they don't apply -- efficiency -- not a key concern here -- -- any error that appears more than once gets tested here -- -- first return codes may not be unique -- safefun.CommonError = function ( errmsg ) local BADSEG = "segment index out of bounds" local ARGCNT = "Expected %d+ arguments." local BADARG = "bad argument #%d+ to '%?' (%b())" local EXPECT = "expected, got" local BADATOM = "atom number out of bounds" local BADBAND = "band index out of bounds" local BADSYMM = "symmetry index out of bounds" local BADACID = "invalid argument, unknown aa code" local errp, errq = errmsg:find ( BADSEG ) if errp ~= nil then return -1, errmsg end -- -- "bad argument" messages include argument type errors -- and some types of argument value errors -- trap only the argument type errors here -- local errp, errq, errd = errmsg:find ( BADARG ) if errp ~= nil then local errp2 = errd:find ( EXPECT ) if errp2 ~= nil then return -997, errmsg -- argument type error end end local errp, errq = errmsg:find ( ARGCNT ) if errp ~= nil then return -998, errmsg end local errp, errq = errmsg:find ( BADATOM ) if errp ~= nil then return -2, errmsg end local errp, errq = errmsg:find ( BADBAND ) if errp ~= nil then return -3, errmsg end local errp, errq = errmsg:find ( BADACID ) if errp ~= nil then return -2, errmsg end return 0, errmsg end -- -- end of common section used by all safe functions -- -- -- band.SafeAdd uses pcall -- to call band.Add, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the band index of the added band (number). -- -1 - bad segment number -- -2 - reserved -- -3 - reserved -- -4 - bad rho -- -5 - bad theta -- -6 - bad phi -- -99x - other error -- band.SafeAdd = function ( ... ) -- -- error messages -- local BADRHO = "rho must be" local BADTHETA = "theta must be" local BADPHI = "phi must be" -- local good, errmsg = pcall ( band.Add, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADRHO ) if errp ~= nil then return -4, err2 end local errp = err2:find ( BADTHETA ) if errp ~= nil then return -5, err2 end local errp = err2:find ( BADPHI ) if errp ~= nil then return -6, err2 end return -999, err2 end end -- -- band.SafeAddBetweenSegments uses pcall -- to call band.AddBetweenSegments, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the band index of the added band (number). -- -1 - bad segment number -- -2 - bad atom number -- -3 - bad symmetric chain number -- -99x - other error -- band.SafeAddBetweenSegments = function ( ... ) local good, errmsg = pcall ( band.AddBetweenSegments, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- band.SafeAddToBandEndpoint uses pcall -- to call band.ToBandEndpoint, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the band index of the added band (number). -- -1 - bad segment number -- -2 - bad atom number -- -3 - bad band number -- -99x - other error -- band.SafeAddToBandEndpoint = function ( ... ) local good, errmsg = pcall ( band.AddToBandEndpoint, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- band.SafeDelete uses pcall -- to call band.Delete, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -3 - bad band number -- -99x - other error -- band.SafeDelete = function ( ... ) local good, errmsg = pcall ( band.Delete, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- band.SafeDisable uses pcall -- to call band.Disable, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -3 - bad band number -- -99x - other error -- band.SafeDisable = function ( ... ) local good, errmsg = pcall ( band.Disable, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- band.SafeGetGoalLength uses pcall -- to call band.GetGoalLength, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the goal length -- of the specifed band (number). -- -3 - bad band number -- -99x - other error -- band.SafeGetGoalLength = function ( ... ) local good, errmsg = pcall ( band.GetGoalLength, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- band.SafeGetLength uses pcall -- to call band.GetLength, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the current length -- of the specifed band (number). -- -3 - bad band number -- -99x - other error -- band.SafeGetLength = function ( ... ) local good, errmsg = pcall ( band.GetLength, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- band.SafeGetStrength uses pcall -- to call band.GetStrength, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the strength -- of the specified band (number). -- -3 - bad band number -- -99x - other error -- band.SafeGetStrength = function ( ... ) local good, errmsg = pcall ( band.GetStrength, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- -- band.SafeSetGoalLength uses pcall -- to call band.SetGoalLength, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful -- -1 - reserved -- -2 - reserved -- -3 - bad band number -- -4 - bad band length -- -99x - other error -- band.SafeSetGoalLength = function ( ... ) -- -- error messages -- local BADLEN = "band length out of bounds" -- local good, errmsg = pcall ( band.SetGoalLength, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADLEN ) if errp ~= nil then return -4, err2 end return -999, err2 end end -- -- band.SafeSetStrength uses pcall -- to call band.SetStrength, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful -- -1 - reserved -- -2 - reserved -- -3 - bad band number -- -4 - bad band strength -- -99x - other error -- band.SafeSetStrength = function ( ... ) -- -- error messages -- local BADSTREN = "band strength out of bounds" -- local good, errmsg = pcall ( band.SetStrength, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADSTREN ) if errp ~= nil then return -4, err2 end return -999, err2 end end -- -- behavior.SafeSetClashImportance uses pcall -- to call behavior.SetClashImportance, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad CI -- -99x - other error -- behavior.SafeSetClashImportance = function ( ... ) -- -- error message -- local BADCLASH = "clashing importance value outside valid range" -- local good, errmsg = pcall ( behavior.SetClashImportance, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADCLASH ) if errp ~= nil then return -1, err2 end return -999, err2 end end -- -- contactmap.SafeIsContact uses pcall -- to call contactmap.IsContact, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the contact status -- of the specified segments (boolean). -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- contactmap.SafeIsContact = function ( ... ) local good, errmsg = pcall ( contactmap.IsContact, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- freeze.SafeFreeze uses pcall -- to call freeze.Freeze, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- freeze.SafeFreeze = function ( ... ) local good, errmsg = pcall ( freeze.Freeze, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- freeze.SafeIsFrozen uses pcall -- to call freeze.IsFrozen, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the freeze status -- of the specified segment (boolean). -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- freeze.SafeIsFrozen = function ( ... ) local good, errmsg = pcall ( freeze.IsFrozen, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- freeze.SafeUnfreeze uses pcall -- to call freeze.Unfreeze, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- freeze.SafeUnfreeze = function ( ... ) local good, errmsg = pcall ( freeze.Unfreeze, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- rotamer.SafeGetCount uses pcall -- to call rotamer.GetCount, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the rotamer count -- of the specified segment (boolean). -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- rotamer.SafeGetCount = function ( ... ) local good, errmsg = pcall ( rotamer.GetCount, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- rotamer.SafeSetRotamer uses pcall -- to call rotamer.SetRotamer, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -2 - bad rotamer index -- -99x - other error -- rotamer.SafeSetRotamer = function ( ... ) -- -- error messages -- local BADSNAP = "snap index out of bounds" -- local good, errmsg = pcall ( rotamer.SetRotamer, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADSNAP ) if errp ~= nil then return -2, err2 end return -999, err2 end end -- -- selection.SafeIsSelected uses pcall -- to call selection.IsSelected, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the selection status -- of the specified segment (boolean). -- -1 - bad segment index -- -99x - other error -- selection.SafeIsSelected = function ( ... ) local good, errmsg = pcall ( selection.IsSelected, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- -- selection.SafeSelect uses pcall -- to call selection.Select, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- selection.SafeSelect = function ( ... ) local good, errmsg = pcall ( selection.Select, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- selection.SafeSelectRange uses pcall -- to call selection.SelectRange, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- selection.SafeSelectRange = function ( ... ) local good, errmsg = pcall ( selection.SelectRange, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeCanMutate uses pcall -- to call structure.CanMutate, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the mutability status -- of the specified segment (boolean). -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- structure.SafeCanMutate = function ( ... ) local good, errmsg = pcall ( structure.CanMutate, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeDeleteCut uses pcall -- to call structure.DeleteCut, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- structure.SafeDeleteCut = function ( ... ) local good, errmsg = pcall ( structure.DeleteCut, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeDeleteResidue uses pcall -- to call structure.DeleteResidue, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- structure.SafeDeleteResidue = function ( ... ) local good, errmsg = pcall ( structure.DeleteResidue, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeGetAminoAcid uses pcall -- to call structure.GetAminoAcid, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the one-letter amino acid code -- of the specified segment (string). -- -1 - bad segment index -- -99x - other error -- structure.SafeGetAminoAcid = function ( ... ) local good, errmsg = pcall ( structure.GetAminoAcid, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeGetAtomCount uses pcall -- to call structure.GetAtomCount, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the atom count -- of the specified segment (number). -- -1 - bad segment index -- -99x - other error -- structure.SafeGetAtomCount = function ( ... ) local good, errmsg = pcall ( structure.GetAtomCount, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeGetDistance uses pcall -- to call structure.GetDistance, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned -- value is the distance -- 0 - successful, second returned value is -- the distance between -- the specified segments (number). -- -1 - bad segment index -- -99x - other error -- structure.SafeGetDistance = function ( ... ) local good, errmsg = pcall ( structure.GetDistance, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeGetNote uses pcall -- to call structure.GetNote, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the note text -- for the specified segment (string). -- -1 - bad segment index -- -99x - other error -- structure.SafeGetNote = function ( ... ) local good, errmsg = pcall ( structure.GetNote, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeGetSecondaryStructure uses pcall -- to call structure.GetSecondaryStructure, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the one-letter secondary structure code -- of the specified segment (string). -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- structure.SafeGetSecondaryStructure = function ( ... ) local good, errmsg = pcall ( structure.GetSecondaryStructure, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeInsertCut uses pcall -- to call structure.InsertCut, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- structure.SafeInsertCut = function ( ... ) local good, errmsg = pcall ( structure.InsertCut, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeInsertResidue uses pcall -- to call structure.InsertResidue, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- structure.SafeInsertResidue = function ( ... ) local good, errmsg = pcall ( structure.InsertResidue, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeIsHydrophobic uses pcall -- to call structure.IsHydrophobic, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the hydrophobicity status -- of the specified segment (boolean). -- -1 - bad segment index -- -99x - other error -- structure.SafeIsHydrophobic = function ( ... ) local good, errmsg = pcall ( structure.IsHydrophobic, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeIsLocked uses pcall -- to call structure.IsLocked, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the locked status -- of the specified segment (boolean). -- -1 - bad segment index -- -99x - other error -- structure.SafeIsLocked = function ( ... ) local good, errmsg = pcall ( structure.IsLocked, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeIsMutable uses pcall -- to call structure.IsMutable, returning -- a numeric return code. -- -- If the return code is non-zero, -- an error message is also returned. -- -- The return codes are: -- -- 0 - successful, second returned value is -- the mutability status -- of the specified segment (boolean). -- -1 - bad segment index -- -99x - other error -- structure.SafeIsMutable = function ( ... ) local good, errmsg = pcall ( structure.IsMutable, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeSetAminoAcid uses pcall -- to call structure.SetAminoAcid, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -2 - bad amino acid code -- -99x - other error -- structure.SafeSetAminoAcid = function ( ... ) local good, errmsg = pcall ( structure.SetAminoAcid, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeSetAminoAcidSelected uses pcall -- to call structure.SetAminoAcidSelected, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -2 - bad amino acid code -- -99x - other error -- structure.SafeSetAminoAcidSelected = function ( ... ) local good, errmsg = pcall ( structure.SetAminoAcidSelected, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeSetNote uses pcall -- to call structure.SetNote, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -99x - other error -- structure.SafeSetNote = function ( ... ) local good, errmsg = pcall ( structure.SetNote, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end return -999, err2 end end -- -- structure.SafeSetSecondaryStructure uses pcall -- to call structure.SetSecondaryStructure, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -1 - bad segment index -- -2 - bad secondary structure code -- -99x - other error -- structure.SafeSetSecondaryStructure = function ( ... ) -- -- error messages -- local BADCODE = "invalid argument" local good, errmsg = pcall ( structure.SetSecondaryStructure, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADCODE ) if errp ~= nil then return -2, err2 end return -999, err2 end end -- -- structure.SafeSetSecondaryStructureSelected uses pcall -- to call structure.SetSecondaryStructureSelected, returning -- a numeric return code and an error message. -- -- The return codes are: -- -- 0 - successful, error message is nil -- -2 - bad secondary structure code -- -99x - other error -- structure.SafeSetSecondaryStructureSelected = function ( ... ) -- -- error message for an invalid secondary structure code -- local BADCODE = "invalid argument" local good, errmsg = pcall ( structure.SetSecondaryStructureSelected, unpack ( arg ) ) if good then return 0, errmsg else local crc, err2 = safefun.CommonError ( errmsg ) if crc ~= 0 then return crc, err2 end local errp = err2:find ( BADCODE ) if errp ~= nil then return -2, err2 end return -999, err2 end end -- -- end of safe functions v 1.0 -- -- -- try to terminate the recipe -- function main () print ( ReVersion ) print ( "Puzzle: " .. puzzle.GetName () ) print ( "Track: " .. ui.GetTrackName () ) save.Quicksave ( 3 ) save.SaveSecondaryStructure () band.DeleteAll () local segCnt = structure.GetCount () print ( "Segment count = " .. segCnt ) -- -- many functions use segment indexes, -- define some useful ones -- local SEGOUT = segCnt + 1 local SEGFOO1 = -1 local SEGFOO2 = -999 local SEGFOO3 = 999 local SEGMID = math.floor ( segCnt / 2 ) print ( "Middle segment = " .. SEGMID ) -- -- assorted other bad values -- local HIATOM = 99 -- atom number local HIROTA = 999 -- rotamer number local HIBAND = 999 local LOBAND = -1 local LNGBND = 11000 local SHRTBND= -1 local STRNGBND = 12 local WEAKBND = -1 local HIRHO = 11000 -- you take the high rho local LORHO = -39 -- and I'll take the low rho local OKRHO = 20 -- and this one is just right local HITHETA = 4.0 local LOTHETA = -1.0 local OKTHETA = 3.14 / 2 local HIPHI = 3.14 * 3 local LOPHI = -1.0 local OKPHI = 3.14 local HISYM = 36 -- when does an oligomer become a polymer? -- -- function table - one entry per function to be tested -- -- somewhat repetitive and repetitious, but foldit does -- not allow access to the Lua "debug" functions -- local FUNCNAME = 1 local FUNCCALL = 2 local func = { Add = { "band.SafeAdd", band.SafeAdd, }, AddBetweenSegments = { "band.SafeAddBetweenSegments", band.SafeAddBetweenSegments, }, AddToBandEndpoint = { "band.SafeAddToBandEndpoint", band.SafeAddToBandEndpoint, }, Delete = { "band.SafeDelete", band.SafeDelete, }, Disable = { "band.SafeDisable", band.SafeDisable, }, GetGoalLength = { "band.SafeGetGoalLength", band.SafeGetGoalLength, }, GetLength = { "band.SafeGetLength", band.SafeGetLength, }, GetStrength = { "band.SafeGetStrength", band.SafeGetStrength, }, SetGoalLength = { "band.SafeSetGoalLength", band.SafeSetGoalLength, }, SetStrength = { "band.SafeSetStrength", band.SafeSetStrength, }, SetClashImportance = { "behavior.SafeSetClashImportance", behavior.SafeSetClashImportance, }, IsContact = { "contactmap.SafeIsContact", contactmap.SafeIsContact, }, Freeze = { "freeze.SafeFreeze", freeze.SafeFreeze, }, IsFrozen = { "freeze.SafeIsFrozen", freeze.SafeIsFrozen, }, Unfreeze = { "freeze.SafeUnfreeze", freeze.SafeUnfreeze, }, GetCount = { "rotamer.SafeGetCount", rotamer.SafeGetCount, }, SetRotamer = { "rotamer.SafeSetRotamer", rotamer.SafeSetRotamer, }, Select = { "selection.SafeSelect", selection.SafeSelect, }, IsSelected = { "selection.SafeIsSelected", selection.SafeIsSelected, }, SelectRange = { "selection.SafeSelectRange", selection.SafeSelectRange, }, CanMutate = { "structure.SafeCanMutate", structure.SafeCanMutate, }, DeleteCut = { "structure.SafeDeleteCut", structure.SafeDeleteCut, }, DeleteResidue = { "structure.SafeDeleteResidue", structure.SafeDeleteResidue, }, GetAminoAcid = { "structure.SafeGetAminoAcid", structure.SafeGetAminoAcid, }, GetAtomCount = { "structure.SafeGetAtomCount", structure.SafeGetAtomCount, }, GetDistance = { "structure.SafeGetDistance", structure.SafeGetDistance, }, GetNote = { "structure.SafeGetNote", structure.SafeGetNote, }, GetSecondaryStructure = { "structure.SafeGetSecondaryStructure", structure.SafeGetSecondaryStructure, }, InsertCut = { "structure.SafeInsertCut", structure.SafeInsertCut, }, InsertResidue = { "structure.SafeInsertResidue", structure.SafeInsertResidue, }, IsHydrophobic = { "structure.SafeIsHydrophobic", structure.SafeIsHydrophobic, }, IsLocked = { "structure.SafeIsLocked", structure.SafeIsLocked, }, IsMutable = { "structure.SafeIsMutable", structure.SafeIsMutable, }, SetAminoAcid = { "structure.SafeSetAminoAcid", structure.SafeSetAminoAcid, }, SetAminoAcidSelected = { "structure.SafeSetAminoAcidSelected", structure.SafeSetAminoAcidSelected, }, SetNote = { "structure.SafeSetNote", structure.SafeSetNote, }, SetSecondaryStructure = { "structure.SafeSetSecondaryStructure", structure.SafeSetSecondaryStructure, }, SetSecondaryStructureSelected = { "structure.SafeSetSecondaryStructureSelected", structure.SafeSetSecondaryStructureSelected, }, } -- -- test table - one entry per test, grouped by function -- -- first column is a reference to the function table -- second column is the expected return code from the "safe" function -- third "column" is a table containing the arguments passed to the function -- local TESTFUNC = 1 -- function table entry local TESTEXRC = 2 -- expected return code local TESTARGZ = 3 -- argument array local TESTTYPE = 4 -- test type -- -- test types - intended to be bit flags, but Lua 5.1 doesn't support them -- TTPASSES = 0x01 -- expected to succeed TTSEGRNG = 0x02 -- segment range error TTPVALUE = 0x04 -- parameter value error TTARGCNT = 0x08 -- argument count error TTARGTYP = 0x10 -- argument type error -- -- band functions -- local bandtest = { { func.Add, -1, { 1, SEGMID, SEGOUT, OKRHO, OKTHETA, OKPHI }, TTSEGRNG, }, { func.Add, -4, { 1, SEGMID, segCnt, HIRHO, OKTHETA, OKPHI }, TTPVALUE, }, { func.Add, -4, { 1, SEGMID, segCnt, LORHO, OKTHETA, OKPHI }, TTPVALUE, }, { func.Add, -5, { 1, SEGMID, segCnt, OKRHO, HITHETA, OKPHI }, TTPVALUE, }, { func.Add, -5, { 1, SEGMID, segCnt, OKRHO, LOTHETA, OKPHI }, TTPVALUE, }, { func.Add, -6, { 1, SEGMID, segCnt, OKRHO, OKTHETA, HIPHI }, TTPVALUE, }, { func.Add, -6, { 1, SEGMID, segCnt, OKRHO, OKTHETA, LOPHI }, TTPVALUE, }, { func.AddBetweenSegments, -1, { 1, SEGOUT, }, TTSEGRNG, }, { func.AddBetweenSegments, -1, { SEGFOO1, segCnt, }, TTSEGRNG, }, { func.AddBetweenSegments, -1, { SEGFOO2, SEGFOO2 }, TTSEGRNG, }, { func.AddBetweenSegments, -997, { "foo", "bar", }, TTARGTYP, }, { func.AddBetweenSegments, -2, { 1, SEGMID, HIATOM, HIATOM }, TTPVALUE, }, { func.AddBetweenSegments, -3, { 1, 10, nil, nil, HISYM, }, TTPVALUE, }, { func.AddBetweenSegments, 0, { 1, 10, }, TTPASSES, },--used below { func.AddBetweenSegments, 0, { 2, 10, }, TTPASSES, },--used below { func.AddToBandEndpoint, 0, { 1, 1, }, TTPASSES, },--uses band { func.AddToBandEndpoint, -2, { 1, 1, HIATOM, }, TTPVALUE, },--uses band { func.AddToBandEndpoint, -1, { SEGFOO1, 1, }, TTSEGRNG, }, { func.AddToBandEndpoint, -3, { 5, 13, }, TTPVALUE, }, { func.AddToBandEndpoint, -1, { SEGFOO3, 1, }, TTSEGRNG, }, { func.AddToBandEndpoint, -997, { "foo", "bar", }, TTARGTYP, }, { func.Delete, 0, { 2, }, TTPASSES, },--uses band { func.Delete, -3, { HIBAND, }, TTPVALUE, }, { func.Delete, -3, { LOBAND, }, TTPVALUE, }, { func.Disable, 0, { 1, }, TTPASSES, },--uses band { func.Disable, -3, { HIBAND, }, TTPVALUE, }, { func.Disable, -3, { LOBAND, }, TTPVALUE, }, { func.GetGoalLength, 0, { 1, }, TTPASSES, },--uses band { func.GetGoalLength, -3, { HIBAND, }, TTPVALUE, }, { func.GetGoalLength, -3, { LOBAND, }, TTPVALUE, }, { func.GetLength, 0, { 1, }, TTPASSES, },--uses band { func.GetLength, -3, { HIBAND, }, TTPVALUE, }, { func.GetLength, -3, { LOBAND, }, TTPVALUE, }, { func.GetStrength, 0, { 1, }, TTPASSES, },--uses band { func.GetStrength, -3, { HIBAND, }, TTPVALUE, }, { func.GetStrength, -3, { LOBAND, }, TTPVALUE, }, { func.SetGoalLength, 0, { 1, 1, }, TTPASSES, },--uses band { func.SetGoalLength, -3, { HIBAND, 1, }, TTPVALUE, }, { func.SetGoalLength, -3, { LOBAND, 1, }, TTPVALUE, }, { func.SetGoalLength, -4, { 1, LNGBND, }, TTPVALUE, },--uses band { func.SetGoalLength, -4, { 1, SHRTBND, }, TTPVALUE, },--uses band { func.SetStrength, 0, { 1, 1, }, TTPASSES, },--uses band { func.SetStrength, -3, { HIBAND, 1, }, TTPVALUE, }, { func.SetStrength, -3, { LOBAND, 1, }, TTPVALUE, }, { func.SetStrength, -4, { 1, STRNGBND, }, TTPVALUE, },--uses band { func.SetStrength, -4, { 1, WEAKBND, }, TTPVALUE, },--uses band } -- -- behavior functions -- local bhavtest = { { func.SetClashImportance, -997, { "foo", }, TTARGTYP, }, { func.SetClashImportance, -1, { -1, }, TTPVALUE, }, { func.SetClashImportance, -1, { SEGFOO3, }, TTPVALUE, }, { func.SetClashImportance, 0, { 1, }, TTPASSES, }, } -- -- contactmap functions -- local conttest = { { func.IsContact, 0, { 1, segCnt, }, TTPASSES, }, { func.IsContact, -1, { 1, SEGOUT, }, TTSEGRNG, }, { func.IsContact, -1, { SEGFOO1, segCnt, }, TTSEGRNG, }, } -- -- freeze functions -- local frzetest = { { func.Freeze, 0, { 1, true, true, }, TTPASSES, }, { func.Freeze, -1, { SEGOUT, true, true, }, TTSEGRNG, }, { func.Freeze, -1, { SEGFOO1, true, true, }, TTSEGRNG, }, { func.Freeze, -998, { 1, true, }, TTARGCNT, }, { func.Freeze, -998, { 1, true, }, TTARGCNT, }, { func.IsFrozen, 0, { 1, }, TTPASSES, }, { func.IsFrozen, -1, { SEGOUT, }, TTSEGRNG, }, { func.IsFrozen, -1, { SEGFOO1, }, TTSEGRNG, }, { func.Unfreeze, 0, { 1, true, true, }, TTPASSES, }, { func.Unfreeze, -1, { SEGOUT, true, true, }, TTSEGRNG, }, { func.Unfreeze, -1, { SEGFOO1, true, true, }, TTSEGRNG, }, { func.Unfreeze, -998, { 1, true, }, TTARGCNT, }, { func.Unfreeze, -998, { 1, true, }, TTARGCNT, }, } -- -- rotamer functions -- local rotatest = { { func.GetCount, 0, { 1, }, TTPASSES, }, { func.GetCount, -1, { SEGOUT, }, TTSEGRNG, }, { func.GetCount, -1, { SEGFOO1, }, TTSEGRNG, }, { func.SetRotamer, -2, { 1, HIROTA, }, TTPVALUE, }, { func.SetRotamer, -1, { SEGFOO1, 1 }, TTSEGRNG, }, { func.SetRotamer, -1, { SEGFOO1, HIROTA, }, TTSEGRNG, }, } -- -- selection functions -- local seletest = { { func.IsSelected, -997, { "foo", }, TTARGTYP, }, { func.IsSelected, -1, { -1, }, TTSEGRNG, }, -- { func.IsSelected, -1, { SEGFOO3, }, TTSEGRNG, }, { func.IsSelected, -1, { SEGOUT, }, TTSEGRNG, }, { func.Select, -997, { "foo", }, TTARGTYP, }, { func.Select, -1, { -1, }, TTSEGRNG, }, -- { func.Select, -1, { SEGFOO3, }, TTSEGRNG, }, { func.Select, -1, { SEGOUT, }, TTSEGRNG, }, { func.Select, -997, { "next error OK" }, TTARGTYP, },--test error { func.Select, 0, { 1, 777, }, TTSEGRNG, },--test error { func.Select, -997, { "prev error OK" }, TTARGTYP, },--test error { func.SelectRange, -1, { 1, SEGOUT, }, TTSEGRNG, }, { func.SelectRange, -1, { SEGFOO1, segCnt }, TTSEGRNG, }, { func.SelectRange, -1, { SEGFOO2, SEGFOO2 }, TTSEGRNG, }, { func.SelectRange, -998, { 1, SEGMID, 999 }, TTARGCNT, }, } -- -- structure functions -- local strutest = { { func.CanMutate, -1, { SEGOUT, "a", }, TTSEGRNG, }, { func.CanMutate, -1, { SEGFOO1, "a", }, TTSEGRNG, }, { func.CanMutate, 0, { 1, "a", }, TTPASSES, }, { func.CanMutate, -2, { 1, "x", }, TTARGVAL, }, { func.DeleteCut, -1, { SEGOUT, }, TTSEGRNG, }, { func.DeleteCut, -1, { SEGFOO1, }, TTSEGRNG, }, { func.DeleteResidue, -1, { SEGOUT, }, TTSEGRNG, }, { func.DeleteResidue, -1, { SEGFOO1, }, TTSEGRNG, }, { func.GetAminoAcid, -1, { SEGOUT, }, TTSEGRNG, }, { func.GetAminoAcid, -1, { SEGFOO1, }, TTSEGRNG, }, { func.GetAtomCount, -1, { SEGOUT, }, TTSEGRNG, }, { func.GetAtomCount, -1, { SEGFOO1, }, TTSEGRNG, }, { func.GetDistance, -1, { 1, SEGOUT, }, TTSEGRNG, }, { func.GetDistance, -1, { 1, SEGFOO1, }, TTSEGRNG, }, { func.GetDistance, -1, { SEGFOO1, 10, }, TTSEGRNG, }, { func.GetDistance, -1, { SEGOUT, 10, }, TTSEGRNG, }, { func.GetNote, -1, { SEGOUT, }, TTSEGRNG, }, { func.GetNote, -1, { SEGFOO1, }, TTSEGRNG, }, { func.GetSecondaryStructure, -1, { SEGOUT, }, TTSEGRNG, }, { func.GetSecondaryStructure, -1, { SEGFOO1, }, TTSEGRNG, }, { func.InsertCut, -1, { SEGOUT, }, TTSEGRNG, }, { func.InsertCut, -1, { SEGFOO1, }, TTSEGRNG, }, { func.InsertResidue, -1, { SEGOUT, }, TTSEGRNG, }, { func.InsertResidue, -1, { SEGFOO1, }, TTSEGRNG, }, { func.IsHydrophobic, -1, { SEGOUT, }, TTSEGRNG, }, { func.IsHydrophobic, -1, { SEGFOO1, }, TTSEGRNG, }, { func.IsLocked, -1, { SEGOUT, }, TTSEGRNG, }, { func.IsLocked, -1, { SEGFOO1, }, TTSEGRNG, }, { func.IsMutable, -1, { SEGOUT, }, TTSEGRNG, }, { func.IsMutable, -1, { SEGFOO1, }, TTSEGRNG, }, { func.SetAminoAcid, -2, { 1, "b", }, TTPVALUE, }, { func.SetAminoAcid, -1, { -1, "a", }, TTSEGRNG, }, { func.SetAminoAcid, -2, { 1, "b", }, TTARGVAL, }, { func.SetAminoAcidSelected, 0, { "a", }, TTPASSES, }, { func.SetAminoAcidSelected, -2, { "b", }, TTARGVAL, }, { func.SetNote, -1, { SEGOUT, "test note", }, TTSEGRNG, }, { func.SetNote, -1, { SEGFOO1, "test note", }, TTSEGRNG, }, { func.SetNote, -998, { 1, }, TTARGCNT, }, { func.SetNote, 0, { 1, "test note", }, TTPASSES, }, { func.SetSecondaryStructure, -2, { 1, "R", }, TTPVALUE, }, { func.SetSecondaryStructure, -1, { SEGFOO1, "X", }, TTSEGRNG, }, { func.SetSecondaryStructure, -1, { SEGFOO1, "L", }, TTSEGRNG, }, { func.SetSecondaryStructureSelected, -2, { "R", }, TTSEGRNG, }, { func.SetSecondaryStructureSelected, -2, { "X", }, TTPVALUE, }, { func.SetSecondaryStructureSelected, 0, { "L", }, TTPASSES, }, } local GPARM = 1 local GNAME = 2 local GDOIT = 3 local testgrpz = { { bandtest, "band", false, }, { bhavtest, "behavior", false, }, { conttest, "contactmap", false, }, { frzetest, "freeze", false, }, { rotatest, "rotamer", false, }, { seletest, "selection", false, }, { strutest, "structure", false, }, } -- -- inline dialog! -- local ask = dialog.CreateDialog ( ReVersion ) ask.l001 = dialog.AddLabel ( "select the type of tests to perform" ) ask.ttpasses = dialog.AddCheckbox ( "run tests expected to pass", opts.ttpasses ) ask.ttsegrng = dialog.AddCheckbox ( "run tests for segment range errors", opts.ttsegrng ) ask.ttpvalue = dialog.AddCheckbox ( "run tests for parameter value errors", opts.ttpvalue ) ask.ttargcnt = dialog.AddCheckbox ( "run tests for parameter count errors", opts.ttargcnt ) ask.ttargtyp = dialog.AddCheckbox ( "run tests for parameter type errors", opts.ttargtyp ) ask.l005 = dialog.AddLabel ( "" ) ask.l006 = dialog.AddLabel ( "Test functions in all Foldit function groups:" ) ask.runall = dialog.AddCheckbox ( "all groups", opts.runall ) ask.l010 = dialog.AddLabel ( "or, uncheck \"all groups\" and select the groups to test:" ) for ii = 1, #testgrpz do ask [ testgrpz [ ii ] [ GNAME ] ] = dialog.AddCheckbox ( testgrpz [ ii ] [ GNAME ], testgrpz [ ii ] [ GDOIT ] ) end ask.OK = dialog.AddButton ( "OK", 1 ) ask.cancel = dialog.AddButton ( "Cancel", -1 ) local askrc = dialog.Show ( ask ) local grpcnt = 0 if askrc > 0 then opts.ttpasses = ask.ttpasses.value opts.ttsegrng = ask.ttsegrng.value opts.ttpvalue = ask.ttpvalue.value opts.ttargcnt = ask.ttargcnt.value opts.ttargtyp = ask.ttargtyp.value opts.runall = ask.runall.value if opts.runall then for ii = 1, #testgrpz do testgrpz [ ii ] [ GDOIT ] = true grpcnt = grpcnt + 1 end else for ii = 1, #testgrpz do testgrpz [ ii ] [ GDOIT ] = ask [ testgrpz [ ii ] [ GNAME ] ].value if testgrpz [ ii ] [ GDOIT ] then grpcnt = grpcnt + 1 end end end else cleanup () end if opts.ttpasses then print ( "running test where the function should succeed" ) end if opts.ttsegrng then print ( "running tests for segment range errors" ) end if opts.ttpvalue then print ( "running tests for invalid parameter value errors" ) end if opts.ttargcnt then print ( "running tests for invalid parameter count errors" ) end if opts.ttargtyp then print ( "running tests for invalid parameter type errors" ) end print ( "" ) print ( grpcnt .. " groups selected" ) if grpcnt == 0 then print ( "no tests selected, cleaning up" ) cleanup () end print ( "--" ) -- -- all this inline too! maybe we need a function or two! -- local totlruns = 0 local totlfailz = 0 local totlfuns = 0 local totlskipz = 0 for gg = 1, #testgrpz do if testgrpz [ gg ] [ GDOIT ] then print ( "-----------------------------------------" ) print ( "testing functions in the " .. testgrpz [ gg ] [ GNAME ] .. " group" ) local lastfun = nil local testruns = 0 local testfailz = 0 local testfuns = 0 local testskipz = 0 local testtabl = testgrpz [ gg ] [ GPARM ] for tt = 1, #testtabl do local ttp = testtabl [ tt ] [ TESTTYPE ] if ( opts.ttpasses and ( ttp == TTPASSES ) ) or ( opts.ttsegrng and ( ttp == TTSEGRNG ) ) or ( opts.ttpvalue and ( ttp == TTPVALUE ) ) or ( opts.ttargcnt and ( ttp == TTARGCNT ) ) or ( opts.ttargtyp and ( ttp == TTARGTYP ) ) then testruns = testruns + 1 if testtabl [ tt ] [ TESTFUNC ] ~= lastfun then testfuns = testfuns + 1 lastfun = testtabl [ tt ] [ TESTFUNC ] print ( "-----------------------------------------" ) end local argz = testtabl [ tt ] [ TESTARGZ ] local calltext -- -- format a description of the function call -- calltext = lastfun [ FUNCNAME ] .. " ( " for ii = 1, #argz do local argtext if type ( argz [ ii ] ) == "string" then argtext = "\"" .. argz [ ii ] .. "\"" else argtext = tostring ( argz [ ii ] ) end if ii > 1 then calltext = calltext .. ", " end calltext = calltext .. argtext end calltext = calltext .. " )" -- -- call the function -- --[[ local ask = dialog.CreateDialog ( ReVersion ) ask.testnum = dialog.AddLabel ( "test # " .. tt ) ask.calltext = dialog.AddTextbox ( "next: ", calltext ) structure.SetNote ( 1, "next test: " .. tt .. " - " .. calltext ) ask.OK = dialog.AddButton ( "OK", 1 ) dialog.Show ( ask ) ]]-- print ( "test # " .. tt .. ": calling " .. calltext ) local rc, errmsg rc, errmsg = lastfun [ FUNCCALL ] ( unpack ( argz ) ) -- -- report the results -- calltext = calltext .. ", rc = " .. rc if rc < 0 and errmsg ~= nil then calltext = calltext .. ", errmsg = \"" .. errmsg .. "\"" end print ( calltext ) if rc ~= testtabl [ tt ] [ TESTEXRC ] then testfailz = testfailz + 1 if rc == 0 then print ( "ERROR: unexpected rc = 0" ) else print ( "ERROR: unexpected rc = " .. rc .. ", errmsg = \"" .. errmsg .. "\"" ) end end else testskipz = testskipz + 1 end end -- print ( "-----------------------------------------" ) print ( "" ) print ( testgrpz [ gg ] [ GNAME ] .. " tests complete" ) print ( ) print ( testfuns .. " functions tested in " .. testgrpz [ gg ] [ GNAME ] .. " group" ) print ( testruns .. " tests run" ) print ( testfailz .. " test failures" ) print ( testskipz .. " tests skipped based on type" ) totlfuns = totlfuns + testfuns totlruns = totlruns + testruns totlfailz = totlfailz + testfailz totlskipz = totlskipz + testskipz -- end end print ( "-----------------------------------------" ) print ( "" ) print ( "all tests complete" ) print ( totlfuns .. " functions tested in " .. grpcnt .. " groups" ) print ( totlruns .. " total tests run" ) print ( totlfailz .. " test failures" ) print ( totlskipz .. " tests skipped based on type" ) cleanup () end function cleanup ( errmsg ) print ( "---" ) -- -- model 100 - print recipe name, puzzle, track, time, score, and gain -- local reason local start, stop, line, msg if errmsg == nil then reason = "complete" else -- -- model 120 - civilized error reporting, -- thanks to Bruno K. and Jean-Bob -- start, stop, line, msg = errmsg:find ( ":(%d+):%s()" ) if msg ~= nil then errmsg = errmsg:sub ( msg, #errmsg ) end if errmsg:find ( "Cancelled" ) ~= nil then reason = "cancelled" else reason = "error" end end print ( ReVersion .. " " .. reason ) print ( "Puzzle: " .. puzzle.GetName () ) print ( "Track: " .. ui.GetTrackName () ) if reason == "error" then print ( "Unexpected error detected" ) if line ~= nil then print ( "Error line: " .. line ) end print ( "Error: \"" .. errmsg .. "\"" ) end -- -- model 130 - reset clash importance, clear selections, restore structures, etc. -- save.Quickload ( 3 ) save.LoadSecondaryStructure () selection.DeselectAll () behavior.SetClashImportance ( 1.0 ) band.DeleteAll () end -- main call xpcall ( main, cleanup ) --end of script

Comments


LociOiling Lv 1

Safe Fun tests many of the Foldit Lua functions for common parameter errors.

Safe Fun demonstrates the use of the Lua pcall function to trap errors which normally terminate a recipe. Each function tested has a wrapper function which handles the pcall and checks for specific errors.

Safe Fun lets you select functions by group:

behavior
contactmap
freeze
rotamer
selection
structure

Some of the newer functions are not yet included.

As of February 2020, segment range errors crash Foldit, so many of the groups will fail. The behavior group doesn't work with individual segments, so it can be used to test normal completion of the recipe.

LociOiling Lv 1

Version 1.2 expands the dialog to allow selecting test by type:

  • tests where the underlying function succeeds
  • tests for segment range errors
  • tests parameter value errors
  • tests for parameter count errors </ul> By default, only the first and last of these types are checked. Segment range errors and at least some other parameter value errors cause a crash. The "all groups" option is now true by default. To test specific groups, uncheck "all groups" and check the individual groups to be tested. The default settings should result in a test run that doesn't crash the client. Unfortunately, the tests being skipped greatly outnumber the non-crashy ones.

LociOiling Lv 1

Version 1.3 allows testing for parameter type errors, along with the type of errors allowed in the previous version.

The error checking logic has been streamlined somewhat. The function CommonError is used to check for errors that may occur in several functions.

In two new return codes have been added. Return code -997 indicates a parameter type error, such as a boolean specified where a numeric was expected. Return code -998 indicates an error in the number of parameters specified.

The new features allow running a greater number of tests. Segment range errors still crash the client, along with atom number errors on certain band calls. Avoiding the band functions and the segment range error tests should result in a clean run, although it still means many tests are being skipped.

An uppercase "ERROR" appears in the scriptlog when there's an expected return code from one of the tests. Unexpected errors of this type appear in the "test failure" counts at the end of each function group and the very end of the recipe.