diff --git a/.gitignore b/.gitignore index 5f2b6788f8..97cb8f6825 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ env/ spack-build_stage/ spack-test_stage/ spack-misc_cache/ -user-config/ +user-config/linux/ user-cache/ \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index 17ce8f8e2c..1bcb58d363 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,13 +12,10 @@ pipeline { } post { always { - archiveArtifacts artifacts: 'log/**/*.log', allowEmptyArchive: true - withCredentials([string(credentialsId: 'd976fe24-cabf-479e-854f-587c152644bc', variable: 'GITHUB_AUTH_TOKEN')]) { - sh """ - source env/bin/activate - python3 src/report_tests.py --auth_token ${GITHUB_AUTH_TOKEN} --build_id ${BUILD_ID} --issue_id ${ghprbPullId} - """ - } + sh """ + python3 tools/summarize_logs.py || true + """ + archiveArtifacts artifacts: 'log/*', allowEmptyArchive: true deleteDir() } } @@ -32,21 +29,20 @@ pipeline { """ } } - stage('Unit Tests') { + stage('Bootstrap spack') { steps { sh """ - mkdir -p log/${NODENAME}/unit_test source env/bin/activate - python3 test/unit_test.py ${NODENAME} > log/${NODENAME}/unit_test/summary.log 2>&1 + source ./setup-env.sh + spack spec gnuconfig """ } } - stage('Bootstrap spack') { + stage('Unit Tests') { steps { sh """ source env/bin/activate - . ./setup-env.sh - spack spec gnuconfig + python3 test/unit_test.py """ } } @@ -54,14 +50,18 @@ pipeline { steps { sh """ source env/bin/activate - pytest -v -n auto --scope \"""" + env.ghprbCommentBody + " \" test/integration_test.py" + source ./setup-env.sh $USER_ENV_ROOT + pytest -v -n auto test/integration_test.py + """ } } stage('System Tests') { steps { sh """ source env/bin/activate - pytest -v -n auto --maxprocesses=24 --scope \"""" + env.ghprbCommentBody + " \" test/system_test.py" + source ./setup-env.sh $USER_ENV_ROOT + pytest -v -n auto test/system_test.py + """ } } } diff --git a/setup-env.sh b/setup-env.sh index da1c8239a5..b939ea95c0 100644 --- a/setup-env.sh +++ b/setup-env.sh @@ -3,24 +3,21 @@ parent_dir=$( cd "$(dirname "${BASH_SOURCE[0]:-${(%):-%x}}")" ; pwd -P ) if [[ "$#" == 1 ]]; then - machine="$1" -else - machine="$( "$parent_dir"/src/machine.sh )" - if [[ "$machine" == "balfrin" || "$machine" == "tasna" ]]; then - machine="/mch-environment/v6" - fi -fi + uenv="$1" + export SPACK_UENV_PATH="$uenv" + export SPACK_SYSTEM_CONFIG_PATH="$uenv"/config -if [[ ${machine:0:1} == "/" ]]; then - export SPACK_SYSTEM_CONFIG_PATH="$machine"/config - export SPACK_USER_CONFIG_PATH="$parent_dir"/sysconfigs/uenv -else - export SPACK_SYSTEM_CONFIG_PATH="$parent_dir"/sysconfigs/"$machine" - export SPACK_USER_CONFIG_PATH="$parent_dir"/user-config + if [[ $uenv == "euler" ]]; then + export SPACK_SYSTEM_CONFIG_PATH="$parent_dir"/sysconfigs/euler + fi fi +export SPACK_USER_CONFIG_PATH="$parent_dir"/user-config export SPACK_USER_CACHE_PATH="$parent_dir"/user-cache - . "$parent_dir"/spack/share/spack/setup-env.sh -echo Spack configured for "$machine". +if [[ -n "$uenv" ]]; then + echo Spack configured with upstream "$uenv". +else + echo Spack configured with no upstream. +fi diff --git a/src/format.py b/src/format.py deleted file mode 100644 index c39e71fcfc..0000000000 --- a/src/format.py +++ /dev/null @@ -1,34 +0,0 @@ -def time_format(seconds) -> str: - "Returns a string formatted as 'XXh YYm ZZ.ZZs'." - - m, s = divmod(seconds, 60) - h, m = divmod(m, 60) - - parts = [] - if h: - parts.append(f'{h:.0f}h') - if m: - parts.append(f'{m:.0f}m') - if s: - parts.append(f'{s:.2f}s') - return ' '.join(parts) - - -def sanitized_filename(filename: str) -> str: - "Removes irrelevant information and replaces problematic chars." - - replacements = [ - (' --show-log-on-error', ''), # irrelevant in filename - (' --test=root', ''), # irrelevant in filename - (' --until build', ''), # irrelevant in filename - (' --dont-restage', ''), # irrelevant in filename - (' -n', ''), # irrelevant in filename - (' -v', ''), # irrelevant in filename - ('%', ''), # causes problems in web browsers - (' ', '_'), # causes problems in bash - ('"', ''), # causes problems with parameter expansion in bash - ("'", ""), # causes problems with parameter expansion in bash - ] - for old, new in replacements: - filename = filename.replace(old, new) - return filename diff --git a/src/github.py b/src/github.py deleted file mode 100644 index 96b8fd2cea..0000000000 --- a/src/github.py +++ /dev/null @@ -1,87 +0,0 @@ -import requests - - -class GitHubRepo: - - def __init__(self, group: str, repo: str, auth_token: str = None) -> None: - self.group: str = group - self.repo: str = repo - self.auth_token: str = auth_token - - def comment(self, issue_id: str, text: str) -> None: - url = f'https://api.github.com/repos/{self.group}/{self.repo}/issues/{issue_id}/comments' - - headers = {'Content-Type': 'application/json'} - if self.auth_token is not None: - headers['Authorization'] = 'token ' + self.auth_token - - requests.post(url, headers=headers, json={'body': text}) - - -class Markdown: - # Source: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet - - @staticmethod - def ordered_list(elements: list) -> str: - return '\n'.join(f'{i+1}. {e}' for i, e in enumerate(elements)) - - @staticmethod - def unordered_list(elements: list) -> str: - return '\n'.join(f'* {e}' for e in elements) - - @staticmethod - def link(text: str, url: str) -> str: - return f'[{text}]({url})' - - @staticmethod - def image(alt_text: str, url: str) -> str: - return f'![{alt_text}]({url})' - - @staticmethod - def inline_code(code: str) -> str: - return f'`{code}`' - - @staticmethod - def code(code: str, language: str = '') -> str: - return f'```{language}\n{code}\n```' - - @staticmethod - def table(head, body) -> str: - data = [head, ['---'] * len(head)] + body - return '\n'.join(' | '.join(row) for row in data) - - @staticmethod - def header(text: str, level: int = 1) -> str: - if (level < 1) or (level > 6): - raise Exception('Invalid header level') - return ('#' * level + ' ' + text + '\n') - - -class HTML: - - @staticmethod - def link(text: str, url: str) -> str: - return f'{text}' - - @staticmethod - def table(head, body) -> str: - table = '' - table += '' - table += '' - for cell in head: - table += f'' - table += '' - table += '' - table += '' - for row in body: - table += '' - for cell in row: - table += f'' - table += '' - table += '' - table += '
{cell}
{cell}
' - return table - - @staticmethod - def collapsible(summary: str, details: str) -> str: - return f'
{summary}{details}
' diff --git a/src/report_tests.py b/src/report_tests.py deleted file mode 100644 index 5637240c9c..0000000000 --- a/src/report_tests.py +++ /dev/null @@ -1,90 +0,0 @@ -import os -import argparse -import glob -from pathlib import Path -from github import GitHubRepo, Markdown, HTML -from machine import machine_name - - -class ResultTable: - - def __init__(self, artifact_path: str) -> None: - self.artifact_path = artifact_path - self.head = ['', 'Test'] - self.body = [] - - def append(self, status: str, log_file: str, comment: str = '') -> None: - link = HTML.link(Path(log_file).stem, self.artifact_path + log_file) - self.body.append([status, f'{link} {comment}']) - - def clear(self) -> None: - self.body = [] - - def __str__(self) -> str: - return HTML.table(self.head, self.body) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument('--auth_token', type=str, required=False) - parser.add_argument('--build_id', type=str, required=False) - parser.add_argument('--issue_id', type=str, required=True) - args = parser.parse_args() - - repo = GitHubRepo(group='c2sm', - repo='spack-c2sm', - auth_token=args.auth_token) - table = ResultTable( - f'https://jenkins-mch.cscs.ch/job/Spack/job/spack_PR/{args.build_id}/artifact/' - ) - - # Trigger phrases that cause a test to get a special icon and comment. - # List[(trigger, icon, comment)] - triggers = [ - ('Timed out waiting for a write lock', ':lock:', - 'spack write lock problem'), - ('Timed out waiting for a read lock', ':lock:', - 'spack read lock problem'), - ('gzip: stdin: decompression OK, trailing garbage ignored', - ':wastebasket:', 'spack cached archive problem'), - ('DUE TO TIME LIMIT', ':hourglass:', 'slurm time limit'), - ('timed out after 5 seconds', ':yellow_circle:', - 'timed out after 5 seconds'), - ] - - report = Markdown.header(machine_name(), level=3) - any_tests_ran_on_machine = False - for test_type in ['unit', 'integration', 'system']: - table.clear() - all_tests_of_type_passed = True - any_tests_of_type = False - for file_name in sorted( - glob.glob(f'log/{machine_name()}/{test_type}_test/**/*.log', - recursive=True)): - any_tests_ran_on_machine = True - any_tests_of_type = True - with open(file_name, 'r') as file: - content = file.read() - second_last_line = content.split('\n')[-2] - if 'OK' in second_last_line: - table.append(':green_circle:', file_name) - else: - all_tests_of_type_passed = False - for trigger, icon, comment in triggers: - if trigger in content: - table.append(icon, file_name, comment) - break - else: - table.append(':red_circle:', file_name) - - if any_tests_of_type: - if all_tests_of_type_passed: - icon = ':green_circle:' - else: - icon = ':red_circle:' - report += HTML.collapsible(f'{icon} {test_type} test', table) - - if not any_tests_ran_on_machine: - report += f'No tests executed.' - - repo.comment(args.issue_id, report) diff --git a/src/scope.py b/src/scope.py deleted file mode 100644 index f1f66cef45..0000000000 --- a/src/scope.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -spack_c2sm_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..') - -all_machines = ['balfrin'] -all_packages = [ - name for name in os.listdir( - os.path.join(spack_c2sm_path, 'repos/c2sm/packages')) if os.path.isdir( - os.path.join(spack_c2sm_path, 'repos/c2sm/packages', name)) -] -all_packages_underscore = [p.replace('-', '_') for p in all_packages] - - -def explicit_scope(scope: str) -> list: - "Adds all packages if none is listed, and all machines if none is listed." - - scope = scope.split(' ') - - if not any(x in scope for x in all_machines): - scope.extend(all_machines) #no machine means all machines - if not any(x in scope for x in all_packages): - scope.extend(all_packages) #no package means all packages - return scope - - -def package_triggers(scope: list) -> list: - triggers = [] - for x, y in zip(all_packages, all_packages_underscore): - if x in scope: - triggers.append(x) - triggers.append(y) - - return triggers diff --git a/src/spack_commands.py b/src/spack_commands.py deleted file mode 100644 index 589288b1ac..0000000000 --- a/src/spack_commands.py +++ /dev/null @@ -1,70 +0,0 @@ -import getpass -import os -import subprocess -import time -from pathlib import Path - -spack_c2sm_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..') - -from .machine import machine_name -from .format import time_format, sanitized_filename - - -def log_with_spack(command: str, - test_category: str, - log_filename: str = None, - cwd=None, - env=None, - srun=False) -> None: - """ - Executes the given command while spack is loaded and writes the output into the log file. - If log_filename is None, command is used to create one. - """ - filename = sanitized_filename(log_filename or command) + '.log' - log_file = Path( - spack_c2sm_path) / 'log' / machine_name() / test_category / filename - - # Setup spack env - spack_env = f'. {spack_c2sm_path}/setup-env.sh' - - # Distribute work with 'srun' - if srun and getpass.getuser() == 'jenkins': - # The '-c' argument should be in sync with - # sysconfig//config.yaml config:build_jobs for max efficiency - srun = { - 'balfrin': '', - }[machine_name()] - else: - srun = '' - - # Make Directory - log_file.parent.mkdir(exist_ok=True, parents=True) - - # Log machine name and command - with log_file.open('a') as f: - f.write(machine_name()) - f.write('\n') - f.write(command) - f.write('\n\n') - - env_activate = f'spack env activate -d {env};' if env else '' - start = time.time() - # The output is streamed as directly as possible to the log_file to avoid buffering and potentially losing buffered content. - # '2>&1' redirects stderr to stdout. - ret = subprocess.run( - f'{spack_env}; {env_activate} ({srun} {command}) >> {log_file} 2>&1', - cwd=cwd, - check=False, - shell=True) - end = time.time() - - # Log time and success - with log_file.open('a') as f: - f.write('\n\n') - f.write(time_format(end - start)) - f.write('\n') - f.write('OK' if ret.returncode == 0 else 'FAILED') - f.write('\n') - - ret.check_returncode() diff --git a/sysconfigs/eiger/compilers.yaml b/sysconfigs/eiger/compilers.yaml deleted file mode 100644 index 0ff7bbe5ed..0000000000 --- a/sysconfigs/eiger/compilers.yaml +++ /dev/null @@ -1,14 +0,0 @@ -compilers: -- compiler: - spec: gcc@=7.5.0 - paths: - cc: /usr/bin/gcc - cxx: /usr/bin/g++ - f77: /usr/bin/gfortran - fc: /usr/bin/gfortran - flags: {} - operating_system: sles15 - target: x86_64 - modules: [] - environment: {} - extra_rpaths: [] diff --git a/sysconfigs/eiger/concretizer.yaml b/sysconfigs/eiger/concretizer.yaml deleted file mode 100644 index bae9dedd9b..0000000000 --- a/sysconfigs/eiger/concretizer.yaml +++ /dev/null @@ -1,2 +0,0 @@ -concretizer: - reuse: true \ No newline at end of file diff --git a/sysconfigs/eiger/config.yaml b/sysconfigs/eiger/config.yaml deleted file mode 100644 index d242e424c6..0000000000 --- a/sysconfigs/eiger/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -config: - build_stage: '$spack/../spack-build_stage' - test_stage: '$spack/../spack-test_stage' - misc_cache: '$spack/../spack-misc_cache' - build_jobs: 12 - db_lock_timeout: 120 diff --git a/sysconfigs/eiger/packages.yaml b/sysconfigs/eiger/packages.yaml deleted file mode 100644 index 6ca88b7d53..0000000000 --- a/sysconfigs/eiger/packages.yaml +++ /dev/null @@ -1,226 +0,0 @@ -packages: - autoconf: - externals: - - spec: autoconf@2.69 - prefix: /usr - automake: - externals: - - spec: automake@1.15.1 - prefix: /usr - bash: - externals: - - spec: bash@4.4.23 - prefix: /usr - binutils: - externals: - - spec: binutils@2.37.20211103 - prefix: /usr - bison: - externals: - - spec: bison@3.0.4 - prefix: /usr - bzip2: - externals: - - spec: bzip2@1.0.6 - prefix: /usr - cmake: - externals: - - spec: cmake@3.17.0 - prefix: /usr - coreutils: - externals: - - spec: coreutils@8.32 - prefix: /usr - cpio: - externals: - - spec: cpio@2.12 - prefix: /usr - curl: - externals: - - spec: curl@7.66.0+gssapi+ldap+nghttp2 - prefix: /usr - diffutils: - externals: - - spec: diffutils@3.6 - prefix: /usr - file: - externals: - - spec: file@5.32 - prefix: /usr - findutils: - externals: - - spec: findutils@4.8.0 - prefix: /usr - flex: - externals: - - spec: flex@2.6.4+lex - prefix: /usr - gawk: - externals: - - spec: gawk@4.2.1 - prefix: /usr - gcc: - externals: - - spec: gcc@7.5.0 languages=c,c++,fortran - prefix: /usr - extra_attributes: - compilers: - c: /usr/bin/gcc-7 - cxx: /usr/bin/g++-7 - fortran: /usr/bin/gfortran-7 - gettext: - externals: - - spec: gettext@0.20.2 - prefix: /usr - ghostscript: - externals: - - spec: ghostscript@9.52 - prefix: /usr - git: - externals: - - spec: git@2.35.3~tcltk - prefix: /usr - gmake: - externals: - - spec: gmake@4.2.1 - prefix: /usr - groff: - externals: - - spec: groff@1.22.3 - prefix: /usr - hwloc: - externals: - - spec: hwloc@2.6.0a1 - prefix: /usr - openjdk: - externals: - - spec: openjdk@11.0.15_10-suse-150000.3.80.1-x8664 - prefix: /usr - libfuse: - externals: - - spec: libfuse@2.9.7 - prefix: /usr - - spec: libfuse@3.6.1 - prefix: /usr - libtool: - externals: - - spec: libtool@2.4.6 - prefix: /usr - lustre: - externals: - - spec: lustre@2.15.0.2_rc2_cray_150_g8176845 - prefix: /usr - m4: - externals: - - spec: m4@1.4.18 - prefix: /usr - ncurses: - externals: - - spec: ncurses@6.1.20180317+termlib abi=6 - prefix: /usr - openssh: - externals: - - spec: openssh@8.4p1 - prefix: /usr - openssl: - externals: - - spec: openssl@1.1.1d - prefix: /usr - perl: - externals: - - spec: perl@5.26.1~cpanm+open+shared+threads - prefix: /usr - pkg-config: - externals: - - spec: pkg-config@0.29.2 - prefix: /usr - python: - externals: - - spec: python@2.7.18+bz2+crypt+ctypes~dbm~lzma+nis+pyexpat+pythoncmd+readline+sqlite3+ssl~tkinter+uuid+zlib - prefix: /usr - - spec: python@3.6.15+bz2+crypt+ctypes~dbm+lzma+nis+pyexpat~pythoncmd+readline+sqlite3+ssl~tkinter+uuid+zlib - prefix: /usr - rsync: - externals: - - spec: rsync@3.1.3 - prefix: /usr - sed: - externals: - - spec: sed@4.4 - prefix: /usr - slurm: - externals: - - spec: slurm@20.11.9 - prefix: /usr - tar: - externals: - - spec: tar@1.34 - prefix: /usr - texinfo: - externals: - - spec: texinfo@6.5 - prefix: /usr - texlive: - externals: - - spec: texlive@20170524 - prefix: /usr - which: - externals: - - spec: which@2.21 - prefix: /usr - xz: - externals: - - spec: xz@5.2.3 - prefix: /usr - zip: - externals: - - spec: zip@3.0 - prefix: /usr - all: - compiler: [gcc, intel, pgi, clang, xl, nag, fj, aocc] - providers: - awk: [gawk] - blas: [openblas, amdblis] - D: [ldc] - daal: [intel-oneapi-daal] - elf: [elfutils] - fftw-api: [fftw, amdfftw] - flame: [libflame, amdlibflame] - fuse: [libfuse] - gl: [glx, osmesa] - glu: [mesa-glu, openglu] - golang: [go, gcc] - go-or-gccgo-bootstrap: [go-bootstrap, gcc] - iconv: [libiconv] - ipp: [intel-oneapi-ipp] - java: [openjdk, jdk, ibm-java] - jpeg: [libjpeg-turbo, libjpeg] - lapack: [openblas, amdlibflame] - libglx: [mesa+glx, mesa18+glx] - libllvm: [llvm] - libosmesa: [mesa+osmesa, mesa18+osmesa] - lua-lang: [lua, lua-luajit-openresty, lua-luajit] - luajit: [lua-luajit-openresty, lua-luajit] - mariadb-client: [mariadb-c-client, mariadb] - mkl: [intel-oneapi-mkl] - mpe: [mpe2] - mpi: [openmpi, mpich] - mysql-client: [mysql, mariadb-c-client] - opencl: [pocl] - onedal: [intel-oneapi-dal] - pbs: [openpbs, torque] - pil: [py-pillow] - pkgconfig: [pkgconf, pkg-config] - rpc: [libtirpc] - scalapack: [netlib-scalapack, amdscalapack] - sycl: [hipsycl] - szip: [libaec, libszip] - tbb: [intel-tbb] - unwind: [libunwind] - uuid: [util-linux-uuid, libuuid] - xxd: [xxd-standalone, vim] - yacc: [bison, byacc] - ziglang: [zig] - permissions: - read: world - write: user diff --git a/sysconfigs/eiger/repos.yaml b/sysconfigs/eiger/repos.yaml deleted file mode 100644 index 5a33f43c97..0000000000 --- a/sysconfigs/eiger/repos.yaml +++ /dev/null @@ -1,2 +0,0 @@ -repos: - - '$spack/../repos/c2sm' \ No newline at end of file diff --git a/sysconfigs/euler/repos.yaml b/sysconfigs/euler/repos.yaml index aac469c084..f85a832f32 100644 --- a/sysconfigs/euler/repos.yaml +++ b/sysconfigs/euler/repos.yaml @@ -1,3 +1,2 @@ repos: - - '$spack/../repos/c2sm' - /cluster/software/repo/2024-05 diff --git a/sysconfigs/uenv/concretizer.yaml b/sysconfigs/uenv/concretizer.yaml deleted file mode 100644 index da9482db15..0000000000 --- a/sysconfigs/uenv/concretizer.yaml +++ /dev/null @@ -1,2 +0,0 @@ -concretizer: - reuse: true diff --git a/sysconfigs/uenv/config.yaml b/sysconfigs/uenv/config.yaml deleted file mode 100644 index d242e424c6..0000000000 --- a/sysconfigs/uenv/config.yaml +++ /dev/null @@ -1,6 +0,0 @@ -config: - build_stage: '$spack/../spack-build_stage' - test_stage: '$spack/../spack-test_stage' - misc_cache: '$spack/../spack-misc_cache' - build_jobs: 12 - db_lock_timeout: 120 diff --git a/sysconfigs/unknown/concretizer.yaml b/sysconfigs/unknown/concretizer.yaml deleted file mode 100644 index bae9dedd9b..0000000000 --- a/sysconfigs/unknown/concretizer.yaml +++ /dev/null @@ -1,2 +0,0 @@ -concretizer: - reuse: true \ No newline at end of file diff --git a/sysconfigs/unknown/config.yaml b/sysconfigs/unknown/config.yaml deleted file mode 100644 index 53ef97f15f..0000000000 --- a/sysconfigs/unknown/config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -config: - build_stage: '$spack/../spack-build_stage' - test_stage: '$spack/../spack-test_stage' - misc_cache: '$spack/../spack-misc_cache' diff --git a/sysconfigs/unknown/repos.yaml b/sysconfigs/unknown/repos.yaml deleted file mode 100644 index 5a33f43c97..0000000000 --- a/sysconfigs/unknown/repos.yaml +++ /dev/null @@ -1,2 +0,0 @@ -repos: - - '$spack/../repos/c2sm' \ No newline at end of file diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/conftest.py b/test/conftest.py deleted file mode 100644 index d5d730adcc..0000000000 --- a/test/conftest.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -import sys -import pytest - -spack_c2sm_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..') -sys.path.append(os.path.normpath(spack_c2sm_path)) -from src import machine_name, explicit_scope, package_triggers, all_machines, all_packages_underscore - - -def pytest_configure(config): - for machine in all_machines: - config.addinivalue_line( - 'markers', f'no_{machine}: mark test to not run on {machine}') - config.addinivalue_line('markers', - 'serial_only: mark test to only run serial') - for package in all_packages_underscore: - config.addinivalue_line('markers', - f'{package}: mark test to run for {package}') - - -def pytest_addoption(parser): - parser.addoption('--scope', action='store', default='') - - -def pytest_collection_modifyitems(config, items): - scope = explicit_scope(config.getoption("--scope")) - - triggers = package_triggers(scope) - - for item in items: - keywords = [k.lower() for k in item.keywords] - if machine_name() not in scope: - item.add_marker(pytest.mark.skip(reason="machine not in scope")) - if f'no_{machine_name()}' in keywords: - item.add_marker( - pytest.mark.skip( - reason="test is marked to not run on this machine")) - - if not any(k in triggers for k in keywords): - item.add_marker(pytest.mark.skip(reason="test not in scope")) diff --git a/test/integration_test.py b/test/integration_test.py index c668c359e7..3dca6c709e 100644 --- a/test/integration_test.py +++ b/test/integration_test.py @@ -1,54 +1,35 @@ import pytest -import sys -import os +from spack_commands import ALL_PACKAGES, spack_info, spack_spec -spack_c2sm_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..') -sys.path.append(os.path.normpath(spack_c2sm_path)) -from src import log_with_spack, sanitized_filename, all_packages, machine_name - - -def spack_info(spec: str, log_filename: str = None): - """ - Tests 'spack info' of the given spec and writes the output into the log file. - """ +@pytest.mark.parametrize("package", ALL_PACKAGES) +def test_info(package: str): + "Tests that the command 'spack info ' works." + spack_info(package) - if log_filename is None: - log_filename = sanitized_filename(f'{spec}-spack_info') - log_with_spack(f'spack info {spec}', 'integration_test', log_filename) +@pytest.mark.parametrize("package", ALL_PACKAGES) +def test_spec(package: str): + "Tests that the command 'spack spec ' works." + spack_spec(package) -def spack_spec(spec: str, log_filename: str = None): - """ - Tests 'spack info' of the given spec and writes the output into the log file. - """ - if log_filename is None: - log_filename = sanitized_filename(f'{spec}-spack_spec') - log_with_spack(f'spack spec {spec}', 'integration_test', log_filename) +def test_icon_serialization(): + spack_spec("icon serialization=create") -@pytest.mark.parametrize('package', all_packages) -def test_spack_info(package: str): - spack_info(package) +def test_icon_fcgroup(): + spack_spec("icon fcgroup=DACE.externals/dace_icon.-O1") -@pytest.mark.parametrize('package', all_packages) -def test_spack_spec(package: str): - spack_spec(package) +def test_icon_extra_config_args(): + spack_spec( + "icon extra-config-args=--disable-new_feature,--enable-old_config_arg") -@pytest.mark.icon -@pytest.mark.parametrize('variant', [ - 'serialization=create', 'fcgroup=DACE.externals/dace_icon.-O1', - 'extra-config-args=--disable-new_feature,--enable-old_config_arg' -]) -def test_icon_spec_with_variant(variant: str): - spack_spec(f'icon {variant}') +def test_int2lm_parallel(): + spack_spec("int2lm +parallel") -@pytest.mark.int2lm -@pytest.mark.parametrize('variant', ['+parallel', '~parallel']) -def test_int2lm_spec_with_variant(variant: str): - spack_spec(f'int2lm {variant}') +def test_int2lm_no_parallel(): + spack_spec("int2lm ~parallel") diff --git a/test/spack_commands.py b/test/spack_commands.py new file mode 100644 index 0000000000..8259ebacc7 --- /dev/null +++ b/test/spack_commands.py @@ -0,0 +1,94 @@ +import os +import subprocess +import time +from pathlib import Path + +REPO_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..") +PACKAGES_DIR = os.path.join(REPO_DIR, "repos", "c2sm", "packages") +ALL_PACKAGES = [ + name for name in os.listdir(PACKAGES_DIR) + if os.path.isdir(os.path.join(PACKAGES_DIR, name)) +] + + +def time_format(seconds) -> str: + "Returns a string formatted as 'XXh YYm ZZ.ZZs'." + + m, s = divmod(seconds, 60) + h, m = divmod(m, 60) + + parts = [] + if h: + parts.append(f"{h:.0f}h") + if m: + parts.append(f"{m:.0f}m") + if s: + parts.append(f"{s:.2f}s") + return " ".join(parts) + + +def log_file(command: str) -> Path: + # Filter out flags + # and join by underscore, because spaces cause problems in bash. + command = "_".join([x for x in command.split() if not x.startswith("-")]) + + # Remove % because they cause problems in web browsers + command = command.replace("%", "") + + # Remove . because they cause problems in shell commands + command = command.replace("%", "") + + return Path(REPO_DIR) / "log" / (command + ".log") + + +def run_with_spack(command: str, log: Path) -> None: + log.parent.mkdir(exist_ok=True, parents=True) + with log.open("a") as f: + f.write(f"{command}\n\n") + + # setup-env.sh may define SPACK_UENV_PATH. + if "SPACK_UENV_PATH" in os.environ: + uenv = os.environ["SPACK_UENV_PATH"] + else: + uenv = "" + + start = time.time() + # Direct stream to avoid buffering. + # 'env -i' clears the environment. + # 'bash -l' makes it a login shell. + # 'bash -c' reads commands from string. + # '2>&1' redirects stderr to stdout. + ret = subprocess.run( + f'env -i bash -l -c "(. {REPO_DIR}/setup-env.sh {uenv}; {command}) >> {log} 2>&1"', + check=False, + shell=True, + ) + end = time.time() + + # Log time and success + duration = time_format(end - start) + success = "OK" if ret.returncode == 0 else "FAILED" + with log.open("a") as f: + f.write(f"\n\n{duration}\n{success}\n") + + ret.check_returncode() + + +def spack_info(spec: str): + log = log_file(f"info {spec}") + run_with_spack(f"spack info {spec}", log) + + +def spack_spec(spec: str): + log = log_file(f"spec {spec}") + run_with_spack(f"spack spec {spec}", log) + + +def spack_install(spec: str, test_root: bool = True): + log = log_file(f"install {spec}") + + # A spec at the top of a log helps debugging. + run_with_spack(f"spack spec {spec}", log) + + test_arg = "--test=root" if test_root else "" + run_with_spack(f"spack install --verbose {test_arg} {spec}", log) diff --git a/test/system_test.py b/test/system_test.py index 8866544b72..4f9e6afee8 100644 --- a/test/system_test.py +++ b/test/system_test.py @@ -1,126 +1,58 @@ import pytest -import subprocess -import sys -import os -import uuid -import shutil -import inspect +from spack_commands import spack_install -spack_c2sm_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..') -sys.path.append(os.path.normpath(spack_c2sm_path)) -from src import machine_name, log_with_spack, sanitized_filename - - -@pytest.fixture(scope='function') -def devirt_env(): - # pytest is run from a virtual environment that breaks the - # Python environment setup by Spack. Additionally "deactivate" - # is not available here, therefore we manually unset - # VIRTUAL_ENV and PATH - - # Remove 'VIRTUAL_ENV/bin' - try: - virtual_env_bin = os.path.join(os.environ['VIRTUAL_ENV'], 'bin') - os.environ.pop('VIRTUAL_ENV') - os.environ['PATH'] = os.environ['PATH'].replace(virtual_env_bin, '') - - # happens if test are run in serial-mode because cannot unset var twice - except KeyError: - pass - - -def compose_logfilename(spec): - func_name = inspect.currentframe().f_back.f_back.f_code.co_name.replace( - 'test_', '') - return sanitized_filename(func_name + '-' + spec) - - -def spack_install(spec: str, test_root: bool = True): - """ - Tests 'spack install' of the given spec and writes the output into the log file. - """ - - log_filename = compose_logfilename(spec) - - # A spec at the top of a log helps debugging. - log_with_spack(f'spack spec {spec}', - 'system_test', - log_filename, - srun=False) - - test_arg = "--test=root" if test_root else "" - log_with_spack(f'spack install -n -v {test_arg} {spec}', - 'system_test', - log_filename, - srun=not spec.startswith('icon ')) - - -@pytest.mark.libfyaml def test_install_libfyaml_default(): spack_install('libfyaml', test_root=False) -@pytest.mark.libtorch def test_install_libtorch_default(): spack_install('libtorch', test_root=False) -@pytest.mark.cosmo_eccodes_definitions @pytest.mark.parametrize("version", ['2.25.0.1', '2.19.0.7']) def test_install_cosmo_eccodes_definitions_version(version): spack_install(f'cosmo-eccodes-definitions @{version}', test_root=False) -@pytest.mark.cosmo def test_install_cosmo_6_0(): - spack_install('cosmo@6.0%nvhpc', test_root=False) + spack_install('cosmo @6.0 %nvhpc', test_root=False) -@pytest.mark.eccodes def test_install_eccodes_2_19_0(): spack_install('eccodes @2.19.0', test_root=False) -@pytest.mark.fdb_fortran def test_install_fdb_fortran(): spack_install('fdb-fortran') -@pytest.mark.flexpart_ifs @pytest.mark.parametrize("version", ['10.4.4', 'fdb']) def test_install_flexpart_ifs_version(version): spack_install(f'flexpart-ifs @{version}', test_root=False) -@pytest.mark.flexpart_cosmo def test_install_flexpart_cosmo(): spack_install('flexpart-cosmo @V8C4.0') -@pytest.mark.fdb def test_install_fdb_5_11_17_gcc(): spack_install('fdb @5.11.17 %gcc') -@pytest.mark.fdb def test_install_fdb_5_11_17_nvhpc(): # tests fail because compiler emitted warnings. spack_install('fdb @5.11.17 %nvhpc', test_root=False) -@pytest.mark.icon -def test_install_icon_24_1_gcc(): +def test_install_icon_2024_1_gcc(): spack_install('icon @2024.1-1 %gcc') -@pytest.mark.icon -def test_install_2024_1_nvhpc(): +def test_install_icon_2024_1_nvhpc(): spack_install('icon @2024.1-1 %nvhpc') -@pytest.mark.icon def test_install_conditional_dependencies(): # +coupling triggers libfyaml, libxml2, netcdf-c # serialization=create triggers serialbox @@ -134,123 +66,85 @@ def test_install_conditional_dependencies(): ) -@pytest.mark.icontools def test_install_icontools(): spack_install('icontools @2.5.2') -@pytest.mark.no_balfrin # int2lm depends on 'libgrib1 @22-01-2020', which fails. -@pytest.mark.int2lm -def test_install_int2ml_version_3_00_gcc(): - spack_install('int2lm @int2lm-3.00 %gcc', test_root=False) - - -@pytest.mark.int2lm -def test_install_int2lm_version_3_00_nvhpc_fixed_definitions(): - spack_install( - 'int2lm @int2lm-3.00 %nvhpc ^cosmo-eccodes-definitions@2.19.0.7%nvhpc', - test_root='balfrin' not in machine_name()) +def test_install_int2lm_3_00_nvhpc(): + spack_install('int2lm @int2lm-3.00 %nvhpc', test_root=False) -@pytest.mark.libgrib1 def test_install_libgrib1_22_01_2020_nvhpc(): - spack_install('libgrib1 @22-01-2020%nvhpc') + spack_install('libgrib1 @22-01-2020 %nvhpc') -@pytest.mark.makedepf90 def test_install_makedepf90(): spack_install('makedepf90 @3.0.1', test_root=False) -@pytest.mark.oasis def test_install_oasis_version_4_0_nvhpc(): spack_install('oasis @4.0 %nvhpc') -@pytest.mark.pytorch_fortran -def test_install_pytorch_fortran_version_0_4(devirt_env): +def test_install_pytorch_fortran_version_0_4(): spack_install( 'pytorch-fortran@0.4%nvhpc ^pytorch-fortran-proxy@0.4%gcc ^python@3.10 ^gmake%gcc ^cmake%gcc', test_root=False) -@pytest.mark.pytorch_fortran_proxy -def test_install_pytorch_fortran_proxy_version_0_4(devirt_env): +def test_install_pytorch_fortran_proxy_version_0_4(): spack_install('pytorch-fortran-proxy@0.4%gcc ^python@3.10', test_root=False) -@pytest.mark.py_cytoolz -def test_py_cytoolz_install_default(devirt_env): +def test_install_py_cytoolz_install_default(): spack_install('py-cytoolz') -@pytest.mark.py_devtools -def test_py_devtools_install_default(devirt_env): +def test_install_py_devtools_install_default(): spack_install('py-devtools') -@pytest.mark.py_factory_boy -def test_py_factory_boy_install_default(devirt_env): +def test_install_py_factory_boy_install_default(): spack_install('py-factory-boy') -@pytest.mark.py_frozendict -def test_py_frozendict_install_default(devirt_env): +def test_install_py_frozendict_install_default(): spack_install('py-frozendict') -@pytest.mark.py_gridtools_cpp -def test_py_gridtools_cpp_install_default(devirt_env): +def test_install_py_gridtools_cpp_install_default(): spack_install('py-gridtools-cpp') -@pytest.mark.py_gt4py -@pytest.mark.parametrize("version", ['1.0.3.3', '1.0.3.7', '1.0.3.9']) -def test_install_py_gt4py_for_version(version, devirt_env): +@pytest.mark.parametrize("version", ['1.0.3.7', '1.0.3.9']) +def test_install_py_gt4py_for_version(version): spack_install(f'py-gt4py @{version}') -@pytest.mark.py_icon4py -def test_install_py_icon4py_version_0_0_10(devirt_env): - spack_install('py-icon4py @ 0.0.10 %gcc ^py-gt4py@1.0.3.3') - - -@pytest.mark.py_icon4py -def test_install_py_icon4py_version_0_0_11(devirt_env): - spack_install('py-icon4py @ 0.0.11 %gcc ^py-gt4py@1.0.3.7') - - -@pytest.mark.py_icon4py -def test_install_py_icon4py_version_0_0_13(devirt_env): - spack_install('py-icon4py @ 0.0.13 %gcc ^py-gt4py@1.0.3.9') +def test_install_py_icon4py(): + spack_install('py-icon4py') -@pytest.mark.py_hatchling -def test_install_py_hatchling_default(devirt_env): +def test_install_py_hatchling_default(): spack_install('py-hatchling') -@pytest.mark.py_inflection -def test_install_py_inflection_default(devirt_env): +def test_install_py_inflection_default(): spack_install('py-inflection') -@pytest.mark.py_pytest_factoryboy -def test_install_py_pytest_factoryboy_default(devirt_env): +def test_install_py_pytest_factoryboy_default(): spack_install('py-pytest-factoryboy') -@pytest.mark.py_tabulate -def test_install_py_tabulate_default(devirt_env): +def test_install_py_tabulate_default(): spack_install('py-tabulate') -@pytest.mark.py_typing_extensions -def test_install_py_typing_extensions_default(devirt_env): +def test_install_py_typing_extensions_default(): spack_install('py-typing-extensions') -@pytest.mark.yaxt def test_install_yaxt_default(): spack_install('yaxt') diff --git a/test/unit_test.py b/test/unit_test.py index 1569fb7cf6..e024555fe9 100644 --- a/test/unit_test.py +++ b/test/unit_test.py @@ -1,135 +1,10 @@ import unittest -import sys -import os - -spack_c2sm_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), - '..') -sys.path.append(os.path.normpath(spack_c2sm_path)) -from src import machine_name, Markdown, HTML, time_format, sanitized_filename, all_machines, all_packages, explicit_scope, package_triggers - - -class MachineDetection(unittest.TestCase): - - @unittest.skipUnless(__name__ == '__main__' and len(sys.argv) > 1, - 'needs machine_name_from_arg') - def test_machine_name(self): - self.assertEqual(machine_name(), machine_name_from_arg) - - -class MarkDownTest(unittest.TestCase): - - def test_ordered_list(self): - self.assertEqual(Markdown.ordered_list(['a', 'b']), '1. a\n2. b') - - def test_unordered_list(self): - self.assertEqual(Markdown.unordered_list(['a', 'b']), '* a\n* b') - - def test_link(self): - self.assertEqual(Markdown.link('text', 'url'), '[text](url)') - - def test_image(self): - self.assertEqual(Markdown.image('text', 'url'), '![text](url)') - - def test_inline_code(self): - self.assertEqual(Markdown.inline_code('code'), '`code`') - - def test_code(self): - self.assertEqual(Markdown.code('code'), '```\ncode\n```') - self.assertEqual(Markdown.code('code', 'language'), - '```language\ncode\n```') - - def test_table(self): - self.assertEqual( - Markdown.table(['title1', 'title2'], - [['data1', 'data2'], ['data3', 'data4']]), - 'title1 | title2\n--- | ---\ndata1 | data2\ndata3 | data4') - - -class HTMLTest(unittest.TestCase): - - def test_link(self): - self.assertEqual(HTML.link('text', 'url'), 'text') - - def test_table(self): - self.assertEqual( - HTML.table(['title1', 'title2'], - [['data1', 'data2'], ['data3', 'data4']]), - '
title1title2
data1data2
data3data4
' - ) - - def test_collapsible(self): - self.assertEqual( - HTML.collapsible('summary', 'details'), - '
summarydetails
') +from spack_commands import time_format class TimeFormatTest(unittest.TestCase): - def test_seconds_only(self): - self.assertEqual(time_format(1), '1.00s') - - def test_minutes_only(self): - self.assertEqual(time_format(60), '1m') - - def test_hours_only(self): - self.assertEqual(time_format(3600), '1h') - - def test_full_combo(self): - self.assertEqual(time_format(3600 + 23 * 60 + 45.67), '1h 23m 45.67s') - - -class FilenameSanitizerTest(unittest.TestCase): - def test_example(self): - example = 'spack installcosmo --until build --dont-restage --test=root --show-log-on-error -n -v cosmo @6.0 %nvhpc cosmo_target=cpu ~cppdycore ^mpich %nvhpc fflags="-O3"' - - sanitized = sanitized_filename(example) - - self.assertFalse(' ' in sanitized) - self.assertFalse('%' in sanitized) - self.assertFalse('"' in sanitized) - self.assertEqual( - sanitized, - 'spack_installcosmo_cosmo_@6.0_nvhpc_cosmo_target=cpu_~cppdycore_^mpich_nvhpc_fflags=-O3' - ) - - -class ScopeTest(unittest.TestCase): - - def test_all_packages(self): - self.assertTrue('icon' in all_packages) - - def test_explicit_scope_1_machine_1_package(self): - scope = explicit_scope('balfrin cosmo') - self.assertEqual(sorted(scope), sorted(['balfrin', 'cosmo'])) - - def test_explicit_scope_2_machines_2_packages(self): - scope = explicit_scope('balfrin cosmo icon') - self.assertEqual(sorted(scope), sorted(['balfrin', 'cosmo', 'icon'])) - - def test_explicit_scope_0_machines_1_package(self): - scope = explicit_scope('cosmo') - self.assertEqual(sorted(scope), sorted(all_machines + ['cosmo'])) - - def test_explicit_scope_0_machines_0_packages(self): - scope = explicit_scope('launch jenkins') - self.assertEqual( - sorted(scope), - sorted(['launch', 'jenkins'] + all_machines + all_packages)) - - def test_explicit_scope_allows_unknowns(self): - scope = explicit_scope('launch jenkins balfrin cosmo') - self.assertEqual(sorted(scope), - sorted(['launch', 'jenkins', 'balfrin', 'cosmo'])) - - def test_package_triggers(self): - triggers = package_triggers(['py-gt4py']) - self.assertTrue('py-gt4py' in triggers) # package name included - self.assertTrue('py_gt4py' in triggers) # marker name included - - -if __name__ == '__main__': - if len(sys.argv) > 1: - machine_name_from_arg = sys.argv[-1] - sys.argv = sys.argv[:-1] # unittest needs this - unittest.main(verbosity=2) + self.assertEqual(time_format(0.123), "0.12s") + self.assertEqual(time_format(123.456), "2m 3.46s") + self.assertEqual(time_format(123456.789), "34h 17m 36.79s") diff --git a/tools/summarize_logs.py b/tools/summarize_logs.py new file mode 100644 index 0000000000..96d2035ab4 --- /dev/null +++ b/tools/summarize_logs.py @@ -0,0 +1,16 @@ +from pathlib import Path + +with Path('log/artifacts_summary.html').open('w') as f: + f.write('\n') + f.write('\n') + for file in sorted(Path("log").iterdir()): + if file.suffix != '.log': + continue + second_last_line = file.read_text().split('\n')[-2] + if 'OK' in second_last_line: + circle = '🟢' + else: + circle = '🔴' + f.write(f'{circle} {file.stem}
\n') + f.write('\n') + f.write('\n') diff --git a/sysconfigs/euler/config.yaml b/user-config/config.yaml similarity index 100% rename from sysconfigs/euler/config.yaml rename to user-config/config.yaml diff --git a/sysconfigs/uenv/repos.yaml b/user-config/repos.yaml similarity index 100% rename from sysconfigs/uenv/repos.yaml rename to user-config/repos.yaml