From 0c1ac0f027e0d4cc9e3b110dd5cc4797c50656c5 Mon Sep 17 00:00:00 2001 From: Antony David Date: Fri, 12 Jul 2024 16:48:04 +0200 Subject: [PATCH] test: improve code coverage --- .github/workflows/ci.yml | 14 +++++- .gitignore | 4 +- Cargo.lock | 2 + Cargo.toml | 1 + README.md | 1 + src/main.rs | 70 +++++++++++++++++++++--------- src/sudoku.rs | 94 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 162 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 877cc75..c8d3cbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,5 +35,15 @@ jobs: - name: Run Build run: cargo build - - name: Run tests - run: cargo test + - name: Install tarpaulin + run: cargo install cargo-tarpaulin + + - name: Generate code coverage + run: | + cargo tarpaulin --all-features --workspace --out xml + + - name: Upload to codecov.io + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + fail_ci_if_error: true diff --git a/.gitignore b/.gitignore index ccb5166..0a58df4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target -.vscode \ No newline at end of file +.vscode +corbetura.xml +coverage* diff --git a/Cargo.lock b/Cargo.lock index 3f8ea40..9c1febc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,6 +112,7 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208" dependencies = [ + "actix-macros", "futures-core", "tokio", ] @@ -1676,6 +1677,7 @@ name = "sudoku-rust" version = "0.1.0" dependencies = [ "actix-files", + "actix-rt", "actix-web", "codspeed-criterion-compat", "criterion 0.4.0", diff --git a/Cargo.toml b/Cargo.toml index eedc6e5..7aec73d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ tera = "1" actix-web = "4" actix-files = "0.6.2" lazy_static = "1.4.0" +actix-rt = "2.10.0" [dev-dependencies] codspeed-criterion-compat = "2.6.0" diff --git a/README.md b/README.md index 3cb5459..8d2ba16 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ ![ci](https://github.com/jayllyz/sudoku-rust/actions/workflows/ci.yml/badge.svg) [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/) +[![codecov](https://codecov.io/github/Jayllyz/sudoku-rust/graph/badge.svg?token=3BH60KHVOV)](https://codecov.io/github/Jayllyz/sudoku-rust) Personal project to learn Rust and improve my algorithm skills. Recently started learning Rust and wanted to build something to practice because i really like the language. diff --git a/src/main.rs b/src/main.rs index 12e5ea5..84b1007 100644 --- a/src/main.rs +++ b/src/main.rs @@ -109,26 +109,54 @@ async fn main() -> std::io::Result<()> { #[cfg(test)] mod tests { - use crate::sudoku::{generate_board, resolv_backtrack}; - - #[test] - fn board_valid() { - const BOARD_SIZE: usize = 9; - let board = generate_board(BOARD_SIZE, 1); - assert_eq!(board.len(), 9); - - let mut hm = std::collections::HashMap::new(); - for row in board.iter().take(BOARD_SIZE).enumerate() { - for value in row.1.iter().take(BOARD_SIZE) { - if hm.contains_key(value) { - panic!("Invalid board"); - } - if *value != 0 { - hm.insert(*value, true); - } - } - hm.clear(); - } - assert!(resolv_backtrack(&mut board.clone(), 0, 0)); + use super::*; + use actix_web::{test, web, App}; + + #[actix_rt::test] + async fn test_home() { + let tera = web::Data::new(TEMPLATES.clone()); + let app = test::init_service(App::new().app_data(tera.clone()).service(home)).await; + + let req = test::TestRequest::get().uri("/").to_request(); + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); + } + + #[actix_rt::test] + async fn test_update_table() { + let tera = web::Data::new(TEMPLATES.clone()); + let app_state = web::Data::new(Sudoku { + board: Mutex::new(vec![vec![0; BOARD_SIZE]; BOARD_SIZE]), + }); + let app = test::init_service( + App::new() + .app_data(tera.clone()) + .app_data(app_state.clone()) + .service(web::resource("/update/{difficulty}").route(web::post().to(update_table))), + ) + .await; + + let req = test::TestRequest::post().uri("/update/1").to_request(); + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); + } + + #[actix_rt::test] + async fn test_solve_table() { + let tera = web::Data::new(TEMPLATES.clone()); + let app_state = web::Data::new(Sudoku { + board: Mutex::new(sudoku::generate_board(BOARD_SIZE, 1)), + }); + let app = test::init_service( + App::new() + .app_data(tera.clone()) + .app_data(app_state.clone()) + .service(web::resource("/solve").route(web::post().to(solve_table))), + ) + .await; + + let req = test::TestRequest::post().uri("/solve").to_request(); + let resp = test::call_service(&app, req).await; + assert!(resp.status().is_success()); } } diff --git a/src/sudoku.rs b/src/sudoku.rs index ccd073d..f4b1694 100644 --- a/src/sudoku.rs +++ b/src/sudoku.rs @@ -111,3 +111,97 @@ pub fn resolv_backtrack(board: &mut [Vec], mut row: usize, mut col: usize false } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_board() { + let board = generate_board(9, 1); + assert_eq!(board.len(), 9); + assert_eq!(board[0].len(), 9); + } + + #[test] + fn test_fill_block() { + let mut board = vec![vec![0; 9]; 9]; + let mut rng = rand::thread_rng(); + fill_block(&mut board, 0, 0, &mut rng); + + let mut numbers = Vec::new(); + for i in 0..3 { + for j in 0..3 { + numbers.push(board[i][j]); + } + } + numbers.sort(); + assert_eq!(numbers, vec![1, 2, 3, 4, 5, 6, 7, 8, 9]); + } + + #[test] + fn test_remove_numbers() { + let mut board = vec![vec![1; 9]; 9]; + let mut rng = rand::thread_rng(); + remove_numbers(&mut board, 1, &mut rng); + + let zeros = board.iter().flatten().filter(|&&x| x == 0).count(); + assert!(zeros > 0); + } + + #[test] + fn test_is_num_valid() { + let board = vec![ + vec![5, 3, 0, 0, 7, 0, 0, 0, 0], + vec![6, 0, 0, 1, 9, 5, 0, 0, 0], + vec![0, 9, 8, 0, 0, 0, 0, 6, 0], + vec![8, 0, 0, 0, 6, 0, 0, 0, 3], + vec![4, 0, 0, 8, 0, 3, 0, 0, 1], + vec![7, 0, 0, 0, 2, 0, 0, 0, 6], + vec![0, 6, 0, 0, 0, 0, 2, 8, 0], + vec![0, 0, 0, 4, 1, 9, 0, 0, 5], + vec![0, 0, 0, 0, 8, 0, 0, 7, 9], + ]; + + assert!(is_num_valid(&board, 0, 2, 4)); + assert!(!is_num_valid(&board, 0, 2, 3)); + } + + #[test] + fn test_resolv_backtrack() { + let mut board = vec![ + vec![5, 3, 0, 0, 7, 0, 0, 0, 0], + vec![6, 0, 0, 1, 9, 5, 0, 0, 0], + vec![0, 9, 8, 0, 0, 0, 0, 6, 0], + vec![8, 0, 0, 0, 6, 0, 0, 0, 3], + vec![4, 0, 0, 8, 0, 3, 0, 0, 1], + vec![7, 0, 0, 0, 2, 0, 0, 0, 6], + vec![0, 6, 0, 0, 0, 0, 2, 8, 0], + vec![0, 0, 0, 4, 1, 9, 0, 0, 5], + vec![0, 0, 0, 0, 8, 0, 0, 7, 9], + ]; + + assert!(resolv_backtrack(&mut board, 0, 0)); + + for row in &board { + assert!(row.iter().all(|&x| x != 0)); + } + } + + #[test] + fn test_generate_board_different_difficulties() { + let easy_board = generate_board(9, 1); + let medium_board = generate_board(9, 2); + let hard_board = generate_board(9, 3); + + let count_zeros = + |board: &Vec>| board.iter().flatten().filter(|&&x| x == 0).count(); + + let easy_zeros = count_zeros(&easy_board); + let medium_zeros = count_zeros(&medium_board); + let hard_zeros = count_zeros(&hard_board); + + assert!(easy_zeros < medium_zeros); + assert!(medium_zeros < hard_zeros); + } +}