diff --git a/.travis.yml b/.travis.yml index de8b7dcc73..14e2484110 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,18 +18,17 @@ env: global: - FC=gfortran - MPI_DIR=/usr - - PHDF5_DIR=/usr - - HDF5_DIR=/usr + - HDF5_ROOT=/usr - OMP_NUM_THREADS=2 - OPENMC_CROSS_SECTIONS=$HOME/nndc_hdf5/cross_sections.xml - OPENMC_ENDF_DATA=$HOME/endf-b-vii.1 - OPENMC_MULTIPOLE_LIBRARY=$HOME/multipole_lib - PATH=$PATH:$HOME/NJOY2016/build matrix: - - OPENMC_CONFIG="^hdf5-debug$" - - OPENMC_CONFIG="^omp-hdf5-debug$" - - OPENMC_CONFIG="^mpi-hdf5-debug$" - - OPENMC_CONFIG="^phdf5-debug$" + - OMP=n MPI=n PHDF5=n + - OMP=y MPI=n PHDF5=n + - OMP=n MPI=y PHDF5=n + - OMP=n MPI=y PHDF5=y before_install: - sudo add-apt-repository ppa:nschloe/hdf5-backports -y diff --git a/tests/run_tests.py b/tests/run_tests.py deleted file mode 100755 index 6e6b886a50..0000000000 --- a/tests/run_tests.py +++ /dev/null @@ -1,552 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function - -import os -import sys -import shutil -import re -import glob -import socket -from subprocess import call, check_output -from collections import OrderedDict -from argparse import ArgumentParser - -# Command line parsing -parser = ArgumentParser() -parser.add_argument('-j', '--parallel', dest='n_procs', default='1', - help="Number of parallel jobs.") -parser.add_argument('-R', '--tests-regex', dest='regex_tests', - help="Run tests matching regular expression. \ - Test names are the directories present in tests folder.\ - This uses standard regex syntax to select tests.") -parser.add_argument('-C', '--build-config', dest='build_config', - help="Build configurations matching regular expression. \ - Specific build configurations can be printed out with \ - optional argument -p, --print. This uses standard \ - regex syntax to select build configurations.") -parser.add_argument('-l', '--list', action="store_true", - dest="list_build_configs", default=False, - help="List out build configurations.") -parser.add_argument("-p", "--project", dest="project", default="", - help="project name for build") -parser.add_argument("-D", "--dashboard", dest="dash", - help="Dash name -- Experimental, Nightly, Continuous") -parser.add_argument("-u", "--update", action="store_true", dest="update", - help="Allow CTest to update repo. (WARNING: may overwrite\ - changes that were not pushed.") -parser.add_argument("-s", "--script", action="store_true", dest="script", - help="Activate CTest scripting mode for coverage, valgrind\ - and dashboard capability.") -args = parser.parse_args() - -# Default compiler paths -FC = 'gfortran' -CC = 'gcc' -CXX = 'g++' -MPI_DIR = '/opt/mpich/3.2-gnu' -HDF5_DIR = '/opt/hdf5/1.8.16-gnu' -PHDF5_DIR = '/opt/phdf5/1.8.16-gnu' - -# Script mode for extra capability -script_mode = False - -# Override default compiler paths if environmental vars are found -if 'FC' in os.environ: - FC = os.environ['FC'] -if 'CC' in os.environ: - CC = os.environ['CC'] -if 'CXX' in os.environ: - CXX = os.environ['CXX'] -if 'MPI_DIR' in os.environ: - MPI_DIR = os.environ['MPI_DIR'] -if 'HDF5_DIR' in os.environ: - HDF5_DIR = os.environ['HDF5_DIR'] -if 'PHDF5_DIR' in os.environ: - PHDF5_DIR = os.environ['PHDF5_DIR'] - -# CTest script template -ctest_str = """set (CTEST_SOURCE_DIRECTORY "{source_dir}") -set (CTEST_BINARY_DIRECTORY "{build_dir}") - -set(CTEST_SITE "{host_name}") -set (CTEST_BUILD_NAME "{build_name}") -set (CTEST_CMAKE_GENERATOR "Unix Makefiles") -set (CTEST_BUILD_OPTIONS "{build_opts}") - -set(CTEST_UPDATE_COMMAND "git") - -set(CTEST_CONFIGURE_COMMAND "${{CMAKE_COMMAND}} -H${{CTEST_SOURCE_DIRECTORY}} -B${{CTEST_BINARY_DIRECTORY}} ${{CTEST_BUILD_OPTIONS}}") -set(CTEST_MEMORYCHECK_COMMAND "{valgrind_cmd}") -set(CTEST_MEMORYCHECK_COMMAND_OPTIONS "--tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes") -#set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE ${{CTEST_SOURCE_DIRECTORY}}/../tests/valgrind.supp) -set(MEM_CHECK {mem_check}) -if(MEM_CHECK) -set(ENV{{MEM_CHECK}} ${{MEM_CHECK}}) -endif() - -set(CTEST_COVERAGE_COMMAND "gcov") -set(COVERAGE {coverage}) -set(ENV{{COVERAGE}} ${{COVERAGE}}) - -{subproject} - -ctest_start("{dashboard}") -ctest_configure(RETURN_VALUE res) -{update} -ctest_build(RETURN_VALUE res) -if(NOT MEM_CHECK) -ctest_test({tests} PARALLEL_LEVEL {n_procs}, RETURN_VALUE res) -endif() -if(MEM_CHECK) -ctest_memcheck({tests} RETURN_VALUE res) -endif(MEM_CHECK) -if(COVERAGE) -ctest_coverage(RETURN_VALUE res) -endif(COVERAGE) -{submit} - -if (res EQUAL 0) -else() -message(FATAL_ERROR "") -endif() -""" - -# Define test data structure -tests = OrderedDict() - - -def cleanup(path): - """Remove generated output files.""" - for dirpath, dirnames, filenames in os.walk(path): - for fname in filenames: - for ext in ['.h5', '.ppm', '.voxel']: - if fname.endswith(ext) and fname != '1d_mgxs.h5': - os.remove(os.path.join(dirpath, fname)) - - -def which(program): - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - - -class Test(object): - def __init__(self, name, debug=False, optimize=False, mpi=False, openmp=False, - phdf5=False, valgrind=False, coverage=False): - self.name = name - self.debug = debug - self.optimize = optimize - self.mpi = mpi - self.openmp = openmp - self.phdf5 = phdf5 - self.valgrind = valgrind - self.coverage = coverage - self.success = True - self.msg = None - self.skipped = False - self.cmake = ['cmake', '-H..', '-Bbuild', - '-DPYTHON_EXECUTABLE=' + sys.executable] - - # Check for MPI - if self.mpi: - if os.path.exists(os.path.join(MPI_DIR, 'bin', 'mpifort')): - self.fc = os.path.join(MPI_DIR, 'bin', 'mpifort') - else: - self.fc = os.path.join(MPI_DIR, 'bin', 'mpif90') - self.cc = os.path.join(MPI_DIR, 'bin', 'mpicc') - self.cxx = os.path.join(MPI_DIR, 'bin', 'mpicxx') - else: - self.fc = FC - self.cc = CC - self.cxx = CXX - - # Sets the build name that will show up on the CDash - def get_build_name(self): - self.build_name = args.project + '_' + self.name - return self.build_name - - # Sets up build options for various tests. It is used both - # in script and non-script modes - def get_build_opts(self): - build_str = "" - if self.debug: - build_str += "-Ddebug=ON " - if self.optimize: - build_str += "-Doptimize=ON " - if not self.openmp: - build_str += "-Dopenmp=OFF " - if self.coverage: - build_str += "-Dcoverage=ON " - if self.phdf5: - build_str += "-DHDF5_PREFER_PARALLEL=ON " - else: - build_str += "-DHDF5_PREFER_PARALLEL=OFF " - self.build_opts = build_str - return self.build_opts - - # Write out the ctest script to tests directory - def create_ctest_script(self, ctest_vars): - with open('ctestscript.run', 'w') as fh: - fh.write(ctest_str.format(**ctest_vars)) - - # Runs the ctest script which performs all the cmake/ctest/cdash - def run_ctest_script(self): - os.environ['FC'] = self.fc - os.environ['CC'] = self.cc - os.environ['CXX'] = self.cxx - if self.mpi: - os.environ['MPI_DIR'] = MPI_DIR - if self.phdf5: - os.environ['HDF5_ROOT'] = PHDF5_DIR - else: - os.environ['HDF5_ROOT'] = HDF5_DIR - rc = call(['ctest', '-S', 'ctestscript.run', '-V']) - if rc != 0: - self.success = False - self.msg = 'Failed on ctest script.' - - # Runs cmake when in non-script mode - def run_cmake(self): - build_opts = self.build_opts.split() - self.cmake += build_opts - - os.environ['FC'] = self.fc - os.environ['CC'] = self.cc - os.environ['CXX'] = self.cxx - if self.mpi: - os.environ['MPI_DIR'] = MPI_DIR - if self.phdf5: - os.environ['HDF5_ROOT'] = PHDF5_DIR - self.cmake.append('-DHDF5_PREFER_PARALLEL=ON') - else: - os.environ['HDF5_ROOT'] = HDF5_DIR - self.cmake.append('-DHDF5_PREFER_PARALLEL=OFF') - rc = call(self.cmake) - if rc != 0: - self.success = False - self.msg = 'Failed on cmake.' - - # Runs make when in non-script mode - def run_make(self): - if not self.success: - return - - # Default make string - make_list = ['make', '-s'] - - # Check for parallel - if args.n_procs is not None: - make_list.append('-j') - make_list.append(args.n_procs) - - # Run make - rc = call(make_list) - if rc != 0: - self.success = False - self.msg = 'Failed on make.' - - # Runs ctest when in non-script mode - def run_ctests(self): - if not self.success: - return - - # Default ctest string - ctest_list = ['ctest'] - - # Check for parallel - if args.n_procs is not None: - ctest_list.append('-j') - ctest_list.append(args.n_procs) - - # Check for subset of tests - if args.regex_tests is not None: - ctest_list.append('-R') - ctest_list.append(args.regex_tests) - - # Run ctests - rc = call(ctest_list) - if rc != 0: - self.success = False - self.msg = 'Failed on testing.' - - -# Simple function to add a test to the global tests dictionary -def add_test(name, debug=False, optimize=False, mpi=False, openmp=False, - phdf5=False, valgrind=False, coverage=False): - tests.update({name: Test(name, debug, optimize, mpi, openmp, phdf5, - valgrind, coverage)}) - -# List of all tests that may be run. User can add -C to command line to specify -# a subset of these configurations -add_test('hdf5-normal') -add_test('hdf5-debug', debug=True) -add_test('hdf5-optimize', optimize=True) -add_test('omp-hdf5-normal', openmp=True) -add_test('omp-hdf5-debug', openmp=True, debug=True) -add_test('omp-hdf5-optimize', openmp=True, optimize=True) -add_test('mpi-hdf5-normal', mpi=True) -add_test('mpi-hdf5-debug', mpi=True, debug=True) -add_test('mpi-hdf5-optimize', mpi=True, optimize=True) -add_test('phdf5-normal', mpi=True, phdf5=True) -add_test('phdf5-debug', mpi=True, phdf5=True, debug=True) -add_test('phdf5-optimize', mpi=True, phdf5=True, optimize=True) -add_test('phdf5-omp-normal', mpi=True, phdf5=True, openmp=True) -add_test('phdf5-omp-debug', mpi=True, phdf5=True, openmp=True, debug=True) -add_test('phdf5-omp-optimize', mpi=True, phdf5=True, openmp=True, optimize=True) -add_test('hdf5-debug_valgrind', debug=True, valgrind=True) -add_test('hdf5-debug_coverage', debug=True, coverage=True) - -# Check to see if we should just print build configuration information to user -if args.list_build_configs: - for key in tests: - print('Configuration Name: {0}'.format(key)) - print(' Debug Flags:..........{0}'.format(tests[key].debug)) - print(' Optimization Flags:...{0}'.format(tests[key].optimize)) - print(' MPI Active:...........{0}'.format(tests[key].mpi)) - print(' OpenMP Active:........{0}'.format(tests[key].openmp)) - print(' Valgrind Test:........{0}'.format(tests[key].valgrind)) - print(' Coverage Test:........{0}\n'.format(tests[key].coverage)) - exit() - -# Delete items of dictionary that don't match regular expression -if args.build_config is not None: - to_delete = [] - for key in tests: - if not re.search(args.build_config, key): - to_delete.append(key) - for key in to_delete: - del tests[key] - -# Check for dashboard and determine whether to push results to server -# Note that there are only 3 basic dashboards: -# Experimental, Nightly, Continuous. On the CDash end, these can be -# reorganized into groups when a hostname, dashboard and build name -# are matched. -if args.dash is None: - dash = 'Experimental' - submit = '' -else: - dash = args.dash - submit = 'ctest_submit()' - -# Check for update command, which will run git fetch/merge and will delete -# any changes to repo that were not pushed to remote origin -update = 'ctest_update()' if args.update else '' - -# Check for CTest scipts mode -# Sets up whether we should use just the basic ctest command or use -# CTest scripting to perform tests. -script_mode = (args.dash is not None or args.script) - -# Setup CTest script vars. Not used in non-script mode -pwd = os.getcwd() -ctest_vars = { - 'source_dir': os.path.join(pwd, os.pardir), - 'build_dir': os.path.join(pwd, 'build'), - 'host_name': socket.gethostname(), - 'dashboard': dash, - 'submit': submit, - 'update': update, - 'n_procs': args.n_procs -} - -# Check project name -subprop = "set_property(GLOBAL PROPERTY SubProject {0})" -if args.project == "": - ctest_vars.update({'subproject': ''}) -elif args.project == 'develop': - ctest_vars.update({'subproject': ''}) -else: - ctest_vars.update({'subproject': subprop.format(args.project)}) - -# Set up default valgrind tests (subset of all tests) -# Currently takes too long to run all the tests with valgrind -# Only used in script mode -valgrind_default_tests = "cmfd_feed|confidence_intervals|\ -density|eigenvalue_genperbatch|energy_grid|entropy|\ -lattice_multiple|output|plotreflective_plane|\ -rotation|salphabetascore_absorption|seed|source_energy_mono|\ -sourcepoint_batch|statepoint_interval|survival_biasing|\ -tally_assumesep|translation|uniform_fs|universe|void" - -# Delete items of dictionary if valgrind or coverage and not in script mode -to_delete = [] -if not script_mode: - for key in tests: - if re.search('valgrind|coverage', key): - to_delete.append(key) - -for key in to_delete: - del tests[key] - -# Check if tests is empty -if not tests: - print('No tests to run.') - exit() - -# Begin testing -shutil.rmtree('build', ignore_errors=True) -cleanup('.') -for key in iter(tests): - test = tests[key] - - # Extra display if not in script mode - if not script_mode: - print('-'*(len(key) + 6)) - print(key + ' tests') - print('-'*(len(key) + 6)) - sys.stdout.flush() - - # Verify fortran compiler exists - if which(test.fc) is None: - test.msg = 'Compiler not found: {0}'.format(test.fc) - test.success = False - continue - - # Verify valgrind command exists - if test.valgrind: - valgrind_cmd = which('valgrind') - if valgrind_cmd is None: - test.msg = 'No valgrind executable found.' - test.success = False - continue - else: - valgrind_cmd = '' - - # Verify gcov/lcov exist - if test.coverage: - if which('gcov') is None: - test.msg = 'No {} executable found.'.format(exe) - test.success = False - continue - - # Set test specific CTest script vars. Not used in non-script mode - ctest_vars.update({'build_name': test.get_build_name()}) - ctest_vars.update({'build_opts': test.get_build_opts()}) - ctest_vars.update({'mem_check': test.valgrind}) - ctest_vars.update({'coverage': test.coverage}) - ctest_vars.update({'valgrind_cmd': valgrind_cmd}) - - # Check for user custom tests - # INCLUDE is a CTest command that allows for a subset - # of tests to be executed. Only used in script mode. - if args.regex_tests is None: - ctest_vars.update({'tests': ''}) - - # No user tests, use default valgrind tests - if test.valgrind: - ctest_vars.update({'tests': 'INCLUDE {0}'. - format(valgrind_default_tests)}) - else: - ctest_vars.update({'tests': 'INCLUDE {0}'. - format(args.regex_tests)}) - - # Main part of code that does the ctest execution. - # It is broken up by two modes, script and non-script - if script_mode: - - # Create ctest script - test.create_ctest_script(ctest_vars) - - # Run test - test.run_ctest_script() - - else: - - # Run CMAKE to configure build - test.run_cmake() - - # Go into build directory - os.chdir('build') - - # Build OpenMC - test.run_make() - - # Run tests - test.run_ctests() - - # Leave build directory - os.chdir(os.pardir) - - # Copy over log file - if script_mode: - logfile = glob.glob('build/Testing/Temporary/LastTest_*.log') - else: - logfile = glob.glob('build/Testing/Temporary/LastTest.log') - if len(logfile) > 0: - logfilename = os.path.split(logfile[0])[1] - logfilename = os.path.splitext(logfilename)[0] - logfilename = logfilename + '_{0}.log'.format(test.name) - shutil.copy(logfile[0], logfilename) - - # For coverage builds, use lcov to generate HTML output - if test.coverage: - if which('lcov') is None or which('genhtml') is None: - print('No lcov/genhtml command found. ' - 'Could not generate coverage report.') - else: - shutil.rmtree('coverage', ignore_errors=True) - call(['lcov', '--directory', '.', '--capture', - '--output-file', 'coverage.info']) - call(['genhtml', '--output-directory', 'coverage', 'coverage.info']) - os.remove('coverage.info') - - if test.valgrind: - # Copy memcheck output to memcheck directory - shutil.rmtree('memcheck', ignore_errors=True) - os.mkdir('memcheck') - memcheck_out = glob.glob('build/Testing/Temporary/MemoryChecker.*.log') - for fname in memcheck_out: - shutil.copy(fname, 'memcheck/') - - # Remove generated XML files - xml_files = check_output(['git', 'ls-files', '.', '--exclude-standard', - '--others']).split() - for f in xml_files: - os.remove(f) - - # Clear build directory and remove binary and hdf5 files - shutil.rmtree('build', ignore_errors=True) - if script_mode: - os.remove('ctestscript.run') - cleanup('.') - -# Print out summary of results -print('\n' + '='*54) -print('Summary of Compilation Option Testing:\n') - -if sys.stdout.isatty(): - OK = '\033[92m' - FAIL = '\033[91m' - ENDC = '\033[0m' - BOLD = '\033[1m' -else: - OK = '' - FAIL = '' - ENDC = '' - BOLD = '' - -return_code = 0 - -for test in tests: - print(test + '.'*(50 - len(test)), end='') - if tests[test].success: - print(BOLD + OK + '[OK]' + ENDC) - else: - print(BOLD + FAIL + '[FAILED]' + ENDC) - print(' '*len(test)+tests[test].msg) - return_code = 1 - -sys.exit(return_code) diff --git a/tools/ci/travis-install.py b/tools/ci/travis-install.py new file mode 100644 index 0000000000..0e4dbdc01b --- /dev/null +++ b/tools/ci/travis-install.py @@ -0,0 +1,65 @@ +import os +import shutil +import subprocess + + +def which(program): + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + + +def install(omp=False, mpi=False, phdf5=False): + # Create build directory and change to it + shutil.rmtree('build', ignore_errors=True) + os.mkdir('build') + os.chdir('build') + + # Build in debug mode by default + cmake_cmd = ['cmake', '-Ddebug=on'] + + # Turn off OpenMP if specified + if not omp: + cmake_cmd.append('-Dopenmp=off') + + # For MPI, we just need to change the Fortran compiler + if mpi: + os.environ['FC'] = 'mpifort' if which('mpifort') else 'mpif90' + + # Tell CMake to prefer parallel HDF5 if specified + if phdf5: + if not mpi: + raise ValueError('Parallel HDF5 must be used in ' + 'conjunction with MPI.') + cmake_cmd.append('-DHDF5_PREFER_PARALLEL=on') + + # Build and install + cmake_cmd.append('..') + subprocess.call(cmake_cmd) + subprocess.call(['make', '-j']) + subprocess.call(['make', 'install']) + + +def main(): + # Convert Travis matrix environment variables into arguments for install() + omp = (os.environ.get('OMP') == 'y') + mpi = (os.environ.get('MPI') == 'y') + phdf5 = (os.environ.get('PHDF5') == 'y') + + # Build and install + install(omp, mpi, phdf5) + + +if __name__ == '__main__': + main() diff --git a/tools/ci/travis-install.sh b/tools/ci/travis-install.sh index 354ae7f7f4..6051f03aa9 100755 --- a/tools/ci/travis-install.sh +++ b/tools/ci/travis-install.sh @@ -15,5 +15,8 @@ if [[ "$TRAVIS_PYTHON_VERSION" == "3.4" ]]; then pip install pandas==0.20.3 fi +# Build and install +python tools/ci/travis-install.py + # Install OpenMC in editable mode pip install -e .[test] diff --git a/tools/ci/travis-script.sh b/tools/ci/travis-script.sh index 978a2811c4..2934cb59a0 100755 --- a/tools/ci/travis-script.sh +++ b/tools/ci/travis-script.sh @@ -1,8 +1,15 @@ #!/bin/bash set -ex -cd tests -if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OPENMC_CONFIG == '^hdf5-debug$' ]]; then + +# Run regression test suite +cd build +ctest + +# Run source check +cd ../tests +if [[ $TRAVIS_PYTHON_VERSION == "3.4" && $OMP == 'n' && $MPI == 'n' ]]; then ./check_source.py fi -./run_tests.py -C $OPENMC_CONFIG -j 2 + +# Run unit tests pytest --cov=../openmc -v unit_tests/