Skip to content

Commit 0911de6

Browse files
committed
Add tests for Submitter, simplify code, add types and tests.
1 parent 36b1ff3 commit 0911de6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2564
-607
lines changed

.coveragerc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[run]
55
branch = True
66
cover_pylib = False
7-
concurrency = multiprocessing
7+
concurrency = thread,multiprocessing
88
data_file = .coverage
99
disable_warnings =
1010
trace-changed

.github/workflows/ci.yaml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ permissions:
1515

1616
jobs:
1717
test-unit:
18+
env:
19+
COVERAGE_PROCESS_START: .coveragerc
1820
# needs: lint
1921
runs-on: ubuntu-latest
2022
timeout-minutes: 10
@@ -52,8 +54,8 @@ jobs:
5254
5355
- name: Coverage report
5456
run: |
55-
coverage xml
5657
coverage report
58+
coverage xml
5759
5860
- name: Upload coverage artifact
5961
uses: actions/upload-artifact@v4
@@ -63,6 +65,8 @@ jobs:
6365
retention-days: 7
6466

6567
test-integration:
68+
env:
69+
COVERAGE_PROCESS_START: .coveragerc
6670
runs-on: ubuntu-latest
6771
timeout-minutes: 10
6872

@@ -92,7 +96,7 @@ jobs:
9296
cache: 'pip' # caching pip dependencies
9397

9498
- name: Install system dependencies
95-
run: sudo apt-get install -y curl git graphviz rsync
99+
run: sudo apt-get install -y curl git graphviz rsync xvfb
96100

97101
- name: Install dependencies
98102
run: |
@@ -112,6 +116,9 @@ jobs:
112116
# CONTRIBUTING.md file for details how to set up your environment to run these.
113117
- name: Integration tests
114118
run: |
119+
Xvfb :99 -screen 0 1024x768x24 -ac &
120+
export DISPLAY=:99
121+
115122
pytest \
116123
--cov=autosubmit --cov-config=.coveragerc \
117124
--cov-report=xml:test/coverage.xml --cov-append \
@@ -121,8 +128,8 @@ jobs:
121128
122129
- name: Coverage report
123130
run: |
124-
coverage xml
125131
coverage report
132+
coverage xml
126133
127134
- name: Upload coverage artifact
128135
uses: actions/upload-artifact@v4
@@ -167,8 +174,8 @@ jobs:
167174
168175
- name: Coverage report
169176
run: |
170-
coverage xml
171177
coverage report
178+
coverage xml
172179
173180
- name: Upload coverage artifact
174181
uses: actions/upload-artifact@v4
@@ -220,8 +227,8 @@ jobs:
220227
221228
- name: Coverage report
222229
run: |
223-
coverage xml
224230
coverage report
231+
coverage xml
225232
226233
- name: Upload coverage artifact
227234
uses: actions/upload-artifact@v4

.github/workflows/lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131

3232
mypy-changes-in-file:
3333
runs-on: ubuntu-latest
34+
continue-on-error: true
3435
name: mypy run
3536
steps:
3637
- uses: actions/checkout@v5

autosubmit/history/platform_monitor/platform_utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def parse_output_number(string_number):
4949
number = float(number) * multiplier
5050
except Exception:
5151
number = 0.0
52-
pass
5352
return number
5453

5554
def try_parse_time_to_timestamp(input_):

autosubmit/history/strategies.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ def execute_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monit
4040

4141

4242
class Strategy(metaclass=ABCMeta):
43-
""" Strategy Interface """
43+
"""Strategy Interface"""
4444

4545
def __init__(self, historiclog_dir_path=DEFAULT_HISTORICAL_LOGS_DIR):
4646
self.historiclog_dir_path = historiclog_dir_path
4747

4848
@abstractmethod
4949
def apply_distribution(self, job_data_dc, job_data_dcs_in_wrapper, slurm_monitor):
50-
pass
50+
pass # pragma: no cover
5151

5252
def set_job_data_dc_as_processed(self, job_data_dc, original_ssh_output):
5353
job_data_dc.platform_output = original_ssh_output

