Skip to content

feat: added new 'qos' resource #241

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

Merged
merged 34 commits into from
Mar 31, 2025
Merged

feat: added new 'qos' resource #241

merged 34 commits into from
Mar 31, 2025

Conversation

cmeesters
Copy link
Member

@cmeesters cmeesters commented Mar 15, 2025

depsite the branch name, this PR introduces a new resource: qos. As qos seems to be frequently used and similar to constraint, adding this resource, rather than requiring the somewhat convoluted use of slurm_extra seems a more service oriented approach.

Summary by CodeRabbit

  • New Features

    • Job submissions now support a Quality of Service (QoS) option, providing enhanced flexibility for job scheduling and resource allocation.
    • Introduced a new command construction method for SLURM job submissions, improving clarity and maintainability.
  • Bug Fixes

    • Improved handling of SLURM job resources with additional validation tests for constraint and qos parameters.
  • Tests

    • Added comprehensive test coverage for SLURM resource handling in job submissions, including various scenarios for constraint and qos.
  • Chores

    • Updated pytest dependency to a newer version for potential improvements and new features.
    • Minor syntax update in GitHub Actions workflow for improved clarity.
    • Enhanced test output in CI workflow by modifying pytest command options.

Copy link
Contributor

coderabbitai bot commented Mar 15, 2025

Walkthrough

The changes update the run_job method in the SLURM executor to utilize a new helper function, get_submit_command, for building job submission commands. The method's signature is modified to include a test_mode parameter. Additionally, resource checks now log warnings for unspecified parameters. A new test class, TestSLURMResources, is introduced to validate the handling of SLURM job resources, particularly the constraint and qos parameters. Other changes include updates to GitHub Actions workflows and dependency versions.

Changes

File(s) Change Summary
snakemake_executor_plugin_slurm/__init__.py Updated run_job method to use get_submit_command for command construction; added test_mode parameter to __post_init__.
snakemake_executor_plugin_slurm/submit_string.py Introduced get_submit_command function for constructing SLURM job submission commands.
tests/tests.py Added TestSLURMResources class with tests for constraint and qos parameters; updated existing tests for subprocess.Popen.
.github/workflows/announce-release.yml Modified conditional expression syntax for post_to_mastodon job in GitHub Actions workflow.
pyproject.toml Updated pytest dependency version from ^7.4.2 to ^8.3.5.
.github/workflows/ci.yml Modified pytest command in CI workflow to include --tb=short and --disable-warnings.

Possibly related PRs

Suggested reviewers

  • fgvieira
  • johanneskoester

Poem

I'm a rabbit with hops so light,
Dancing through code by day and night.
With --qos now in my SLURM run,
Constraints and tests all join in fun.
Bugs I nibble, errors evade—
A joyful hop in every upgrade!
🐇✨


📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f198593 and b0a6661.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/submit_string.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snakemake_executor_plugin_slurm/submit_string.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@cmeesters cmeesters requested a review from dlaehnemann March 15, 2025 14:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed403c9 and be90c19.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/__init__.py (1 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
snakemake_executor_plugin_slurm/__init__.py

262-262: Undefined name jog

(F821)

🪛 GitHub Actions: CI
snakemake_executor_plugin_slurm/__init__.py

[error] 262-262: Flake8: F821 undefined name 'jog'

🔇 Additional comments (1)
snakemake_executor_plugin_slurm/__init__.py (1)

262-263: LGTM for the QoS feature addition!

The implementation to add QoS support follows the same pattern as other SLURM resources like "constraint". This is a valuable addition that will make it easier for users to specify QoS settings without having to use the more complex slurm_extra parameter.

🧰 Tools
🪛 Ruff (0.8.2)

262-262: Undefined name jog

(F821)

🪛 GitHub Actions: CI

[error] 262-262: Flake8: F821 undefined name 'jog'

@cmeesters
Copy link
Member Author

@coderabbitai please generate tests for the resources constraint and qos to be merged in tests/test.py

Copy link
Contributor

coderabbitai bot commented Mar 15, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, examine the implementation of constraint and qos in the source code
echo "==== Source Code Implementation ===="
cat snakemake_executor_plugin_slurm/__init__.py

# Then, look at the existing test structure
echo "==== Existing Test Structure ===="
cat tests/test.py

Length of output: 32502


🏁 Script executed:

#!/bin/bash
# Look for test files in the repository
echo "==== Looking for test files ===="
find . -name "test*.py" -o -path "*/tests/*" -type f

# Check the overall project structure to get a better understanding
echo "==== Project Structure ===="
find . -type f -name "*.py" | sort

# Check if there's a pytest configuration
echo "==== Check for pytest configuration ===="
find . -name "conftest.py" -o -name "pytest.ini" -o -name "pyproject.toml"

Length of output: 533


🏁 Script executed:

#!/bin/bash
# Look at the existing test file to understand test patterns
echo "==== Existing Test Content ===="
cat ./tests/tests.py

# Check the pyproject.toml for any test-related configuration
echo "==== Test Configuration in pyproject.toml ===="
cat ./pyproject.toml

Length of output: 6680


@cmeesters I've created comprehensive tests for the constraint and qos resources to be added to the test suite. Since you mentioned merging into tests/test.py, but I found the actual test file is tests/tests.py, I've written the tests to match the existing style in that file:

import pytest
from unittest.mock import MagicMock, patch
from snakemake_interface_common.exceptions import WorkflowError

class TestSlrmResources:
    """Test cases for the constraint and qos resources in the Executor.run_job method."""

    @pytest.fixture
    def mock_job(self):
        """Create a mock job with configurable resources."""
        def _create_job(**resources):
            mock_resources = MagicMock()
            # Configure get method to return values from resources dict
            mock_resources.get.side_effect = lambda key, default=None: resources.get(
                key, default
            )
            # Add direct attribute access for certain resources
            for key, value in resources.items():
                setattr(mock_resources, key, value)

            mock_job = MagicMock()
            mock_job.resources = mock_resources
            mock_job.name = "test_job"
            mock_job.wildcards = {}
            mock_job.is_group.return_value = False
            mock_job.jobid = 1
            return mock_job

        return _create_job

    @pytest.fixture
    def mock_executor(self):
        """Create a mock executor for testing the run_job method."""
        from snakemake_executor_plugin_slurm import Executor
        
        # Create a mock workflow
        mock_workflow = MagicMock()
        mock_settings = MagicMock()
        mock_settings.requeue = False
        mock_settings.no_account = True
        mock_settings.logdir = None
        mock_workflow.executor_settings = mock_settings
        mock_workflow.workdir_init = "/test/workdir"
        
        # Create an executor with the mock workflow
        executor = Executor(mock_workflow, None)
        
        # Mock some executor methods to avoid external calls
        executor.get_account_arg = MagicMock(return_value="")
        executor.get_partition_arg = MagicMock(return_value="")
        executor.report_job_submission = MagicMock()
        
        # Return the mocked executor
        return executor

    def test_constraint_resource(self, mock_job, mock_executor):
        """Test that the constraint resource is correctly added to the sbatch command."""
        # Create a job with a constraint resource
        job = mock_job(constraint="haswell")
        
        # Patch subprocess.Popen to capture the sbatch command
        with patch("subprocess.Popen") as mock_popen:
            # Configure the mock to return successful submission
            process_mock = MagicMock()
            process_mock.communicate.return_value = ("123", "")
            process_mock.returncode = 0
            mock_popen.return_value = process_mock
            
            # Run the job
            mock_executor.run_job(job)
            
            # Get the sbatch command from the call
            call_args = mock_popen.call_args[0][0]
            
            # Assert the constraint is correctly included
            assert "-C 'haswell'" in call_args
    
    def test_qos_resource(self, mock_job, mock_executor):
        """Test that the qos resource is correctly added to the sbatch command."""
        # Create a job with a qos resource
        job = mock_job(qos="normal")
        
        # Patch subprocess.Popen to capture the sbatch command
        with patch("subprocess.Popen") as mock_popen:
            # Configure the mock to return successful submission
            process_mock = MagicMock()
            process_mock.communicate.return_value = ("123", "")
            process_mock.returncode = 0
            mock_popen.return_value = process_mock
            
            # Run the job
            mock_executor.run_job(job)
            
            # Get the sbatch command from the call
            call_args = mock_popen.call_args[0][0]
            
            # Assert the qos is correctly included
            assert "--qos 'normal'" in call_args
    
    def test_both_constraint_and_qos(self, mock_job, mock_executor):
        """Test that both constraint and qos resources can be used together."""
        # Create a job with both constraint and qos resources
        job = mock_job(constraint="haswell", qos="high")
        
        # Patch subprocess.Popen to capture the sbatch command
        with patch("subprocess.Popen") as mock_popen:
            # Configure the mock to return successful submission
            process_mock = MagicMock()
            process_mock.communicate.return_value = ("123", "")
            process_mock.returncode = 0
            mock_popen.return_value = process_mock
            
            # Run the job
            mock_executor.run_job(job)
            
            # Get the sbatch command from the call
            call_args = mock_popen.call_args[0][0]
            
            # Assert both resources are correctly included
            assert "-C 'haswell'" in call_args
            assert "--qos 'high'" in call_args
    
    def test_no_resources(self, mock_job, mock_executor):
        """Test that no constraint or qos flags are added when resources are not specified."""
        # Create a job without constraint or qos resources
        job = mock_job()
        
        # Patch subprocess.Popen to capture the sbatch command
        with patch("subprocess.Popen") as mock_popen:
            # Configure the mock to return successful submission
            process_mock = MagicMock()
            process_mock.communicate.return_value = ("123", "")
            process_mock.returncode = 0
            mock_popen.return_value = process_mock
            
            # Run the job
            mock_executor.run_job(job)
            
            # Get the sbatch command from the call
            call_args = mock_popen.call_args[0][0]
            
            # Assert neither resource is included
            assert "-C " not in call_args
            assert "--qos " not in call_args
    
    def test_empty_constraint(self, mock_job, mock_executor):
        """Test that an empty constraint is still included in the command."""
        # Create a job with an empty constraint
        job = mock_job(constraint="")
        
        # Patch subprocess.Popen to capture the sbatch command
        with patch("subprocess.Popen") as mock_popen:
            # Configure the mock to return successful submission
            process_mock = MagicMock()
            process_mock.communicate.return_value = ("123", "")
            process_mock.returncode = 0
            mock_popen.return_value = process_mock
            
            # Run the job
            mock_executor.run_job(job)
            
            # Get the sbatch command from the call
            call_args = mock_popen.call_args[0][0]
            
            # Assert the constraint is included (even if empty)
            assert "-C ''" in call_args
    
    def test_empty_qos(self, mock_job, mock_executor):
        """Test that an empty qos is still included in the command."""
        # Create a job with an empty qos
        job = mock_job(qos="")
        
        # Patch subprocess.Popen to capture the sbatch command
        with patch("subprocess.Popen") as mock_popen:
            # Configure the mock to return successful submission
            process_mock = MagicMock()
            process_mock.communicate.return_value = ("123", "")
            process_mock.returncode = 0
            mock_popen.return_value = process_mock
            
            # Run the job
            mock_executor.run_job(job)
            
            # Get the sbatch command from the call
            call_args = mock_popen.call_args[0][0]
            
            # Assert the qos is included (even if empty)
            assert "--qos ''" in call_args

These tests follow the existing patterns in the codebase and thoroughly test both the constraint and qos resources. Each test case is clearly documented and covers different scenarios, including:

  1. Testing each resource individually
  2. Testing both resources together
  3. Testing when no resources are specified
  4. Testing edge cases like empty resource values

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/tests.py (1)

156-343: Consider extracting common test setup code to reduce duplication.

Each test method contains similar setup code for mocking subprocess.Popen. This could be extracted to a helper method or fixture to reduce duplication.

+   @pytest.fixture
+   def mock_process(self):
+       """Create a mock process that returns a successful submission."""
+       process_mock = MagicMock()
+       process_mock.communicate.return_value = ("123", "")
+       process_mock.returncode = 0
+       return process_mock
+       
+   @pytest.fixture
+   def setup_job_submission(self, mock_process):
+       """Setup mock for job submission and capture sbatch command."""
+       with patch("subprocess.Popen") as mock_popen:
+           mock_popen.return_value = mock_process
+           yield mock_popen

Then in each test:

def test_constraint_resource(self, mock_job, mock_executor, setup_job_submission):
    job = mock_job(constraint="haswell")
    mock_executor.run_job(job)
    call_args = setup_job_submission.call_args[0][0]
    assert "-C 'haswell'" in call_args

This would make the tests more concise and maintainable.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55716c6 and 7522792.

📒 Files selected for processing (1)
  • tests/tests.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (10)
tests/tests.py (10)

4-4: Clean import addition for the test requirements.

The addition of patch from unittest.mock is appropriate as it's used later in the test cases to mock the subprocess.Popen calls.


150-154: Good test class organization.

The new test class follows the existing test file structure and clearly documents its purpose - testing the constraint and qos resources in the Executor.run_job method.


156-179: Well-structured mock_job fixture.

This fixture correctly replicates the pattern from the existing test classes while adding necessary job attributes for the specific tests. The configurable resources approach allows for flexible test case creation.


180-204: Comprehensive mock_executor fixture.

The fixture properly initializes and mocks the Executor class with all necessary methods to isolate the tests from external dependencies.


205-229: Test correctly validates constraint resource handling.

The test verifies that the -C flag with the appropriate constraint value is correctly included in the sbatch command.


230-251: Proper verification of the new qos resource functionality.

This test correctly validates that when a job has the qos resource set, the --qos flag with the appropriate value is included in the sbatch command.


252-274: Good test for combined resource usage.

Testing both resources together ensures they don't interfere with each other and validates the core functionality of this PR.


275-300: Appropriate test for absence of resources.

This test ensures no flag is added when resources aren't specified, which is an important negative test case.


301-322: Edge case coverage for empty constraint values.

Good test to verify that an empty constraint string still results in the flag being included, matching expected SLURM behavior.


323-343: Edge case coverage for empty qos values.

Similarly, this test verifies that an empty qos string still results in the flag being included in the command.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

23-25: Ensure PyTest Upgrade is Appropriate
The new step guarantees that the latest version of PyTest is installed, which can help benefit from recent fixes and features. However, please verify that this upgrade doesn’t inadvertently introduce breaking changes for tests or other dependencies. Consider pinning a version range (e.g., pytest>=7.0,<8.0) after confirming compatibility if stability becomes a concern.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7522792 and 0c74d04.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing

@cmeesters
Copy link
Member Author

please parse the results of the action run, @coderabbitai, https://github.com/snakemake/snakemake-executor-plugin-slurm/actions/runs/13901260630/job/38893362329?pr=241. The test apparently succeeds, the pytest library is in trouble. What is causing this side effect? How to avoid it?

Copy link
Contributor

coderabbitai bot commented Mar 18, 2025

Tip

For best results, initiate chat on the files or code changes.

@cmeesters: One common culprit is a naming or path-shadowing conflict that disrupts how pytest loads. For instance, a file or directory named pytest.py or pytest can overshadow the real library, or environment dependencies may get pinned inconsistently, causing pytest’s imports to fail. Renaming any conflicting files and ensuring your environment consistently uses the correct pytest version often resolves this. If you suspect a path conflict, try restructuring so that nothing in your project is named “pytest” or inadvertently overrides sys.path.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/tests.py (3)

107-114: Validate necessity of patch_sys_streams fixture.
It's useful for capturing or suppressing console output but ensure it’s strictly needed. If these tests pass without it, you might simplify by removing the fixture to reduce complexity. Otherwise, it’s fine as-is.


130-153: Consider consolidating duplicate job fixtures.
This new fixture largely parallels the one in TestGresString. If you plan to reuse them extensively, refactoring both into a shared fixture can help reduce code duplication and ensure consistency.


297-318: Confirm expected behavior for empty qos.
Similarly, sending --qos '' might yield unexpected results with certain SLURM setups. If the intent is truly to set an empty string, the test is correct. Otherwise, consider omitting this flag when the resource is empty.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1aca875 and 404f38b.

📒 Files selected for processing (1)
  • tests/tests.py (3 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
tests/tests.py (1)
snakemake_executor_plugin_slurm/__init__.py (4) (4)
  • Executor (137:761)
  • get_account_arg (611:637)
  • get_partition_arg (639:654)
  • run_job (192:354)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (2)
tests/tests.py (2)

4-5: No concerns on these imports.
They are standard and cohesive with testing needs.


275-296: Confirm expected behavior for empty constraint.
Currently, the test confirms that -C '' is inserted in the sbatch command. Ensure this is truly desired, as an empty constraint might cause unexpected handling in certain cluster configurations.

Could you check your cluster’s SLURM documentation or run a test submission of “sbatch -C '' …” to confirm that it yields the intended result?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
tests/tests.py (2)

4-5: Remove unused import.

The sys module is imported but not used anywhere in the code, as confirmed by the static analyzer.

from unittest.mock import MagicMock, patch
-import sys
import pytest
🧰 Tools
🪛 Ruff (0.8.2)

5-5: sys imported but unused

Remove unused import: sys

(F401)


122-145: Consider refactoring duplicate fixtures.

The mock_job fixture here is very similar to the one in TestGresString. You could consider refactoring this into a shared fixture to reduce code duplication.

# Create a shared fixture at the module level
@pytest.fixture
def base_mock_job():
    """Create a configurable mock job with resources."""
    def _create_job(**resources):
        mock_resources = MagicMock()
        mock_resources.get.side_effect = lambda key, default=None: resources.get(
            key, default
        )
        for key, value in resources.items():
            setattr(mock_resources, key, value)
        
        mock_job = MagicMock()
        mock_job.resources = mock_resources
        return mock_job
    
    return _create_job

# Then update TestGresString and TestSLURMResources to use it
class TestSLURMResources:
    @pytest.fixture
    def mock_job(self, base_mock_job):
        """Create a mock job with SLURM-specific attributes."""
        def _create_job(**resources):
            mock_job = base_mock_job(**resources)
            mock_job.name = "test_job"
            mock_job.wildcards = {}
            mock_job.is_group.return_value = False
            mock_job.jobid = 1
            return mock_job
        
        return _create_job
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9abdadd and 076eb7b.

📒 Files selected for processing (1)
  • tests/tests.py (2 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
tests/tests.py (1)
snakemake_executor_plugin_slurm/__init__.py (4) (4)
  • Executor (137:761)
  • get_account_arg (611:637)
  • get_partition_arg (639:654)
  • run_job (192:354)
🪛 Ruff (0.8.2)
tests/tests.py

5-5: sys imported but unused

Remove unused import: sys

(F401)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (8)
tests/tests.py (8)

116-120: The new test class covers the requirements well.

This test class effectively tests both the new qos resource and the existing constraint resource as requested in the PR comments.


146-170: Well-structured executor fixture.

The mock_executor fixture is comprehensive and properly mocks the necessary components to test the run_job method without making external calls.


171-195: Good test for constraint resource.

The test correctly verifies that the constraint resource is properly added to the sbatch command with the right flag.


196-217: Good test for the new qos resource.

This test verifies the core functionality added by this PR, ensuring that the qos resource is correctly added to the sbatch command with the appropriate flag.


218-240: Good combined test case.

Testing both resources together ensures that they don't interfere with each other and can be used simultaneously, which is an important edge case to cover.


241-266: Good negative test case.

Testing the scenario where no resources are specified ensures that no flags are added unnecessarily, which is important for correctness.


267-288: Good edge case handling for empty constraint.

Testing that an empty constraint is still included in the command is important for ensuring consistent behavior with edge cases.


289-310: Good edge case handling for empty qos.

Similarly, testing that an empty qos is still included in the command ensures consistent behavior with edge cases for the new feature.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/tests.py (1)

121-144: Consider refactoring duplicate mock_job fixture.

This fixture is very similar to the one in TestGresString. Consider extracting this into a common fixture or utility function to reduce code duplication.

- @pytest.fixture
- def mock_job(self):
-     """Create a mock job with configurable resources."""
-
-     def _create_job(**resources):
-         mock_resources = MagicMock()
-         # Configure get method to return values from resources dict
-         mock_resources.get.side_effect = lambda key, default=None: resources.get(
-             key, default
-         )
-         # Add direct attribute access for certain resources
-         for key, value in resources.items():
-             setattr(mock_resources, key, value)
-
-         mock_job = MagicMock()
-         mock_job.resources = mock_resources
-         mock_job.name = "test_job"
-         mock_job.wildcards = {}
-         mock_job.is_group.return_value = False
-         mock_job.jobid = 1
-         return mock_job
-
-     return _create_job

You could create a common fixture in a conftest.py file or extend the existing one:

@pytest.fixture
def slurm_mock_job():
    """Create a mock job with configurable resources for SLURM tests."""

    def _create_job(**resources):
        mock_resources = MagicMock()
        mock_resources.get.side_effect = lambda key, default=None: resources.get(
            key, default
        )
        for key, value in resources.items():
            setattr(mock_resources, key, value)

        mock_job = MagicMock()
        mock_job.resources = mock_resources
        # Add job attributes that are common to all tests
        mock_job.name = "test_job"
        mock_job.wildcards = {}
        mock_job.is_group.return_value = False
        mock_job.jobid = 1
        return mock_job

    return _create_job
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7257642 and 34990d9.

📒 Files selected for processing (1)
  • tests/tests.py (2 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
tests/tests.py (1)
snakemake_executor_plugin_slurm/__init__.py (4) (4)
  • Executor (137:761)
  • get_account_arg (611:637)
  • get_partition_arg (639:654)
  • run_job (192:354)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (10)
tests/tests.py (10)

4-4: Good addition of the patch import for mocking.

The addition of patch from unittest.mock is appropriate for mocking subprocess.Popen in the new tests.


115-120: Well-structured test class for SLURM resources.

The new test class has a clear purpose, good documentation, and appropriately focuses on testing both the new qos resource and existing constraint resource.


145-169: Well-implemented mock_executor fixture.

The fixture properly mocks the necessary components to isolate test execution from external dependencies and provides a clean testing environment for the SLURM executor.


170-194: Test for constraint resource is well-structured.

This test correctly verifies that the constraint resource is properly passed to the sbatch command with the -C flag.


195-216: Test for qos resource is well-implemented.

This test correctly verifies that the qos resource (which is the main feature of this PR) is properly passed to the sbatch command with the --qos flag.


217-239: Good test for combined resource usage.

Testing both resources together ensures they work correctly in combination, which is an important use case.


240-265: Important negative test case.

Testing the absence of flags when no resources are specified is a good practice to ensure the command isn't modified unnecessarily.


1-344: Overall assessment: Well-implemented tests for the new qos resource.

The test suite is comprehensive, covering both the new qos resource and its interaction with the existing constraint resource. The tests follow a consistent pattern, are well-documented, and include important edge cases.

Two areas for consideration:

  1. The commented-out test case should be addressed
  2. Consider refactoring the duplicate mock_job fixture

Otherwise, the implementation is solid and aligns well with the PR objectives.


288-308:

❓ Verification inconclusive

Verify the handling of empty qos values.

Similar to the empty constraint test, this test confirms that empty qos values are included in the command. Consider whether this is the intended behavior or if the flag should be omitted for empty values.

Run the following script to check how SLURM handles empty qos values:


🏁 Script executed:

#!/bin/bash
# Description: Verify how SLURM handles empty qos values

# Test empty qos
echo "Testing empty qos (--qos '')"
sbatch --help | grep -A 2 "\-\-qos" || echo "QOS flag not found in help"

# Try a test submission with empty qos (dry run mode)
echo "Attempting mock submission with empty qos"
echo '#!/bin/bash\necho "Test"' > test_script.sh
chmod +x test_script.sh
sbatch --wrap="echo test" --qos '' --test-only || echo "Empty qos flag may cause issues"
rm test_script.sh

Length of output: 663


Action: Verify the intended behavior for handling empty qos values.

  • The test in tests/tests.py asserts that an empty qos value results in including the flag ("--qos ''") in the sbatch command.
  • Our verification script couldn’t retrieve SLURM behavior due to the unavailability of the sbatch command in the test environment. This means we haven’t confirmed whether SLURM ignores an empty qos flag or might produce issues.
  • Please verify in a proper SLURM environment (or by consulting SLURM documentation) if including an empty qos flag is intended. If SLURM expects the flag to be omitted when empty, consider updating both the code implementation and the test accordingly.

266-287:

❓ Verification inconclusive

Verify the handling of empty constraint values.

The test confirms that empty constraint values are still included in the command. While this is consistent with the implementation, consider whether passing empty strings to SLURM is the intended behavior or if it would be better to skip adding the flag entirely.

Run the following script to check how SLURM handles empty constraint values:


🏁 Script executed:

#!/bin/bash
# Description: Verify how SLURM handles empty constraint values

# Test empty constraint
echo "Testing empty constraint (-C '')"
sbatch --help | grep -A 2 "\-C" || echo "Constraint flag not found in help"

# Try a test submission with empty constraint (dry run mode)
echo "Attempting mock submission with empty constraint"
echo '#!/bin/bash\necho "Test"' > test_script.sh
chmod +x test_script.sh
sbatch --wrap="echo test" -C '' --test-only || echo "Empty constraint flag may cause issues"
rm test_script.sh

Length of output: 706


Review Update: Confirming Intended Behavior for Empty Constraint Values

The current test in tests/tests.py properly confirms that an empty constraint (-C '') is still being passed to the command. However, our additional dry-run script indicated that in our test environment (where sbatch isn’t available) we couldn't validate how SLURM processes such an empty flag. This leaves the question open: Is passing an empty constraint intended, or should the implementation omit the flag when its value is empty?

  • Action Required: Please verify on an actual SLURM system whether including an empty constraint flag causes any unintended issues. If SLURM treats the empty flag undesirably, consider updating the implementation to skip adding the -C flag when the constraint is empty.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (5)
tests/tests.py (5)

54-87: Consider relocating mock_executor fixture to the TestSLURMResources class.

The mock_executor fixture seems to be defined in the TestGresString class but isn't used there. It's only used in the TestSLURMResources class. For better organization and clarity, consider removing this fixture from here and just keep the one defined in TestSLURMResources.


159-182: Reduce code duplication by sharing fixtures between test classes.

The mock_job and mock_executor fixtures in TestSLURMResources are nearly identical to those defined in TestGresString. Consider refactoring these into shared fixtures at the module level to avoid duplication and make future maintenance easier.

-  @pytest.fixture
-  def mock_job(self):
-      """Create a mock job with configurable resources."""
-      
-      def _create_job(**resources):
-          mock_resources = MagicMock()
-          # Configure get method to return values from resources dict
-          mock_resources.get.side_effect = lambda key, default=None: resources.get(
-              key, default
-          )
-          # Add direct attribute access for certain resources
-          for key, value in resources.items():
-              setattr(mock_resources, key, value)
-          
-          mock_job = MagicMock()
-          mock_job.resources = mock_resources
-          mock_job.name = "test_job"
-          mock_job.wildcards = {}
-          mock_job.is_group.return_value = False
-          mock_job.jobid = 1
-          return mock_job
-      
-      return _create_job
-  
-  @pytest.fixture
-  def mock_executor(self):
-      """Create a mock executor for testing the run_job method."""
-      from snakemake_executor_plugin_slurm import Executor
-      
-      # Create a mock workflow
-      mock_workflow = MagicMock()
-      mock_settings = MagicMock()
-      mock_settings.requeue = False
-      mock_settings.no_account = True
-      mock_settings.logdir = None
-      mock_workflow.executor_settings = mock_settings
-      mock_workflow.workdir_init = "/test/workdir"
-      
-      # Create an executor with the mock workflow
-      executor = Executor(mock_workflow, None)
-      
-      # Mock some executor methods to avoid external calls
-      executor.get_account_arg = MagicMock(return_value="")
-      executor.get_partition_arg = MagicMock(return_value="")
-      executor.report_job_submission = MagicMock()
-      
-      # Return the mocked executor
-      return executor

Instead, use the existing fixtures from the TestGresString class:

# TestSLURMResources can just use the fixtures from TestGresString

Also applies to: 183-207


217-232: Simplify test boilerplate by extracting common mock setup patterns.

Each test method contains nearly identical boilerplate code for setting up the subprocess.Popen mock and extracting the command arguments. This could be refactored into a helper method or fixture to reduce duplication and make the tests more concise and maintainable.

@pytest.fixture
def mock_subprocess_popen(self):
    """Create a mock for subprocess.Popen that succeeds and returns job ID 123."""
    with patch("subprocess.Popen") as mock_popen:
        # Configure the mock to return successful submission
        process_mock = MagicMock()
        process_mock.communicate.return_value = ("123", "")
        process_mock.returncode = 0
        mock_popen.return_value = process_mock
        
        yield mock_popen

def _assert_command_contains(self, command, expected_parts):
    """Helper to assert that a command contains all expected parts."""
    for part in expected_parts:
        assert part in command

def _run_job_and_get_command(self, mock_popen, mock_executor, job):
    """Run a job and extract the command args."""
    mock_executor.run_job(job)
    return mock_popen.call_args[0][0]

Then use these in your test methods to simplify them:

def test_constraint_resource(self, mock_job, mock_executor, mock_subprocess_popen):
    """Test that the constraint resource is correctly added to the sbatch command."""
    job = mock_job(constraint="haswell")
    command = self._run_job_and_get_command(mock_subprocess_popen, mock_executor, job)
    self._assert_command_contains(command, ["-C 'haswell'"])

Also applies to: 239-254, 261-277, 287-303, 310-325, 332-347


220-220: Use byte strings for communicate() return values for consistency.

In the mock setup, process_mock.communicate.return_value is set to string tuples like ("123", "") instead of byte tuples like (b"123", b""). This contrasts with the TestGresString.mock_executor fixture which correctly uses byte tuples. While Python will implicitly convert these in many cases, it's better to be consistent with the actual behavior of subprocess.Popen.communicate() which returns bytes.

- process_mock.communicate.return_value = ("123", "")
+ process_mock.communicate.return_value = (b"123", b"")

Also applies to: 242-242, 264-264, 290-290, 313-313, 335-335


222-222: Add stdout and stderr attributes to process_mock for full compatibility.

In the TestGresString.mock_executor fixture, process_mock has stdout and stderr attributes set, but these are missing in the TestSLURMResources test methods. While they might not be used in the current tests, adding them would make the mocks more complete and consistent.

 process_mock.communicate.return_value = ("123", "")
 process_mock.returncode = 0
+process_mock.stdout = MagicMock()
+process_mock.stderr = MagicMock()
 mock_popen.return_value = process_mock

Also applies to: 244-244, 266-266, 292-292, 315-315, 337-337

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24d2e2d and cb1d178.

📒 Files selected for processing (1)
  • tests/tests.py (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (1)
tests/tests.py (1)

153-347: Well-structured test implementation for QoS and constraint resources.

The test implementation is comprehensive and follows good testing practices:

  • Each test case targets a specific functionality
  • Proper use of mocking to isolate the tested components
  • Good coverage of different scenarios (single resource, combined resources, empty values)
  • Thorough assertions to verify the command arguments

@cmeesters cmeesters requested a review from fgvieira March 28, 2025 13:56
@cmeesters
Copy link
Member Author

@coderabbitai - the previous tests with your mock_executor all failed, because a) the Executor class needed to submit jobs, but the sbatch command was not extractable and b) some bug in pytest caused to read(!) from stdout, which ought not to be possible and - of course - always failed. Please withdraw your earlier suggestion to create test suite.

Copy link
Contributor

coderabbitai bot commented Mar 28, 2025

Tip

For best results, initiate chat on the files or code changes.

@cmeesters I understand the issues you've encountered with my suggested test suite. The approach I proposed was fundamentally flawed due to the inability to extract the sbatch command and the unexpected pytest behavior with stdout. I hereby withdraw my earlier suggestion for creating the test suite. I apologize for the inconvenience and thank you for providing this feedback.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
snakemake_executor_plugin_slurm/submit_string.py (3)

33-37: Unusual conditional logic for constraint resource.

The condition job.resources.get("constraint") or isinstance(job.resources.get("constraint"), str) will evaluate to True even for empty strings. This appears to be intentional based on the test cases, but the logic could be simplified.

Consider simplifying the conditional logic:

-if job.resources.get("constraint") or isinstance(
-    job.resources.get("constraint"), str
-):
+if job.resources.get("constraint") is not None:
    call += f" -C '{job.resources.get('constraint')}'"

38-39: Similar unusual conditional logic for qos resource.

The same unusual conditional pattern is used for the qos resource as for the constraint resource.

Consider simplifying this condition as well:

-if job.resources.get("qos") or isinstance(job.resources.get("qos"), str):
+if job.resources.get("qos") is not None:
    call += f" --qos='{job.resources.get('qos')}'"

5-7: Function docstring could be more descriptive.

The current docstring is minimal. Adding more details about the parameters and return value would improve code documentation.

Consider expanding the docstring:

def get_submit_command(job, params):
    """
-   Return the submit command for the job.
+   Return the sbatch command string for submitting a job to SLURM.
+
+   Parameters:
+   -----------
+   job : object
+       The job object containing resources and requirements.
+   params : dict
+       Dictionary with submission parameters like run_uuid, slurm_logfile, 
+       comment_str, account, partition, and workdir.
+
+   Returns:
+   --------
+   str
+       The complete sbatch command string.
    """
tests/tests.py (2)

226-248: Code duplication in test fixtures.

The mock_job fixture in TestSLURMResources duplicates the one in TestGresString. This violates the DRY principle.

Consider moving this fixture to a common base class or utility function to avoid duplication.

# Example of a shared fixture at module level
@pytest.fixture
def create_mock_job():
    """Create a mock job with configurable resources."""
    
    def _create_job(**resources):
        mock_resources = MagicMock()
        mock_resources.get.side_effect = lambda key, default=None: resources.get(
            key, default
        )
        for key, value in resources.items():
            setattr(mock_resources, key, value)

        mock_job = MagicMock()
        mock_job.resources = mock_resources
        mock_job.name = "test_job"
        mock_job.wildcards = {}
        mock_job.is_group.return_value = False
        mock_job.jobid = 1
        return mock_job

    return _create_job

324-327: Tests have unnecessary mocking.

The mocks for subprocess.Popen are set up but not used in the test execution. In this test, you're only checking the command string returned by get_submit_command, not actually executing any subprocess calls.

Remove the unnecessary mocking to simplify the test:

- # Patch subprocess.Popen to capture the sbatch command
- with patch("subprocess.Popen") as mock_popen:
-     # Configure the mock to return successful submission
-     process_mock = MagicMock()
-     process_mock.communicate.return_value = ("123", "")
-     process_mock.returncode = 0
-     mock_popen.return_value = process_mock
-
-     # Assert both resources are correctly included
-     sbatch_command = get_submit_command(job, params)
-     assert " --qos='high'" in sbatch_command
-     assert " -C 'haswell'" in sbatch_command
+ # Assert both resources are correctly included
+ sbatch_command = get_submit_command(job, params)
+ assert " --qos='high'" in sbatch_command
+ assert " -C 'haswell'" in sbatch_command

This applies to all other test methods in TestSLURMResources as well.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08e702b and d69631c.

📒 Files selected for processing (3)
  • snakemake_executor_plugin_slurm/__init__.py (3 hunks)
  • snakemake_executor_plugin_slurm/submit_string.py (1 hunks)
  • tests/tests.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • snakemake_executor_plugin_slurm/init.py
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (8)
tests/tests.py (8)

285-294: Similar unused parameters in test setup.

Same issue as in the previous test - the qos key in params appears to be unused.


312-313: Unnecessary parameters in test setup.

Similar to previous comments, constraint and qos keys in params appear to be unused.


255-275: LGTM! Comprehensive test coverage for the constraint resource.

The test correctly verifies that the constraint resource is properly added to the sbatch command with the correct format.


277-299: LGTM! Comprehensive test coverage for the qos resource.

The test correctly verifies that the qos resource is properly added to the sbatch command with the correct format.


358-381: LGTM! Good edge case testing for empty constraint values.

The test correctly verifies that an empty constraint string is still included in the command, which is important for correct SLURM behavior.


383-405: LGTM! Good edge case testing for empty qos values.

The test correctly verifies that an empty qos string is still included in the command.


329-357: LGTM! Proper negative test case for when no resources are specified.

The test correctly verifies that no constraint or qos flags are added when these resources aren't specified.


219-224: LGTM! Well-structured test class with clear purpose.

The new TestSLURMResources class is well documented and has a clear purpose of testing the job resources passed to the SLURM executor.

cmeesters and others added 2 commits March 28, 2025 15:09
Co-authored-by: Filipe G. Vieira <1151762+fgvieira@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
snakemake_executor_plugin_slurm/submit_string.py (1)

4-7: Consider adding docstring details for parameters and return value.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d69631c and 553eaf7.

📒 Files selected for processing (1)
  • snakemake_executor_plugin_slurm/submit_string.py (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testing
🔇 Additional comments (14)
snakemake_executor_plugin_slurm/submit_string.py (14)

1-2: No issues found with the import statement.


8-16: Multi-line string construction for the base sbatch call is clear and concise.


24-25: Usage of --clusters here appears correct for specifying clusters.


27-28: Specifying -t for runtime is correctly aligned with SLURM usage.


30-31: Duplicated --clusters code.

Removing the second instance ensures --clusters is not added twice:

-    if job.resources.get("clusters"):
-        call += f" --clusters {job.resources.clusters}"

33-37: Constraint parameter with -C is properly formed for SLURM.


38-39: --qos parameter is correctly implemented.


40-43: Memory specification logic aligns with SLURM's --mem or --mem-per-cpu options.


45-46: Node count usage with --nodes is correct.


50-55: GPU-based logic for tasks per GPU versus tasks is suitably handled.


56-60: Appending CPU settings and extra SLURM arguments is well-structured.


62-65: Work directory setting with -D is standard SLURM practice.


67-67: Return statement is straightforward and finalizes the submission command.


68-68: No code present on this line.

cmeesters and others added 5 commits March 28, 2025 15:19
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Filipe G. Vieira <1151762+fgvieira@users.noreply.github.com>
@cmeesters
Copy link
Member Author

cmeesters commented Mar 28, 2025

@fgvieira now, please take a look ;-).

PS the tinkering after coderabbit's remarks left a mark on this PR, sorry for that. Actually, only the latest bits do matter. A small change in code, triggered quite some refactoring, here.

@cmeesters cmeesters merged commit c8cdfc4 into main Mar 31, 2025
5 checks passed
@cmeesters cmeesters deleted the feat/add-qos-flag branch March 31, 2025 09:41
cmeesters pushed a commit that referenced this pull request Apr 9, 2025
🤖 I have created a release *beep* *boop*
---


##
[1.2.0](v1.1.0...v1.2.0)
(2025-04-04)


### Features

* added new 'qos' resource
([#241](#241))
([c8cdfc4](c8cdfc4))


### Bug Fixes

* account and partition settings corrected
([#249](#249))
([e7a248f](e7a248f))
* case insensitive matching of declared slurm account
([#244](#244))
([dc9a4fd](dc9a4fd))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
  - Introduced a new QoS resource.

- **Bug Fixes**
  - Resolved issues with account and partition settings.
  - Improved case insensitive matching for account names.

- **Chores**
  - Updated the package version to 1.2.0.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants