servo/servo's test infrastructure carries the highest risk — 5 functions to fix

Five critical-band functions in servo's WPT and tooling layers are both structurally extreme and actively changing, with cyclomatic complexity reaching 108 and fan-out hitting 64 in functions touched within the last day.

Stephen Collins ·
Generated by hotspots · free & open source
pip
$ pip install hotspots-cli
Activity Risk21.06Low
Hottest Functionrun

Antipatterns Detected

long_function6complex_branching5god_function5exit_heavy4deeply_nested4hub_function3

Run this on your own codebase

See if your own repo has a run-style hotspot — run this in any local git repo:

pip
$ pip install hotspots-cli
npm
$ npm install -g @stephencollinstech/hotspots
Run in any repo
$ hotspots analyze .
★ Star on GitHub

Key Points

What is a god function and why does it matter in servo?

A god function is a single function that has accumulated so many distinct responsibilities that it effectively 'knows too much' — it handles parsing, business logic, I/O, and error reporting all in one place rather than delegating to focused helpers. In concrete metric terms, every one of the top five hotspots in servo carries the god_function pattern, and four of them also have fan-out values above 50, meaning they each call into more than 50 distinct functions across the codebase. That breadth of coupling means a change anywhere in that call graph — a renamed helper, a changed return type, a new error condition — has a direct path to breaking the god function's behavior. In a Rust project like servo, this coupling is especially consequential because the compiler enforces correctness contracts at type boundaries; a god function that crosses many of those boundaries in one place concentrates the blast radius of type-level changes into a single, hard-to-test method.

How do I reduce cyclomatic complexity in a large Python or JavaScript function?

The most reliable technique is extract-method refactoring: identify a coherent sub-task within the function — a block of code that reads data, transforms it, or makes a single decision — and move it into a named function with a clear input-output contract. A cyclomatic complexity above 15 is a reasonable threshold to start extracting; above 30 it warrants immediate attention; at 108, as with `clean_cargo_cache` in `python/servo/bootstrap_commands.py`, the function almost certainly contains multiple complete algorithms that should never have shared a scope. A concrete first step for `clean_cargo_cache` is to extract the TOML parsing and crate-classification loop into a `_collect_locked_packages` method — that block is self-contained, has clear inputs (the TOML content) and outputs (the packages dict), and its extraction alone would remove dozens of branches from the parent method. For JavaScript functions like `serialize` in `channel.sub.js`, replacing a large type-dispatch switch with a lookup table of handler functions achieves the same reduction while keeping the dispatch logic readable.

Is servo actively maintained?

The quadrant data shows 6,985 functions in the fire quadrant and zero in the debt quadrant, which means every structurally complex function in the repository is also seeing recent commit activity — there is no backlog of dormant complexity accumulating unnoticed. All five top hotspots were touched once in the last 30 days and modified as recently as yesterday, placing each of them in the fire quadrant: actively changing code with high structural complexity. Four of those five functions are in vendored WPT test files, so the recent activity likely reflects an upstream sync rather than servo-specific feature work — but the underlying engine is clearly being maintained alongside those test updates. High structural complexity and active maintenance are not mutually exclusive; the fire quadrant is evidence of both at once.

How do I reproduce this analysis?

The hotspots CLI is available at github.com/hotspots-dev/hotspots. To reproduce this exact analysis, check out servo/servo at commit 683eab2 with `git checkout 683eab2`, then run `hotspots analyze . --mode snapshot --explain-patterns --force` from the repository root. The same command works on any local git repository without additional configuration, and the `--explain-patterns` flag will annotate each hotspot with the antipattern labels used in this post.

What does activity-weighted risk mean?

Activity-weighted risk is a score that combines structural complexity — derived from cyclomatic complexity, nesting depth, and fan-out — with recent commit frequency, so that functions which are both hard to understand and actively being changed score the highest. A function with cyclomatic complexity of 108 that has not been touched in two years poses a different kind of risk than one with cyclomatic complexity of 35 modified yesterday; the dormant function is structural debt, but the active one is a live regression surface right now. This framing helps teams avoid the trap of refactoring the most visually intimidating code while ignoring the code where bugs are most likely to be introduced this sprint. In servo's case, all five top-ranked functions are in the fire quadrant with activity-weighted risk ranging from 19.61 (`checkCallResults`) to 21.06 (`run`), meaning the structural complexity and the recent activity are both present simultaneously — making them refactoring candidates that cannot be deferred to a future cleanup sprint.

