langfun 0.1.2.dev202509120804__py3-none-any.whl → 0.1.2.dev202512040805__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 (162) hide show
  1. langfun/__init__.py +1 -1
  2. langfun/core/__init__.py +7 -1
  3. langfun/core/agentic/__init__.py +8 -1
  4. langfun/core/agentic/action.py +740 -112
  5. langfun/core/agentic/action_eval.py +9 -2
  6. langfun/core/agentic/action_test.py +189 -24
  7. langfun/core/async_support.py +104 -5
  8. langfun/core/async_support_test.py +23 -0
  9. langfun/core/coding/python/correction.py +19 -9
  10. langfun/core/coding/python/execution.py +14 -12
  11. langfun/core/coding/python/generation.py +21 -16
  12. langfun/core/coding/python/sandboxing.py +23 -3
  13. langfun/core/component.py +42 -3
  14. langfun/core/concurrent.py +70 -6
  15. langfun/core/concurrent_test.py +9 -2
  16. langfun/core/console.py +1 -1
  17. langfun/core/data/conversion/anthropic.py +12 -3
  18. langfun/core/data/conversion/anthropic_test.py +8 -6
  19. langfun/core/data/conversion/gemini.py +11 -2
  20. langfun/core/data/conversion/gemini_test.py +48 -9
  21. langfun/core/data/conversion/openai.py +145 -31
  22. langfun/core/data/conversion/openai_test.py +161 -17
  23. langfun/core/eval/base.py +48 -44
  24. langfun/core/eval/base_test.py +5 -5
  25. langfun/core/eval/matching.py +5 -2
  26. langfun/core/eval/patching.py +3 -3
  27. langfun/core/eval/scoring.py +4 -3
  28. langfun/core/eval/v2/__init__.py +2 -0
  29. langfun/core/eval/v2/checkpointing.py +76 -7
  30. langfun/core/eval/v2/checkpointing_test.py +9 -2
  31. langfun/core/eval/v2/config_saver.py +37 -0
  32. langfun/core/eval/v2/config_saver_test.py +36 -0
  33. langfun/core/eval/v2/eval_test_helper.py +104 -3
  34. langfun/core/eval/v2/evaluation.py +92 -17
  35. langfun/core/eval/v2/evaluation_test.py +9 -3
  36. langfun/core/eval/v2/example.py +50 -40
  37. langfun/core/eval/v2/example_test.py +16 -8
  38. langfun/core/eval/v2/experiment.py +84 -15
  39. langfun/core/eval/v2/experiment_test.py +19 -0
  40. langfun/core/eval/v2/metric_values.py +31 -3
  41. langfun/core/eval/v2/metric_values_test.py +32 -0
  42. langfun/core/eval/v2/metrics.py +157 -44
  43. langfun/core/eval/v2/metrics_test.py +39 -18
  44. langfun/core/eval/v2/progress.py +31 -1
  45. langfun/core/eval/v2/progress_test.py +27 -0
  46. langfun/core/eval/v2/progress_tracking.py +13 -5
  47. langfun/core/eval/v2/progress_tracking_test.py +9 -1
  48. langfun/core/eval/v2/reporting.py +90 -71
  49. langfun/core/eval/v2/reporting_test.py +24 -6
  50. langfun/core/eval/v2/runners/__init__.py +30 -0
  51. langfun/core/eval/v2/{runners.py → runners/base.py} +72 -180
  52. langfun/core/eval/v2/runners/beam.py +354 -0
  53. langfun/core/eval/v2/runners/beam_test.py +153 -0
  54. langfun/core/eval/v2/runners/ckpt_monitor.py +294 -0
  55. langfun/core/eval/v2/runners/ckpt_monitor_test.py +162 -0
  56. langfun/core/eval/v2/runners/debug.py +40 -0
  57. langfun/core/eval/v2/runners/debug_test.py +76 -0
  58. langfun/core/eval/v2/runners/parallel.py +243 -0
  59. langfun/core/eval/v2/runners/parallel_test.py +182 -0
  60. langfun/core/eval/v2/runners/sequential.py +47 -0
  61. langfun/core/eval/v2/runners/sequential_test.py +169 -0
  62. langfun/core/langfunc.py +45 -130
  63. langfun/core/langfunc_test.py +7 -5
  64. langfun/core/language_model.py +189 -36
  65. langfun/core/language_model_test.py +54 -3
  66. langfun/core/llms/__init__.py +12 -1
  67. langfun/core/llms/anthropic.py +157 -2
  68. langfun/core/llms/azure_openai.py +29 -17
  69. langfun/core/llms/cache/base.py +25 -3
  70. langfun/core/llms/cache/in_memory.py +48 -7
  71. langfun/core/llms/cache/in_memory_test.py +14 -4
  72. langfun/core/llms/compositional.py +25 -1
  73. langfun/core/llms/deepseek.py +30 -2
  74. langfun/core/llms/fake.py +32 -1
  75. langfun/core/llms/gemini.py +64 -12
  76. langfun/core/llms/gemini_test.py +110 -0
  77. langfun/core/llms/google_genai.py +34 -1
  78. langfun/core/llms/groq.py +28 -3
  79. langfun/core/llms/llama_cpp.py +23 -4
  80. langfun/core/llms/openai.py +120 -3
  81. langfun/core/llms/openai_compatible.py +148 -27
  82. langfun/core/llms/openai_compatible_test.py +207 -20
  83. langfun/core/llms/openai_test.py +0 -2
  84. langfun/core/llms/rest.py +16 -1
  85. langfun/core/llms/vertexai.py +58 -8
  86. langfun/core/logging.py +1 -1
  87. langfun/core/mcp/__init__.py +10 -0
  88. langfun/core/mcp/client.py +177 -0
  89. langfun/core/mcp/client_test.py +71 -0
  90. langfun/core/mcp/session.py +241 -0
  91. langfun/core/mcp/session_test.py +54 -0
  92. langfun/core/mcp/testing/simple_mcp_client.py +33 -0
  93. langfun/core/mcp/testing/simple_mcp_server.py +33 -0
  94. langfun/core/mcp/tool.py +254 -0
  95. langfun/core/mcp/tool_test.py +197 -0
  96. langfun/core/memory.py +1 -0
  97. langfun/core/message.py +160 -55
  98. langfun/core/message_test.py +65 -81
  99. langfun/core/modalities/__init__.py +8 -0
  100. langfun/core/modalities/audio.py +21 -1
  101. langfun/core/modalities/image.py +73 -3
  102. langfun/core/modalities/image_test.py +116 -0
  103. langfun/core/modalities/mime.py +64 -3
  104. langfun/core/modalities/mime_test.py +11 -0
  105. langfun/core/modalities/pdf.py +19 -1
  106. langfun/core/modalities/video.py +21 -1
  107. langfun/core/modality.py +167 -29
  108. langfun/core/modality_test.py +42 -12
  109. langfun/core/natural_language.py +1 -1
  110. langfun/core/sampling.py +4 -4
  111. langfun/core/sampling_test.py +20 -4
  112. langfun/core/structured/__init__.py +2 -24
  113. langfun/core/structured/completion.py +34 -44
  114. langfun/core/structured/completion_test.py +23 -43
  115. langfun/core/structured/description.py +54 -50
  116. langfun/core/structured/function_generation.py +29 -12
  117. langfun/core/structured/mapping.py +81 -37
  118. langfun/core/structured/parsing.py +95 -79
  119. langfun/core/structured/parsing_test.py +0 -3
  120. langfun/core/structured/querying.py +230 -154
  121. langfun/core/structured/querying_test.py +69 -33
  122. langfun/core/structured/schema/__init__.py +49 -0
  123. langfun/core/structured/schema/base.py +664 -0
  124. langfun/core/structured/schema/base_test.py +531 -0
  125. langfun/core/structured/schema/json.py +174 -0
  126. langfun/core/structured/schema/json_test.py +121 -0
  127. langfun/core/structured/schema/python.py +316 -0
  128. langfun/core/structured/schema/python_test.py +410 -0
  129. langfun/core/structured/schema_generation.py +33 -14
  130. langfun/core/structured/scoring.py +47 -36
  131. langfun/core/structured/tokenization.py +26 -11
  132. langfun/core/subscription.py +2 -2
  133. langfun/core/template.py +175 -50
  134. langfun/core/template_test.py +123 -17
  135. langfun/env/__init__.py +43 -0
  136. langfun/env/base_environment.py +827 -0
  137. langfun/env/base_environment_test.py +473 -0
  138. langfun/env/base_feature.py +304 -0
  139. langfun/env/base_feature_test.py +228 -0
  140. langfun/env/base_sandbox.py +842 -0
  141. langfun/env/base_sandbox_test.py +1235 -0
  142. langfun/env/event_handlers/__init__.py +14 -0
  143. langfun/env/event_handlers/chain.py +233 -0
  144. langfun/env/event_handlers/chain_test.py +253 -0
  145. langfun/env/event_handlers/event_logger.py +472 -0
  146. langfun/env/event_handlers/event_logger_test.py +304 -0
  147. langfun/env/event_handlers/metric_writer.py +726 -0
  148. langfun/env/event_handlers/metric_writer_test.py +214 -0
  149. langfun/env/interface.py +1640 -0
  150. langfun/env/interface_test.py +153 -0
  151. langfun/env/load_balancers.py +59 -0
  152. langfun/env/load_balancers_test.py +141 -0
  153. langfun/env/test_utils.py +507 -0
  154. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/METADATA +7 -3
  155. langfun-0.1.2.dev202512040805.dist-info/RECORD +217 -0
  156. langfun/core/eval/v2/runners_test.py +0 -343
  157. langfun/core/structured/schema.py +0 -987
  158. langfun/core/structured/schema_test.py +0 -982
  159. langfun-0.1.2.dev202509120804.dist-info/RECORD +0 -172
  160. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/WHEEL +0 -0
  161. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/licenses/LICENSE +0 -0
  162. {langfun-0.1.2.dev202509120804.dist-info → langfun-0.1.2.dev202512040805.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1235 @@
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 time
15
+ from typing import Any
16
+ import unittest
17
+
18
+ from langfun.env import interface
19
+ from langfun.env import test_utils
20
+
21
+ TestingEnvironment = test_utils.TestingEnvironment
22
+ TestingSandbox = test_utils.TestingSandbox
23
+ TestingFeature = test_utils.TestingFeature
24
+ TestingEventHandler = test_utils.TestingEventHandler
25
+
26
+
27
+ class SandboxStateTests(unittest.TestCase):
28
+
29
+ def setUp(self):
30
+ super().setUp()
31
+ self.event_handler = TestingEventHandler(
32
+ log_sandbox_status=True,
33
+ log_feature_setup=True,
34
+ log_session_setup=True,
35
+ )
36
+ self.maxDiff = None
37
+
38
+ def _create_env(
39
+ self,
40
+ features,
41
+ *,
42
+ pool_size=0,
43
+ **kwargs
44
+ ) -> TestingEnvironment:
45
+ return TestingEnvironment(
46
+ pool_size=pool_size,
47
+ features=features,
48
+ outage_grace_period=0,
49
+ event_handler=self.event_handler,
50
+ outage_retry_interval=0,
51
+ **kwargs
52
+ )
53
+
54
+ def test_passive_session_setup(self):
55
+ env = self._create_env(
56
+ features={
57
+ 'feature1': TestingFeature(),
58
+ 'feature2': TestingFeature(),
59
+ },
60
+ )
61
+ self.assertFalse(env.enable_pooling('test_image'))
62
+ with env:
63
+ with env.sandbox('session1') as sb:
64
+ sb.shell('echo "hello"')
65
+ self.assertEqual(
66
+ self.event_handler.logs,
67
+ [
68
+ # pylint: disable=line-too-long
69
+ '[testing-env] environment started',
70
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
71
+ '[testing-env/test_image:0/feature1] feature setup',
72
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
73
+ '[testing-env/test_image:0/feature2] feature setup',
74
+ '[testing-env/test_image:0] created -> ready',
75
+ '[testing-env/test_image:0] sandbox started',
76
+ '[testing-env/test_image:0] ready -> acquired',
77
+ '[testing-env/test_image:0] acquired -> setting_up',
78
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
79
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
80
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
81
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
82
+ '[testing-env/test_image:0] setting_up -> in_session',
83
+ "[testing-env/test_image:0] session 'session1' started",
84
+ '[testing-env/test_image:0@session1] shell: echo "hello"',
85
+ '[testing-env/test_image:0] in_session -> exiting_session',
86
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown session',
87
+ '[testing-env/test_image:0/feature1@session1] feature teardown session',
88
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
89
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
90
+ "[testing-env/test_image:0] session 'session1' ended",
91
+ '[testing-env/test_image:0] exiting_session -> acquired',
92
+ '[testing-env/test_image:0] acquired -> shutting_down',
93
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
94
+ '[testing-env/test_image:0/feature1] feature teardown',
95
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
96
+ '[testing-env/test_image:0/feature2] feature teardown',
97
+ '[testing-env/test_image:0] shutting_down -> offline',
98
+ '[testing-env/test_image:0] sandbox shutdown'
99
+ # pylint: enable=line-too-long
100
+ ]
101
+ )
102
+
103
+ def test_proactive_session_setup(self):
104
+ env = self._create_env(
105
+ features={
106
+ 'feature1': TestingFeature(setup_session_delay=0.1),
107
+ 'feature2': TestingFeature(),
108
+ },
109
+ pool_size=1,
110
+ proactive_session_setup=True,
111
+ )
112
+ self.assertTrue(env.enable_pooling('test_image'))
113
+ with env:
114
+ with env.sandbox('session1') as sb:
115
+ sb.shell('echo "hello"')
116
+ sb.wait_until_not(
117
+ (
118
+ interface.Sandbox.Status.IN_SESSION,
119
+ interface.Sandbox.Status.SETTING_UP
120
+ )
121
+ )
122
+ self.assertEqual(sb.status, interface.Sandbox.Status.READY)
123
+ self.assertEqual(
124
+ self.event_handler.logs,
125
+ [
126
+ # pylint: disable=line-too-long
127
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup',
128
+ '[testing-env/test_image:0:0/feature1] feature setup',
129
+ '[testing-env/test_image:0:0@<idle>] shell: "feature2" setup',
130
+ '[testing-env/test_image:0:0/feature2] feature setup',
131
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup session',
132
+ '[testing-env/test_image:0:0/feature1@<idle>] feature setup session',
133
+ '[testing-env/test_image:0:0@<idle>] shell: "feature2" setup session',
134
+ '[testing-env/test_image:0:0/feature2@<idle>] feature setup session',
135
+ '[testing-env/test_image:0:0] created -> ready',
136
+ '[testing-env/test_image:0:0] sandbox started',
137
+ '[testing-env] environment started',
138
+ '[testing-env/test_image:0:0] ready -> acquired',
139
+ '[testing-env/test_image:0:0] acquired -> setting_up',
140
+ '[testing-env/test_image:0:0] setting_up -> in_session',
141
+ "[testing-env/test_image:0:0] session 'session1' started",
142
+ '[testing-env/test_image:0:0@session1] shell: echo "hello"',
143
+ '[testing-env/test_image:0:0] in_session -> exiting_session',
144
+ '[testing-env/test_image:0:0@session1] shell: "feature1" teardown session',
145
+ '[testing-env/test_image:0:0/feature1@session1] feature teardown session',
146
+ '[testing-env/test_image:0:0@session1] shell: "feature2" teardown session',
147
+ '[testing-env/test_image:0:0/feature2@session1] feature teardown session',
148
+ "[testing-env/test_image:0:0] session 'session1' ended",
149
+ '[testing-env/test_image:0:0] exiting_session -> setting_up',
150
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup session',
151
+ '[testing-env/test_image:0:0/feature1@<idle>] feature setup session',
152
+ '[testing-env/test_image:0:0@<idle>] shell: "feature2" setup session',
153
+ '[testing-env/test_image:0:0/feature2@<idle>] feature setup session',
154
+ '[testing-env/test_image:0:0] setting_up -> ready'
155
+ # pylint: enable=line-too-long
156
+ ]
157
+ )
158
+
159
+ def test_proactive_session_setup_with_setup_session_error(self):
160
+ env = self._create_env(
161
+ features={'test_feature': TestingFeature(setup_session_delay=0.5)},
162
+ pool_size=1,
163
+ housekeep_interval=10.0,
164
+ )
165
+ with env:
166
+ with env.sandbox('session1') as sb:
167
+ sb.test_feature.rebind(
168
+ simulate_setup_session_error=interface.SandboxStateError,
169
+ skip_notification=True
170
+ )
171
+ sb.wait_until_not(
172
+ (
173
+ interface.Sandbox.Status.SETTING_UP,
174
+ interface.Sandbox.Status.SHUTTING_DOWN
175
+ )
176
+ )
177
+ self.assertEqual(len(sb.state_errors), 1)
178
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
179
+ self.assertEqual(
180
+ self.event_handler.logs,
181
+ [
182
+ # pylint: disable=line-too-long
183
+ '[testing-env/test_image:0:0@<idle>] shell: "test_feature" setup',
184
+ '[testing-env/test_image:0:0/test_feature] feature setup',
185
+ '[testing-env/test_image:0:0@<idle>] shell: "test_feature" setup session',
186
+ '[testing-env/test_image:0:0/test_feature@<idle>] feature setup session',
187
+ '[testing-env/test_image:0:0] created -> ready',
188
+ '[testing-env/test_image:0:0] sandbox started',
189
+ '[testing-env] environment started',
190
+ '[testing-env/test_image:0:0] ready -> acquired',
191
+ '[testing-env/test_image:0:0] acquired -> setting_up',
192
+ '[testing-env/test_image:0:0] setting_up -> in_session',
193
+ "[testing-env/test_image:0:0] session 'session1' started",
194
+ '[testing-env/test_image:0:0] in_session -> exiting_session',
195
+ '[testing-env/test_image:0:0@session1] shell: "test_feature" teardown session',
196
+ '[testing-env/test_image:0:0/test_feature@session1] feature teardown session',
197
+ "[testing-env/test_image:0:0] session 'session1' ended",
198
+ '[testing-env/test_image:0:0] exiting_session -> setting_up',
199
+ '[testing-env/test_image:0:0/test_feature@<idle>] feature setup session with SandboxStateError',
200
+ '[testing-env/test_image:0:0] setting_up -> shutting_down',
201
+ '[testing-env/test_image:0:0@<idle>] shell: "test_feature" teardown',
202
+ '[testing-env/test_image:0:0/test_feature] feature teardown',
203
+ '[testing-env/test_image:0:0] shutting_down -> offline',
204
+ '[testing-env/test_image:0:0] sandbox shutdown'
205
+ # pylint: enable=line-too-long
206
+ ]
207
+ )
208
+
209
+ def test_sandbox_start_non_state_error(self):
210
+ env = self._create_env(
211
+ features={
212
+ 'feature1': TestingFeature(),
213
+ 'feature2': TestingFeature(),
214
+ },
215
+ simulate_start_error=ValueError,
216
+ )
217
+ with env:
218
+ with self.assertRaises(ValueError):
219
+ with env.sandbox('session1'):
220
+ pass
221
+ self.assertTrue(env.is_online)
222
+ self.assertEqual(
223
+ self.event_handler.logs,
224
+ [
225
+ '[testing-env] environment started',
226
+ '[testing-env/test_image:0] sandbox started with ValueError',
227
+ '[testing-env/test_image:0] created -> shutting_down',
228
+ '[testing-env/test_image:0] shutting_down -> offline',
229
+ '[testing-env/test_image:0] sandbox shutdown'
230
+ ]
231
+ )
232
+
233
+ def test_sandbox_start_state_error(self):
234
+ env = self._create_env(
235
+ features={
236
+ 'feature1': TestingFeature(),
237
+ 'feature2': TestingFeature(),
238
+ },
239
+ pool_size=1,
240
+ simulate_start_error=interface.SandboxStateError,
241
+ )
242
+ with self.assertRaises(interface.EnvironmentOutageError):
243
+ with env:
244
+ pass
245
+ self.assertEqual(
246
+ self.event_handler.logs,
247
+ [
248
+ # pylint: disable=line-too-long
249
+ '[testing-env/test_image:0:0] sandbox started with SandboxStateError',
250
+ '[testing-env/test_image:0:0] created -> shutting_down',
251
+ '[testing-env/test_image:0:0] shutting_down -> offline',
252
+ '[testing-env/test_image:0:0] sandbox shutdown',
253
+ '[testing-env] environment started with EnvironmentOutageError',
254
+ '[testing-env] environment shutdown'
255
+ # pylint: enable=line-too-long
256
+ ]
257
+ )
258
+
259
+ def test_sandbox_shutdown_non_state_error(self):
260
+ env = self._create_env(
261
+ features={
262
+ 'feature1': TestingFeature(),
263
+ 'feature2': TestingFeature(),
264
+ },
265
+ simulate_shutdown_error=ValueError,
266
+ )
267
+ with env:
268
+ with self.assertRaises(ValueError):
269
+ with env.sandbox('session1') as sb:
270
+ sb.shell('echo "hello"')
271
+ self.assertEqual(len(sb.state_errors), 0)
272
+ self.assertEqual(
273
+ self.event_handler.logs,
274
+ [
275
+ # pylint: disable=line-too-long
276
+ '[testing-env] environment started',
277
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
278
+ '[testing-env/test_image:0/feature1] feature setup',
279
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
280
+ '[testing-env/test_image:0/feature2] feature setup',
281
+ '[testing-env/test_image:0] created -> ready',
282
+ '[testing-env/test_image:0] sandbox started',
283
+ '[testing-env/test_image:0] ready -> acquired',
284
+ '[testing-env/test_image:0] acquired -> setting_up',
285
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
286
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
287
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
288
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
289
+ '[testing-env/test_image:0] setting_up -> in_session',
290
+ "[testing-env/test_image:0] session 'session1' started",
291
+ '[testing-env/test_image:0@session1] shell: echo "hello"',
292
+ '[testing-env/test_image:0] in_session -> exiting_session',
293
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown session',
294
+ '[testing-env/test_image:0/feature1@session1] feature teardown session',
295
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
296
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
297
+ "[testing-env/test_image:0] session 'session1' ended",
298
+ '[testing-env/test_image:0] exiting_session -> acquired',
299
+ '[testing-env/test_image:0] acquired -> shutting_down',
300
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
301
+ '[testing-env/test_image:0/feature1] feature teardown',
302
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
303
+ '[testing-env/test_image:0/feature2] feature teardown',
304
+ '[testing-env/test_image:0] shutting_down -> offline',
305
+ '[testing-env/test_image:0] sandbox shutdown with ValueError',
306
+ '[testing-env] environment shutdown',
307
+ # pylint: enable=line-too-long
308
+ ]
309
+ )
310
+
311
+ def test_env_shutdown_non_state_error(self):
312
+ env = self._create_env(
313
+ pool_size=1,
314
+ features={
315
+ 'feature1': TestingFeature(),
316
+ 'feature2': TestingFeature(),
317
+ },
318
+ simulate_shutdown_error=ValueError,
319
+ )
320
+ with self.assertRaises(ValueError):
321
+ with env:
322
+ pass
323
+
324
+ self.assertEqual(
325
+ self.event_handler.logs,
326
+ [
327
+ # pylint: disable=line-too-long
328
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup',
329
+ '[testing-env/test_image:0:0/feature1] feature setup',
330
+ '[testing-env/test_image:0:0@<idle>] shell: "feature2" setup',
331
+ '[testing-env/test_image:0:0/feature2] feature setup',
332
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup session',
333
+ '[testing-env/test_image:0:0/feature1@<idle>] feature setup session',
334
+ '[testing-env/test_image:0:0@<idle>] shell: "feature2" setup session',
335
+ '[testing-env/test_image:0:0/feature2@<idle>] feature setup session',
336
+ '[testing-env/test_image:0:0] created -> ready',
337
+ '[testing-env/test_image:0:0] sandbox started',
338
+ '[testing-env] environment started',
339
+ '[testing-env/test_image:0:0] ready -> shutting_down',
340
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" teardown',
341
+ '[testing-env/test_image:0:0/feature1] feature teardown',
342
+ '[testing-env/test_image:0:0@<idle>] shell: "feature2" teardown',
343
+ '[testing-env/test_image:0:0/feature2] feature teardown',
344
+ '[testing-env/test_image:0:0] shutting_down -> offline',
345
+ '[testing-env/test_image:0:0] sandbox shutdown with ValueError',
346
+ '[testing-env] environment shutdown with ValueError'
347
+ # pylint: enable=line-too-long
348
+ ]
349
+ )
350
+
351
+ def test_sandbox_shutdown_state_error(self):
352
+ env = self._create_env(
353
+ features={
354
+ 'feature1': TestingFeature(),
355
+ 'feature2': TestingFeature(),
356
+ },
357
+ simulate_shutdown_error=interface.SandboxStateError,
358
+ )
359
+ with env:
360
+ with env.sandbox('session1') as sb:
361
+ sb.shell('echo "hello"')
362
+ self.assertEqual(len(sb.state_errors), 1)
363
+
364
+ self.assertEqual(
365
+ self.event_handler.logs,
366
+ [
367
+ # pylint: disable=line-too-long
368
+ '[testing-env] environment started',
369
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
370
+ '[testing-env/test_image:0/feature1] feature setup',
371
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
372
+ '[testing-env/test_image:0/feature2] feature setup',
373
+ '[testing-env/test_image:0] created -> ready',
374
+ '[testing-env/test_image:0] sandbox started',
375
+ '[testing-env/test_image:0] ready -> acquired',
376
+ '[testing-env/test_image:0] acquired -> setting_up',
377
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
378
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
379
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
380
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
381
+ '[testing-env/test_image:0] setting_up -> in_session',
382
+ "[testing-env/test_image:0] session 'session1' started",
383
+ '[testing-env/test_image:0@session1] shell: echo "hello"',
384
+ '[testing-env/test_image:0] in_session -> exiting_session',
385
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown session',
386
+ '[testing-env/test_image:0/feature1@session1] feature teardown session',
387
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
388
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
389
+ "[testing-env/test_image:0] session 'session1' ended",
390
+ '[testing-env/test_image:0] exiting_session -> acquired',
391
+ '[testing-env/test_image:0] acquired -> shutting_down',
392
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
393
+ '[testing-env/test_image:0/feature1] feature teardown',
394
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
395
+ '[testing-env/test_image:0/feature2] feature teardown',
396
+ '[testing-env/test_image:0] shutting_down -> offline',
397
+ '[testing-env/test_image:0] sandbox shutdown with SandboxStateError',
398
+ '[testing-env] environment shutdown',
399
+ # pylint: enable=line-too-long
400
+ ]
401
+ )
402
+
403
+ def test_feature_setup_non_state_error(self):
404
+ env = self._create_env(
405
+ features={
406
+ 'feature1': TestingFeature(),
407
+ 'feature2': TestingFeature(
408
+ simulate_setup_error=ValueError
409
+ ),
410
+ },
411
+ )
412
+ with env:
413
+ with self.assertRaises(ValueError):
414
+ with env.sandbox('session1'):
415
+ pass
416
+ self.assertEqual(
417
+ self.event_handler.logs,
418
+ [
419
+ # pylint: disable=line-too-long
420
+ '[testing-env] environment started',
421
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
422
+ '[testing-env/test_image:0/feature1] feature setup',
423
+ '[testing-env/test_image:0/feature2] feature setup with ValueError',
424
+ '[testing-env/test_image:0] sandbox started with ValueError',
425
+ '[testing-env/test_image:0] created -> shutting_down',
426
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
427
+ '[testing-env/test_image:0/feature1] feature teardown',
428
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
429
+ '[testing-env/test_image:0/feature2] feature teardown',
430
+ '[testing-env/test_image:0] shutting_down -> offline',
431
+ '[testing-env/test_image:0] sandbox shutdown'
432
+ # pylint: enable=line-too-long
433
+ ]
434
+ )
435
+
436
+ def test_feature_setup_state_error(self):
437
+ env = self._create_env(
438
+ features={
439
+ 'feature1': TestingFeature(
440
+ simulate_setup_error=interface.SandboxStateError
441
+ ),
442
+ 'feature2': TestingFeature(),
443
+ },
444
+ )
445
+ with env:
446
+ with self.assertRaises(interface.EnvironmentOutageError):
447
+ with env.sandbox('session1'):
448
+ pass
449
+ self.assertEqual(
450
+ self.event_handler.logs,
451
+ [
452
+ # pylint: disable=line-too-long
453
+ '[testing-env] environment started',
454
+ '[testing-env/test_image:0/feature1] feature setup with SandboxStateError',
455
+ '[testing-env/test_image:0] sandbox started with SandboxStateError',
456
+ '[testing-env/test_image:0] created -> shutting_down',
457
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
458
+ '[testing-env/test_image:0/feature1] feature teardown',
459
+ '[testing-env/test_image:0] shutting_down -> offline',
460
+ '[testing-env/test_image:0] sandbox shutdown',
461
+ '[testing-env] environment shutdown',
462
+ # pylint: enable=line-too-long
463
+ ]
464
+ )
465
+
466
+ def test_feature_teardown_non_state_error(self):
467
+ env = self._create_env(
468
+ features={
469
+ 'feature1': TestingFeature(),
470
+ 'feature2': TestingFeature(
471
+ simulate_teardown_error=ValueError
472
+ ),
473
+ },
474
+ )
475
+ with env:
476
+ with self.assertRaises(interface.FeatureTeardownError):
477
+ with env.sandbox('session1'):
478
+ pass
479
+ self.assertEqual(
480
+ self.event_handler.logs,
481
+ [
482
+ # pylint: disable=line-too-long
483
+ '[testing-env] environment started',
484
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
485
+ '[testing-env/test_image:0/feature1] feature setup',
486
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
487
+ '[testing-env/test_image:0/feature2] feature setup',
488
+ '[testing-env/test_image:0] created -> ready',
489
+ '[testing-env/test_image:0] sandbox started',
490
+ '[testing-env/test_image:0] ready -> acquired',
491
+ '[testing-env/test_image:0] acquired -> setting_up',
492
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
493
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
494
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
495
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
496
+ '[testing-env/test_image:0] setting_up -> in_session',
497
+ "[testing-env/test_image:0] session 'session1' started",
498
+ '[testing-env/test_image:0] in_session -> exiting_session',
499
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown session',
500
+ '[testing-env/test_image:0/feature1@session1] feature teardown session',
501
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
502
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
503
+ "[testing-env/test_image:0] session 'session1' ended",
504
+ '[testing-env/test_image:0] exiting_session -> acquired',
505
+ '[testing-env/test_image:0] acquired -> shutting_down',
506
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
507
+ '[testing-env/test_image:0/feature1] feature teardown',
508
+ '[testing-env/test_image:0/feature2] feature teardown with ValueError',
509
+ '[testing-env/test_image:0] shutting_down -> offline',
510
+ '[testing-env/test_image:0] sandbox shutdown with FeatureTeardownError',
511
+ # pylint: enable=line-too-long
512
+ ]
513
+ )
514
+
515
+ def test_feature_teardown_state_error(self):
516
+ env = self._create_env(
517
+ features={
518
+ 'feature1': TestingFeature(
519
+ simulate_teardown_error=interface.SandboxStateError
520
+ ),
521
+ 'feature2': TestingFeature(
522
+ ),
523
+ },
524
+ )
525
+ with env:
526
+ with env.sandbox('session1') as sb:
527
+ pass
528
+ self.assertEqual(len(sb.state_errors), 1)
529
+ self.assertEqual(
530
+ self.event_handler.logs,
531
+ [
532
+ # pylint: disable=line-too-long
533
+ '[testing-env] environment started',
534
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
535
+ '[testing-env/test_image:0/feature1] feature setup',
536
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
537
+ '[testing-env/test_image:0/feature2] feature setup',
538
+ '[testing-env/test_image:0] created -> ready',
539
+ '[testing-env/test_image:0] sandbox started',
540
+ '[testing-env/test_image:0] ready -> acquired',
541
+ '[testing-env/test_image:0] acquired -> setting_up',
542
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
543
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
544
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
545
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
546
+ '[testing-env/test_image:0] setting_up -> in_session',
547
+ "[testing-env/test_image:0] session 'session1' started",
548
+ '[testing-env/test_image:0] in_session -> exiting_session',
549
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown session',
550
+ '[testing-env/test_image:0/feature1@session1] feature teardown session',
551
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
552
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
553
+ "[testing-env/test_image:0] session 'session1' ended",
554
+ '[testing-env/test_image:0] exiting_session -> acquired',
555
+ '[testing-env/test_image:0] acquired -> shutting_down',
556
+ '[testing-env/test_image:0/feature1] feature teardown with SandboxStateError',
557
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
558
+ '[testing-env/test_image:0/feature2] feature teardown',
559
+ '[testing-env/test_image:0] shutting_down -> offline',
560
+ '[testing-env/test_image:0] sandbox shutdown with FeatureTeardownError',
561
+ # pylint: enable=line-too-long
562
+ ]
563
+ )
564
+
565
+ def test_feature_setup_session_non_state_error(self):
566
+ env = self._create_env(
567
+ features={
568
+ 'feature1': TestingFeature(),
569
+ 'feature2': TestingFeature(
570
+ simulate_setup_session_error=ValueError
571
+ ),
572
+ },
573
+ )
574
+ with env:
575
+ with self.assertRaises(ValueError):
576
+ with env.sandbox('session1') as sb:
577
+ sb.shell('echo "hello"')
578
+ self.assertEqual(
579
+ self.event_handler.logs,
580
+ [
581
+ # pylint: disable=line-too-long
582
+ '[testing-env] environment started',
583
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
584
+ '[testing-env/test_image:0/feature1] feature setup',
585
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
586
+ '[testing-env/test_image:0/feature2] feature setup',
587
+ '[testing-env/test_image:0] created -> ready',
588
+ '[testing-env/test_image:0] sandbox started',
589
+ '[testing-env/test_image:0] ready -> acquired',
590
+ '[testing-env/test_image:0] acquired -> setting_up',
591
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
592
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
593
+ '[testing-env/test_image:0/feature2@session1] feature setup session with ValueError',
594
+ "[testing-env/test_image:0] session 'session1' started with ValueError",
595
+ '[testing-env/test_image:0] setting_up -> shutting_down',
596
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown',
597
+ '[testing-env/test_image:0/feature1] feature teardown',
598
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown',
599
+ '[testing-env/test_image:0/feature2] feature teardown',
600
+ '[testing-env/test_image:0] shutting_down -> offline',
601
+ '[testing-env/test_image:0] sandbox shutdown'
602
+ # pylint: enable=line-too-long
603
+ ]
604
+ )
605
+
606
+ def test_feature_teardown_session_non_state_error(self):
607
+ env = self._create_env(
608
+ features={
609
+ 'feature1': TestingFeature(
610
+ simulate_teardown_session_error=ValueError
611
+ ),
612
+ 'feature2': TestingFeature(),
613
+ },
614
+ )
615
+ with env:
616
+ with self.assertRaises(interface.SessionTeardownError):
617
+ with env.sandbox('session1') as sb:
618
+ sb.shell('echo "hello"')
619
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
620
+ self.assertEqual(
621
+ self.event_handler.logs,
622
+ [
623
+ # pylint: disable=line-too-long
624
+ '[testing-env] environment started',
625
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
626
+ '[testing-env/test_image:0/feature1] feature setup',
627
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
628
+ '[testing-env/test_image:0/feature2] feature setup',
629
+ '[testing-env/test_image:0] created -> ready',
630
+ '[testing-env/test_image:0] sandbox started',
631
+ '[testing-env/test_image:0] ready -> acquired',
632
+ '[testing-env/test_image:0] acquired -> setting_up',
633
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
634
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
635
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
636
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
637
+ '[testing-env/test_image:0] setting_up -> in_session',
638
+ "[testing-env/test_image:0] session 'session1' started",
639
+ '[testing-env/test_image:0@session1] shell: echo "hello"',
640
+ '[testing-env/test_image:0] in_session -> exiting_session',
641
+ '[testing-env/test_image:0/feature1@session1] feature teardown session with ValueError',
642
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
643
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
644
+ "[testing-env/test_image:0] session 'session1' ended",
645
+ '[testing-env/test_image:0] exiting_session -> acquired',
646
+ '[testing-env/test_image:0] acquired -> shutting_down',
647
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
648
+ '[testing-env/test_image:0/feature1] feature teardown',
649
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
650
+ '[testing-env/test_image:0/feature2] feature teardown',
651
+ '[testing-env/test_image:0] shutting_down -> offline',
652
+ '[testing-env/test_image:0] sandbox shutdown',
653
+ # pylint: enable=line-too-long
654
+ ]
655
+ )
656
+
657
+ def test_feature_teardown_session_state_error(self):
658
+ env = self._create_env(
659
+ features={
660
+ 'feature1': TestingFeature(
661
+ simulate_teardown_session_error=interface.SandboxStateError
662
+ ),
663
+ 'feature2': TestingFeature(),
664
+ },
665
+ )
666
+ with env:
667
+ with env.sandbox('session1') as sb:
668
+ sb.shell('echo "hello"')
669
+ self.assertEqual(len(sb.state_errors), 1)
670
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
671
+ self.assertEqual(
672
+ self.event_handler.logs,
673
+ [
674
+ # pylint: disable=line-too-long
675
+ '[testing-env] environment started',
676
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
677
+ '[testing-env/test_image:0/feature1] feature setup',
678
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
679
+ '[testing-env/test_image:0/feature2] feature setup',
680
+ '[testing-env/test_image:0] created -> ready',
681
+ '[testing-env/test_image:0] sandbox started',
682
+ '[testing-env/test_image:0] ready -> acquired',
683
+ '[testing-env/test_image:0] acquired -> setting_up',
684
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
685
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
686
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
687
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
688
+ '[testing-env/test_image:0] setting_up -> in_session',
689
+ "[testing-env/test_image:0] session 'session1' started",
690
+ '[testing-env/test_image:0@session1] shell: echo "hello"',
691
+ '[testing-env/test_image:0] in_session -> exiting_session',
692
+ '[testing-env/test_image:0/feature1@session1] feature teardown session with SandboxStateError',
693
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
694
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
695
+ "[testing-env/test_image:0] session 'session1' ended with SandboxStateError",
696
+ '[testing-env/test_image:0] exiting_session -> acquired',
697
+ '[testing-env/test_image:0] acquired -> shutting_down',
698
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
699
+ '[testing-env/test_image:0/feature1] feature teardown',
700
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
701
+ '[testing-env/test_image:0/feature2] feature teardown',
702
+ '[testing-env/test_image:0] shutting_down -> offline',
703
+ '[testing-env/test_image:0] sandbox shutdown',
704
+ # pylint: enable=line-too-long
705
+ ]
706
+ )
707
+
708
+ def test_feature_teardown_session_calling_end_session(self):
709
+ env = self._create_env(
710
+ features={
711
+ 'feature1': TestingFeature(
712
+ call_end_session_on_teardown_session=True
713
+ ),
714
+ 'feature2': TestingFeature(),
715
+ },
716
+ )
717
+ with env:
718
+ with env.sandbox('session1') as sb:
719
+ sb.shell('echo "hello"')
720
+ self.assertEqual(
721
+ self.event_handler.logs,
722
+ [
723
+ # pylint: disable=line-too-long
724
+ '[testing-env] environment started',
725
+ '[testing-env/test_image:0@<idle>] shell: "feature1" setup',
726
+ '[testing-env/test_image:0/feature1] feature setup',
727
+ '[testing-env/test_image:0@<idle>] shell: "feature2" setup',
728
+ '[testing-env/test_image:0/feature2] feature setup',
729
+ '[testing-env/test_image:0] created -> ready',
730
+ '[testing-env/test_image:0] sandbox started',
731
+ '[testing-env/test_image:0] ready -> acquired',
732
+ '[testing-env/test_image:0] acquired -> setting_up',
733
+ '[testing-env/test_image:0@session1] shell: "feature1" setup session',
734
+ '[testing-env/test_image:0/feature1@session1] feature setup session',
735
+ '[testing-env/test_image:0@session1] shell: "feature2" setup session',
736
+ '[testing-env/test_image:0/feature2@session1] feature setup session',
737
+ '[testing-env/test_image:0] setting_up -> in_session',
738
+ "[testing-env/test_image:0] session 'session1' started",
739
+ '[testing-env/test_image:0@session1] shell: echo "hello"',
740
+ '[testing-env/test_image:0] in_session -> exiting_session',
741
+ '[testing-env/test_image:0@session1] shell: "feature1" teardown session',
742
+ '[testing-env/test_image:0/feature1@session1] feature teardown session',
743
+ '[testing-env/test_image:0@session1] shell: "feature2" teardown session',
744
+ '[testing-env/test_image:0/feature2@session1] feature teardown session',
745
+ "[testing-env/test_image:0] session 'session1' ended",
746
+ '[testing-env/test_image:0] exiting_session -> acquired',
747
+ '[testing-env/test_image:0] acquired -> shutting_down',
748
+ '[testing-env/test_image:0@<idle>] shell: "feature1" teardown',
749
+ '[testing-env/test_image:0/feature1] feature teardown',
750
+ '[testing-env/test_image:0@<idle>] shell: "feature2" teardown',
751
+ '[testing-env/test_image:0/feature2] feature teardown',
752
+ '[testing-env/test_image:0] shutting_down -> offline',
753
+ '[testing-env/test_image:0] sandbox shutdown'
754
+ # pylint: enable=line-too-long
755
+ ]
756
+ )
757
+
758
+ def test_session_activity_non_state_error(self):
759
+ env = self._create_env(
760
+ pool_size=1,
761
+ features={
762
+ 'feature1': TestingFeature(),
763
+ },
764
+ )
765
+ with env:
766
+ with env.sandbox('session1') as sb:
767
+ with self.assertRaises(ValueError):
768
+ sb.shell('echo foo', raise_error=ValueError)
769
+ self.assertEqual(len(sb.state_errors), 0)
770
+ sb.shell('echo bar')
771
+ self.assertEqual(sb.status, interface.Sandbox.Status.IN_SESSION)
772
+ sb.wait_until_not(interface.Sandbox.Status.SETTING_UP)
773
+ self.assertEqual(sb.status, interface.Sandbox.Status.READY)
774
+ self.assertEqual(
775
+ self.event_handler.logs,
776
+ [
777
+ # pylint: disable=line-too-long
778
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup',
779
+ '[testing-env/test_image:0:0/feature1] feature setup',
780
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup session',
781
+ '[testing-env/test_image:0:0/feature1@<idle>] feature setup session',
782
+ '[testing-env/test_image:0:0] created -> ready',
783
+ '[testing-env/test_image:0:0] sandbox started',
784
+ '[testing-env] environment started',
785
+ '[testing-env/test_image:0:0] ready -> acquired',
786
+ '[testing-env/test_image:0:0] acquired -> setting_up',
787
+ '[testing-env/test_image:0:0] setting_up -> in_session',
788
+ "[testing-env/test_image:0:0] session 'session1' started",
789
+ '[testing-env/test_image:0:0@session1] shell: echo foo with ValueError',
790
+ '[testing-env/test_image:0:0@session1] shell: echo bar',
791
+ '[testing-env/test_image:0:0] in_session -> exiting_session',
792
+ '[testing-env/test_image:0:0@session1] shell: "feature1" teardown session',
793
+ '[testing-env/test_image:0:0/feature1@session1] feature teardown session',
794
+ "[testing-env/test_image:0:0] session 'session1' ended",
795
+ '[testing-env/test_image:0:0] exiting_session -> setting_up',
796
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup session',
797
+ '[testing-env/test_image:0:0/feature1@<idle>] feature setup session',
798
+ '[testing-env/test_image:0:0] setting_up -> ready',
799
+ # pylint: enable=line-too-long
800
+ ]
801
+ )
802
+
803
+ def test_session_activity_state_error(self):
804
+ env = self._create_env(
805
+ pool_size=1,
806
+ features={
807
+ 'feature1': TestingFeature(),
808
+ },
809
+ )
810
+ with env:
811
+ with self.assertRaises(interface.SandboxStateError):
812
+ with env.sandbox('session1') as sb:
813
+ sb.shell('echo foo', raise_error=RuntimeError)
814
+ self.assertEqual(len(sb.state_errors), 1)
815
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
816
+ self.assertEqual(
817
+ self.event_handler.logs,
818
+ [
819
+ # pylint: disable=line-too-long
820
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup',
821
+ '[testing-env/test_image:0:0/feature1] feature setup',
822
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" setup session',
823
+ '[testing-env/test_image:0:0/feature1@<idle>] feature setup session',
824
+ '[testing-env/test_image:0:0] created -> ready',
825
+ '[testing-env/test_image:0:0] sandbox started',
826
+ '[testing-env] environment started',
827
+ '[testing-env/test_image:0:0] ready -> acquired',
828
+ '[testing-env/test_image:0:0] acquired -> setting_up',
829
+ '[testing-env/test_image:0:0] setting_up -> in_session',
830
+ "[testing-env/test_image:0:0] session 'session1' started",
831
+ '[testing-env/test_image:0:0@session1] shell: echo foo with RuntimeError',
832
+ '[testing-env/test_image:0:0] in_session -> exiting_session',
833
+ '[testing-env/test_image:0:0@session1] shell: "feature1" teardown session',
834
+ '[testing-env/test_image:0:0/feature1@session1] feature teardown session',
835
+ "[testing-env/test_image:0:0] session 'session1' ended with SandboxStateError",
836
+ '[testing-env/test_image:0:0] exiting_session -> acquired',
837
+ '[testing-env/test_image:0:0] acquired -> shutting_down',
838
+ '[testing-env/test_image:0:0@<idle>] shell: "feature1" teardown',
839
+ '[testing-env/test_image:0:0/feature1] feature teardown',
840
+ '[testing-env/test_image:0:0] shutting_down -> offline',
841
+ '[testing-env/test_image:0:0] sandbox shutdown',
842
+ # pylint: enable=line-too-long
843
+ ]
844
+ )
845
+
846
+
847
+ class SandboxActivityTests(unittest.TestCase):
848
+
849
+ def test_session_id(self):
850
+ env = TestingEnvironment(
851
+ features={'test_feature': TestingFeature()},
852
+ pool_size=0
853
+ )
854
+ with env:
855
+ with env.sandbox() as sb:
856
+ self.assertRegex(sb.session_id, r'session-[0-9a-f]{7}')
857
+
858
+ with env.test_feature() as test_feature:
859
+ self.assertIsInstance(test_feature, TestingFeature)
860
+ self.assertRegex(
861
+ test_feature.session_id,
862
+ r'test_feature-session-[0-9a-f]{7}'
863
+ )
864
+
865
+ def test_ping_error(self):
866
+ env = TestingEnvironment(
867
+ features={'test_feature': TestingFeature(housekeep_interval=0)},
868
+ pool_size=1,
869
+ sandbox_keepalive_interval=0,
870
+ )
871
+ with env:
872
+ with env.sandbox('session1') as sb:
873
+ sb.rebind(
874
+ simulate_ping_error=interface.SandboxStateError,
875
+ skip_notification=True
876
+ )
877
+ sb.wait_until_next_housekeep()
878
+ self.assertIn(sb.status, (sb.Status.SHUTTING_DOWN, sb.Status.OFFLINE))
879
+
880
+ def test_housekeep_error(self):
881
+ event_handler = TestingEventHandler(log_housekeep=False)
882
+ env = TestingEnvironment(
883
+ features={'test_feature': TestingFeature(housekeep_interval=0)},
884
+ pool_size=1,
885
+ housekeep_interval=1.0,
886
+ outage_grace_period=0,
887
+ outage_retry_interval=0.1,
888
+ sandbox_keepalive_interval=0,
889
+ event_handler=event_handler,
890
+ )
891
+ with env:
892
+ with env.sandbox('session1') as sb:
893
+ self.assertEqual(len(env.sandbox_pool), 1)
894
+ self.assertEqual(sb.status, interface.Sandbox.Status.IN_SESSION)
895
+ self.assertEqual(sb.session_id, 'session1')
896
+ housekeep_count = sb.housekeep_counter
897
+ sb.test_feature.rebind(
898
+ simulate_housekeep_error=interface.SandboxStateError,
899
+ skip_notification=True
900
+ )
901
+ while sb.housekeep_counter == housekeep_count or (
902
+ sb.status == interface.Sandbox.Status.IN_SESSION
903
+ ):
904
+ time.sleep(0.01)
905
+ time.sleep(1.0)
906
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
907
+ env.wait_for_housekeeping()
908
+ self.assertEqual(
909
+ event_handler.logs,
910
+ [
911
+ # pylint: disable=line-too-long
912
+ '[testing-env/test_image:0:0@<idle>] shell: "test_feature" setup',
913
+ '[testing-env/test_image:0:0/test_feature] feature setup',
914
+ '[testing-env/test_image:0:0@<idle>] shell: "test_feature" setup session',
915
+ '[testing-env/test_image:0:0] sandbox started',
916
+ '[testing-env] environment started',
917
+ "[testing-env/test_image:0:0] session 'session1' started",
918
+ '[testing-env/test_image:0:0@session1] shell: "test_feature" teardown session',
919
+ "[testing-env/test_image:0:0] session 'session1' ended with SandboxStateError",
920
+ '[testing-env/test_image:0:0@<idle>] shell: "test_feature" teardown',
921
+ '[testing-env/test_image:0:0/test_feature] feature teardown',
922
+ '[testing-env/test_image:0:0] sandbox shutdown',
923
+ '[testing-env/test_image:0:1@<idle>] shell: "test_feature" setup',
924
+ '[testing-env/test_image:0:1/test_feature] feature setup',
925
+ '[testing-env/test_image:0:1@<idle>] shell: "test_feature" setup session',
926
+ '[testing-env/test_image:0:1] sandbox started',
927
+ '[testing-env/test_image:0:1@<idle>] shell: "test_feature" teardown',
928
+ '[testing-env/test_image:0:1/test_feature] feature teardown',
929
+ '[testing-env/test_image:0:1] sandbox shutdown',
930
+ '[testing-env] environment shutdown'
931
+ # pylint: enable=line-too-long
932
+ ]
933
+ )
934
+
935
+
936
+ class SandboxServiceTests(unittest.TestCase):
937
+
938
+ def setUp(self):
939
+ super().setUp()
940
+ self.maxDiff = None
941
+ self.event_handler = TestingEventHandler()
942
+ self.env = TestingEnvironment(
943
+ features={'test_feature': TestingFeature()},
944
+ pool_size=0,
945
+ outage_grace_period=0,
946
+ outage_retry_interval=0,
947
+ sandbox_keepalive_interval=0,
948
+ event_handler=self.event_handler,
949
+ random_seed=1,
950
+ )
951
+
952
+ def test_service_call_activity_log(self):
953
+
954
+ class CustomEventHandler(interface.EventHandler):
955
+
956
+ def __init__(self):
957
+ self.calls = []
958
+
959
+ def on_sandbox_activity(
960
+ self,
961
+ name: str,
962
+ sandbox: interface.Sandbox,
963
+ session_id: str | None,
964
+ duration: float,
965
+ error: BaseException | None,
966
+ **kwargs: Any):
967
+ self.calls.append((session_id, name, kwargs))
968
+
969
+ def on_feature_activity(
970
+ self,
971
+ name: str,
972
+ feature: interface.Feature,
973
+ session_id: str | None,
974
+ duration: float,
975
+ error: BaseException | None,
976
+ **kwargs: Any):
977
+ self.calls.append((session_id, name, kwargs))
978
+
979
+ event_handler = CustomEventHandler()
980
+ env = TestingEnvironment(
981
+ features={'test_feature': TestingFeature()},
982
+ pool_size=0,
983
+ event_handler=event_handler,
984
+ )
985
+ with env:
986
+ with env.test_feature(session_id='session1') as test_feature:
987
+ test_feature.call_with_varargs('sum', 1, 2, debug=True)
988
+ self.assertEqual(
989
+ event_handler.calls,
990
+ [
991
+ (None, 'shell', {'code': '"test_feature" setup'}),
992
+ ('session1', 'shell', {'code': '"test_feature" setup session'}),
993
+ ('session1', 'test_feature.call_with_varargs', {'args': (1, 2), 'code': 'sum', 'debug': True}), # pylint: disable=line-too-long
994
+ ('session1', 'shell', {'code': '"test_feature" teardown session'}),
995
+ (None, 'shell', {'code': '"test_feature" teardown'}),
996
+ ]
997
+ )
998
+
999
+ def test_service_call_from_feature(self):
1000
+ with self.env:
1001
+ with self.env.sandbox('session1') as sb:
1002
+ self.assertEqual(sb.test_feature.num_shell_calls(), 2)
1003
+ self.assertEqual(sb.test_feature.num_shell_calls(), 2)
1004
+ self.assertEqual(
1005
+ self.event_handler.logs,
1006
+ [
1007
+ # pylint: disable=line-too-long
1008
+ '[testing-env] environment started',
1009
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1010
+ '[testing-env/test_image:0/test_feature] feature setup',
1011
+ '[testing-env/test_image:0] sandbox started',
1012
+ '[testing-env/test_image:0@session1] shell: "test_feature" setup session',
1013
+ "[testing-env/test_image:0] session 'session1' started",
1014
+ '[testing-env/test_image:0/test_feature@session1] test_feature.num_shell_calls: None',
1015
+ '[testing-env/test_image:0/test_feature@session1] test_feature.num_shell_calls: None',
1016
+ '[testing-env/test_image:0@session1] shell: "test_feature" teardown session',
1017
+ "[testing-env/test_image:0] session 'session1' ended",
1018
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1019
+ '[testing-env/test_image:0/test_feature] feature teardown',
1020
+ '[testing-env/test_image:0] sandbox shutdown',
1021
+ '[testing-env] environment shutdown',
1022
+ # pylint: enable=line-too-long
1023
+ ]
1024
+ )
1025
+
1026
+ def test_service_call_from_feature_with_error(self):
1027
+ with self.env:
1028
+ with self.assertRaises(interface.SandboxStateError):
1029
+ with self.env.sandbox('session1') as sb:
1030
+ sb.test_feature.bad_shell_call()
1031
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
1032
+ self.assertEqual(len(sb.state_errors), 1)
1033
+ self.assertEqual(
1034
+ self.event_handler.logs,
1035
+ [
1036
+ # pylint: disable=line-too-long
1037
+ '[testing-env] environment started',
1038
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1039
+ '[testing-env/test_image:0/test_feature] feature setup',
1040
+ '[testing-env/test_image:0] sandbox started',
1041
+ '[testing-env/test_image:0@session1] shell: "test_feature" setup session',
1042
+ "[testing-env/test_image:0] session 'session1' started",
1043
+ '[testing-env/test_image:0@session1] shell: bad command with RuntimeError',
1044
+ '[testing-env/test_image:0/test_feature@session1] test_feature.bad_shell_call: None with SandboxStateError',
1045
+ '[testing-env/test_image:0@session1] shell: "test_feature" teardown session',
1046
+ "[testing-env/test_image:0] session 'session1' ended with SandboxStateError",
1047
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1048
+ '[testing-env/test_image:0/test_feature] feature teardown',
1049
+ '[testing-env/test_image:0] sandbox shutdown',
1050
+ '[testing-env] environment shutdown'
1051
+ # pylint: enable=line-too-long
1052
+ ]
1053
+ )
1054
+
1055
+ def test_service_call_from_environment(self):
1056
+ with self.env:
1057
+ with self.env.test_feature() as test_feature:
1058
+ self.assertEqual(test_feature.num_shell_calls(), 2)
1059
+ self.assertEqual(
1060
+ self.event_handler.logs,
1061
+ [
1062
+ # pylint: disable=line-too-long
1063
+ '[testing-env] environment started',
1064
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1065
+ '[testing-env/test_image:0/test_feature] feature setup',
1066
+ '[testing-env/test_image:0] sandbox started',
1067
+ '[testing-env/test_image:0@test_feature-session-2291d8c] shell: "test_feature" setup session',
1068
+ "[testing-env/test_image:0] session 'test_feature-session-2291d8c' started",
1069
+ '[testing-env/test_image:0/test_feature@test_feature-session-2291d8c] test_feature.num_shell_calls: None',
1070
+ '[testing-env/test_image:0@test_feature-session-2291d8c] shell: "test_feature" teardown session',
1071
+ "[testing-env/test_image:0] session 'test_feature-session-2291d8c' ended",
1072
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1073
+ '[testing-env/test_image:0/test_feature] feature teardown',
1074
+ '[testing-env/test_image:0] sandbox shutdown',
1075
+ '[testing-env] environment shutdown'
1076
+ # pylint: enable=line-too-long
1077
+ ]
1078
+ )
1079
+
1080
+ def test_service_call_from_environment_with_error(self):
1081
+ with self.env:
1082
+ with self.assertRaises(interface.SandboxStateError):
1083
+ with self.env.test_feature(session_id='session1') as test_feature:
1084
+ test_feature.bad_shell_call()
1085
+ self.assertEqual(
1086
+ self.event_handler.logs,
1087
+ [
1088
+ # pylint: disable=line-too-long
1089
+ '[testing-env] environment started',
1090
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1091
+ '[testing-env/test_image:0/test_feature] feature setup',
1092
+ '[testing-env/test_image:0] sandbox started',
1093
+ '[testing-env/test_image:0@session1] shell: "test_feature" setup session',
1094
+ "[testing-env/test_image:0] session 'session1' started",
1095
+ '[testing-env/test_image:0@session1] shell: bad command with RuntimeError',
1096
+ '[testing-env/test_image:0/test_feature@session1] test_feature.bad_shell_call: None with SandboxStateError',
1097
+ '[testing-env/test_image:0@session1] shell: "test_feature" teardown session',
1098
+ "[testing-env/test_image:0] session 'session1' ended with SandboxStateError",
1099
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1100
+ '[testing-env/test_image:0/test_feature] feature teardown',
1101
+ '[testing-env/test_image:0] sandbox shutdown',
1102
+ '[testing-env] environment shutdown',
1103
+ # pylint: enable=line-too-long
1104
+ ]
1105
+ )
1106
+
1107
+ def test_service_context_manager_from_feature(self):
1108
+ with self.env:
1109
+ with self.env.sandbox('session1') as sb:
1110
+ with sb.test_feature.my_service() as service:
1111
+ service.do('hello')
1112
+ sb.shell('foo')
1113
+ self.assertEqual(sb.status, interface.Sandbox.Status.IN_SESSION)
1114
+ self.assertEqual(
1115
+ self.event_handler.logs,
1116
+ [
1117
+ # pylint: disable=line-too-long
1118
+ '[testing-env] environment started',
1119
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1120
+ '[testing-env/test_image:0/test_feature] feature setup',
1121
+ '[testing-env/test_image:0] sandbox started',
1122
+ '[testing-env/test_image:0@session1] shell: "test_feature" setup session',
1123
+ "[testing-env/test_image:0] session 'session1' started",
1124
+ '[testing-env/test_image:0@session1] shell: hello',
1125
+ '[testing-env/test_image:0@session1] shell: foo',
1126
+ '[testing-env/test_image:0@session1] shell: "test_feature" teardown session',
1127
+ "[testing-env/test_image:0] session 'session1' ended",
1128
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1129
+ '[testing-env/test_image:0/test_feature] feature teardown',
1130
+ '[testing-env/test_image:0] sandbox shutdown',
1131
+ '[testing-env] environment shutdown',
1132
+ # pylint: enable=line-too-long
1133
+ ]
1134
+ )
1135
+
1136
+ def test_service_context_manager_from_feature_with_error(self):
1137
+ with self.env:
1138
+ with self.assertRaises(interface.SandboxStateError):
1139
+ with self.env.sandbox('session1') as sb:
1140
+ with sb.test_feature.my_service() as service:
1141
+ service.do('hello', raise_error=interface.SandboxStateError)
1142
+ self.assertEqual(sb.status, interface.Sandbox.Status.OFFLINE)
1143
+ self.assertEqual(len(sb.state_errors), 1)
1144
+ self.assertEqual(
1145
+ self.event_handler.logs,
1146
+ [
1147
+ # pylint: disable=line-too-long
1148
+ '[testing-env] environment started',
1149
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1150
+ '[testing-env/test_image:0/test_feature] feature setup',
1151
+ '[testing-env/test_image:0] sandbox started',
1152
+ '[testing-env/test_image:0@session1] shell: "test_feature" setup session',
1153
+ "[testing-env/test_image:0] session 'session1' started",
1154
+ '[testing-env/test_image:0@session1] shell: hello with SandboxStateError',
1155
+ '[testing-env/test_image:0@session1] shell: "test_feature" teardown session',
1156
+ "[testing-env/test_image:0] session 'session1' ended with SandboxStateError",
1157
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1158
+ '[testing-env/test_image:0/test_feature] feature teardown',
1159
+ '[testing-env/test_image:0] sandbox shutdown',
1160
+ '[testing-env] environment shutdown',
1161
+ # pylint: enable=line-too-long
1162
+ ]
1163
+ )
1164
+
1165
+ def test_service_context_manager_from_environment(self):
1166
+ with self.env:
1167
+ with self.env.test_feature(session_id='session1') as test_feature:
1168
+ with test_feature.my_service() as service:
1169
+ service.do('foo')
1170
+
1171
+ with self.env.test_feature() as test_feature:
1172
+ with test_feature.my_service() as service:
1173
+ service.do('bar')
1174
+ self.assertEqual(
1175
+ self.event_handler.logs,
1176
+ [
1177
+ # pylint: disable=line-too-long
1178
+ '[testing-env] environment started',
1179
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1180
+ '[testing-env/test_image:0/test_feature] feature setup',
1181
+ '[testing-env/test_image:0] sandbox started',
1182
+ '[testing-env/test_image:0@session1] shell: "test_feature" setup session',
1183
+ "[testing-env/test_image:0] session 'session1' started",
1184
+ '[testing-env/test_image:0@session1] shell: foo',
1185
+ '[testing-env/test_image:0@session1] shell: "test_feature" teardown session',
1186
+ "[testing-env/test_image:0] session 'session1' ended",
1187
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1188
+ '[testing-env/test_image:0/test_feature] feature teardown',
1189
+ '[testing-env/test_image:0] sandbox shutdown',
1190
+ '[testing-env/test_image:1@<idle>] shell: "test_feature" setup',
1191
+ '[testing-env/test_image:1/test_feature] feature setup',
1192
+ '[testing-env/test_image:1] sandbox started',
1193
+ '[testing-env/test_image:1@test_feature-session-2291d8c] shell: "test_feature" setup session',
1194
+ "[testing-env/test_image:1] session 'test_feature-session-2291d8c' started",
1195
+ '[testing-env/test_image:1@test_feature-session-2291d8c] shell: bar',
1196
+ '[testing-env/test_image:1@test_feature-session-2291d8c] shell: "test_feature" teardown session',
1197
+ "[testing-env/test_image:1] session 'test_feature-session-2291d8c' ended",
1198
+ '[testing-env/test_image:1@<idle>] shell: "test_feature" teardown',
1199
+ '[testing-env/test_image:1/test_feature] feature teardown',
1200
+ '[testing-env/test_image:1] sandbox shutdown',
1201
+ '[testing-env] environment shutdown',
1202
+ # pylint: enable=line-too-long
1203
+ ]
1204
+ )
1205
+
1206
+ def test_service_context_manager_from_environment_with_error(self):
1207
+ with self.env:
1208
+ with self.assertRaises(interface.SandboxStateError):
1209
+ with self.env.test_feature() as test_feature:
1210
+ with test_feature.my_service() as service:
1211
+ service.do('hello', raise_error=interface.SandboxStateError)
1212
+ self.assertEqual(
1213
+ self.event_handler.logs,
1214
+ [
1215
+ # pylint: disable=line-too-long
1216
+ '[testing-env] environment started',
1217
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" setup',
1218
+ '[testing-env/test_image:0/test_feature] feature setup',
1219
+ '[testing-env/test_image:0] sandbox started',
1220
+ '[testing-env/test_image:0@test_feature-session-2291d8c] shell: "test_feature" setup session',
1221
+ "[testing-env/test_image:0] session 'test_feature-session-2291d8c' started",
1222
+ '[testing-env/test_image:0@test_feature-session-2291d8c] shell: hello with SandboxStateError',
1223
+ '[testing-env/test_image:0@test_feature-session-2291d8c] shell: "test_feature" teardown session',
1224
+ "[testing-env/test_image:0] session 'test_feature-session-2291d8c' ended with SandboxStateError",
1225
+ '[testing-env/test_image:0@<idle>] shell: "test_feature" teardown',
1226
+ '[testing-env/test_image:0/test_feature] feature teardown',
1227
+ '[testing-env/test_image:0] sandbox shutdown',
1228
+ '[testing-env] environment shutdown',
1229
+ # pylint: enable=line-too-long
1230
+ ]
1231
+ )
1232
+
1233
+
1234
+ if __name__ == '__main__':
1235
+ unittest.main()