nexus: preliminary testing framework

This commit is contained in:
Jaron Krogel 2018-03-02 09:22:21 -05:00
parent 77df73250c
commit 161515a188
11 changed files with 1979 additions and 76 deletions

1537
nexus/executables/ntest Executable file

File diff suppressed because it is too large Load Diff

View File

@ -31,7 +31,7 @@
#====================================================================#
from generic import obj,object_interface,log,error,warn,devlog
from generic import obj,object_interface,log,error,warn
from debug import ci,interact

View File

@ -60,6 +60,12 @@ class Gamess(Simulation):
Gamess.mcppath = mcppath
#end def settings
@staticmethod
def restore_default_settings():
Gamess.ericfmt = None
Gamess.mcppath = None
#end def restore_default_settings
def post_init(self):
# gamess seems to need lots of environment variables to run properly

View File

@ -36,17 +36,30 @@ from copy import deepcopy
import cPickle
from random import randint
class generic_settings:
devlog = sys.stdout
raise_error = False
#end class generic_settings
class NexusError(Exception):
None
#end class NexusError
exit_call = sys.exit
devlog = sys.stdout
def nocopy(value):
return value
#end def nocopy
def log(*items,**kwargs):
indent=None
logfile=devlog
logfile=generic_settings.devlog
if len(kwargs)>0:
n=0
if 'indent' in kwargs:
@ -81,24 +94,36 @@ def log(*items,**kwargs):
#end def log
def message(msg,header=None,post_header=' message:',indent=' ',logfile=devlog):
def message(msg,header=None,post_header=' message:',indent=' ',logfile=None):
if logfile is None:
logfile = generic_settings.devlog
#end if
if header is None:
header = post_header.lstrip()
else:
header += post_header
#end if
log('\n '+header)
log('\n '+header,logfile=logfile)
log(msg.rstrip(),indent=indent,logfile=logfile)
#end def message
def warn(msg,header=None,indent=' ',logfile=devlog):
def warn(msg,header=None,indent=' ',logfile=None):
if logfile is None:
logfile = generic_settings.devlog
#end if
post_header=' warning:'
message(msg,header,post_header,indent,logfile)
#end def warn
def error(msg,header=None,exit=True,trace=True,indent=' ',logfile=devlog):
def error(msg,header=None,exit=True,trace=True,indent=' ',logfile=None):
if generic_settings.raise_error:
raise NexusError(msg)
#end if
if logfile is None:
logfile = generic_settings.devlog
#end if
post_header=' error:'
message(msg,header,post_header,indent,logfile)
if exit:
@ -569,9 +594,9 @@ class obj(object_interface):
def to_dict(self):
d = dict()
for k,v in self:
for k,v in self._iteritems():
if isinstance(v,obj):
d[k] = v.to_dict()
d[k] = v._to_dict()
else:
d[k] = v
#end if
@ -651,54 +676,6 @@ class obj(object_interface):
return self
#end def set_optional
def set_path(self,path,value=None):
o = self
cls = self.__class__
if isinstance(path,str):
path = path.split('/')
#end if
for p in path[0:-1]:
if not p in o:
o[p] = cls()
#end if
o = o[p]
#end for
o[path[-1]] = value
#end def set_path
def get_path(self,path,value=None):
o = self
if isinstance(path,str):
path = path.split('/')
#end if
for p in path[0:-1]:
if not p in o:
return value
#end if
o = o[p]
#end for
lp = path[-1]
if lp not in o:
return value
else:
return o[lp]
#end if
#end def get_path
def path_exists(self,path):
o = self
if isinstance(path,str):
path = path.split('/')
#end if
for p in path:
if not p in o:
return False
#end if
o = o[p]
#end for
return True
#end def path_exists
def get(self,key,value=None): # follow dict interface, no plural
if key in self:
value = self[key]
@ -855,7 +832,7 @@ class obj(object_interface):
def shallow_copy(self):
new = self.__class__()
for k,v in self._iteritems():
self[k] = v
new[k] = v
#end for
return new
#end def shallow_copy
@ -868,6 +845,54 @@ class obj(object_interface):
return new
#end def inverse
def path_exists(self,path):
o = self
if isinstance(path,str):
path = path.split('/')
#end if
for p in path:
if not p in o:
return False
#end if
o = o[p]
#end for
return True
#end def path_exists
def set_path(self,path,value=None):
o = self
cls = self.__class__
if isinstance(path,str):
path = path.split('/')
#end if
for p in path[0:-1]:
if not p in o:
o[p] = cls()
#end if
o = o[p]
#end for
o[path[-1]] = value
#end def set_path
def get_path(self,path,value=None):
o = self
if isinstance(path,str):
path = path.split('/')
#end if
for p in path[0:-1]:
if not p in o:
return value
#end if
o = o[p]
#end for
lp = path[-1]
if lp not in o:
return value
else:
return o[lp]
#end if
#end def get_path
def serial(self,s=None,path=None):
first = s is None
if first:
@ -877,7 +902,11 @@ class obj(object_interface):
for k,v in self._iteritems():
p = path+str(k)
if isinstance(v,obj):
v._serial(s,p+'/')
if len(v)==0:
s[p]=v
else:
v._serial(s,p+'/')
#end if
else:
s[p]=v
#end if
@ -920,8 +949,6 @@ class obj(object_interface):
obj.set(self,*args,**kwargs)
def _set_optional(self,*args,**kwargs):
obj.set_optional(self,*args,**kwargs)
def _set_path(self,*args,**kwargs):
obj.set_path(self,*args,**kwargs)
def _get(self,*args,**kwargs):
obj.get(self,*args,**kwargs)
def _get_optional(self,*args,**kwargs):
@ -954,6 +981,12 @@ class obj(object_interface):
obj.shallow_copy(self,*args,**kwargs)
def _inverse(self,*args,**kwargs):
return obj.inverse(self,*args,**kwargs)
def _path_exists(self,*args,**kwargs):
obj.path_exists(self,*args,**kwargs)
def _set_path(self,*args,**kwargs):
obj.set_path(self,*args,**kwargs)
def _get_path(self,*args,**kwargs):
obj.get_path(self,*args,**kwargs)
def _serial(self,*args,**kwargs):
return obj.serial(self,*args,**kwargs)
@ -971,12 +1004,12 @@ class hobj(obj):
@property
def _dict(self):
return self.__dict__
#end def __get_dict
#end def _dict
@property
def _alt(self):
return self.__dict__
#end def __alt
#end def _alt
def __len__(self):
return len(self._dict)