At commit 683eab2, servo/servo has 1,921 critical-band functions out of 50,646 total — and five are in the fire quadrant right now, meaning they are both structurally complex and actively changing. The top-ranked function carries an activity-weighted risk score of 21.06, flagged as a god function and hub function with a fan-out of 55 distinct callees; any engineer merging into files it touches this week is working against a live regression risk, not a deferred cleanup item. I would start triage with clean_cargo_cache in python/servo/bootstrap_commands.py — a cyclomatic complexity of 108 with nine levels of nesting is the single most structurally dangerous function in this list, and it was modified one day ago.

The table below ranks functions by activity-weighted risk — a score that multiplies structural complexity by recent commit frequency. A function that is both hard to understand (high cyclomatic complexity) and actively changing is a higher priority than one that is complex but untouched. CC = cyclomatic complexity (independent execution paths); ND = max nesting depth; FO = fan-out (distinct callees).

Top 5 Hotspots

FunctionFileRiskCCNDFO
runtests/wpt/webgpu/tests/webgpu/webgpu/web_platform/reftests/canvas_complex.html.js21.135455
serializetests/wpt/tests/resources/channel.sub.js20.060713
<anonymous>tests/wpt/webgl/tests/js/glsl-constructor-tests-generator.js19.950753
clean_cargo_cachepython/servo/bootstrap_commands.py19.7108953
checkCallResultstests/wpt/webgpu/tests/webgpu/webgpu/shader/execution/expression/call/builtin/texture_utils.js19.636764

Large Repo Analysis

servo is a large repository. To stay within memory constraints, this analysis used hybrid touch mode: structural complexity — CC, ND, FO — is measured precisely for every function. Git activity is tracked at the function level (via git log -L) only for files with 20 or more commits in the last 30 days; other files use a file-level approximation. Rankings therefore surface functions that are both structurally complex and in the most actively-changing parts of the codebase. Dormant code with high structural complexity will rank lower than it would under a full per-function analysis — to surface it, run hotspots analyze . --per-function-touches on a machine with sufficient memory.

Codemod / Tooling Files in Results

Four of the five top hotspots — run in canvas_complex.html.js, serialize in channel.sub.js, the anonymous function in glsl-constructor-tests-generator.js, and checkCallResults in texture_utils.js — live under tests/wpt/, which is servo’s local mirror of the W3C Web Platform Tests suite and the WebGPU conformance test suite (CTS). These files are upstream test assets vendored into the repository, not servo-authored code; their structural complexity reflects the test suite’s design, not servo’s own architecture. To exclude them from future hotspots runs and focus on servo’s own Rust and Python source, add the following to .hotspotsrc.json: { "exclude": ["tests/wpt/webgpu/", "tests/wpt/webgl/", "tests/wpt/tests/"] }. That will surface the servo-native hotspots that represent actual refactoring opportunity in the engine itself.

What the numbers show across the codebase

Triage Band Distribution
Fire6985Watch43661

50,646 functions analyzed

The quadrant picture is striking: every function in servo/servo falls into either fire or watch — there is no debt quadrant and no ok quadrant. That means every structurally complex function is also seeing recent activity, and every dormant function is structurally simple. The 6,985 fire-quadrant functions are the ones that matter most for near-term regression risk.

Detected Antipatterns
Long Function×6Long Function
Function body is too long to review in a single pass; likely contains multiple distinct responsibilities.
Complex Branching×5Complex Branching
High cyclomatic complexity — many independent execution paths, each a potential bug surface and required test case.
God Function×5God Function
Calls an unusually large number of distinct functions (high fan-out), making it the structural centre of gravity for a subsystem.
Exit Heavy×4Exit Heavy
Multiple return or throw paths dispersed through the body — each exit needs separate test coverage.
Deeply Nested×4Deeply Nested
Control structures nested 4+ levels deep, making it hard to reason about the full execution state at inner branches.
Hub Function×3Hub Function
Many other functions call this one — a change here ripples widely through callers.

