Code

The test checks a property, not an output

Three files: the harness, the tests, and three new commands.

command what it does
algo balance step 1's table: BST and AVL, two insertion orders
algo rotate the price — build comparisons, time, and rotation count
algo reload the answer to lesson 11's first drill

The tests check a property, not an output

That distinction matters enough to say out loud.

Every earlier lesson's tests checked an output: is it sorted, was the key found, were there fewer than so many comparisons. A test like that can in principle be passed by an algorithm other than the one asked for.

TestHeightStaysBoundedOnSortedInput checks the shape:

bound := 1.4405*math.Log2(float64(n)+2) - 0.3277
if float64(avl.Height()) > bound {
	t.Errorf("n=%d: height %d exceeds the AVL bound %.1f", n, avl.Height(), bound)
}

The input is strictly ascending — the same one that destroyed lesson 11's tree.

Lesson 11's BST returns every key correctly here. Keys() is sorted. Get never errs. And still:

n=  1000: height 1000 exceeds the AVL bound 14.0  (71x over)
n= 10000: height 10000 exceeds the AVL bound 18.8  (532x over)

532×. Correct answers do not come close to being enough here. Only the shape passes.

test what it requires
TestAVLStoresAndFinds finds everything stored, misses what was not
TestRotationsPreserveTheOrdering the BST invariant and a balance of −1..1 during the build, not only at the end
TestHeightStaysBoundedOnSortedInput height and comparisons stay under the AVL bound
TestHeightStaysBoundedOnEveryAdversarialOrder the same for ascending, descending and zigzag
TestRotationsAreAmortizedConstant rotations per insert do not grow with n

The second one checks every 97th insertion, not at the end. A wrong rotation often corrupts the tree temporarily and later insertions happen to "repair" it; a check only at the end lets that through.

The fourth takes the third worst-case order from lesson 11's homework — the zigzag, which also produced height n.