autosubmit/job/job_list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1758,7 +1758,7 @@ def get_date_format(self):
17581758
return date_format
17591759

17601760
def copy_ordered_jobs_by_date_member(self):
1761-
pass
1761+
pass # pragma: no cover
17621762

17631763
def get_ordered_jobs_by_date_member(self, section):
17641764
"""

autosubmit/job/job_list_persistence.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def save(self, persistence_path, persistence_file, job_list, graph):
5050
:param persistence_path: str
5151
:param graph: DiGraph
5252
"""
53-
raise NotImplementedError
53+
raise NotImplementedError # pragma: no cover
5454

5555
def load(self, persistence_path, persistence_file):
5656
"""
@@ -59,7 +59,7 @@ def load(self, persistence_path, persistence_file):
5959
:param persistence_path: str
6060
6161
"""
62-
raise NotImplementedError
62+
raise NotImplementedError # pragma: no cover
6363

6464
def pkl_exists(self, persistence_path, persistence_file):
6565
"""

autosubmit/job/job_packager.py

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from contextlib import suppress
2121
from math import ceil
2222
from operator import attrgetter
23-
from typing import List
23+
from typing import List, TYPE_CHECKING
2424

2525
from bscearth.utils.date import sum_str_hours
2626

@@ -31,6 +31,11 @@
3131
from autosubmit.job.template import Language
3232
from autosubmit.log.log import Log, AutosubmitCritical
3333

34+
if TYPE_CHECKING:
35+
from autosubmit.config.configcommon import AutosubmitConfig
36+
from autosubmit.job.job_list import JobList
37+
from autosubmit.platforms.paramiko_platform import ParamikoPlatform
38+
3439

3540
class JobPackager(object):
3641
"""
@@ -44,8 +49,7 @@ class JobPackager(object):
4449
:type jobs_list: JobList object.
4550
"""
4651

47-
48-
def __init__(self, as_config, platform, jobs_list, hold=False):
52+
def __init__(self, as_config: 'AutosubmitConfig', platform: 'ParamikoPlatform', jobs_list: 'JobList', hold=False):
4953
self.current_wrapper_section = "WRAPPERS"
5054
self._as_config = as_config
5155
self._platform = platform
@@ -65,9 +69,8 @@ def __init__(self, as_config, platform, jobs_list, hold=False):
6569
self.special_variables = dict()
6670
self.wrappers_with_error = {}
6771

68-
69-
#todo add default values
70-
#Wrapper building starts here
72+
# TODO: Add default values
73+
# Wrapper building starts here
7174
for wrapper_section,wrapper_data in self._as_config.experiment_data.get("WRAPPERS",{}).items():
7275
if isinstance(wrapper_data,collections.abc.Mapping ):
7376
self.wrapper_type[wrapper_section] = self._as_config.get_wrapper_type(wrapper_data)
@@ -127,7 +130,6 @@ def compute_weight(self, job_list):
127130
job.distance_weight = job.distance_weight - 1
128131

129132
def calculate_wrapper_bounds(self, section_list):
130-
131133
"""
132134
Returns the minimum and maximum number of jobs that can be wrapped
133135
@@ -433,56 +435,62 @@ def error_message_policy(self, min_h: int, min_v: int, wrapper_limits: dict, bal
433435
message += "\nThis message is activated when only jobs_in_wrappers are in active(Ready+) status.\n"
434436
return message
435437

436-
def check_if_packages_are_ready_to_build(self):
437-
"""
438-
Check if the packages are ready to be built
439-
:return: List of jobs ready to be built, boolean indicating if packages can't be built for other reasons ( max_total_jobs...)
438+
def check_if_packages_are_ready_to_build(self) -> tuple[list[Job], bool]:
439+
"""Check if the packages are ready to be built.
440+
441+
Returns a tuple with two elements. First contains the list of jobs ready to be built.
442+
The second element in the tuple is a boolean indicating if it can be built or not.
443+
444+
:return: list of jobs ready to be built, boolean indicating if there are underlying blocking errors.
440445
"""
441446
Log.info("Calculating possible ready jobs for {0}".format(self._platform.name))
442-
jobs_ready = list()
447+
jobs_ready = []
443448
if len(self._jobs_list.jobs_to_run_first) > 0:
444-
jobs_ready = [job for job in self._jobs_list.jobs_to_run_first if
445-
( self._platform is None or job.platform.name.upper() == self._platform.name.upper()) and
446-
job.status == Status.READY]
447-
if len(jobs_ready) == 0:
449+
jobs_ready = [
450+
job
451+
for job in self._jobs_list.jobs_to_run_first
452+
if (self._platform is None or job.platform.name.upper() == self._platform.name.upper())
453+
and job.status == Status.READY
454+
]
455+
if not jobs_ready:
448456
if self.hold:
449457
jobs_ready = self._jobs_list.get_prepared(self._platform)
450458
else:
451459
jobs_ready = self._jobs_list.get_ready(self._platform)
452460

453-
if self.hold and len(jobs_ready) > 0:
461+
if self.hold and jobs_ready:
454462
self.compute_weight(jobs_ready)
455-
sorted_jobs = sorted(
456-
jobs_ready, key=operator.attrgetter('distance_weight'))
457-
jobs_in_held_status = self._jobs_list.get_held_jobs() + self._jobs_list.get_submitted(self._platform, hold=self.hold)
463+
sorted_jobs = sorted(jobs_ready, key=operator.attrgetter('distance_weight'))
464+
jobs_in_held_status = self._jobs_list.get_held_jobs() + self._jobs_list.get_submitted(
465+
self._platform, hold=self.hold)
458466
held_by_id = dict()
459467
for held_job in jobs_in_held_status:
460468
if held_job.id not in held_by_id:
461469
held_by_id[held_job.id] = []
462470
held_by_id[held_job.id].append(held_job)
463471
current_held_jobs = len(list(held_by_id.keys()))
464472
remaining_held_slots = 5 - current_held_jobs
465-
Log.debug("there are currently {0} held jobs".format(remaining_held_slots))
466-
try:
473+
Log.debug(f"There are currently {remaining_held_slots} held jobs")
474+
with suppress(IndexError):
467475
while len(sorted_jobs) > remaining_held_slots:
468476
del sorted_jobs[-1]
469477
for job in sorted_jobs:
470478
if job.distance_weight > 3:
471479
sorted_jobs.remove(job)
472480
jobs_ready = sorted_jobs
473-
pass
474-
except IndexError:
475-
pass
476-
if len(jobs_ready) == 0:
477-
# If there are no jobs ready, result is tuple of empty
478-
return jobs_ready,False
479-
#check if there are jobs listed on calculate_job_limits
481+
482+
# If there are no jobs ready, result is tuple of empty
483+
if not jobs_ready:
484+
return jobs_ready, False
485+
486+
# Check if there are jobs listed on calculate_job_limits
480487
self.calculate_job_limits(self._platform)
488+
# If there is no more space in platform, result is tuple of empty
481489
if not (self._max_wait_jobs_to_submit > 0 and self._max_jobs_to_submit > 0):
482-
# If there is no more space in platform, result is tuple of empty
483-
Log.debug('Max jobs to submit reached, waiting for more space in platform {0}'.format(self._platform.name))
484-
return jobs_ready,False
485-
return jobs_ready,True
490+
Log.debug(f'Max jobs to submit reached, waiting for more space in platform {self._platform.name}')
491+
return jobs_ready, False
492+
493+
return jobs_ready, True
486494

487495
def calculate_job_limits(self,platform,job=None):
488496
jobs_list = self._jobs_list
@@ -683,7 +691,6 @@ def _divide_list_by_section(self, jobs_list):
683691
del jobs_by_section[wrappers]
684692
return jobs_by_section
685693

686-
687694
def _build_horizontal_packages(self, section_list, wrapper_limits, section, wrapper_info={}):
688695
packages = []
689696
horizontal_packager = JobPackagerHorizontal(section_list, self._platform.max_processors, wrapper_limits,
@@ -731,7 +738,7 @@ def _build_vertical_packages(self, section_list, wrapper_limits,wrapper_info={})
731738
return packages
732739

733740
def _build_hybrid_package(self, jobs_list, wrapper_limits, section,wrapper_info={}):
734-
#self.wrapper_info = wrapper_info
741+
# self.wrapper_info = wrapper_info
735742
jobs_resources = dict()
736743
jobs_resources['MACHINEFILES'] = self._as_config.get_wrapper_machinefiles()
737744

@@ -802,7 +809,9 @@ def _build_vertical_horizontal_package(self, horizontal_packager, jobs_resources
802809
return JobPackageVerticalHorizontal(current_package, total_processors, total_wallclock,
803810
jobs_resources=jobs_resources, method=self.wrapper_method[self.current_wrapper_section], configuration=self._as_config, wrapper_section=self.current_wrapper_section )
804811

805-
#TODO rename and unite JobPackerVerticalMixed to JobPackerVertical since the difference between the two is not needed anymore
812+
813+
# TODO: Rename and unite JobPackerVerticalMixed to JobPackerVertical since
814+
# the difference between the two is not needed anymore
806815
class JobPackagerVertical(object):
807816
"""
808817
Vertical Packager Parent Class
@@ -867,7 +876,7 @@ def build_vertical_package(self, job, wrapper_info):
867876
return self.jobs_list
868877

869878
def get_wrappable_child(self, job):
870-
pass
879+
pass # pragma: no cover
871880

872881
def _is_wrappable(self, job):
873882
"""
@@ -888,6 +897,7 @@ def _is_wrappable(self, job):
888897
return True
889898
return False
890899

900+
891901
class JobPackagerVerticalMixed(JobPackagerVertical):
892902
"""
893903
Vertical Mixed Class. First statement of the constructor builds JobPackagerVertical.
@@ -926,7 +936,6 @@ def __init__(self, dict_jobs, ready_job, jobs_list, total_wallclock, max_jobs, w
926936
self.sorted_jobs = dict_jobs[date][member]
927937
self.index = 0
928938

929-
930939
def get_wrappable_child(self, job: Job) -> Job:
931940
"""
932941
Goes through the jobs with the same date and member as the input job, and returns the first that satisfies self._is_wrappable().
@@ -1113,7 +1122,8 @@ def components_dict(self):
11131122
def create_components_dict(self):
11141123
self._sectionList = []
11151124
# it was job.parameters
1116-
parameters = {} # TODO machinefiles, can wait nobody is using it and I really think this was not working before anyway
1125+
# TODO machinefiles, can wait nobody is using it and I really think this was not working before anyway
1126+
parameters = {}
11171127
for job in self.job_list:
11181128
if job.section not in self._sectionList:
11191129
self._sectionList.append(job.section)

autosubmit/job/job_packages.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def _create_scripts_threaded(self, jobs: list[Job], configuration: 'AutosubmitCo
134134
self._job_scripts[jobs[i].name] = jobs[i].create_script(configuration)
135135

136136
def _create_common_script(self, filename: str = ""):
137-
pass
137+
pass # pragma: no cover
138138

139139
def submit_unthreaded(self, configuration: 'AutosubmitConfig', only_generate: bool = False):
140140
"""
@@ -225,7 +225,7 @@ def _send_files(self):
225225

226226
def _do_submission(self, job_scripts=None, hold: bool = False):
227227
""" Submit package to the platform. """
228-
pass
228+
pass # pragma: no cover
229229

230230
def process_jobs_to_submit(self, job_id: str, hold: bool = False) -> None:
231231
for i, job in enumerate(self.jobs):
@@ -664,7 +664,7 @@ def _do_submission(self, job_scripts: dict[str, str] = None, hold: bool = False)
664664
self.jobs[i].wrapper_name = self.name
665665

666666
def _common_script_content(self) -> str:
667-
pass
667+
pass # pragma: no cover
668668

669669

670670
class JobPackageThreadWrapped(JobPackageThread):

autosubmit/log/log.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class Log:
177177
log.addHandler(console_handler)
178178

179179
def __init__(self):
180-
pass
180+
pass # pragma: no cover
181181

182182
def init_variables(self, file_path="") -> None:
183183
self.file_path = file_path

0 commit comments

Comments
 (0)