The pattern distribution across the top five tells a consistent story: every hotspot is a long function, every one shows complex branching, and most are flagged as god functions. These are not isolated quirks — they are the signature of functions that have accumulated responsibility over time and are now difficult to reason about, test, or change safely.


Top 5 hotspots

run — canvas_complex.html.js

run
tests/wpt/webgpu/tests/webgpu/webgpu/web_platform/reftests/canvas_complex.html.js
21.06
critical
CC 35
ND 4
FO 55
touches/30d 1

This is the top-ranked function in the repository with an activity-weighted risk score of 21.06. Its name and path tell a clear story: it is the entry point for a WebGPU canvas reference test, responsible for orchestrating a runRefTest call that exercises multiple GPU texture formats. The source excerpt confirms what the metrics already suggest — a large outer switch dispatching on format strings (bgra8unorm, rgba8unorm, rgba16float, and their sRGB variants), followed by a nested inner function copyBufferToTexture that contains a second large switch with per-format typed array initialization.

Fan-Out (distinct callees) 55
threshold: 15

A fan-out of 55 is the primary concern here. The hub_function pattern confirms it: this function is a coordination point that calls into GPU buffer creation, texture format utilities, alignment helpers, float conversion routines, and test assertion machinery. A change to any of those callees can ripple back through run in ways that are hard to anticipate. The exit_heavy pattern, combined with a cyclomatic complexity of 35, means there are 35 independent execution paths to cover — but since this is itself a test file, testing the test becomes the challenge. The double switch structure is the concrete extract-method opportunity: the inner copyBufferToTexture function is already partially extracted but still carries its own branching weight. Pulling each format’s buffer-fill logic into dedicated helpers would cut both the CC and the fan-out of the outer run substantially.


serialize — channel.sub.js

serialize
tests/wpt/tests/resources/channel.sub.js
19.98
critical
CC 60
ND 7
FO 13
touches/30d 1

With a cyclomatic complexity of 60 and a maximum nesting depth of 7, serialize in the WPT channel resources file is the most structurally dangerous of the JavaScript hotspots on a pure branching basis. The source excerpt shows an iterative serializer — it avoids recursion by maintaining an explicit queue of objects to process, a deliberate design choice for handling cyclic references. That design is sound, but the implementation has grown to handle every JavaScript type: undefined, null, string, boolean, number (with four special numeric cases — NaN, -0, +Infinity, -Infinity), bigint, function, remoteobject, sendchannel, regexp, date, and error, each as a case in a large switch statement nested inside a while loop.

Cyclomatic Complexity 60
threshold: 10

The deeply_nested and exit_heavy patterns are direct consequences of that structure: 7 levels of nesting arise from the while loop, the seen-object guard, the outer switch, and per-type sub-branches. With 60 paths, any change to how a single type is serialized risks breaking the general loop logic. My recommendation is to extract each type’s serialization into a standalone handler function keyed by type name, then replace the switch with a dispatch table. That refactoring would reduce the CC of the main loop to roughly the number of distinct type categories and make each type handler independently testable.


<anonymous> — glsl-constructor-tests-generator.js

<anonymous>
tests/wpt/webgl/tests/js/glsl-constructor-tests-generator.js
19.93
critical
CC 50
ND 7
FO 53
touches/30d 1

An anonymous IIFE at the module level of a GLSL test generator with a cyclomatic complexity of 50, a nesting depth of 7, and a fan-out of 53. The source excerpt confirms this is a module factory: it defines shader template strings for vertex and fragment shaders, encodes GLSL type dimensions (s, v2, v3, v4, m2, m3, m4), and generates test cases for every combination of constructor argument type and target type.

Fan-Out (distinct callees) 53
threshold: 15

The god_function pattern is apt — this single anonymous block handles template definition, type encoding, argument generation, and test case assembly all in one place. The fan-out of 53 means it reaches into WebGL test utilities, GLSL type helpers, and test harness machinery broadly. Because the function is anonymous, stack traces referencing it will be opaque, making debugging failures in generated tests harder than necessary. The concrete first step is to give the IIFE a name — GLSLConstructorTestsGenerator is already used as the variable receiving its return value — and then extract the type-dimension encoding and shader template assembly into named module-level functions. That alone would reduce the complexity of the top-level factory and surface the logical seams for further splitting.


clean_cargo_cache — bootstrap_commands.py

