hw_checker 0.4.5

An interactive checker built for the homeworks
Documentation
// use std::io::{BufRead, BufReader, Write};
use std::process::{exit, Stdio};
use std::time::{Duration, Instant};
use tokio::fs::{self, File};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::Command;

use hw_checker::app::{App, Test};
use tokio::time::timeout;

const CHECKSTYLE_SCORE: isize = 10;

pub async fn run_tests(mut app: App) {
    let mut score: isize = 0;

    println!("Running makefile");
    let mut make = Command::new("make");
    let make_run = make.arg("build");
    let res = match make_run.output().await {
        Ok(out) => out,
        Err(err) => {
            println!("Error {:?}", err);
            exit(1);
        }
    };

    if let Some(code) = res.status.code() {
        if code != 0 {
            println!("Makefile error or makefile not found, stopping");
            println!("{}", String::from_utf8(res.stdout).unwrap());
            println!("{}", String::from_utf8(res.stderr).unwrap());
            return;
        }
        println!(
            "{}",
            match String::from_utf8(res.stdout) {
                Ok(stdout) => stdout,
                Err(err) => {
                    println!("Error {:?}", err);
                    exit(1);
                }
            }
        );
    }

    for (i, test_list) in app.test_list.iter().enumerate() {
        let app_name = &app.exec_name[i];

        println!();
        println!("==== {app_name} ====");

        for (index, test) in test_list.iter().enumerate() {
            match run_test(test, index, app_name, &app.test_path).await {
                Ok(amount) => score += amount as isize,
                Err(err) => println!("Error {:?}", err),
            };
        }
    }

    println!();
    println!("Running checkstyle");

    let mut cs = Command::new(format!("{}/cs/cs.sh", app.test_path));
    cs.arg(".");

    let output = match cs.output().await {
        Ok(res) => res.stdout,
        Err(err) => {
            println!("Error {:?}", err);
            exit(1);
        }
    };

    app.checkstyle.clear();
    app.checkstyle.push_str(match std::str::from_utf8(&output) {
        Ok(res) => res,
        Err(err) => {
            println!("Error {:?}", err);
            exit(1);
        }
    });

    let mut out_file = match File::create(format!("{}checkstyle.txt", app.test_path)).await {
        Ok(res) => res,
        Err(err) => {
            println!("Error {:?}", err);
            exit(1);
        }
    };

    match out_file.write_all(&output).await {
        Ok(_) => {}
        Err(err) => {
            println!("Error {:?}", err);
            exit(1);
        }
    };

    if app.checkstyle.is_empty() {
        println!("No coding style errors found");
    } else {
        println!(
            "Check {}chekstyle.txt for all the {} errors\n",
            app.test_path,
            app.checkstyle.lines().count()
        );

        let mut errors = vec![0, 0, 0];

        app.checkstyle.lines().for_each(|line| {
            match line {
                _ if line.contains("CHECK") => errors[0] += 1,
                _ if line.contains("WARNING") => errors[1] += 1,
                _ if line.contains("ERROR") => errors[2] += 1,
                _ => (),
            };
        });

        if errors[0] >= 10 {
            println!("Found {} checks, reduce them to 0", errors[0]);
            score -= CHECKSTYLE_SCORE / 2;
        }

        if errors[1] >= 5 {
            println!("Found {} warnings, reduce them to 0", errors[1]);
            score -= CHECKSTYLE_SCORE / 2;
        }

        if errors[2] > 0 {
            println!("Found {} errors, reduce them to 0", errors[2]);
            score -= CHECKSTYLE_SCORE;
        }
    }

    println!("Running make clean");
    let mut make = Command::new("make");
    make.arg("clean");
    let mut child = match make.spawn() {
        Ok(chld) => chld,
        Err(err) => {
            println!("Error {:?}", err);
            exit(1);
        }
    };

    match child.wait().await {
        Ok(_) => {}
        Err(err) => {
            println!("Error {:?}", err);
        }
    };

    println!("Total: {score}\n");
}

