AtCoderStudyBooster 0.3__py3-none-any.whl → 0.3.2__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.
atcdr/test.py CHANGED
@@ -4,435 +4,430 @@ import tempfile
4
4
  import time
5
5
  from dataclasses import dataclass, field
6
6
  from enum import Enum
7
- from typing import Dict, Generator, List, Tuple, Union, Optional
7
+ from typing import Dict, List, Optional, Tuple, Union
8
8
 
9
- from bs4 import BeautifulSoup as bs
10
- from rich.console import Console, Group, RenderableType
9
+ import rich_click as click
10
+ from rich.console import Group, RenderableType
11
+ from rich.live import Live
11
12
  from rich.markup import escape
12
13
  from rich.panel import Panel
14
+ from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn
13
15
  from rich.rule import Rule
14
16
  from rich.style import Style
15
17
  from rich.syntax import Syntax
16
18
  from rich.table import Table
17
19
  from rich.text import Text
18
20
 
19
- from atcdr.util.execute import execute_files
21
+ from atcdr.util.fileops import add_file_selector
20
22
  from atcdr.util.filetype import (
21
- COMPILED_LANGUAGES,
22
- INTERPRETED_LANGUAGES,
23
- Lang,
24
- detect_language,
25
- lang2str,
23
+ COMPILED_LANGUAGES,
24
+ INTERPRETED_LANGUAGES,
25
+ Lang,
26
+ detect_language,
27
+ lang2str,
26
28
  )
29
+ from atcdr.util.parse import ProblemHTML
27
30
 
28
31
 
29
32
  @dataclass
30
33
  class TestCase:
31
- input: str
32
- output: str
34
+ input: str
35
+ output: str
33
36
 
34
37
 
35
38
  @dataclass
36
39
  class LabeledTestCase:
37
- label: str
38
- case: TestCase
40
+ label: str
41
+ case: TestCase
39
42
 
40
43
 
41
44
  class ResultStatus(Enum):
42
- AC = 'Accepted'
43
- WA = 'Wrong Answer'
44
- TLE = 'Time Limit Exceeded'
45
- MLE = 'Memory Limit Exceeded'
46
- RE = 'Runtime Error'
47
- CE = 'Compilation Error'
48
- WJ = 'Juding ...'
45
+ AC = 'Accepted'
46
+ WA = 'Wrong Answer'
47
+ TLE = 'Time Limit Exceeded'
48
+ MLE = 'Memory Limit Exceeded'
49
+ RE = 'Runtime Error'
50
+ CE = 'Compilation Error'
51
+ WJ = 'Juding ...'
49
52
 
50
53
 
51
54
  @dataclass
52
55
  class TestCaseResult:
53
- output: str
54
- executed_time: Union[int, None]
55
- # memory_usage: Union[int, None]
56
- passed: ResultStatus
56
+ output: str
57
+ executed_time: Union[int, None]
58
+ # memory_usage: Union[int, None]
59
+ passed: ResultStatus
57
60
 
58
61
 
59
62
  @dataclass
60
63
  class LabeledTestCaseResult:
61
- label: str
62
- testcase: TestCase
63
- result: TestCaseResult
64
+ label: str
65
+ testcase: TestCase
66
+ result: TestCaseResult
64
67
 
65
68
 
66
69
  @dataclass
67
70
  class TestInformation:
68
- lang: Lang
69
- sourcename: str
70
- case_number: int
71
- result_summary: ResultStatus = ResultStatus.WJ
72
- resultlist: List[LabeledTestCaseResult] = field(default_factory=list) # 修正
73
- compiler_message: str = ''
74
- compile_time : Optional[int] = None
75
-
76
-
77
- def create_testcases_from_html(html: str) -> List[LabeledTestCase]:
78
- soup = bs(html, 'html.parser')
79
- test_cases = []
80
-
81
- for i in range(1, 20):
82
- sample_input_section = soup.find('h3', text=f'Sample Input {i}')
83
- sample_output_section = soup.find('h3', text=f'Sample Output {i}')
84
- if not sample_input_section or not sample_output_section:
85
- break
86
-
87
- sample_input_pre = sample_input_section.find_next('pre')
88
- sample_output_pre = sample_output_section.find_next('pre')
89
-
90
- sample_input = (
91
- sample_input_pre.get_text(strip=True)
92
- if sample_input_pre is not None
93
- else ''
94
- )
95
- sample_output = (
96
- sample_output_pre.get_text(strip=True)
97
- if sample_output_pre is not None
98
- else ''
99
- )
100
-
101
- test_case = TestCase(input=sample_input, output=sample_output)
102
- labeled_test_case = LabeledTestCase(label=f'Sample {i}', case=test_case)
103
- test_cases.append(labeled_test_case)
104
-
105
- return test_cases
71
+ lang: Lang
72
+ sourcename: str
73
+ case_number: int
74
+ results: List[ResultStatus] = field(default_factory=list)
75
+ compiler_message: str = ''
76
+ compile_time: Optional[int] = None
77
+ _summary: Optional[ResultStatus] = None
78
+
79
+ @property
80
+ def summary(self) -> ResultStatus:
81
+ if self._summary:
82
+ return self._summary
83
+ else:
84
+ priority_order = [
85
+ ResultStatus.CE,
86
+ ResultStatus.RE,
87
+ ResultStatus.WA,
88
+ ResultStatus.TLE,
89
+ ResultStatus.MLE,
90
+ ResultStatus.WJ,
91
+ ResultStatus.AC,
92
+ ]
93
+ priority_dict = {
94
+ status: index + 1 for index, status in enumerate(priority_order)
95
+ }
96
+ summary = min(
97
+ self.results,
98
+ key=lambda status: priority_dict[status],
99
+ default=ResultStatus.WJ,
100
+ )
101
+
102
+ if len(self.results) == self.case_number:
103
+ return summary
104
+
105
+ return ResultStatus.WJ if summary == ResultStatus.AC else summary
106
+
107
+ @summary.setter
108
+ def summary(self, value: ResultStatus) -> None:
109
+ self._summary = value
110
+
111
+ def update(
112
+ self, updator: Union[TestCaseResult, LabeledTestCaseResult, ResultStatus]
113
+ ) -> None:
114
+ match updator:
115
+ case TestCaseResult():
116
+ self.results.append(updator.passed)
117
+ case LabeledTestCaseResult():
118
+ self.results.append(updator.result.passed)
119
+ case ResultStatus():
120
+ self.results.append(updator)
121
+
122
+ def __iadd__(
123
+ self, other: Union[TestCaseResult, LabeledTestCaseResult, ResultStatus]
124
+ ) -> 'TestInformation':
125
+ self.update(other)
126
+ return self
127
+
128
+
129
+ class TestRunner:
130
+ def __init__(self, path: str, lcases: List[LabeledTestCase]) -> None:
131
+ self.source = path
132
+ self.lcases = iter(lcases)
133
+ self.info = TestInformation(
134
+ lang=detect_language(self.source),
135
+ sourcename=path,
136
+ case_number=len(lcases),
137
+ )
138
+
139
+ def __iter__(self):
140
+ lang = self.info.lang
141
+ if lang in COMPILED_LANGUAGES:
142
+ exe_path, compile_result, compile_time = run_compile(self.source, lang)
143
+ self.info.compiler_message = compile_result.stderr
144
+ self.info.compile_time = compile_time
145
+ if compile_result.returncode != 0:
146
+ self.info.results = [ResultStatus.CE]
147
+ return iter([])
148
+
149
+ self.cmd = [
150
+ arg.format(source_path=self.source, exec_path=exe_path)
151
+ for arg in LANGUAGE_RUN_COMMANDS[lang]
152
+ ]
153
+ self.exe = exe_path
154
+ run_code(self.cmd, TestCase(input='', output='')) # バイナリーの慣らし運転
155
+ elif lang in INTERPRETED_LANGUAGES:
156
+ self.cmd = [
157
+ arg.format(source_path=self.source)
158
+ for arg in LANGUAGE_RUN_COMMANDS[lang]
159
+ ]
160
+ self.exe = None
161
+ else:
162
+ raise ValueError(f'{lang}の適切な言語のランナーが見つかりませんでした.')
163
+
164
+ return self
165
+
166
+ def __next__(self):
167
+ try:
168
+ lcase = next(self.lcases)
169
+ result = run_code(self.cmd, lcase.case)
170
+ self.info += result
171
+ return LabeledTestCaseResult(lcase.label, lcase.case, result)
172
+ except StopIteration:
173
+ if self.exe and os.path.exists(self.exe):
174
+ os.remove(self.exe)
175
+ raise
106
176
 
107
177
 
108
178
  def run_code(cmd: list, case: TestCase) -> TestCaseResult:
109
- start_time = time.time()
110
- try:
111
- proc = subprocess.run(
112
- cmd, input=case.input, text=True, capture_output=True, timeout=4
113
- )
114
- executed_time = int((time.time() - start_time) * 1000)
115
- except subprocess.TimeoutExpired as e_proc:
116
- executed_time = int((time.time() - start_time) * 1000)
117
- stdout_text = e_proc.stdout.decode('utf-8') if e_proc.stdout is not None else ''
118
- stderr_text = e_proc.stderr.decode('utf-8') if e_proc.stderr is not None else ''
119
- text = stdout_text + '\n' + stderr_text
120
- return TestCaseResult(
121
- output=text, executed_time=executed_time, passed=ResultStatus.TLE
122
- )
123
-
124
- # プロセスの終了コードを確認し、異常終了ならREを返す
125
- if proc.returncode != 0:
126
- return TestCaseResult(
127
- output=proc.stdout + '\n' + proc.stderr,
128
- executed_time=executed_time,
129
- passed=ResultStatus.RE,
130
- )
131
-
132
- # 実際の出力と期待される出力を比較
133
- actual_output = proc.stdout.strip()
134
- expected_output = case.output.strip()
135
-
136
- if actual_output != expected_output:
137
- return TestCaseResult(
138
- output=actual_output,
139
- executed_time=executed_time,
140
- passed=ResultStatus.WA,
141
- )
142
- else:
143
- return TestCaseResult(
144
- output=actual_output, executed_time=executed_time, passed=ResultStatus.AC
145
- )
179
+ start_time = time.time()
180
+ try:
181
+ proc = subprocess.run(
182
+ cmd, input=case.input, text=True, capture_output=True, timeout=4
183
+ )
184
+ end_time = time.time()
185
+ executed_time = int((end_time - start_time) * 1000)
186
+ except subprocess.TimeoutExpired as e_proc:
187
+ end_time = time.time()
188
+ executed_time = int((end_time - start_time) * 1000)
189
+ stdout_text = e_proc.stdout.decode('utf-8') if e_proc.stdout is not None else ''
190
+ stderr_text = e_proc.stderr.decode('utf-8') if e_proc.stderr is not None else ''
191
+ text = stdout_text + '\n' + stderr_text
192
+ return TestCaseResult(
193
+ output=text, executed_time=executed_time, passed=ResultStatus.TLE
194
+ )
195
+
196
+ # プロセスの終了コードを確認し、異常終了ならREを返す
197
+ if proc.returncode != 0:
198
+ return TestCaseResult(
199
+ output=proc.stdout + '\n' + proc.stderr,
200
+ executed_time=executed_time,
201
+ passed=ResultStatus.RE,
202
+ )
203
+
204
+ # 実際の出力と期待される出力を比較
205
+ actual_output = proc.stdout.strip()
206
+ expected_output = case.output.strip()
207
+
208
+ if actual_output != expected_output:
209
+ return TestCaseResult(
210
+ output=actual_output,
211
+ executed_time=executed_time,
212
+ passed=ResultStatus.WA,
213
+ )
214
+ else:
215
+ return TestCaseResult(
216
+ output=actual_output, executed_time=executed_time, passed=ResultStatus.AC
217
+ )
146
218
 
147
219
 
