langfun 0.1.2.dev202511010804__py3-none-any.whl → 0.1.2.dev202511020804__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.
Potentially problematic release.
This version of langfun might be problematic. Click here for more details.
- langfun/core/eval/v2/runners_test.py +3 -0
- langfun/env/__init__.py +1 -1
- langfun/env/base_environment.py +62 -5
- langfun/env/base_environment_test.py +5 -5
- langfun/env/base_feature.py +38 -20
- langfun/env/base_feature_test.py +228 -0
- langfun/env/base_sandbox.py +27 -33
- langfun/env/base_sandbox_test.py +258 -251
- langfun/env/event_handlers/chain.py +58 -80
- langfun/env/event_handlers/chain_test.py +83 -111
- langfun/env/event_handlers/event_logger.py +77 -80
- langfun/env/event_handlers/event_logger_test.py +18 -18
- langfun/env/event_handlers/metric_writer.py +176 -140
- langfun/env/interface.py +493 -199
- langfun/env/interface_test.py +30 -1
- langfun/env/test_utils.py +110 -78
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511020804.dist-info}/METADATA +1 -1
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511020804.dist-info}/RECORD +21 -20
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511020804.dist-info}/WHEEL +0 -0
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511020804.dist-info}/licenses/LICENSE +0 -0
- {langfun-0.1.2.dev202511010804.dist-info → langfun-0.1.2.dev202511020804.dist-info}/top_level.txt +0 -0
langfun/env/__init__.py
CHANGED
|
@@ -28,7 +28,7 @@ from langfun.env.interface import EventHandler
|
|
|
28
28
|
|
|
29
29
|
# Decorators for sandbox/feature methods.
|
|
30
30
|
from langfun.env.interface import treat_as_sandbox_state_error
|
|
31
|
-
from langfun.env.interface import
|
|
31
|
+
from langfun.env.interface import log_activity
|
|
32
32
|
|
|
33
33
|
from langfun.env.base_environment import BaseEnvironment
|
|
34
34
|
from langfun.env.base_sandbox import BaseSandbox
|
langfun/env/base_environment.py
CHANGED
|
@@ -179,6 +179,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
179
179
|
)
|
|
180
180
|
self._housekeep_thread = None
|
|
181
181
|
self._offline_start_time = None
|
|
182
|
+
self._non_sandbox_based_features_with_setup_called = set()
|
|
182
183
|
|
|
183
184
|
# Check image IDs and feature requirements.
|
|
184
185
|
self._check_image_ids()
|
|
@@ -192,7 +193,9 @@ class BaseEnvironment(interface.Environment):
|
|
|
192
193
|
if self.supports_dynamic_image_loading:
|
|
193
194
|
return
|
|
194
195
|
for name, feature in self.features.items():
|
|
195
|
-
if
|
|
196
|
+
if not feature.is_sandbox_based or any(
|
|
197
|
+
feature.is_applicable(image_id) for image_id in self.image_ids
|
|
198
|
+
):
|
|
196
199
|
continue
|
|
197
200
|
raise ValueError(
|
|
198
201
|
f'Feature {name!r} is not applicable to all available images: '
|
|
@@ -267,6 +270,13 @@ class BaseEnvironment(interface.Environment):
|
|
|
267
270
|
def _start(self) -> None:
|
|
268
271
|
"""Implementation of starting the environment."""
|
|
269
272
|
sandbox_startup_infos = []
|
|
273
|
+
self._non_sandbox_based_features_with_setup_called.clear()
|
|
274
|
+
# Setup all non-sandbox-based features.
|
|
275
|
+
for feature in self.non_sandbox_based_features():
|
|
276
|
+
self._non_sandbox_based_features_with_setup_called.add(feature.name)
|
|
277
|
+
feature.setup(sandbox=None)
|
|
278
|
+
|
|
279
|
+
# Setup sandbox pools.
|
|
270
280
|
for image_id in self.image_ids:
|
|
271
281
|
next_sandbox_id = 0
|
|
272
282
|
if self.enable_pooling(image_id):
|
|
@@ -312,10 +322,15 @@ class BaseEnvironment(interface.Environment):
|
|
|
312
322
|
self._housekeep_thread.join()
|
|
313
323
|
self._housekeep_thread = None
|
|
314
324
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
325
|
+
# Teardown all non-sandbox-based features.
|
|
326
|
+
for feature in self.non_sandbox_based_features():
|
|
327
|
+
if feature.name in self._non_sandbox_based_features_with_setup_called:
|
|
328
|
+
try:
|
|
329
|
+
feature.teardown()
|
|
330
|
+
except BaseException: # pylint: disable=broad-except
|
|
331
|
+
pass
|
|
318
332
|
|
|
333
|
+
# Shutdown sandbox pools.
|
|
319
334
|
if self._sandbox_pool:
|
|
320
335
|
sandboxes = []
|
|
321
336
|
for sandbox in self._sandbox_pool.values():
|
|
@@ -323,6 +338,10 @@ class BaseEnvironment(interface.Environment):
|
|
|
323
338
|
self._sandbox_pool = {}
|
|
324
339
|
|
|
325
340
|
if sandboxes:
|
|
341
|
+
def _shutdown_sandbox(sandbox: base_sandbox.BaseSandbox) -> None:
|
|
342
|
+
if sandbox is not None:
|
|
343
|
+
sandbox.shutdown()
|
|
344
|
+
|
|
326
345
|
_ = list(
|
|
327
346
|
lf.concurrent_map(
|
|
328
347
|
_shutdown_sandbox,
|
|
@@ -635,9 +654,45 @@ class BaseEnvironment(interface.Environment):
|
|
|
635
654
|
indices_by_image_id[image_id].append(i)
|
|
636
655
|
return indices_by_image_id
|
|
637
656
|
|
|
657
|
+
last_housekeep_time = {
|
|
658
|
+
f.name: time.time() for f in self.non_sandbox_based_features()
|
|
659
|
+
}
|
|
660
|
+
|
|
638
661
|
while self._status not in (self.Status.SHUTTING_DOWN, self.Status.OFFLINE):
|
|
639
662
|
housekeep_start_time = time.time()
|
|
663
|
+
feature_housekeep_successes = []
|
|
664
|
+
feature_housekeep_failures = []
|
|
665
|
+
|
|
666
|
+
# Housekeeping non-sandbox-based features.
|
|
667
|
+
for feature in self.non_sandbox_based_features():
|
|
668
|
+
if feature.housekeep_interval is None:
|
|
669
|
+
continue
|
|
670
|
+
if (last_housekeep_time[feature.name]
|
|
671
|
+
+ feature.housekeep_interval < time.time()):
|
|
672
|
+
try:
|
|
673
|
+
feature.housekeep()
|
|
674
|
+
last_housekeep_time[feature.name] = time.time()
|
|
675
|
+
feature_housekeep_successes.append(feature.name)
|
|
676
|
+
except BaseException as e: # pylint: disable=broad-except
|
|
677
|
+
pg.logging.error(
|
|
678
|
+
'[%s/%s]: Feature housekeeping failed with error: %s.'
|
|
679
|
+
'Shutting down environment...',
|
|
680
|
+
self.id,
|
|
681
|
+
feature.name,
|
|
682
|
+
e,
|
|
683
|
+
)
|
|
684
|
+
feature_housekeep_failures.append(feature.name)
|
|
685
|
+
self._housekeep_counter += 1
|
|
686
|
+
self.on_housekeep(
|
|
687
|
+
duration=time.time() - housekeep_start_time,
|
|
688
|
+
error=e,
|
|
689
|
+
feature_housekeep_successes=feature_housekeep_successes,
|
|
690
|
+
feature_housekeep_failures=feature_housekeep_failures,
|
|
691
|
+
)
|
|
692
|
+
self.shutdown()
|
|
693
|
+
return
|
|
640
694
|
|
|
695
|
+
# Replace dead sandboxes.
|
|
641
696
|
is_online = True
|
|
642
697
|
dead_sandbox_entries = []
|
|
643
698
|
for image_id, sandboxes in self._sandbox_pool.items():
|
|
@@ -658,6 +713,8 @@ class BaseEnvironment(interface.Environment):
|
|
|
658
713
|
duration = time.time() - housekeep_start_time
|
|
659
714
|
|
|
660
715
|
kwargs = dict(
|
|
716
|
+
feature_housekeep_successes=feature_housekeep_successes,
|
|
717
|
+
feature_housekeep_failures=feature_housekeep_failures,
|
|
661
718
|
dead_sandboxes=_indices_by_image_id(dead_sandbox_entries),
|
|
662
719
|
replaced_sandboxes=replaced_indices_by_image_id,
|
|
663
720
|
offline_duration=self.offline_duration,
|
|
@@ -666,7 +723,6 @@ class BaseEnvironment(interface.Environment):
|
|
|
666
723
|
self.on_housekeep(duration, **kwargs)
|
|
667
724
|
time.sleep(self.housekeep_interval)
|
|
668
725
|
else:
|
|
669
|
-
self.shutdown()
|
|
670
726
|
self.on_housekeep(
|
|
671
727
|
duration,
|
|
672
728
|
interface.EnvironmentOutageError(
|
|
@@ -674,6 +730,7 @@ class BaseEnvironment(interface.Environment):
|
|
|
674
730
|
),
|
|
675
731
|
**kwargs
|
|
676
732
|
)
|
|
733
|
+
self.shutdown()
|
|
677
734
|
|
|
678
735
|
def _replace_dead_sandboxes(
|
|
679
736
|
self,
|
|
@@ -57,7 +57,7 @@ class BaseEnvironmentTests(unittest.TestCase):
|
|
|
57
57
|
self.assertEqual(env.sandbox_pool, {})
|
|
58
58
|
self.assertEqual(env.working_dir, '/tmp/testing-env')
|
|
59
59
|
|
|
60
|
-
with env.sandbox(
|
|
60
|
+
with env.sandbox('session1') as sb:
|
|
61
61
|
self.assertEqual(
|
|
62
62
|
sb.id, interface.Sandbox.Id(
|
|
63
63
|
environment_id=env.id,
|
|
@@ -79,7 +79,7 @@ class BaseEnvironmentTests(unittest.TestCase):
|
|
|
79
79
|
with self.assertRaisesRegex(
|
|
80
80
|
ValueError, 'Environment .* does not serve image ID .*'
|
|
81
81
|
):
|
|
82
|
-
env.sandbox('test_image2')
|
|
82
|
+
env.sandbox(image_id='test_image2')
|
|
83
83
|
|
|
84
84
|
with env.test_feature() as feature:
|
|
85
85
|
self.assertIsInstance(feature, TestingFeature)
|
|
@@ -174,7 +174,7 @@ class BaseEnvironmentTests(unittest.TestCase):
|
|
|
174
174
|
with self.assertRaisesRegex(
|
|
175
175
|
ValueError, 'Feature .* is not applicable to .*'
|
|
176
176
|
):
|
|
177
|
-
with env.test_feature('test_image2'):
|
|
177
|
+
with env.test_feature(image_id='test_image2'):
|
|
178
178
|
pass
|
|
179
179
|
|
|
180
180
|
with env.test_feature2() as feature:
|
|
@@ -183,7 +183,7 @@ class BaseEnvironmentTests(unittest.TestCase):
|
|
|
183
183
|
with env.test_feature3() as feature:
|
|
184
184
|
self.assertEqual(feature.sandbox.image_id, 'test_image1')
|
|
185
185
|
|
|
186
|
-
with env.test_feature3('test_image2') as feature:
|
|
186
|
+
with env.test_feature3(image_id='test_image2') as feature:
|
|
187
187
|
self.assertEqual(feature.sandbox.image_id, 'test_image2')
|
|
188
188
|
|
|
189
189
|
def test_feature_applicability_check(self):
|
|
@@ -218,7 +218,7 @@ class BaseEnvironmentTests(unittest.TestCase):
|
|
|
218
218
|
pass
|
|
219
219
|
|
|
220
220
|
# Dynamically loaded IDs.
|
|
221
|
-
with env.test_feature2('test_image2') as feature:
|
|
221
|
+
with env.test_feature2(image_id='test_image2') as feature:
|
|
222
222
|
self.assertEqual(feature.sandbox.image_id, 'test_image2')
|
|
223
223
|
|
|
224
224
|
def test_pool_size(self):
|
langfun/env/base_feature.py
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
-
"""Common base class for
|
|
14
|
+
"""Common base class for environment features.
|
|
15
15
|
|
|
16
|
-
This module provides an base class `BaseFeature` for
|
|
16
|
+
This module provides an base class `BaseFeature` for environment features,
|
|
17
17
|
which provides event handlers for the feature lifecycle events, which can be
|
|
18
18
|
overridden by subclasses to provide custom behaviors. Please note that this base
|
|
19
19
|
class is intended to provide a convenient way to implement features, and not
|
|
@@ -22,18 +22,24 @@ coupled with `BaseEnvironment` and `BaseSandbox`, and is expected to work with
|
|
|
22
22
|
the `Environment` and `Sandbox` interfaces directly.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
+
import contextlib
|
|
25
26
|
import functools
|
|
26
27
|
import os
|
|
27
28
|
import re
|
|
28
29
|
import time
|
|
29
|
-
from typing import Annotated, Callable
|
|
30
|
+
from typing import Annotated, Any, Callable, Iterator
|
|
30
31
|
|
|
31
32
|
from langfun.env import interface
|
|
32
33
|
import pyglove as pg
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class BaseFeature(interface.Feature):
|
|
36
|
-
"""Common base class for
|
|
37
|
+
"""Common base class for environment features."""
|
|
38
|
+
|
|
39
|
+
is_sandbox_based: Annotated[
|
|
40
|
+
bool,
|
|
41
|
+
'Whether the feature is sandbox-based.'
|
|
42
|
+
] = True
|
|
37
43
|
|
|
38
44
|
applicable_images: Annotated[
|
|
39
45
|
list[str],
|
|
@@ -120,9 +126,11 @@ class BaseFeature(interface.Feature):
|
|
|
120
126
|
return env
|
|
121
127
|
|
|
122
128
|
@property
|
|
123
|
-
def sandbox(self) -> interface.Sandbox:
|
|
129
|
+
def sandbox(self) -> interface.Sandbox | None:
|
|
124
130
|
"""Returns the sandbox that the feature is running in."""
|
|
125
|
-
assert self._sandbox is not None
|
|
131
|
+
assert self._sandbox is not None or not self.is_sandbox_based, (
|
|
132
|
+
'Feature has not been set up yet.'
|
|
133
|
+
)
|
|
126
134
|
return self._sandbox
|
|
127
135
|
|
|
128
136
|
@property
|
|
@@ -159,7 +167,7 @@ class BaseFeature(interface.Feature):
|
|
|
159
167
|
finally:
|
|
160
168
|
event_handler(duration=time.time() - start_time, error=error)
|
|
161
169
|
|
|
162
|
-
def setup(self, sandbox: interface.Sandbox) -> None:
|
|
170
|
+
def setup(self, sandbox: interface.Sandbox | None = None) -> None:
|
|
163
171
|
"""Sets up the feature."""
|
|
164
172
|
self._sandbox = sandbox
|
|
165
173
|
self._do(self._setup, self.on_setup)
|
|
@@ -198,8 +206,6 @@ class BaseFeature(interface.Feature):
|
|
|
198
206
|
) -> None:
|
|
199
207
|
"""Called when the feature is setup."""
|
|
200
208
|
self.environment.event_handler.on_feature_setup(
|
|
201
|
-
environment=self.environment,
|
|
202
|
-
sandbox=self.sandbox,
|
|
203
209
|
feature=self,
|
|
204
210
|
duration=duration,
|
|
205
211
|
error=error
|
|
@@ -212,8 +218,6 @@ class BaseFeature(interface.Feature):
|
|
|
212
218
|
) -> None:
|
|
213
219
|
"""Called when the feature is teardown."""
|
|
214
220
|
self.environment.event_handler.on_feature_teardown(
|
|
215
|
-
environment=self.environment,
|
|
216
|
-
sandbox=self.sandbox,
|
|
217
221
|
feature=self,
|
|
218
222
|
duration=duration,
|
|
219
223
|
error=error
|
|
@@ -227,8 +231,6 @@ class BaseFeature(interface.Feature):
|
|
|
227
231
|
) -> None:
|
|
228
232
|
"""Called when the feature has done housekeeping."""
|
|
229
233
|
self.environment.event_handler.on_feature_housekeep(
|
|
230
|
-
environment=self.environment,
|
|
231
|
-
sandbox=self.sandbox,
|
|
232
234
|
feature=self,
|
|
233
235
|
counter=self._housekeep_counter,
|
|
234
236
|
duration=duration,
|
|
@@ -243,8 +245,6 @@ class BaseFeature(interface.Feature):
|
|
|
243
245
|
) -> None:
|
|
244
246
|
"""Called when the feature is setup for a user session."""
|
|
245
247
|
self.environment.event_handler.on_feature_setup_session(
|
|
246
|
-
environment=self.environment,
|
|
247
|
-
sandbox=self.sandbox,
|
|
248
248
|
feature=self,
|
|
249
249
|
session_id=self.session_id,
|
|
250
250
|
duration=duration,
|
|
@@ -258,8 +258,6 @@ class BaseFeature(interface.Feature):
|
|
|
258
258
|
) -> None:
|
|
259
259
|
"""Called when the feature is teardown for a user session."""
|
|
260
260
|
self.environment.event_handler.on_feature_teardown_session(
|
|
261
|
-
environment=self.environment,
|
|
262
|
-
sandbox=self.sandbox,
|
|
263
261
|
feature=self,
|
|
264
262
|
session_id=self.session_id,
|
|
265
263
|
duration=duration,
|
|
@@ -274,13 +272,33 @@ class BaseFeature(interface.Feature):
|
|
|
274
272
|
**kwargs
|
|
275
273
|
) -> None:
|
|
276
274
|
"""Called when a sandbox activity is performed."""
|
|
277
|
-
self.environment.event_handler.
|
|
275
|
+
self.environment.event_handler.on_feature_activity(
|
|
278
276
|
name=f'{self.name}.{name}',
|
|
279
|
-
environment=self.environment,
|
|
280
|
-
sandbox=self.sandbox,
|
|
281
277
|
feature=self,
|
|
282
278
|
session_id=self.session_id,
|
|
283
279
|
duration=duration,
|
|
284
280
|
error=error,
|
|
285
281
|
**kwargs
|
|
286
282
|
)
|
|
283
|
+
|
|
284
|
+
@contextlib.contextmanager
|
|
285
|
+
def track_activity(
|
|
286
|
+
self,
|
|
287
|
+
name: str,
|
|
288
|
+
**kwargs: Any
|
|
289
|
+
) -> Iterator[None]:
|
|
290
|
+
"""Context manager that tracks a feature activity."""
|
|
291
|
+
start_time = time.time()
|
|
292
|
+
error = None
|
|
293
|
+
try:
|
|
294
|
+
yield None
|
|
295
|
+
except BaseException as e: # pylint: disable=broad-except
|
|
296
|
+
error = e
|
|
297
|
+
raise
|
|
298
|
+
finally:
|
|
299
|
+
self.on_activity(
|
|
300
|
+
name=name,
|
|
301
|
+
duration=time.time() - start_time,
|
|
302
|
+
error=error,
|
|
303
|
+
**kwargs
|
|
304
|
+
)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Copyright 2025 The Langfun Authors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import unittest
|
|
15
|
+
|
|
16
|
+
from langfun.env import test_utils
|
|
17
|
+
|
|
18
|
+
TestingEnvironment = test_utils.TestingEnvironment
|
|
19
|
+
TestingNonSandboxBasedFeature = test_utils.TestingNonSandboxBasedFeature
|
|
20
|
+
TestingEventHandler = test_utils.TestingEventHandler
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class NonSandboxBasedFeatureTests(unittest.TestCase):
|
|
24
|
+
|
|
25
|
+
def test_basics(self):
|
|
26
|
+
feature = TestingNonSandboxBasedFeature()
|
|
27
|
+
event_handler = TestingEventHandler(
|
|
28
|
+
log_session_setup=True,
|
|
29
|
+
log_feature_setup=True,
|
|
30
|
+
log_sandbox_status=True
|
|
31
|
+
)
|
|
32
|
+
env = TestingEnvironment(
|
|
33
|
+
image_ids=[],
|
|
34
|
+
features={'test_feature': feature},
|
|
35
|
+
event_handler=event_handler,
|
|
36
|
+
)
|
|
37
|
+
self.assertFalse(env.is_online)
|
|
38
|
+
self.assertEqual(len(list(env.non_sandbox_based_features())), 1)
|
|
39
|
+
with env:
|
|
40
|
+
self.assertTrue(env.is_online)
|
|
41
|
+
with env.test_feature('session1') as feature:
|
|
42
|
+
self.assertIsNone(feature.sandbox)
|
|
43
|
+
self.assertEqual(feature.session_id, 'session1')
|
|
44
|
+
|
|
45
|
+
self.assertEqual(
|
|
46
|
+
event_handler.logs,
|
|
47
|
+
[
|
|
48
|
+
'[testing-env/test_feature] feature setup',
|
|
49
|
+
'[testing-env] environment started',
|
|
50
|
+
'[testing-env/test_feature@session1] feature setup session',
|
|
51
|
+
'[testing-env/test_feature@session1] feature teardown session',
|
|
52
|
+
'[testing-env/test_feature] feature teardown',
|
|
53
|
+
'[testing-env] environment shutdown'
|
|
54
|
+
]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def test_feature_setup_error(self):
|
|
58
|
+
event_handler = TestingEventHandler(
|
|
59
|
+
log_session_setup=True,
|
|
60
|
+
log_feature_setup=True,
|
|
61
|
+
log_sandbox_status=True
|
|
62
|
+
)
|
|
63
|
+
env = TestingEnvironment(
|
|
64
|
+
image_ids=[],
|
|
65
|
+
features={
|
|
66
|
+
'test_feature': TestingNonSandboxBasedFeature(
|
|
67
|
+
simulate_setup_error=ValueError
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
event_handler=event_handler,
|
|
71
|
+
)
|
|
72
|
+
with self.assertRaises(ValueError):
|
|
73
|
+
with env:
|
|
74
|
+
pass
|
|
75
|
+
self.assertEqual(
|
|
76
|
+
event_handler.logs,
|
|
77
|
+
[
|
|
78
|
+
'[testing-env/test_feature] feature setup with ValueError',
|
|
79
|
+
'[testing-env] environment started with ValueError',
|
|
80
|
+
'[testing-env/test_feature] feature teardown',
|
|
81
|
+
'[testing-env] environment shutdown'
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_feature_teardown_error(self):
|
|
86
|
+
event_handler = TestingEventHandler(
|
|
87
|
+
log_session_setup=True,
|
|
88
|
+
log_feature_setup=True,
|
|
89
|
+
log_sandbox_status=True
|
|
90
|
+
)
|
|
91
|
+
env = TestingEnvironment(
|
|
92
|
+
image_ids=[],
|
|
93
|
+
features={
|
|
94
|
+
'test_feature': TestingNonSandboxBasedFeature(
|
|
95
|
+
simulate_teardown_error=ValueError
|
|
96
|
+
)
|
|
97
|
+
},
|
|
98
|
+
event_handler=event_handler,
|
|
99
|
+
)
|
|
100
|
+
with env:
|
|
101
|
+
pass
|
|
102
|
+
self.assertEqual(
|
|
103
|
+
event_handler.logs,
|
|
104
|
+
[
|
|
105
|
+
'[testing-env/test_feature] feature setup',
|
|
106
|
+
'[testing-env] environment started',
|
|
107
|
+
'[testing-env/test_feature] feature teardown with ValueError',
|
|
108
|
+
'[testing-env] environment shutdown'
|
|
109
|
+
]
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def test_feature_setup_session_error(self):
|
|
113
|
+
event_handler = TestingEventHandler(
|
|
114
|
+
log_session_setup=True,
|
|
115
|
+
log_feature_setup=True,
|
|
116
|
+
log_sandbox_status=True
|
|
117
|
+
)
|
|
118
|
+
env = TestingEnvironment(
|
|
119
|
+
image_ids=[],
|
|
120
|
+
features={
|
|
121
|
+
'test_feature': TestingNonSandboxBasedFeature(
|
|
122
|
+
simulate_setup_session_error=ValueError
|
|
123
|
+
)
|
|
124
|
+
},
|
|
125
|
+
event_handler=event_handler,
|
|
126
|
+
)
|
|
127
|
+
with env:
|
|
128
|
+
with self.assertRaises(ValueError):
|
|
129
|
+
with env.test_feature('session1'):
|
|
130
|
+
pass
|
|
131
|
+
self.assertEqual(
|
|
132
|
+
event_handler.logs,
|
|
133
|
+
[
|
|
134
|
+
# pylint: disable=line-too-long
|
|
135
|
+
'[testing-env/test_feature] feature setup',
|
|
136
|
+
'[testing-env] environment started',
|
|
137
|
+
'[testing-env/test_feature@session1] feature setup session with ValueError',
|
|
138
|
+
'[testing-env/test_feature@session1] feature teardown session',
|
|
139
|
+
'[testing-env/test_feature] feature teardown',
|
|
140
|
+
'[testing-env] environment shutdown',
|
|
141
|
+
# pylint: enable=line-too-long
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def test_feature_teardown_session_error(self):
|
|
146
|
+
event_handler = TestingEventHandler(
|
|
147
|
+
log_session_setup=True,
|
|
148
|
+
log_feature_setup=True,
|
|
149
|
+
log_sandbox_status=True
|
|
150
|
+
)
|
|
151
|
+
env = TestingEnvironment(
|
|
152
|
+
image_ids=[],
|
|
153
|
+
features={
|
|
154
|
+
'test_feature': TestingNonSandboxBasedFeature(
|
|
155
|
+
simulate_teardown_session_error=ValueError
|
|
156
|
+
)
|
|
157
|
+
},
|
|
158
|
+
event_handler=event_handler,
|
|
159
|
+
)
|
|
160
|
+
with env:
|
|
161
|
+
with env.test_feature('session1'):
|
|
162
|
+
pass
|
|
163
|
+
self.assertEqual(
|
|
164
|
+
event_handler.logs,
|
|
165
|
+
[
|
|
166
|
+
# pylint: disable=line-too-long
|
|
167
|
+
'[testing-env/test_feature] feature setup',
|
|
168
|
+
'[testing-env] environment started',
|
|
169
|
+
'[testing-env/test_feature@session1] feature setup session',
|
|
170
|
+
'[testing-env/test_feature@session1] feature teardown session with ValueError',
|
|
171
|
+
'[testing-env/test_feature] feature teardown',
|
|
172
|
+
'[testing-env] environment shutdown',
|
|
173
|
+
# pylint: enable=line-too-long
|
|
174
|
+
]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def test_feature_housekeeping(self):
|
|
178
|
+
event_handler = TestingEventHandler(
|
|
179
|
+
log_sandbox_status=False,
|
|
180
|
+
log_feature_setup=False,
|
|
181
|
+
log_housekeep=True
|
|
182
|
+
)
|
|
183
|
+
env = TestingEnvironment(
|
|
184
|
+
image_ids=[],
|
|
185
|
+
features={
|
|
186
|
+
'test_feature': TestingNonSandboxBasedFeature(
|
|
187
|
+
housekeep_interval=0.1
|
|
188
|
+
)
|
|
189
|
+
},
|
|
190
|
+
event_handler=event_handler,
|
|
191
|
+
housekeep_interval=0.2
|
|
192
|
+
)
|
|
193
|
+
with env:
|
|
194
|
+
env.wait_for_housekeeping()
|
|
195
|
+
self.assertIn(
|
|
196
|
+
'[testing-env/test_feature] feature housekeeping 0',
|
|
197
|
+
event_handler.logs
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def test_feature_housekeeping_error(self):
|
|
201
|
+
event_handler = TestingEventHandler(
|
|
202
|
+
log_sandbox_status=False,
|
|
203
|
+
log_feature_setup=False,
|
|
204
|
+
log_housekeep=True
|
|
205
|
+
)
|
|
206
|
+
env = TestingEnvironment(
|
|
207
|
+
image_ids=[],
|
|
208
|
+
features={
|
|
209
|
+
'test_feature': TestingNonSandboxBasedFeature(
|
|
210
|
+
simulate_housekeep_error=ValueError,
|
|
211
|
+
housekeep_interval=0.1
|
|
212
|
+
)
|
|
213
|
+
},
|
|
214
|
+
event_handler=event_handler,
|
|
215
|
+
housekeep_interval=0.2
|
|
216
|
+
)
|
|
217
|
+
with env:
|
|
218
|
+
env.wait_for_housekeeping()
|
|
219
|
+
self.assertFalse(env.is_online)
|
|
220
|
+
self.assertIn(
|
|
221
|
+
'[testing-env/test_feature] feature housekeeping 0 with ValueError',
|
|
222
|
+
event_handler.logs
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
if __name__ == '__main__':
|
|
227
|
+
unittest.main()
|
|
228
|
+
|
langfun/env/base_sandbox.py
CHANGED
|
@@ -648,7 +648,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
648
648
|
def track_activity(
|
|
649
649
|
self,
|
|
650
650
|
name: str,
|
|
651
|
-
feature: interface.Feature | None = None,
|
|
652
651
|
**kwargs: Any
|
|
653
652
|
) -> Iterator[None]:
|
|
654
653
|
"""Tracks an activity for the sandbox."""
|
|
@@ -662,7 +661,6 @@ class BaseSandbox(interface.Sandbox):
|
|
|
662
661
|
finally:
|
|
663
662
|
self.on_activity(
|
|
664
663
|
name=name,
|
|
665
|
-
feature=feature,
|
|
666
664
|
duration=time.time() - start_time,
|
|
667
665
|
error=error,
|
|
668
666
|
**kwargs
|
|
@@ -755,9 +753,7 @@ class BaseSandbox(interface.Sandbox):
|
|
|
755
753
|
error: BaseException | None = None
|
|
756
754
|
) -> None:
|
|
757
755
|
"""Called when the sandbox is started."""
|
|
758
|
-
self._event_handler.on_sandbox_start(
|
|
759
|
-
self.environment, self, duration, error
|
|
760
|
-
)
|
|
756
|
+
self._event_handler.on_sandbox_start(self, duration, error)
|
|
761
757
|
|
|
762
758
|
def on_status_change(
|
|
763
759
|
self,
|
|
@@ -767,7 +763,7 @@ class BaseSandbox(interface.Sandbox):
|
|
|
767
763
|
"""Called when the sandbox status changes."""
|
|
768
764
|
status_duration = time.time() - self._status_start_time
|
|
769
765
|
self._event_handler.on_sandbox_status_change(
|
|
770
|
-
self
|
|
766
|
+
self, old_status, new_status, status_duration
|
|
771
767
|
)
|
|
772
768
|
|
|
773
769
|
def on_shutdown(
|
|
@@ -777,11 +773,11 @@ class BaseSandbox(interface.Sandbox):
|
|
|
777
773
|
) -> None:
|
|
778
774
|
"""Called when the sandbox is shutdown."""
|
|
779
775
|
self._event_handler.on_sandbox_shutdown(
|
|
780
|
-
self
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
error
|
|
776
|
+
sandbox=self,
|
|
777
|
+
duration=duration,
|
|
778
|
+
lifetime=(0.0 if self._start_time is None
|
|
779
|
+
else (time.time() - self._start_time)),
|
|
780
|
+
error=error
|
|
785
781
|
)
|
|
786
782
|
|
|
787
783
|
def on_housekeep(
|
|
@@ -792,11 +788,10 @@ class BaseSandbox(interface.Sandbox):
|
|
|
792
788
|
) -> None:
|
|
793
789
|
"""Called when the sandbox finishes a round of housekeeping."""
|
|
794
790
|
self._event_handler.on_sandbox_housekeep(
|
|
795
|
-
self
|
|
796
|
-
self,
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
error,
|
|
791
|
+
sandbox=self,
|
|
792
|
+
counter=self._housekeep_counter,
|
|
793
|
+
duration=duration,
|
|
794
|
+
error=error,
|
|
800
795
|
**kwargs
|
|
801
796
|
)
|
|
802
797
|
|
|
@@ -807,27 +802,27 @@ class BaseSandbox(interface.Sandbox):
|
|
|
807
802
|
error: BaseException | None = None
|
|
808
803
|
) -> None:
|
|
809
804
|
"""Called when the user session starts."""
|
|
810
|
-
self._event_handler.
|
|
811
|
-
self
|
|
805
|
+
self._event_handler.on_sandbox_session_start(
|
|
806
|
+
sandbox=self,
|
|
807
|
+
session_id=session_id,
|
|
808
|
+
duration=duration,
|
|
809
|
+
error=error
|
|
812
810
|
)
|
|
813
811
|
|
|
814
812
|
def on_activity(
|
|
815
813
|
self,
|
|
816
814
|
name: str,
|
|
817
815
|
duration: float,
|
|
818
|
-
feature: interface.Feature | None = None,
|
|
819
816
|
error: BaseException | None = None,
|
|
820
817
|
**kwargs
|
|
821
818
|
) -> None:
|
|
822
819
|
"""Called when a sandbox activity is performed."""
|
|
823
820
|
self._event_handler.on_sandbox_activity(
|
|
824
|
-
name,
|
|
825
|
-
self
|
|
826
|
-
self,
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
duration,
|
|
830
|
-
error,
|
|
821
|
+
name=name,
|
|
822
|
+
sandbox=self,
|
|
823
|
+
session_id=self.session_id,
|
|
824
|
+
duration=duration,
|
|
825
|
+
error=error,
|
|
831
826
|
**kwargs
|
|
832
827
|
)
|
|
833
828
|
|
|
@@ -838,11 +833,10 @@ class BaseSandbox(interface.Sandbox):
|
|
|
838
833
|
error: BaseException | None = None
|
|
839
834
|
) -> None:
|
|
840
835
|
"""Called when the user session ends."""
|
|
841
|
-
self._event_handler.
|
|
842
|
-
self
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
error
|
|
836
|
+
self._event_handler.on_sandbox_session_end(
|
|
837
|
+
sandbox=self,
|
|
838
|
+
session_id=session_id,
|
|
839
|
+
duration=duration,
|
|
840
|
+
lifetime=time.time() - self._session_start_time,
|
|
841
|
+
error=error
|
|
848
842
|
)
|