Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

### 44.0.1 [#1349](https://github.com/openfisca/openfisca-core/pull/1349)

#### Technical changes

- Add parallel execution of tests to `openfisca test` command line.

# 44.0.0 [#1346](https://github.com/openfisca/openfisca-core/pull/1346)

#### Technical changes
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ To run the entire test suite:
make test
```

If you have many tests, you could run them in parallel :
```sh
make test-core openfisca_args="--in-parallel"
```

You could add an option `--num-workers=4` to limit to 4 threads. Default is your CPU Core number minus 1.

Be aware that this add overhead so use it only for huge test suite.

To run all the tests defined on a test file:

```sh
Expand Down
18 changes: 18 additions & 0 deletions openfisca_core/scripts/openfisca_command.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import argparse
import sys
import warnings
from importlib.metadata import version

from openfisca_core.scripts import add_tax_benefit_system_arguments

Expand All @@ -11,6 +12,11 @@

def get_parser():
parser = argparse.ArgumentParser()
parser.add_argument(
"--version",
action="version",
version=f"OpenFisca-Core {version('OpenFisca-Core')}",
)

subparsers = parser.add_subparsers(help="Available commands", dest="command")
subparsers.required = (
Expand Down Expand Up @@ -134,6 +140,18 @@ def build_test_parser(parser):
default=None,
help="variables to ignore. If specified, do not test the given variables.",
)
parser.add_argument(
"--in-parallel",
action="store_true",
default=False,
help="run tests in parallel",
)
parser.add_argument(
"--num-workers",
type=int,
default=0,
help="number of parallel workers (default: CPU count - 1)",
)

return parser

Expand Down
15 changes: 15 additions & 0 deletions openfisca_core/scripts/run_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ def main(parser) -> None:
)

options = {
"country_package": args.country_package,
"extensions": args.extensions,
"reforms": args.reforms,
"pdb": args.pdb,
"performance_graph": args.performance_graph,
"performance_tables": args.performance_tables,
Expand All @@ -32,4 +35,16 @@ def main(parser) -> None:
}

paths = [os.path.abspath(path) for path in args.path]

# Parallel mode
if args.in_parallel:
from openfisca_core.tools.test_runner import run_tests_in_parallel

return sys.exit(
run_tests_in_parallel(
tax_benefit_system, paths, options, args.num_workers, args.verbose
)
)

# Default serial mode
sys.exit(run_tests(tax_benefit_system, paths, options))
55 changes: 55 additions & 0 deletions openfisca_core/tools/parallel_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Pytest plugin for running OpenFisca tests in parallel worker processes.

This plugin is loaded by pytest workers spawned by run_tests_in_parallel().
It configures each worker with its own tax-benefit system and test options.

The parallel test architecture works as follows:
1. Main process: run_tests_in_parallel() splits test files into batches
2. Main process: spawns multiple pytest workers via subprocess
3. Each worker: loads this plugin to configure its test environment
4. Each worker: runs its batch of tests independently
5. Main process: aggregates results and reports failures

Configuration is passed via environment variables:
- OPENFISCA_COUNTRY_PACKAGE: The country package to load
- OPENFISCA_EXTENSIONS: JSON array of extensions to load
- OPENFISCA_REFORMS: JSON array of reforms to apply
- OPENFISCA_OPTIONS: JSON object with test options (verbose, name_filter, etc.)
"""

import json
import os

from openfisca_core.scripts import build_tax_benefit_system
from openfisca_core.tools.test_runner import OpenFiscaPlugin


def pytest_configure(config):
"""Pytest hook called when each worker process initializes.

This function is automatically invoked by pytest when it loads this plugin.
It sets up the OpenFisca test environment for the worker by:
1. Reading configuration from environment variables
2. Building the tax-benefit system with specified country, extensions, and reforms
3. Registering the OpenFisca plugin with pytest

Args:
config: Pytest config object provided by pytest framework
"""
# Extract country package from environment (optional)
country = os.environ.get("OPENFISCA_COUNTRY_PACKAGE") or None

# Parse JSON arrays for extensions and reforms
extensions = json.loads(os.environ.get("OPENFISCA_EXTENSIONS", "[]"))
reforms = json.loads(os.environ.get("OPENFISCA_REFORMS", "[]"))

# Build the tax-benefit system with the specified configuration
tbs = build_tax_benefit_system(country, extensions, reforms)

# Parse test options (verbose, name_filter, etc.)
options_json = os.environ.get("OPENFISCA_OPTIONS", "{}")
options = json.loads(options_json)

# Create and register the OpenFisca plugin for this worker
plugin = OpenFiscaPlugin(tbs, options)
config.pluginmanager.register(plugin, name="openfisca_parallel_plugin")
Loading
Loading