148
220
  LANGUAGE_RUN_COMMANDS: Dict[Lang, list] = {
149
- Lang.PYTHON: ['python3', '{source_path}'],
150
- Lang.JAVASCRIPT: ['node', '{source_path}'],
151
- Lang.C: ['{exec_path}'],
152
- Lang.CPP: ['{exec_path}'],
153
- Lang.RUST: ['{exec_path}'],
154
- Lang.JAVA: ['java', '{exec_path}'],
221
+ Lang.PYTHON: ['python3', '{source_path}'],
222
+ Lang.JAVASCRIPT: ['node', '{source_path}'],
223
+ Lang.C: ['{exec_path}'],
224
+ Lang.CPP: ['{exec_path}'],
225
+ Lang.RUST: ['{exec_path}'],
226
+ Lang.JAVA: ['java', os.path.splitext(os.path.basename('{source_path}'))[0]],
155
227
  }
156
228
 
157
229
  LANGUAGE_COMPILE_COMMANDS: Dict[Lang, list] = {
158
- Lang.C: ['gcc', '{source_path}', '-o', '{exec_path}'],
159
- Lang.CPP: ['g++', '{source_path}', '-o', '{exec_path}'],
160
- Lang.RUST: ['rustc', '{source_path}', '-o', '{exec_path}'],
161
- Lang.JAVA: ['javac', '{source_path}'],
230
+ Lang.C: ['gcc', '{source_path}', '-o', '{exec_path}'],
231
+ Lang.CPP: ['g++', '{source_path}', '-o', '{exec_path}'],
232
+ Lang.RUST: ['rustc', '{source_path}', '-o', '{exec_path}'],
233
+ Lang.JAVA: ['javac', '{source_path}'],
162
234
  }
163
235
 
164
236
 
165
- def run_compile(path: str, lang: Lang) -> Tuple[str, subprocess.CompletedProcess]:
166
- with tempfile.NamedTemporaryFile(delete=True) as tmp:
167
- exec_path = tmp.name
168
- cmd = [
169
- arg.format(source_path=path, exec_path=exec_path)
170
- for arg in LANGUAGE_COMPILE_COMMANDS[lang]
171
- ]
172
- start_time = time.time()
173
- compile_result = subprocess.run(cmd, capture_output=True, text=True)
174
- compile_time = int((time.time() - start_time) * 1000)
175
-
176
- return exec_path, compile_result, compile_time
177
-
178
-
179
- def judge_code_from(
180
- lcases: List[LabeledTestCase], path: str
181
- ) -> Generator[
182
- Union[LabeledTestCaseResult, TestInformation], # type: ignore
183
- None,
184
- None,
185
- ]:
186
- lang = detect_language(path)
187
- if lang in COMPILED_LANGUAGES:
188
- exe_path, compile_result, compile_time = run_compile(path, lang)
189
- if compile_result.returncode != 0:
190
- yield TestInformation(
191
- lang=lang,
192
- sourcename=path,
193
- case_number=len(lcases),
194
- result_summary=ResultStatus.CE,
195
- compiler_message=compile_result.stderr,
196
- )
197
- return
198
- else:
199
- yield TestInformation(
200
- lang=lang,
201
- sourcename=path,
202
- case_number=len(lcases),
203
- compiler_message=compile_result.stderr,
204
- compile_time=compile_time,
205
- )
206
-
207
- cmd = [
208
- arg.format(exec_path=exe_path) for arg in LANGUAGE_RUN_COMMANDS[lang]
209
- ]
210
-
211
- for lcase in lcases:
212
- yield LabeledTestCaseResult(
213
- lcase.label, lcase.case, run_code(cmd, lcase.case)
214
- )
215
-
216
- if os.path.exists(exe_path):
217
- os.remove(exe_path)
218
-
219
- elif lang in INTERPRETED_LANGUAGES:
220
- yield TestInformation(
221
- lang=lang,
222
- sourcename=path,
223
- case_number=len(lcases),
224
- )
225
- cmd = [arg.format(source_path=path) for arg in LANGUAGE_RUN_COMMANDS[lang]]
226
- for lcase in lcases:
227
- yield LabeledTestCaseResult(
228
- lcase.label, lcase.case, run_code(cmd, lcase.case)
229
- )
230
- else:
231
- raise ValueError('適切な言語が見つかりませんでした.')
237
+ def run_compile(
238
+ path: str, lang: Lang
239
+ ) -> Tuple[str, subprocess.CompletedProcess, Optional[int]]:
240
+ with tempfile.NamedTemporaryFile(delete=False) as tmp:
241
+ exec_path = tmp.name
242
+ cmd = [
243
+ arg.format(source_path=path, exec_path=exec_path)
244
+ for arg in LANGUAGE_COMPILE_COMMANDS[lang]
245
+ ]
246
+ start_time = time.time()
247
+ compile_result = subprocess.run(cmd, capture_output=True, text=True)
248
+ compile_time = int((time.time() - start_time) * 1000)
249
+
250
+ return exec_path, compile_result, compile_time
232
251
 
233
252
 
234
253
  COLOR_MAP = {
235
- ResultStatus.AC: 'green',
236
- ResultStatus.WA: 'red',
237
- ResultStatus.TLE: 'yellow',
238
- ResultStatus.MLE: 'yellow',
239
- ResultStatus.RE: 'yellow',
240
- ResultStatus.CE: 'yellow',
241
- ResultStatus.WJ: 'grey',
254
+ ResultStatus.AC: 'green',
255
+ ResultStatus.WA: 'red',
256
+ ResultStatus.TLE: 'yellow',
257
+ ResultStatus.MLE: 'yellow',
258
+ ResultStatus.RE: 'yellow',
259
+ ResultStatus.CE: 'yellow',
260
+ ResultStatus.WJ: 'grey',
242
261
  }
243
262
 
244
263
  STATUS_TEXT_MAP = {
245
- ResultStatus.AC: Text.assemble(
246
- ('\u2713 ', 'green'),
247
- (
248
- f'{ResultStatus.AC.value}',
249
- Style(bgcolor=COLOR_MAP[ResultStatus.AC], bold=True),
250
- ),
251
- ),
252
- ResultStatus.WA: Text(
253
- f'\u00d7 {ResultStatus.WA.value}', style=COLOR_MAP[ResultStatus.WA]
254
- ),
255
- ResultStatus.TLE: Text(
256
- f'\u00d7 {ResultStatus.TLE.value}', style=COLOR_MAP[ResultStatus.TLE]
257
- ),
258
- ResultStatus.MLE: Text(
259
- f'\u00d7 {ResultStatus.MLE.value}', style=COLOR_MAP[ResultStatus.MLE]
260
- ),
261
- ResultStatus.RE: Text(
262
- f'\u00d7 {ResultStatus.RE.value}', style=COLOR_MAP[ResultStatus.RE]
263
- ),
264
- ResultStatus.CE: Text(
265
- f'\u00d7 {ResultStatus.CE.value}', style=COLOR_MAP[ResultStatus.CE]
266
- ),
267
- ResultStatus.WJ: Text(
268
- f'\u23f3 {ResultStatus.WJ.value}', style=COLOR_MAP[ResultStatus.WJ]
269
- ),
264
+ ResultStatus.AC: Text.assemble(
265
+ ('\u2713 ', 'green'),
266
+ (
267
+ f'{ResultStatus.AC.value}',
268
+ Style(bgcolor=COLOR_MAP[ResultStatus.AC], bold=True),
269
+ ),
270
+ ),
271
+ ResultStatus.WA: Text(
272
+ f'\u00d7 {ResultStatus.WA.value}', style=COLOR_MAP[ResultStatus.WA]
273
+ ),
274
+ ResultStatus.TLE: Text(
275
+ f'\u00d7 {ResultStatus.TLE.value}', style=COLOR_MAP[ResultStatus.TLE]
276
+ ),
277
+ ResultStatus.MLE: Text(
278
+ f'\u00d7 {ResultStatus.MLE.value}', style=COLOR_MAP[ResultStatus.MLE]
279
+ ),
280
+ ResultStatus.RE: Text(
281
+ f'\u00d7 {ResultStatus.RE.value}', style=COLOR_MAP[ResultStatus.RE]
282
+ ),
283
+ ResultStatus.CE: Text(
284
+ f'\u00d7 {ResultStatus.CE.value}', style=COLOR_MAP[ResultStatus.CE]
285
+ ),
286
+ ResultStatus.WJ: Text(
287
+ f'\u23f3 {ResultStatus.WJ.value}', style=COLOR_MAP[ResultStatus.WJ]
288
+ ),
270
289
  }
