Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for profiles #1489

Merged
merged 22 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
7685685
update schema to add support for 'profile' keyword
shahzebsiddiqui May 30, 2023
b03239d
add option buildtest build --save-profile
shahzebsiddiqui May 30, 2023
6fd1ca3
add --save-profile to bash completion script
shahzebsiddiqui May 30, 2023
648ec8c
saving my work for initial implementation
shahzebsiddiqui May 31, 2023
5bbe6fd
add implementation to save profile into configuration file. Update js…
shahzebsiddiqui May 31, 2023
7e603d5
add option --profile and add to bash completion
shahzebsiddiqui May 31, 2023
06f8d65
add implementation for '--profile'. We load the profile configuration…
shahzebsiddiqui May 31, 2023
8fb7543
add test coverage for saving profile and running test from a saved pr…
shahzebsiddiqui May 31, 2023
f58cd91
remove accidental addition of profile in buildtest configuration
shahzebsiddiqui May 31, 2023
41fdd15
remove unnecessary checks if configuration file exist when saving a p…
shahzebsiddiqui May 31, 2023
16df4c2
add documentation for managing profiles.
shahzebsiddiqui May 31, 2023
1aaaa25
fix issue with yamllint
shahzebsiddiqui May 31, 2023
6a65918
change url in README.rst for issues page
shahzebsiddiqui May 31, 2023
af12751
add support for filter in profile configuration. update json schema t…
shahzebsiddiqui Jun 5, 2023
4120613
add key 'executor-type' in profile configuration to store option valu…
shahzebsiddiqui Jun 5, 2023
2d60ae8
remove uncommented lines
shahzebsiddiqui Jun 5, 2023
de4b6b0
fix issue with documentation
shahzebsiddiqui Jun 5, 2023
250a387
add documentation on managing profiles in configuring buildtest.
shahzebsiddiqui Jun 5, 2023
07c2d76
add entry in 'buildtest help build' for --save-profile and --profile
shahzebsiddiqui Jun 5, 2023
0212355
fix issue with documentation and rename profile
shahzebsiddiqui Jun 12, 2023
33d9f7b
revert changes for default config.yml
shahzebsiddiqui Jun 12, 2023
a86f14d
remove 2nd example for saving profile from documentation
shahzebsiddiqui Jun 12, 2023
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
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ The schemas are found in top-level folder `buildtest/schemas/ <https://github.co
and published via Github Pages at https://buildtesters.github.io/buildtest/. Each schema has a unique URI defined
by `$id <https://json-schema.org/understanding-json-schema/structuring.html#the-id-property>`_.

For any issues with schema, please create an `issue <https://github.com/buildtesters/buildtest/issues>`_ in buildtest.
For any issues with schema, please create an `issue <https://github.com/buildtesters/buildtest/issues/new/choose>`_ in buildtest.

References
------------
Expand All @@ -119,7 +119,7 @@ Documentation
buildtest `documentation <http://buildtest.readthedocs.io/en/latest/>`_ is your
source for getting help with buildtest. If you get stuck check out the
`current issues <https://github.com/buildtesters/buildtest/issues>`_ to see
if you face similar issue. If all else fails please create an `issue <https://buildtest.readthedocs.io/en/devel/what_is_buildtest.html>`_.
if you face similar issue. If all else fails please create an `issue <https://github.com/buildtesters/buildtest/issues/new/choose>`_.

Source Code
------------
Expand Down
2 changes: 1 addition & 1 deletion bash_completion.sh
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ _buildtest ()
#case "${prev}" in
build|bd)
local shortoption="-b -e -et -f -m -s -t -u -x -xt"
local longoption="--buildspec --disable-executor-check --executor --executor-type --exclude --exclude-tags --filter --helpfilter --limit --maxpendtime --modules --module-purge --nodes --pollinterval --procs --rerun --remove-stagedir --retry --stage --tags --timeout --unload-modules"
local longoption="--buildspec --disable-executor-check --executor --executor-type --exclude --exclude-tags --filter --helpfilter --limit --maxpendtime --modules --module-purge --nodes --pollinterval --procs --profile --rerun --remove-stagedir --retry --save-profile --stage --tags --timeout --unload-modules"
local allopts="${longoption} ${shortoption}"

