Skip to content

Commit 0c9fbc7

Browse files
authored
11 use the return of the test function and render it to the user properly (#26)
* feat(result-formatter): parsing and formatting the test result Signed-off-by: dfayd <78728332+dfayd0@users.noreply.github.com> * feat(result-formatter): parsing and formatting the test result Signed-off-by: dfayd <78728332+dfayd0@users.noreply.github.com> * style(result-formatter, utils): linting Signed-off-by: dfayd <78728332+dfayd0@users.noreply.github.com> --------- Signed-off-by: dfayd <78728332+dfayd0@users.noreply.github.com>
1 parent 7659e86 commit 0c9fbc7

5 files changed

Lines changed: 328 additions & 74 deletions

File tree

src/leetcode_api_runner.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::{
66
},
77
};
88

9-
use colored::Colorize;
9+
use colored::*;
1010
use leetcoderustapi::{
1111
problem_actions::Problem,
1212
ProgrammingLanguage,
@@ -18,6 +18,7 @@ use crate::{
1818
config::RuntimeConfigSetup,
1919
local_config::LocalConfig,
2020
readme_parser::LeetcodeReadmeParser,
21+
result_formatter::format_test_result,
2122
test_generator::TestGenerator,
2223
utils::*,
2324
};
@@ -162,12 +163,15 @@ impl LeetcodeApiRunner {
162163
&self, id: u32, path_to_file: &String,
163164
) -> io::Result<String> {
164165
let problem_info = self.api.set_problem_by_id(id).await?;
166+
let name = problem_info.description()?.name;
165167
let file_content = std::fs::read_to_string(path_to_file)
166168
.expect("Unable to read the file");
167169
let language = get_language_from_extension(path_to_file);
168-
169-
let test_res = problem_info.send_test(language, &file_content).await?;
170-
Ok(format!("Test response for problem {id}: \n{test_res:#?}"))
170+
let _ = run_local_check(path_to_file, &language).await?;
171+
let processed_code = preprocess_code(&file_content, &language);
172+
let test_res =
173+
problem_info.send_test(language, &processed_code).await?;
174+
Ok(format_test_result(id, &name, &test_res))
171175
}
172176

173177
pub async fn submit_response(

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod config;
44
pub mod leetcode_api_runner;
55
pub mod local_config;
66
pub mod readme_parser;
7+
pub mod result_formatter;
78
pub mod test_generator;
89
pub mod utils;
910

src/result_formatter.rs

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
use colored::Colorize;
2+
use leetcoderustapi::resources::test_send::TestExecutionResult;
3+
4+
pub fn format_test_result(
5+
id: u32, name: &str, result: &TestExecutionResult,
6+
) -> String {
7+
let mut output = String::new();
8+
9+
output.push_str(&format!("🧪 Test Results for Problem {id}: {name}\n"));
10+
output.push_str(&"=".repeat(50));
11+
output.push('\n');
12+
output.push_str(&format_status_message(result.status_msg.as_deref()));
13+
output.push('\n');
14+
15+
// Language
16+
if let Some(ref lang) = result.pretty_lang {
17+
output.push_str(&format!("🔧 Language: {}\n", lang.cyan()));
18+
}
19+
20+
// // Execution success
21+
// if let Some(run_success) = result.run_success {
22+
// let success_text = if run_success { "Yes".green() } else { "No".red()
23+
// }; output.push_str(&format!("> Execution Success: {}\n",
24+
// success_text)); }
25+
26+
// Test case results
27+
if let (Some(correct), Some(total)) =
28+
(result.total_correct, result.total_testcases)
29+
{
30+
let ratio_color = if correct == total {
31+
|s: String| s.green()
32+
} else if correct > 0 {
33+
|s: String| s.yellow()
34+
} else {
35+
|s: String| s.red()
36+
};
37+
output.push_str(&format!(
38+
"📊 Test Cases: {}\n",
39+
ratio_color(format!("{correct}/{total}"))
40+
));
41+
}
42+
43+
// Runtime and memory (only if successful)
44+
if result.run_success == Some(true) {
45+
if let Some(ref runtime) = result.status_runtime {
46+
if runtime != "N/A" {
47+
output.push_str(&format!("⏱️ Runtime: {}\n", runtime.blue()));
48+
}
49+
}
50+
51+
if let Some(ref memory) = result.status_memory {
52+
if memory != "N/A" {
53+
output.push_str(&format!("💾 Memory: {}\n", memory.blue()));
54+
}
55+
}
56+
57+
// Percentiles if available
58+
if let Some(Some(runtime_perc)) = result.runtime_percentile {
59+
output.push_str(&format!(
60+
"📈 Runtime Percentile: {runtime_perc:.1}%\n"
61+
));
62+
}
63+
64+
if let Some(Some(memory_perc)) = result.memory_percentile {
65+
output.push_str(&format!(
66+
"📈 Memory Percentile: {memory_perc:.1}%\n"
67+
));
68+
}
69+
}
70+
71+
// Compilation errors
72+
if let Some(ref compile_error) = result.compile_error {
73+
if !compile_error.is_empty() {
74+
output.push_str(&format!(
75+
"\n🔴 {}\n",
76+
"Compilation Error:".red().bold()
77+
));
78+
output.push_str(&format!("{}\n", compile_error.red()));
79+
}
80+
}
81+
82+
// Detailed compilation errors
83+
if let Some(ref full_error) = result.full_compile_error {
84+
if !full_error.is_empty() && result.compile_error.is_none() {
85+
output.push_str(&format!(
86+
"\n📋 {}\n",
87+
"Detailed Error:".red().bold()
88+
));
89+
output.push_str(&format!("{full_error}\n"));
90+
}
91+
}
92+
93+
// Wrong answer details
94+
if let Some(ref code_output) = result.code_output {
95+
if !code_output.is_empty() {
96+
output.push_str(&format!("\n❌ {}\n", "Your Output:".red().bold()));
97+
for (i, out) in code_output.iter().enumerate() {
98+
output.push_str(&format!("Test {}: {}\n", i + 1, out));
99+
}
100+
}
101+
}
102+
103+
if let Some(ref expected_output) = result.expected_code_output {
104+
if !expected_output.is_empty() {
105+
output.push_str(&format!(
106+
"\n✅ {}\n",
107+
"Expected Output:".green().bold()
108+
));
109+
for (i, out) in expected_output.iter().enumerate() {
110+
output.push_str(&format!("Test {}: {}\n", i + 1, out));
111+
}
112+
}
113+
}
114+
115+
// Standard output (if any)
116+
if let Some(ref std_output) = result.std_output_list {
117+
if !std_output.is_empty()
118+
&& std_output.iter().any(|s| !s.trim().is_empty())
119+
{
120+
output.push_str(&format!(
121+
"\n📤 {}\n",
122+
"Standard Output:".blue().bold()
123+
));
124+
for (i, out) in std_output.iter().enumerate() {
125+
if !out.trim().is_empty() {
126+
output.push_str(&format!("Test {}: {}\n", i + 1, out));
127+
}
128+
}
129+
}
130+
}
131+
132+
output.push('\n');
133+
output
134+
}
135+
136+
// Status with icon
137+
fn format_status_message(status: Option<&str>) -> String {
138+
match status {
139+
Some("Accepted") => "✅ Accepted".green().to_string(),
140+
Some("Wrong Answer") => "❌ Wrong Answer".red().to_string(),
141+
Some("Compile Error") => "🔴 Compile Error".red().to_string(),
142+
Some("Runtime Error") => "⚠️ Runtime Error".red().to_string(),
143+
Some("Time Limit Exceeded") => {
144+
"⏰ Time Limit Exceeded".yellow().to_string()
145+
},
146+
Some("Memory Limit Exceeded") => {
147+
"💾 Memory Limit Exceeded".yellow().to_string()
148+
},
149+
_ => "Unknown Status".yellow().to_string(),
150+
}
151+
}

src/utils.rs

Lines changed: 121 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,30 @@ pub fn get_language_from_extension(
135135
}
136136
}
137137

138+
pub fn get_extension_from_language(
139+
lang: &leetcoderustapi::ProgrammingLanguage,
140+
) -> String {
141+
match lang {
142+
leetcoderustapi::ProgrammingLanguage::CPP => "cpp".to_string(),
143+
leetcoderustapi::ProgrammingLanguage::Java => "java".to_string(),
144+
leetcoderustapi::ProgrammingLanguage::Python => "py".to_string(),
145+
leetcoderustapi::ProgrammingLanguage::Python3 => "py".to_string(),
146+
leetcoderustapi::ProgrammingLanguage::C => "c".to_string(),
147+
leetcoderustapi::ProgrammingLanguage::CSharp => "cs".to_string(),
148+
leetcoderustapi::ProgrammingLanguage::JavaScript => "js".to_string(),
149+
leetcoderustapi::ProgrammingLanguage::TypeScript => "ts".to_string(),
150+
leetcoderustapi::ProgrammingLanguage::Ruby => "rb".to_string(),
151+
leetcoderustapi::ProgrammingLanguage::Swift => "swift".to_string(),
152+
leetcoderustapi::ProgrammingLanguage::Go => "go".to_string(),
153+
leetcoderustapi::ProgrammingLanguage::Bash => "sh".to_string(),
154+
leetcoderustapi::ProgrammingLanguage::Scala => "scala".to_string(),
155+
leetcoderustapi::ProgrammingLanguage::Kotlin => "kt".to_string(),
156+
leetcoderustapi::ProgrammingLanguage::Rust => "rs".to_string(),
157+
leetcoderustapi::ProgrammingLanguage::PHP => "php".to_string(),
158+
_ => panic!("Unsupported language: {lang:?}"),
159+
}
160+
}
161+
138162
pub fn spin_the_spinner(message: &str) -> spinners::Spinner {
139163
spinners::Spinner::new(spinners::Spinners::Dots12, message.to_string())
140164
}
@@ -174,30 +198,6 @@ pub fn prompt_for_language(
174198
}
175199
}
176200

