fishertools 0.2.1__py3-none-any.whl → 0.4.0__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 (69) hide show
  1. fishertools/__init__.py +16 -5
  2. fishertools/errors/__init__.py +11 -3
  3. fishertools/errors/exception_types.py +282 -0
  4. fishertools/errors/explainer.py +87 -1
  5. fishertools/errors/models.py +73 -1
  6. fishertools/errors/patterns.py +40 -0
  7. fishertools/examples/cli_example.py +156 -0
  8. fishertools/examples/learn_example.py +65 -0
  9. fishertools/examples/logger_example.py +176 -0
  10. fishertools/examples/menu_example.py +101 -0
  11. fishertools/examples/storage_example.py +175 -0
  12. fishertools/input_utils.py +185 -0
  13. fishertools/learn/__init__.py +19 -2
  14. fishertools/learn/examples.py +88 -1
  15. fishertools/learn/knowledge_engine.py +321 -0
  16. fishertools/learn/repl/__init__.py +19 -0
  17. fishertools/learn/repl/cli.py +31 -0
  18. fishertools/learn/repl/code_sandbox.py +229 -0
  19. fishertools/learn/repl/command_handler.py +544 -0
  20. fishertools/learn/repl/command_parser.py +165 -0
  21. fishertools/learn/repl/engine.py +479 -0
  22. fishertools/learn/repl/models.py +121 -0
  23. fishertools/learn/repl/session_manager.py +284 -0
  24. fishertools/learn/repl/test_code_sandbox.py +261 -0
  25. fishertools/learn/repl/test_code_sandbox_pbt.py +148 -0
  26. fishertools/learn/repl/test_command_handler.py +224 -0
  27. fishertools/learn/repl/test_command_handler_pbt.py +189 -0
  28. fishertools/learn/repl/test_command_parser.py +160 -0
  29. fishertools/learn/repl/test_command_parser_pbt.py +100 -0
  30. fishertools/learn/repl/test_engine.py +190 -0
  31. fishertools/learn/repl/test_session_manager.py +310 -0
  32. fishertools/learn/repl/test_session_manager_pbt.py +182 -0
  33. fishertools/learn/test_knowledge_engine.py +241 -0
  34. fishertools/learn/test_knowledge_engine_pbt.py +180 -0
  35. fishertools/patterns/__init__.py +46 -0
  36. fishertools/patterns/cli.py +175 -0
  37. fishertools/patterns/logger.py +140 -0
  38. fishertools/patterns/menu.py +99 -0
  39. fishertools/patterns/storage.py +127 -0
  40. fishertools/readme_transformer.py +631 -0
  41. fishertools/safe/__init__.py +6 -1
  42. fishertools/safe/files.py +329 -1
  43. fishertools/transform_readme.py +105 -0
  44. fishertools-0.4.0.dist-info/METADATA +104 -0
  45. fishertools-0.4.0.dist-info/RECORD +131 -0
  46. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/WHEEL +1 -1
  47. tests/test_documentation_properties.py +329 -0
  48. tests/test_documentation_structure.py +349 -0
  49. tests/test_errors/test_exception_types.py +446 -0
  50. tests/test_errors/test_exception_types_pbt.py +333 -0
  51. tests/test_errors/test_patterns.py +52 -0
  52. tests/test_input_utils/__init__.py +1 -0
  53. tests/test_input_utils/test_input_utils.py +65 -0
  54. tests/test_learn/test_examples.py +179 -1
  55. tests/test_learn/test_explain_properties.py +307 -0
  56. tests/test_patterns_cli.py +611 -0
  57. tests/test_patterns_docstrings.py +473 -0
  58. tests/test_patterns_logger.py +465 -0
  59. tests/test_patterns_menu.py +440 -0
  60. tests/test_patterns_storage.py +447 -0
  61. tests/test_readme_enhancements_v0_3_1.py +2036 -0
  62. tests/test_readme_transformer/__init__.py +1 -0
  63. tests/test_readme_transformer/test_readme_infrastructure.py +1023 -0
  64. tests/test_readme_transformer/test_transform_readme_integration.py +431 -0
  65. tests/test_safe/test_files.py +726 -1
  66. fishertools-0.2.1.dist-info/METADATA +0 -256
  67. fishertools-0.2.1.dist-info/RECORD +0 -81
  68. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/licenses/LICENSE +0 -0
  69. {fishertools-0.2.1.dist-info → fishertools-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,121 @@
1
+ """
2
+ Data models for the Knowledge Engine REPL.
3
+
4
+ This module defines the core data structures used throughout the REPL system.
5
+ """
6
+
7
+ from dataclasses import dataclass, field, asdict
8
+ from datetime import datetime
9
+ from typing import List, Dict, Optional, Any
10
+
11
+
12
+ @dataclass
13
+ class ExampleDisplay:
14
+ """Represents a code example for display in the REPL."""
15
+
16
+ number: int
17
+ description: str
18
+ code: str
19
+ expected_output: Optional[str] = None
20
+
21
+ def to_dict(self) -> Dict[str, Any]:
22
+ """Convert to dictionary for serialization."""
23
+ return asdict(self)
24
+
25
+ @classmethod
26
+ def from_dict(cls, data: Dict[str, Any]) -> "ExampleDisplay":
27
+ """Create from dictionary."""
28
+ return cls(**data)
29
+
30
+
31
+ @dataclass
32
+ class TopicDisplay:
33
+ """Represents a topic with all its information for display."""
34
+
35
+ name: str
36
+ category: str
37
+ difficulty: str
38
+ description: str
39
+ examples: List[ExampleDisplay] = field(default_factory=list)
40
+ common_mistakes: List[str] = field(default_factory=list)
41
+ related_topics: List[str] = field(default_factory=list)
42
+ tips: List[str] = field(default_factory=list)
43
+
44
+ def to_dict(self) -> Dict[str, Any]:
45
+ """Convert to dictionary for serialization."""
46
+ data = asdict(self)
47
+ data["examples"] = [ex.to_dict() for ex in self.examples]
48
+ return data
49
+
50
+ @classmethod
51
+ def from_dict(cls, data: Dict[str, Any]) -> "TopicDisplay":
52
+ """Create from dictionary."""
53
+ examples = [ExampleDisplay.from_dict(ex) for ex in data.get("examples", [])]
54
+ return cls(
55
+ name=data["name"],
56
+ category=data["category"],
57
+ difficulty=data["difficulty"],
58
+ description=data["description"],
59
+ examples=examples,
60
+ common_mistakes=data.get("common_mistakes", []),
61
+ related_topics=data.get("related_topics", []),
62
+ tips=data.get("tips", []),
63
+ )
64
+
65
+
66
+ @dataclass
67
+ class ProgressStats:
68
+ """Statistics about learning progress."""
69
+
70
+ total_topics: int
71
+ viewed_topics: int
72
+ total_examples: int
73
+ executed_examples: int
74
+ categories_explored: Dict[str, int] = field(default_factory=dict)
75
+ difficulty_distribution: Dict[str, int] = field(default_factory=dict)
76
+ session_duration: float = 0.0
77
+ last_viewed_topic: Optional[str] = None
78
+
79
+ def to_dict(self) -> Dict[str, Any]:
80
+ """Convert to dictionary for serialization."""
81
+ return asdict(self)
82
+
83
+ @classmethod
84
+ def from_dict(cls, data: Dict[str, Any]) -> "ProgressStats":
85
+ """Create from dictionary."""
86
+ return cls(**data)
87
+
88
+
89
+ @dataclass
90
+ class SessionState:
91
+ """Represents the state of a REPL session."""
92
+
93
+ current_topic: Optional[str] = None
94
+ viewed_topics: List[str] = field(default_factory=list)
95
+ executed_examples: Dict[str, List[int]] = field(default_factory=dict)
96
+ session_history: List[str] = field(default_factory=list)
97
+ created_at: datetime = field(default_factory=datetime.now)
98
+ last_updated: datetime = field(default_factory=datetime.now)
99
+
100
+ def to_dict(self) -> Dict[str, Any]:
101
+ """Convert to dictionary for serialization."""
102
+ return {
103
+ "current_topic": self.current_topic,
104
+ "viewed_topics": self.viewed_topics,
105
+ "executed_examples": self.executed_examples,
106
+ "session_history": self.session_history,
107
+ "created_at": self.created_at.isoformat(),
108
+ "last_updated": self.last_updated.isoformat(),
109
+ }
110
+
111
+ @classmethod
112
+ def from_dict(cls, data: Dict[str, Any]) -> "SessionState":
113
+ """Create from dictionary."""
114
+ return cls(
115
+ current_topic=data.get("current_topic"),
116
+ viewed_topics=data.get("viewed_topics", []),
117
+ executed_examples=data.get("executed_examples", {}),
118
+ session_history=data.get("session_history", []),
119
+ created_at=datetime.fromisoformat(data["created_at"]) if "created_at" in data else datetime.now(),
120
+ last_updated=datetime.fromisoformat(data["last_updated"]) if "last_updated" in data else datetime.now(),
121
+ )
@@ -0,0 +1,284 @@
1
+ """
2
+ Session manager for tracking learning progress and session state.
3
+
4
+ This module handles tracking viewed topics, executed examples, and persisting
5
+ session state to disk for resuming learning sessions.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ from datetime import datetime
11
+ from typing import Dict, List, Optional, Any
12
+ from pathlib import Path
13
+
14
+ from fishertools.learn.repl.models import SessionState, ProgressStats
15
+
16
+
17
+ class SessionManager:
18
+ """
19
+ Manages learning progress and session state for the REPL.
20
+
21
+ Tracks:
22
+ - Viewed topics
23
+ - Executed examples
24
+ - Session history
25
+ - Learning statistics
26
+
27
+ Persists state to JSON files for resuming sessions.
28
+
29
+ Example:
30
+ >>> manager = SessionManager()
31
+ >>> manager.mark_topic_viewed("Lists")
32
+ >>> manager.mark_example_executed("Lists", 1)
33
+ >>> stats = manager.get_progress()
34
+ >>> manager.save_session()
35
+ """
36
+
37
+ DEFAULT_SESSION_FILE = ".repl_session.json"
38
+ DEFAULT_PROGRESS_FILE = ".repl_progress.json"
39
+
40
+ def __init__(self, storage_path: Optional[str] = None):
41
+ """
42
+ Initialize the session manager.
43
+
44
+ Args:
45
+ storage_path: Optional path to store session files.
46
+ Defaults to user's home directory.
47
+ """
48
+ if storage_path is None:
49
+ storage_path = str(Path.home())
50
+
51
+ self.storage_path = Path(storage_path)
52
+ self.session_file = self.storage_path / self.DEFAULT_SESSION_FILE
53
+ self.progress_file = self.storage_path / self.DEFAULT_PROGRESS_FILE
54
+
55
+ # Initialize session state
56
+ self.state = SessionState()
57
+ self.session_start_time = datetime.now()
58
+
59
+ # Load existing session if available
60
+ self.load_session()
61
+
62
+ def mark_topic_viewed(self, topic_name: str) -> None:
63
+ """
64
+ Mark a topic as viewed.
65
+
66
+ Args:
67
+ topic_name: Name of the topic to mark as viewed
68
+ """
69
+ if topic_name not in self.state.viewed_topics:
70
+ self.state.viewed_topics.append(topic_name)
71
+
72
+ # Add to session history
73
+ if not self.state.session_history or self.state.session_history[-1] != topic_name:
74
+ self.state.session_history.append(topic_name)
75
+
76
+ self.state.last_updated = datetime.now()
77
+
78
+ def mark_example_executed(self, topic_name: str, example_num: int) -> None:
79
+ """
80
+ Mark an example as executed.
81
+
82
+ Args:
83
+ topic_name: Name of the topic
84
+ example_num: Number of the example
85
+ """
86
+ if topic_name not in self.state.executed_examples:
87
+ self.state.executed_examples[topic_name] = []
88
+
89
+ if example_num not in self.state.executed_examples[topic_name]:
90
+ self.state.executed_examples[topic_name].append(example_num)
91
+
92
+ self.state.last_updated = datetime.now()
93
+
94
+ def get_progress(self) -> ProgressStats:
95
+ """
96
+ Get current learning progress statistics.
97
+
98
+ Returns:
99
+ ProgressStats object with current statistics
100
+ """
101
+ # Calculate statistics
102
+ total_topics = len(self.state.viewed_topics)
103
+ viewed_topics = len(self.state.viewed_topics)
104
+
105
+ total_examples = sum(len(examples) for examples in self.state.executed_examples.values())
106
+ executed_examples = total_examples
107
+
108
+ # Calculate session duration
109
+ session_duration = (datetime.now() - self.session_start_time).total_seconds()
110
+
111
+ return ProgressStats(
112
+ total_topics=total_topics,
113
+ viewed_topics=viewed_topics,
114
+ total_examples=total_examples,
115
+ executed_examples=executed_examples,
116
+ categories_explored={},
117
+ difficulty_distribution={},
118
+ session_duration=session_duration,
119
+ last_viewed_topic=self.state.current_topic,
120
+ )
121
+
122
+ def get_viewed_topics(self) -> List[str]:
123
+ """
124
+ Get list of viewed topics.
125
+
126
+ Returns:
127
+ List of topic names that have been viewed
128
+ """
129
+ return self.state.viewed_topics.copy()
130
+
131
+ def get_executed_examples(self) -> Dict[str, List[int]]:
132
+ """
133
+ Get dictionary of executed examples by topic.
134
+
135
+ Returns:
136
+ Dictionary mapping topic names to lists of executed example numbers
137
+ """
138
+ return {k: v.copy() for k, v in self.state.executed_examples.items()}
139
+
140
+ def set_current_topic(self, topic_name: str) -> None:
141
+ """
142
+ Set the current topic.
143
+
144
+ Args:
145
+ topic_name: Name of the current topic
146
+ """
147
+ self.state.current_topic = topic_name
148
+ self.state.last_updated = datetime.now()
149
+
150
+ def get_current_topic(self) -> Optional[str]:
151
+ """
152
+ Get the current topic.
153
+
154
+ Returns:
155
+ Name of the current topic or None if not set
156
+ """
157
+ return self.state.current_topic
158
+
159
+ def get_session_history(self) -> List[str]:
160
+ """
161
+ Get history of viewed topics in current session.
162
+
163
+ Returns:
164
+ List of topic names in order they were viewed
165
+ """
166
+ return self.state.session_history.copy()
167
+
168
+ def save_session(self) -> bool:
169
+ """
170
+ Save session state to persistent storage.
171
+
172
+ Returns:
173
+ True if save was successful, False otherwise
174
+ """
175
+ try:
176
+ # Ensure storage directory exists
177
+ self.storage_path.mkdir(parents=True, exist_ok=True)
178
+
179
+ # Save session state
180
+ session_data = self.state.to_dict()
181
+ with open(self.session_file, 'w', encoding='utf-8') as f:
182
+ json.dump(session_data, f, indent=2, default=str)
183
+
184
+ return True
185
+ except Exception as e:
186
+ print(f"Warning: Could not save session: {e}")
187
+ return False
188
+
189
+ def load_session(self) -> bool:
190
+ """
191
+ Load session state from persistent storage.
192
+
193
+ Returns:
194
+ True if load was successful, False otherwise
195
+ """
196
+ try:
197
+ if not self.session_file.exists():
198
+ return False
199
+
200
+ with open(self.session_file, 'r', encoding='utf-8') as f:
201
+ session_data = json.load(f)
202
+
203
+ self.state = SessionState.from_dict(session_data)
204
+ self.session_start_time = datetime.now()
205
+ return True
206
+ except Exception as e:
207
+ print(f"Warning: Could not load session: {e}")
208
+ return False
209
+
210
+ def reset_progress(self) -> None:
211
+ """
212
+ Reset all learning progress.
213
+
214
+ Clears viewed topics, executed examples, and session history.
215
+ """
216
+ self.state = SessionState()
217
+ self.session_start_time = datetime.now()
218
+
219
+ def clear_session_history(self) -> None:
220
+ """
221
+ Clear the session history.
222
+
223
+ Keeps viewed topics and executed examples but clears the history.
224
+ """
225
+ self.state.session_history = []
226
+ self.state.last_updated = datetime.now()
227
+
228
+ def get_session_info(self) -> Dict[str, Any]:
229
+ """
230
+ Get information about the current session.
231
+
232
+ Returns:
233
+ Dictionary with session information
234
+ """
235
+ session_duration = (datetime.now() - self.session_start_time).total_seconds()
236
+
237
+ return {
238
+ "current_topic": self.state.current_topic,
239
+ "topics_viewed": len(self.state.viewed_topics),
240
+ "examples_executed": sum(len(ex) for ex in self.state.executed_examples.values()),
241
+ "session_duration_seconds": session_duration,
242
+ "session_history_length": len(self.state.session_history),
243
+ "created_at": self.state.created_at.isoformat(),
244
+ "last_updated": self.state.last_updated.isoformat(),
245
+ }
246
+
247
+ def is_topic_viewed(self, topic_name: str) -> bool:
248
+ """
249
+ Check if a topic has been viewed.
250
+
251
+ Args:
252
+ topic_name: Name of the topic
253
+
254
+ Returns:
255
+ True if topic has been viewed, False otherwise
256
+ """
257
+ return topic_name in self.state.viewed_topics
258
+
259
+ def is_example_executed(self, topic_name: str, example_num: int) -> bool:
260
+ """
261
+ Check if an example has been executed.
262
+
263
+ Args:
264
+ topic_name: Name of the topic
265
+ example_num: Number of the example
266
+
267
+ Returns:
268
+ True if example has been executed, False otherwise
269
+ """
270
+ if topic_name not in self.state.executed_examples:
271
+ return False
272
+ return example_num in self.state.executed_examples[topic_name]
273
+
274
+ def get_examples_executed_for_topic(self, topic_name: str) -> List[int]:
275
+ """
276
+ Get list of executed examples for a topic.
277
+
278
+ Args:
279
+ topic_name: Name of the topic
280
+
281
+ Returns:
282
+ List of executed example numbers
283
+ """
284
+ return self.state.executed_examples.get(topic_name, []).copy()
@@ -0,0 +1,261 @@
1
+ """
2
+ Unit tests for the CodeSandbox class.
3
+ """
4
+
5
+ import pytest
6
+ from fishertools.learn.repl.code_sandbox import CodeSandbox
7
+
8
+
9
+ class TestCodeSandboxBasic:
10
+ """Test basic code execution in the sandbox."""
11
+
12
+ def test_execute_simple_print(self):
13
+ """Test executing simple print statement."""
14
+ sandbox = CodeSandbox()
15
+ success, output = sandbox.execute("print('Hello')")
16
+ assert success is True
17
+ assert "Hello" in output
18
+
19
+ def test_execute_arithmetic(self):
20
+ """Test executing arithmetic operations."""
21
+ sandbox = CodeSandbox()
22
+ success, output = sandbox.execute("print(2 + 2)")
23
+ assert success is True
24
+ assert "4" in output
25
+
26
+ def test_execute_variable_assignment(self):
27
+ """Test executing variable assignment."""
28
+ sandbox = CodeSandbox()
29
+ success, output = sandbox.execute("x = 5\nprint(x * 2)")
30
+ assert success is True
31
+ assert "10" in output
32
+
33
+ def test_execute_list_operations(self):
34
+ """Test executing list operations."""
35
+ sandbox = CodeSandbox()
36
+ success, output = sandbox.execute("lst = [1, 2, 3]\nprint(sum(lst))")
37
+ assert success is True
38
+ assert "6" in output
39
+
40
+ def test_execute_loop(self):
41
+ """Test executing for loop."""
42
+ sandbox = CodeSandbox()
43
+ code = """
44
+ for i in range(3):
45
+ print(i)
46
+ """
47
+ success, output = sandbox.execute(code)
48
+ assert success is True
49
+ assert "0" in output
50
+ assert "1" in output
51
+ assert "2" in output
52
+
53
+ def test_execute_function_definition(self):
54
+ """Test executing function definition."""
55
+ sandbox = CodeSandbox()
56
+ code = """
57
+ def add(a, b):
58
+ return a + b
59
+
60
+ print(add(3, 4))
61
+ """
62
+ success, output = sandbox.execute(code)
63
+ assert success is True
64
+ assert "7" in output
65
+
66
+
67
+ class TestCodeSandboxErrors:
68
+ """Test error handling in the sandbox."""
69
+
70
+ def test_syntax_error(self):
71
+ """Test handling of syntax errors."""
72
+ sandbox = CodeSandbox()
73
+ success, output = sandbox.execute("print('unclosed")
74
+ assert success is False
75
+ assert "Syntax Error" in output
76
+
77
+ def test_runtime_error(self):
78
+ """Test handling of runtime errors."""
79
+ sandbox = CodeSandbox()
80
+ success, output = sandbox.execute("print(1 / 0)")
81
+ assert success is False
82
+ assert "ZeroDivisionError" in output
83
+
84
+ def test_name_error(self):
85
+ """Test handling of undefined variables."""
86
+ sandbox = CodeSandbox()
87
+ success, output = sandbox.execute("print(undefined_var)")
88
+ assert success is False
89
+ assert "NameError" in output
90
+
91
+ def test_empty_code(self):
92
+ """Test handling of empty code."""
93
+ sandbox = CodeSandbox()
94
+ success, output = sandbox.execute("")
95
+ assert success is False
96
+ assert "empty" in output.lower()
97
+
98
+ def test_whitespace_only_code(self):
99
+ """Test handling of whitespace-only code."""
100
+ sandbox = CodeSandbox()
101
+ success, output = sandbox.execute(" \n \n ")
102
+ assert success is False
103
+
104
+
105
+ class TestCodeSandboxRestrictions:
106
+ """Test that dangerous operations are blocked."""
107
+
108
+ def test_block_import(self):
109
+ """Test that imports are blocked."""
110
+ sandbox = CodeSandbox()
111
+ success, output = sandbox.execute("import os")
112
+ assert success is False
113
+ assert "import" in output.lower() or "not allowed" in output.lower()
114
+
115
+ def test_block_from_import(self):
116
+ """Test that from imports are blocked."""
117
+ sandbox = CodeSandbox()
118
+ success, output = sandbox.execute("from os import path")
119
+ assert success is False
120
+ assert "import" in output.lower() or "not allowed" in output.lower()
121
+
122
+ def test_block_open_function(self):
123
+ """Test that open() is blocked."""
124
+ sandbox = CodeSandbox()
125
+ success, output = sandbox.execute("open('file.txt')")
126
+ assert success is False
127
+ assert "not allowed" in output.lower() or "file" in output.lower()
128
+
129
+ def test_block_exec(self):
130
+ """Test that exec() is blocked."""
131
+ sandbox = CodeSandbox()
132
+ success, output = sandbox.execute("exec('print(1)')")
133
+ assert success is False
134
+ assert "not allowed" in output.lower()
135
+
136
+ def test_block_eval(self):
137
+ """Test that eval() is blocked."""
138
+ sandbox = CodeSandbox()
139
+ success, output = sandbox.execute("eval('1+1')")
140
+ assert success is False
141
+ assert "not allowed" in output.lower()
142
+
143
+ def test_block_globals(self):
144
+ """Test that globals() is blocked."""
145
+ sandbox = CodeSandbox()
146
+ success, output = sandbox.execute("globals()")
147
+ assert success is False
148
+ assert "not allowed" in output.lower()
149
+
150
+
151
+ class TestCodeSandboxMath:
152
+ """Test math operations in the sandbox."""
153
+
154
+ def test_math_module_available(self):
155
+ """Test that math module is available."""
156
+ sandbox = CodeSandbox()
157
+ success, output = sandbox.execute("import math\nprint(math.pi)")
158
+ # Math module should be available through restricted globals
159
+ # But import should be blocked
160
+ assert success is False
161
+
162
+ def test_builtin_math_functions(self):
163
+ """Test built-in math functions."""
164
+ sandbox = CodeSandbox()
165
+ success, output = sandbox.execute("print(abs(-5))")
166
+ assert success is True
167
+ assert "5" in output
168
+
169
+ def test_pow_function(self):
170
+ """Test pow function."""
171
+ sandbox = CodeSandbox()
172
+ success, output = sandbox.execute("print(pow(2, 3))")
173
+ assert success is True
174
+ assert "8" in output
175
+
176
+ def test_round_function(self):
177
+ """Test round function."""
178
+ sandbox = CodeSandbox()
179
+ success, output = sandbox.execute("print(round(3.7))")
180
+ assert success is True
181
+ assert "4" in output
182
+
183
+
184
+ class TestCodeSandboxUtilityMethods:
185
+ """Test utility methods of CodeSandbox."""
186
+
187
+ def test_get_available_builtins(self):
188
+ """Test getting list of available built-ins."""
189
+ sandbox = CodeSandbox()
190
+ builtins = sandbox.get_available_builtins()
191
+ assert isinstance(builtins, list)
192
+ assert "print" in builtins
193
+ assert "len" in builtins
194
+ assert "sum" in builtins
195
+
196
+ def test_get_blocked_builtins(self):
197
+ """Test getting list of blocked built-ins."""
198
+ sandbox = CodeSandbox()
199
+ blocked = sandbox.get_blocked_builtins()
200
+ assert isinstance(blocked, list)
201
+ assert "open" in blocked
202
+ assert "exec" in blocked
203
+ assert "eval" in blocked
204
+
205
+ def test_get_blocked_modules(self):
206
+ """Test getting list of blocked modules."""
207
+ sandbox = CodeSandbox()
208
+ blocked = sandbox.get_blocked_modules()
209
+ assert isinstance(blocked, list)
210
+ assert "os" in blocked
211
+ assert "sys" in blocked
212
+ assert "subprocess" in blocked
213
+
214
+
215
+ class TestCodeSandboxEdgeCases:
216
+ """Test edge cases in code execution."""
217
+
218
+ def test_multiline_code(self):
219
+ """Test executing multiline code."""
220
+ sandbox = CodeSandbox()
221
+ code = """
222
+ x = 10
223
+ y = 20
224
+ z = x + y
225
+ print(z)
226
+ """
227
+ success, output = sandbox.execute(code)
228
+ assert success is True
229
+ assert "30" in output
230
+
231
+ def test_nested_loops(self):
232
+ """Test executing nested loops."""
233
+ sandbox = CodeSandbox()
234
+ code = """
235
+ for i in range(2):
236
+ for j in range(2):
237
+ print(f"{i},{j}")
238
+ """
239
+ success, output = sandbox.execute(code)
240
+ assert success is True
241
+ assert "0,0" in output
242
+
243
+ def test_list_comprehension(self):
244
+ """Test list comprehension."""
245
+ sandbox = CodeSandbox()
246
+ success, output = sandbox.execute("print([x*2 for x in range(3)])")
247
+ assert success is True
248
+ assert "0" in output
249
+ assert "2" in output
250
+ assert "4" in output
251
+
252
+ def test_dictionary_operations(self):
253
+ """Test dictionary operations."""
254
+ sandbox = CodeSandbox()
255
+ code = """
256
+ d = {'a': 1, 'b': 2}
257
+ print(d['a'])
258
+ """
259
+ success, output = sandbox.execute(code)
260
+ assert success is True
261
+ assert "1" in output