DIRAC 9.0.0a66__py3-none-any.whl → 9.0.0a67__py3-none-any.whl

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.
Files changed (36) hide show
  1. DIRAC/ConfigurationSystem/Client/VOMS2CSSynchronizer.py +1 -1
  2. DIRAC/Core/Security/IAMService.py +4 -3
  3. DIRAC/Core/Utilities/ClassAd/ClassAdLight.py +4 -290
  4. DIRAC/Core/Utilities/DErrno.py +1 -1
  5. DIRAC/Core/Utilities/JDL.py +1 -195
  6. DIRAC/Core/Utilities/List.py +1 -127
  7. DIRAC/Core/Utilities/ReturnValues.py +2 -2
  8. DIRAC/Core/Utilities/StateMachine.py +12 -178
  9. DIRAC/Core/Utilities/TimeUtilities.py +10 -253
  10. DIRAC/Core/Utilities/test/Test_JDL.py +0 -3
  11. DIRAC/DataManagementSystem/DB/FTS3DB.py +3 -0
  12. DIRAC/RequestManagementSystem/DB/test/RMSTestScenari.py +2 -0
  13. DIRAC/Resources/Catalog/RucioFileCatalogClient.py +1 -1
  14. DIRAC/Resources/Computing/test/Test_PoolComputingElement.py +2 -1
  15. DIRAC/Workflow/Modules/test/Test_Modules.py +5 -0
  16. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_JobAgent.py +2 -0
  17. DIRAC/WorkloadManagementSystem/Agent/test/Test_Agent_PushJobAgent.py +1 -0
  18. DIRAC/WorkloadManagementSystem/Client/JobState/JobManifest.py +32 -261
  19. DIRAC/WorkloadManagementSystem/Client/JobStatus.py +8 -93
  20. DIRAC/WorkloadManagementSystem/DB/JobDBUtils.py +18 -147
  21. DIRAC/WorkloadManagementSystem/JobWrapper/JobWrapper.py +4 -2
  22. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapper.py +21 -5
  23. DIRAC/WorkloadManagementSystem/JobWrapper/test/Test_JobWrapperTemplate.py +4 -0
  24. DIRAC/WorkloadManagementSystem/Utilities/JobModel.py +28 -199
  25. DIRAC/WorkloadManagementSystem/Utilities/JobStatusUtility.py +1 -63
  26. DIRAC/WorkloadManagementSystem/Utilities/ParametricJob.py +7 -171
  27. DIRAC/WorkloadManagementSystem/Utilities/test/Test_JobModel.py +1 -5
  28. DIRAC/WorkloadManagementSystem/Utilities/test/Test_ParametricJob.py +45 -128
  29. {dirac-9.0.0a66.dist-info → dirac-9.0.0a67.dist-info}/METADATA +2 -2
  30. {dirac-9.0.0a66.dist-info → dirac-9.0.0a67.dist-info}/RECORD +34 -36
  31. DIRAC/Core/Utilities/test/Test_List.py +0 -150
  32. DIRAC/Core/Utilities/test/Test_Time.py +0 -88
  33. {dirac-9.0.0a66.dist-info → dirac-9.0.0a67.dist-info}/WHEEL +0 -0
  34. {dirac-9.0.0a66.dist-info → dirac-9.0.0a67.dist-info}/entry_points.txt +0 -0
  35. {dirac-9.0.0a66.dist-info → dirac-9.0.0a67.dist-info}/licenses/LICENSE +0 -0
  36. {dirac-9.0.0a66.dist-info → dirac-9.0.0a67.dist-info}/top_level.txt +0 -0
@@ -1,185 +1,19 @@
1
- """ StateMachine
1
+ """Backward compatibility wrapper - moved to DIRACCommon
2
2
 
