Skip to content

Simple Partition Scaler test coverage#11076

Open
carlydf wants to merge 4 commits into
mainfrom
cdf/scaler-test-coverage
Open

Simple Partition Scaler test coverage#11076
carlydf wants to merge 4 commits into
mainfrom
cdf/scaler-test-coverage

Conversation

@carlydf

@carlydf carlydf commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

What changed?

Simple Partition Scaler was mocked only and did not have tests for these paths.

Why?

We need tests :) Removed nil pointer test because it is tested implicitly.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

Potential risks

Only tests, no risk


Note

Low Risk
Test-only changes to matching partition scaler; no production code paths modified.

Overview
Replaces the single enabled-does-not-panic check in simple_partition_scaler_test.go with a broader unit suite for simplePartitionScaler.

Adds shared helpers (newTestScaler, primedScaler, onTasksLoop) that use EventTimeSource and a fixed scalerWindow so rate-based decisions are deterministic. New cases cover the factory (New / Stop), disabled and Fixed short-circuits, enabled with no Up/Down windows, scale up and down (including floor at 1), Min/Max bounds, hysteresis between Up/Down thresholds, and multiple measurement windows (longest window gates the first decision).

The old nil-map panic test is dropped in favor of implicit coverage via tracker creation in the new tests. A couple of TODO comments note flaky or surprising behavior in scale-down and hysteresis loops.

Reviewed by Cursor Bugbot for commit 732974c. Bugbot is set up for automated code reviews on this repo. Configure here.

carlydf and others added 4 commits July 14, 2026 01:21
newSimplePartitionScaler never initialized the trackers map, so the
first time OnTasks ran with the scaler enabled and an Up/Down window
configured, getTracker's map write panicked with "assignment to entry
in nil map" and crash-looped the matching service.

Initialize trackers in the constructor and add a regression test that
drives the real scaler through that path (the existing scale_manager
tests only exercise a mock). Broader behavioral coverage follows in a
separate PR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the nil-map fix. The scaler's real OnTasks/updateAddTarget
paths were only ~64-88% covered; scale-down, the Min/Max bounds, and the
Up/Down hysteresis deadband were entirely untested (the existing
scale_manager tests only drive a mock).

Add tests covering the disabled/fixed/no-window short-circuits, scale-up,
scale-down (including the floor at one partition), Min and Max bounds,
the hysteresis deadband, PrivateState round-trip across calls, multiple
windows gating a decision, and the factory/lifecycle wiring. This takes
OnTasks and updateAddTarget to 100% statement coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@carlydf
carlydf requested a review from a team as a code owner July 15, 2026 06:24
dec := onTasksLoop(scaler, 1, 3, 10, nil, 0)
require.False(t, dec.NoChange)
require.Equal(t, 0, dec.NewTarget)
require.Empty(t, scaler.trackers, "disabled scaler must not create trackers")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't part of any spec or contract. tests shouldn't care about internal implementation details like this, it just makes them more brittle for no reason

dec := onTasksLoop(scaler, 100, 1, 10, ts, 100*time.Millisecond)
require.False(t, dec.NoChange)
require.Equal(t, 10, dec.NewTarget)
require.NotNil(t, dec.PrivateState, "a real decision carries private state")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this either. it's private. shouldn't we just test the outcomes?

// Current target 20, only 300 tasks/s against TargetRate 100 => target 3.
dec := onTasksLoop(scaler, 300, 20, 1, ts, 100*time.Millisecond)
require.False(t, dec.NoChange)
require.Equal(t, 3, dec.NewTarget) // TODO: this works with 300 tasks and 1 repetition, not sure why it is 1 with 30 tasks 10 reps (in 1 second)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it's that the earliest window will look like 30 tasks in 1 second, and only grows to 300 in 1 second at the end of the sequence? priming makes the first call meaningful when here you may not want it to be

Comment on lines +252 to +254
// TestSimplePartitionScalerMultipleWindows verifies that distinct windows create
// distinct trackers and that a decision is produced only once every window is
// full (the longest window gates the first decision).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is true, but I think the more interesting thing to test is different windows with different thresholds (targets)

}

// onTasksLoop calls OnTasks with the same numTasks for numRepetitions.
func onTasksLoop(scaler *simplePartitionScaler, numTasks, currentTarget, numRepetitions int, ts *clock.EventTimeSource, delay time.Duration) (dec PartitionScalerDecision) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like the calls might be easier to read if numTasks, numReps, and delay were next to each other in the argument list

},
})

dec := onTasksLoop(scaler, 150, 2, 2, ts, 100*time.Millisecond) // TODO: target goes to 1 after third rep, why?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess these isolated tests do have value, but I feel like it might be easier to understand and also more valuable to have one test that runs through a sequence with one scaler with a full representative configuration: add tasks at rate r0 for x seconds, check the last output, add at higher rate r1 for x seconds, check that ups applied, add at lower rate r2 for x/2 seconds, check no movement due to deadband, continue for another x/2 seconds, check that downs finally applied, add at rate r3 that would imply a target < min, check that min applied, add at rate r4 that would imply target > max, check that max applied. also check deadband on the way up, multiple windows, etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants