From b689d09449c2fee480b4ce9a9e257a54b81c2722 Mon Sep 17 00:00:00 2001 From: Emmanuel Le Guirriec <emmanuel.le.guirriec@cern.ch> Date: Tue, 11 Dec 2018 11:33:36 +0000 Subject: [PATCH] Merge branch '21.2-add-frozen-derivation-test' into '21.2' First version of the frozen derivation test script See merge request atlas/athena!16488 (cherry picked from commit 830e8d306b780740082dfcc8dc3c03ac745d6efe [formerly 8b307f6fb2c9f3e683260cfa6e05264c5c0d8278]) 6c0f0b46 First version of the frozen derivation test script ee5ceb38 Add option to enable the user to run the test in data or mc Former-commit-id: 3ecbb60ed59808e08dee2416be46ac6fc16e6fbf --- .../python/iconfTool/__init__.py | 0 .../python/iconfTool/utils/__init__.py | 0 .../DerivationFrameworkCore/CMakeLists.txt | 2 +- .../scripts/frozen_derivation_test.py | 307 ++++++++++++++++++ 4 files changed, 308 insertions(+), 1 deletion(-) mode change 100644 => 100755 Control/AthenaConfiguration/python/iconfTool/__init__.py mode change 100755 => 100644 Control/AthenaConfiguration/python/iconfTool/utils/__init__.py create mode 100755 PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/scripts/frozen_derivation_test.py diff --git a/Control/AthenaConfiguration/python/iconfTool/__init__.py b/Control/AthenaConfiguration/python/iconfTool/__init__.py old mode 100644 new mode 100755 diff --git a/Control/AthenaConfiguration/python/iconfTool/utils/__init__.py b/Control/AthenaConfiguration/python/iconfTool/utils/__init__.py old mode 100755 new mode 100644 diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/CMakeLists.txt b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/CMakeLists.txt index 547e982716c..f5ed44409d3 100644 --- a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/CMakeLists.txt +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/CMakeLists.txt @@ -31,4 +31,4 @@ atlas_add_component( DerivationFrameworkCore # Install files from the package: atlas_install_python_modules( python/*.py ) atlas_install_joboptions( share/*.py ) - +atlas_install_scripts( scripts/frozen_derivation_test.py ) diff --git a/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/scripts/frozen_derivation_test.py b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/scripts/frozen_derivation_test.py new file mode 100755 index 00000000000..f3ae6d70299 --- /dev/null +++ b/PhysicsAnalysis/DerivationFramework/DerivationFrameworkCore/scripts/frozen_derivation_test.py @@ -0,0 +1,307 @@ +#!/usr/bin/env python + +# Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration + +import glob +import logging +import multiprocessing +import os +import subprocess +import sys +import uuid + +# Setting logging options +fmt = '%(asctime)s :: %(levelname)-8s :: %(message)s' +datefmt = '%m/%d/%Y %H:%M' +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG, + format=fmt, + datefmt=datefmt, + filename='./frozen_derivation_test.log', + filemode='w') +console = logging.StreamHandler() +formatter = logging.Formatter(fmt,datefmt=datefmt) +console.setFormatter(formatter) +logger.addHandler(console) + +### +# Function to check if the derivation job succeeded or not +### +def check_job_success(logFile): + ''' Return true/false depending on whether the provided log file has "successful run" pattern. ''' + + # Simply tail the log and look for the famous "successful run" + cmd = ' tail -n 20 ' + logFile + ' | grep "\\"successful run\\"" | wc -l ' + + result = subprocess.check_output(['/bin/bash', '-c',cmd]).rstrip() + logger.debug('Job success check for the log file %s is %s', logFile, result) + + return (result == '1') + +#### +## Function to run the clean test +#### +def run_clean_test(inputFile,extraArgs,release,cleanDir,uniqId): + ''' Runs the clean derivation test and checks if the job succeeded or not. ''' + + logger.info('Starting clean test...') + + # Build the actual command + cmd = ( ' mkdir -p ' + cleanDir + '/clean_derivation_test_' + uniqId + ';' + ' cd ' + cleanDir + '/clean_derivation_test_' + uniqId + ';' + ' source $AtlasSetup/scripts/asetup.sh ' + release + ' >& /dev/null;' + ' Reco_tf.py --inputAODFile %s %s > derivation_test.log 2>&1;'%(inputFile,extraArgs) ) + + # Execute the actual command + subprocess.call(cmd,shell=True) + + # Check the job status + logFile = cleanDir + '/clean_derivation_test_' + uniqId + '/log.AODtoDAOD' + logger.debug('Attempting to read the clean log file from %s', logFile) + result = check_job_success(logFile) + + logger.info('Finished clean test w/ status %s...', 'SUCCESS' if result else 'FAILURE') + pass + +#### +## Function to run the patched test +#### +def run_patched_test(inputFile,extraArgs,pwd,release): + ''' Runs the patched derivation test and checks if the job succeeded or not. ''' + + logger.info('Starting patched test...') + + # Build the actual command + cmd = ' cd '+pwd+';' + if not release: # This is the CI escape + pass + elif 'WorkDir_DIR' in os.environ: + cmake_build_dir = (os.environ['WorkDir_DIR']) + logger.debug('Using the local cmake build directory %s'%(cmake_build_dir)) + cmd += ( ' source $AtlasSetup/scripts/asetup.sh '+release+' >& /dev/null;' + ' source '+cmake_build_dir+'/setup.sh;' ) + else : + cmd += ( ' source $AtlasSetup/scripts/asetup.sh '+release+' >& /dev/null;' ) + cmd += ( ' mkdir -p patched_derivation_test; cd patched_derivation_test;' + ' Reco_tf.py --inputAODFile %s %s > derivation_test.log 2>&1;'%(inputFile,extraArgs) ) + + # Execute the actual command + subprocess.call(cmd,shell=True) + + # Check the job status + logFile = pwd + '/patched_derivation_test/log.AODtoDAOD' + logger.debug('Attempting to read the patched log file from %s', logFile ) + result = check_job_success(logFile) + + logger.info('Finished patched test w/ status %s...', 'SUCCESS' if result else 'FAILURE' ) + pass + +#### +## Function to run the diff-root +#### +def compare_files(cleanHeadDir,uniqId,pwd,isPatchedOnly): + ''' Compares the clean and patched DAOD files to see if the contents differ. ''' + + cleanDir = cleanHeadDir + '/clean_derivation_test_' + uniqId + if isPatchedOnly: + cleanDir = cleanHeadDir + + logger.debug('Reading the reference file from location '+cleanDir) + + comparison_command = ( 'acmd.py diff-root ' + cleanDir + '/DAOD_PHYSVAL.pool.root ' + 'patched_derivation_test/DAOD_PHYSVAL.pool.root ' + '--error-mode resilient ' + '--entries 20 > patched_derivation_test/diff-root.DAOD.log 2>&1' ) + output,error = subprocess.Popen(['/bin/bash', '-c', comparison_command], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() + + # We want to catch/print both container additions/subtractions as well as + # changes in these containers. `allGood_return_code` is meant to catch + # other issues found in the diff (sync issues etc.) + passed_all_test=True + allGood_return_code=False + + logFile = pwd + '/patched_derivation_test/diff-root.DAOD.log' + logger.debug('Attempting to read the diff-log file from %s', logFile ) + with open(logFile) as f: + for line in f: + if 'WARNING' in line: # Catches container addition/subtractions + logger.error(line) + passed_all_test=False + if 'leaves differ' in line: # Catches changes in branches + logger.error(line) + passed_all_test=False + if 'INFO all good.' in line: + allGood_return_code = True + + _decision = passed_all_test and allGood_return_code + if _decision: + logger.info('Passed!') + else: + logger.error('Your tag breaks changes the AOD content! See patched_derivation_test/diff-root.DAOD.log file for more information.') + + return _decision + +#### +## Function to get release information +#### +def get_release_setup(): + ''' This function tries to read environment variables to figure out the appropriate ATLAS setup ''' + + # Read in environment variables + current_nightly = os.environ['AtlasBuildStamp'] + release_base=os.environ['AtlasBuildBranch'] + release_head=os.environ['AtlasVersion'] + platform=os.environ['AthDerivation_PLATFORM'] + project=os.environ['AtlasProject'] + + # Search through the main nightly installation location + builds_dir_searchStr='/cvmfs/atlas-nightlies.cern.ch/repo/sw/'+release_base+'/[!latest_]*/'+project+'/'+release_head + sorted_list = sorted(glob.glob(builds_dir_searchStr), key=os.path.getmtime) + + latest_nightly = '' + for folder in reversed(sorted_list): + if not glob.glob(folder+'/../../'+release_base+'__'+project+'*-opt*.log') : continue + latest_nightly = folder.split('/')[-3] + break + + if current_nightly != latest_nightly: + logger.info('Please be aware that you are not testing your tags in the latest available nightly, which is %s',latest_nightly) + + setup='%s,%s,%s,AthDerivation'%(release_base,platform.replace('-', ','),current_nightly) + + logger.info('Your tags will be tested in environment %s',setup) + + return setup + + +#### +## Main Function +#### +def main(): + ''' Main function that parses user inputs, prepares and runs the tests ''' + + from optparse import OptionParser + parser=OptionParser(usage='%prog [options]', + version='%prog 0.1.0') + + parser.add_option('--log-level', + type='choice', + action='store', + dest='logLevel', + choices=['critical','error','warning','info','debug'], + default='info', + help='Logging level (default: info)') + + parser.add_option('-c', + '--clean-dir', + type='string', + dest='cleanDir', + default='/tmp/', + help='Specify the head directory for running the clean derivation tests ' + '(default: /tmp/${USER}). If provided w/ -p (--patched) option, ' + 'the reference results are expected to be in this location.') + + parser.add_option('-p', + '--patched', + action='store_true', + dest='patched', + default=False, + help='Flag to avoid clean run. ' + 'In this mode only patched test will run and the ' + 'changes will be compared against predefined ' + ' reference files (default: false)') + + parser.add_option('--mode', + type='choice', + action='store', + dest='mode', + choices=['data','mc'], + default='mc', + help='Test mode (default: mc)') + + (options,args) = parser.parse_args() + + logger.info(76*'-') + + # Set logging level + logger.setLevel(options.logLevel.upper()) + + # Check if patched only + isPatchedOnly = options.patched + + # Check if the clean directory exists + cleanHeadDir = options.cleanDir + if str(cleanHeadDir) == '/tmp/': + cleanHeadDir += os.environ['USER'] + + if os.path.exists(cleanHeadDir): + logger.info('The head directory for the output of the clean tests will be %s', cleanHeadDir) + else: + logger.error('The head directory for the output of the clean tests doesn\'t exist!') + logger.error('Please check that %s exists and you have enough permissions to read/write.', cleanHeadDir) + logger.error('If not please specify a folder that satisfies the requirements via -c or --clean-dir options.') + sys.exit(-1) + + # Check if an ATLAS project is setup + if 'AtlasVersion' not in os.environ: + logging.error('Please setup the an ATLAS release!') + sys.exit(-1) + + # Get the release information + mySetup = '' + try: + mySetup = get_release_setup() + except KeyError as error: + logger.warning('Cannot find the key %s',error) + logger.warning('This might be benign if running in CI enviroment') + cleanSetup = mySetup + + # Get the PWD and create UUID to avoid possible overlap between runs + myPwd = os.getcwd() + logger.debug('Current working directory is %s',myPwd) + uniqId = str(uuid.uuid4()) + logger.debug('Created uuid %s',uniqId) + + # Run the tests + logger.info('%s %s %s',25*'-',' Run AthDerivation Test ',25*'-') + inputFile = ( '/cvmfs/atlas-nightlies.cern.ch/repo/data/data-art/DerivationFrameworkART/{}' + ''.format('AOD.14795494._005958.pool.root.1' if options.mode == 'mc' else 'data18_13TeV.00348403.physics_Main.merge.AOD.f920_m1947._lb0829._0001.1') ) + extraArgs = ( '--outputDAODFile pool.root --maxEvents 20 --reductionConf PHYSVAL --preExec \'rec.doApplyAODFix.set_Value_and_Lock(True); ' + 'from BTagging.BTaggingFlags import BTaggingFlags;BTaggingFlags.CalibrationTag = "{}"; ' + 'from AthenaCommon.AlgSequence import AlgSequence; topSequence = AlgSequence(); ' + 'topSequence += CfgMgr.xAODMaker__DynVarFixerAlg("InDetTrackParticlesFixer", Containers = ["InDetTrackParticlesAux." ] )\'' + ''.format('BTagCalibRUN12-08-40' if options.mode == 'mc' else 'BTagCalibRUN12Onl-08-40') ) + + myprocesses = {} + if not isPatchedOnly: + myprocesses['clean'] = multiprocessing.Process(target=run_clean_test,kwargs={'inputFile':inputFile,'extraArgs' : extraArgs,'release':cleanSetup,'cleanDir':cleanHeadDir,'uniqId':uniqId}) + myprocesses['patched'] = multiprocessing.Process(target=run_patched_test,kwargs={'inputFile':inputFile,'extraArgs' : extraArgs,'pwd':myPwd,'release':mySetup}) + myprocesses['clean'].start() + myprocesses['patched'].start() + else: + myprocesses['patched'] = multiprocessing.Process(target=run_patched_test,kwargs={'inputFile':inputFile,'extraArgs' : extraArgs,'pwd':myPwd,'release':mySetup}) + myprocesses['patched'].start() + + for process in myprocesses: + myprocesses[process].join() + + # Post processing + logger.info('%s %s %s',25*'-',' Run xAOD content check ',25*'-') + + all_tests_passed = True + + if not compare_files(cleanHeadDir,uniqId,myPwd,isPatchedOnly): + all_tests_passed = False + + logger.info('%s %s %s',25*'-',' Summary of all tests ',25*'-') + if all_tests_passed: + logger.info(' All Tests: Passed (0)') + logger.info(76*'-') + sys.exit(0) + else: + logger.info(' All Tests: FAILED (-1)') + logger.info(76*'-') + sys.exit(-1) + +if __name__ == '__main__': + main() -- GitLab