hypershell 2.7.0__tar.gz → 2.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hypershell-2.7.1/.github/FUNDING.yml +12 -0
- hypershell-2.7.1/.github/workflows/publish.yml +19 -0
- hypershell-2.7.1/.github/workflows/tests.yml +24 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/PKG-INFO +2 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/index.rst +1 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/conf.py +1 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/getting_started.rst +4 -4
- {hypershell-2.7.0 → hypershell-2.7.1}/pyproject.toml +12 -2
- {hypershell-2.7.0 → hypershell-2.7.1}/share/man/man1/hs.1 +1 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/share/man/man1/hsx.1 +1 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/share/man/man1/hyper-shell.1 +1 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/__init__.py +7 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/config.py +5 -5
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/data/__init__.py +1 -1
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/submit.py +5 -4
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/task.py +8 -8
- hypershell-2.7.1/tests/__init__.py +75 -0
- hypershell-2.7.1/tests/conftest.py +27 -0
- hypershell-2.7.1/tests/test_cluster.py +184 -0
- hypershell-2.7.1/tests/test_initdb.py +82 -0
- hypershell-2.7.1/tests/test_main.py +179 -0
- hypershell-2.7.1/tests/test_server.py +102 -0
- hypershell-2.7.1/tests/test_submit.py +214 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/uv.lock +25 -1
- hypershell-2.7.0/docs/_static/logo.svg +0 -19
- hypershell-2.7.0/tests/__init__.py +0 -4
- hypershell-2.7.0/tests/unit/__init__.py +0 -4
- hypershell-2.7.0/tests/unit/core/__init__.py +0 -2
- {hypershell-2.7.0 → hypershell-2.7.1}/.dockerignore +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/.gitignore +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/.readthedocs.yaml +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/Apptainer +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/CODE_OF_CONDUCT.md +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/Dockerfile +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/LICENSE +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/README.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/Makefile +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/citation.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/client_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/client_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/client_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/cluster_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/cluster_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/cluster_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_edit_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_edit_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_edit_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_get_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_get_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_get_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_intro.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_intro_alt.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_param_ref.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_pythonpath.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_set_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_set_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_set_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_site_vars.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_task_env.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_task_env_alt.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_which_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_which_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/config_which_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/database.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/database_alt.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/exit_status.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/initdb_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/initdb_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/initdb_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/license.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/server_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/server_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/server_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/signals.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/submit_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/submit_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/submit_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_info_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_info_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_info_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_run_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_run_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_run_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_search_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_search_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_search_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_submit_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_submit_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_submit_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_update_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_update_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_update_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_wait_desc.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_wait_help.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/task_wait_usage.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/templates.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_include/templates_alt.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_static/custom.css +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_static/logo-dark-mode.png +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/_static/logo-light-mode.png +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/api/client.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/api/cluster.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/api/data.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/api/index.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/api/server.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/api/submit.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20230329_2_2_0_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20230413_2_3_0_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20230602_2_4_0_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20240518_2_5_0_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20240706_2_5_2_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20241115_2_6_0_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20241115_announce_logo.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20241231_2_6_1_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20250215_2_6_5_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20250405_2_6_6_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/blog/20250504_2_7_0_release.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/client.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/cluster.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/config_edit.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/config_get.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/config_set.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/config_which.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/index.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/initdb.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/server.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/submit.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/task_info.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/task_run.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/task_search.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/task_submit.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/task_update.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/cli/task_wait.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/config.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/database.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/index.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/install.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/logging.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/manual.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/roadmap.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/templates.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/tutorial/advanced.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/tutorial/basic.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/tutorial/distributed.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/docs/tutorial/hybrid.rst +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/share/bash_completion.d/hs +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/share/zsh/site-functions/_hs +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/client.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/cluster/__init__.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/cluster/local.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/cluster/remote.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/cluster/ssh.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/__init__.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/config.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/exceptions.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/fsm.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/heartbeat.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/logging.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/platform.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/pretty_print.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/queue.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/remote.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/signal.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/sys.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/tag.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/template.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/thread.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/types.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/core/uuid.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/data/core.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/data/model.py +0 -0
- {hypershell-2.7.0 → hypershell-2.7.1}/src/hypershell/server.py +0 -0
- {hypershell-2.7.0/tests/unit → hypershell-2.7.1/tests}/test_data/__init__.py +0 -0
- {hypershell-2.7.0/tests/unit → hypershell-2.7.1/tests}/test_data/test_core.py +0 -0
- {hypershell-2.7.0/tests/unit → hypershell-2.7.1/tests}/test_data/test_model.py +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: glentner
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: # Replace with a single Ko-fi username
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
otechie: # Replace with a single Otechie username
|
|
12
|
+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [created]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
publish:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
steps:
|
|
10
|
+
- uses: actions/checkout@v4
|
|
11
|
+
|
|
12
|
+
- name: Install uv
|
|
13
|
+
uses: astral-sh/setup-uv@v5
|
|
14
|
+
with:
|
|
15
|
+
enable-cache: true
|
|
16
|
+
cache-dependency-glob: "uv.lock"
|
|
17
|
+
|
|
18
|
+
- name: Publish to PyPI
|
|
19
|
+
run: uv build && uv publish --token ${{ secrets.PYPI_TOKEN }}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [ master, develop ]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [ develop ]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
tests:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13' ]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
cache-dependency-glob: "uv.lock"
|
|
22
|
+
|
|
23
|
+
- name: Run tests
|
|
24
|
+
run: uv run --python ${{ matrix.python-version }} pytest -v
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypershell
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.1
|
|
4
4
|
Summary: A cross-platform, high-throughput computing utility for processing shell commands over a distributed, asynchronous queue.
|
|
5
5
|
Author-email: Geoffrey Lentner <glentner@purdue.edu>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.12
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
20
|
Classifier: Topic :: Utilities
|
|
20
21
|
Requires-Python: >=3.9
|
|
21
22
|
Requires-Dist: cmdkit[toml]>=2.7.7
|
|
@@ -72,7 +72,7 @@ html_css_files = [
|
|
|
72
72
|
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/solid.min.css",
|
|
73
73
|
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/brands.min.css",
|
|
74
74
|
]
|
|
75
|
-
html_favicon = '_static/logo.
|
|
75
|
+
html_favicon = '_static/logo-dark-mode.png'
|
|
76
76
|
html_theme_options = {
|
|
77
77
|
# 'announcement': 'See Installation page for not on PyPI package name issue!',
|
|
78
78
|
'sidebar_hide_name': True,
|
|
@@ -56,7 +56,7 @@ will manage tasks entirely within memory.
|
|
|
56
56
|
|
|
57
57
|
.. code-block:: shell
|
|
58
58
|
|
|
59
|
-
seq 4 |
|
|
59
|
+
seq 4 | hsx -t 'echo {}'
|
|
60
60
|
|
|
61
61
|
.. details:: Output
|
|
62
62
|
|
|
@@ -81,7 +81,7 @@ see additional detail about what is running, where, and when.
|
|
|
81
81
|
|
|
82
82
|
.. code-block:: shell
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
hsx tasks.in -N16 --ssh-group=xyz --capture
|
|
85
85
|
|
|
86
86
|
.. details:: Logs
|
|
87
87
|
|
|
@@ -107,7 +107,7 @@ example we stagger the launch process over one minute.
|
|
|
107
107
|
|
|
108
108
|
.. code-block:: shell
|
|
109
109
|
|
|
110
|
-
|
|
110
|
+
hsx tasks.in -N128 -b128 --launcher=srun --max-retries=2 --delay-start=-60 >task.out
|
|
111
111
|
|
|
112
112
|
.. details:: Logs
|
|
113
113
|
|
|
@@ -175,7 +175,7 @@ expansion. Many meta-patterns are supported (see full overview of :ref:`template
|
|
|
175
175
|
|
|
176
176
|
.. code-block:: shell
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
hsx tasks.in -N12 -t './some_program.py {} >outputs/{/-}.out'
|
|
179
179
|
|
|
180
180
|
Capturing `stdout` and `stderr` is supported directly in fact with the ``--capture`` option.
|
|
181
181
|
See the full documentation for environment variables under :ref:`configuration <config>`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "hypershell"
|
|
3
|
-
version = "2.7.
|
|
3
|
+
version = "2.7.1"
|
|
4
4
|
description = "A cross-platform, high-throughput computing utility for processing shell commands over a distributed, asynchronous queue."
|
|
5
5
|
readme = "README.rst"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -25,6 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Programming Language :: Python :: 3.11",
|
|
26
26
|
"Programming Language :: Python :: 3.12",
|
|
27
27
|
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Programming Language :: Python :: 3.14",
|
|
28
29
|
"Operating System :: POSIX :: Linux",
|
|
29
30
|
"Operating System :: MacOS",
|
|
30
31
|
"Operating System :: Microsoft :: Windows",
|
|
@@ -54,7 +55,8 @@ dev = [
|
|
|
54
55
|
"uuid-utils>=0.10.0",
|
|
55
56
|
"pytest>=8.1.1",
|
|
56
57
|
"hypothesis>=6.100.0",
|
|
57
|
-
"sphinx-autobuild>=2024.2.4"
|
|
58
|
+
"sphinx-autobuild>=2024.2.4",
|
|
59
|
+
"pytest-xdist>=3.7.0",
|
|
58
60
|
]
|
|
59
61
|
docs = [
|
|
60
62
|
"sphinx>=6.0,<8.0",
|
|
@@ -81,3 +83,11 @@ hsx = "hypershell:main_x"
|
|
|
81
83
|
[build-system]
|
|
82
84
|
requires = ["hatchling"]
|
|
83
85
|
build-backend = "hatchling.build"
|
|
86
|
+
|
|
87
|
+
[tool.pytest.ini_options]
|
|
88
|
+
addopts = ["--strict-markers", ]
|
|
89
|
+
markers = [
|
|
90
|
+
'unit', # Unit tests are short, interface driven tests on discrete components.",
|
|
91
|
+
'integration', # Integration tests are often longer and deal with the interaction between systems.",
|
|
92
|
+
'parameterize', # Placeholder for parameterized tests (not a real type).",
|
|
93
|
+
]
|
|
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
|
27
27
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
28
28
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
29
29
|
..
|
|
30
|
-
.TH "HS" "1" "
|
|
30
|
+
.TH "HS" "1" "Jun 07, 2025" "2.7.1" "hypershell"
|
|
31
31
|
.SH NAME
|
|
32
32
|
hs \- Process shell commands over a distributed, asynchronous queue
|
|
33
33
|
.SH SYNOPSIS
|
|
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
|
27
27
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
28
28
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
29
29
|
..
|
|
30
|
-
.TH "HS" "1" "
|
|
30
|
+
.TH "HS" "1" "Jun 07, 2025" "2.7.1" "hypershell"
|
|
31
31
|
.SH NAME
|
|
32
32
|
hs \- Process shell commands over a distributed, asynchronous queue
|
|
33
33
|
.SH SYNOPSIS
|
|
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
|
27
27
|
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
|
28
28
|
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
|
29
29
|
..
|
|
30
|
-
.TH "HYPER-SHELL" "1" "
|
|
30
|
+
.TH "HYPER-SHELL" "1" "Jun 07, 2025" "2.7.1" "hypershell"
|
|
31
31
|
.SH NAME
|
|
32
32
|
hyper-shell \- Process shell commands over a distributed, asynchronous queue
|
|
33
33
|
.SH SYNOPSIS
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
"""Initialization and entry-point for console application."""
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
# Type annotations
|
|
7
8
|
from __future__ import annotations
|
|
8
9
|
from typing import List
|
|
@@ -31,7 +32,12 @@ from hypershell.config import ConfigApp
|
|
|
31
32
|
from hypershell.data import InitDBApp
|
|
32
33
|
|
|
33
34
|
# Public interface
|
|
34
|
-
__all__ = [
|
|
35
|
+
__all__ = [
|
|
36
|
+
'HyperShellApp', 'main', '__version__', '__citation__',
|
|
37
|
+
'APP_VERSION', 'APP_USAGE', 'APP_HELP',
|
|
38
|
+
'SubmitApp', 'ServerApp', 'ClientApp', 'InitDBApp', 'ConfigApp',
|
|
39
|
+
'TaskGroupApp', 'TaskInfoApp', 'TaskWaitApp', 'TaskRunApp', 'TaskSearchApp', 'TaskUpdateApp',
|
|
40
|
+
]
|
|
35
41
|
|
|
36
42
|
# project metadata
|
|
37
43
|
__version__ = get_version('hypershell')
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
# Type annotations
|
|
8
8
|
from __future__ import annotations
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, Final
|
|
10
10
|
|
|
11
11
|
# Standard libs
|
|
12
12
|
import os
|
|
@@ -386,16 +386,16 @@ class ConfigWhichApp(Application):
|
|
|
386
386
|
|
|
387
387
|
|
|
388
388
|
if os.name == 'nt':
|
|
389
|
-
CONFIG_PATH_INFO = f"""\
|
|
389
|
+
CONFIG_PATH_INFO: Final[str] = f"""\
|
|
390
390
|
--system %ProgramData%\\HyperShell\\Config.toml
|
|
391
391
|
--user %AppData%\\HyperShell\\Config.toml
|
|
392
|
-
--local {path.local.config}
|
|
392
|
+
--local {path.local.config}\
|
|
393
393
|
"""
|
|
394
394
|
else:
|
|
395
|
-
CONFIG_PATH_INFO = f"""\
|
|
395
|
+
CONFIG_PATH_INFO: Final[str] = f"""\
|
|
396
396
|
--system /etc/hypershell.toml
|
|
397
397
|
--user ~/.hypershell/config.toml
|
|
398
|
-
--local {path.local.config}
|
|
398
|
+
--local {path.local.config}\
|
|
399
399
|
"""
|
|
400
400
|
|
|
401
401
|
|
|
@@ -258,7 +258,7 @@ class InitDBApp(Application):
|
|
|
258
258
|
raise ArgumentError('Can only rotate SQLite')
|
|
259
259
|
if self.backup_path and os.path.exists(self.backup_path):
|
|
260
260
|
raise RuntimeError(f'Backup path already exists ({self.backup_path})')
|
|
261
|
-
if not sys.stdout.isatty():
|
|
261
|
+
if not sys.stdout.isatty() and not self.auto_confirm:
|
|
262
262
|
raise RuntimeError('Non-interactive prompt cannot confirm (see --yes).')
|
|
263
263
|
|
|
264
264
|
def confirm_action(self: InitDBApp, message: str) -> bool:
|
|
@@ -766,9 +766,8 @@ class SubmitApp(Application):
|
|
|
766
766
|
source: IO | List[str] | None = None
|
|
767
767
|
task_args: List[str] = []
|
|
768
768
|
task_file: str = None
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
input_interface.add_argument('-f', '--task-file', default=task_file)
|
|
769
|
+
interface.add_argument('task_args', nargs='*')
|
|
770
|
+
interface.add_argument('-f', '--task-file', default=task_file)
|
|
772
771
|
|
|
773
772
|
bundlesize: int = config.submit.bundlesize
|
|
774
773
|
interface.add_argument('-b', '--bundlesize', type=int, default=bundlesize)
|
|
@@ -784,7 +783,7 @@ class SubmitApp(Application):
|
|
|
784
783
|
|
|
785
784
|
tags: Dict[str, JSONValue] = {}
|
|
786
785
|
taglist: List[str] = None
|
|
787
|
-
interface.add_argument('-t', '--tag', nargs='
|
|
786
|
+
interface.add_argument('-t', '--tag', nargs='+', default=[], dest='taglist')
|
|
788
787
|
|
|
789
788
|
exceptions = {
|
|
790
789
|
**get_shared_exception_mapping(__name__)
|
|
@@ -833,6 +832,8 @@ class SubmitApp(Application):
|
|
|
833
832
|
|
|
834
833
|
def check_source(self: SubmitApp) -> None:
|
|
835
834
|
"""Determine task submission mode."""
|
|
835
|
+
if self.task_args and self.task_file:
|
|
836
|
+
raise ArgumentError(f'Cannot specify both -f/--task-file and positional arguments')
|
|
836
837
|
if self.task_file == '-':
|
|
837
838
|
log.debug(f'Submitted from <stdin> (explicit)')
|
|
838
839
|
self.source = sys.stdin
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2025 Geoffrey Lentner
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
"""Task
|
|
4
|
+
"""Task-based operations."""
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Type annotations
|
|
@@ -85,7 +85,7 @@ class TaskSubmitApp(Application):
|
|
|
85
85
|
|
|
86
86
|
tags: Dict[str, JSONValue] = {}
|
|
87
87
|
taglist: List[str] = []
|
|
88
|
-
interface.add_argument('-t', '--tag', nargs='
|
|
88
|
+
interface.add_argument('-t', '--tag', nargs='+', dest='taglist')
|
|
89
89
|
|
|
90
90
|
exceptions = {
|
|
91
91
|
**get_shared_exception_mapping(__name__)
|
|
@@ -382,7 +382,7 @@ class TaskRunApp(Application):
|
|
|
382
382
|
interface.add_argument('-n', '--interval', type=int, default=interval)
|
|
383
383
|
|
|
384
384
|
taglist: List[str] = []
|
|
385
|
-
interface.add_argument('-t', '--tag', nargs='
|
|
385
|
+
interface.add_argument('-t', '--tag', nargs='+', dest='taglist')
|
|
386
386
|
|
|
387
387
|
exceptions = {
|
|
388
388
|
**get_shared_exception_mapping(__name__)
|
|
@@ -549,10 +549,10 @@ class TaskSearchApp(Application, SearchableMixin):
|
|
|
549
549
|
interface.add_argument('field_names', nargs='*', default=field_names)
|
|
550
550
|
|
|
551
551
|
where_clauses: List[str] = None
|
|
552
|
-
interface.add_argument('-w', '--where', nargs='
|
|
552
|
+
interface.add_argument('-w', '--where', nargs='+', default=[], dest='where_clauses')
|
|
553
553
|
|
|
554
554
|
taglist: List[str] = None
|
|
555
|
-
interface.add_argument('-t', '--with-tag', nargs='
|
|
555
|
+
interface.add_argument('-t', '--with-tag', nargs='+', default=[], dest='taglist')
|
|
556
556
|
|
|
557
557
|
order_by: str = None
|
|
558
558
|
order_desc: bool = False
|
|
@@ -815,10 +815,10 @@ class TaskUpdateApp(Application, SearchableMixin):
|
|
|
815
815
|
field_names: List[str] = []
|
|
816
816
|
|
|
817
817
|
where_clauses: List[str] = None
|
|
818
|
-
interface.add_argument('-w', '--where', nargs='
|
|
818
|
+
interface.add_argument('-w', '--where', nargs='+', default=[], dest='where_clauses')
|
|
819
819
|
|
|
820
820
|
taglist: List[str] = None
|
|
821
|
-
interface.add_argument('-t', '--with-tag', nargs='
|
|
821
|
+
interface.add_argument('-t', '--with-tag', nargs='+', default=[], dest='taglist')
|
|
822
822
|
|
|
823
823
|
order_by: str = None
|
|
824
824
|
order_desc: bool = False
|
|
@@ -847,7 +847,7 @@ class TaskUpdateApp(Application, SearchableMixin):
|
|
|
847
847
|
action_interface.add_argument('--delete', action='store_true', dest='delete_mode')
|
|
848
848
|
|
|
849
849
|
remove_tag: List[str] = None
|
|
850
|
-
interface.add_argument('--remove-tag', nargs='
|
|
850
|
+
interface.add_argument('--remove-tag', nargs='+')
|
|
851
851
|
|
|
852
852
|
no_confirm: bool = False
|
|
853
853
|
interface.add_argument('-f', '--no-confirm', action='store_true')
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Geoffrey Lentner
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Automated tests for HyperShell."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Type annotations
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import Final, List, Tuple, Dict, Any
|
|
10
|
+
|
|
11
|
+
# Standard libs
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from subprocess import run, PIPE
|
|
16
|
+
|
|
17
|
+
# Public interface
|
|
18
|
+
__all__ = ['NO_OUTPUT', 'main', 'main_lines', 'assert_output',
|
|
19
|
+
'create_taskfile', 'create_taskfile_echo',
|
|
20
|
+
'UUID_PATTERN']
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Flag for empty output from `main_lines` function
|
|
24
|
+
NO_OUTPUT: Final[List[str]] = ['', ]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main(argv: List[str]) -> Tuple[int, str, str]:
|
|
28
|
+
"""Return stdout, stderr, and exit code of command-line interface."""
|
|
29
|
+
proc = run(argv, stdout=PIPE, stderr=PIPE, env=os.environ)
|
|
30
|
+
return (
|
|
31
|
+
proc.returncode,
|
|
32
|
+
proc.stdout.decode('utf-8').strip(),
|
|
33
|
+
proc.stderr.decode('utf-8').strip()
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main_lines(argv: List[str]) -> Tuple[int, List[str], List[str]]:
|
|
38
|
+
"""Return stdout, stderr, and exit code of command-line interface with output as List[str]."""
|
|
39
|
+
rc, stdout, stderr = main(argv)
|
|
40
|
+
return (
|
|
41
|
+
rc,
|
|
42
|
+
[line.strip() for line in stdout.split('\n')],
|
|
43
|
+
[line.strip() for line in stderr.split('\n')]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def assert_output(pattern: str, output: str, count: int = 1, groups: Dict[str, str | re.Pattern] = None) -> None:
|
|
48
|
+
"""Assert some count of lines in output match pattern, optionally with capture groups pattern matching."""
|
|
49
|
+
n = 0
|
|
50
|
+
for line in output.strip().splitlines():
|
|
51
|
+
if match := re.search(pattern, line):
|
|
52
|
+
n += 1
|
|
53
|
+
for key, value in (groups or {}).items():
|
|
54
|
+
assert re.match(value, match.group(key))
|
|
55
|
+
assert n == count
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def create_taskfile(temp_site: Path, lines: List[str]) -> Path:
|
|
59
|
+
"""Produce task input file for test, return path."""
|
|
60
|
+
taskfile = temp_site / 'task.in'
|
|
61
|
+
with open(taskfile, mode='w', encoding='utf-8') as stream:
|
|
62
|
+
for line in lines:
|
|
63
|
+
print(line, file=stream)
|
|
64
|
+
return taskfile
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def create_taskfile_echo(temp_site: Path, count: int, tags: Dict[str, Any] = None) -> Path:
|
|
68
|
+
"""Produce task input file with 'echo {}' lines, return path."""
|
|
69
|
+
tagline = ' '.join([f'{k}:{v}' for k, v in (tags or {}).items()])
|
|
70
|
+
return create_taskfile(temp_site, lines=[f'echo {n} # HYPERSHELL: {tagline} n:{n}' for n in range(count)])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
UUID_PATTERN: re.Pattern = re.compile(
|
|
74
|
+
r'^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$'
|
|
75
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Geoffrey Lentner
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Fixtures for testing."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Standard libs
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
# External libs
|
|
11
|
+
from pytest import fixture
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@fixture(scope="function")
|
|
16
|
+
def temp_site(tmpdir_factory) -> Path:
|
|
17
|
+
"""Setup empty site and database path."""
|
|
18
|
+
for name in os.environ:
|
|
19
|
+
if name.startswith('HYPERSHELL_'):
|
|
20
|
+
os.environ.pop(name)
|
|
21
|
+
site = tmpdir_factory.mktemp('data')
|
|
22
|
+
db_path = site.join('local.db')
|
|
23
|
+
os.environ['HYPERSHELL_CONFIG_FILE'] = ''
|
|
24
|
+
os.environ['HYPERSHELL_SITE'] = str(site)
|
|
25
|
+
os.environ['HYPERSHELL_DATABASE_FILE'] = str(db_path)
|
|
26
|
+
os.environ['HYPERSHELL_LOGGING_LEVEL'] = 'DEBUG'
|
|
27
|
+
return site
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2025 Geoffrey Lentner
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Test cluster operations."""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# Type annotations
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from typing import Final, Dict
|
|
10
|
+
|
|
11
|
+
# Standard libs
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# External libs
|
|
15
|
+
from pytest import mark
|
|
16
|
+
from cmdkit.app import exit_status
|
|
17
|
+
|
|
18
|
+
# Internal libs
|
|
19
|
+
from tests import main, main_lines, NO_OUTPUT, create_taskfile_echo, assert_output
|
|
20
|
+
from hypershell.cluster import ClusterApp
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@mark.integration
|
|
24
|
+
def test_cluster_usage() -> None:
|
|
25
|
+
"""The 'hs cluster' command prints usage statement without arguments."""
|
|
26
|
+
rc, stdout, stderr = main(['hs', 'cluster', ])
|
|
27
|
+
assert rc == exit_status.usage
|
|
28
|
+
assert stdout == ClusterApp.interface.usage_text
|
|
29
|
+
assert stderr == ''
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@mark.integration
|
|
33
|
+
def test_cluster_usage_hsx() -> None:
|
|
34
|
+
"""The 'hsx' command prints usage statement without arguments."""
|
|
35
|
+
rc, stdout, stderr = main(['hsx', ])
|
|
36
|
+
assert rc == exit_status.usage
|
|
37
|
+
assert stdout == ClusterApp.interface.usage_text
|
|
38
|
+
assert stderr == ''
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@mark.integration
|
|
42
|
+
@mark.parametrize('opt', ['-h', '--help'])
|
|
43
|
+
def test_cluster_help(opt: str) -> None:
|
|
44
|
+
"""The 'hs cluster' command prints help statement with -h, --help option."""
|
|
45
|
+
rc, stdout, stderr = main(['hs', 'cluster', opt, ])
|
|
46
|
+
assert rc == exit_status.success
|
|
47
|
+
assert stdout == ClusterApp.interface.help_text
|
|
48
|
+
assert stderr == ''
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@mark.integration
|
|
52
|
+
@mark.parametrize('opt', ['-h', '--help'])
|
|
53
|
+
def test_cluster_help_hsx(opt: str) -> None:
|
|
54
|
+
"""The 'hsx' command prints help statement with -h, --help option."""
|
|
55
|
+
rc, stdout, stderr = main(['hsx', opt, ])
|
|
56
|
+
assert rc == exit_status.success
|
|
57
|
+
assert stdout == ClusterApp.interface.help_text
|
|
58
|
+
assert stderr == ''
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@mark.integration
|
|
62
|
+
def test_cluster_mixed_modes() -> None:
|
|
63
|
+
"""Cannot use multiple start methods."""
|
|
64
|
+
assert main_lines(['hs', 'cluster', '--forever', '--restart']) == (
|
|
65
|
+
exit_status.bad_argument, NO_OUTPUT, [
|
|
66
|
+
'CRITICAL [hypershell] Using --forever with --restart is invalid',
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@mark.integration
|
|
72
|
+
@mark.parametrize('opt', ['--foo', '--bar', '--baz=7'])
|
|
73
|
+
def test_cluster_bad_options(opt: str) -> None:
|
|
74
|
+
"""Error on invalid options."""
|
|
75
|
+
assert main_lines(['hs', 'cluster', opt]) == (
|
|
76
|
+
exit_status.bad_argument, NO_OUTPUT, [
|
|
77
|
+
f'CRITICAL [hypershell] unrecognized arguments: {opt}',
|
|
78
|
+
]
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@mark.integration
|
|
83
|
+
def test_cluster_missing_file(temp_site: Path) -> None:
|
|
84
|
+
"""Error on missing file."""
|
|
85
|
+
missing_file = str(temp_site / 'missing_file.txt')
|
|
86
|
+
rc, stdout, stderr = main(['hs', 'cluster', missing_file])
|
|
87
|
+
assert rc == exit_status.runtime_error
|
|
88
|
+
assert stdout == ''
|
|
89
|
+
assert_output(r'CRITICAL .* FileNotFoundError: .* No such file .*', stderr, 1)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@mark.integration
|
|
93
|
+
def test_cluster_multiple_arguments() -> None:
|
|
94
|
+
"""Error on multiple positional arguments."""
|
|
95
|
+
assert main_lines(['hs', 'cluster', 'file_a', 'file_b']) == (
|
|
96
|
+
exit_status.bad_argument, NO_OUTPUT, [
|
|
97
|
+
f'CRITICAL [hypershell] unrecognized arguments: file_b',
|
|
98
|
+
]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
OPTION_PAIR: Final[Dict[str, str]] = {
|
|
103
|
+
'-N': '-N/--num-tasks', '--num-tasks': '-N/--num-tasks',
|
|
104
|
+
'-t': '-t/--template', '--template': '-t/--template',
|
|
105
|
+
'-p': '-p/--port', '--port': '-p/--port',
|
|
106
|
+
'-b': '-b/--bundlesize', '--bundlesize': '-b/--bundlesize',
|
|
107
|
+
'-w': '-w/--bundlewait', '--bundlewait': '-w/--bundlewait',
|
|
108
|
+
'-r': '-r/--max-retries', '--max-retries': '-r/--max-retries',
|
|
109
|
+
'-o': '-o/--output', '--output': '-o/--output',
|
|
110
|
+
'-e': '-e/--errors', '--errors': '-e/--errors',
|
|
111
|
+
'-f': '-f/--failures', '--failures': '-f/--failures',
|
|
112
|
+
'--ssh-args': '--ssh-args', '--ssh-group': '--ssh-group',
|
|
113
|
+
'--remote-exe': '--remote-exe',
|
|
114
|
+
'-d': '-d/--delay-start', '--delay-start': '-d/--delay-start',
|
|
115
|
+
'-T': '-T/--timeout', '--timeout': '-T/--timeout',
|
|
116
|
+
'-W': '-W/--task-timeout', '--task-timeout': '-W/--task-timeout',
|
|
117
|
+
'-S': '-S/--signalwait', '--signalwait': '-S/--signalwait',
|
|
118
|
+
'-F': '-F/--factor', '--factor': '-F/--factor',
|
|
119
|
+
'-P': '-P/--period', '--period': '-P/--period',
|
|
120
|
+
'-I': '-I/--init-size', '--init-size': '-I/--init-size',
|
|
121
|
+
'-X': '-X/--min-size', '--min-size': '-X/--min-size',
|
|
122
|
+
'-Y': '-Y/--max-size', '--max-size': '-Y/--max-size',
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@mark.integration
|
|
127
|
+
@mark.parametrize('opt', list(OPTION_PAIR))
|
|
128
|
+
def test_cluster_missing_option_value(opt: str) -> None:
|
|
129
|
+
"""Error on missing option value."""
|
|
130
|
+
assert main_lines(['hs', 'cluster', '-', opt]) == (
|
|
131
|
+
exit_status.bad_argument, NO_OUTPUT, [
|
|
132
|
+
f'CRITICAL [hypershell] argument {OPTION_PAIR[opt]}: expected one argument',
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@mark.integration
|
|
138
|
+
def test_cluster_basic(temp_site: Path) -> None:
|
|
139
|
+
"""Run small collection of tasks without database."""
|
|
140
|
+
taskfile = create_taskfile_echo(temp_site, count=4)
|
|
141
|
+
rc, stdout, stderr = main(['hs', 'cluster', taskfile, '--no-db', '--no-confirm'])
|
|
142
|
+
assert rc == exit_status.success
|
|
143
|
+
assert stdout == '\n'.join(map(str, range(4)))
|
|
144
|
+
assert_output(r'Registered client', stderr, 1)
|
|
145
|
+
assert_output(r'INFO \[hypershell.client\] Running task', stderr, 4)
|
|
146
|
+
assert_output(r'DEBUG \[hypershell.client\] Running task', stderr, 4)
|
|
147
|
+
assert_output(r'\[hypershell.server\] Completed task', stderr, 4)
|
|
148
|
+
assert_output(r'\[hypershell.client\] Completed task', stderr, 4)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@mark.integration
|
|
152
|
+
def test_cluster_database(temp_site: Path) -> None:
|
|
153
|
+
"""Run small collection of tasks from file to database."""
|
|
154
|
+
|
|
155
|
+
# Start out with an empty database
|
|
156
|
+
assert main_lines(['hs', 'list', '--count']) == (
|
|
157
|
+
exit_status.success, ['0', ], NO_OUTPUT
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Run with `seq 4` inputs
|
|
161
|
+
taskfile = create_taskfile_echo(temp_site, count=4)
|
|
162
|
+
rc, stdout, stderr = main(['hs', 'cluster', taskfile])
|
|
163
|
+
assert rc == exit_status.success
|
|
164
|
+
assert stdout == '\n'.join(map(str, range(4)))
|
|
165
|
+
assert_output(r'Registered client', stderr, 1)
|
|
166
|
+
assert_output(r'INFO \[hypershell.client\] Running task', stderr, 4)
|
|
167
|
+
assert_output(r'DEBUG \[hypershell.client\] Running task', stderr, 4)
|
|
168
|
+
assert_output(r'\[hypershell.server\] Completed task', stderr, 4)
|
|
169
|
+
assert_output(r'\[hypershell.client\] Completed task', stderr, 4)
|
|
170
|
+
|
|
171
|
+
# Now there are 4 tasks in the database
|
|
172
|
+
assert main_lines(['hs', 'list', '--count']) == (
|
|
173
|
+
exit_status.success, ['4', ], NO_OUTPUT
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Confirm arguments
|
|
177
|
+
assert main_lines(['hs', 'list', 'args', '-f', 'plain', '-s', 'submit_time']) == (
|
|
178
|
+
exit_status.success, ['echo 0', 'echo 1', 'echo 2', 'echo 3'], NO_OUTPUT
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Confirm status
|
|
182
|
+
assert main_lines(['hs', 'list', 'exit_status', '-f', 'plain', '-s', 'submit_time']) == (
|
|
183
|
+
exit_status.success, ['0'] * 4, NO_OUTPUT
|
|
184
|
+
)
|