Unverified Commit 896a0888 authored by Axel Kohlmeyer's avatar Axel Kohlmeyer
Browse files

add a custom python script to check the completeness of tests inputs

parent 75dcd9ae
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -5,6 +5,23 @@ if(NOT YAML_FOUND)
  return()
endif()

if(CMAKE_VERSION VERSION_LESS 3.12)
  # adjust so we find Python 3 versions before Python 2 on old systems with old CMake
  set(Python_ADDITIONAL_VERSIONS 3.8 3.7 3.6 3.5)
  find_package(PythonInterp) # Deprecated since version 3.12
  if(PYTHONINTERP_FOUND)
    set(Python_EXECUTABLE ${PYTHON_EXECUTABLE})
  endif()
else()
  find_package(Python COMPONENTS Interpreter)
endif()
if (Python_EXECUTABLE)
  add_custom_target(check-tests
    ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check_tests.py
    -s ${LAMMPS_SOURCE_DIR} -t ${CMAKE_CURRENT_SOURCE_DIR}/tests
    COMMENT "Check completeness of force style tests")
endif()

set(TEST_INPUT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}/tests)
add_library(style_tests STATIC yaml_writer.cpp error_stats.cpp test_config_reader.cpp test_main.cpp)
target_compile_definitions(style_tests PRIVATE -DTEST_INPUT_FOLDER=${TEST_INPUT_FOLDER})
+218 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3

from __future__ import print_function
from glob import glob
from argparse import ArgumentParser
import os, re, sys

parser = ArgumentParser(prog='check_tests.py',
                        description="Check force tests for completeness")

parser.add_argument("-v", "--verbose",
                    action='store_const',
                    const=True, default=False,
                    help="Enable verbose output")

parser.add_argument("-t", "--tests",
                    help="Path to LAMMPS test YAML format input files")
parser.add_argument("-s", "--src",
                    help="Path to LAMMPS sources")

args = parser.parse_args()
verbose = args.verbose
src = args.src
tests = args.tests

if not src:
  src = os.path.join('.','..','..','src')

if not tests:
  tests = os.path.join(src,'..','unittest','force-styles','tests')

try:
  src = os.path.abspath(os.path.expanduser(src))
  tests = os.path.abspath(os.path.expanduser(tests))
except:
  parser.print_help()
  sys.exit(1)

if not os.path.isdir(src):
    sys.exit("LAMMPS source path %s does not exist" % src)

if not os.path.isdir(tests):
    sys.exit("LAMMPS test inputs path %s does not exist" % tests)

headers = glob(os.path.join(src, '*', '*.h'))
headers += glob(os.path.join(src, '*.h'))

angle = {}
bond = {}
compute = {}
dihedral = {}
dump = {}
fix = {}
improper = {}
kspace = {}
pair = {}
total = 0

upper = re.compile("[A-Z]+")
gpu = re.compile("(.+)/gpu$")
intel = re.compile("(.+)/intel$")
kokkos = re.compile("(.+)/kk$")
kokkos_skip = re.compile("(.+)/kk/(host|device)$")
omp = re.compile("(.+)/omp$")
opt = re.compile("(.+)/opt$")
removed = re.compile("(.*)Deprecated$")

def register_style(list,style,info):
    if style in list.keys():
        list[style]['gpu'] += info['gpu']
        list[style]['intel'] += info['intel']
        list[style]['kokkos'] += info['kokkos']
        list[style]['omp'] += info['omp']
        list[style]['opt'] += info['opt']
        list[style]['removed'] += info['removed']
    else:
        list[style] = info

def add_suffix(list,style):
    suffix = ""
    if list[style]['gpu']:
        suffix += 'g'
    if list[style]['intel']:
        suffix += 'i'
    if list[style]['kokkos']:
        suffix += 'k'
    if list[style]['omp']:
        suffix += 'o'
    if list[style]['opt']:
        suffix += 't'
    if suffix:
        return style + ' (' + suffix + ')'
    else:
        return style

