source: flex_extract.git/Testing/Regression/Mars_request/test_cmp_mars_requests.py @ be6c0a2

ctbtodev
Last change on this file since be6c0a2 was be6c0a2, checked in by Anne Philipp <anne.philipp@…>, 4 years ago

added Readme to tests; changed directory structure; added recent test results;

  • Property mode set to 100755
File size: 10.3 KB
Line 
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""Comparison of the created MARS requests of two flex_extract versions.
4
5There will be comparisons for the given standard control files in the
6"Controls" - directory. The result of the comparison is stored in the
7"Log" - directory with an individual timestamp in the format %Y-%m-%d_%H-%M-%S.
8(E.g. log_2018-11-23_12-42-29)
9The MARS request files are named such that it contains the name of the
10corresponding control files "<control-identifier>.csv" (e.g. EA5_mr.csv).
11They are stored in the corresponding version directory and have the same
12name in both versions.
13
14The script should be called like:
15
16    python test_cmp_mars_requests.py <old_version_number> <new_version_number>
17
18Note
19----
20The MARS request files from the older version have to be in place already.
21The request files of the new/current version will be generated automatically
22with the "run_local.sh" script.
23It is necessary to have a directory named after the version number of
24flex_extract. For example: "7.0.3" and "7.1".
25
26Example
27-------
28    python test_cmp_mars_requests.py 7.0.4 7.1
29"""
30
31# ------------------------------------------------------------------------------
32# MODULES
33# ------------------------------------------------------------------------------
34import os
35import sys
36import pandas as pd
37import subprocess
38import shutil
39from datetime import datetime
40
41sys.path.append('../../../Source/Python')
42import _config
43from  Mods.tools import init128
44
45# ------------------------------------------------------------------------------
46# FUNCTION
47# ------------------------------------------------------------------------------
48def test_mr_column_equality(mr_old, mr_new):
49    '''Check if old and new version of MARS requests have the same
50    amount of columns.
51
52    If the number is not equal and/or the column names are not equal
53    an error message is stored in global variable "err_msg".
54
55    Parameters
56    ----------
57    mr_old : :obj:`pandas DataFrame`
58        Contains the mars requests from the old version of
59        flex_extract.
60
61    mr_new : :obj:`pandas DataFrame`
62        Contains the mars requests from the new version of
63        flex_extract.
64
65    Return
66    ------
67    bool
68        True if successful, False otherwise.
69    '''
70    global err_msg
71    if (len(mr_old.columns.values) == len(mr_new.columns.values) and
72        sorted(mr_old.columns.values) == sorted(mr_new.columns.values)):
73        return True
74    else:
75        err_msg = 'Unequal number and/or column names!\n'
76        return False
77
78
79def test_mr_number_equality(mr_old, mr_new):
80    '''Check if old and new version have the same number of requests.
81
82    If the number is not equal an error message is stored in
83    global variable "err_msg".
84
85    Parameters
86    ----------
87    mr_old : :obj:`pandas DataFrame`
88        Contains the mars requests from the old version of
89        flex_extract.
90
91    mr_new : :obj:`pandas DataFrame`
92        Contains the mars requests from the new version of
93        flex_extract.
94
95    Return
96    ------
97    bool
98        True if successful, False otherwise.
99    '''
100    global err_msg
101    if len(mr_new.index) == len(mr_old.index):
102        return True
103    else:
104        err_msg = 'Unequal number of mars requests!\n'
105        return False
106
107def test_mr_content_equality(mr_old, mr_new):
108    '''Check if old and new version have the same request contents.
109
110    If the content in a column is not equal an error message is stored in
111    global variable "err_msg", recording the column.
112
113    Parameters
114    ----------
115    mr_old : :obj:`pandas DataFrame`
116        Contains the mars requests from the old version of
117        flex_extract.
118
119    mr_new : :obj:`pandas DataFrame`
120        Contains the mars requests from the new version of
121        flex_extract.
122
123    Return
124    ------
125    bool
126        True if successful, False otherwise.
127    '''
128    global err_msg
129    lresult = None
130    columns = list(mr_new.columns.values)
131    del columns[columns.index('target')]
132    mr_new = trim_all_columns(mr_new)
133    mr_old = trim_all_columns(mr_old)
134    for col in columns:
135        if mr_new[col].equals(mr_old[col]):
136            lresult = True
137        else:
138            err_msg += 'Unconsistency happend to be in column: ' + col + '\n'
139            print("THERE SEEMS TO BE AN ERROR:")
140            print("CONTENT OF NEW VERSION:")
141            print(mr_new[col])
142            print("CONTENT OF OLD VERSION:")
143            print(mr_old[col])
144            return False
145    return lresult
146
147
148def trim_all_columns(df):
149    """
150    Trim whitespace from ends of each value across all series in dataframe
151    """
152    trim_strings = lambda x: x.strip() if isinstance(x, str) else x
153    return df.applymap(trim_strings)
154
155def convert_param_numbers(mr_old):
156    """
157    Convert the numbers parameter into integers with 3 digits.
158    """
159
160    if str(mr_old).strip() != "OFF" and mr_old != None and '/' in str(mr_old) :
161        numbers = mr_old.split('/')
162        number = str(int(numbers[0])).zfill(3)+'/TO/'+str(int(numbers[2])).zfill(3)
163
164        return number
165
166    return mr_old
167
168def convert_param_step(mr_old):
169    """
170    For pure forecast with steps greater than 23 hours, the older versions
171    writes out a list of steps instead with the syntax 'to' and 'by'.
172    e.g. 000/003/006/009/012/015/018/021/024/027/030/033/036
173   
174    Convert this to 0/to/36/by/3
175    """
176
177    #if 'to' in str(mr_old) and 'by' in str(mr_old):
178    #    steps = mr_old.split('/')
179    #    step = []
180    #    for i in range(int(steps[0]),int(steps[2]),int(steps[4])):
181    #        step.append(str(int(i)).zfill(2))
182    #    return '/'.join(step)
183   
184    if not mr_old.isdigit() and 'to' not in mr_old.lower():
185        if int(mr_old.split('/')[-1]) > 23:
186   
187            steps = mr_old.split('/')
188            dtime = int(steps[1]) - int(steps[0])
189           
190            nsteps = str(int(steps[1]))+'/to/'+str(int(steps[-1]))+'/by/'+str(int(dtime))
191            return nsteps           
192   
193    return mr_old
194
195def to_param_id(pars, table):
196    '''Transform parameter names to parameter ids with ECMWF grib table 128.
197
198    Parameters
199    ----------
200    pars : str
201        Addpar argument from CONTROL file in the format of
202        parameter names instead of ids. The parameter short
203        names are sepearted with "/" and they are passed as
204        one single string.
205
206    table : dict
207        Contains the ECMWF grib table 128 information.
208        The key is the parameter number and the value is the
209        short name of the parameter.
210
211    Return
212    ------
213    ipar : list of int
214        List of addpar parameters from CONTROL file transformed to
215        parameter ids in the format of integer.
216    '''
217    if not pars:
218        return []
219    if not isinstance(pars, str):
220        pars=str(pars)
221
222    cpar = pars.upper().split('/')
223    spar = []
224    for par in cpar:
225        par = par.strip()
226        for k, v in table.items():
227            if par.isdigit():
228                par = str(int(par)).zfill(3)
229            if par == k or par == v:
230                spar.append(k + '.128')
231                break
232        else:
233            print('\n\Warning: par ' + par + ' not found in table 128\n\n”')
234
235    return '/'.join(str(l) for l in spar)
236
237
238
239if __name__ == '__main__':
240
241    # basic values for paths and versions
242    control_path = 'Controls'
243    log_path = 'Log'
244    old_dir = sys.argv[1] # e.g. '7.0.4'
245    new_dir = sys.argv[2] # e.g. '7.1'
246    mr_filename = 'mars_requests.csv'
247
248    # have to be set to "True" in the beginnning
249    # so it only fails if a test fails
250    lfinal = True
251
252    # prepare log file for this test run
253    currenttime = datetime.now()
254    time_str = currenttime.strftime('%Y-%m-%d_%H-%M-%S')
255    logfile = os.path.join(log_path, 'log_' + time_str)
256    with open(logfile, 'a') as f:
257        f.write('Compare mars requests between version ' + old_dir +
258                ' and version ' + new_dir + ' : \n')
259
260    # list all controlfiles
261    controls =  os.listdir(control_path)
262
263    # loop over controlfiles
264    for c in controls:
265        # empty error message for every controlfile
266        err_msg = ''
267
268        # start flex_extract with controlfiles to get mars_request files
269        shutil.copy(os.path.join(control_path,c), _config.PATH_CONTROLFILES)
270        subprocess.check_output(['run_local.sh', new_dir, c])
271        os.remove(os.path.join(_config.PATH_CONTROLFILES,c))
272
273        # cut-of "CONTROL_" string and mv mars_reqeust file
274        # to a name including control specific name
275        mr_name = c.split('CONTROL_')[1] + '.csv'
276        shutil.move(os.path.join(new_dir,mr_filename), os.path.join(new_dir,mr_name))
277
278        # read both mr files (old & new)
279        mr_old = pd.read_csv(os.path.join(old_dir, mr_name))
280        mr_new = pd.read_csv(os.path.join(new_dir, mr_name))
281
282        mr_old.columns = mr_old.columns.str.strip()
283        mr_new.columns = mr_new.columns.str.strip()
284
285        # convert names in old to ids
286        table128 = init128(_config.PATH_GRIBTABLE)
287        #print mr_old['param']
288
289        # some format corrections are necessary to compare older versions with 7.1
290        mr_old['param'] = mr_old['param'].apply(to_param_id, args=[table128])
291        mr_old['number'] = mr_old['number'].apply(convert_param_numbers) 
292        if '142' in mr_old.loc[0,'param']: # if flux request
293            mr_old.loc[0,'step'] = convert_param_step(mr_old.loc[0,'step'])
294
295        print('Results: ', c)
296
297        # do tests on mr files
298        lcoleq = test_mr_column_equality(mr_old, mr_new)
299        lnoeq = test_mr_number_equality(mr_old, mr_new)
300        lcoeq = test_mr_content_equality(mr_old, mr_new)
301
302        # check results for mr file
303        lfinal = lfinal and lcoleq and lnoeq and lcoeq
304
305        # write out result to logging file
306        with open(logfile, 'a') as f:
307            if lcoleq and lnoeq and lcoeq:
308                f.write('... ' + c + ' ... OK!' + '\n')
309            else:
310                f.write('... ' + c + ' ... FAILED!' + '\n')
311                if err_msg:
312                    f.write('... \t' + err_msg + '\n')
313
314        exit
315
316    # exit with success or error status
317    if lfinal:
318        sys.exit(0) # 'SUCCESS'
319    else:
320        sys.exit(1) # 'FAIL'
Note: See TracBrowser for help on using the repository browser.
hosted by ZAMG