271
290
 
272
291
 
273
- def create_renderable_test_info(test_info: TestInformation) -> RenderableType:
274
- components = []
275
-
276
- success_count = sum(
277
- 1 for result in test_info.resultlist if result.result.passed == ResultStatus.AC
278
- )
279
- total_count = test_info.case_number
280
-
281
- # 結果に応じたスタイル付きのテキストを取得
282
- status_text = STATUS_TEXT_MAP[test_info.result_summary]
283
-
284
- # ヘッダーのテキストを構築
285
- header_text = Text.assemble(
286
- Text.from_markup(f'[cyan]{test_info.sourcename}[/]のテスト [\n]'),
287
- Text.from_markup(f'[gray]コンパイルにかかった時間: [italic]{test_info.compile_time} ms[/][/][\n]') if test_info.compile_time else Text(''),
288
- status_text,
289
- Text.from_markup(
290
- f' [{COLOR_MAP[test_info.result_summary]} bold]{success_count}[/] / [white bold]{total_count}[/]'
291
- ),
292
- )
293
-
294
- components.append(Panel(header_text, expand=False))
295
-
296
- if test_info.compiler_message:
297
- rule = Rule(
298
- title='コンパイラーのメッセージ',
299
- style=COLOR_MAP[ResultStatus.CE],
300
- )
301
- components.append(rule)
302
- error_message = Syntax(
303
- test_info.compiler_message, lang2str(test_info.lang), line_numbers=False
304
- )
305
- components.append(error_message)
306
-
307
- return Group(*components)
308
-
309
-
310
- def update_test_info(
311
- test_info: TestInformation, test_result: LabeledTestCaseResult
312
- ) -> None:
313
- test_info.resultlist.append(test_result)
314
-
315
- priority_order = [
316
- ResultStatus.RE,
317
- ResultStatus.WA,
318
- ResultStatus.TLE,
319
- ResultStatus.MLE,
320
- ResultStatus.WJ,
321
- ResultStatus.AC,
322
- ]
323
-
324
- # 現在の結果の中で最も高い優先順位のステータスを見つける
325
- highest_priority_status = (
326
- test_info.result_summary
327
- ) # デフォルトはWJまたは現在のサマリー
328
- for result in test_info.resultlist:
329
- status = result.result.passed
330
- if priority_order.index(status) < priority_order.index(highest_priority_status):
331
- highest_priority_status = status
332
-
333
- # 特殊ケース: すべてのテストケースがACである場合(途中でも)
334
- if all(result.result.passed == ResultStatus.AC for result in test_info.resultlist):
335
- test_info.result_summary = ResultStatus.AC
336
- else:
337
- test_info.result_summary = highest_priority_status
292
+ def create_renderable_test_info(
293
+ test_info: TestInformation, progress: Optional[Progress] = None
294
+ ) -> RenderableType:
295
+ components = []
296
+
297
+ success_count = sum(1 for result in test_info.results if result == ResultStatus.AC)
298
+ total_count = test_info.case_number
299
+
300
+ status_text = STATUS_TEXT_MAP[test_info.summary]
301
+
302
+ header_text = Text.assemble(
303
+ Text.from_markup(f'[cyan]{test_info.sourcename}[/]のテスト \n'),
304
+ Text.from_markup(
305
+ f'[italic #0f0f0f]コンパイルにかかった時間: [not italic cyan]{test_info.compile_time}[/] ms[/]\n'
306
+ )
307
+ if test_info.compile_time
308
+ else Text(''),
309
+ status_text,
310
+ Text.from_markup(
311
+ f' [{COLOR_MAP[test_info.summary]} bold]{success_count}[/] / [white bold]{total_count}[/]'
312
+ ),
313
+ )
314
+
315
+ if progress:
316
+ components.append(Panel(Group(header_text, progress), expand=False))
317
+ else:
318
+ components.append(Panel(header_text, expand=False))
319
+
320
+ if test_info.compiler_message:
321
+ rule = Rule(
322
+ title='コンパイラーのメッセージ',
323
+ style=COLOR_MAP[ResultStatus.CE],
324
+ )
325
+ components.append(rule)
326
+ error_message = Syntax(
327
+ test_info.compiler_message, lang2str(test_info.lang), line_numbers=False
328
+ )
329
+ components.append(error_message)
330
+
331
+ return Group(*components)
338
332
 
