source: CCCC/trunk/utils_c4.py @ 103

Subversion URL: http://proj.badc.rl.ac.uk/svn/exarch/CCCC/trunk/utils_c4.py@103
Revision 103, 30.0 KB checked in by mjuckes, 7 years ago (diff)

added specific file handle tracking test to unit test suite 2

Line 
1
2import string, re, os, sys, traceback
3
4from fcc_utils import mipTableScan
5
6class reportSection:
7
8  def __init__(self,id,cls,parent=None, description=None):
9    self.id = id
10    self.cls = cls
11    self.parent = parent
12    self.description = description
13    self.records = []
14    self.subsections = []
15    self.closed = False
16    self.npass = 0
17    self.fail = 0
18    self.auditDone = True
19
20  def addSubSection( self, id, cls, description=None):
21    assert not self.closed, 'Attempt to add sub-section to closed report section'
22    self.subsections.append( reportSection(id, cls, parent=self, description=description )  )
23    self.auditDone = False
24    return self.subsections[-1]
25
26  def addRecord( self, id, cls, res, msg ):
27    assert not self.closed, 'Attempt to add record to closed report section'
28    self.records.append( (id, cls, res, msg) )
29    self.auditDone = False
30
31  def close(self):
32    self.closed = True
33
34  def reopen(self):
35    self.closed = False
36
37  def audit(self):
38    if self.auditDone:
39      return
40    self.closed = True
41    self.fail = 0
42    self.npass = 0
43    for ss in self.subsections:
44      ss.audit()
45      self.fail += ss.fail
46      self.npass += ss.npass
47
48    for r in self.records:
49      if r[2]:
50        self.npass += 1
51      else:
52        self.fail += 1
53
54class abortChecks(Exception):
55  pass
56class loggedException(Exception):
57  pass
58class baseException(Exception):
59 
60  def __init__(self,msg):
61    self.msg = 'utils_c4:: %s' % msg
62
63  def __str__(self):
64        return unicode(self).encode('utf-8')
65
66  def __unicode__(self):
67        return self.msg % tuple([force_unicode(p, errors='replace')
68                                 for p in self.params])
69class checkSeq:
70  def __init__(self):
71    pass
72
73  def check(self,x):
74    d = map( lambda i: x[i+1] - x[i], range(len(x)-1) )
75    self.delt = sum(d)/len(d)
76    self.dmx = max(d)
77    self.dmn = min(d)
78    return self.dmx - self.dmn < abs(self.delt)*1.e-4
79
80cs = checkSeq()
81
82class checkBase:
83
84  def  __init__(self,cls="CORDEX",reportPass=True,parent=None,monitor=None):
85    self.cls = cls
86    self.project = cls
87    self.abortMessageCount = parent.abortMessageCount
88    self.monitor = monitor
89    ## check done earlier
90    ## assert cls in ['CORDEX','SPECS'],'This version of the checker only supports CORDEX, SPECS'
91    self.re_isInt = re.compile( '[0-9]+' )
92    self.errorCount = 0
93    self.passCount = 0
94    self.missingValue = 1.e20
95    self.parent = parent
96    self.reportPass=reportPass
97    self.pcfg = parent.pcfg
98################################
99    self.requiredGlobalAttributes = self.pcfg.requiredGlobalAttributes
100    self.controlledGlobalAttributes = self.pcfg.controlledGlobalAttributes
101    self.globalAttributesInFn = self.pcfg.globalAttributesInFn
102    self.requiredVarAttributes = self.pcfg.requiredVarAttributes
103    self.drsMappings = self.pcfg.drsMappings
104#######################################
105    self.checks = ()
106    self.messageCount = 0
107    self.init()
108
109  def isInt(self,x):
110    return self.re_isInt.match( x ) != None
111
112  def logMessage(self, msg, error=False ):
113    self.messageCount += 1
114    assert self.abortMessageCount < 0 or self.abortMessageCount > self.messageCount, 'Raising error [TESTX01], perhaps for testing'
115    if self.parent != None and self.parent.log != None:
116       if error:
117         self.parent.log.error( msg )
118       else:
119         self.parent.log.info( msg )
120    else:
121       print msg
122
123    doThis = True
124    if self.appendLogFile[0] != None and doThis:
125      if self.monitor != None:
126         nofh0 = self.monitor.get_open_fds()
127      xlog = self.c4i.getFileLog( self.appendLogFile[1], flf=self.appendLogFile[0] )
128      if error:
129         xlog.error( msg )
130      else:
131         xlog.info( msg )
132      self.c4i.closeFileLog()
133      if self.monitor != None:
134         nofh9 = self.monitor.get_open_fds()
135         if nofh9 > nofh0:
136           print 'Leaking file handles [1]: %s --- %s' % (nofh0, nofh9)
137
138  def log_exception( self, msg):
139    if self.parent != None and self.parent.log != None:
140        self.parent.log.error("Exception has occured" ,exc_info=1)
141    else:
142        traceback.print_exc(file=sys.stdout)
143
144  def log_error( self, msg ):
145    self.lastError = msg
146    self.errorCount += 1
147    self.logMessage( '%s.%s: FAILED:: %s' % (self.id,self.checkId,msg), error=True )
148
149  def log_pass( self ):
150    self.passCount = True
151    if self.reportPass:
152      self.logMessage(  '%s.%s: OK' % (self.id,self.checkId) )
153
154  def log_abort( self ):
155    self.completed = False
156    self.logMessage(   '%s.%s: ABORTED:: Errors too severe to complete further checks in this module' % (self.id,'xxx') )
157    raise abortChecks
158
159  def status(self):
160    return '%s.%s' % (self.id,self.checkId)
161
162  def test(self,res,msg,abort=False,part=False,appendLogFile=(None,None)):
163    self.appendLogFile = appendLogFile
164    if res:
165      if not part:
166         self.log_pass()
167    else:
168      self.log_error(msg)
169      if abort:
170        self.log_abort()
171    return res
172
173  def runChecks(self):
174
175    try:
176      for c in self.checks:
177        c()  # run check
178      self.completed = True
179    except abortChecks:
180      ## error logging done before raising this exception
181      return
182    except:
183      self.log_exception( 'Exception caught by runChecks' )
184      raise loggedException
185      ##traceback.print_exc(file=open("errlog.txt","a"))
186      ##logger.error("Exception has occured" ,exc_info=1)
187   
188class checkFileName(checkBase):
189
190  def init(self):
191    self.id = 'C4.001'
192    self.checkId = 'unset'
193    self.step = 'Initialised'
194    self.checks = (self.do_check_fn,)
195####
196
197  def check(self,fn):
198    self.errorCount = 0
199    assert type(fn) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(fn)
200    self.fn = fn
201
202    self.runChecks()
203###
204  def do_check_fn(self):
205    fn = self.fn
206    self.errorCount = 0
207    self.completed = False
208
209## check basic parsing of file name
210    self.checkId = '001'
211    self.test( fn[-3:] == '.nc', 'File name ending ".nc" expected', abort=True, part=True )
212    bits = string.split( fn[:-3], '_' )
213    self.fnParts = bits[:]
214
215    if self.pcfg.domainIndex != None:
216      self.domain = self.fnParts[self.pcfg.domainIndex]
217    else:
218      self.domain = None
219    ##if self.cls == 'CORDEX':
220      ##self.fnPartsOkLen = [8,9]
221      ##self.fnPartsOkFixedLen = [8,]
222      ##self.fnPartsOkUnfixedLen = [9,]
223      ##checkTrangeLen = True
224    ##elif self.cls == 'SPECS':
225      ##self.fnPartsOkLen = [6,7]
226      ##self.fnPartsOkFixedLen = [6,]
227      ##self.fnPartsOkUnfixedLen = [7,]
228      ##checkTrangeLen = False
229
230    self.test( len(bits) in self.pcfg.fnPartsOkLen, 'File name not parsed in %s elements [%s]' % (str(self.pcfg.fnPartsOkLen),str(bits)), abort=True )
231
232    if self.pcfg.freqIndex != None:
233      self.freq = self.fnParts[self.pcfg.freqIndex]
234    else:
235      self.freq = None
236    ##if self.cls == 'CORDEX':
237      ##self.freq = self.fnParts[7]
238    ##elif self.cls == 'SPECS':
239      ##self.freq = self.fnParts[1]
240
241    self.var = self.fnParts[0]
242
243    self.isFixed = self.freq == 'fx'
244
245    self.checkId = '002'
246    if not self.isFixed:
247
248## test time segment
249      bits = string.split( self.fnParts[-1], '-' )
250      self.test( len(bits) == 2, 'File time segment [%s] will not parse into 2 elements' % (self.fnParts[-1] ), abort=True, part=True )
251
252      self.test(  len(bits[0]) == len(bits[1]), 'Start and end time specified in file name [%s] of unequal length' % (self.fnParts[-1] ), abort=True, part=True  )
253
254      for b in bits:
255        self.test( self.isInt(b), 'Time segment in filename [%s] contains non integer characters' % (self.fnParts[-1] ),  abort=True, part=True  )
256      self.log_pass()
257      self.fnTimeParts = bits[:]
258
259    self.checkId = '003'
260    if self.isFixed:
261      self.test( len(self.fnParts) in self.pcfg.fnPartsOkFixedLen, 'Number of file name elements not acceptable for fixed data' )
262
263    self.checkId, ok = ('004',True)
264    if len(self.fnParts) == 9 and self.pcfg.checkTrangeLen:
265      ltr = { 'mon':6, 'sem':6, 'day':8, '3hr':[10,12], '6hr':10 }
266      ok &=self.test( self.freq in ltr.keys(), 'Frequency [%s] not recognised' % self.freq, part=True )
267      if ok:
268        if type( ltr[self.freq] ) == type(0):
269          msg = 'Length of time range parts [%s,%s] not equal to required length [%s] for frequency %s' % (self.fnTimeParts[0],self.fnTimeParts[1],ltr[self.freq],self.freq)
270          ok &= self.test( len(self.fnTimeParts[0]) == ltr[self.freq], msg, part=True )
271        elif type( ltr[self.freq] ) in [type([]),type( () )]:
272          msg = 'Length of time range parts [%s,%s] not in acceptable list [%s] for frequency %s' % (self.fnTimeParts[0],self.fnTimeParts[1],str(ltr[self.freq]),self.freq)
273          ok &= self.test( len(self.fnTimeParts[0]) in ltr[self.freq], msg, part=True )
274
275      if ok:
276        self.log_pass()
277    self.completed = True
278
279class checkGlobalAttributes(checkBase):
280
281  def init(self):
282    self.id = 'C4.002'
283    self.checkId = 'unset'
284    self.step = 'Initialised'
285    self.checks = (self.do_check_ga,)
286
287  def check(self,globalAts, varAts,varName,varGroup, vocabs, fnParts):
288    self.errorCount = 0
289    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
290    self.var = varName
291    self.globalAts = globalAts
292    self.varAts = varAts
293    self.varGroup = varGroup
294    self.vocabs = vocabs
295    self.fnParts = fnParts
296    self.runChecks()
297
298  def getDrs( self ):
299    assert self.completed, 'method getDrs should not be called if checks have not been completed successfully'
300    ee = {}
301    for k in self.drsMappings:
302      if self.drsMappings[k] == '@var':
303        ee[k] = self.var
304      else:
305        ee[k] = self.globalAts[ self.drsMappings[k] ]
306
307    for k in ['creation_date','tracking_id']:
308      if k in self.globalAts.keys():
309        ee[k] = self.globalAts[k]
310
311    return ee
312
313  def do_check_ga(self):
314    varName = self.var
315    globalAts = self.globalAts
316    varAts = self.varAts
317    varGroup = self.varGroup
318    vocabs = self.vocabs
319    fnParts = self.fnParts
320
321    self.completed = False
322    self.checkId = '001'
323    m = []
324    for k in self.requiredGlobalAttributes:
325      if not globalAts.has_key(k):
326         m.append(k)
327
328    gaerr = not self.test( len(m)  == 0, 'Required global attributes missing: %s' % str(m) )
329
330    self.checkId = '002'
331
332    self.test( varAts.has_key( varName ), 'Expected variable [%s] not present' % varName, abort=True, part=True )
333    self.test( vocabs['variable'].isInTable( varName, varGroup ), 'Variable %s not in table %s' % (varName,varGroup), abort=True, part=True )
334
335    self.checkId = '003'
336
337    self.test( varAts[varName]['_type'] == "float32", 'Variable [%s] not of type float' % varName )
338
339    self.checkId = '004'
340    m = []
341    reqAts = self.requiredVarAttributes[:]
342    if varGroup != 'fx':
343      reqAts.append( 'cell_methods' )
344    for k in reqAts + vocabs['variable'].lists(varName, 'addRequiredAttributes'):
345      if not varAts[varName].has_key(k):
346         m.append(k)
347    vaerr = not self.test( len(m)  == 0, 'Required variable attributes missing: %s' % str(m) )
348
349    if vaerr or gaerr:
350      self.log_abort()
351
352## need to insert a check that variable is present
353    self.checkId = '005'
354    ok = True
355    if varAts[varName].has_key( 'missing_value' ) or varAts[varName].has_key( '_FillValue' ):
356      ok &= self.test( varAts[varName].has_key( 'missing_value' ) and varAts[varName].has_key( '_FillValue' ), \
357                'missing_value and _FillValue must both be present if one is [%s]' % varName )
358      if varAts[varName].has_key( 'missing_value' ):
359         msg = 'Variable [%s] has incorrect missing_value attribute' % varName
360         ok &= self.test( varAts[varName]['missing_value'] == self.missingValue, msg, part=True )
361      if varAts[varName].has_key( '_FillValue' ):
362         msg = 'Variable [%s] has incorrect _FillValue attribute' % varName
363         ok &= self.test( varAts[varName]['_FillValue'] == self.missingValue, msg, part=True )
364
365    mm = []
366   
367    contAts = ['long_name', 'standard_name', 'units']
368    if varGroup != 'fx':
369      contAts.append( 'cell_methods' )
370    for k in contAts + vocabs['variable'].lists(varName,'addControlledAttributes'):
371      if varAts[varName][k] != vocabs['variable'].getAttr( varName, varGroup, k ):
372        mm.append( k )
373
374    ok &= self.test( len(mm)  == 0, 'Variable [%s] has incorrect attributes: %s' % (varName, str(mm)), part=True )
375    if ok:
376       self.log_pass()
377
378    if varGroup != 'fx':
379      self.isInstantaneous = string.find( varAts[varName]['cell_methods'], 'time: point' ) != -1
380    else:
381      self.isInstantaneous = True
382
383    self.checkId = '006'
384    m = []
385    for a in self.controlledGlobalAttributes:
386       if not vocabs[a].check( str(globalAts[a]) ):
387          m.append( (a,globalAts[a]) )
388
389    self.test( len(m)  == 0, 'Global attributes do not match constraints: %s' % str(m) )
390
391    self.checkId = '007'
392    m = []
393    for i in range(len(self.globalAttributesInFn)):
394       if self.globalAttributesInFn[i] != None:
395         if globalAts[self.globalAttributesInFn[i]] != fnParts[i]:
396           m.append( (i,self.globalAttributesInFn[i]) )
397
398    self.test( len(m)  == 0,'File name segments do not match corresponding global attributes: %s' % str(m) )
399
400    self.completed = True
401       
402class checkStandardDims(checkBase):
403
404  def init(self):
405    self.id = 'C4.003'
406    self.checkId = 'unset'
407    self.step = 'Initialised'
408    self.checks = (self.do_check,)
409    self.plevRequired = self.pcfg.plevRequired
410    self.plevValues = self.pcfg.plevValues
411    self.heightRequired = self.pcfg.heightRequired
412    self.heightValues = self.pcfg.heightValues
413    self.heightRange = self.pcfg.heightRange
414
415  def check(self,varName,varGroup, da, va, isInsta):
416    self.errorCount = 0
417    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
418    self.var = varName
419    self.varGroup = varGroup
420    self.da = da
421    self.va = va
422    self.isInsta = isInsta
423    self.runChecks()
424
425  def do_check(self):
426    varName = self.var
427    varGroup = self.varGroup
428    da = self.da
429    va = self.va
430    isInsta = self.isInsta
431
432    self.errorCount = 0
433    self.completed = False
434    self.checkId = '001'
435    if varGroup != 'fx':
436      ok = True
437      self.test( 'time' in da.keys(), 'Time dimension not found' , abort=True, part=True )
438      if not isInsta:
439         ok &= self.test(  da['time'].get( 'bounds', 'xxx' ) == 'time_bnds', 'Required bounds attribute not present or not correct value', part=True )
440
441## is time zone designator needed?
442      ok &= self.test( da['time'].get( 'units', 'xxx' ) in ["days since 1949-12-01 00:00:00Z", "days since 1949-12-01 00:00:00", "days since 1949-12-01"],
443                       'Time units [%s] attribute not set correctly to "days since 1949-12-01 00:00:00Z"' % da['time'].get( 'units', 'xxx' ), part=True )
444
445      ok &= self.test(  da['time'].has_key( 'calendar' ), 'Time: required attibute calendar missing', part=True )
446
447      ok &= self.test( da['time']['_type'] == "float64", 'Time: data type not float64', part=True )
448       
449      if ok:
450        self.log_pass()
451      self.calendar = da['time'].get( 'calendar', 'None' )
452    else:
453      self.calendar = 'None'
454    self.checkId = '002'
455    if varName in self.plevRequired:
456      ok = True
457      self.test( 'plev' in va.keys(), 'plev coordinate not found %s' % str(va.keys()), abort=True, part=True )
458
459      ok &= self.test( int( va['plev']['_data'] ) == self.plevValues[varName],  \
460                  'plev value [%s] does not match required [%s]' % (va['plev']['_data'],self.plevValues[varName] ), part=True )
461     
462      plevAtDict = {'standard_name':"air_pressure", \
463                    'long_name':"pressure", \
464                    'units':"Pa", \
465                    'positive':"down", \
466                    'axis':"Z" }
467
468      if varName in ['clh','clm','cll']:
469        plevAtDict['bounds']= "plev_bnds"
470
471      for k in plevAtDict.keys():
472        ok &= self.test( va['plev'].get( k, None ) == plevAtDict[k], 
473                     'plev attribute %s absent or wrong value (should be %s)' % (k,plevAtDict[k]), part=True )
474
475      if varName in ['clh','clm','cll']:
476         self.test( "plev_bnds" in va.keys(), 'plev_bnds variable not found %s' % str(va.keys()), abort=True, part=True )
477         mm = []
478         for k in plevAtDict.keys():
479            if k != 'bounds' and k in va['plev_bnds'].keys():
480               if va['plev_bnds'][k] != va['plev'][k]:
481                 mm.append(k)
482         ok &= self.test( len(mm) == 0, 'Attributes of plev_bnds do not match those of plev: %s' % str(mm), part=True )
483
484         bndsVals = {'clh':[44000, 0], 'clm':[68000, 44000], 'cll':[100000, 68000] }
485         res = self.test( len( va['plev_bnds']['_data'] ) == 2, 'plev_bnds array is of wrong length', part=True )
486         ok &= res
487         if res:
488            kk = 0
489            for i in [0,1]:
490               if int(va['plev_bnds']['_data'][i]) != bndsVals[varName][i]:
491                  kk+=1
492            ok &= self.test( kk == 0, 'plev_bnds values not correct: should be %s' % str(bndsVals[varName]), part=True )
493
494      if ok:
495        self.log_pass()
496
497    self.checkId = '003'
498    if varName in self.heightRequired:
499      heightAtDict = {'long_name':"height", 'standard_name':"height", 'units':"m", 'positive':"up", 'axis':"Z" }
500      ok = True
501      ok &= self.test( 'height' in va.keys(), 'height coordinate not found %s' % str(va.keys()), abort=True, part=True )
502      ##ok &= self.test( abs( va['height']['_data'] - self.heightValues[varName]) < 0.001, \
503                ##'height value [%s] does not match required [%s]' % (va['height']['_data'],self.heightValues[varName] ), part=True )
504
505      r = self.heightRange[varName]
506      ok &= self.test( r[0] <= va['height']['_data'] <= r[1], \
507                'height value [%s] not in specified range [%s]' % (va['height']['_data'], (self.heightRange[varName] ) ), part=True )
508     
509      for k in heightAtDict.keys():
510        ok &= self.test( va['height'].get( k, None ) == heightAtDict[k], \
511                         'height attribute %s absent or wrong value (should be %s)' % (k,heightAtDict[k]), part=True )
512
513      if ok:
514        self.log_pass()
515
516    self.completed = True
517
518class checkGrids(checkBase):
519
520  def init(self):
521    self.id = 'C4.004'
522    self.checkId = 'unset'
523    self.step = 'Initialised'
524    self.checks = (self.do_check_rp,self.do_check_intd)
525
526  def check(self,varName, domain, da, va):
527    self.errorCount = 0
528    assert type(varName) in [type('x'),type(u'x')], '1st argument to "check" method of checkGrids shound be a string variable name (not %s)' % type(varName)
529    self.var = varName
530    self.domain = domain
531    self.da = da
532    self.va = va
533
534    self.runChecks()
535    ##for c in self.checks:
536      ##c()
537    ##self.do_check_rp()
538    ##self.do_check_intd()
539
540  def do_check_rp(self):
541    varName = self.var
542    domain = self.domain
543    da = self.da
544    va = self.va
545    if va[varName].get( 'grid_mapping', None ) == "rotated_pole":
546      self.checkId = '001'
547      atDict = { 'grid_mapping_name':'rotated_latitude_longitude' }
548      atDict['grid_north_pole_latitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lat']
549      if self.pcfg.rotatedPoleGrids[domain]['grid_np_lon'] != 'N/A':
550        atDict['grid_north_pole_longitude'] = self.pcfg.rotatedPoleGrids[domain]['grid_np_lon']
551
552      self.checkId = '002'
553      self.test( 'rlat' in da.keys() and 'rlon' in da.keys(), 'rlat and rlon not found (required for grid_mapping = rotated_pole )', abort=True, part=True )
554
555      atDict = {'rlat':{'long_name':"rotated latitude", 'standard_name':"grid_latitude", 'units':"degrees", 'axis':"Y", '_type':'float64'},
556                'rlon':{'long_name':"rotated longitude", 'standard_name':"grid_longitude", 'units':"degrees", 'axis':"X", '_type':'float64'} }
557      mm = []
558      for k in ['rlat','rlon']:
559        for k2 in atDict[k].keys():
560          if atDict[k][k2] != da[k].get(k2, None ):
561            mm.append( (k,k2) )
562      self.test( len(mm) == 0, 'Required attributes of grid coordinate arrays not correct: %s' % str(mm) )
563
564      self.checkId = '003'
565      ok = True
566      for k in ['rlat','rlon']:
567        res = len(da[k]['_data']) == self.pcfg.rotatedPoleGrids[domain][ {'rlat':'nlat','rlon':'nlon' }[k] ]
568        if not res:
569          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True  )
570          ok = False
571
572      a = ( da['rlat']['_data'][0], da['rlat']['_data'][-1], da['rlon']['_data'][0], da['rlon']['_data'][-1] )
573      b = map( lambda x: self.pcfg.rotatedPoleGrids[domain][x], ['s','n','w','e'] )
574      mm = []
575      for i in range(4):
576        if a[i] != b[i]:
577          mm.append( (a[i],b[i]) )
578
579      ok &= self.test( len(mm) == 0, 'Domain boundaries for rotated pole grid do not match %s' % str(mm), part=True )
580
581      for k in ['rlat','rlon']:
582        ok &= self.test( cs.check( da[k]['_data'] ), '%s values not evenly spaced -- min/max delta = %s, %s' % (k,cs.dmn,cs.dmx), part=True )
583
584      if ok:
585        self.log_pass()
586
587  def do_check_intd(self):
588    varName = self.var
589    domain = self.domain
590    da = self.da
591    va = self.va
592    if domain[-1] == 'i':
593      self.checkId = '002'
594      self.test( 'lat' in da.keys() and 'lon' in da.keys(), 'lat and lon not found (required for interpolated data)', abort=True, part=True )
595
596      atDict = {'lat':{'long_name':"latitude", 'standard_name':"latitude", 'units':"degrees_north", '_type':'float64'},
597                'lon':{'long_name':"longitude", 'standard_name':"longitude", 'units':"degrees_east", '_type':'float64'} }
598      mm = []
599      for k in ['lat','lon']:
600        for k2 in atDict[k].keys():
601          if atDict[k][k2] != da[k].get(k2, None ):
602            mm.append( (k,k2) )
603
604      self.test( len(mm) == 0,  'Required attributes of grid coordinate arrays not correct: %s' % str(mm), part=True )
605
606      ok = True
607      for k in ['lat','lon']:
608        res = len(da[k]['_data']) >= self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
609        if not res:
610          a,b =  len(da[k]['_data']), self.pcfg.interpolatedGrids[domain][ {'lat':'nlat','lon':'nlon' }[k] ]
611          self.test( res, 'Size of %s dimension does not match specification (%s,%s)' % (k,a,b), part=True )
612          ok = False
613
614      a = ( da['lat']['_data'][0], da['lat']['_data'][-1], da['lon']['_data'][0], da['lon']['_data'][-1] )
615      b = map( lambda x: self.pcfg.interpolatedGrids[domain][x], ['s','n','w','e'] )
616      rs = self.pcfg.interpolatedGrids[domain]['res']
617      c = [-rs,rs,-rs,rs]
618      mm = []
619      for i in range(4):
620        if a[i] != b[i]:
621          x = (a[i]-b[i])/c[i]
622          if x < 0 or abs( x - int(x) ) > 0.001:
623             skipThis = False
624             if self.project  == 'CORDEX':
625               if domain[:3] == 'ANT':
626                 if i == 2 and abs( a[i] - 0.25 ) < 0.001:
627                    skipThis = True
628                 elif i == 3 and abs( a[i] - 359.75 ) < 0.001:
629                    skipThis = True
630             if not skipThis:
631               mm.append( (a[i],b[i]) )
632
633      ok &= self.test( len(mm) == 0, 'Domain boundaries for interpolated grid do not match %s' % str(mm), part=True )
634
635      for k in ['lat','lon']:
636        ok &= self.test( cs.check( da[k]['_data'] ), '%s values not evenly spaced -- min/max delta = %s, %s' % (k,cs.dmn,cs.dmx), part=True )
637      if ok:
638        self.log_pass()
639
640class mipVocab:
641
642  def __init__(self,pcfg,dummy=False):
643     project = pcfg.project
644     if dummy:
645       self.pcfg = pcfg
646       return self.dummyMipTable()
647     assert project in ['CORDEX','SPECS'],'Project %s not recognised' % project
648     ##if project == 'CORDEX':
649       ##dir = 'cordex_vocabs/mip/'
650       ##tl = ['fx','sem','mon','day','6h','3h']
651       ##vgmap = {'6h':'6hr','3h':'3hr'}
652       ##fnpat = 'CORDEX_%s'
653     ##elif project == 'SPECS':
654       ##dir = 'specs_vocabs/mip/'
655       ##tl = ['fx','Omon','Amon','Lmon','OImon','day','6hrLev']
656       ##vgmap = {}
657       ##fnpat = 'SPECS_%s'
658     dir, tl, vgmap, fnpat = pcfg.mipVocabPars
659     ms = mipTableScan()
660     self.varInfo = {}
661     self.varcons = {}
662     for f in tl:
663        vg = vgmap.get( f, f )
664        self.varcons[vg] = {}
665        fn = fnpat % f
666        ll = open( '%s%s' % (dir,fn) ).readlines()
667        ee = ms.scan_table(ll,None,asDict=True)
668        for v in ee.keys():
669          eeee = {}
670          ar = []
671          ac = []
672          for a in ee[v][1].keys():
673            eeee[a] = ee[v][1][a]
674          ##if 'positive' in eeee.keys():
675            ##ar.append( 'positive' )
676            ##ac.append( 'positive' )
677          self.varInfo[v] = {'ar':ar, 'ac':ac }
678          self.varcons[vg][v] = eeee
679           
680  def dummyMipTable(self):
681     self.varInfo = {}
682     self.varcons = {}
683     ee = { 'standard_name':'sn%s', 'long_name':'n%s', 'units':'1' }
684     dir, tl, vgmap, fnpat = self.pcfg.mipVocabPars
685     for f in tl:
686        vg = vgmap.get( f, f )
687        self.varcons[vg] = {}
688        for i in range(12):
689          v = 'v%s' % i
690          eeee = {}
691          eeee['standard_name'] = ee['standard_name'] % i
692          eeee['long_name'] = ee['long_name'] % i
693          eeee['cell_methods'] = 'time: point'
694          eeee['units'] = ee['units']
695          ar = []
696          ac = []
697          self.varInfo[v] = {'ar':ar, 'ac':ac }
698          self.varcons[vg][v] = eeee
699
700  def lists( self, k, k2 ):
701     if k2 == 'addRequiredAttributes':
702       return self.varInfo[k]['ar']
703     elif k2 == 'addControlledAttributes':
704       return self.varInfo[k]['ac']
705     else:
706       raise 'mipVocab.lists called with bad list specifier %s' % k2
707
708  def isInTable( self, v, vg ):
709    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys()' % vg
710    return (v in self.varcons[vg].keys())
711     
712  def getAttr( self, v, vg, a ):
713    assert vg in self.varcons.keys(), '%s not found in  self.varcons.keys()'
714    assert v in self.varcons[vg].keys(), '%s not found in self.varcons[%s].keys()' % (v,vg)
715     
716    return self.varcons[vg][v][a]
717     
718class patternControl:
719
720  def __init__(self,tag,pattern):
721    try:
722      self.re_pat = re.compile( pattern )
723    except:
724      print "Failed to compile pattern >>%s<< (%s)" % (pattern, tag)
725    self.pattern = pattern
726
727  def check(self,val):
728    return self.re_pat.match( val ) != None
729   
730class listControl:
731  def __init__(self,tag,list):
732    self.list = list
733    self.tag = tag
734
735  def check(self,val):
736    return val in self.list
737
738
739class checkByVar(checkBase):
740
741  def init(self):
742    self.id = 'C5.001'
743    self.checkId = 'unset'
744    self.step = 'Initialised'
745    self.checks = (self.checkTrange,)
746
747  def setLogDict( self,fLogDict ):
748    self.fLogDict = fLogDict
749
750  def impt(self,flist):
751    ee = {}
752    for f in flist:
753      fn = string.split(f, '/' )[-1]
754      fnParts = string.split( fn[:-3], '_' )
755      ##if self.cls == 'CORDEX':
756        ##isFixed = fnParts[7] == 'fx'
757        ##group = fnParts[7]
758      ##elif self.cls == 'SPECS':
759        ##isFixed = fnParts[1] == 'fx'
760        ##group = fnParts[1]
761
762      if self.pcfg.freqIndex != None:
763        freq = fnParts[self.pcfg.freqIndex]
764      else:
765        freq = None
766
767      isFixed = freq == 'fx'
768      group = fnParts[ self.pcfg.groupIndex ]
769
770      if isFixed:
771        trange = None
772      else:
773        trange = string.split( fnParts[-1], '-' )
774      var = fnParts[0]
775      if group not in ee.keys():
776        ee[group] = {}
777      if var not in ee[group].keys():
778        ee[group][var] = []
779      ee[group][var].append( (f,fn,group,trange) )
780
781    nn = len(flist)
782    n2 = 0
783    for k in ee.keys():
784      for k2 in ee[k].keys():
785        n2 += len( ee[k][k2] )
786
787    assert nn==n2, 'some file lost!!!!!!'
788    self.info =  '%s files, %s frequencies' % (nn,len(ee.keys()) )
789    self.ee = ee
790
791  def check(self, recorder=None,calendar='None',norun=False):
792    self.errorCount = 0
793    self.recorder=recorder
794    self.calendar=calendar
795    if calendar == '360-day':
796      self.enddec = 30
797    else:
798      self.enddec = 31
799    mm = { 'enddec':self.enddec }
800    self.pats = {'mon':('(?P<d>[0-9]{3})101','(?P<e>[0-9]{3})012'), \
801            'sem':('(?P<d>[0-9]{3})(012|101)','(?P<e>[0-9]{3})(011|010)'), \
802            'day':('(?P<d>[0-9]{3}[16])0101','(?P<e>[0-9]{3}[50])12%(enddec)s' % mm), \
803            'subd':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})(?P<mm>[30]0){0,1}$', '(?P<e>[0-9]{4})12%(enddec)s(?P<h2>[0-9]{2})([30]0){0,1}$' % mm ), \
804            'subd2':('(?P<d>[0-9]{4})0101(?P<h1>[0-9]{2})', '(?P<e>[0-9]{4})010100' ) }
805
806    if not norun:
807      self.runChecks()
808
809  def checkTrange(self):
810    keys = self.ee.keys()
811    keys.sort()
812    for k in keys:
813      if k != 'fx':
814        keys2 = self.ee[k].keys()
815        keys2.sort()
816        for k2 in keys2:
817          self.checkThisTrange( self.ee[k][k2], k, k2 )
818
819  def checkThisTrange( self, tt, group, var):
820
821    if group in ['3hr','6hr']:
822       kg = 'subd'
823    else:
824       kg = group
825    ps = self.pats[kg]
826    rere = (re.compile( ps[0] ), re.compile( ps[1] ) )
827
828    n = len(tt)
829    for j in range(n):
830      if self.monitor != None:
831         nofh0 = self.monitor.get_open_fds()
832      t = tt[j]
833      fn = t[1]
834      isFirst = j == 0
835      isLast = j == n-1
836      lok = True
837      for i in [0,1]:
838        if not (i==0 and isFirst or i==1 and isLast):
839          x = rere[i].match( t[3][i] )
840          lok &= self.test( x != None, 'Cannot match time range %s: %s' % (i,fn), part=True, appendLogFile=(self.fLogDict.get(fn,None),fn) )
841        if not lok:
842          ### print 'Cannot match time range %s:' % t[1]
843          if self.recorder != None:
844            self.recorder.modify( t[1], 'ERROR: time range' )
845      if self.monitor != None:
846         nofh9 = self.monitor.get_open_fds()
847         if nofh9 > nofh0:
848           print 'Open file handles: %s --- %s [%s]' % (nofh0, nofh9, j )
849
850### http://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python
851class sysMonitor:
852
853  def __init__(self):
854    self.fhCountMax = 0
855
856  def get_open_fds(self):
857    '''
858    return the number of open file descriptors for current process
859    .. warning: will only work on UNIX-like os-es.
860    '''
861    import subprocess
862    import os
863
864    pid = os.getpid()
865    self.procs = subprocess.check_output( 
866        [ "lsof", '-w', '-Ff', "-p", str( pid ) ] )
867
868    self.ps = filter( 
869            lambda s: s and s[ 0 ] == 'f' and s[1: ].isdigit(),
870            self.procs.split( '\n' ) )
871    self.fhCountMax = max( self.fhCountMax, len(self.ps) )
872    return len( self.ps )
Note: See TracBrowser for help on using the repository browser.