Repository navigation
Mutation Testing Results :: Dead Code + Missing Tests #1396
Copy link
Copy link
Open
Description
results of running mutation testing on the rust codebase. I ran it over the entire codebase on my Ryzen 7 5800X3D ~3h (09:56 → 13:07). I left it running through the day as I was at work and these are the results.
files
programmatic breakdowns
issues by file
[reap@jager:~/Desktop/SQLPage]$ awk -F: 'FNR==NR{m[$1]++;next}{c[$1]++}
END{for(f in m) printf "%d\t%d\t%d\t%.0f%%\t%s\n", m[f], c[f], m[f]+c[f], 100*m[f]/(m[f]+c[f]), f}' \
mutants.out/{missed,caught}.txt | sort -k1,1rn -k3,3rn| File | Missed | Caught | Viable | Survival |
|---|---|---|---|---|
telemetry.rs |
52 | 13 | 65 | 80% |
template_helpers.rs |
49 | 26 | 75 | 65% |
render.rs |
29 | 48 | 77 | 38% |
webserver/database/sql/rewrite.rs |
21 | 92 | 113 | 19% |
webserver/database/sql_to_json.rs |
21 | 23 | 44 | 48% |
app_config.rs |
18 | 73 | 91 | 20% |
webserver/oidc.rs |
18 | 54 | 72 | 25% |
filesystem.rs |
18 | 31 | 49 | 37% |
webserver/http.rs |
18 | 26 | 44 | 41% |
webserver/lambda_http.rs |
15 | 3 | 18 | 83% |
webserver/database/mod.rs |
12 | 7 | 19 | 63% |
webserver/database/execute_queries.rs |
10 | 34 | 44 | 23% |
webserver/database/csv_import.rs |
9 | 25 | 34 | 26% |
webserver/database/connect.rs |
9 | 4 | 13 | 69% |
webserver/database/sqlpage_functions/function_traits.rs |
7 | 45 | 52 | 13% |
webserver/database/error_highlighting.rs |
7 | 22 | 29 | 24% |
cli/commands.rs |
6 | 0 | 6 | 100% |
webserver/http_client.rs |
5 | 4 | 9 | 56% |
webserver/database/migrations.rs |
5 | 2 | 7 | 71% |
webserver/server_timing.rs |
4 | 12 | 16 | 25% |
templates.rs |
4 | 7 | 11 | 36% |
webserver/content_security_policy.rs |
4 | 3 | 7 | 57% |
webserver/http_request_info.rs |
3 | 40 | 43 | 7% |
webserver/error.rs |
3 | 16 | 19 | 16% |
file_cache.rs |
3 | 13 | 16 | 19% |
webserver/response_writer.rs |
3 | 4 | 7 | 43% |
webserver/database/sqlpage_functions/functions.rs |
3 | 0 | 3 | 100% |
webserver/database/blob_to_data_url.rs |
2 | 28 | 30 | 7% |
webserver/database/sql.rs |
2 | 16 | 18 | 11% |
webserver/database/sqlpage_functions/http_fetch_request.rs |
2 | 2 | 4 | 50% |
webserver/error_with_status.rs |
2 | 1 | 3 | 67% |
webserver/database/sqlpage_expr.rs |
1 | 16 | 17 | 6% |
webserver/database/sqlpage_functions/url_parameters.rs |
1 | 14 | 15 | 7% |
main.rs |
1 | 5 | 6 | 17% |
webserver/database/sql/statement.rs |
1 | 1 | 2 | 50% |
webserver/http_metrics.rs |
1 | 0 | 1 | 100% |
Files with zero iissues: single_or_vec.rs, dynamic_component.rs, webserver/routing.rs, webserver/request_variables.rs, lib.rs, utils.rs.
issue by group
[reap@jager:~/Desktop/SQLPage]$ jq -r --rawfile missed mutants.out/missed.txt \
--argjson dead '["src/template_helpers.rs:525:9","src/template_helpers.rs:526:13",
"src/webserver/database/mod.rs:76:9","src/webserver/error_with_status.rs:22:9",
"src/webserver/error_with_status.rs:27:24",
"src/webserver/database/sqlpage_functions/url_parameters.rs:132:9"]' \
--argjson harness '["src/telemetry.rs","src/webserver/lambda_http.rs","src/cli/commands.rs","src/main.rs"]' \
--arg dialect 'Mssql|MySql|Postgres|postgres|mssql|mysql|oracle|duckdb|Snowflake|sql server|sql_nonnull_to_json|decode_pg_range|decimal_to_json' '
($missed|split("\n")|map(select(length>0))|INDEX(.)) as $M |
map(select($M[.name])) | map(. as $m | {cat:
if $dead | index($m.name|split(": ")[0]) then "dead-code"
elif $harness | index($m.file) then "harness-gap"
elif $m.name | test($dialect) then "dialect"
else "logic" end})
| group_by(.cat)[] | "\(.[0].cat)\t\(length)"' mutants.out/mutants.json
dead-code 7
dialect 33
harness-gap 74
logic 255Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
81reap commentedon Aug 22, 2026
LLM Breakdown
since this is a large amount of issues, I also asked claude to break it up by type. I am not aware of a programmatic way to make this breakdown without the use of an LLM.
note :: while an LLM was used for this part it was only used to process the files attached at the top of this issue, the data in
SQLPage/mutants.outwas created from a deterministic program.Harness gap (74)(maybe, up to maintainers if these are added benifits or not)
telemetry.rs(), 16 of those inlogfmtwebserver/lambda_http.rslambda-webfeature on AWScli/commands.rsmain.rsDialect-gated (33)(no-op, further deep dive required)
The run used the default
sqlite::memory:. These arms are unreachable without the matching driver.webserver/database/sql_to_json.rsdelete match arm "UUID" | "UNIQUEIDENTIFIER","JSONB","INT8RANGE",matches!(db_type, Mssql(_))guardswebserver/database/mod.rsdelete match arm "postgres" | "postgresql" in SupportedDatabase::from_dbms_namefilesystem.rsdelete match arm SupportedDatabase::Mssql in DbFsQueries::get_create_table_sqlwebserver/database/csv_import.rsdelete match arm AnyConnectionKind::Postgres in run_csv_importwebserver/database/execute_queries.rsdelete match arm sqlx::any::AnyKind::Postgres in set_trace_contextwebserver/database/sql/rewrite.rsmatch guard self.database.database_type == SupportedDatabase::MssqlRe-running with
DATABASE_URLpointed at each backend would reclassify these; CI already has the matrix, but I do not have my dev machine properly setup to be able to re-run tests against these other DBs.Dead code (7)(yes, we should clean up/refactor these)
Every item in #1389 shows up here independently.
template_helpers.rs:525:9replace<impl CanHelp for H0>::callwithOk(Default::default())template_helpers.rs:526:13delete match arm[]in<impl CanHelp for H0>::calldatabase/mod.rs:76:9replaceSupportedDatabase::otel_name_from_kindwith""database/mod.rs:76:9replaceSupportedDatabase::otel_name_from_kindwith"xyzzy"error_with_status.rs:22:9replace<impl ResponseError for ErrorWithStatus>::status_codewithDefault::default()error_with_status.rs:27:24replace==with!=inerror_responseurl_parameters.rs:132:9replace<impl From<URLParameters> for String>::fromwithDefault::default()Logic gaps worth a test (subset of the 255)(yes, this is just adding more tests which is always a good thing)
Each row was checked against the source; the last column is what a test would have to assert.
render.rsHeaderContext::add_cookiesecureandhttp_onlydefault to on,remove/max_agehandling. The only cookie test (tests/sql_test_files/data/cookie.sql) reads a cookie; nothing sets one through the component.template_helpers.rsplus/minus/sum+→-,+→*,+=→-=all survive; no test exercises the arithmetic helperstemplate_helpers.rsMarkdownConfig for AppConfigallow_dangerous_htmlandallow_dangerous_protocolcan each be hardcodedtrueorfalseunnoticedoidc.rsvalidate_redirect_urldelete !in!url.starts_with(redirect_uri)and returning""both survive; the existing test coversis_safe_relative_redirect, not this wrapperoidc.rsset_auth_cookie/verify_logout_params>→>=,>→==)sql/rewrite.rsrecognize_sqlpage_functionsql/rewrite.rshas_single_row_shape/emulated_functiondelete match arm "concat"app_config.rsdefault_*markdown_allow_dangerous_protocol,system_root_ca_certificates, OIDC client idapp_config.rsvalidation & durationsAppConfig::validate,validate_smtp_host,cache_stale_duration_ms,deserialize_duration_seconds,resolve_timeouterror_highlighting.rsquote_source_with_highlight+→-and>→>=on the caret column arithmeticcontent_security_policy.rsis_enabledcan return a constant either wayhttp_client.rsdefault_system_root_ca_certificates_from_envcsv_import.rsCopyCsvOption::{quote,header,null,escape}server_timing.rsServerTiming::header_value/→%,%→+in the duration formattingfilesystem.rsio_error_status+is_path_missing_errorguardsErrorKindto a status code, and the missing-path guards inread_file,modified_since,file_existsfile_cache.rsCached::needs_check<→<=on the staleness comparisonhttp_metrics.rsmiddlewarecall>=→<on the status-class bucketblob_to_data_url.rsdetect_mime_typeFound while writing the tests (not in the tables above)
migrations.rsunwraps the version of the failing migration.sqlx::migrate::MigrateErroris#[non_exhaustive], so an added variant turns a "migration N failed" message into a server panic.OidcState::maybe_refreshwas only pinned in the stale direction. A mutant that refreshes a snapshot that is still fresh survives, so every request could hammer the identity provider unnoticed.distinctover a column the rewriter computes, which SQLPage rejects.configuration.mdlists a DuckDB DSN as a possibledatabase_urldefault, butcreate_default_databaseonly ever builds asqlite://URL.configuration.mdlistson_connect.sqlandon_rest.sqlbut no test coveragelovasoa commentedon Aug 22, 2026
Hi @81reap ! Did you review the results? Did you find anything that would be likely to cause problems? Before changing CI, I'd be keen to see actual interesting PRs that fix real issues this uncovered. 100% code coverage is not a goal, what I'd like to see is a realistic assessment of the risk for anything that is not covered. Most of the machine output above does not seem to have enough context to effectively prune the results.
lovasoa commentedon Aug 22, 2026
Would you be ok with starting with small targeted PRs that add actually useful tests based on what you uncovered here?
lovasoa commentedon Aug 22, 2026
Plus, looking at the output, I think your mutation testing exercised only the rust crate tests. I think a lot of what it reports is actually covered in end to end playwright tests (the best kind of tests), and in hurl tests from the./examples/ folder.
81reap commentedon Aug 22, 2026
hey @lovasoa! btw, are you the only maintainer? we seem to interact a lot lol
yes. so I was able to use the results to deterministically identify the same issues I had discovered with claude in #1389
interesting, then I may be following the same principals I do at work too much then. we have a goal to reach +95% test coverage in unit and integ tests as well as adding more layers of automated checks in order to be more confident that the changes do not have unintended side effects. I had assumed this would also be a benefit for an open source project.
Personally, I also feel more confident in my changes when it goes through a swiss cheese layer of robust tests first. I'm not trying to imply there isn't this high bar set in SQLPage's codebase already, just that tools like mutation testing help push that high bar even further.
yes, so the way I read these are code smells. there may be something there, or there may just be a missing test case it's trying to flag. There's a lot of strings to pull on here and the end of the rope isn't always "just add a test". for example #1389 were manual code smells I found, but the actual fix was #1394 . To me sharing the current set of reports helps string the PRs together into a bigger picture, rather than a flood of random OPS & OE changes to the codebase
yes, ofc!! this is why I created the report and issue to share :) I was hoping to also do the work to fix the gaps. I know there are a lot of changes here and not all of them may be applicable but from my understanding, the boon of mutation testing is to be able to find code smells that we can improve.
ah, this is my first time setting up mutation testing from scratch myself and the results were already quite long so I missed that. I can run mutation testing again on the codebase once we knock out this batch of fixes, if that works with you?
so my main goal is to still use SQLPage to power my machine dashboards. this is the main thing I care about as customer trying to use SQLPage and so far my concerns have been
But I also like to follow the principal of leaving a codebase better than I found it so I also raised these smaller fixes :: #1366 , #1367 , #1368 , #1369 , #1372 , and #1373 . IMO this also helps AI tooling perform better as they are susceptible to broken windows but for code quality.
While making those changes I also discovered more issues like B3 from #1386 , C1 & C2 from #1387 , and sql issues in #1385 . That's why I raised these issues and am also breaking them up and raising PRs to address them .
if there's a more github native way to do this (Stacked PRs, Project Planning, etc.) I'm happy to learn and use that instead. we don't use github at work and my personal usage thus far has been limited to personal projects or addressing small issues in open source ones