3
- This module contains the basic blocks to build a state machine (State and StateMachine)
4
- """
5
- from DIRAC import S_OK, S_ERROR, gLogger
6
-
7
-
8
- class State:
9
- """
10
- State class that represents a single step on a StateMachine, with all the
11
- possible transitions, the default transition and an ordering level.
12
-
13
-
14
- examples:
15
- >>> s0 = State(100)
16
- >>> s1 = State(0, ['StateName1', 'StateName2'], defState='StateName1')
17
- >>> s2 = State(0, ['StateName1', 'StateName2'])
18
- # this example is tricky. The transition rule says that will go to
19
- # nextState, e.g. 'StateNext'. But, it is not on the stateMap, and there
20
- # is no default defined, so it will end up going to StateNext anyway. You
21
- # must be careful while defining states and their stateMaps and defaults.
22
- """
23
-
24
- def __init__(self, level, stateMap=None, defState=None):
25
- """
26
- :param int level: each state is mapped to an integer, which is used to sort the states according to that integer.
27
- :param list stateMap: it is a list (of strings) with the reachable states from this particular status.
28
- If not defined, we assume there are no restrictions.
29
- :param str defState: default state used in case the next state is not in stateMap (not defined or simply not there).
30
- """
31
-
32
- self.level = level
33
- self.stateMap = stateMap if stateMap else []
34
- self.default = defState
35
-
36
- def transitionRule(self, nextState):
37
- """
38
- Method that selects next state, knowing the default and the transitions
39
- map, and the proposed next state. If <nextState> is in stateMap, goes there.
40
- If not, then goes to <self.default> if any. Otherwise, goes to <nextState>
41
- anyway.
42
-
43
- examples:
44
- >>> s0.transitionRule('nextState')
45
- 'nextState'
46
- >>> s1.transitionRule('StateName2')
47
- 'StateName2'
48
- >>> s1.transitionRule('StateNameNotInMap')
49
- 'StateName1'
50
- >>> s2.transitionRule('StateNameNotInMap')
51
- 'StateNameNotInMap'
52
-
53
- :param str nextState: name of the state in the stateMap
54
- :return: state name
55
- :rtype: str
56
- """
57
-
58
- # If next state is on the list of next states, go ahead.
59
- if nextState in self.stateMap:
60
- return nextState
61
-
62
- # If not, calculate defaultState:
63
- # if there is a default, that one
64
- # otherwise is nextState (states with empty list have no movement restrictions)
65
- defaultNext = self.default if self.default else nextState
66
- return defaultNext
67
-
68
-
69
- class StateMachine:
70
- """
71
- StateMachine class that represents the whole state machine with all transitions.
72
-
73
- examples:
74
- >>> sm0 = StateMachine()
75
- >>> sm1 = StateMachine(state = 'Active')
76
-
77
- :param state: current state of the StateMachine, could be None if we do not use the
78
- StateMachine to calculate transitions. Beware, it is not checked if the
79
- state is on the states map !
80
- :type state: None or str
81
-
82
- """
3
+ This module has been moved to DIRACCommon.Core.Utilities.StateMachine to avoid
4
+ circular dependencies and allow DiracX to use these utilities without
5
+ triggering DIRAC's global state initialization.
83
6
 
84
- def __init__(self, state=None):
85
- """
86
- Constructor.
87
- """
88
-
89
- self.state = state
90
- # To be overwritten by child classes, unless you like Nirvana state that much.
91
- self.states = {"Nirvana": State(100)}
92
-
93
- def getLevelOfState(self, state):
94
- """
95
- Given a state name, it returns its level (integer), which defines the hierarchy.
7
+ All exports are maintained for backward compatibility.
8
+ """
9
+ # Re-export everything from DIRACCommon for backward compatibility
10
+ from DIRACCommon.Core.Utilities.StateMachine import * # noqa: F401, F403
96
11
 
97
- >>> sm0.getLevelOfState('Nirvana')
98
- 100
99
- >>> sm0.getLevelOfState('AnotherState')
100
- -1
12
+ from DIRAC import gLogger
101
13
 
102
- :param str state: name of the state, it should be on <self.states> key set
103
- :return: `int` || -1 (if not in <self.states>)
104
- """
105
14
 