COMPREPLY=( $( compgen -W "$allopts" -- $cur ) )
Expand Down
2 changes: 1 addition & 1 deletion buildtest/buildsystem/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def __init__(
buildtest_system (buildtest.system.BuildTestSystem): Instance of BuildTestSystem class
rebuild (int, option): Number of rebuild for test. This is specified via ``buildtest build --rebuild``. Defaults to 1
numprocs (list, optional): List of processor values to create builder objects specified via ``buildtest build --procs``
numnodes (list, optional): List of processor values to create builder objects specified via ``buildtest build --numnodes``
numnodes (list, optional): List of processor values to create builder objects specified via ``buildtest build --nodes``
executor_type (str, optional): Filter test by executor type (local, batch)
exclude_tags (list, optional): List of tags to exclude tests from buildspec file
"""
Expand Down
7 changes: 7 additions & 0 deletions buildtest/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,13 @@ def build_menu(subparsers):
help="Specify test timeout in number of seconds",
type=positive_number,
)
extra_group.add_argument(
"--save-profile",
help="Save buildtest command options into a profile and update configuration file",
)
extra_group.add_argument(
"--profile", help="Specify a profile to load from configuration file"
)


def buildspec_menu(subparsers, parent_parser):
Expand Down
156 changes: 128 additions & 28 deletions buildtest/cli/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import traceback
from datetime import datetime

import yaml
from buildtest import BUILDTEST_VERSION
from buildtest.builders.compiler import CompilerBuilder
from buildtest.builders.script import ScriptBuilder
Expand All @@ -38,7 +39,7 @@
)
from buildtest.executors.setup import BuildExecutor
from buildtest.log import init_logfile
from buildtest.schemas.defaults import schema_table
from buildtest.schemas.defaults import custom_validator, schema_table
from buildtest.system import BuildTestSystem
from buildtest.utils.file import (
create_dir,
Expand Down Expand Up @@ -503,6 +504,8 @@ def __init__(
executor_type=None,
timeout=None,
limit=None,
save_profile=None,
profile=None,
):
"""The initializer method is responsible for checking input arguments for type
check, if any argument fails type check we raise an error. If all arguments pass
Expand All @@ -527,37 +530,28 @@ def __init__(
retry (int, optional): Number of retry for failed jobs
account (str, optional): Project account to charge jobs. This takes input argument ``buildtest build --account``
helpfilter (bool, optional): Display available filter fields for ``buildtest build --filter`` command. This argument is set to ``True`` if one specifies ``buildtest build --helpfilter``
numprocs (str, optional): List of comma separated process values to run batch jobs specified via ``buildtest build --procs``
numnodes (str, optional): List of comma separated nodes values to run batch jobs specified via ``buildtest build --nodes``
numprocs (list, optional): List of comma separated process values to run batch jobs specified via ``buildtest build --procs``
numnodes (list, optional): List of comma separated nodes values to run batch jobs specified via ``buildtest build --nodes``
modules (str, optional): List of modules to load for every test specified via ``buildtest build --modules``.
modulepurge (bool, optional): Determine whether to run 'module purge' before running test. This is specified via ``buildtest build --modulepurge``.
unload_modules (str, optional): List of modules to unload for every test specified via ``buildtest build --unload-modules``.
rerun (bool, optional): Rerun last successful **buildtest build** command. This is specified via ``buildtest build --rerun``. All other options will be ignored and buildtest will read buildtest options from file **BUILDTEST_RERUN_FILE**.
executor_type (bool, optional): Filter test by executor type. This option will filter test after discovery by local or batch executors. This can be specified via ``buildtest build --exec-type``
timeout (int, optional): Test timeout in seconds specified by ``buildtest build --timeout``
limit (int, optional): Limit number of tests that can be run. This option is specified by ``buildtest build --limit``
save_profile (str, optional): Save profile to buildtest configuration specified by ``buildtest build --save-profile``
profile (str, optional): Profile to load from buildtest configuration specified by ``buildtest build --profile``
"""

if buildspecs and not isinstance(buildspecs, list):
raise BuildTestError(f"{buildspecs} is not of type list")
# check for input arguments that are expected to be a list
for arg_name in [buildspecs, exclude_buildspecs, tags, exclude_tags, executors]:
if arg_name and not isinstance(arg_name, list):
raise BuildTestError(f"{arg_name} is not of type list")

if exclude_buildspecs and not isinstance(exclude_buildspecs, list):
raise BuildTestError(f"{exclude_buildspecs} is not of type list")

if tags and not isinstance(tags, list):
raise BuildTestError(f"{tags} is not of type list")

if exclude_tags and not isinstance(exclude_tags, list):
raise BuildTestError(f"{exclude_tags} is not of type list")

if executors and not isinstance(executors, list):
raise BuildTestError(f"{executors} is not of type list")

if testdir and not isinstance(testdir, str):
raise BuildTestError(f"{testdir} is not of type str")

if stage and not isinstance(stage, str):
raise BuildTestError(f"{stage} is not of type str")
# check for input arguments that are expected to be a string
for arg_name in [testdir, stage, save_profile, profile]:
if arg_name and not isinstance(arg_name, str):
raise BuildTestError(f"{arg_name} is not of type str")

# if --rebuild is specified check if its an integer and within 50 rebuild limit
if rebuild:
Expand Down Expand Up @@ -606,6 +600,8 @@ def __init__(
self.executor_type = executor_type
self.timeout = timeout
self.limit = limit
self.save_profile = save_profile
self.profile = profile

# this variable contains the detected buildspecs that will be processed by buildtest.
self.detected_buildspecs = None
Expand Down Expand Up @@ -664,11 +660,19 @@ def __init__(
f"{report_file} is a directory please specify a file name where report will be written"
)

if self.save_profile:
self.save_profile_to_configuration()
return

# if buildtest build --rerun is set read file then rerun last command regardless of input specified in command line.
# the last command is stored in file BUILDTEST_RERUN_FILE which is a dictionary containing the input arguments.
if self.rerun:
self.load_rerun_file()

# if --profile is invoked then we load profile from configuration file
if self.profile:
self.load_profile()

self.buildexecutor = BuildExecutor(
self.configuration,
maxpendtime=self.maxpendtime,
Expand All @@ -682,7 +686,8 @@ def __init__(
if not isinstance(self.system, BuildTestSystem):
self.system = BuildTestSystem()

self._validate_filters()
if self.filter_buildspecs:
self._validate_filters()

msg = f"""
[magenta]User:[/] [cyan]{self.system.system['user']}
Expand Down Expand Up @@ -777,6 +782,105 @@ def save_rerun_file(self):
with open(BUILDTEST_RERUN_FILE, "w") as fd:
fd.write(json.dumps(buildtest_cmd, indent=2))

def save_profile_to_configuration(self):
"""This method will save profile to configuration file. This method is called when ``buildtest build --save-profile`` is invoked. We will open the configuration
file and update the profile section, if profile already exist we will override it, otherwise we will insert into the configuration file.
"""

resolved_buildspecs = []
if self.buildspecs:
for file in self.buildspecs:
resolved_buildspecs.append(resolve_path(file, exist=True))

# dictionary to store configuration for a profile
profile_configuration = {
"buildspecs": resolved_buildspecs or None,
"exclude-buildspecs": self.exclude_buildspecs,
"tags": self.tags,
"exclude-tags": self.exclude_tags,
"executors": self.executors,
"module": self.modules,
"unload-modules": self.unload_modules,
"module-purge": self.modulepurge,
"rebuild": self.rebuild,
"limit": self.limit,
"account": self.account,
"procs": self.numprocs,
"nodes": self.numnodes,
"testdir": self.testdir,
"timeout": self.timeout,
"filter": self.filter_buildspecs,
"executor-type": self.executor_type,
}
# we need to set module-purge to None if it is False. We delete all keys that are 'None' before writing to configuration file
profile_configuration["module-purge"] = (
None if self.modulepurge is False else True
)

# iterate over profile configuration and remove keys that are None
remove_keys = []
for key, value in profile_configuration.items():
if value is None:
remove_keys.append(key)
for key in remove_keys:
del profile_configuration[key]

system = self.configuration.name()
# delete system entry since we need to update with new profile
del self.configuration.config["system"][system]

self.configuration.config["system"][system] = self.configuration.target_config

# if profile section does not exist we create it
if not self.configuration.target_config.get("profiles"):
self.configuration.target_config["profiles"] = {}

# update profile section with new profile. If profile already exist we override it
self.configuration.target_config["profiles"][
self.save_profile
] = profile_configuration

# validate entire buildtest configuration with schema to ensure configuration is valid
custom_validator(
self.configuration.config, schema_table["settings.schema.json"]["recipe"]
)

console.print(
f"Saved profile {self.save_profile} to configuration file {self.configuration.file}"
)

with open(self.configuration.file, "w") as fd:
yaml.safe_dump(
self.configuration.config,
fd,
default_flow_style=False,
sort_keys=False,
)

def load_profile(self):
"""This method will load profile from configuration file and update class variables used for ``buildtest build`` command.
This method is called when ``buildtest build --profile`` is invoked."""

profile_configuration = self.configuration.get_profile(self.profile)

self.buildspecs = profile_configuration.get("buildspecs")
self.exclude_buildspecs = profile_configuration.get("exclude-buildspecs")
self.tags = profile_configuration.get("tags")
self.exclude_tags = profile_configuration.get("exclude-tags")
self.executors = profile_configuration.get("executors")
self.limit = profile_configuration.get("limit")
self.account = profile_configuration.get("account")
self.numprocs = profile_configuration.get("procs")
self.numnodes = profile_configuration.get("nodes")
self.testdir = profile_configuration.get("testdir")
self.timeout = profile_configuration.get("timeout")
self.modules = profile_configuration.get("module")
self.unload_modules = profile_configuration.get("unload-modules")
self.modulepurge = profile_configuration.get("module-purge")
self.rebuild = profile_configuration.get("rebuild")
self.filter_buildspecs = profile_configuration.get("filter")
self.executor_type = profile_configuration.get("executor-type")

def _validate_filters(self):
"""Check filter fields provided by ``buildtest build --filter`` are valid types and supported. Currently
supported filter fields are ``tags``, ``type``, ``maintainers``
Expand All @@ -787,10 +891,6 @@ def _validate_filters(self):

valid_fields = ["tags", "type", "maintainers"]

# if filter fields not specified there is no need to check fields
if not self.filter_buildspecs:
return

for key in self.filter_buildspecs.keys():
if key not in valid_fields:
raise BuildTestError(
Expand All @@ -817,7 +917,7 @@ def build(self):
"""

# if --helpfilter is specified then return immediately.
if self.helpfilter:
if self.helpfilter or self.save_profile:
return

self.discovered_bp = discover_buildspecs(
Expand Down
9 changes: 8 additions & 1 deletion buildtest/cli/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,14 @@ def print_build_help():
table.add_row(
"buildtest build -t python --limit=5", "Limit number of test runs to 5"
)

table.add_row(
"buildtest build -t python --save-profile=python-profile",
"Save buildtest options to profile name 'python-profile'",
)
table.add_row(
"buildtest build --profile=python-profile",
"Run buildtest from profile name 'python-profile'",
)
console.print(table)


Expand Down
22 changes: 22 additions & 0 deletions buildtest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,3 +433,25 @@ def _validate_pbs_executors(self):
self.valid_executors[executor_type][executor_name] = {
"setting": pbs_executor[executor]
}

def get_profile(self, profile_name):
"""Return configuration for a given profile name

Args:
profile_name (str): name of profile to retrieve

Returns:
dict: dictionary containing a profile configuration
"""

if not self.target_config.get("profiles"):
raise BuildTestError(
"There are no profiles defined in configuration file, please consider creating a profile using the --save-profile option"
)

if not self.target_config["profiles"].get(profile_name):
raise BuildTestError(
f"Unable to find profile: {profile_name} in configuration file. List of available profiles are: {list(self.target_config['profiles'].keys())}"
)

return self.target_config["profiles"][profile_name]
2 changes: 2 additions & 0 deletions buildtest/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ def main():
executor_type=args.executor_type,
timeout=args.timeout,
limit=args.limit,
save_profile=args.save_profile,
profile=args.profile,
)
cmd.build()

Expand Down
9 changes: 9 additions & 0 deletions buildtest/schemas/definitions.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
"type": "integer"
}
},
"list_of_positive_integers": {
"type": "array",
"uniqueItems": true,
"minItems": 1,
"items": {
"type": "integer",
"minimum": 1
}
},
"int_or_list": {
"oneOf": [
{
Expand Down
Loading