codefix-env 0.2.1__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.
@@ -0,0 +1,244 @@
1
+ """
2
+ Easy Tasks — Syntax errors, simple fixes, missing colons, wrong indentation.
3
+ Each task has 3+ test cases and a clear hint.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from codefix_env.models import BugCategory, Difficulty, Task, TestCase
9
+
10
+ EASY_TASKS: list[Task] = [
11
+ # ── Task E1: Missing return ──────────────────────────────────────────
12
+ Task(
13
+ id="easy-001-missing-return",
14
+ title="Missing Return Statement",
15
+ description="The function adds two numbers but never returns the result.",
16
+ difficulty=Difficulty.EASY,
17
+ bug_category=BugCategory.RETURN_BUG,
18
+ tags=["return", "arithmetic"],
19
+ max_steps=10,
20
+ buggy_code="""\
21
+ def add_numbers(a, b):
22
+ result = a + b
23
+ """,
24
+ solution_code="""\
25
+ def add_numbers(a, b):
26
+ result = a + b
27
+ return result
28
+ """,
29
+ hints=[
30
+ "The function computes a result but never sends it back to the caller.",
31
+ "You need a `return` statement at the end of the function.",
32
+ ],
33
+ test_cases=[
34
+ TestCase(name="test_positive", code="assert add_numbers(2, 3) == 5"),
35
+ TestCase(name="test_negative", code="assert add_numbers(-1, -4) == -5"),
36
+ TestCase(name="test_zero", code="assert add_numbers(0, 0) == 0"),
37
+ TestCase(name="test_float", code="assert add_numbers(1.5, 2.5) == 4.0"),
38
+ ],
39
+ ),
40
+ # ── Task E2: Wrong comparison operator ──────────────────────────────
41
+ Task(
42
+ id="easy-002-wrong-operator",
43
+ title="Wrong Comparison Operator",
44
+ description="The is_even function uses assignment `=` instead of equality `==`.",
45
+ difficulty=Difficulty.EASY,
46
+ bug_category=BugCategory.WRONG_OPERATOR,
47
+ tags=["operators", "boolean"],
48
+ max_steps=8,
49
+ buggy_code="""\
50
+ def is_even(n):
51
+ return n % 2 = 0
52
+ """,
53
+ solution_code="""\
54
+ def is_even(n):
55
+ return n % 2 == 0
56
+ """,
57
+ hints=[
58
+ "Python uses `==` for comparison, `=` is only for assignment.",
59
+ "Look at the return expression carefully — one character is wrong.",
60
+ ],
61
+ test_cases=[
62
+ TestCase(name="test_even_2", code="assert is_even(2) == True"),
63
+ TestCase(name="test_odd_3", code="assert is_even(3) == False"),
64
+ TestCase(name="test_zero", code="assert is_even(0) == True"),
65
+ TestCase(name="test_neg", code="assert is_even(-4) == True"),
66
+ ],
67
+ ),
68
+ # ── Task E3: Off-by-one in range ─────────────────────────────────────
69
+ Task(
70
+ id="easy-003-off-by-one-range",
71
+ title="Off-by-One in Range",
72
+ description="sum_to_n should sum numbers 1 through n inclusive, but misses the last number.",
73
+ difficulty=Difficulty.EASY,
74
+ bug_category=BugCategory.OFF_BY_ONE,
75
+ tags=["range", "loop", "off-by-one"],
76
+ max_steps=10,
77
+ buggy_code="""\
78
+ def sum_to_n(n):
79
+ total = 0
80
+ for i in range(1, n):
81
+ total += i
82
+ return total
83
+ """,
84
+ solution_code="""\
85
+ def sum_to_n(n):
86
+ total = 0
87
+ for i in range(1, n + 1):
88
+ total += i
89
+ return total
90
+ """,
91
+ hints=[
92
+ "Python's range(a, b) excludes b. Think about whether n should be included.",
93
+ "Change `range(1, n)` to include n itself.",
94
+ ],
95
+ test_cases=[
96
+ TestCase(name="test_sum_5", code="assert sum_to_n(5) == 15"),
97
+ TestCase(name="test_sum_10", code="assert sum_to_n(10) == 55"),
98
+ TestCase(name="test_sum_1", code="assert sum_to_n(1) == 1"),
99
+ TestCase(name="test_sum_0", code="assert sum_to_n(0) == 0"),
100
+ ],
101
+ ),
102
+ # ── Task E4: Wrong indentation ───────────────────────────────────────
103
+ Task(
104
+ id="easy-004-wrong-indent",
105
+ title="Wrong Indentation in Loop",
106
+ description="The return statement is inside the loop — it exits too early.",
107
+ difficulty=Difficulty.EASY,
108
+ bug_category=BugCategory.LOGIC,
109
+ tags=["indentation", "loop"],
110
+ max_steps=10,
111
+ buggy_code="""\
112
+ def find_max(numbers):
113
+ max_val = numbers[0]
114
+ for num in numbers:
115
+ if num > max_val:
116
+ max_val = num
117
+ return max_val
118
+ """,
119
+ solution_code="""\
120
+ def find_max(numbers):
121
+ max_val = numbers[0]
122
+ for num in numbers:
123
+ if num > max_val:
124
+ max_val = num
125
+ return max_val
126
+ """,
127
+ hints=[
128
+ "The return statement is indented inside the for loop — it exits after checking the first element.",
129
+ "De-indent `return max_val` by one level so it runs after the loop finishes.",
130
+ ],
131
+ test_cases=[
132
+ TestCase(name="test_basic", code="assert find_max([3, 1, 4, 1, 5, 9]) == 9"),
133
+ TestCase(name="test_sorted", code="assert find_max([1, 2, 3, 4, 5]) == 5"),
134
+ TestCase(name="test_single", code="assert find_max([42]) == 42"),
135
+ TestCase(name="test_negative", code="assert find_max([-5, -1, -3]) == -1"),
136
+ ],
137
+ ),
138
+ # ── Task E5: Missing colon ───────────────────────────────────────────
139
+ Task(
140
+ id="easy-005-missing-colon",
141
+ title="Missing Colon in Function Definition",
142
+ description="A function definition is missing its colon, causing a SyntaxError.",
143
+ difficulty=Difficulty.EASY,
144
+ bug_category=BugCategory.SYNTAX,
145
+ tags=["syntax", "colon"],
146
+ max_steps=8,
147
+ buggy_code="""\
148
+ def greet(name)
149
+ return f"Hello, {name}!"
150
+ """,
151
+ solution_code="""\
152
+ def greet(name):
153
+ return f"Hello, {name}!"
154
+ """,
155
+ hints=[
156
+ "Every `def` statement must end with a colon `:`.",
157
+ ],
158
+ test_cases=[
159
+ TestCase(name="test_hello", code='assert greet("Alice") == "Hello, Alice!"'),
160
+ TestCase(name="test_empty", code='assert greet("") == "Hello, !"'),
161
+ ],
162
+ ),
163
+ # ── Task E6: String not converted to int ────────────────────────────
164
+ Task(
165
+ id="easy-006-type-mismatch",
166
+ title="Type Mismatch — String + Integer",
167
+ description="The function tries to add a string to an integer without converting.",
168
+ difficulty=Difficulty.EASY,
169
+ bug_category=BugCategory.TYPE_ERROR,
170
+ tags=["type", "casting"],
171
+ max_steps=10,
172
+ buggy_code="""\
173
+ def add_age(base_age, years_str):
174
+ return base_age + years_str
175
+ """,
176
+ solution_code="""\
177
+ def add_age(base_age, years_str):
178
+ return base_age + int(years_str)
179
+ """,
180
+ hints=[
181
+ "years_str is a string. You need to convert it to int before adding.",
182
+ "Use `int(years_str)` to convert.",
183
+ ],
184
+ test_cases=[
185
+ TestCase(name="test_basic", code='assert add_age(25, "5") == 30'),
186
+ TestCase(name="test_zero", code='assert add_age(30, "0") == 30'),
187
+ TestCase(name="test_large", code='assert add_age(0, "100") == 100'),
188
+ ],
189
+ ),
190
+ # ── Task E7: Wrong list index ────────────────────────────────────────
191
+ Task(
192
+ id="easy-007-wrong-index",
193
+ title="Wrong List Index",
194
+ description="get_last should return the last element but uses index 0.",
195
+ difficulty=Difficulty.EASY,
196
+ bug_category=BugCategory.INDEX_ERROR,
197
+ tags=["list", "index"],
198
+ max_steps=8,
199
+ buggy_code="""\
200
+ def get_last(items):
201
+ return items[0]
202
+ """,
203
+ solution_code="""\
204
+ def get_last(items):
205
+ return items[-1]
206
+ """,
207
+ hints=[
208
+ "Python index -1 refers to the last element of a list.",
209
+ ],
210
+ test_cases=[
211
+ TestCase(name="test_basic", code="assert get_last([1, 2, 3]) == 3"),
212
+ TestCase(name="test_single", code="assert get_last(['x']) == 'x'"),
213
+ TestCase(name="test_strings", code="assert get_last(['a', 'b', 'c']) == 'c'"),
214
+ ],
215
+ ),
216
+ # ── Task E8: Boolean logic inverted ─────────────────────────────────
217
+ Task(
218
+ id="easy-008-inverted-bool",
219
+ title="Inverted Boolean Condition",
220
+ description="is_adult returns True for ages under 18 and False for adults.",
221
+ difficulty=Difficulty.EASY,
222
+ bug_category=BugCategory.LOGIC,
223
+ tags=["boolean", "condition"],
224
+ max_steps=8,
225
+ buggy_code="""\
226
+ def is_adult(age):
227
+ return age < 18
228
+ """,
229
+ solution_code="""\
230
+ def is_adult(age):
231
+ return age >= 18
232
+ """,
233
+ hints=[
234
+ "The condition is backwards — an adult is someone 18 or older.",
235
+ "Change `<` to `>=`.",
236
+ ],
237
+ test_cases=[
238
+ TestCase(name="test_adult", code="assert is_adult(18) == True"),
239
+ TestCase(name="test_child", code="assert is_adult(10) == False"),
240
+ TestCase(name="test_exactly", code="assert is_adult(17) == False"),
241
+ TestCase(name="test_old", code="assert is_adult(65) == True"),
242
+ ],
243
+ ),
244
+ ]
@@ -0,0 +1,430 @@
1
+ """
2
+ Hard Tasks — Multi-function bugs, complex algorithms, subtle logic errors.
3
+ These tasks require understanding interactions between multiple functions.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from codefix_env.models import BugCategory, Difficulty, Task, TestCase
9
+
10
+ HARD_TASKS: list[Task] = [
11
+ # ── Task H1: LRU Cache — wrong eviction ─────────────────────────────
12
+ Task(
13
+ id="hard-001-lru-cache",
14
+ title="LRU Cache — Wrong Eviction Policy",
15
+ description=(
16
+ "A manual LRU cache implementation evicts the most recently used item "
17
+ "instead of the least recently used. The `get` method also fails to update "
18
+ "recency on a cache hit."
19
+ ),
20
+ difficulty=Difficulty.HARD,
21
+ bug_category=BugCategory.MULTI_FUNCTION,
22
+ tags=["lru", "cache", "data-structures", "ordered-dict"],
23
+ max_steps=25,
24
+ buggy_code="""\
25
+ from collections import OrderedDict
26
+
27
+ class LRUCache:
28
+ def __init__(self, capacity: int):
29
+ self.capacity = capacity
30
+ self.cache = OrderedDict()
31
+
32
+ def get(self, key: int) -> int:
33
+ if key not in self.cache:
34
+ return -1
35
+ # BUG: does not move to end (not updating recency)
36
+ return self.cache[key]
37
+
38
+ def put(self, key: int, value: int) -> None:
39
+ if key in self.cache:
40
+ self.cache.move_to_end(key)
41
+ self.cache[key] = value
42
+ if len(self.cache) > self.capacity:
43
+ self.cache.popitem(last=True) # BUG: evicts MRU not LRU
44
+ """,
45
+ solution_code="""\
46
+ from collections import OrderedDict
47
+
48
+ class LRUCache:
49
+ def __init__(self, capacity: int):
50
+ self.capacity = capacity
51
+ self.cache = OrderedDict()
52
+
53
+ def get(self, key: int) -> int:
54
+ if key not in self.cache:
55
+ return -1
56
+ self.cache.move_to_end(key) # FIX: mark as recently used
57
+ return self.cache[key]
58
+
59
+ def put(self, key: int, value: int) -> None:
60
+ if key in self.cache:
61
+ self.cache.move_to_end(key)
62
+ self.cache[key] = value
63
+ if len(self.cache) > self.capacity:
64
+ self.cache.popitem(last=False) # FIX: evict LRU (first item)
65
+ """,
66
+ hints=[
67
+ "When `get` is called, the accessed item should become the most recently used.",
68
+ "To evict the *least* recently used, use `popitem(last=False)` — removes the first (oldest) item.",
69
+ "Both `get` and `put` have bugs. Fix them independently.",
70
+ ],
71
+ test_cases=[
72
+ TestCase(
73
+ name="test_basic_put_get",
74
+ code="""\
75
+ cache = LRUCache(2)
76
+ cache.put(1, 1)
77
+ cache.put(2, 2)
78
+ assert cache.get(1) == 1
79
+ """,
80
+ ),
81
+ TestCase(
82
+ name="test_eviction",
83
+ code="""\
84
+ cache = LRUCache(2)
85
+ cache.put(1, 1)
86
+ cache.put(2, 2)
87
+ cache.get(1) # 1 is now MRU
88
+ cache.put(3, 3) # evicts 2 (LRU)
89
+ assert cache.get(2) == -1
90
+ assert cache.get(3) == 3
91
+ """,
92
+ ),
93
+ TestCase(
94
+ name="test_update_existing",
95
+ code="""\
96
+ cache = LRUCache(2)
97
+ cache.put(1, 1)
98
+ cache.put(2, 2)
99
+ cache.put(1, 10) # update key 1
100
+ assert cache.get(1) == 10
101
+ """,
102
+ ),
103
+ TestCase(
104
+ name="test_miss",
105
+ code="""\
106
+ cache = LRUCache(1)
107
+ assert cache.get(99) == -1
108
+ """,
109
+ ),
110
+ ],
111
+ ),
112
+ # ── Task H2: Graph BFS — wrong visited tracking ─────────────────────
113
+ Task(
114
+ id="hard-002-graph-bfs",
115
+ title="Graph BFS — Infinite Loop from Wrong Visited Tracking",
116
+ description=(
117
+ "BFS adds nodes to `visited` after dequeuing instead of before enqueuing, "
118
+ "causing nodes to be visited multiple times and potentially looping."
119
+ ),
120
+ difficulty=Difficulty.HARD,
121
+ bug_category=BugCategory.ALGORITHM_BUG,
122
+ tags=["graph", "bfs", "visited"],
123
+ max_steps=25,
124
+ buggy_code="""\
125
+ from collections import deque
126
+
127
+ def bfs(graph, start):
128
+ visited = set()
129
+ queue = deque([start])
130
+ order = []
131
+ while queue:
132
+ node = queue.popleft()
133
+ if node not in visited:
134
+ visited.add(node)
135
+ order.append(node)
136
+ for neighbour in graph.get(node, []):
137
+ queue.append(neighbour) # BUG: should check visited before enqueuing
138
+ return order
139
+ """,
140
+ solution_code="""\
141
+ from collections import deque
142
+
143
+ def bfs(graph, start):
144
+ visited = set([start])
145
+ queue = deque([start])
146
+ order = []
147
+ while queue:
148
+ node = queue.popleft()
149
+ order.append(node)
150
+ for neighbour in graph.get(node, []):
151
+ if neighbour not in visited:
152
+ visited.add(neighbour)
153
+ queue.append(neighbour)
154
+ return order
155
+ """,
156
+ hints=[
157
+ "Nodes should be added to `visited` when they are *enqueued*, not when dequeued.",
158
+ "Initialise `visited` with the start node.",
159
+ ],
160
+ test_cases=[
161
+ TestCase(
162
+ name="test_linear",
163
+ code="""\
164
+ g = {0: [1], 1: [2], 2: [3], 3: []}
165
+ assert bfs(g, 0) == [0, 1, 2, 3]
166
+ """,
167
+ ),
168
+ TestCase(
169
+ name="test_branching",
170
+ code="""\
171
+ g = {0: [1, 2], 1: [3], 2: [3], 3: []}
172
+ result = bfs(g, 0)
173
+ assert result[0] == 0
174
+ assert set(result) == {0, 1, 2, 3}
175
+ assert len(result) == 4 # no duplicates
176
+ """,
177
+ ),
178
+ TestCase(
179
+ name="test_single_node",
180
+ code="""\
181
+ g = {0: []}
182
+ assert bfs(g, 0) == [0]
183
+ """,
184
+ ),
185
+ ],
186
+ ),
187
+ # ── Task H3: Decorator — wrong wraps usage ──────────────────────────
188
+ Task(
189
+ id="hard-003-decorator-bug",
190
+ title="Decorator — Lost Function Metadata",
191
+ description=(
192
+ "A timing decorator is missing `@functools.wraps(func)`, which causes the "
193
+ "wrapped function to lose its `__name__` and `__doc__`, breaking introspection."
194
+ ),
195
+ difficulty=Difficulty.HARD,
196
+ bug_category=BugCategory.MULTI_FUNCTION,
197
+ tags=["decorator", "functools", "wraps", "metadata"],
198
+ max_steps=15,
199
+ buggy_code="""\
200
+ import functools
201
+ import time
202
+
203
+ def timer(func):
204
+ def wrapper(*args, **kwargs): # BUG: missing @functools.wraps(func)
205
+ start = time.perf_counter()
206
+ result = func(*args, **kwargs)
207
+ elapsed = time.perf_counter() - start
208
+ return result
209
+ return wrapper
210
+
211
+ @timer
212
+ def compute(n):
213
+ \"\"\"Compute n^2.\"\"\"
214
+ return n * n
215
+ """,
216
+ solution_code="""\
217
+ import functools
218
+ import time
219
+
220
+ def timer(func):
221
+ @functools.wraps(func)
222
+ def wrapper(*args, **kwargs):
223
+ start = time.perf_counter()
224
+ result = func(*args, **kwargs)
225
+ elapsed = time.perf_counter() - start
226
+ return result
227
+ return wrapper
228
+
229
+ @timer
230
+ def compute(n):
231
+ \"\"\"Compute n^2.\"\"\"
232
+ return n * n
233
+ """,
234
+ hints=[
235
+ "Without `@functools.wraps(func)`, the wrapper function replaces the original's metadata.",
236
+ "Add `@functools.wraps(func)` directly above `def wrapper`.",
237
+ ],
238
+ test_cases=[
239
+ TestCase(name="test_result", code="assert compute(5) == 25"),
240
+ TestCase(name="test_name", code='assert compute.__name__ == "compute"'),
241
+ TestCase(name="test_doc", code='assert compute.__doc__ == "Compute n^2."'),
242
+ ],
243
+ ),
244
+ # ── Task H4: Stack with linked list — pop bug ───────────────────────
245
+ Task(
246
+ id="hard-004-linked-stack",
247
+ title="Linked List Stack — Pop Returns Wrong Value",
248
+ description=(
249
+ "A stack implemented with a linked list has a `pop` method that updates the head "
250
+ "pointer but returns the new head's value instead of the removed node's value."
251
+ ),
252
+ difficulty=Difficulty.HARD,
253
+ bug_category=BugCategory.MULTI_FUNCTION,
254
+ tags=["linked-list", "stack", "data-structures"],
255
+ max_steps=25,
256
+ buggy_code="""\
257
+ class Node:
258
+ def __init__(self, val):
259
+ self.val = val
260
+ self.next = None
261
+
262
+ class Stack:
263
+ def __init__(self):
264
+ self.head = None
265
+ self.size = 0
266
+
267
+ def push(self, val):
268
+ node = Node(val)
269
+ node.next = self.head
270
+ self.head = node
271
+ self.size += 1
272
+
273
+ def pop(self):
274
+ if self.head is None:
275
+ raise IndexError("pop from empty stack")
276
+ self.head = self.head.next # BUG: loses the removed node before capturing value
277
+ self.size -= 1
278
+ return self.head.val if self.head else None
279
+
280
+ def peek(self):
281
+ if self.head is None:
282
+ raise IndexError("peek from empty stack")
283
+ return self.head.val
284
+
285
+ def is_empty(self):
286
+ return self.head is None
287
+ """,
288
+ solution_code="""\
289
+ class Node:
290
+ def __init__(self, val):
291
+ self.val = val
292
+ self.next = None
293
+
294
+ class Stack:
295
+ def __init__(self):
296
+ self.head = None
297
+ self.size = 0
298
+
299
+ def push(self, val):
300
+ node = Node(val)
301
+ node.next = self.head
302
+ self.head = node
303
+ self.size += 1
304
+
305
+ def pop(self):
306
+ if self.head is None:
307
+ raise IndexError("pop from empty stack")
308
+ removed_val = self.head.val # FIX: capture value before moving head
309
+ self.head = self.head.next
310
+ self.size -= 1
311
+ return removed_val
312
+
313
+ def peek(self):
314
+ if self.head is None:
315
+ raise IndexError("peek from empty stack")
316
+ return self.head.val
317
+
318
+ def is_empty(self):
319
+ return self.head is None
320
+ """,
321
+ hints=[
322
+ "You need to save the current head's value before moving `self.head` to the next node.",
323
+ "Store `removed_val = self.head.val` before updating `self.head`.",
324
+ ],
325
+ test_cases=[
326
+ TestCase(
327
+ name="test_push_pop",
328
+ code="""\
329
+ s = Stack()
330
+ s.push(1); s.push(2); s.push(3)
331
+ assert s.pop() == 3
332
+ assert s.pop() == 2
333
+ assert s.pop() == 1
334
+ """,
335
+ ),
336
+ TestCase(
337
+ name="test_peek",
338
+ code="""\
339
+ s = Stack()
340
+ s.push(42)
341
+ assert s.peek() == 42
342
+ assert s.size == 1
343
+ """,
344
+ ),
345
+ TestCase(
346
+ name="test_empty_pop",
347
+ code="""\
348
+ s = Stack()
349
+ try:
350
+ s.pop()
351
+ assert False, "Should have raised"
352
+ except IndexError:
353
+ pass
354
+ """,
355
+ ),
356
+ TestCase(
357
+ name="test_size_tracking",
358
+ code="""\
359
+ s = Stack()
360
+ s.push(1); s.push(2)
361
+ s.pop()
362
+ assert s.size == 1
363
+ """,
364
+ ),
365
+ ],
366
+ ),
367
+ # ── Task H5: Tokenizer — wrong string parsing ────────────────────────
368
+ Task(
369
+ id="hard-005-tokenizer",
370
+ title="Mini Tokenizer — String Literal Handling Bug",
371
+ description=(
372
+ "A simple expression tokenizer fails on string literals because it doesn't "
373
+ "handle quoted strings — it splits on spaces inside quotes."
374
+ ),
375
+ difficulty=Difficulty.HARD,
376
+ bug_category=BugCategory.MULTI_FUNCTION,
377
+ tags=["parsing", "tokenizer", "string-literals"],
378
+ max_steps=25,
379
+ buggy_code="""\
380
+ def tokenize(expr):
381
+ \"\"\"Tokenize a simple expression into a list of tokens.\"\"\"
382
+ tokens = []
383
+ current = ''
384
+ for ch in expr:
385
+ if ch == ' ':
386
+ if current:
387
+ tokens.append(current)
388
+ current = ''
389
+ else:
390
+ current += ch
391
+ if current:
392
+ tokens.append(current)
393
+ return tokens
394
+ """,
395
+ solution_code="""\
396
+ def tokenize(expr):
397
+ \"\"\"Tokenize a simple expression into a list of tokens.\"\"\"
398
+ tokens = []
399
+ current = ''
400
+ in_string = False
401
+ for ch in expr:
402
+ if ch == '\"' :
403
+ in_string = not in_string
404
+ current += ch
405
+ elif ch == ' ' and not in_string:
406
+ if current:
407
+ tokens.append(current)
408
+ current = ''
409
+ else:
410
+ current += ch
411
+ if current:
412
+ tokens.append(current)
413
+ return tokens
414
+ """,
415
+ hints=[
416
+ "The tokenizer doesn't track whether it's inside a quoted string.",
417
+ 'Add an `in_string` flag that toggles when a `"` is encountered.',
418
+ "Only split on spaces when `in_string` is False.",
419
+ ],
420
+ test_cases=[
421
+ TestCase(name="test_simple", code='assert tokenize("a + b") == ["a", "+", "b"]'),
422
+ TestCase(
423
+ name="test_string_literal",
424
+ code="assert tokenize('print \"hello world\"') == ['print', '\"hello world\"']",
425
+ ),
426
+ TestCase(name="test_no_spaces", code='assert tokenize("abc") == ["abc"]'),
427
+ TestCase(name="test_empty", code='assert tokenize("") == []'),
428
+ ],
429
+ ),
430
+ ]