106
- if state not in self.states:
107
- return -1
108
- return self.states[state].level
15
+ class StateMachine(StateMachine): # noqa: F405 pylint: disable=function-redefined
16
+ """Backward compatibility wrapper - moved to DIRACCommon"""
109
17
 
110
18
  def setState(self, candidateState, noWarn=False):
111
- """Makes sure the state is either None or known to the machine, and that it is a valid state to move into.
112
- Final states are also checked.
113
-
114
- examples:
115
- >>> sm0.setState(None)['OK']
116
- True
117
- >>> sm0.setState('Nirvana')['OK']
118
- True
119
- >>> sm0.setState('AnotherState')['OK']
120
- False
121
-
122
- :param state: state which will be set as current state of the StateMachine
123
- :type state: None or str
124
- :return: S_OK || S_ERROR
125
- """
126
- if candidateState == self.state:
127
- return S_OK(candidateState)
128
-
129
- if not candidateState:
130
- self.state = candidateState
131
- elif candidateState in self.states:
132
- if not self.states[self.state].stateMap:
133
- if not noWarn:
134
- gLogger.warn("Final state, won't move", f"({self.state}, asked to move to {candidateState})")
135
- return S_OK(self.state)
136
- if candidateState not in self.states[self.state].stateMap:
137
- gLogger.warn(f"Can't move from {self.state} to {candidateState}, choosing a good one")
138
- result = self.getNextState(candidateState)
139
- if not result["OK"]:
140
- return result
141
- self.state = result["Value"]
142
- # If the StateMachine does not accept the candidate, return error message
143
- else:
144
- return S_ERROR(f"setState: {candidateState!r} is not a valid state")
145
-
146
- return S_OK(self.state)
147
-
148
- def getStates(self):
149
- """
150
- Returns all possible states in the state map
151
-
152
- examples:
153
- >>> sm0.getStates()
154
- [ 'Nirvana' ]
155
-
156
- :return: list(stateNames)
157
- """
158
-
159
- return list(self.states)
160
-
161
- def getNextState(self, candidateState):
162
- """
163
- Method that gets the next state, given the proposed transition to candidateState.
164
- If candidateState is not on the state map <self.states>, it is rejected. If it is
165
- not the case, we have two options: if <self.state> is None, then the next state
166
- will be <candidateState>. Otherwise, the current state is using its own
167
- transition rule to decide.
168
-
169
- examples:
170
- >>> sm0.getNextState(None)
171
- S_OK(None)
172
- >>> sm0.getNextState('NextState')
173
- S_OK('NextState')
174
-
175
- :param str candidateState: name of the next state
176
- :return: S_OK(nextState) || S_ERROR
177
- """
178
- if candidateState not in self.states:
179
- return S_ERROR(f"getNextState: {candidateState!r} is not a valid state")
180
-
181
- # FIXME: do we need this anymore ?
182
- if self.state is None:
183
- return S_OK(candidateState)
184
-
185
- return S_OK(self.states[self.state].transitionRule(candidateState))
19
+ return super().setState(candidateState, noWarn, logger_warn=gLogger.warn)
@@ -1,260 +1,17 @@
1
- """
2
- DIRAC TimeUtilities module
3
- Support for basic Date and Time operations
4
- based on system datetime module.
5
-
6
- It provides common interface to UTC timestamps,
7
- converter to string types and back.
8
-
9
- Useful timedelta constant are also provided to
10
- define time intervals.
11
-
12
- Notice: datetime.timedelta objects allow multiplication and division by interger
13
- but not by float. Thus:
14
-
15
- - DIRAC.TimeUtilities.second * 1.5 is not allowed
16
- - DIRAC.TimeUtilities.second * 3 / 2 is allowed
1
+ """Backward compatibility wrapper - moved to DIRACCommon
17
2
 