async fn run_test(
    test: &Test,
    index: usize,
    app_name: &String,
    path: &String,
) -> Result<usize, std::io::Error> {
    let mut run: Command;
    if index < 13 {
        run = Command::new("valgrind");
        run.arg(format!(
            "--log-file={}output/{:02}-{}.valgrind",
            path, index, app_name
        ))
        .arg("--leak-check=full")
        .arg("--track-origins=yes")
        .arg("--show-leak-kinds=all")
        .arg("--error-exitcode=69")
        .arg(format!("./{}", app_name));
    } else {
        run = Command::new(format!("./{}", app_name));
    }

    print!("Running {app_name} test {index}");

    let mut out_file =
        File::create(format!("{}output/{:02}-{}.out", path, index, app_name)).await?;

    let ref_file = fs::read(format!("{}ref/{:02}-{}.ref", path, index, app_name)).await?;

    let in_file = std::fs::File::open(format!("{}input/{:02}-{}.in", path, index, app_name))?;

    run.stdin(in_file).stdout(Stdio::piped());
    if let Ok(mut child) = run.spawn() {
        let mut log_file = String::new();
        let start = Instant::now();

        if let Some(ref mut stdout) = child.stdout {
            let mut lines = BufReader::new(stdout).lines();

            loop {
                let time_left = test.timeout as u128 - start.elapsed().as_millis();
                if let Ok(res) =
                    timeout(Duration::from_millis(time_left as u64), lines.next_line()).await
                {
                    if let Ok(Some(line)) = res {
                        let l: String = format!("{}\n", line);
                        log_file.push_str(&l);
                    } else {
                        break;
                    }

                    if start.elapsed().as_millis() > test.timeout as u128 {
                        println!("\t\tTime: {:.5}", start.elapsed().as_secs_f64());
                        println!(
                            "Test {index:02}{}TIMEOUT: 0/{}",
                            ".".repeat(26),
                            test.test_score
                        );

                        if let Err(err) = child.kill().await {
                            println!("ERROR: Can't kill child: {:?}", err);
                        }

                        return Ok(0);
                    }
                } else {
                    println!("\t\tTime: {:.5}", start.elapsed().as_secs_f64());
                    println!(
                        "Test {index:02}{}TIMEOUT: 0/{}",
                        ".".repeat(26),
                        test.test_score
                    );

                    if let Err(err) = child.kill().await {
                        println!("ERROR: Can't kill child: {:?}", err);
                    }

                    return Ok(0);
                }
            }
        } else {
            eprintln!("Broken pipe");
        }

        if let Ok(out) = child.wait_with_output().await {
            if out.status.code().is_none() {
                println!("\t\tTime: {:.5}", start.elapsed().as_secs_f64());
                println!(
                    "Test {index:02}{}CRASHED: 0/{}",
                    ".".repeat(26),
                    test.test_score
                );

                log_file.push_str("Crashed\n");
                out_file.write_all(log_file.as_bytes()).await?;

                return Ok(0);
            } else if let Some(69) = out.status.code() {
                println!("\t\tTime: {:.5}", start.elapsed().as_secs_f64());
                println!(
                    "Test {index:02}{}MEMLEAKS: 0/{}",
                    ".".repeat(25),
                    test.test_score
                );
                
                log_file.push_str("MEMLEAKS: Check .valgrind file for memory leak info\n");
                out_file.write_all(log_file.as_bytes()).await?;

                return Ok(0);
            }
        } else {
            eprintln!("Cannot wait for child");
        }

        println!("\t\tTime: {:.5}", start.elapsed().as_secs_f64());

        out_file.write_all(log_file.as_bytes()).await?;
        if log_file
            == match std::str::from_utf8(&ref_file) {
                Ok(res) => res,
                Err(err) => {
                    println!("Error {:?}", err);
                    exit(1);
                }
            }
        {
            println!(
                "Test {index:02}{}PASSED: {}/{}",
                ".".repeat(27),
                test.test_score,
                test.test_score
            );

            return Ok(test.test_score);
        } else {
            println!(
                "Test {index:02}{}FAILED: 0/{}",
                ".".repeat(27),
                test.test_score
            );
        }
    }

    Ok(0)
}