testcase_class.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import os, sys, subprocess, time
4 from scipy.interpolate import interp1d
5 import numpy as np
6 
7 # helper function to run shell commands and grab output
8 def shell_command(cmdlist):
9  p = subprocess.Popen(cmdlist, stdout=subprocess.PIPE,\
10  stderr=subprocess.PIPE)
11  out, err = p.communicate()
12  return out
13 
14 
15 def compare_default (stest, strial, tol,l_limit, flog):
16  """
17  compare text data from two files
18  - ignore all strings which start with '#'
19  - use relative tolerance 'tol'
20  - compare test file 'stest' against trial file 'strial'
21  - flog is a log file
22  """
23  i1 = 0
24  i2 = 0
25  lines1 = 0
26  lines2 = 0
27  errcode = 0
28  for i1 in range(len(strial)):
29  s1 = strial[i1].strip()
30  # skip all empty lines and lines which start with '#'
31  if (len(s1)==0) or (s1[0]=='#'):
32  continue
33 
34  # same for the test file
35  i2min = i2
36  for i2 in range(i2min,len(stest)):
37  s2 = stest[i2].strip()
38  if (len(s2)==0) or (s2[0]=='#'):
39  continue
40  break
41 
42  # if reached the end of second file
43  if i2==len(stest):
44  break
45 
46  # compare strings field-by-field
47  fields1 = s1.split()
48  fields2 = s2.split()
49  if len(fields1)!=len(fields2):
50  errcode += 1
51  else:
52  for i in range(len(fields1)):
53  try:
54  f1 = float(fields1[i])
55  f2 = float(fields2[i])
56 
57  #Check for lower limit
58  if ((abs(f1)<=l_limit) and (abs(f2)<=l_limit)):
59  continue
60  #Check for tolerance
61  if abs(f2-f1)/(abs(f1)+1e-20)>tol:
62  errcode += 1
63  break
64  except ValueError:
65  if fields1[i]!=fields2[i]:
66  errcode += 1
67  break
68 
69  lines1 += 1
70  lines2 += 1
71  i2 += 1
72 
73  if errcode!=0:
74  flog.write("FAILED, files differ significantly in %d lines\n" % errcode)
75  elif lines1!=lines2:
76  flog.write("FAILED, files have different number of lines\n")
77  errcode = 1000
78 
79  return errcode
80 
81 
82 
83 
84 def compare_lists( stest, strial, x_col, y_col, tol, l_limit, flog):
85  """
86  compare x- and y- data of two lists from two files via linear interpolation
87  - ignore all strings which start with '#'
88  - use relative tolerance 'tol'
89  - compare test file 'stest' against trial file 'strial'
90  - x_col is the index of the x-column, this column should be monotonic increasing
91  - y_col is the index of the y-column
92  - flog is a log file
93  """
94  i = 0
95  errcode = 0
96  # Convert them to lists
97  if isinstance(y_col,int):
98  y_col = [y_col]
99  if isinstance(tol,int):
100  tol = [tol]
101 
102  # Get the amount of y-cols to compare with. There are more than 1 y-column, but only one x-column
103  amount_ycols = len(y_col)
104 
105  x_trial = []
106  y_trial = [[] for i in range(amount_ycols)]
107 
108  #Create x- and y-list of trial file
109  for i in range(len(strial)):
110  s1 = strial[i].strip()
111  # skip all empty lines and lines which start with '#'
112  if (len(s1)==0) or (s1[0]=='#'):
113  continue
114 
115  try:
116  # Only one x-value
117  x_trial.append(float(s1.split()[x_col]))
118  # Multiple y-values
119  for j in range(amount_ycols):
120  y_trial[j].append(float(s1.split()[y_col[j]]))
121  except:
122  errcode = 1
123 
124 
125  x_test = []
126  y_test = [[] for i in range(amount_ycols)]
127  # Create x- and y-list of test file
128  for i in range(len(stest)):
129  s1 = stest[i].strip()
130  # skip all empty lines and lines which start with '#'
131  if (len(s1)==0) or (s1[0]=='#'):
132  continue
133 
134  try:
135  # Only one x-value
136  x_test.append(float(s1.split()[x_col]))
137  # Multiple y-values
138  for j in range(amount_ycols):
139  y_test[j].append(float(s1.split()[y_col[j]]))
140  except:
141  errcode = 1
142 
143 
144  # Get the first and the last index of the x-value list
145  start_x = max(x_trial[0],x_test[0])
146  end_x = max(start_x,min(x_trial[-1],x_test[-1]))
147  comp_ind = []
148  for i in range(len(x_trial)):
149  if (x_trial[i] >= start_x) and (x_trial[i] <= end_x):
150  comp_ind.append(i)
151 
152  # Get start and end comparison index
153  comp_ind_start = min(comp_ind)
154  comp_ind_end = max(comp_ind)
155 
156  # Cycle through all lists
157  for i in range(amount_ycols):
158  # Now we can compare the two lists
159  # Take the x-values of trial as interpolation points
160  try:
161  test_function = interp1d(x_test,y_test[i] ,kind='linear')
162  trial = np.array(y_trial[i][comp_ind_start:comp_ind_end])
163  test = np.array(test_function(x_trial[comp_ind_start:comp_ind_end]))
164  diff_list = list(map(lambda x,y: abs(1.-abs(x)/(abs(y)+1e-20)),trial,test))
165  # Lower limit
166  diff_list =np.array(diff_list)
167  diff_list[(test<l_limit) & (trial<l_limit)] = 0
168  except:
169  errcode=4
170 
171  # Check for empty list
172  try:
173  max_diff = max(diff_list)
174  except:
175  max_diff = 0.
176  errcode=3
177 
178 
179  # Is it above the tolerance?
180  if max_diff > tol[i]:
181  errcode = 2
182 
183  # Write errors to logfile
184  if errcode==1:
185  flog.write("FAILED, error in reading columns\n")
186  elif errcode==2:
187  flog.write("FAILED, maximum difference %4.2e" % max_diff + " was above the tolerance of %4.2e" % tol[i]+" in column "+str(y_col[i])+"\n")
188  elif errcode==3:
189  flog.write("FAILED, could not find a maximum difference"+"\n")
190  elif errcode==4:
191  flog.write("FAILED, interpolation failed"+"\n")
192 
193  if errcode != 0:
194  # Set the errorcode to some undefined value
195  errcode = 100
196 
197  return errcode
198 
199 
200 def compare_analytic( stest, strial, x_col, y_col, function, tol, flog):
201  """
202  compare x- and y- data of two lists from two files via linear interpolation
203  - ignore all strings which start with '#'
204  - use relative tolerance 'tol'
205  - compare test file 'stest' against an analytic solution
206  - x_col is the index of the x-column
207  - y_col is the index of the y-column
208  - function is a list of functions, representing the behavior of the specific columns
209  - flog is a log file
210  """
211  i = 0
212  errcode = 0
213  # Convert them to lists
214  if isinstance(y_col,int):
215  y_col = [y_col]
216  if isinstance(tol,int):
217  tol = [tol]
218 
219  # Get the amount of y-cols to compare with. There are more than 1 y-column, but only one x-column
220  amount_ycols = len(y_col)
221 
222  x_trial = []
223  y_trial = [[] for i in range(amount_ycols)]
224  max_diff = 0
225  #Create x- and y-list of trial file
226  for i in range(len(strial)):
227  s1 = strial[i].strip()
228  # skip all empty lines and lines which start with '#'
229  if (len(s1)==0) or (s1[0]=='#'):
230  continue
231 
232  try:
233  # Only one x-value
234  x_trial.append(float(s1.split()[x_col]))
235  # Multiple y-values
236  for j in range(amount_ycols):
237  y_trial[j].append(float(s1.split()[y_col[j]]))
238  except:
239  errcode = 1
240 
241 
242  x_test = []
243  y_test = [[] for i in range(amount_ycols)]
244  # Create x- and y-list of test file
245  for i in range(len(stest)):
246  s1 = stest[i].strip()
247  # skip all empty lines and lines which start with '#'
248  if (len(s1)==0) or (s1[0]=='#'):
249  continue
250 
251  try:
252  # Only one x-value
253  x_test.append(float(s1.split()[x_col]))
254  # Multiple y-values
255  for j in range(amount_ycols):
256  y_test[j].append(float(s1.split()[y_col[j]]))
257  except:
258  errcode = 1
259 
260 
261 
262  # Cycle through all lists
263  for i in range(amount_ycols):
264  # Now we can compare the analytic solution to all given columns
265  # Get the actual analytic function
266  current_f = function[i]
267  # Calculate the analytic solution
268  try:
269  analytic_y = current_f(np.array(x_test))
270  except:
271  # Something wrong with the x-input or the input function
272  errcode=2
273 
274  # Calculate the difference in percent
275  diff_list = list(map(lambda x,y: abs(1.-abs(x)/(abs(y)+1e-20)),y_test[i],analytic_y))
276  # Check for empty list
277  try:
278  max_diff = max(diff_list)
279  except:
280  errcode=3
281 
282  # Is it above the tolerance?
283  if max_diff > tol[i]:
284  errcode = 4
285 
286  # Write errors to logfile
287  if errcode==1:
288  flog.write("FAILED, error in reading columns\n")
289  elif errcode==2:
290  flog.write("FAILED, could not calculate analytic solution for column "+str(y_col[i])+"\n")
291  elif errcode==3:
292  flog.write("FAILED, could not find a maximum difference"+"\n")
293  elif errcode==4:
294  flog.write("FAILED, maximum difference %4.2e" % max_diff + " was above the tolerance of %4.2e" % tol[i]+" in column "+str(y_col[i])+"\n")
295 
296  if errcode != 0:
297  # Set the errorcode to some undefined value
298  errcode = 100
299 
300  return errcode
301 
302 
303 class testcase:
304  """ Class for winnet testcases
305 
306  Attributes:
307  - testname: the name of the testcase
308  - basedir: winnet base directory (where the Makefile is located)
309  - program: path to the program that is being tested
310  - testdir: test directory (normally $WINNET/test/<TESTNAME>
311  - parfile: name of the parameter file that is used
312  - logfile: where the test log will be written
313 
314  Methods:
315  - deploy()
316  - launch()
317  - monitor()
318  - analyze()
319  - cleanup()
320  """
321  def __init__(self, tc, bdir):
322  self.testname = tc
323  self.basedir = bdir
324  self.program = bdir + "/bin/winnet"
325 
326  def deploy(self):
327  #--- unpack testcase ---------------------------------------------------
328  if not hasattr(self,'testdir'):
329  self.testdir = self.basedir + "/test/" + self.testname
330  if os.path.exists(self.testdir): # delete if test directory already exists
331  subprocess.call(["rm","-rf", self.testdir])
332  if not hasattr(self,'logfile'):
333  self.logfile = self.basedir+"/test/"+self.testname+".log"
334  if os.path.exists(self.logfile):
335  subprocess.call(["rm","-rf",self.logfile])
336  if not hasattr(self,'arcfile'):
337  self.arcfile = self.basedir + "/test/" + self.testname + ".tgz"
338  if not os.path.exists(self.arcfile):
339  print("ERROR: archive file " + self.arcfile + " is missing")
340  return 404
341 
342  os.chdir(self.basedir + "/test")
343  subprocess.call(["tar","axf",self.arcfile])
344  if not os.path.isdir(self.testdir):
345  print("ERROR: archive "+self.arcfile+" is corrupt. Exiting...")
346  return 101
347 
348  #--- parameter file ---------------------------------------------------
349  if not hasattr(self,'parfile'):
350  # the unpacked testcase directory must contain one (and only one!)
351  # parameter file, matching the template '*.par'
352  os.chdir(self.testdir)
353  out = os.listdir('./')
354  parfile_count = 0
355  for fname in out:
356 
357  if fname.endswith('.par'):
358  self.parfile = fname
359  parfile_count = parfile_count + 1
360  if parfile_count != 1:
361  print("ERROR: cannot determine parameter file for the test")
362  return 103
363  else: # check if the parfile exists
364  if not os.path.exists(self.parfile):
365  print("ERROR: specified paramter file "+self.parfile+" is missing")
366  return 404
367  self.parfile_name = os.path.basename(self.parfile)
368 
369  #--- prepare temporary simulation directory ----------------------------
370  os.chdir(self.testdir)
371  if os.path.exists("testrun"):
372  subprocess.call(["rm","-rf","testrun"])
373  os.mkdir("testrun")
374  # substitute @WINNET@ -> basedir, @TESTDIR@ -> testdir (with UNIX sed)
375  command = "sed 's%@WINNET@%" +self.basedir+"%g;" \
376  + "s%@TESTDIR@%"+self.testdir+"%g' "\
377  + self.parfile+" > " \
378  + self.testdir + "/testrun/" + self.parfile_name
379  subprocess.call(command,shell=True)
380  # create directoris for snapshots and flow files
381  os.chdir("testrun")
382  os.mkdir("snaps")
383  os.mkdir("flow")
384 
385  #--- compile code if necessary -----------------------------------------
386  if not os.path.exists(self.program):
387  os.chdir(self.basedir)
388  subprocess.call(["make","clean"])
389  subprocess.call("make")
390 
391  #--- return successful termination code --------------------------------
392  return 0
393 
394 
395  def launch(self):
396  #--- run the program, don't forget to save process ID -----------------
397  # (the following assumes that the shell is bash; might not work in more
398  # primitive shells; will not work with csh or tcsh)
399  # Also run only on one core
400  environment_command = 'export OMP_NUM_THREADS=1 && '
401  os.chdir(self.testdir+"/testrun")
402  command= environment_command+self.program+" "+self.parfile_name+" >OUT 2>ERR &\n"
403  subprocess.call("ulimit -s unlimited\n" + command + \
404  """echo $! > PID
405  if [ $? -ne 0 ]; then
406  echo "Test simulation with PID #`cat PID` probably failed to launch."
407  echo "Check """+self.testdir+"""/testrun/ERR for errors"
408  fi
409  """, shell=True)
410  return 0
411 
412  def monitor(self):
413  #--- monitor the progress ----------------------------------------------
414  # must have OUT file in the test suite
415  os.chdir(self.testdir+"/testrun")
416  if not os.path.exists("../OUT"):
417  print("\nWARNING: test suite doesn't have example OUT file!")
418  return 0
419 
420  out = shell_command(["wc","-l","../OUT"])
421  expected_lines = int(out.split()[0])
422  current_lines = 0
423 
424  # obtain process ID
425  out = shell_command(["cat","PID"])
426  PID = int(out.split()[0])
427  still_running = True
428  i = 0
429  while still_running:
430  time.sleep(0.1)
431  if i%2==1:
432  out = shell_command(["ps","h",str(PID)])
433  still_running = (len(out.split())>0)
434  #
435  out = shell_command(["wc","-l","OUT"])
436  current_lines = int(out.split()[0])
437  #
438  n1 = self.len_spaces * current_lines / expected_lines
439  n1 = int(min(n1, self.len_spaces))
440  n2 = self.len_spaces - n1
441  q = 100.0 * current_lines / expected_lines
442  q = min(100.0, q)
443  i = i + 1
444  sys.stdout.write("\r [" + self.whichtest + "] " + self.testname \
445  + ": .."+"."*n1 + "-/|\\"[i%4] +" "*n2 \
446  + (" [%5.1f" % q) + "%]")
447  sys.stdout.flush()
448 
449  return 0
450 
451 
452  def analyze(self):
453  os.chdir(self.testdir+"/testrun")
454  errcode = 0
455  flog = open(self.logfile,'w')
456  if not hasattr(self, 'checklist'):
457  flog.write("WARNING: no files defined for checking\n")
458  return 1
459  flog.write("Comparing files:\n")
460  for outfile in list(self.checklist.keys()):
461  flog.write(" - %-20s: " % outfile)
462 
463 
464  # call specified method with given tolerance
465  meth = self.checklist[outfile]['method']
466  # read trial file
467  if os.path.exists('../'+outfile) and (meth != 'exists'):
468  f_trial = open('../'+outfile)
469  s_trial = f_trial.read().split('\n')
470  f_trial.close()
471  elif (meth == 'exists'):
472  pass # No trial file needed
473  else:
474  errcode += 1
475  flog.write("FAILED, missing trial file in the test archive\n")
476  continue
477 
478  # read test file
479  if os.path.exists(outfile):
480  if (meth != 'exists'):
481  f_test = open(outfile)
482  s_test = f_test.read().split('\n')
483  f_test.close()
484  else:
485  errcode += 1
486  flog.write("FAILED, missing file\n")
487  continue
488 
489  # Set the tolerance, not necessary for exists method
490  if (meth != 'exists'):
491  tol = self.checklist[outfile]['tolerance']
492  #Check for lower limit of the comparison
493  if 'lowerlimit' in self.checklist[outfile]:
494  l_limit = self.checklist[outfile]['lowerlimit']
495  else:
496  l_limit = 0. #Default value
497  try:
498  if (meth == 'default'):
499  errc = compare_default (s_test, s_trial, tol,l_limit, flog)
500  errcode += errc
501  elif(meth == 'listcompare'):
502  x_column = self.checklist[outfile]['x_column']
503  y_column = self.checklist[outfile]['y_column']
504  errc = compare_lists (s_test, s_trial, x_column, y_column, tol, l_limit, flog)
505  errcode += errc
506  elif(meth == 'analyticcompare'):
507  x_column = self.checklist[outfile]['x_column']
508  y_column = self.checklist[outfile]['y_column']
509  functions = self.checklist[outfile]['function']
510  errc = compare_analytic (s_test, s_trial, x_column, y_column, functions, tol, flog)
511  errcode += errc
512  elif(meth == 'exists'):
513  errc = 0
514  else: #Insert new methods here
515  errcode += 1
516  flog.write("FAILED, unknown file comparison method '%s'\n" % meth)
517  continue
518  except:
519  errcode += 1
520  errc = 10
521  flog.write("FAILED, test method failed.\n")
522 
523  if errc==0:
524  flog.write("OK\n")
525 
526  flog.close()
527  return errcode
528 
529  def cleanup(self):
530  if os.path.exists(self.logfile):
531  subprocess.call(["rm","-rf",self.logfile])
532  if os.path.exists(self.testdir):
533  subprocess.call(["rm","-rf",self.testdir])
534 
bin.testcase_class.compare_analytic
def compare_analytic(stest, strial, x_col, y_col, function, tol, flog)
Definition: testcase_class.py:200
bin.testcase_class.shell_command
def shell_command(cmdlist)
Definition: testcase_class.py:8
bin.testcase_class.testcase.monitor
def monitor(self)
Definition: testcase_class.py:412
bin.testcase_class.testcase.analyze
def analyze(self)
Definition: testcase_class.py:452
bin.testcase_class.testcase.parfile
parfile
Definition: testcase_class.py:358
bin.testcase_class.compare_default
def compare_default(stest, strial, tol, l_limit, flog)
Definition: testcase_class.py:15
bin.testcase_class.testcase
Definition: testcase_class.py:303
bin.testcase_class.testcase.basedir
basedir
Definition: testcase_class.py:323
bin.testcase_class.testcase.deploy
def deploy(self)
Definition: testcase_class.py:326
bin.testcase_class.testcase.cleanup
def cleanup(self)
Definition: testcase_class.py:529
bin.testcase_class.testcase.__init__
def __init__(self, tc, bdir)
Definition: testcase_class.py:321
bin.testcase_class.testcase.launch
def launch(self)
Definition: testcase_class.py:395
bin.testcase_class.testcase.program
program
Definition: testcase_class.py:324
inter_module::interp1d
subroutine, public interp1d(n, xp, xb, yp, res, flin, itype)
Interface for 1D interpolation routines.
Definition: inter_module.f90:99
bin.testcase_class.testcase.testdir
testdir
Definition: testcase_class.py:329
bin.testcase_class.testcase.logfile
logfile
Definition: testcase_class.py:333
bin.testcase_class.testcase.parfile_name
parfile_name
Definition: testcase_class.py:367
bin.testcase_class.testcase.testname
testname
Definition: testcase_class.py:322
bin.testcase_class.compare_lists
def compare_lists(stest, strial, x_col, y_col, tol, l_limit, flog)
Definition: testcase_class.py:84
bin.testcase_class.testcase.arcfile
arcfile
Definition: testcase_class.py:337