Skip to content
quarry Artifact [345d5309bf]
Login

Artifact [345d5309bf]

Artifact 345d5309bfe66d6b5d052f8a80011922d7421f2a4e836ebf22b7155ac549f45f:


package web

import (
	"fmt"
	"testing"
)

// bigSample is a repository the size the coarsening exists for: years of
// history, many people, one check-in an hour.
func bigSample(days, people int) activityOut {
	act := activityOut{Since: "2019-01-01", Until: "2026-01-01", Window: "all"}
	for i := 0; i < people; i++ {
		act.Authors = append(act.Authors, activityAuthor{Name: fmt.Sprintf("person-%d", i)})
	}
	for p := 0; p < 40; p++ {
		act.Paths = append(act.Paths, fmt.Sprintf("src/module-%02d/file.c", p))
	}
	for d := 0; d < days; d++ {
		for h := 0; h < 24; h += 3 {
			a := (d + h) % people
			act.Rows = append(act.Rows, activityRow{Day: d, Hour: h, Author: a, Files: 3,
				Hash: fmt.Sprintf("%040x", d*100+h), Branch: "trunk"})
			act.Touches = append(act.Touches, activityTouch{Day: d, Path: (d + h) % 40, Author: a})
		}
	}
	return act
}

// The claim: the width is chosen from the data, so the picture is drawn
// ONCE. The page used to render the whole thing, measure it, double the
// bucket and render again — up to five discarded renders of every
// check-in before the one it kept.
//
// Pinned by rendering at the chosen width and checking it fits: if it
// does, the caller's loop breaks on its first pass.
func TestChosenBucketFitsOnTheFirstRender(t *testing.T) {
	for _, axis := range []string{"clock", "tree"} {
		act := bigSample(2500, 12)
		b := sampleBucket(axis, act, activityByteBudget)
		got := len(coreSampleSVG("sqlite", axis, act, -1, b))
		if got > activityByteBudget && b < activityBucketMax {
			t.Errorf("%s: chose bucket %d and the drawing is %d bytes over budget, "+
				"so the loop still has to render again", axis, b, got-activityByteBudget)
		}
	}
}

// ...and not needlessly coarse. Choosing 32 for everything would also
// "fit on the first render" while throwing away every picture worth
// looking at, so the other half of the claim is that a small repository
// is still drawn check-in by check-in.
func TestASmallRepositoryIsStillDrawnFine(t *testing.T) {
	act := bigSample(30, 3)
	for _, axis := range []string{"clock", "tree"} {
		if b := sampleBucket(axis, act, activityByteBudget); b != 1 {
			t.Errorf("%s: a 30-day repository was coarsened to %d-day buckets", axis, b)
		}
	}
}

// The per-mark costs are measurements, and a measurement rots. Each
// carries headroom over what was measured; this fails if the markup
// grows past that headroom (widths that no longer fit, an extra render
// of everything) or shrinks far below it (widths coarser than the
// picture needed).
func TestMarkCostsMatchTheDrawing(t *testing.T) {
	act := bigSample(120, 4)
	for _, tc := range []struct {
		name   string
		axis   string
		bucket int
		assume int
	}{
		{"clock, per check-in", "clock", 1, sampleClockFine},
		{"clock, coarsened", "clock", 4, sampleClockCoarse},
		{"tree, per touch", "tree", 1, sampleTreeFine},
		{"tree, coarsened", "tree", 4, sampleTreeCoarse},
	} {
		marks := marksAt(t, tc.axis, act, tc.bucket)
		if marks == 0 {
			t.Fatalf("%s: no marks in the fixture", tc.name)
		}
		per := (len(coreSampleSVG("sqlite", tc.axis, act, -1, tc.bucket)) - sampleFixedBytes) / marks
		switch {
		case per > tc.assume:
			t.Errorf("%s costs %d bytes, past the %d the estimate allows: chosen widths "+
				"will not fit and the page renders everything twice", tc.name, per, tc.assume)
		case per < tc.assume*2/3:
			t.Errorf("%s costs only %d bytes against an assumed %d: the estimate has gone "+
				"pessimistic and coarsens pictures that did not need it", tc.name, per, tc.assume)
		}
	}
}

// marksAt counts what a width would draw, the way sampleBucket does.
func marksAt(t *testing.T, axis string, act activityOut, bucket int) int {
	t.Helper()
	type cell struct{ b, y, a int }
	seen := map[cell]struct{}{}
	if axis == "tree" {
		n := 0
		for _, tc := range act.Touches {
			if tc.Inherited != 0 || tc.Path < 0 || tc.Path >= len(act.Paths) {
				continue
			}
			n++
			seen[cell{tc.Day / bucket, tc.Path, tc.Author}] = struct{}{}
		}
		if bucket == 1 {
			return n
		}
		return len(seen)
	}
	if bucket == 1 {
		return len(act.Rows)
	}
	for _, r := range act.Rows {
		seen[cell{r.Day / bucket, r.Hour, r.Author}] = struct{}{}
	}
	return len(seen)
}