def check_style(file,dir,pattern,list,name,suffix=False,skip=()):
    f = os.path.join(dir, file)
    fp = open(f)
    text = fp.read()
    fp.close()
    matches = re.findall(pattern,text,re.MULTILINE)
    counter = 0
    for c in list.keys():
        # known undocumented aliases we need to skip
        if c in skip: continue
        s = c
        if suffix: s = add_suffix(list,c)
        if not s in matches:
            if not list[c]['removed']:
                print("%s style entry %s" % (name,s),
                      "is missing or incomplete in %s" % file)
                counter += 1
    return counter

print("Parsing style names from C++ tree in: ",src)

for h in headers:
    if verbose: print("Checking ", h)
    fp = open(h)
    text = fp.read()
    fp.close()
    matches = re.findall("(.+)Style\((.+),(.+)\)",text,re.MULTILINE)
    for m in matches:

        # skip over internal styles w/o explicit documentation
        style = m[1]
        total += 1
        if upper.match(style):
            continue

        # detect, process, and flag suffix styles:
        info = { 'kokkos':  0, 'gpu':     0, 'intel':   0, \
                 'omp':     0, 'opt':     0, 'removed': 0 }
        suffix = kokkos_skip.match(style)
        if suffix:
            continue
        suffix = gpu.match(style)
        if suffix:
            style = suffix.groups()[0]
            info['gpu'] = 1
        suffix = intel.match(style)
        if suffix:
            style = suffix.groups()[0]
            info['intel'] = 1
        suffix = kokkos.match(style)
        if suffix:
            style = suffix.groups()[0]
            info['kokkos'] = 1
        suffix = omp.match(style)
        if suffix:
            style = suffix.groups()[0]
            info['omp'] = 1
        suffix = opt.match(style)
        if suffix:
            style = suffix.groups()[0]
            info['opt'] = 1
        deprecated = removed.match(m[2])
        if deprecated:
            info['removed'] = 1

        # register style and suffix flags
        if m[0] == 'Angle':
            register_style(angle,style,info)
        elif m[0] == 'Bond':
            register_style(bond,style,info)
        elif m[0] == 'Dihedral':
            register_style(dihedral,style,info)
        elif m[0] == 'Improper':
            register_style(improper,style,info)
        elif m[0] == 'KSpace':
            register_style(kspace,style,info)
        elif m[0] == 'Pair':
            register_style(pair,style,info)


counter = 0

def check_tests(name,list,yaml,search,skip=()):
    num = 0
    yaml_files = glob(os.path.join(tests, yaml))
    styles = []
    missing = []
    for y in yaml_files:
        if verbose: print("Checking: ",y)
        fp = open(y)
        text = fp.read()
        fp.close()
        matches = re.findall(search,text,re.MULTILINE)
        for m in matches:
            styles.append(m)
    for s in list.keys():
        # known undocumented aliases we need to skip
        if s in skip: continue
        if not s in styles:
            if not list[s]['removed']:
                if verbose: print("No test for %s style %s" % (name,s))
                num += 1
                missing.append(s)
    total = len(list)
    print("\nTests for %s styles: %d of %d" % (name,total - num, total))
    print("No tests for: ", missing)
    return num

counter += check_tests('pair',pair,'*-pair-*.yaml',
                       '.*pair_style:\s*(\S+).*',skip=('meam','lj/sf'))
counter += check_tests('bond',bond,'bond-*.yaml',
                       '.*bond_style:\s*(\S+).*')
counter += check_tests('angle',angle,'angle-*.yaml',
                       '.*angle_style:\s*(\S+).*')
counter += check_tests('dihedral',dihedral,'dihedral-*.yaml',
                       '.*dihedral_style:\s*(\S+).*')
counter += check_tests('improper',improper,'improper-*.yaml',
                       '.*improper_style:\s*(\S+).*')
counter += check_tests('kspace',kspace,'kspace-*.yaml',
                       '.*kspace_style\s*(\S+).*')

total = len(pair)+len(bond)+len(angle)+len(dihedral)+len(improper)+len(kspace)
print("\nTotal tests missing: %d of %d" % (total - counter, total))