View File

@ -155,6 +155,12 @@ class Job(NexusCore):
cray_compilers = set(['cray'])
@staticmethod
def restore_default_settings():
Job.machine = None
#end def restore_default_settings
@staticmethod
def generate_jobid():
Job.job_count += 1
@ -924,6 +930,14 @@ class Machine(NexusCore):
Machine.add(self)
#end def __init__
def restore_default_settings(self):
self.account = None
self.local_directory = None
self.app_directory = None
self.app_directories = None
#end def restore_default_settings
def add_job(self,job):
if isinstance(job,Job):

View File

@ -27,7 +27,7 @@ import os
from generic import obj
from developer import error
from nexus_base import NexusCore,nexus_core,nexus_noncore,nexus_core_noncore
from nexus_base import NexusCore,nexus_core,nexus_noncore,nexus_core_noncore,restore_nexus_core_defaults
from machines import Job,job,Machine,Supercomputer,get_machine
from simulation import generate_simulation,input_template,multi_input_template,generate_template_input,generate_multi_template_input
from project_manager import ProjectManager
@ -104,8 +104,8 @@ class Settings(NexusCore):
ericfmt mcppath
'''.split())
pwscf_vars = set('''
vdw_table
pwscf_vars = set('''
vdw_table
'''.split())
nexus_core_vars = core_assign_vars | core_process_vars
@ -133,7 +133,7 @@ class Settings(NexusCore):
if Settings.singleton is None:
Settings.singleton = self
else:
self.error('attempted to create a second Settings object\n please just use the original')
self.error('attempted to create a second Settings object\nplease just use the original')
#end if
#end def __init__
@ -156,6 +156,9 @@ class Settings(NexusCore):
self.error('unrecognized variables provided\nyou provided: {0}\nallowed variables are: {1}'.format(sorted(not_allowed),sorted(Settings.allowed_vars)))
#end if
# restore default core default settings
restore_nexus_core_defaults()
# assign simple variables
for name in Settings.core_assign_vars:
if name in kwargs:
@ -204,9 +207,11 @@ class Settings(NexusCore):
# process gamess settings
Gamess.restore_default_settings()
Gamess.settings(**gamess_kw)
# process pwscf settings
# process pwscf settings
Pwscf.restore_default_settings()
Pwscf.settings(**pwscf_kw)
return
@ -214,6 +219,9 @@ class Settings(NexusCore):
def process_machine_settings(self,mset):
Job.restore_default_settings()
ProjectManager.restore_default_settings()
mid_set = set()
if 'machine_info' in mset:
machine_info = mset.machine_info
if isinstance(machine_info,dict) or isinstance(machine_info,obj):
@ -221,7 +229,9 @@ class Settings(NexusCore):
mname = machine_name.lower()
if Machine.exists(mname):
machine = Machine.get(mname)
machine.restore_default_settings()
machine.incorporate_user_info(minfo)
mid_set.add(id(machine))
else:
self.error('machine {0} is unknown\n cannot set machine_info'.format(machine_name))
#end if
@ -236,7 +246,11 @@ class Settings(NexusCore):
self.error('machine {0} is unknown'.format(machine_name))
#end if
Job.machine = machine_name
ProjectManager.machine = Machine.get(machine_name)
machine = Machine.get(machine_name)
ProjectManager.machine = machine
if machine is not None and id(machine) not in mid_set:
machine.restore_default_settings()
#end if
if 'account' in mset:
account = mset.account
if not isinstance(account,str):

