Skip to main content

checker_tema_3_sd/io/
handler.rs

1use std::io::{Error, ErrorKind};
2use std::time::Duration;
3use std::{process::Stdio, sync::Arc};
4
5// use eyre::{Result, ErrReport};
6use log::{debug, error, info, warn};
7use serde_json::Value;
8use similar::{ChangeTag, TextDiff};
9use tokio::fs::{self, File};
10use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
11use tokio::process::Command;
12use tokio::time::{timeout, Instant};
13
14use super::IoEvent;
15use crate::app::{App, Data};
16
17const DB_PATH: &str = "./data.json";
18
19/// In the IO thread, we handle IO event without blocking the UI thread
20pub struct IoAsyncHandler {
21    app: Arc<tokio::sync::Mutex<App>>,
22}
23
24impl IoAsyncHandler {
25    pub fn new(app: Arc<tokio::sync::Mutex<App>>) -> Self {
26        Self { app }
27    }
28
29    /// We could be async here
30    pub async fn handle_io_event(&mut self, io_event: IoEvent) {
31        let result = match io_event {
32            IoEvent::Initialize => self.do_initialize().await,
33            IoEvent::RunTest(index, exec) => self.run_test(index, exec).await,
34            IoEvent::RunAll(size) => self.run_all(size).await,
35            IoEvent::RunFailed(indexes) => self.run_failed(indexes).await,
36            IoEvent::SaveData(data) => self.save_data(data).await,
37            IoEvent::LoadChecksyle => self.load_cs().await,
38            IoEvent::Make => self.run_make().await,
39            IoEvent::UpdateRef => self.update_ref().await,
40            IoEvent::SendVMChecker => self.send_vmchecker().await,
41            IoEvent::LoadVMChecker => self.load_vmchecker().await,
42        };
43
44        self.update_ref().await.unwrap();
45
46        if let Err(Some(output)) = result {
47            error!("Oops, something wrong happen: \n{}", output.to_string());
48        }
49    }
50
51    /// We use dummy implementation here, just wait 1s
52    async fn do_initialize(&mut self) -> Result<(), Option<Error>> {
53        let mut app = self.app.lock().await;
54        app.initialized(); // we could update the app state
55                           // info!("Application initialized");
56
57        self.run_make().await?;
58
59        Ok(())
60    }
61
62    async fn load_vmchecker(&self) -> Result<(), Option<Error>> {
63        let client = reqwest::Client::new();
64        let cookie;
65
66        let (username, password) = Self::get_credentials().await?;
67
68        match client
69            .post("https://vmchecker.cs.pub.ro/services/services.py/login")
70            .body(format!("username={}&password={}", username, password))
71            .send()
72            .await
73        {
74            Ok(res) => {
75                cookie = res.headers().get("set-cookie").unwrap().clone();
76
77                if let Ok(body) = res.text().await {
78                    if body.contains("false") {
79                        return Err(Some(Error::new(ErrorKind::Other, body)));
80                    }
81                }
82            }
83            Err(err) => {
84                return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
85            }
86        };
87
88        match client.get("https://vmchecker.cs.pub.ro/services/services.py/getResults?courseId=SD&assignmentId=3-mk-kNN")
89                    .header("cookie", &cookie)
90                    .send().await {
91            Ok(res) => {
92				let v: Value = serde_json::from_str(&res.text().await.unwrap()).unwrap();
93
94				let mut app = self.app.lock().await;
95
96				match v.get(5) {
97					Some(output) => {
98						app.vmchecker_out.clear();
99						app.vmchecker_out.push_str(&output["Execuția testelor (stdout)"].to_string());
100					},
101					None => {
102						app.vmchecker_out.clear();
103						app.vmchecker_out.push_str(&v[2].to_string());
104					},
105				}
106			},
107            Err(err) => {
108                return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
109            },
110        };
111
112        Ok(())
113    }
114
115    async fn send_vmchecker(&self) -> Result<(), Option<Error>> {
116        let client = reqwest::Client::new();
117        let cookie;
118
119        let (username, password) = Self::get_credentials().await?;
120
121        match client
122            .post("https://vmchecker.cs.pub.ro/services/services.py/login")
123            .body(format!("username={}&password={}", username, password))
124            .send()
125            .await
126        {
127            Ok(res) => {
128                cookie = res.headers().get("set-cookie").unwrap().clone();
129
130                if let Ok(body) = res.text().await {
131                    if body.contains("false") {
132                        return Err(Some(Error::new(ErrorKind::Other, body)));
133                    }
134                }
135            }
136            Err(err) => {
137                return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
138            }
139        };
140
141        let mut make = Command::new("make");
142        make.arg("pack");
143
144        let res = make.output().await?;
145
146        if let Some(code) = res.status.code() {
147            if code != 0 {
148                return Err(Some(Error::new(
149                    ErrorKind::Other,
150                    std::str::from_utf8(&res.stderr).unwrap(),
151                )));
152            }
153            info!("\n{}", String::from_utf8(res.stdout).unwrap());
154        }
155
156        let body = Self::build_request().await;
157
158        match client.post("https://vmchecker.cs.pub.ro/services/services.py/uploadAssignment")
159                    .header("cookie", &cookie)
160                    .header("Content-Type", "multipart/form-data; boundary=---------------------------27347846016281380843096153774")
161                    .body(body)
162                    .send().await {
163            Ok(res) => {
164                info!("{:?}", res.text().await);
165
166                info!("Uploaded file to VMChecker, waiting for results");
167            },
168            Err(err) => {
169                return Err(Some(Error::new(ErrorKind::Other, err.to_string())));
170            },
171        };
172
173        Ok(())
174    }
175
176    async fn get_credentials() -> Result<(String, String), Option<Error>> {
177        let mut username = String::new();
178        let mut password = String::new();
179        let mut buf = String::new();
180
181        match File::open(".env").await {
182            Ok(file) => {
183                let mut bufread = BufReader::new(file);
184                bufread.read_line(&mut buf).await.unwrap();
185
186                let cred: Vec<&str> = buf.split('=').collect();
187                username.push_str(cred[1]);
188
189                buf.clear();
190                bufread.read_line(&mut buf).await.unwrap();
191
192                let cred: Vec<&str> = buf.split('=').collect();
193                password.push_str(cred[1]);
194            }
195            Err(_) => {
196                let mut file = File::create(".env").await.unwrap();
197                file.write(String::from("username=\npassword=\n").as_bytes())
198                    .await
199                    .unwrap();
200                return Err(Some(Error::new(
201                    ErrorKind::Other,
202                    "No env file found, creating... Please input your credentials",
203                )));
204            }
205        }
206
207        Ok((
208            String::from(username.strip_suffix('\n').unwrap()),
209            String::from(password.strip_suffix('\n').unwrap()),
210        ))
211    }
212
213    async fn build_request() -> Vec<u8> {
214        let mut body: Vec<u8> = Vec::new();
215
216        let mut paths = fs::read_dir("./").await.unwrap();
217        let mut zip = String::new();
218
219        while let Ok(Some(path)) = paths.next_entry().await {
220            if path.path().display().to_string().contains("Tema3.zip") {
221                zip.push_str(&path.path().display().to_string());
222            }
223        }
224
225        let mut zip_buffer = BufReader::new(File::open(&zip).await.unwrap());
226
227        // Add zip file
228        body.append(&mut "-----------------------------27347846016281380843096153774\r\n".into());
229        body.append(&mut format!("Content-Disposition: form-data; name=\"archiveFile\"; filename=\"{}\"\r\nContent-Type: application/x-zip-compressed\r\n\r\n", zip).into());
230
231        loop {
232            if let Ok(byte) = zip_buffer.read_u8().await {
233                body.push(byte);
234            } else {
235                break;
236            }
237        }
238
239        body.push(b'\r');
240        body.push(b'\n');
241
242        // Add the section
243        body.append(&mut "-----------------------------27347846016281380843096153774\r\n".into());
244        body.append(&mut "Content-Disposition: form-data; name=\"courseId\"\r\n\r\nSD\r\n".into());
245
246        // Add the task
247        body.append(&mut "-----------------------------27347846016281380843096153774\r\n".into());
248        body.append(
249            &mut "Content-Disposition: form-data; name=\"assignmentId\"\r\n\r\n3-mk-kNN\r\n".into(),
250        );
251        body.append(&mut "-----------------------------27347846016281380843096153774--\r\n".into());
252
253        body
254    }
255
256    async fn update_ref(&self) -> Result<(), Option<Error>> {
257        let mut app = self.app.lock().await;
258
259        let index = app.test_list_state.selected().unwrap();
260        let (test_index, exec_index) = (
261            index % app.test_list[0].len(),
262            index / app.test_list[0].len(),
263        );
264
265        app.current_ref = fs::read_to_string(format!(
266            "{}ref/{:02}-{}.ref",
267            app.test_path, app.test_list[exec_index][test_index].id, app.exec_name[exec_index]
268        ))
269        .await?;
270
271        app.diff =
272            TextDiff::from_lines(&app.current_ref, &app.test_list[exec_index][test_index].log)
273                .iter_all_changes()
274                .map(|item| {
275                    let sign = match item.tag() {
276                        ChangeTag::Delete => "-",
277                        ChangeTag::Insert => "+",
278                        ChangeTag::Equal => " ",
279                    };
280
281                    match item.missing_newline() {
282                        true => (sign, format!("{}", item)),
283                        false => (sign, format!("{}⏎", item)),
284                    }
285                })
286                .collect();
287
288        Ok(())
289    }
290
291    async fn save_data(&mut self, data: Data) -> Result<(), Option<Error>> {
292        debug!("Saving data");
293        tokio::fs::write(DB_PATH, serde_json::to_string_pretty(&data).unwrap()).await?;
294        Ok(())
295    }
296
297    async fn load_cs(&mut self) -> Result<(), Option<Error>> {
298        info!("Running checkstyle");
299
300        let mut app = self.app.lock().await;
301
302        let mut cs = Command::new(format!("{}/cs/cs.sh", app.test_path));
303        cs.arg(".");
304
305        let output = cs.output().await?.stdout;
306
307        app.checkstyle.clear();
308        app.checkstyle
309            .push_str(std::str::from_utf8(&output).unwrap());
310
311        let mut out_file = File::create(format!("{}checkstyle.txt", app.test_path)).await?;
312
313        out_file.write_all(&output).await?;
314
315        Ok(())
316    }
317
318    async fn run_make(&self) -> Result<(), Option<Error>> {
319        info!("Running makefile");
320        let mut make = Command::new("make");
321        let make_run = make.arg("build");
322        let res = make_run.output().await?;
323
324        if let Some(code) = res.status.code() {
325            if code != 0 {
326                return Err(Some(Error::new(
327                    ErrorKind::Other,
328                    std::str::from_utf8(&res.stderr).unwrap(),
329                )));
330            }
331            info!("\n{}", String::from_utf8(res.stdout).unwrap());
332        }
333
334        Ok(())
335    }
336
337    async fn run_all(&mut self, size: usize) -> Result<(), Option<Error>> {
338        let mut threads = Vec::new();
339
340        self.run_make().await?;
341
342        for index in 0..size {
343            let copy = Arc::clone(&self.app);
344
345            let thread = tokio::spawn(async move {
346                debug!("Waiting on mutex");
347                let mut app = copy.lock().await;
348
349                let (test_index, exec_index) = (
350                    index % app.test_list[0].len(),
351                    index / app.test_list[0].len(),
352                );
353
354                app.test_list[exec_index][test_index].status.clear();
355                app.test_list[exec_index][test_index]
356                    .status
357                    .push_str("STARTING");
358                app.dispatch(IoEvent::RunTest(test_index, exec_index)).await;
359            });
360
361            threads.push(thread);
362        }
363
364        for thread in threads {
365            thread.await.unwrap();
366        }
367
368        Ok(())
369    }
370
371    async fn run_failed(&self, indexes: Vec<(usize, usize)>) -> Result<(), Option<Error>> {
372        let mut threads = Vec::new();
373
374        self.run_make().await?;
375
376        for index in indexes {
377            let copy = Arc::clone(&self.app);
378
379            let thread = tokio::spawn(async move {
380                debug!("Waiting on mutex");
381                let mut app = copy.lock().await;
382
383                let (test_index, exec_index) = index;
384
385                app.test_list[exec_index][test_index].status.clear();
386                app.test_list[exec_index][test_index]
387                    .status
388                    .push_str("STARTING");
389                app.dispatch(IoEvent::RunTest(test_index, exec_index)).await;
390            });
391
392            threads.push(thread);
393        }
394
395        for thread in threads {
396            thread.await.unwrap();
397        }
398
399        Ok(())
400    }
401
402    /**
403     * Runs a single test, by opening or creating an output file,
404     * adding the input of the program to the stdin of the executable and
405     * by comparing the ref file with the program's output.
406     *
407     * Oh god, this is a mess but it is working
408     */
409    async fn run_test(&self, index: usize, exec: usize) -> Result<(), Option<Error>> {
410        let mut app = self.app.lock().await;
411
412        let app_name = String::from(&app.exec_name[exec]);
413
414        let mut out_file = File::create(format!(
415            "{}output/{:02}-{}.out",
416            app.test_path, index, app_name
417        ))
418        .await?;
419
420        let valgrind = app.valgrind_enabled;
421
422        let ref_prom = fs::read(format!(
423            "{}ref/{:02}-{}.ref",
424            app.test_path, index, app_name
425        ));
426
427        let ref_file = match ref_prom.await {
428            Ok(f1) => f1,
429            Err(a) => {
430                error!(
431                    "Cannot find {}",
432                    format!("{}input/{:02}-{}.ref", app.test_path, index, app_name)
433                );
434
435                let current_test = &mut app.test_list[exec][index];
436                current_test.status.clear();
437                current_test.status.push_str("ERROR");
438                return Err(Some(a));
439            }
440        };
441
442        let mut binding: Command;
443        if valgrind {
444            binding = Command::new("valgrind");
445            binding
446                .arg(format!(
447                    "--log-file={}output/{:02}-{}.valgrind",
448                    app.test_path, index, app_name
449                ))
450                .arg("--leak-check=full")
451                .arg("--track-origins=yes")
452                .arg("--show-leak-kinds=all")
453                .arg("--error-exitcode=69")
454                .arg(format!("./{}", app_name));
455        } else {
456            binding = Command::new(format!("./{}", app_name));
457        }
458
459        let current_test = &mut app.test_list[exec][index];
460        current_test.status.clear();
461        current_test.status.push_str("RUNNING");
462        let timelimit = current_test.timeout;
463
464        info!(
465            "Running test number {} with status {}",
466            index, current_test.status
467        );
468        let in_file = std::fs::File::open(format!(
469            "{}input/{:02}-{}.in",
470            app.test_path, index, app_name
471        ))?;
472        drop(app);
473
474        let run = binding.stdin(in_file).stdout(Stdio::piped());
475
476        debug!("Executing {:?}", run);
477        match run.spawn() {
478            Ok(mut child) => {
479                debug!("{:?}", child);
480
481                let mut log = String::new();
482                let mut res = String::new();
483                let start = Instant::now();
484
485                debug!("Finished input, waiting for stdout");
486
487                if let Some(ref mut stdout) = child.stdout {
488                    let mut lines = BufReader::new(stdout).lines();
489
490                    loop {
491                        if let Ok(res_line) =
492                            timeout(Duration::from_millis(timelimit), lines.next_line()).await
493                        {
494                            if let Ok(Some(line)) = res_line {
495                                let l: String = format!("{}\n", line);
496                                debug!("file_contains {}", l);
497                                log.push_str(&l);
498                            } else {
499                                debug!("Finished reading from stdout");
500                                break;
501                            }
502
503                            if start.elapsed().as_millis() > timelimit as u128 {
504                                warn!("timeout");
505                                res.push_str("TIMEOUT");
506
507                                let mut app = self.app.lock().await;
508                                let current_test = &mut app.test_list[exec][index];
509                                current_test.status.clear();
510                                current_test.status.push_str(&res);
511                                current_test.log.clear();
512
513                                if valgrind {
514                                    current_test.time_valgrind = start.elapsed().as_secs_f64();
515                                } else {
516                                    current_test.time_normal = start.elapsed().as_secs_f64();
517                                }
518
519                                child.kill().await?;
520
521                                return Ok(());
522                            }
523                        } else {
524                            warn!("timeout");
525                            res.push_str("TIMEOUT");
526
527                            let mut app = self.app.lock().await;
528                            let current_test = &mut app.test_list[exec][index];
529                            current_test.status.clear();
530                            current_test.status.push_str(&res);
531                            current_test.log.clear();
532
533                            if valgrind {
534                                current_test.time_valgrind = start.elapsed().as_secs_f64();
535                            } else {
536                                current_test.time_normal = start.elapsed().as_secs_f64();
537                            }
538
539                            child.kill().await?;
540
541                            return Ok(());
542                        }
543                    }
544                } else {
545                    warn!("I think it crashed");
546                }
547
548                debug!("time here is {}", start.elapsed().as_secs_f64());
549
550                if let Ok(out) = child.wait_with_output().await {
551                    debug!("exit status {:?}", out.status.code());
552                    if out.status.code().is_none() {
553                        let runtime = start.elapsed().as_secs_f64();
554
555                        log.push_str(&out.status.to_string().split_off(8));
556                        res.push_str("CRASHED");
557
558                        let mut app = self.app.lock().await;
559                        let current_test = &mut app.test_list[exec][index];
560
561                        current_test.status.clear();
562                        current_test.status.push_str(&res);
563                        current_test.log.clear();
564                        current_test.log.push_str(&log);
565
566                        if valgrind {
567                            current_test.time_valgrind = runtime;
568                        } else {
569                            current_test.time_normal = runtime;
570                        }
571
572                        app.unwritten_data = true;
573                        return Ok(());
574                    } else if let Some(69) = out.status.code() {
575                        let runtime = start.elapsed().as_secs_f64();
576
577                        log.push_str("Check output folder for valgrind errors");
578                        res.push_str("MEMLEAKS");
579
580                        let mut app = self.app.lock().await;
581                        let current_test = &mut app.test_list[exec][index];
582
583                        current_test.status.clear();
584                        current_test.status.push_str(&res);
585                        current_test.log.clear();
586                        current_test.log.push_str(&log);
587
588                        if valgrind {
589                            current_test.time_valgrind = runtime;
590                        } else {
591                            current_test.time_normal = runtime;
592                        }
593
594                        app.unwritten_data = true;
595                        return Ok(());
596                    }
597                } else {
598                    error!("Oops");
599                }
600
601                let correct: bool = if log == std::str::from_utf8(&ref_file).unwrap() {
602                    true
603                } else {
604                    res.push('0');
605                    false
606                };
607
608                let runtime = start.elapsed().as_secs_f64();
609                debug!("time={:5}", runtime);
610
611                out_file.write_all(log.as_bytes()).await?;
612
613                let mut app = self.app.lock().await;
614                let current_test = &mut app.test_list[exec][index];
615
616                if valgrind {
617                    current_test.time_valgrind = runtime;
618                } else {
619                    current_test.time_normal = runtime;
620                }
621
622                if correct {
623                    res.push_str(&current_test.test_score.to_string());
624                }
625
626                current_test.status.clear();
627                current_test.status.push_str(&res);
628                current_test.log.clear();
629                current_test.log.push_str(&log);
630
631                app.unwritten_data = true;
632            }
633            Err(error) => {
634                let mut app = self.app.lock().await;
635                let current_test = &mut app.test_list[exec][index];
636
637                current_test.status.clear();
638                current_test.status.push_str("ERROR");
639
640                current_test.log.clear();
641                current_test.log.push_str(&error.to_string());
642
643                app.unwritten_data = true;
644                warn!("{:?}", error);
645            }
646        }
647
648        Ok(())
649    }
650}