339
333
 
340
334
  def create_renderable_test_result(
341
- i: int,
342
- test_result: LabeledTestCaseResult,
335
+ i: int,
336
+ test_result: LabeledTestCaseResult,
343
337
  ) -> RenderableType:
344
- rule = Rule(
345
- title=f'No.{i+1} {test_result.label}',
346
- style=COLOR_MAP[test_result.result.passed],
347
- )
348
-
349
- # 以下の部分は if-else ブロックの外に移動
350
- status_header = Text.assemble(
351
- 'ステータス ',
352
- STATUS_TEXT_MAP[test_result.result.passed], # status_text をここに追加
353
- )
354
-
355
- execution_time_text = None
356
- if test_result.result.executed_time is not None:
357
- execution_time_text = Text.from_markup(
358
- f'実行時間 [cyan]{test_result.result.executed_time}[/cyan] ms'
359
- )
360
-
361
- table = Table(show_header=True, header_style='bold')
362
- table.add_column('入力', style='cyan', min_width=10)
363
-
364
- if test_result.result.passed != ResultStatus.AC:
365
- table.add_column(
366
- '出力', style=COLOR_MAP[test_result.result.passed], min_width=10
367
- )
368
- table.add_column('正解の出力', style=COLOR_MAP[ResultStatus.AC], min_width=10)
369
- table.add_row(
370
- escape(test_result.testcase.input),
371
- escape(test_result.result.output),
372
- escape(test_result.testcase.output),
373
- )
374
- else:
375
- table.add_column(
376
- '出力', style=COLOR_MAP[test_result.result.passed], min_width=10
377
- )
378
- table.add_row(
379
- escape(test_result.testcase.input), escape(test_result.result.output)
380
- )
381
-
382
- components = [
383
- rule,
384
- status_header,
385
- execution_time_text if execution_time_text else '',
386
- table,
387
- ]
388
-
389
- return Group(*components)
390
-
391
-
392
- def render_results(
393
- results: Generator[Union[LabeledTestCaseResult, TestInformation], None, None],
394
- ) -> None:
395
- console = Console()
396
-
397
- # 最初の結果は TestInformation として取得
398
- first_result = next(results)
399
- if not isinstance(first_result, TestInformation):
400
- raise ValueError('最初のジェネレーターの結果はTestInformationです')
401
- test_info: TestInformation = first_result
402
-
403
- curernt_display_object = [test_info]
404
-
405
- while True :
406
- # 各テストケースの結果表示
407
- for i, result in enumerate(results):
408
- if isinstance(result, LabeledTestCaseResult):
409
- console.print(create_renderable_test_result(i, result))
410
- update_test_info(test_info, result)
411
- else:
412
- raise ValueError('テスト結果がyieldする型はLabeledTestCaseResultです')
413
-
414
- console.print(create_renderable_test_info(test_info))
338
+ rule = Rule(
339
+ title=f'No.{i+1} {test_result.label}',
340
+ style=COLOR_MAP[test_result.result.passed],
341
+ )
342
+
343
+ # 以下の部分は if-else ブロックの外に移動
344
+ status_header = Text.assemble(
345
+ 'ステータス ',
346
+ STATUS_TEXT_MAP[test_result.result.passed], # status_text をここに追加
347
+ )
348
+
349
+ execution_time_text = None
350
+ if test_result.result.executed_time is not None:
351
+ execution_time_text = Text.from_markup(
352
+ f'実行時間 [cyan]{test_result.result.executed_time}[/cyan] ms'
353
+ )
354
+
355
+ table = Table(show_header=True, header_style='bold')
356
+ table.add_column('入力', style='cyan', min_width=10)
357
+
358
+ if test_result.result.passed != ResultStatus.AC:
359
+ table.add_column(
360
+ '出力',
361
+ style=COLOR_MAP[test_result.result.passed],
362
+ min_width=10,
363
+ overflow='fold',
364
+ )
365
+ table.add_column('正解の出力', style=COLOR_MAP[ResultStatus.AC], min_width=10)
366
+ table.add_row(
367
+ escape(test_result.testcase.input),
368
+ escape(test_result.result.output),
369
+ escape(test_result.testcase.output),
370
+ )
371
+ else:
372
+ table.add_column(
373
+ '出力', style=COLOR_MAP[test_result.result.passed], min_width=10
374
+ )
375
+ table.add_row(
376
+ escape(test_result.testcase.input), escape(test_result.result.output)
377
+ )
378
+
379
+ components = [
380
+ rule,
381
+ status_header,
382
+ execution_time_text if execution_time_text else '',
383
+ table,
384
+ ]
385
+
386
+ return Group(*components)
387
+
388
+
389
+ def render_results(test: TestRunner) -> None:
390
+ progress = Progress(
391
+ SpinnerColumn(style='white', spinner_name='circleHalves'),
392
+ TextColumn('{task.description}'),
393
+ SpinnerColumn(style='white', spinner_name='simpleDots'),
394
+ BarColumn(),
395
+ )
396
+ task_id = progress.add_task(description='テスト進行中', total=test.info.case_number)
397
+
398
+ current_display = [create_renderable_test_info(test.info, progress)]
399
+
400
+ with Live(Group(*current_display)) as live:
401
+ for i, result in enumerate(test):
402
+ progress.advance(task_id, advance=1)
403
+ current_display[-1] = create_renderable_test_info(test.info, progress)
404
+ current_display.insert(-1, (create_renderable_test_result(i, result)))
405
+ live.update(Group(*current_display))
406
+
407
+ progress.update(task_id, description='テスト完了') # 完了メッセージに更新
408
+ current_display[-1] = create_renderable_test_info(test.info, progress)
409
+ live.update(Group(*current_display))
415
410
 
