File: testsuite.py

package info (click to toggle)
python-ase 3.24.0-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 15,448 kB
  • sloc: python: 144,945; xml: 2,728; makefile: 113; javascript: 47
file content (218 lines) | stat: -rw-r--r-- 6,960 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import argparse
import os
import sys
import warnings
from multiprocessing import cpu_count
from pathlib import Path
from subprocess import Popen

from ase.calculators.names import names as calc_names
from ase.cli.main import CLIError, main

testdir = Path(__file__).parent


def all_test_modules():
    for abspath in testdir.rglob('test_*.py'):
        path = abspath.relative_to(testdir)
        yield path


def all_test_modules_and_groups():
    groups = set()
    for testpath in all_test_modules():
        group = testpath.parent
        if group not in groups:
            yield group
            groups.add(group)
        yield testpath


def test(calculators=tuple(), jobs=0, verbose=False,
         stream='ignored', strict='ignored'):
    """Run the tests programmatically.

    This is here for compatibility and perhaps convenience."""

    if stream != 'ignored':
        warnings.warn('Ignoring old "stream" keyword', FutureWarning)
    if strict != 'ignored':
        warnings.warn('Ignoring old "strict" keyword', FutureWarning)

    args = ['test']
    if verbose:
        args += ['--verbose']
    if calculators:
        args += ['--calculators={}'.format(','.join(calculators))]
    if jobs:
        args += f'--jobs={jobs}'

    main(args=args)


def have_module(module):
    import importlib.util
    return importlib.util.find_spec(module) is not None


MULTIPROCESSING_MAX_WORKERS = 32
MULTIPROCESSING_DISABLED = 0
MULTIPROCESSING_AUTO = -1


def choose_how_many_workers(jobs):

    if jobs == MULTIPROCESSING_AUTO:
        if have_module('xdist'):
            jobs = min(cpu_count(), MULTIPROCESSING_MAX_WORKERS)
        else:
            jobs = MULTIPROCESSING_DISABLED
    return jobs


help_calculators = """\
Calculator testing is currently work in progress.  This
notice applies to the calculators abinit, cp2k, dftb, espresso,
lammpsrun, nwchem, octopus, and siesta.  The goal of this work is to
provide a configuration in which tests are more reproducible.

Most calculators require datafiles such as pseudopotentials
which are available at

  https://gitlab.com/ase/ase-datafiles

Please install this package using e.g.:

  $ pip install --user --upgrade git+https://gitlab.com/ase/ase-datafiles.git

The ASE test suite needs to know the exact binaries for each
of the aforementioned programs.  Currently these must be specified
in ~/.config/ase/ase.conf or another file if specified by
the ASE_CONFIG environment variable.  Example configuration file:

[executables]
abinit = abinit
cp2k = cp2k_shell
dftb = dftb+
espresso = pw.x
lammpsrun = lmp
nwchem = /usr/bin/nwchem
octopus = octopus
siesta = /usr/local/bin/siesta
"""


class CLICommand:
    """Run ASE's test-suite.

    Requires the pytest package.  pytest-xdist is recommended
    in addition as the tests will then run in parallel.
    """

    @staticmethod
    def add_arguments(parser):
        parser.add_argument(
            '-c', '--calculators', default='',
            help='comma-separated list of calculators to test; '
            'see --help-calculators')
        parser.add_argument('--help-calculators', action='store_true',
                            help='show extended help about calculator tests '
                            'and exit')
        parser.add_argument('--list', action='store_true',
                            help='print all tests and exit')
        parser.add_argument('--list-calculators', action='store_true',
                            help='print all calculator names and exit')
        parser.add_argument(
            '-j', '--jobs', type=int, metavar='N',
            default=MULTIPROCESSING_AUTO,
            help='number of worker processes.  If pytest-xdist is available,'
            ' defaults to all available processors up to a maximum of {}.  '
            '0 disables multiprocessing'
            .format(MULTIPROCESSING_MAX_WORKERS))
        parser.add_argument('-v', '--verbose', action='store_true',
                            help='write test outputs to stdout.  '
                            'Mostly useful when inspecting a single test')
        parser.add_argument('--strict', action='store_true',
                            help='convert warnings to errors.  '
                            'This option currently has no effect')
        parser.add_argument('--fast', action='store_true',
                            help='skip slow tests')
        parser.add_argument('--coverage', action='store_true',
                            help='measure code coverage.  '
                            'Requires pytest-cov')
        parser.add_argument('--nogui', action='store_true',
                            help='do not run graphical tests')
        parser.add_argument('tests', nargs='*',
                            help='specify particular test files '
                            'or directories')
        parser.add_argument('--pytest', nargs=argparse.REMAINDER,
                            help='forward all remaining arguments to pytest.  '
                            'See pytest --help')

    @staticmethod
    def run(args):

        if args.help_calculators:
            print(help_calculators)
            sys.exit(0)

        if args.list_calculators:
            for name in calc_names:
                print(name)
            sys.exit(0)

        if args.nogui:
            os.environ.pop('DISPLAY')

        pytest_args = []

        def add_args(*args):
            pytest_args.extend(args)

        if args.list:
            add_args('--collect-only')

        jobs = choose_how_many_workers(args.jobs)
        if jobs:
            add_args(f'--numprocesses={jobs}')

        if args.fast:
            add_args('-m', 'not slow')

        if args.coverage:
            add_args('--cov=ase',
                     '--cov-config=.coveragerc',
                     # '--cov-report=term',
                     '--cov-report=html')

        if args.tests:
            for testname in args.tests:
                add_args(testname)

        if args.calculators:
            add_args(f'--calculators={args.calculators}')

        if args.verbose:
            add_args('--capture=no')

        if args.pytest:
            add_args(*args.pytest)

        print('About to run pytest with these parameters:')
        for line in pytest_args:
            print('    ' + line)

        if not have_module('pytest'):
            raise CLIError('Cannot import pytest; please install pytest '
                           'to run tests')

        # We run pytest through Popen rather than pytest.main().
        #
        # This is because some ASE modules were already imported and
        # would interfere with code coverage measurement.
        # (Flush so we don't get our stream mixed with the pytest output)
        sys.stdout.flush()
        proc = Popen([sys.executable, '-m', 'pytest'] + pytest_args,
                     cwd=str(testdir))
        exitcode = proc.wait()
        sys.exit(exitcode)