177-
pub fn get_extension_from_language(
178-
lang: &leetcoderustapi::ProgrammingLanguage,
179-
) -> String {
180-
match lang {
181-
leetcoderustapi::ProgrammingLanguage::CPP => "cpp".to_string(),
182-
leetcoderustapi::ProgrammingLanguage::Java => "java".to_string(),
183-
leetcoderustapi::ProgrammingLanguage::Python => "py".to_string(),
184-
leetcoderustapi::ProgrammingLanguage::Python3 => "py".to_string(),
185-
leetcoderustapi::ProgrammingLanguage::C => "c".to_string(),
186-
leetcoderustapi::ProgrammingLanguage::CSharp => "cs".to_string(),
187-
leetcoderustapi::ProgrammingLanguage::JavaScript => "js".to_string(),
188-
leetcoderustapi::ProgrammingLanguage::TypeScript => "ts".to_string(),
189-
leetcoderustapi::ProgrammingLanguage::Ruby => "rb".to_string(),
190-
leetcoderustapi::ProgrammingLanguage::Swift => "swift".to_string(),
191-
leetcoderustapi::ProgrammingLanguage::Go => "go".to_string(),
192-
leetcoderustapi::ProgrammingLanguage::Bash => "sh".to_string(),
193-
leetcoderustapi::ProgrammingLanguage::Scala => "scala".to_string(),
194-
leetcoderustapi::ProgrammingLanguage::Kotlin => "kt".to_string(),
195-
leetcoderustapi::ProgrammingLanguage::Rust => "rs".to_string(),
196-
leetcoderustapi::ProgrammingLanguage::PHP => "php".to_string(),
197-
_ => panic!("Unsupported language: {lang:?}"),
198-
}
199-
}
200-
201201
pub fn prefix_code(file_content: &str, lang: &ProgrammingLanguage) -> String {
202202
let prefix = match lang {
203203
ProgrammingLanguage::Rust => "pub struct Solution;\n\n".to_string(),
@@ -241,3 +241,100 @@ pub fn difficulty_color(difficulty: &str) -> colored::ColoredString {
241241
_ => "Unknown".normal(),
242242
}
243243
}
244+
245+
/// Preprocesses file content before sending to LeetCode by removing local
246+
/// compilation helpers
247+
pub fn preprocess_code(
248+
content: &str, language: &ProgrammingLanguage,
249+
) -> String {
250+
match language {
251+
ProgrammingLanguage::Rust => preprocess_rust_content(content),
252+
// For other languages, return as-is for now
253+
_ => content.to_string(),
254+
}
255+
}
256+
257+
/// Removes pub struct Solution; from the top of the file
258+
fn preprocess_rust_content(content: &str) -> String {
259+
let n = delete_line_content(content, "pub struct Solution;");
260+
remove_main(&n)
261+
}
262+
fn remove_main(content: &str) -> String {
263+
let mut c = vec![];
264+
265+
for line in content.lines() {
266+
if line.contains("fn main() {") {
267+
break;
268+
}
269+
c.push(line);
270+
}
271+
c.join("\n")
272+
}
273+
274+
fn delete_line_content(content: &str, target: &str) -> String {
275+
content
276+
.lines()
277+
.filter(|line| line.trim() != target)
278+
.collect::<Vec<_>>()
279+
.join("\n")
280+
}
281+
282+
/// Find the nearest Cargo project root (directory containing Cargo.toml)
283+
/// starting from `start_dir` and walking up.
284+
fn find_manifest_dir(start_dir: &Path) -> Option<PathBuf> {
285+
for dir in start_dir.ancestors() {
286+
let candidate = dir.join("Cargo.toml");
287+
if candidate.is_file() {
288+
return Some(dir.to_path_buf());
289+
}
290+
}
291+
None
292+
}
293+
294+
/// Runs local compilation check before sending to LeetCode
295+
pub async fn run_local_check(
296+
path_to_file: &str, language: &ProgrammingLanguage,
297+
) -> io::Result<String> {
298+
use std::process::Command;
299+
300+
match language {
301+
ProgrammingLanguage::Rust => {
302+
let file_path = Path::new(path_to_file);
303+
304+
// If within a Cargo project, run `cargo check` at the project root
305+
if let Some(parent) = file_path.parent() {
306+
if let Some(manifest_dir) = find_manifest_dir(parent) {
307+
let output = Command::new("cargo")
308+
.args(["check", "--quiet"])
309+
.current_dir(&manifest_dir)
310+
.output()?;
311+
312+
if !output.status.success() {
313+
let stderr = String::from_utf8_lossy(&output.stderr);
314+
return Ok(format!("❌ Local check failed:\n{stderr}"));
315+
}
316+
317+
return Ok("✅ Local compilation passed!".to_string());
318+
}
319+
}
320+
321+
// Fallback: compile the single file directly with rustc
322+
let output = Command::new("rustc")
323+
.args([
324+
"--edition=2021",
325+
"--emit=metadata",
326+
"--crate-type=bin",
327+
path_to_file,
328+
])
329+
.output()?;
330+
331+
if !output.status.success() {
332+
let stderr = String::from_utf8_lossy(&output.stderr);
333+
return Ok(format!("❌ Compilation failed:\n{stderr}"));
334+
}
335+
336+
Ok("✅ Local compilation passed!".to_string())
337+
},
338+
_ => Ok(format!("⚠️ Local check not implemented for {language:?}",)),
339+
}
340+
}

0 commit comments

Comments
 (0)