Skip to content

Commit

Permalink
Adds some simple checks to slurm code blocks.
Browse files Browse the repository at this point in the history
  • Loading branch information
CallumWalley committed Dec 1, 2024
1 parent 8eea9b8 commit ad1c116
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 13 deletions.
21 changes: 21 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ on:
description: Check Meta
default: true
type: boolean
checkSlurm:
description: Check Slurm
default: true
type: boolean
testBuild:
description: Test Build
default: true
Expand Down Expand Up @@ -135,6 +139,23 @@ jobs:
run: |
shopt -s globstar extglob
python3 checks/run_meta_check.py ${{needs.get.outputs.filelist}}
slurmcheck:
name: Check slurm scripts
if: ${{github.event_name != 'workflow_dispatch'|| inputs.checkSlurm}}
runs-on: ubuntu-22.04
needs: get
steps:
- if: ${{ ! needs.get.outputs.filelist}}
name: No files to check meta on.
run: exit 0
- if: ${{needs.get.outputs.filelist}}
name: Check out repo.
uses: actions/checkout@v4
- if: ${{needs.get.outputs.filelist}}
name: Check markdown meta.
run: |
shopt -s globstar extglob
python3 checks/run_slurm_lint.py ${{needs.get.outputs.filelist}}
testBuild:
name: Test build
if: ${{github.event_name != 'workflow_dispatch' || inputs.testBuild}}
Expand Down
62 changes: 49 additions & 13 deletions checks/run_slurm_lint.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3

"""
Runs checks on article meta block and outputs in github action readable format
Runs checks on slurm scrips found in code and outputs in github action readable format
"""

__author__ = "cal w"
Expand All @@ -15,11 +15,13 @@
LINES_AFTER_SHEBANG = 1
LINES_AFTER_HEADER = 1
SHEBANG = r"^#!\/bin\/bash -e\s*$"
SBATCH_HEADER = r"^#SBATCH\s*(-[\S]*)([\s=]*)(\S*.*?)(#.*)?$"
SBATCH_HEADER = r"^(.*)#SBATCH\s*(-[^=\s]*)([\s=]*)(\S*.*?)(#.*)?$"
SBATCH_DELIM = r"=|\s+"

REQUIRED_SBATCH_HEADER = [
{"long": "--job-name", "short": "-j"}
{"long": "--job-name", "short": "-j"},
{"long": "--account", "short": "-a"},
{"long": "--time", "short": "-t"},
]


Expand Down Expand Up @@ -48,16 +50,25 @@ def main():

def parse_script(start_linno, indent, slurm):
global step, n_lines_after_shebang, n_lines_after_header, line, uses_equals_delim, uses_whitespace_delim
global match_header_line
global match_header_line, start_of_header, slurm_headers

uses_equals_delim = False
uses_whitespace_delim = False

def _run_check(f):
for r in f():
print(f"::{r.get('level', 'warning')} file={input_path},title={f.__name__},col={r.get('col', 0) + indent},endColumn={r.get('endColumn', 99) + indent},line={start_linno + lineno}::{r.get('message', 'something wrong')}")
print(f"::{r.get('level', 'warning')} file={input_path},title={f.__name__}," +
f"col={r.get('col', 0) + indent},endColumn={r.get('endColumn', 99) + indent}," +
f"line={start_linno + r.get('line', lineno)}::{r.get('message', 'something wrong')}")
sys.stdout.flush()
time.sleep(0.01)

n_lines_after_shebang = 0
n_lines_after_header = 0
slurm_headers = []

start_of_header = 0
end_of_header = 0
step = 0 # 0 : shebang
# 1 : slurm header
# 2 : bash
Expand All @@ -72,26 +83,31 @@ def _run_check(f):
elif re.match(SBATCH_HEADER, line):
_run_check(lines_after_shebang)
step = 1
start_of_header = lineno
else:
_run_check(content_before_slurm_header)
if step == 1:
if re.match(r"^\s*$", line):
n_lines_after_header += 1
continue
match_header_line = re.match(SBATCH_HEADER, line)

slurm_headers.append(match_header_line)
if match_header_line:
for check in SBATCH_HEADER_CHECKS:
for check in SBATCH_HEADER_WALK:
_run_check(check)
else:
step = 2
end_of_header = line
for check in SBATCH_HEADER_ALL:
_run_check(check)
# else:

# elif :


def finditer2(pattern, string, flags):
"""
A version of ``re.finditer`` that returns ``(match, line_number)`` pairs.
"""
"""
matches = list(re.finditer(pattern, string, flags))
if matches:
end = matches[-1].start()
Expand All @@ -115,16 +131,18 @@ def slurm_shebang():
if not re.match(SHEBANG, line):
yield {"message": f"Your shebang was '{line}', should use '{SHEBANG}'"}


def lines_after_shebang():
if n_lines_after_shebang != LINES_AFTER_SHEBANG:
yield {"level": "notice", "message": f"There are {n_lines_after_shebang} blank lines after the shebang, should be {LINES_AFTER_SHEBANG}."}


def content_before_slurm_header():
yield {"level": "error", "message": f"There is text ('{line}') between the shebang and slurm header. This is not a valid SLURM script."}


def malformed_delimiter():
delim = match_header_line.group(2)
delim = match_header_line.group(3)
if delim == "=":
uses_equals_delim = True
elif delim.isspace():
Expand All @@ -133,14 +151,32 @@ def malformed_delimiter():
yield {"level": "error", "message": f"'{delim}' is not a valid SLURM header delimiter."}


def inconsistant_delimiter():
if uses_equals_delim and uses_whitespace_delim:
yield {"message": "Header uses both whitespace and '=' delimiters.", "line": start_of_header}


def short_option():
if not match_header_line.group(1)[:2] == "--":
yield {"level": "info", "col": 8, "endColumn": 8 + len(match_header_line.group(1)), "message": f"Using short form flag '{match_header_line.group(1)}'. Long form is prefered."}
if not match_header_line.group(2)[:2] == "--":
yield {"level": "info", "col": 8, "endColumn": 8 + len(match_header_line.group(2)),
"message": f"Using short form flag '{match_header_line.group(2)}'. Long form is prefered."}


def minimum_options():
for header in REQUIRED_SBATCH_HEADER:
for h in slurm_headers:
if not h:
continue
a = h.group(2)
if a == header["long"] or a == header["short"]:
break
else:
yield {"message": f"Script header must contain \'{header['long']}\'.",
"line": start_of_header}

SBATCH_HEADER_CHECKS = [malformed_delimiter, short_option]

SBATCH_HEADER_WALK = [malformed_delimiter, short_option]
SBATCH_HEADER_ALL = [inconsistant_delimiter, minimum_options]

if __name__ == "__main__":
main()
Expand Down

0 comments on commit ad1c116

Please sign in to comment.