18
- An timeInterval class provides a method to check
19
- if a give datetime is in the defined interval.
3
+ This module has been moved to DIRACCommon.Core.Utilities.TimeUtilities to avoid
4
+ circular dependencies and allow DiracX to use these utilities without
5
+ triggering DIRAC's global state initialization.
20
6
 
7
+ All exports are maintained for backward compatibility.
21
8
  """
22
- import datetime
23
- import sys
24
- import time
25
-
26
- from DIRAC import gLogger
27
-
28
- # Some useful constants for time operations
29
- microsecond = datetime.timedelta(microseconds=1)
30
- second = datetime.timedelta(seconds=1)
31
- minute = datetime.timedelta(minutes=1)
32
- hour = datetime.timedelta(hours=1)
33
- day = datetime.timedelta(days=1)
34
- week = datetime.timedelta(days=7)
35
-
36
-
37
- def timeThis(method):
38
- """Function to be used as a decorator for timing other functions/methods"""
39
-
40
- def timed(*args, **kw):
41
- """What actually times"""
42
- ts = time.time()
43
- result = method(*args, **kw)
44
- if sys.stdout.isatty():
45
- return result
46
- te = time.time()
47
-
48
- pre = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC ")
49
-
50
- try:
51
- pre += args[0].log.getName() + "/" + args[0].log.getSubName() + " TIME: " + args[0].transString
52
- except AttributeError:
53
- try:
54
- pre += args[0].log.getName() + " TIME: " + args[0].transString
55
- except AttributeError:
56
- try:
57
- pre += args[0].log.getName() + "/" + args[0].log.getSubName() + " TIME: "
58
- except AttributeError:
59
- pre += "TIME: "
60
- except IndexError:
61
- pre += "TIME: "
62
-
63
- argsLen = ""
64
- if args:
65
- try:
66
- if isinstance(args[1], (list, dict)):
67
- argsLen = f"arguments len: {len(args[1])}"
68
- except IndexError:
69
- if kw:
70
- try:
71
- if isinstance(list(list(kw.items())[0])[1], (list, dict)):
72
- argsLen = f"arguments len: {len(list(list(kw.items())[0])[1])}"
73
- except IndexError:
74
- argsLen = ""
75
-
76
- gLogger.info(f"{pre} Exec time ===> function {method.__name__!r} {argsLen} -> {te - ts:2.2f} sec")
77
- return result
78
-
79
- return timed
80
-
81
-
82
- def toEpoch(dateTimeObject=None):
83
- """
84
- Get seconds since epoch. Accepts datetime or date objects
85
- """
86
- return toEpochMilliSeconds(dateTimeObject) // 1000
9
+ from functools import partial
87
10
 
11
+ # Re-export everything from DIRACCommon for backward compatibility
12
+ from DIRACCommon.Core.Utilities.TimeUtilities import * # noqa: F401, F403
88
13
 
89
- def toEpochMilliSeconds(dateTimeObject=None):
90
- """
91
- Get milliseconds since epoch
92
- """
93
- if dateTimeObject is None:
94
- dateTimeObject = datetime.datetime.utcnow()
95
- if dateTimeObject.resolution == datetime.timedelta(days=1):
96
- # Add time information corresponding to midnight UTC if it's a datetime.date
97
- dateTimeObject = datetime.datetime.combine(
98
- dateTimeObject, datetime.time.min.replace(tzinfo=datetime.timezone.utc)
99
- )
100
- posixTime = dateTimeObject.replace(tzinfo=datetime.timezone.utc).timestamp()
101
- return int(posixTime * 1000)
102
-
103
-
104
- def fromEpoch(epoch):
105
- """
106
- Get datetime object from epoch
107
- """
108
- # Check if the timestamp is in milliseconds
109
- if epoch > 10**17: # nanoseconds
110
- epoch /= 1000**3
111
- elif epoch > 10**14: # microseconds
112
- epoch /= 1000**2
113
- elif epoch > 10**11: # milliseconds
114
- epoch /= 1000
115
- return datetime.datetime.utcfromtimestamp(epoch)
116
-
117
-
118
- def toString(myDate=None):
119
- """
120
- Convert to String
121
- if argument type is neither _dateTimeType, _dateType, nor _timeType
122
- the current dateTime converted to String is returned instead
123
-
124
- Notice: datetime.timedelta are converted to strings using the format:
125
- [day] days [hour]:[min]:[sec]:[microsec]
126
- where hour, min, sec, microsec are always positive integers,
127
- and day carries the sign.
128
- To keep internal consistency we are using:
129
- [hour]:[min]:[sec]:[microsec]
130
- where min, sec, microsec are always positive integers and hour carries the sign.
131
- """
132
- if isinstance(myDate, datetime.date):
133
- return str(myDate)
134
-
135
- elif isinstance(myDate, datetime.time):
136
- return "%02d:%02d:%02d.%06d" % (
137
- myDate.days * 24 + myDate.seconds / 3600,
138
- myDate.seconds % 3600 / 60,
139
- myDate.seconds % 60,
140
- myDate.microseconds,
141
- )
142
- else:
143
- return toString(datetime.datetime.utcnow())
144
-
145
-
146
- def fromString(myDate=None):
147
- """
148
- Convert date/time/datetime String back to appropriated objects
149
-
150
- The format of the string it is assume to be that returned by toString method.
151
- See notice on toString method
152
- On Error, return None
153
-
154
- :param myDate: the date string to be converted
155
- :type myDate: str or datetime.datetime
156
- """
157
- if isinstance(myDate, datetime.datetime):
158
- return myDate
159
- if isinstance(myDate, str):
160
- if myDate.find(" ") > 0:
161
- dateTimeTuple = myDate.split(" ")
162
- dateTuple = dateTimeTuple[0].split("-")
163
- try:
164
- return datetime.datetime(year=dateTuple[0], month=dateTuple[1], day=dateTuple[2]) + fromString(
165
- dateTimeTuple[1]
166
- )
167
- # return datetime.datetime.utcnow().combine( fromString( dateTimeTuple[0] ),
168
- # fromString( dateTimeTuple[1] ) )
169
- except Exception:
170
- try:
171
- return datetime.datetime(
172
- year=int(dateTuple[0]), month=int(dateTuple[1]), day=int(dateTuple[2])
173
- ) + fromString(dateTimeTuple[1])
174
- except ValueError:
175
- return None
176
- # return datetime.datetime.utcnow().combine( fromString( dateTimeTuple[0] ),
177
- # fromString( dateTimeTuple[1] ) )
178
- elif myDate.find(":") > 0:
179
- timeTuple = myDate.replace(".", ":").split(":")
180
- try:
181
- if len(timeTuple) == 4:
182
- return datetime.timedelta(
183
- hours=int(timeTuple[0]),
184
- minutes=int(timeTuple[1]),
185
- seconds=int(timeTuple[2]),
186
- microseconds=int(timeTuple[3]),
187
- )
188
- elif len(timeTuple) == 3:
189
- try:
190
- return datetime.timedelta(
191
- hours=int(timeTuple[0]),
192
- minutes=int(timeTuple[1]),
193
- seconds=int(timeTuple[2]),
194
- microseconds=0,
195
- )
196
- except ValueError:
197
- return None
198
- else:
199
- return None
200
- except Exception:
201
- return None
202
- elif myDate.find("-") > 0:
203
- dateTuple = myDate.split("-")
204
- try:
205
- return datetime.date(int(dateTuple[0]), int(dateTuple[1]), int(dateTuple[2]))
206
- except Exception:
207
- return None
208
-
209
- return None
210
-
211
-
212
- class timeInterval:
213
- """
214
- Simple class to define a timeInterval object able to check if a given
215
- dateTime is inside
216
- """
217
-
218
- def __init__(self, initialDateTime, intervalTimeDelta):
219
- """
220
- Initialization method, it requires the initial dateTime and the
221
- timedelta that define the limits.
222
- The upper limit is not included thus it is [begin,end)
223
- If not properly initialized an error flag is set, and subsequent calls
224
- to any method will return None
225
- """
226
- if not isinstance(initialDateTime, datetime.datetime) or not isinstance(intervalTimeDelta, datetime.timedelta):
227
- self.__error = True
228
- return None
229
- self.__error = False
230
- if intervalTimeDelta.days < 0:
231
- self.__startDateTime = initialDateTime + intervalTimeDelta
232
- self.__endDateTime = initialDateTime
233
- else:
234
- self.__startDateTime = initialDateTime
235
- self.__endDateTime = initialDateTime + intervalTimeDelta
236
-
237
- def includes(self, myDateTime):
238
- """ """
239
- if self.__error:
240
- return None
241
- if not isinstance(myDateTime, datetime.datetime):
242
- return None
243
- if myDateTime < self.__startDateTime:
244
- return False
245
- if myDateTime >= self.__endDateTime:
246
- return False
247
- return True
248
-
249
-
250
- def queryTime(f):
251
- """Decorator to measure the function call time"""
14
+ from DIRAC import gLogger
252
15
 
253
- def measureQueryTime(*args, **kwargs):
254
- start = time.time()
255
- result = f(*args, **kwargs)
256
- if result["OK"] and "QueryTime" not in result:
257
- result["QueryTime"] = time.time() - start
258
- return result
259
16
 
260
- return measureQueryTime
17
+ timeThis = partial(timeThis, logger_info=gLogger.info)
@@ -19,9 +19,6 @@ def jdl_monkey_business(monkeypatch):
19
19
  monkeypatch.setattr("DIRAC.Core.Base.API.getSites", lambda: S_OK(["LCG.IN2P3.fr"]))
20
20
  monkeypatch.setattr("DIRAC.WorkloadManagementSystem.Utilities.JobModel.getSites", lambda: S_OK(["LCG.IN2P3.fr"]))
21
21
  monkeypatch.setattr("DIRAC.Interfaces.API.Job.getDIRACPlatforms", lambda: S_OK("x86_64-slc6-gcc49-opt"))
22
- monkeypatch.setattr(
23
- "DIRAC.WorkloadManagementSystem.Utilities.JobModel.getDIRACPlatforms", lambda: S_OK("x86_64-slc6-gcc49-opt")
24
- )
25
22
  yield
26
23
 
27
24
 
@@ -16,6 +16,7 @@ from sqlalchemy import (
16
16
  Enum,
17
17
  Float,
18
18
  ForeignKey,
19
+ Index,
19
20
  Integer,
20
21
  MetaData,
21
22
  SmallInteger,
@@ -85,6 +86,7 @@ fts3JobTable = Table(
85
86
  Column("error", String(2048)),
86
87
  Column("status", Enum(*FTS3Job.ALL_STATES), server_default=FTS3Job.INIT_STATE, index=True),
87
88
  Column("assignment", String(255), server_default=None),
89
+ Index("idx_jobs_lastupdate_assignment", "lastUpdate", "assignment"),
88
90
  mysql_engine="InnoDB",
89
91
  )
90
92
 
@@ -110,6 +112,7 @@ fts3OperationTable = Table(
110
112
  Column("error", String(1024)),
111
113
  Column("type", String(255)),
112
114
  Column("assignment", String(255), server_default=None),
115
+ Index("idx_operations_lastupdate_assignment", "lastUpdate", "assignment"),
113
116
  mysql_engine="InnoDB",
114
117
  )
115
118
 
@@ -6,6 +6,7 @@ integration tests (Test_ReqDB.py)
6
6
  # pylint: disable=invalid-name,wrong-import-position
7
7
  import time
8
8
 
9
+ import pytest
9
10
 
10
11
  from DIRAC.RequestManagementSystem.Client.Request import Request
11
12
  from DIRAC.RequestManagementSystem.Client.Operation import Operation
@@ -42,6 +43,7 @@ def test_stress(reqDB):
42
43
  assert delete["OK"], delete
43
44
 
44
45
 
46
+ @pytest.mark.slow
45
47
  def test_stressBulk(reqDB):
46
48
  """stress test bulk"""
47
49
 
@@ -151,7 +151,7 @@ class RucioFileCatalogClient(FileCatalogClientBase):
151
151
  self.authHost = options.get("AuthHost", None)
152
152
  self.caCertPath = Locations.getCAsLocation()
153
153
  try:
154
- sLog.info(f"Logging in with a proxy located at: {self.proxyPath}")
154
+ sLog.debug(f"Logging in with a proxy located at: {self.proxyPath}")
155
155
  sLog.debug("account: ", self.username)
156
156
  sLog.debug("rucio host: ", self.rucioHost)
157
157
  sLog.debug("auth host: ", self.authHost)
@@ -25,7 +25,7 @@ while True:
25
25
  if os.path.isfile(stopFile):
26
26
  os.remove(stopFile)
27
27
  break
28
- if (time.time() - start) > 30:
28
+ if (time.time() - start) > 5:
29
29
  break
30
30
  print("End job", jobNumber, time.time())
31
31
  """
@@ -262,6 +262,7 @@ def test_executeJob_wholeNode8(createAndDelete):
262
262
  assert "Not enough processors" in submissionResult["Message"]
263
263
 
264
264
 
265
+ @pytest.mark.slow
265
266
  def test_executeJob_submitAndStop(createAndDelete):
266
267
  time.sleep(0.5)
267
268
 
@@ -5,6 +5,8 @@ import os
5
5
  import copy
6
6
  import shutil
7
7
 
8
+ import pytest
9
+
8
10
  from unittest.mock import MagicMock as Mock
9
11
 
10
12
  from DIRAC import gLogger
@@ -742,6 +744,7 @@ class FailoverRequestSuccess(ModulesTestCase):
742
744
  class ScriptSuccess(ModulesTestCase):
743
745
  #################################################
744
746
 
747
+ @pytest.mark.slow
745
748
  def test_execute(self):
746
749
  self.script.jobType = "merge"
747
750
  self.script.stepInputData = ["foo", "bar"]
@@ -770,6 +773,7 @@ class ScriptSuccess(ModulesTestCase):
770
773
  class ScriptUnicode(ModulesTestCase):
771
774
  #################################################
772
775
 
776
+ @pytest.mark.slow
773
777
  def test_execute(self):
774
778
  self.script.jobType = "merge"