clean_cargo_cache
python/servo/bootstrap_commands.py
19.65
critical
CC 108
ND 9
FO 53
touches/30d 1

This is the function I would prioritize for a refactoring session today. A cyclomatic complexity of 108 means 108 independent execution paths through a single Python method. Nesting reaches 9 levels deep — a strong signal that the function’s control flow has been extended incrementally rather than designed. The source excerpt confirms it: clean_cargo_cache parses Cargo.lock via TOML, distinguishes between registry crates and git-sourced crates, walks the filesystem under ~/.cargo, reconciles what is present against what is locked, and deletes stale entries — all inline, with branch conditions layered inside loops inside conditionals inside more loops.

Cyclomatic Complexity 108
threshold: 30
Max Nesting Depth 9
threshold: 4

The deeply_nested pattern at ND 9 is a direct consequence of that layering: by the time you are reasoning about a branch nine levels in, you are simultaneously tracking the state of the CARGO_HOME environment variable, whether a crates vs git key exists in the package dict, whether the directory structure matches the expected layout, and whether force was passed. The fan-out of 53 means this method touches filesystem utilities, path manipulation, TOML parsing, and OS environment APIs broadly, so errors here can cascade silently. My recommendation is a staged extract-method refactoring: pull _collect_locked_packages (TOML parsing and crate classification), _scan_cache_entries (filesystem enumeration), and _remove_stale_entries (deletion logic) into separate methods, each testable in isolation. That would cut the CC of the orchestrating method to the single-digit range and make the force and show_size flag logic far easier to follow.


checkCallResults — texture_utils.js

checkCallResults
tests/wpt/webgpu/tests/webgpu/webgpu/shader/execution/expression/call/builtin/texture_utils.js
19.61
critical
CC 36
ND 7
FO 64
touches/30d 1

checkCallResults holds the highest fan-out of any function in the top five at 64 distinct callees, earning it both the god_function and hub_function patterns. Its path places it deep in the WebGPU shader execution test suite, specifically in the builtin texture utility layer. The source excerpt reveals an async function that compares GPU texture sampling results against a software reference implementation: it initializes mip-level weights, computes per-format fractional difference tolerances, iterates over every test call, reads back GPU texels, and formats detailed diagnostic messages when results diverge.

Fan-Out (distinct callees) 64
threshold: 15

The nesting depth of 7 comes from the combination of the outer call loop, per-call result comparison, format-conditional tolerance logic, and the textureSampleBias precision-handling branches — the source excerpt even includes inline commentary explaining why bias values above ~12 produce unexpected GPU results. That commentary is valuable domain knowledge, but buried inside a 64-callee function it is hard to find and easy to break. With 36 execution paths and a fan-out touching mip-weight initialization, software texture reads, GPU readback, and error formatting, a change to any one of those subsystems has a plausible path to silently breaking the comparison logic. I would extract the per-call comparison logic — the tolerance calculation, the software vs GPU texel comparison, and the diagnostic message formatting — into a compareTextureCallResult helper. That would keep the outer loop simple and let the tolerance and precision logic be unit-tested independently of the GPU readback machinery.

Patterns Found

Antipatterns detected across the top functions in this snapshot:

PatternOccurrences
long_function6
complex_branching5
god_function5
exit_heavy4
deeply_nested4
hub_function3

These labels belong to two tiers — Tier 1 (structural): complex_branching, deeply_nested, exit_heavy, long_function, god_function. Tier 2 (relational/temporal): hub_function, cyclic_hub, middle_man, neighbor_risk, stale_complex, churn_magnet, shotgun_target, volatile_god.

See more analyses with these patterns: complex_branching, deeply_nested, exit_heavy, god_function, long_function.

Reproduce This Analysis

git clone https://github.com/servo/servo
cd servo
git checkout 683eab21c0b640649d511f11f599a2efdd9f102f
hotspots analyze . --mode snapshot --explain-patterns --force --hybrid-touches 20

To run the same analysis on your own codebase, run hotspots analyze . --mode snapshot in any local git repo — no configuration required.

I use Hotspots to highlight structural and activity risk — not “bad code.” I treat these findings as a prioritization aid, not a bug predictor. Editorial policy →

Was this useful? Let me know →

Related Analyses