Using mutations to drive better testing

I'm a big fan of mutation testing. The idea that a tool tries to find weak spots in your work is very appealing. I wrote more about mutation testing in general here. This text will be more about my approach towards testing Qpackt.

At the very beginning Qpackt had almost no unit tests. I checked only tiny things like password matching and other bits. The remaining stuff was tested manually. It just had so few features, that it didn't do anything else - I have to manually test it just by using it. Obviously, this is not scalable long term. If I'm to take a project seriously, I also have to take its tests seriously. So I resorted to mutation testing to drive me towards testing error-prone code.

At fd32465cd7a6f493b3587318051fd6b16dd20e0b `cargo mutants` found the following mutations not killed:

    
MISSED   qpackt-backend/src/dao/version.rs:77:9: replace Dao::delete_version -> crate::error::Result<String> with Ok(String::new()) in 3.7s build + 3.0s test
MISSED   qpackt-backend/src/config.rs:134:5: replace read_stdin -> Result<String> with Ok("xyzzy".into()) in 3.8s build + 3.0s test
MISSED   qpackt-backend/src/config.rs:59:9: replace QpacktConfig::save -> Result<()> with Ok(()) in 3.6s build + 3.0s test
MISSED   qpackt-backend/src/analytics/hash.rs:37:9: replace <impl From for i64>::from -> Self with Default::default() in 3.5s build + 3.0s test
MISSED   qpackt-backend/src/proxy/handler.rs:96:5: replace previous_url -> Option<(Arc<Url>, VersionName)> with None in 3.5s build + 3.0s test
MISSED   qpackt-backend/src/panel/auth/token.rs:82:5: replace create_token -> String with "xyzzy".into() in 3.7s build + 3.1s test
MISSED   qpackt-backend/src/panel/versions/delete.rs:39:5: replace delete_version -> Result<String> with Ok(String::new()) in 3.5s build + 3.0s test
MISSED   qpackt-backend/src/main.rs:109:5: replace start_http with () in 3.6s build + 3.0s test
MISSED   qpackt-backend/src/config.rs:51:9: replace QpacktConfig::create with () in 3.5s build + 3.0s test
MISSED   qpackt-backend/src/analytics/writer.rs:66:29: replace >= with < in request_receiver in 3.6s build + 3.0s test
MISSED   qpackt-backend/src/dao/version.rs:61:9: replace Dao::register_version -> crate::error::Result<()> with Ok(()) in 3.6s build + 3.0s test
MISSED   qpackt-backend/src/dao/state.rs:79:5: replace deserialize_row -> Result<Option<T>> with Ok(None) in 3.6s build + 3.0s test
MISSED   qpackt-backend/src/proxy/handler.rs:65:5: replace proxy_to_new -> HttpResponse with HttpResponse::Ok().finish() in 3.7s build + 3.0s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:142:5: replace unzip_site -> Result<PathBuf> with Ok(Default::default()) in 4.4s build + 3.0s test
MISSED   qpackt-backend/src/dao/requests.rs:58:9: replace Dao::get_daily_seed -> Result<Option<DailySeed>> with Ok(None) in 4.3s build + 3.1s test
MISSED   qpackt-backend/src/panel/auth/token.rs:74:26: replace && with || in is_token_valid in 3.5s build + 3.1s test
MISSED   qpackt-backend/src/dao/state.rs:91:9: replace <impl State for DailySeed>::name -> &'static str with "xyzzy" in 3.5s build + 2.9s test
MISSED   qpackt-backend/src/proxy/handler.rs:88:5: replace proxy_to_previous -> HttpResponse with HttpResponse::Ok().finish() in 3.5s build + 2.3s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:89:5: replace create_path -> Result<PathBuf> with Ok(Default::default()) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/proxy/mod.rs:61:5: replace serve_challenge -> HttpResponse with HttpResponse::Ok().finish() in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/ssl/challenge.rs:46:9: replace AcmeChallenge::set_challenge with () in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/dao/version.rs:77:9: replace Dao::delete_version -> crate::error::Result<String> with Ok("xyzzy".into()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/main.rs:140:5: replace create_app_dir -> Result<()> with Ok(()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/server.rs:101:9: replace Versions::add_version with () in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/ssl/mod.rs:40:42: replace > with == in get_certificate in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/config.rs:125:9: replace QpacktConfig::password -> &str with "" in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/server.rs:72:9: replace Versions::update_strategies with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/server.rs:110:9: replace Versions::get_url_for_cookie -> Option<(Arc<Url>, VersionName)> with None in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:124:5: replace find_web_root -> Result<PathBuf> with Ok(Default::default()) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/dao/version.rs:47:9: replace VersionName::matches -> bool with true in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/server.rs:135:5: replace build_version_servers -> Vec<VersionServer> with vec![] in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/analytics/writer.rs:92:5: replace merge_requests -> Vec<Visit> with vec![] in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/error.rs:66:9: replace <impl ResponseError for QpacktError>::status_code -> StatusCode with Default::default() in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/ssl/challenge.rs:40:9: replace AcmeChallenge::get_proof -> Option<String> with Some(String::new()) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/panel/auth/token.rs:71:5: replace is_token_valid -> bool with false in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/proxy/handler.rs:104:94: replace != with == in build_response in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/auth/token.rs:82:5: replace create_token -> String with String::new() in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/versions/delete.rs:39:5: replace delete_version -> Result<String> with Ok("xyzzy".into()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/config.rs:119:9: replace QpacktConfig::domain -> &str with "xyzzy" in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/config.rs:130:5: replace from_yaml -> Result<Option<String>> with Ok(Some(String::new())) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/config.rs:134:5: replace read_stdin -> Result<String> with Ok(String::new()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/auth/token.rs:71:5: replace is_token_valid -> bool with true in 2.6s build + 2.2s test
MISSED   qpackt-backend/src/config.rs:98:9: replace QpacktConfig::http_proxy_addr -> &str with "xyzzy" in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/config.rs:98:9: replace QpacktConfig::http_proxy_addr -> &str with "" in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:130:26: replace > with < in find_web_root in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/ssl/challenge.rs:62:5: replace spawn_receiver_task with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/proxy/mod.rs:35:5: replace start_proxy_http with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/dao/state.rs:48:9: replace Dao::set_state -> Result<()> with Ok(()) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/https_redirect.rs:66:95: replace == with != in <impl Service for CheckHttpsRedirectMiddleware<S>>::call in 2.8s build + 2.2s test
MISSED   qpackt-backend/src/server.rs:59:24: replace <= with > in Versions::pick_upstream in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/ssl/challenge.rs:40:9: replace AcmeChallenge::get_proof -> Option<String> with None in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/auth/token.rs:74:21: replace != with == in is_token_valid in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/dao/version.rs:47:24: replace == with != in VersionName::matches in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/dao/state.rs:36:9: replace Dao::get_state -> Result<Option<T>> with Ok(None) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/dao/requests.rs:62:9: replace Dao::save_daily_seed -> Result<()> with Ok(()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/auth/token.rs:74:37: replace == with != in is_token_valid in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/server.rs:87:32: replace == with != in Versions::delete_version in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/dao/state.rs:91:9: replace <impl State for DailySeed>::name -> &'static str with "" in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/config.rs:144:5: replace if_empty_then -> String with "xyzzy".into() in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/dao/version.rs:47:9: replace VersionName::matches -> bool with false in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/proxy/handler.rs:53:5: replace proxy_handler -> HttpResponse with HttpResponse::Ok().finish() in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/analytics/mod.rs:80:19: replace < with > in convert_to_response in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/server.rs:117:5: replace start_version_servers with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/dao/visits.rs:65:9: replace Dao::get_visits -> Result<Vec<Visit>> with Ok(vec![]) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/config.rs:130:5: replace from_yaml -> Result<Option<String>> with Ok(None) in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/analytics/hash.rs:83:47: replace > with < in spawn_refresh_loop in 3.0s build + 2.4s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:102:5: replace wait_for_content -> Result<PathBuf> with Ok(Default::default()) in 2.8s build + 2.2s test
MISSED   qpackt-backend/src/panel/analytics/mod.rs:80:19: replace < with == in convert_to_response in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/dao/state.rs:67:9: replace Dao::read_row -> Result<Option<SqliteRow>> with Ok(None) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/panel/mod.rs:49:5: replace start_panel_http with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/analytics/hash.rs:83:47: replace > with == in spawn_refresh_loop in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/ssl/challenge.rs:40:9: replace AcmeChallenge::get_proof -> Option<String> with Some("xyzzy".into()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/analytics/hash.rs:56:5: replace init -> Result<()> with Ok(()) in 2.8s build + 2.2s test
MISSED   qpackt-backend/src/analytics/writer.rs:60:5: replace request_receiver with () in 2.8s build + 2.2s test
MISSED   qpackt-backend/src/dao/version.rs:41:9: replace <impl Display for VersionName>::fmt -> std::fmt::Result with Ok(Default::default()) in 2.8s build + 2.2s test
MISSED   qpackt-backend/src/panel/versions/update.rs:59:37: replace == with != in update_versions in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/main.rs:72:5: replace main with () in 3.3s build + 2.1s test
MISSED   qpackt-backend/src/https_redirect.rs:66:57: replace && with || in <impl Service for CheckHttpsRedirectMiddleware<S>>::call in 3.4s build + 2.2s test
MISSED   qpackt-backend/src/config.rs:119:9: replace QpacktConfig::domain -> &str with "" in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/proxy/mod.rs:52:5: replace start_proxy_https with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/dao/requests.rs:66:9: replace Dao::save_requests -> Result<()> with Ok(()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/ssl/resolver.rs:49:9: replace <impl ResolvesServerCert for CertResolver>::resolve -> Option<Arc<CertifiedKey>> with None in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/analytics/writer.rs:79:5: replace save_requests with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/ssl/challenge.rs:51:9: replace AcmeChallenge::clear with () in 2.7s build + 2.1s test
MISSED   qpackt-backend/src/panel/mod.rs:79:5: replace validate_permission -> Result<HttpResponse> with Ok(HttpResponse::Ok().finish()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/analytics/writer.rs:51:9: replace RequestWriter::save with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/config.rs:122:9: replace QpacktConfig::https_proxy_addr -> Option<&String> with None in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/main.rs:130:5: replace ensure_app_dir_exists -> Result<()> with Ok(()) in 2.6s build + 2.1s test
MISSED   qpackt-backend/src/server.rs:75:33: replace == with != in Versions::update_strategies in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/proxy/handler.rs:101:5: replace build_response -> HttpResponse with HttpResponse::Ok().finish() in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/server.rs:124:5: replace start_version_server with () in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/dao/visits.rs:44:9: replace Dao::update_visits -> Result<()> with Ok(()) in 2.7s build + 2.2s test
MISSED   qpackt-backend/src/server.rs:85:9: replace Versions::delete_version with () in 3.2s build + 2.5s test
169 mutants tested in 13m 2s: 93 missed, 15 caught, 61 unviable
    
    

Only 15 mutations cought out of 108, so the coverage is below 14%. That's not good. Instead of writing tests checking single functions, I would much rather start testing features, like getting an admin token (with http client). This is quite a deep test. Getting a token requires the following things to work:

Besides, it's more of "functional" test instead of "method" test. It allows to refactor implementation without changing the test's code. And this is only the beginning. Once I have this test, I can reuse parts of it to build more tests, like uploading a page version and all other things that require a token.

Now, I could just spawn a process with 'Command::new("cargo").spawn()' (with some extra args) but it doesn't work for mutation testing. Mutation testing tools rarely (if ever) write down mutations to disk, so cargo will run unmutated version of code.

So, I came up with this code:

    
use crate::panel::auth::token::TokenResponse;
    use crate::run_app;
    use serde_json::json;
    use std::env;
    use std::path::{Path, PathBuf};
    use std::time::Duration;
    use tokio::fs;
    use tokio::task::JoinHandle;
    use tokio::time::sleep;

    /// Builds a config
    /// Starts backend
    /// Gets admin token
    #[tokio::test]
    async fn test_get_token() {
        let dir = tmpdir::TmpDir::new("qpackt_dir").await.expect("Unable to create temp dir");
        let config = write_config_file(&dir.to_path_buf()).await;
        env::set_var("QPACKT_HTML_DIR", ".");
        let task = run_app(&config).await;
        let _process = ProcessHandler { task };
        let token = get_token().await;
        assert!(!token.is_empty());
    }

    async fn write_config_file(dir: &Path) -> PathBuf {
        // password is "admin"
        let content =  format!("domain: qpackt.com\nhttp_proxy: 0.0.0.0:8080\npassword: $scrypt$ln=17,r=8,p=1$H63UY378M+ql3bpQMQ37aQ$XXt3kOaWrW/CQr+/lPIDtPlPTLJSHbaaGBEVo3l3wFY\nrun_directory: {}", dir.to_str().unwrap());
        let config = dir.join("qpackt.yaml");
        fs::write(&config, content).await.expect("Unable to write config");
        config
    }

    async fn get_token() -> String {
        for _ in 0..5 {
            let client = reqwest::Client::new();
            let request = client.post("http://localhost:9080/token").json(&json!({"password":"admin"}));
            if let Ok(response) = request.send().await {
                if let Ok(token) = response.json::<TokenResponse>().await {
                    return token.token;
                }
            }
            sleep(Duration::from_secs(1)).await;
        }
        panic!("No token after timeout!");
    }

    /// Convenience struct to ensure task aborted when dropped.
    struct ProcessHandler {
        task: JoinHandle<()>,
    }

    impl Drop for ProcessHandler {
        fn drop(&mut self) {
            self.task.abort();
        }
    }
    
    

It runs the main server, tries to grab a token and tests whether token isn't empty. Let see how many mutations it killed:

    
MISSED   qpackt-backend/src/analytics/hash.rs:37:9: replace <impl From for i64>::from -> Self with Default::default() in 7.6s build + 11.1s test
MISSED   qpackt-backend/src/tests/mod.rs:38:9: replace token_tests::test_get_token with () in 3.3s build + 2.3s test
MISSED   qpackt-backend/src/analytics/writer.rs:60:5: replace request_receiver with () in 11.1s build + 10.9s test
MISSED   qpackt-backend/src/ssl/challenge.rs:46:9: replace AcmeChallenge::set_challenge with () in 9.5s build + 10.8s test
MISSED   qpackt-backend/src/ssl/challenge.rs:51:9: replace AcmeChallenge::clear with () in 8.8s build + 10.7s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:102:5: replace wait_for_content -> Result<PathBuf> with Ok(Default::default()) in 9.5s build + 10.2s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:124:5: replace find_web_root -> Result<PathBuf> with Ok(Default::default()) in 8.6s build + 10.5s test
MISSED   qpackt-backend/src/config.rs:134:5: replace read_stdin -> Result<String> with Ok(String::new()) in 8.2s build + 10.9s test
MISSED   qpackt-backend/src/main.rs:152:5: replace create_app_dir -> Result<()> with Ok(()) in 9.4s build + 10.6s test
MISSED   qpackt-backend/src/analytics/writer.rs:79:5: replace save_requests with () in 9.4s build + 10.5s test
MISSED   qpackt-backend/src/server.rs:75:33: replace == with != in Versions::update_strategies in 8.5s build + 11.1s test
MISSED   qpackt-backend/src/analytics/writer.rs:66:29: replace >= with < in request_receiver in 8.9s build + 11.0s test
MISSED   qpackt-backend/src/proxy/handler.rs:88:5: replace proxy_to_previous -> HttpResponse with HttpResponse::Ok().finish() in 8.4s build + 10.8s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:89:5: replace create_path -> Result<PathBuf> with Ok(Default::default()) in 8.6s build + 10.1s test
MISSED   qpackt-backend/src/panel/auth/token.rs:74:37: replace == with != in is_token_valid in 8.6s build + 11.1s test
MISSED   qpackt-backend/src/server.rs:124:5: replace start_version_server with () in 9.7s build + 11.0s test
MISSED   qpackt-backend/src/dao/version.rs:47:24: replace == with != in VersionName::matches in 7.6s build + 11.0s test
MISSED   qpackt-backend/src/dao/state.rs:67:9: replace Dao::read_row -> Result<Option<SqliteRow>> with Ok(None) in 11.5s build + 10.8s test
MISSED   qpackt-backend/src/server.rs:72:9: replace Versions::update_strategies with () in 9.2s build + 10.7s test
MISSED   qpackt-backend/src/proxy/handler.rs:53:5: replace proxy_handler -> HttpResponse with HttpResponse::Ok().finish() in 8.9s build + 10.6s test
MISSED   qpackt-backend/src/dao/version.rs:47:9: replace VersionName::matches -> bool with false in 9.1s build + 10.7s test
MISSED   qpackt-backend/src/panel/versions/delete.rs:39:5: replace delete_version -> Result<String> with Ok(String::new()) in 8.7s build + 10.6s test
MISSED   qpackt-backend/src/dao/visits.rs:44:9: replace Dao::update_visits -> Result<()> with Ok(()) in 9.6s build + 10.7s test
MISSED   qpackt-backend/src/panel/versions/upload.rs:130:26: replace > with == in find_web_root in 9.3s build + 10.6s test
MISSED   qpackt-backend/src/panel/analytics/mod.rs:80:19: replace < with > in convert_to_response in 8.5s build + 10.8s test
MISSED   qpackt-backend/src/analytics/hash.rs:83:47: replace > with < in spawn_refresh_loop in 9.0s build + 11.1s test
MISSED   qpackt-backend/src/proxy/mod.rs:52:5: replace start_proxy_https with () in 9.6s build + 10.6s test
MISSED   qpackt-backend/src/ssl/challenge.rs:40:9: replace AcmeChallenge::get_proof -> Option<String> with None in 9.9s build + 10.8s test
MISSED   qpackt-backend/src/analytics/writer.rs:92:5: replace merge_requests -> Vec<Visit> with vec![] in 8.5s build + 10.5s test
MISSED   qpackt-backend/src/tests/mod.rs:55:9: replace token_tests::get_token -> String with "xyzzy".into() in 10.7s build + 10.5s test
MISSED   qpackt-backend/src/ssl/mod.rs:40:42: replace > with < in get_certificate in 9.7s build + 11.0s test
MISSED   qpackt-backend/src/proxy/handler.rs:65:5: replace proxy_to_new -> HttpResponse with HttpResponse::Ok().finish() in 9.5s build + 10.9s test
MISSED   qpackt-backend/src/config.rs:144:5: replace if_empty_then -> String with String::new() in 9.0s build + 11.2s test
MISSED   qpackt-backend/src/dao/requests.rs:66:9: replace Dao::save_requests -> Result<()> with Ok(()) in 9.2s build + 10.7s test
MISSED   qpackt-backend/src/panel/auth/token.rs:71:5: replace is_token_valid -> bool with false in 8.9s build + 10.5s test
MISSED   qpackt-backend/src/proxy/mod.rs:35:5: replace start_proxy_http with () in 9.7s build + 10.6s test
MISSED   qpackt-backend/src/proxy/handler.rs:104:94: replace != with == in build_response in 9.9s build + 10.8s test
MISSED   qpackt-backend/src/error.rs:66:9: replace <impl ResponseError for QpacktError>::status_code -> StatusCode with Default::default() in 8.1s build + 10.8s test
MISSED   qpackt-backend/src/dao/version.rs:117:9: replace Dao::save_versions -> crate::error::Result<()> with Ok(()) in 8.5s build + 10.6s test
MISSED   qpackt-backend/src/main.rs:142:5: replace ensure_app_dir_exists -> Result<()> with Ok(()) in 8.6s build + 10.7s test
MISSED   qpackt-backend/src/dao/requests.rs:62:9: replace Dao::save_daily_seed -> Result<()> with Ok(()) in 9.4s build + 10.9s test
MISSED   qpackt-backend/src/dao/version.rs:90:9: replace Dao::list_versions -> crate::error::Result<Vec<Version>> with Ok(vec![]) in 9.7s build + 10.3s test
MISSED   qpackt-backend/src/dao/version.rs:61:9: replace Dao::register_version -> crate::error::Result<()> with Ok(()) in 8.5s build + 10.2s test
MISSED   qpackt-backend/src/panel/analytics/mod.rs:80:19: replace < with == in convert_to_response in 8.6s build + 11.1s test
MISSED   qpackt-backend/src/analytics/writer.rs:51:9: replace RequestWriter::save with () in 9.4s build + 10.9s test
MISSED   qpackt-backend/src/https_redirect.rs:66:95: replace == with != in <impl Service for CheckHttpsRedirectMiddleware<S>>::call in 9.5s build + 10.9s test
MISSED   qpackt-backend/src/dao/version.rs:47:9: replace VersionName::matches -> bool with true in 8.5s build + 10.8s test
MISSED   qpackt-backend/src/ssl/challenge.rs:40:9: replace AcmeChallenge::get_proof -> Option<String> with Some("xyzzy".into()) in 9.5s build + 10.6s test
MISSED   qpackt-backend/src/panel/versions/update.rs:59:37: replace == with != in update_versions in 9.6s build + 10.9s test
MISSED   qpackt-backend/src/server.rs:101:9: replace Versions::add_version with () in 9.6s build + 11.0s test
MISSED   qpackt-backend/src/tests/mod.rs:75:13: replace token_tests::<impl Drop for ProcessHandler>::drop with () in 9.5s build + 10.8s test
MISSED   qpackt-backend/src/analytics/hash.rs:80:5: replace spawn_refresh_loop with () in 9.2s build + 10.9s test
MISSED   qpackt-backend/src/config.rs:122:9: replace QpacktConfig::https_proxy_addr -> Option<&String> with None in 8.6s build + 10.9s test
MISSED   qpackt-backend/src/config.rs:144:5: replace if_empty_then -> String with "xyzzy".into() in 7.6s build + 11.1s test
MISSED   qpackt-backend/src/analytics/hash.rs:83:47: replace > with == in spawn_refresh_loop in 9.3s build + 10.8s test
MISSED   qpackt-backend/src/dao/version.rs:77:9: replace Dao::delete_version -> crate::error::Result<String> with Ok("xyzzy".into()) in 9.5s build + 10.7s test
MISSED   qpackt-backend/src/config.rs:51:9: replace QpacktConfig::create with () in 8.6s build + 11.0s test
MISSED   qpackt-backend/src/ssl/challenge.rs:40:9: replace AcmeChallenge::get_proof -> Option<String> with Some(String::new()) in 9.5s build + 10.9s test
MISSED   qpackt-backend/src/server.rs:135:5: replace build_version_servers -> Vec<VersionServer> with vec![] in 9.6s build + 10.6s test
MISSED   qpackt-backend/src/proxy/handler.rs:101:5: replace build_response -> HttpResponse with HttpResponse::Ok().finish() in 8.8s build + 10.8s test
MISSED   qpackt-backend/src/panel/auth/token.rs:82:5: replace create_token -> String with "xyzzy".into() in 8.8s build + 11.0s test
MISSED   qpackt-backend/src/main.rs:75:5: replace main with () in 11.5s build + 11.0s test
MISSED   qpackt-backend/src/panel/mod.rs:79:5: replace validate_permission -> Result<HttpResponse> with Ok(HttpResponse::Ok().finish()) in 11.4s build + 10.8s test
MISSED   qpackt-backend/src/ssl/mod.rs:40:42: replace > with == in get_certificate in 9.3s build + 10.9s test
MISSED   qpackt-backend/src/dao/state.rs:91:9: replace <impl State for DailySeed>::name -> &'static str with "" in 8.8s build + 11.1s test
MISSED   qpackt-backend/src/config.rs:119:9: replace QpacktConfig::domain -> &str with "xyzzy" in 7.6s build + 10.9s test
MISSED   qpackt-backend/src/dao/state.rs:48:9: replace Dao::set_state -> Result<()> with Ok(()) in 9.4s build + 10.7s test
MISSED   qpackt-backend/src/dao/state.rs:91:9: replace <impl State for DailySeed>::name -> &'static str with "xyzzy" in 7.6s build + 11.3s test
MISSED   qpackt-backend/src/main.rs:82:5: replace run_with_args with () in 8.9s build + 11.0s test
MISSED   qpackt-backend/src/server.rs:59:24: replace <= with > in Versions::pick_upstream in 8.6s build + 10.9s test
MISSED   qpackt-backend/src/panel/versions/delete.rs:39:5: replace delete_version -> Result<String> with Ok("xyzzy".into()) in 8.9s build + 10.5s test
MISSED   qpackt-backend/src/server.rs:85:9: replace Versions::delete_version with () in 9.5s build + 10.8s test
MISSED   qpackt-backend/src/panel/auth/token.rs:71:5: replace is_token_valid -> bool with true in 9.4s build + 10.9s test
MISSED   qpackt-backend/src/proxy/handler.rs:96:5: replace previous_url -> Option<(Arc<Url>, VersionName)> with None in 8.7s build + 10.7s test
MISSED   qpackt-backend/src/dao/state.rs:79:5: replace deserialize_row -> Result<Option<T>> with Ok(None) in 8.9s build + 10.9s test
179 mutants tested in 41m 11s: 75 missed, 39 caught, 65 unviable
    
    

39 mutations were caught (out of total 114). That means about 34% coverage. Not bad for just one more test. You can read more about mutation testing here.

Back to blog