775
779
  self.script.stepInputData = ["foo", "bar"]
@@ -799,6 +803,7 @@ class ScriptUnicode(ModulesTestCase):
799
803
  class ScriptFailure(ModulesTestCase):
800
804
  #################################################
801
805
 
806
+ @pytest.mark.slow
802
807
  def test_execute(self):
803
808
  self.script.jobType = "merge"
804
809
  self.script.stepInputData = ["foo", "bar"]
@@ -260,6 +260,7 @@ def test__checkMatcherInfo(mocker, matcherInfo, matcherParams, expectedResult):
260
260
  #############################################################################
261
261
 
262
262
 
263
+ @pytest.mark.slow
263
264
  @pytest.mark.parametrize(
264
265
  "mockGCReply, mockPMReply, expected",
265
266
  [
@@ -308,6 +309,7 @@ def test__setupProxy(mocker, mockGCReply, mockPMReply, expected):
308
309
  assert result["Message"] == expected["Message"]
309
310
 
310
311
 
312
+ @pytest.mark.slow
311
313
  @pytest.mark.parametrize(
312
314
  "mockGCReply, mockPMReply, expected",
313
315
  [
@@ -178,6 +178,7 @@ def jobID():
178
178
  shutil.rmtree(jobID)
179
179
 
180
180
 
181
+ @pytest.mark.slow
181
182
  def test_submitJobWrapper(mocker, jobID):
182
183
  """Test JobAgent._submitJobWrapper()"""
183
184
  mocker.patch("DIRAC.WorkloadManagementSystem.Agent.JobAgent.AgentModule.__init__")