View File

@ -38,6 +38,7 @@ from developer import DevBase
# nexus_noncore: allows read only access to some nexus_core data to non-core classes
nexus_core = obj()
nexus_noncore = obj()
nexus_core_noncore = obj()
status_modes = obj(
none = 0,
@ -61,18 +62,18 @@ modes = obj(
garbage_collector.enable()
nexus_noncore.set(
nexus_noncore_defaults = obj(
basis_dir = None,
basissets = None,
)
# core namespace elements that can be accessed by noncore classes
nexus_core_noncore = obj(
nexus_core_noncore_defaults = obj(
pseudo_dir = None, # used by: Settings, VaspInput
pseudopotentials = None, # used by: Simulation, GamessInput
)
nexus_core.set(
nexus_core_defaults = obj(
status_only = False, # used by: ProjectManager
generate_only = False, # used by: Simulation,Machine
sleep = 3, # used by: ProjectManager
@ -98,9 +99,22 @@ nexus_core.set(
status = status_modes.none, # used by: ProjectManager
emulate = False, # unused
progress_tty = False, # used by: ProjectManager
**nexus_core_noncore
**nexus_core_noncore_defaults
)
def restore_nexus_core_defaults():
nexus_core.clear()
nexus_noncore.clear()
nexus_core_noncore.clear()
nexus_core.set(**nexus_core_defaults.copy())
nexus_noncore.set(**nexus_noncore_defaults.copy())
nexus_core_noncore.transfer_from(nexus_core,keys=nexus_core_noncore_defaults.keys())
#end def restore_nexus_core_defaults
restore_nexus_core_defaults()
nexus_core_no_process = set('''
status_only generate_only sleep
'''.split())

View File

@ -31,6 +31,14 @@ def trivial(sim,*args,**kwargs):
class ProjectManager(NexusCore):
machine = None
@staticmethod
def restore_default_settings():
ProjectManager.machine = None
#end def restore_default_settings
def __init__(self):
modes = nexus_core.modes
self.persistent_modes = set([modes.submit,modes.all])

View File

@ -99,6 +99,12 @@ class Pwscf(Simulation):
Pwscf.vdw_table = vdw_table
#end def settings
@staticmethod
def restore_default_settings():
Pwscf.vdw_table = None
#end def restore_default_settings
#def propagate_identifier(self):
# self.input.control.prefix = self.identifier
##end def propagate_identifier

View File

@ -3,7 +3,8 @@ import os
import itertools
from time import clock
from numpy import ndarray,ceil
from developer import obj,ci,error as dev_error,devlog,DevBase
from generic import generic_settings
from developer import obj,ci,error as dev_error,DevBase
from physical_system import generate_physical_system
from simulation import Simulation,GenericSimulation,graph_sims
from bundle import bundle as bundle_function
@ -60,8 +61,7 @@ from qmcpack import generate_qmcpack
# should probably make temp simlist at qmcpack_workflow start
def error(msg,loc=None,exit=True,trace=True,indent=' ',logfile=devlog):
def error(msg,loc=None,exit=True,trace=True,indent=' ',logfile=generic_settings.devlog):
header = 'qmcpack_workflows'
if loc!=None:
msg+='\nfunction location: {0}'.format(loc)

271
nexus/library/testing.py Normal file
View File

@ -0,0 +1,271 @@
import os
import sys
import traceback
from numpy import ndarray,array,abs
def value_diff(v1,v2,tol=1e-6):
diff = False
if not isinstance(v1,type(v2)):
diff = True
elif isinstance(v1,(bool,int,str)):
diff = v1!=v2
elif isinstance(v1,float):
diff = abs(v1-v2)>tol
elif isinstance(v1,(list,tuple)):
v1 = array(v1,dtype=object).ravel()
v2 = array(v2,dtype=object).ravel()
for vv1,vv2 in zip(v1,v2):
diff |= value_diff(vv1,vv2)
#end for
elif isinstance(v1,ndarray):
v1 = v1.ravel()
v2 = v2.ravel()
for vv1,vv2 in zip(v1,v2):
diff |= value_diff(vv1,vv2)
#end for
elif isinstance(v1,dict):
diff = v1!=v2
elif isinstance(v1,set):
diff = v1!=v2
elif v1 is None and v2 is None:
diff = False
else:
diff = True # unsupported types
#end if
return diff
#end def value_diff
def object_diff(o1,o2,tol=1e-6,full=False):
diff1 = dict()
diff2 = dict()
o1 = o1._serial()
o2 = o2._serial()
keys1 = set(o1._keys())
keys2 = set(o2._keys())
ku1 = keys1 - keys2
ku2 = keys2 - keys1
km = keys1 & keys2
for k in ku1:
diff1[k] = o1[k]
#end for
for k in ku2:
diff2[k] = o2[k]
#end for
for k in km:
v1 = o1[k]
v2 = o2[k]
if value_diff(v1,v2,tol):
diff1[k] = v1
diff2[k] = v2
#end if
#end for
diff = len(diff1)!=0 or len(diff2)!=0
if not full:
return diff
else:
return diff,diff1,diff2
#end if
#end def object_diff
value_neq = value_diff
def value_eq(*args,**kwargs):
return not value_neq(*args,**kwargs)
#end def value_eq
object_neq = object_diff
def object_eq(*args,**kwargs):
return not object_neq(*args,**kwargs)
#end def object_eq
class NexusTestFail(Exception):
None
#end class NexusTestFail
class NexusTestMisconstructed(Exception):
None
#end class NexusTestMisconstructed
class NexusTestTripped(Exception):
None
#end class NexusTestTripped
class NexusTestBase(object):
nexus_test_dir = '.nexus_test' # ntest directory
launch_path = None # path from which ntest exe was launched
current_test = '' # current NexusTest.name
current_label = '' # current nlabel()
test_count = 0 # current test count
label_count = 0 # current label count in NexusTest.operation()
current_assert = 0 # current assert count
assert_trip = -1 # developer tool to trip assert's one by one
@staticmethod
def assert_called():
NexusTestBase.current_assert+=1
ca = NexusTestBase.current_assert
if ca==NexusTestBase.assert_trip:
raise NexusTestTripped
#end if
#end def assert_called
#end class NexusTestBase
def nlabel(label):
os.chdir(NexusTestBase.launch_path)
NexusTestBase.current_label = label
NexusTestBase.label_count += 1
#end def nlabel
def nenter(path):
test = NexusTestBase.current_test
label = NexusTestBase.current_label
tcount = str(NexusTestBase.test_count).zfill(2)
lcount = str(NexusTestBase.label_count).zfill(2)
test_dir = '{0}_{1}'.format(tcount,test)
label_dir = '{0}_{1}'.format(lcount,label)
ntdir = NexusTestBase.nexus_test_dir
nlpath = NexusTestBase.launch_path
if len(label)==0:
enter_path = os.path.join(nlpath,ntdir,test_dir)
else:
enter_path = os.path.join(nlpath,ntdir,test_dir,label_dir)
#end if
os.makedirs(enter_path)
#end def nenter
def nleave():
os.chdir(NexusTestBase.launch_path)
#end def nleave
def npass():
None
#end def npass
def nfail(exception=NexusTestFail('Nexus test failed.')):
raise exception
#end def nfail
def nassert(result):
if not isinstance(result,bool):
raise NexusTestMisconstructed
elif not result:
nfail()
else:
npass()
#end if
NexusTestBase.assert_called()
#end def nassert
class NexusTest(NexusTestBase):
status_options = dict(
unused = 0,
passed = 1,
failed = 2,
)
status_map = dict()
for k,v in status_options.iteritems():
status_map[v] = k
#end for
test_list = []
test_dict = {}
@staticmethod
def setup():
NexusTestBase.launch_path = os.path.getcwd()
#end def setup
def __init__(self,name,operation):
if not isinstance(name,str):
raise NexusTestMisconstructed
#end if
self.name = name
self.operation = operation
self.exception = None
self.status = NexusTest.status_options['unused']
NexusTest.test_list.append(self)
NexusTest.test_dict[self.name] = self
#end def __init__
@property
def unused(self):
return self.status==NexusTest.status_options['unused']
#end def unused
@property
def passed(self):
return self.status==NexusTest.status_options['passed']
#end def passed
@property
def failed(self):
return self.status==NexusTest.status_options['failed']
#end def failed
def run(self):
NexusTestBase.current_test = self.name
NexusTestBase.current_label = ''
NexusTestBase.test_count += 1
NexusTestBase.label_count = 0
os.chdir(NexusTestBase.launch_path)
try:
self.operation()
self.status=NexusTest.status_options['passed']
except Exception,e:
self.exception = e
self.traceback = sys.exc_info()[2]
self.status=NexusTest.status_options['failed']
#end try
#end def run
def message(self):
s = ''
s+='test name : {0}\n'.format(self.name)
status = NexusTest.status_map[self.status]
s+='test status : {0}\n'.format(status)
if self.failed and self.exception is not None:# and not isinstance(self.exception,NexusTestFail):
if len(NexusTestBase.current_label)>0:
s+='test sublabel: {0}\n'.format(NexusTestBase.current_label)
#end if
e = self.exception
btrace = traceback.format_tb(self.traceback)
if isinstance(e,NexusTestFail):
btrace = btrace[:-1]
elif isinstance(e,NexusTestMisconstructed):
btrace = btrace[:-1]
s+='exception : Nexus test is misconstructed. Please contact developers.\n'
else:
s+='exception : "{0}"\n'.format(e.__class__.__name__+': '+str(e).replace('\n','\n '))
#end if
s+='backtrace :\n'
for s2 in btrace:
s+=s2
#end for
#end if
return s
#end def message
#end class NexusTest