1
################################################################################
3
# Copyright (c) 2009 The MadGraph5_aMC@NLO Development team and Contributors
5
# This file is a part of the MadGraph5_aMC@NLO project, an application which
6
# automatically generates Feynman diagrams and matrix elements for arbitrary
7
# high-energy processes in the Standard Model and beyond.
9
# It is subject to the MadGraph5_aMC@NLO license which should accompany this
12
# For more information, visit madgraph.phys.ucl.ac.be and amcatnlo.web.cern.ch
14
################################################################################
28
root_path = os.path.split(os.path.dirname(os.path.realpath( __file__ )))[0]
29
sys.path.append(root_path)
31
import madgraph.various.misc as misc
33
from madgraph.interface.extended_cmd import Cmd
35
from madgraph.iolibs.files import cp, ln, mv
36
from madgraph import MadGraph5Error
41
_file_path = os.path.dirname(os.path.realpath(__file__))
42
_input_file_path = path.abspath(os.path.join(_file_path,'input_files'))
43
_hc_comparison_files = pjoin(_input_file_path,'IOTestsComparison')
44
_hc_comparison_tarball = pjoin(_input_file_path,'IOTestsComparison.tar.bz2')
47
""" IOTest runner and attribute container. It can be overloaded depending on
48
what kind of IO test will be necessary later """
51
proc_files = ['[^.+\.(f|dat|inc)$]']
52
# Some model files are veto because they are sourced by dictionaries whose
54
model_files = ['../../Source/MODEL/[^.+\.(f|inc)$]',
55
'-../../Source/MODEL/lha_read.f',
56
'-../../Source/MODEL/param_read.inc',
57
'-../../Source/MODEL/param_write.inc']
58
helas_files = ['../../Source/DHELAS/[^.+\.(f|inc)$]']
60
# We also exclude the helas_files because they are sourced from unordered
62
all_files = proc_files+model_files
64
def __init__(self, hel_amp=None,
69
""" Can be overloaded to add more options if necessary.
70
The format above is typically useful because we don't aim at
71
testing all processes for all exporters and all model, but we
72
choose certain combinations which spans most possibilities.
73
Notice that the process and model can anyway be recovered from the
74
LoopAmplitude object, so it does not have to be specified here."""
76
if testedFiles is None:
77
raise MadGraph5Error, "TestedFiles must be specified in IOTest."
79
if outputPath is None:
80
raise MadGraph5Error, "outputPath must be specified in IOTest."
82
self.testedFiles = testedFiles
83
self.hel_amp = hel_amp
84
self.helasModel = helasModel
85
self.exporter = exporter
87
if not str(path.dirname(_file_path)) in str(outputPath) and \
88
not str(outputPath).startswith('/tmp/'):
89
raise MadGraph5Error, "OutputPath must be within MG directory or"+\
92
self.outputPath = outputPath
95
""" Run the test and returns the path where the files have been
96
produced and relative to which the paths in TestedFiles are specified. """
99
model = self.hel_amp.get('processes')[0].get('model')
100
self.exporter.copy_v4template(model.get('name'))
101
self.exporter.generate_loop_subprocess(self.hel_amp, self.helasModel)
102
wanted_lorentz = self.hel_amp.get_used_lorentz()
103
wanted_couplings = list(set(sum(self.hel_amp.get_used_couplings(),[])))
104
self.exporter.convert_model_to_mg4(model,wanted_lorentz,wanted_couplings)
106
proc_name='P'+self.hel_amp.get('processes')[0].shell_string()
107
return pjoin(self.outputPath,'SubProcesses',proc_name)
109
def clean_output(self):
110
""" Remove the output_path if existing. Careful!"""
111
if not str(path.dirname(_file_path)) in str(self.outputPath) and \
112
not str(self.outputPath).startswith('/tmp/'):
113
raise MadGraph5Error, "Cannot safely remove %s."%str(self.outputPath)
115
if path.isdir(self.outputPath):
116
shutil.rmtree(self.outputPath)
118
#===============================================================================
120
#===============================================================================
121
class IOTestManager(unittest.TestCase):
122
""" A helper class to perform tests based on the comparison of files output
123
by exporters against hardcoded ones. """
125
# Define a bunch of paths useful
126
_input_file_path = path.abspath(os.path.join(_file_path,'input_files'))
127
_mgme_file_path = path.abspath(os.path.join(_file_path, *([os.path.pardir]*1)))
128
_loop_file_path = pjoin(_mgme_file_path,'Template','loop_material')
129
_cuttools_file_path = pjoin(_mgme_file_path, 'vendor','CutTools')
130
_hc_comparison_files = pjoin(_input_file_path,'IOTestsComparison')
132
# The tests loaded are stored here
133
# Each test is stored in a dictionary with entries of the format:
134
# {(folder_name, test_name) : IOTest}
137
# filesChecked_filter allows to filter files which are checked.
138
# These filenames should be the path relative to the
139
# position SubProcess/<P0_proc_name>/ in the output. Notice that you can
140
# use the parent directory keyword ".." and instead of the filename you
141
# can exploit the syntax [regexp] (brackets not part of the regexp)
142
# Ex. ['../../Source/DHELAS/[.+\.(inc|f)]']
143
# You can also prepend '-' to a filename to veto it (it cannot be a regexp
144
# in this case though.)
145
filesChecked_filter = ['ALL']
146
# To filter what tests you want to use, edit the tag ['ALL'] by the
147
# list of test folders and names you want in.
148
# You can prepend '-' to the folder or test name to veto it instead of
149
# selecting it. Typically, ['-longTest'] considers all tests but the
150
# longTest one (synthax not available for filenames)
151
testFolders_filter = ['ALL']
152
testNames_filter = ['ALL']
154
def __init__(self,*args,**opts):
155
""" Add the object attribute my_local_tests."""
156
# Lists the keys for the tests of this particular instance
157
self.instance_tests = []
158
super(IOTestManager,self).__init__(*args,**opts)
160
def runTest(self,*args,**opts):
161
""" This method is added so that one can instantiate this class """
162
raise MadGraph5Error, 'runTest in IOTestManager not supposed to be called.'
164
def assertFileContains(self, source, solution):
165
""" Check the content of a file """
166
list_cur=source.read().split('\n')
167
list_sol=solution.split('\n')
178
for a, b in zip(list_sol, list_cur):
179
self.assertEqual(a,b)
180
self.assertEqual(len(list_sol), len(list_cur))
183
def need(cls,folderName=None, testName=None):
184
""" Returns True if the selected filters do not exclude the testName
185
and folderName given in argument. Specify None to disregard the filters
186
corresponding to this category."""
188
if testName is None and folderName is None:
191
if not testName is None:
192
pattern = [f[1:] for f in cls.testNames_filter if f.startswith('+')]
193
chosen = [f for f in cls.testNames_filter if \
194
not f.startswith('-') and not f.startswith('+')]
195
veto = [f[1:] for f in cls.testNames_filter if f.startswith('-')]
198
if chosen!=['ALL'] and not testName in chosen:
199
if not any(testName.startswith(pat) for pat in pattern):
202
if not folderName is None:
203
pattern = [f[1:] for f in cls.testFolders_filter if f.startswith('+')]
204
chosen = [f for f in cls.testFolders_filter if \
205
not f.startswith('-') and not f.startswith('+')]
206
veto = [f[1:] for f in cls.testFolders_filter if f.startswith('-')]
207
if folderName in veto:
209
if chosen!=['ALL'] and not folderName in chosen:
210
if not any(folderName.startswith(pat) for pat in pattern):
213
if not folderName is None and not testName is None:
214
if (folderName,testName) in cls.all_tests.keys() and \
215
(folderName,testName) in cls.instance_tests:
221
def toFileName(cls, file_path):
222
""" transforms a file specification like ../../Source/MODEL/myfile to
223
%..%..%Source%MODEL%myfile """
224
fpath = copy.copy(file_path)
225
if not isinstance(fpath, str):
230
return '%'+'%'.join(file_path.split('/'))
233
def toFilePath(cls, file_name):
234
""" transforms a file name specification like %..%..%Source%MODEL%myfile
235
to ../../Source/MODEL/myfile"""
237
if not file_name.startswith('%'):
240
return pjoin(file_name[1:].split('%'))
242
def test_IOTests(self):
243
""" A test function in the mother so that all childs automatically run
244
their tests when scanned with the test_manager. """
246
# Set it to True if you want info during the regular test_manager.py runs
247
self.runIOTests(verbose=False)
249
def addIOTest(self, folderName, testName, IOtest):
250
""" Add the test (folderName, testName) to class attribute all_tests. """
252
if not self.need(testName=testName, folderName=folderName):
255
# Add this to the instance test_list
256
if (folderName, testName) not in self.instance_tests:
257
self.instance_tests.append((folderName, testName))
259
# Add this to the global test_list
260
if (folderName, testName) in self.all_tests.keys() and \
261
self.all_tests[(folderName, testName)]!=IOtest:
262
raise MadGraph5Error, \
263
"Test (%s,%s) already defined."%(folderName, testName)
265
self.all_tests[(folderName, testName)] = IOtest
267
def runIOTests(self, update = False, force = 0, verbose=False, \
268
testKeys='instanceList'):
269
""" Run the IOTests for this instance (defined in self.instance_tests)
270
and compare the files of the chosen tests against the hardcoded ones
271
stored in tests/input_files/IOTestsComparison. If you see
272
an error in the comparison and you are sure that the newest output
273
is correct (i.e. you understand that this modification is meant to
274
be so). Then feel free to automatically regenerate this file with
275
the newest version by doing
277
./test_manager -i U folderName/testName/fileName
279
If update is True (meant to be used by __main__ only) then
280
it will create/update/remove the files instead of testing them.
281
The argument tests can be a list of tuple-keys describing the tests
282
to cover. Otherwise it is the instance_test list.
283
The force argument must be 10 if you do not want to monitor the
284
modifications on the updated files. If it is 0 you will monitor
285
all modified file and if 1 you will monitor each modified file of
286
a given name only once.
289
# First make sure that the tarball need not be untarred
290
# Extract the tarball for hardcoded in all cases to make sure the
291
# IOTestComparison folder is synchronized with it.
292
if path.isdir(_hc_comparison_files):
294
shutil.rmtree(_hc_comparison_files)
297
if path.isfile(_hc_comparison_tarball):
298
tar = tarfile.open(_hc_comparison_tarball,mode='r:bz2')
299
tar.extractall(path.dirname(_hc_comparison_files))
302
raise MadGraph5Error, \
303
"Could not find the comparison tarball %s."%_hc_comparison_tarball
305
# In update = True mode, we keep track of the modification to
306
# provide summary information
307
modifications={'updated':[],'created':[], 'removed':[]}
309
# List all the names of the files for which modifications have been
310
# reviewed at least once.The approach taken here is different than
311
# with the list refusedFolder and refusedTest.
312
# The key of the dictionary are the filenames and the value are string
313
# determining the user answer for that file.
314
reviewed_file_names = {}
316
# Chose what test to cover
317
if testKeys == 'instanceList':
318
testKeys = self.instance_tests
320
if verbose: print "\n== Operational mode : file %s ==\n"%\
321
('UPDATE' if update else 'TESTING')
322
for (folder_name, test_name) in testKeys:
324
iotest=self.all_tests[(folder_name, test_name)]
326
raise MadGraph5Error, 'Test (%s,%s) could not be found.'\
327
%(folder_name, test_name)
328
if verbose: print "Processing %s in %s"%(test_name,folder_name)
329
files_path = iotest.run()
331
# First create the list of files to check as the user might be using
332
# regular expressions.
334
# Store here the files reckognized as veto rules (with filename
337
for fname in iotest.testedFiles:
338
# Disregard the veto rules
339
if fname.endswith(']'):
340
split=fname[:-1].split('[')
341
# folder without the final /
343
search = re.compile('['.join(split[1:]))
344
# In filesToCheck, we must remove the files_path/ prepended
345
filesToCheck += [ f[(len(str(files_path))+1):]
346
for f in glob.glob(pjoin(files_path,folder,'*')) if \
347
(not search.match(path.basename(f)) is None and \
348
not path.isdir(f) and not path.islink(f))]
349
elif fname.startswith('-'):
350
veto_rules.append(fname[1:])
352
filesToCheck.append(fname)
354
# Apply the trimming of the veto rules
355
filesToCheck = [f for f in filesToCheck if f not in veto_rules]
358
# Remove files which are no longer used for comparison
359
activeFiles = [self.toFileName(f) for f in filesToCheck]
360
for file in glob.glob(pjoin(_hc_comparison_files,folder_name,\
362
# Ignore the .BackUp files and directories
363
if path.basename(file).endswith('.BackUp') or\
366
if path.basename(file) not in activeFiles:
367
if force==0 or (force==1 and \
368
path.basename(file) not in reviewed_file_names.keys()):
369
answer = Cmd.timed_input(question=
370
"""Obsolete ref. file %s in %s/%s detected, delete it? [y/n] >"""\
371
%(path.basename(file),folder_name,test_name)
373
reviewed_file_names[path.basename(file)] = answer
375
path.basename(file) in reviewed_file_names.keys()):
376
answer = reviewed_file_names[path.basename(file)]
380
if answer not in ['Y','y','']:
382
print " > [ IGNORED ] file deletion "+\
383
"%s/%s/%s"%(folder_name,test_name,path.basename(file))
387
if verbose: print " > [ REMOVED ] %s/%s/%s"\
388
%(folder_name,test_name,path.basename(file))
389
modifications['removed'].append(
390
'/'.join(str(file).split('/')[-3:]))
393
# Make sure it is not filtered out by the user-filter
394
if self.filesChecked_filter!=['ALL']:
395
new_filesToCheck = []
396
for file in filesToCheck:
397
# Try if it matches any filter
398
for filter in self.filesChecked_filter:
399
# A regular expression
400
if filter.endswith(']'):
401
split=filter[:-1].split('[')
402
# folder without the final /
404
if folder!=path.dirname(pjoin(file)):
406
search = re.compile('['.join(split[1:]))
407
if not search.match(path.basename(file)) is None:
408
new_filesToCheck.append(file)
410
# Just the exact filename
412
new_filesToCheck.append(file)
414
filesToCheck = new_filesToCheck
416
# Now we can scan them and process them one at a time
417
# Keep track of the folders and testNames the user did not want to
420
refused_testNames = []
421
for fname in filesToCheck:
422
file_path = path.abspath(pjoin(files_path,fname))
423
self.assertTrue(path.isfile(file_path),
424
'File %s not found.'%str(file_path))
425
comparison_path = pjoin(_hc_comparison_files,\
426
folder_name,test_name,self.toFileName(fname))
428
if not os.path.isfile(comparison_path):
429
raise MadGraph5Error, 'The file %s'%str(comparison_path)+\
431
goal = open(comparison_path).read()%misc.get_pkg_info()
433
self.assertFileContains(open(file_path), goal)
436
self.assertFileContains(open(file_path), goal)
437
except AssertionError:
438
print " > %s differs from the reference."%fname
441
if not path.isdir(pjoin(_hc_comparison_files,folder_name)):
443
if folder_name in refused_Folders:
445
answer = Cmd.timed_input(question=
446
"""New folder %s detected, create it? [y/n] >"""%folder_name
448
if answer not in ['Y','y','']:
449
refused_Folders.append(folder_name)
450
if verbose: print " > [ IGNORED ] folder %s"\
453
if verbose: print " > [ CREATED ] folder %s"%folder_name
454
os.makedirs(pjoin(_hc_comparison_files,folder_name))
455
if not path.isdir(pjoin(_hc_comparison_files,folder_name,
458
if (folder_name,test_name) in refused_testNames:
460
answer = Cmd.timed_input(question=
461
"""New test %s/%s detected, create it? [y/n] >"""%(folder_name,test_name)
463
if answer not in ['Y','y','']:
464
refused_testNames.append((folder_name,test_name))
465
if verbose: print " > [ IGNORED ] test %s/%s"\
466
%(folder_name,test_name)
468
if verbose: print " > [ CREATED ] test %s/%s"\
469
%(folder_name,test_name)
470
os.makedirs(pjoin(_hc_comparison_files,folder_name,
472
# Transform the package information to make it a template
473
file = open(file_path,'r')
475
target = target.replace('MadGraph5_aMC@NLO v. %(version)s, %(date)s'\
476
%misc.get_pkg_info(),
477
'MadGraph5_aMC@NLO v. %(version)s, %(date)s')
479
if os.path.isfile(comparison_path):
480
file = open(comparison_path,'r')
481
existing = file.read()
483
if existing == target:
486
# Copying the existing reference as a backup
487
tmp_path = pjoin(_hc_comparison_files,folder_name,\
488
test_name,self.toFileName(fname)+'.tmp')
489
if os.path.isfile(tmp_path):
491
file = open(tmp_path,'w')
494
if force==0 or (force==1 and path.basename(\
495
comparison_path) not in reviewed_file_names.keys()):
497
"""File %s in test %s/%s differs by the following (reference file first):
498
"""%(fname,folder_name,test_name)
499
text += misc.Popen(['diff',str(comparison_path),
500
str(tmp_path)],stdout=subprocess.PIPE).\
502
# Remove the last newline
505
if (len(text.split('\n'))<15):
509
print "Difference displayed in editor."
510
answer = Cmd.timed_input(question=
511
"""Ref. file %s differs from the new one (see diff. before), update it? [y/n] >"""%fname
514
reviewed_file_names[path.basename(\
515
comparison_path)] = answer
516
elif (force==1 and path.basename(\
517
comparison_path) in reviewed_file_names.keys()):
518
answer = reviewed_file_names[path.basename(\
522
if answer not in ['Y','y','']:
523
if verbose: print " > [ IGNORED ] %s"%fname
526
# Copying the existing reference as a backup
527
back_up_path = pjoin(_hc_comparison_files,folder_name,\
528
test_name,self.toFileName(fname)+'.BackUp')
529
if os.path.isfile(back_up_path):
530
os.remove(back_up_path)
531
cp(comparison_path,back_up_path)
532
if verbose: print " > [ UPDATED ] %s"%fname
533
modifications['updated'].append(
534
'/'.join(comparison_path.split('/')[-3:]))
536
if force==0 or (force==1 and path.basename(\
537
comparison_path) not in reviewed_file_names.keys()):
538
answer = Cmd.timed_input(question=
539
"""New file %s detected, create it? [y/n] >"""%fname
541
reviewed_file_names[path.basename(\
542
comparison_path)] = answer
543
elif (force==1 and path.basename(\
544
comparison_path) in reviewed_file_names.keys()):
545
answer = reviewed_file_names[\
546
path.basename(comparison_path)]
549
if answer not in ['Y','y','']:
550
if verbose: print " > [ IGNORED ] %s"%fname
552
if verbose: print " > [ CREATED ] %s"%fname
553
modifications['created'].append(
554
'/'.join(comparison_path.split('/')[-3:]))
555
file = open(comparison_path,'w')
559
# Clean the iotest output
560
iotest.clean_output()
562
# Monitor the modifications when in creation files mode by returning the
563
# modifications dictionary.