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.