diff --git a/setup.py b/setup.py index 9913499c..590dd0c8 100755 --- a/setup.py +++ b/setup.py @@ -75,6 +75,8 @@ "src/python/frontend/climatology.py", "src/python/frontend/metadiags.py", "src/python/frontend/metadiags", + "src/python/frontend/multidiags.py", + "src/python/frontend/multidiags", "src/python/devel/update_metrics_baselines.py", "src/python/devel/update_metrics_baselines", "src/python/packages/atm_tier1b/u850.ncl", diff --git a/src/python/computation/reductions.py b/src/python/computation/reductions.py index af190d57..42821679 100644 --- a/src/python/computation/reductions.py +++ b/src/python/computation/reductions.py @@ -2856,7 +2856,8 @@ def run_cdscan( fam, famfiles, cache_path=None ): # get here. # Another problem is that units stuck in the long_name sometimes are # nonstandard. So fix them! - if hasattr(f['time'],'long_name'): + if hasattr(f['time'],'long_name') and\ + getattr(f['time'],'long_name',None) not in ['time',None,'']: time_units = f['time'].long_name else: time_units = 'days' # probably wrong but we can't go on without something @@ -2881,8 +2882,8 @@ def run_cdscan( fam, famfiles, cache_path=None ): import shlex logger.info('cdscan command line: %s', cdscan_line) try: - cdscan_line = shlex.split(cdscan_line) - cdscan.main(cdscan_line) + cdscan_lineargs = shlex.split(cdscan_line) + cdscan.main(cdscan_lineargs) except Exception,err: print "CDSCAN RUN ERROR",err import traceback,sys @@ -2890,7 +2891,8 @@ def run_cdscan( fam, famfiles, cache_path=None ): traceback.print_tb(exc_traceback) print "END ERROR LOG" logger.error( 'ERROR: cdscan terminated. This is usually fatal. The arguments were:%s\n', - cdscan_line ) + cdscan_lineargs ) + print "original cdscan_line:",cdscan_line except Exception,err: print "CDSCAN IMPORT ERROR",err import traceback diff --git a/src/python/fileio/filetable.py b/src/python/fileio/filetable.py index 28a55ddb..13499bf8 100755 --- a/src/python/fileio/filetable.py +++ b/src/python/fileio/filetable.py @@ -266,7 +266,8 @@ def addfile( self, filep, options): filesupp = get_datafile_filefmt( dfile, options ) if self.filefmt is None: self.filefmt = filesupp.name - elif self.filefmt!= filesupp.name: + elif self.filefmt!= filesupp.name and self.filefmt.find(filesupp.name)<0 and\ + filesupp.name.find(self.filefmt)<0: self.filefmt = "various" vars = filesupp.interesting_variables() if len(vars)>0: diff --git a/src/python/fileio/findfiles.py b/src/python/fileio/findfiles.py index 931b63eb..743acdb1 100755 --- a/src/python/fileio/findfiles.py +++ b/src/python/fileio/findfiles.py @@ -209,7 +209,9 @@ def _getdatafiles( self, root, filt ): def shortest_name(self): if len(self._filt.mystr())>3: - return self._filt.mystr() + shortname = self._filt.mystr() + shortname = shortname.strip('-_') + return shortname else: return self.short_name() @@ -226,6 +228,7 @@ def short_name(self): else: shortname = ','.join([os.path.basename(str(r)) for r in self._root]) # + shortname = shortname.strip('-_') return shortname def long_name(self): diff --git a/src/python/frontend/amwgmaster.py b/src/python/frontend/amwgmaster.py index a6a8f052..aa569154 100644 --- a/src/python/frontend/amwgmaster.py +++ b/src/python/frontend/amwgmaster.py @@ -361,8 +361,9 @@ diags_collection['7s']['options'] = {'logo':'no'} diags_collection['7s']['combined'] = True diags_collection['7s']['TREFHT'] = {'plottype': '7', 'obs': ['WILLMOTT_1']} -diags_collection['7s']['PS'] = {'plottype': '7', 'obs': ['NCEP_1']} -diags_collection['7s']['PSL'] = {'plottype': '7', 'obs': ['JRA25_1', 'NCEP_1']} +diags_collection['7s']['PSL'] = {'plottype': '7', 'obs': ['JRA25_1']} +diags_collection['7s']['Z3'] = {'plottype': '7', 'obs': ['JRA25_1']} + # These 4 are northern only. diff --git a/src/python/frontend/diags.py b/src/python/frontend/diags.py index 1db88310..740675d8 100755 --- a/src/python/frontend/diags.py +++ b/src/python/frontend/diags.py @@ -83,6 +83,8 @@ def setManualColormap(canvas=None, level=0): def getNames(opts, model, obs): """ The purpose of this kludge is to get the names of the model and obs specified on the command line for output on the graphic. + These particular names should be sufficiently unique and human-readable, and + reasonably short. Each of model and obs should be a list of instances of basic_filetable. This function returns a dictionary of the form {'model':modelname, 'obs':obsname} where modelname and obsname are strings.""" @@ -107,7 +109,8 @@ def get_model_case(filetable): nicknames['obs'] = opts['obs'][0]['name'] elif ft2 is not None: #nicknames['obs'] = ft2.source().split('_')[-1] - nicknames['obs'] = ft2.source().split('_')[0] + #nicknames['obs'] = ft2.source().split('_')[0] #doesn't work on HadISST_CL, which isn't HadISST + nicknames['obs'] = ft2.source() return nicknames def setnum( setname ): @@ -156,7 +159,7 @@ def run_diags( opts ): # Setup filetable arrays modelfts = [] obsfts = [] - names = {'model':'', 'obs':''} + names = {} for i in range(len(opts['model'])): ft = path2filetable(opts, modelid=i) if ft._id.nickname=='': @@ -292,7 +295,11 @@ def run_diags( opts ): filter = opts._opts['obs'][0]['filter'] obsfilter = None if filter != None: - obsfilter = filter.split('"')[1] + # Assume the filter is something simple like 'f_startswith("NCEP")', or "f_startswith('NCEP')" + splitfilt = filter.split('"') + if len(splitfilt)<=1: + splitfilt = filter.split("'") + obsfilter = splitfilt[1] computeall = not opts['output']['table'] table = sclass( modelfts, obsfts, varid=varid, obsfilter=obsfilter, dryrun=opts['dryrun'], sbatch=opts["sbatch"], computeall=computeall, outdir=outdir) @@ -325,7 +332,8 @@ def run_diags( opts ): logger.warning('Could not find any of the requested variables %s among %s', opts['vars'], pclass.list_variables(modelfts,obsfts,sname) ) logger.warning("among %s", variables) - return {} + print "multidiags start here names=", names, "multidiags stop here" # do not mess with this! + return names # Ok, start the next layer of work - seasons and regions # loop over the seasons for this plot @@ -470,6 +478,7 @@ def run_diags( opts ): # If this were called from multidiags, the names dictionary would be helpful. In particular, # it will help to not have to re-open a file to re-compute the case name for the model. + print "multidiags start here names=", names, "multidiags stop here" # do not mess with this! return names @@ -867,6 +876,7 @@ def makeplots(res, vcanvas, vcanvas2, varid, frname, plot, package, opts, displa descr=descr, vname=vname, more_id='combined' ) else: source_descr2 = list(set(source_descr2)) + #source_descr2.sort() fnamepng,fnamesvg,fnamepdf = form_filename( frnamebase, ('png','svg','pdf'), modobs=list(source_descr2), more_id='combined' ) @@ -888,15 +898,21 @@ def makeplots(res, vcanvas, vcanvas2, varid, frname, plot, package, opts, displa if __name__ == '__main__': print "UV-CDAT Diagnostics, command-line version" - print ' '.join(sys.argv) - try: - irunby = sys.argv.index('--runby') - runby = sys.argv[irunby+1] - o = Options(runby=runby) - except ValueError: - o = Options() + print repr(repr(' '.join(sys.argv))) + if sys.argv[1][:10]=='--pickopt=': + # special case. All options are described in the argument, a pickled Options object. + pickopt = sys.argv[1][10:] + pickopt = pickopt.replace('jfpsingleq','\'') + o = pickle.loads(pickopt) + else: + # Normal case, with command-line options. Their interpretation differs slightly according + # to how this was run, as specified in the '--runby' option. + try: + irunby = sys.argv.index('--runby') + runby = sys.argv[irunby+1] + o = Options(runby=runby) + except ValueError: + o = Options() o.parseCmdLine() o.verifyOptions() - #print o._opts['levels'] - #print o._opts['displayunits'] run_diags(o) diff --git a/src/python/frontend/metadiags.py b/src/python/frontend/metadiags.py index 29ca2145..13d836d8 100644 --- a/src/python/frontend/metadiags.py +++ b/src/python/frontend/metadiags.py @@ -200,7 +200,8 @@ def makeTables(collnum, model_dict, obspath, outpath, pname, outlogdir, dryrun=F runcmdline(cmdline, outlogdir, dryrun) -def generatePlots(model_dict, obspath, outpath, pname, xmlflag, data_hash, colls=None, dryrun=False): +def generatePlots(model_dict, obspath, outpath, pname, xmlflag, data_hash, colls=None, + dryrun=False, opts_dryrun=False): import os # Did the user specify a single collection? If not find out what collections we have if colls == None: @@ -469,7 +470,7 @@ def generatePlots(model_dict, obspath, outpath, pname, xmlflag, data_hash, colls pstr2 = '' cmdline = (def_executable, pstr1, pstr2, obsstr, optionsstr, packagestr, setstr, seasonstr, varstr, outstr, xmlstr, prestr, poststr, regionstr) if collnum != 'dontrun': - runcmdline(cmdline, outlogdir, dryrun) + runcmdline(cmdline, outlogdir, dryrun, opts_dryrun) else: message = cmdline logger.debug('DONTRUN: %s', cmdline) @@ -532,7 +533,7 @@ def generatePlots(model_dict, obspath, outpath, pname, xmlflag, data_hash, colls if varopts: cmdline += [varopts] if collnum != 'dontrun': - runcmdline(cmdline, outlogdir, dryrun) + runcmdline(cmdline, outlogdir, dryrun, opts_dryrun) else: logger.debug('DONTRUN: %s', cmdline) else: # different executable; just pass all option key:values as command line options. @@ -569,7 +570,7 @@ def generatePlots(model_dict, obspath, outpath, pname, xmlflag, data_hash, colls execstr = execstr+' --figurebase '+ fnamebase if execstr != def_executable: - runcmdline([execstr], outlogdir, dryrun) + runcmdline([execstr], outlogdir, dryrun, opts_dryrun) # VIEWER Code # Build rows for this group in the index... @@ -688,16 +689,75 @@ def generatePlots(model_dict, obspath, outpath, pname, xmlflag, data_hash, colls pid_to_tmpfile = {} active_processes = [] DIAG_TOTAL = 0 +optsout = None + + +def dequote(s): # from stackoverflow, and a little better than the way I would have done it: + """ + If a string has single or double quotes around it, remove them. + Make sure the pair of quotes match. + If a matching pair of quotes is not found, return the string unchanged. + """ + if (s[0] == s[-1]) and s.startswith(("'", '"')): + return s[1:-1] + return s + +def cmd2opts( cmdline ): + """Input is a metadiags command line, a tuple or list of strings. + This function computes and returns the corresponding Options object.""" + opts = Options() + # Some strings in cmdline contain blanks, e.g. '--seasons DJF JJA'. + # We have to convert this to a list of strings like sys.argv when cmdline is run, i.e. '--seasons','DJF','JJA'. + cmdl = ' '.join(cmdline) + sys.argv = [item for item in cmdl.split(' ') if item!=''] + opts.parseCmdLine() + return opts + +def cmd2print( cmdline, opts_dryrun=True ): + """Input is a metadiags command line, and optionally the dryrun option. This dryrun option may + be the name of a file, or True or False. This function will print values of some key options. + If dryrun be True, they will be printed to stdout. If dryrun be the name of a file, they will + be written to the file. Nothing will happen if dryrun be False.""" + global optsout + if opts_dryrun==False: + return + opts = cmd2opts( cmdline ) + if optsout is None: + try: + optsout = open(opts_dryrun,'w') + except TypeError: + # happens in the common case, opts_dryrun==True + optsout = None # print to this means print to stdout + if 'modelpath' not in opts.keys(): + opts['modelpath'] = [os.path.abspath(opts['model'][0]['path'])] + if 'obspath' not in opts.keys(): + opts['obspath'] = [os.path.abspath(opts['obs'][0]['path'])] + + print >>optsout, "Options instance" + for key in ['modelpath', 'obspath', ('obs','filter'), 'package', + 'sets', 'seasons', 'vars' ]: + try: + if type(key) is str: + print >>optsout, " ", key, "=", opts[key] + else: # nested dictionaries, two keys in a tuple + if type(opts[key[0]]) is list: # we also have to drill through a list! (happens with model,obs) + print >>optsout, " ", key[0], "[0][", key[1], "] =", dequote( opts[key[0]][0][key[1]] ) + else: + print >>optsout, " ", key[0], "[", key[1], "] =", opts[key[0]][key[1]] + except KeyError: + print( " ", key, "undefined" ) def cmderr(popened): logfile = pid_to_cmd[popened.pid].split(" ")[-1] logger.error("Command \n%s\n failed with code of %d. Log file is at %s.", pid_to_cmd[popened.pid], popened.returncode, logfile) -def runcmdline(cmdline, outlogdir, dryrun=False): +def runcmdline(cmdline, outlogdir, dryrun=False, opts_dryrun=False): global DIAG_TOTAL + cmd2print( cmdline, opts_dryrun ) + # the following is a total KLUDGE. It's more of a KLUDGE than last time. # I'm not proud of this but I feel threatned if I don't do it. # there is some sort of memory leak in vcs. @@ -928,6 +988,7 @@ def postDB(fts, dsname, package, host=None): cmaps = opts._opts["colormaps"] tmpDict["colormaps"] = " ".join(["%s=%s" % (k, cmaps[k]) for k in cmaps]) diags_collection[K]["options"] = tmpDict + print "jfp opts['dryrun']=",opts['dryrun'] if opts["dryrun"]: fnm = os.path.join(outpath, "metadiags_commands.sh") dryrun = open(fnm, "w") @@ -945,6 +1006,7 @@ def postDB(fts, dsname, package, host=None): module use /usr/common/contrib/acme/modulefiles module load uvcdat/batch """ % (opts["sbatch"]) + else: dryrun = False @@ -960,7 +1022,8 @@ def postDB(fts, dsname, package, host=None): data_path_hmac.update(obspath) data_hash = data_path_hmac.hexdigest() - menus, pages = generatePlots(model_dict, obspath, outpath, package, xmlflag, data_hash, colls=colls,dryrun=dryrun) + menus, pages = generatePlots(model_dict, obspath, outpath, package, xmlflag, data_hash, + colls=colls,dryrun=dryrun,opts_dryrun=opts['dryrun']) for page in pages: # Grab file metadata for every image that exists. diff --git a/src/python/frontend/multidiags b/src/python/frontend/multidiags new file mode 120000 index 00000000..f349c823 --- /dev/null +++ b/src/python/frontend/multidiags @@ -0,0 +1 @@ +multidiags.py \ No newline at end of file diff --git a/src/python/frontend/multidiags.py b/src/python/frontend/multidiags.py index 5af22684..b2108317 100644 --- a/src/python/frontend/multidiags.py +++ b/src/python/frontend/multidiags.py @@ -1,9 +1,20 @@ +#!/usr/bin/env python + # Run multiple diagnostics. The user only specifies paths and a collection name. # The collection name is a key into multimaster.py, or (>>>> TO DO >>>>) another file provided by the user. -import logging, pdb, importlib, time, cProfile +# ---------------- section 0: Imports and global initializations ---------------- + +from __future__ import print_function # for new-style print( string, file=outputfile ) + +import logging, os, pdb, importlib, time, cProfile, pickle, tempfile, subprocess from pprint import pprint from itertools import groupby +from multiprocessing import Pool, current_process, Process, Queue +import multiprocessing +from tempfile import NamedTemporaryFile +from os import getpid +from subprocess import PIPE from metrics.frontend.multimaster import * # this file is just a demo import metrics.frontend.multimaster as multimaster import metrics.frontend.defines as defines @@ -13,6 +24,11 @@ from output_viewer.index import OutputIndex, OutputPage, OutputGroup, OutputRow, OutputFile, OutputMenu logger = logging.getLogger(__name__) +MAX_N_PROCS = 4 # maximum number of simultaneous processes. + # These are run with Popen or the multiprocessing module. +ARG_MAX = subprocess.check_output(['getconf', 'ARG_MAX']) # max length of a command line + +# ---------------- section 1: Expand names and merge options. ---------------- def merge_option_with_its_defaults( opt, diags_collection ): """If opt, an Options instance, has a default_opts option set, this will import the @@ -58,54 +74,6 @@ def expand_and_merge_with_defaults( opt ): opts.append(newopt) return opts -def merge_all_options_old( opt ): - """Merge the supplied Options object with other Options instances. Conflicts are resolved by - ordering them in this priority: - opt, opt['default_opts'], my_opts, my_opts['default_opts'], diags_opts, options_defaults - At present the use of 'default_opts' cannot be recursive.""" - try: - import my_opts # user can put my_opts.py in his PYTHONPATH - myopts = my_opts.my_opts - except: - my_opts = None - myopts = None - try: - import metrics.frontend.diags_opts - diagsopts = diags_opts.diags_opts - except: - diags_opts = None - diagsopts = None - # The 'sets' or 'colls' option identifies a member (or more) of diags_collection, whose values - # are Options instances. We have to expand it now, to a list 'opts' of real Options instances, - # in order to perform the merger. - # Similarly, if any 'model' or 'obs' option be a key into a collection, we have to expand it - # now in order to perform a proper merge. - if opt.get('sets') is None: # later use a specialized option name, e.g. 'collec' <<<< - opts = [opt] - else: - opts = [] - for dset in opt['sets']: - # Each set in opt['sets'] may be a list. And opt['sets'] may be a list of length>1. - # Either way, we end out with a list of Options instances which should be merged with - # opt (opt having priority) but should not be merged together. - if isinstance(diags_collection[dset],Options): - merge_option_with_its_defaults(diags_collection[dset],diags_collection) - opts.append(opt) - elif type(diags_collection[dset]) is list: - for optset in diags_collection[dset]: - merge_option_with_its_defaults(optset,diags_collection) - newopt = opt.clone() - opts.append(newopt) - # (done in the newer version of this function): This should also be done for the myopts, diagsopts, etc. - if my_opts is not None: - merge_option_with_its_defaults( myopts, my_opts.diags_collection ) - for op in opts: - merge_option_with_its_defaults( op, diags_collection ) - op.merge( myopts ) - op.merge( diagsopts ) - op.merge( options_defaults ) - return opts - def merge_all_options( opt ): """Merge the supplied Options object with other Options instances. When two Options instances are merged and only one has a value for an option, then that @@ -253,29 +221,132 @@ def expand_lists_collections( opt, okeys=None ): flattened = [ o for ops in expanded_opts for o in ops ] return flattened +# ---------------- section 2: Higher-level run functions, including multiprocessing ---------------- + +def run_next(): + """Runs a diags process on the next option in the newopts list.""" + global newopts + global optidx + global procs + global active + if None in active: + active[active.index(None)] = optidx + o = newopts[optidx] + ostr = pickle.dumps( o, 0 ) # easier than generating a full-length command line + ostr = ostr.replace('\'','jfpsingleq') + ostr = '\'' + ostr + '\'' + cmd = r"diags --pickopt=%s" % ostr # don't change this syntax, c.f. bottom of diags.py + if len(cmd)>=ARG_MAX: + logger.critical("generated command line is too long for the operating sytem!") + quit() + p = subprocess.Popen([cmd],shell=True,stdout=PIPE,stderr=PIPE) + procs[optidx] = p + optidx += 1 + elif optidx0: + newopts[idx]['modobs_names'] = eval( namstr ) + active[active.index(idx)] = None + +def multidiags_dryrun( newopts, dryrun ): + """A list of Options objects newopts is set up, but we don'e want to actually run the + diagnostics. Just print out something about what we have to do.""" + try: + outfile = open(dryrun,'w') + except TypeError: # happens in the common case, dryrun==True + outfile = None + for opt in newopts: + print( "Options instance", file=outfile ) + #print( "keys=", sorted(opt.keys()) ) + for key in ['modelpath', 'obspath', ('obs','filter'), 'package', + 'sets', 'seasons', 'vars' ]: + try: + if type(key) is str: + print( " ", key, "=", opt[key], file=outfile ) + else: # nested dictionaries, two keys in a tuple + if type(opt[key[0]]) is list: # we also have to drill through a list! (happens with model,obs) + print( " ", key[0], "[0][", key[1], "] =", opt[key[0]][0][key[1]], file=outfile ) + else: + print( " ", key[0], "[", key[1], "] =", opt[key[0]][key[1]], file=outfile ) + except KeyError: + print( " ", key, "undefined" ) + if outfile is not None: + outfile.close() + +def multidiags2( optslist ): + """Now that a list of Options objects newopts is set up, actually run the diagnostics. Then + set up for running the viewer.""" + global optidx + global procs + global active + + if len(optslist)==1 or MAX_N_PROCS==1: + # single-processing: + for i,o in enumerate(optslist): + o['modobs_names'] = run_diags(o.clone()) # run_diags messes with its input arg, though it shouldn't + else: + # multi-processing: + procs = [None] * len(optslist) + active = [None] * MAX_N_PROCS # MAX_N_PROCS is defined at the top of this file. + optidx = 0 + while optidx1: where = where+'-combined' - return form_filename( where, 'nc', descr=descr, vname=self.vars[0].id, more_id=self.more_id ) + return form_filename( where, 'nc', descr=descr, vname=varid, more_id=self.more_id ) elif len(self.title)<=0: fname = 'foo'+''.join([random.choice('0123456789') for _ in range(4)])+'.nc' else: @@ -917,7 +930,11 @@ def write_plot_data( self, format="", where="" ): zax.filetable2id= str(zax.filetable2id) # and the named tuple ids aren't writeable as such except: pass - for ax in zax.getAxisList(): + if type(zax) is tuple: + axes = zax[0].getAxisList() + else: + axes = zax.getAxisList() + for ax in axes: try: del ax.filetable except: diff --git a/src/python/packages/amwg/amwg1.py b/src/python/packages/amwg/amwg1.py index 02d87158..2b2bffd1 100644 --- a/src/python/packages/amwg/amwg1.py +++ b/src/python/packages/amwg/amwg1.py @@ -21,7 +21,7 @@ from metrics.common.utilities import * from metrics.computation.region import * from unidata import udunits -import cdutil.times, numpy, pdb +import cdutil.times, numpy, pdb, os import logging logger = logging.getLogger(__name__) @@ -491,7 +491,7 @@ def write_plot_data( self, format="text", where="", fname="" ): self.ptype = "text" if fname != "": logger.debug('filename was: %s', fname) - filename = where + fname + filename = os.path.join( where, fname ) else: filename = self.outfile( format, where ) writer = open( filename, 'w' ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f90ee209..7bb49f4e 100755 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -123,3 +123,15 @@ ${metrics_SOURCE_DIR}/test/diagsmeta.py --datadir=${UVCMETRICS_TEST_DATA_DIRECTORY}/ --baseline=${BASELINE_DIR}/ ) #set_tests_properties(diags_meta PROPERTIES DEPENDS diags_test_15) + +add_test("diags_multi" +"python" +${metrics_SOURCE_DIR}/test/diagsmulti.py +--datadir=${UVCMETRICS_TEST_DATA_DIRECTORY}/ +--baseline=${BASELINE_DIR}/ ) + +add_test("diags_multimeta" +"python" +${metrics_SOURCE_DIR}/test/diagsmultimeta.py +--datadir=${UVCMETRICS_TEST_DATA_DIRECTORY}/ +--baseline=${BASELINE_DIR}/ ) diff --git a/test/baselines/diagsmulti/dryrunopts b/test/baselines/diagsmulti/dryrunopts new file mode 100644 index 00000000..4ec9e060 --- /dev/null +++ b/test/baselines/diagsmulti/dryrunopts @@ -0,0 +1,24 @@ +Options instance + modelpath = ['/test/data/cam35_data_smaller'] + obspath = ['/test/data/obs_data_5.6'] + obs [0][ filter ] = f_startswith('ISCCP_') + package = AMWG + sets = ['5'] + seasons = ['DJF', 'JJA', 'ANN'] + vars = ['FLUT', 'LWCF'] +Options instance + modelpath = ['/test/data/cam35_data_smaller'] + obspath = ['/test/data/obs_data_5.6'] + obs [0][ filter ] = f_and( f_startswith('CERES_'), f_not(f_contains('EBAF'))) + package = AMWG + sets = ['5'] + seasons = ['DJF', 'JJA', 'ANN'] + vars = ['FLUT', 'LWCF'] +Options instance + modelpath = ['/test/data/cam35_data_smaller'] + obspath = ['/test/data/obs_data_5.6'] + obs [0][ filter ] = f_startswith('NCEP') + package = AMWG + sets = ['5'] + seasons = ['DJF', 'JJA', 'ANN'] + vars = ['FLUT', 'LWCF'] diff --git a/test/diagsmulti.py b/test/diagsmulti.py new file mode 100644 index 00000000..c6707a3e --- /dev/null +++ b/test/diagsmulti.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +"""" This is a test of multidiags""" +import sys, os, shutil, tempfile, subprocess, filecmp +import argparse, pdb +import diags_test + +#to run this app use +#uvcmetrics/test/diagsmulti.py --datadir $HOME/uvcmetrics_test_data/ --baseline $HOME/uvcmetrics/test/baselines/ + +#get commmand line args +p = argparse.ArgumentParser(description="Basic multidiags test") +p.add_argument("--datadir", dest="datadir", help="root directory for model and obs data") +p.add_argument("--baseline", dest="baseline", help="directory with baseline files for comparing results") +p.add_argument("--rebaseline", dest="rebaseline", help="instructions to rebaseline the output") + +args = p.parse_args(sys.argv[1:]) +datadir = args.datadir +print 'datadir = ', datadir +baselinepath = args.baseline + '/diagsmulti/' +print "baselinepath = ", baselinepath +outpath = tempfile.mkdtemp() + "/" +print "outpath=", outpath +rebaseline = False +if 'rebaseline' in dir(args): + # Actually this never happens. + rebaseline = args.rebaseline + +test_str = 'multidiags\n' +#run this from command line to get the files required +command = "multidiags.py --package AMWG --set 5 --modelpath=%s/cam35_data_smaller/ --obspath=%sobs_data_5.6/ --dryrun=%s/dryrunopts --outputdir=%s "%(datadir, datadir, outpath, outpath) +print command +#+ "--outputdir " + outpath + +#run the command +proc = subprocess.Popen([command], shell=True) +proc_status = proc.wait() +if proc_status!=0: + raise Exception("multidiags test failed") + +new = open(os.path.join(outpath,"dryrunopts")) +old = open(os.path.join(baselinepath,"dryrunopts")) + +print "Comparing: %s %s" % (new.name, old.name) + +new = new.readlines() +old = old.readlines() + +diff = False +for OLD, NEW in zip(old, new): + # Get rid of root part of model and obs paths: + # This may not work if there be more than one item in the path list... + testfind = NEW.find('/test/') + if testfind>0: + NEW = NEW[0:NEW.find("path = [")+9] + "" + NEW[testfind:] + if not rebaseline: + if not (OLD==NEW): + print "OLD=",OLD + print "NEW=",NEW + raise Exception("multidiags.sh generated different files") +if rebaseline: + print 'new baseline is',os.path.join(baselinepath,'dryrunopts') diff --git a/test/diagsmultimeta.py b/test/diagsmultimeta.py new file mode 100644 index 00000000..97aa76d9 --- /dev/null +++ b/test/diagsmultimeta.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +"""" This is a test of whether multidiags and metadiags do the same thing""" +import sys, os, shutil, tempfile, subprocess, filecmp +import argparse, pdb +import diags_test + +#to run this app use +#uvcmetrics/test/diagsmultmetai.py --datadir $HOME/uvcmetrics_test_data/ --baseline $HOME/uvcmetrics/test/baselines/ + +#get commmand line args +p = argparse.ArgumentParser(description="Basic multidiags test") +p.add_argument("--datadir", dest="datadir", help="root directory for model and obs data") +p.add_argument("--baseline", dest="baseline", help="directory with baseline files for comparing results") +p.add_argument("--rebaseline", dest="rebaseline", help="instructions to rebaseline the output") + +args = p.parse_args(sys.argv[1:]) +datadir = args.datadir +print 'datadir = ', datadir +baselinepath = args.baseline + '/diagsmultimeta/' +print "baselinepath = ", baselinepath +outpath = tempfile.mkdtemp() + "/" +print "outpath=", outpath +rebaseline = False +if 'rebaseline' in dir(args): + # Actually this never happens. + rebaseline = args.rebaseline + +test_str = 'multidiags\n' +#run this from command line to get the files required +multi_command = "multidiags.py --package AMWG --set 7s --modelpath=%s/cam35_data_smaller/ --obspath=%sobs_data_5.6/ --dryrun=%s/multi_dryrunopts --outputdir=%s "%(datadir, datadir, outpath, outpath) +print multi_command +#+ "--outputdir " + outpath + +test_str = 'metadiags\n' +#run this from command line to get the files required +meta_command = "metadiags.py --package AMWG --set 7s --model path=%s/cam35_data_smaller/,climos=yes --obs path=%sobs_data_5.6/,climos=yes --dryrun=%s/meta_dryrunopts --outputdir=%s"%(datadir, datadir, outpath, outpath) +print meta_command +#+ "--outputdir " + outpath + +#run the multidiags command +multi_proc = subprocess.Popen([multi_command], shell=True) + +#run the metadiags command +meta_proc = subprocess.Popen([meta_command], shell=True) + +# Wait for both processes to finish. +multi_proc_status = multi_proc.wait() +if multi_proc_status!=0: + raise Exception("multidiags-metadiags test failed in multidiags") +meta_proc_status = meta_proc.wait() +if meta_proc_status!=0: + raise Exception("multidiags-metadiags test failed in metadiags") + +metafile = open(os.path.join(outpath,"meta_dryrunopts")) +multifile = open(os.path.join(outpath,"multi_dryrunopts")) +print "Comparing: %s %s" % (metafile.name, multifile.name) + +if filecmp.cmp( metafile.name, multifile.name ): + pass +else: + raise Exception("metadiags and multidiags produced different files")