416
411
 
417
412
  def run_test(path_of_code: str) -> None:
418
- html_paths = [f for f in os.listdir('.') if f.endswith('.html')]
419
- if not html_paths:
420
- print(
421
- '問題のファイルが見つかりません。\n問題のファイルが存在するディレクトリーに移動してから実行してください。'
422
- )
423
- return
424
-
425
- with open(html_paths[0], 'r') as file:
426
- html = file.read()
427
-
428
- test_cases = create_testcases_from_html(html)
429
- test_results = judge_code_from(test_cases, path_of_code)
430
- render_results(test_results)
431
-
432
-
433
- def test(*args: str) -> None:
434
- execute_files(
435
- *args,
436
- func=run_test,
437
- target_filetypes=INTERPRETED_LANGUAGES + COMPILED_LANGUAGES,
438
- )
413
+ html_paths = [f for f in os.listdir('.') if f.endswith('.html')]
414
+ if not html_paths:
415
+ print(
416
+ '問題のファイルが見つかりません。\n問題のファイルが存在するディレクトリーに移動してから実行してください。'
417
+ )
418
+ return
419
+
420
+ with open(html_paths[0], 'r') as file:
421
+ html = file.read()
422
+
423
+ lcases = ProblemHTML(html).load_labeled_testcase()
424
+ test = TestRunner(path_of_code, lcases)
425
+ render_results(test)
426
+
427
+
428
+ @click.command(short_help='テストを実行')
429
+ @add_file_selector('files', filetypes=COMPILED_LANGUAGES + INTERPRETED_LANGUAGES)
430
+ def test(files):
431
+ """指定したソースコードをサンプルケースでテストします。"""
432
+ for path in files:
433
+ run_test(path)