main_test.go #7

  • //
  • guest/
  • perforce_software/
  • p4survey/
  • cmd/
  • p4changestats/
  • main_test.go
  • View
  • Commits
  • Open Download .zip Download (6 KB)
package main

import (
	"encoding/base64"
	"encoding/json"
	"strings"
	"testing"
	"time"
)

func TestTaggedRecordsStartsNewRecordAtFirstField(t *testing.T) {
	output := []byte("... change 12\n... time 1767225600\n... user sam\n... change 11\n... time 1767139200\n")
	records := taggedRecords(output, "change")
	if len(records) != 2 || records[0]["change"] != "12" || records[1]["change"] != "11" {
		t.Fatalf("unexpected records: %#v", records)
	}
}

func TestChangelistsByMonthSkipsIncompleteRecords(t *testing.T) {
	if !completeChangeRecord(map[string]string{"change": "12", "time": "1767225600"}) {
		t.Fatal("expected complete changelist record")
	}
	if completeChangeRecord(map[string]string{"detail": "tracking"}) {
		t.Fatal("expected incomplete changelist record to be rejected")
	}
}

func TestRevisionCountsAddAction(t *testing.T) {
	var counts revisionCounts
	for _, action := range []string{"add", "edit", "move/add", "integrate", "delete", "move/delete", "branch", "import", "purge", "ignored"} {
		counts.addAction(action)
	}
	if counts != (revisionCounts{addEdit: 4, deleted: 2, branched: 2, purged: 1}) {
		t.Fatalf("unexpected revision counts: %+v", counts)
	}
}

func TestLocalDepotFilespec(t *testing.T) {
	depot := depotInfo{name: "main", typev: "local"}
	filespecs, err := (&p4Runner{}).filespecsForDepot(depot, 12, 34)
	if err != nil {
		t.Fatal(err)
	}
	if len(filespecs) != 1 || filespecs[0] != "//main/...@12,34" {
		t.Fatalf("unexpected local depot filespecs: %v", filespecs)
	}
}

func TestStreamDirsSpecUsesDepotDepth(t *testing.T) {
	for _, test := range []struct {
		depth int
		want  string
	}{
		{depth: 1, want: "//tools/*"},
		{depth: 2, want: "//tools/*/*"},
	} {
		got, err := streamDirsSpec(depotInfo{name: "tools", typev: "stream", streamDepth: test.depth})
		if err != nil || got != test.want {
			t.Fatalf("depth %d: got %q, err %v; want %q", test.depth, got, err, test.want)
		}
	}
}

func TestParseStreamDepth(t *testing.T) {
	for _, value := range []string{"1", "//tools/2"} {
		depth, err := parseStreamDepth(value)
		if err != nil || depth != map[string]int{"1": 1, "//tools/2": 2}[value] {
			t.Fatalf("value %q: got %d, err %v", value, depth, err)
		}
	}
}

func TestStreamDirectories(t *testing.T) {
	output := []byte("... dir //tools/main\n... dir //tools/release\n")
	streams := streamDirectories(output)
	if got := strings.Join(streams, ","); got != "//tools/main,//tools/release" {
		t.Fatalf("got %q, want stream directories", got)
	}
}

func TestMonthsBetweenIncludesPartialMonths(t *testing.T) {
	start := time.Date(2026, time.January, 31, 0, 0, 0, 0, time.Local)
	end := time.Date(2026, time.March, 1, 0, 0, 0, 0, time.Local)
	months := monthsBetween(start, end)
	if got := []string{months[0].Format("2006-01"), months[1].Format("2006-01"), months[2].Format("2006-01")}; strings.Join(got, ",") != "2026-01,2026-02,2026-03" {
		t.Fatalf("unexpected months: %v", got)
	}
}

func TestWriteReport(t *testing.T) {
	var output strings.Builder
	err := writeReport([]reportRow{{Month: "2026-01", Changes: 2, AddEdit: 3, Deleted: 4, Branched: 5, Purged: 6}}, &output)
	if err != nil {
		t.Fatal(err)
	}
	const expected = "month,changes,add_edit,deleted,branched,purged\n2026-01,2,3,4,5,6\n"
	if output.String() != expected {
		t.Fatalf("got %q, want %q", output.String(), expected)
	}
}

func TestDataPushPayloadIncludesVersion(t *testing.T) {
	payload, err := dataPushPayload([]reportRow{}, trackingTotals{})
	if err != nil {
		t.Fatal(err)
	}
	var entries []map[string]string
	if err := json.Unmarshal(payload, &entries); err != nil {
		t.Fatal(err)
	}
	if len(entries) != 1 {
		t.Fatalf("unexpected payload entries: %d", len(entries))
	}
	output, err := base64.StdEncoding.DecodeString(entries[0]["output"])
	if err != nil {
		t.Fatal(err)
	}
	var report remoteReport
	if err := json.Unmarshal(output, &report); err != nil {
		t.Fatal(err)
	}
	if report.Version != Version {
		t.Fatalf("unexpected report version: %s", report.Version)
	}
}

func TestDataPushPayloadIncludesTracking(t *testing.T) {
	tracking := trackingTotals{commands: 2, lapse: 6 * time.Millisecond, tableLockHeld: map[string]lockHeld{
		"db.user": {read: 3 * time.Millisecond, write: 4 * time.Millisecond},
	}}
	payload, err := dataPushPayload([]reportRow{{Month: "2026-09"}}, tracking)
	if err != nil {
		t.Fatal(err)
	}
	var entries []map[string]string
	if err := json.Unmarshal(payload, &entries); err != nil {
		t.Fatal(err)
	}
	output, err := base64.StdEncoding.DecodeString(entries[0]["output"])
	if err != nil {
		t.Fatal(err)
	}
	var report remoteReport
	if err := json.Unmarshal(output, &report); err != nil {
		t.Fatal(err)
	}
	if report.Tracking.Commands != 2 || report.Tracking.AverageLapse != "3.0ms" || len(report.Tracking.Tables) != 1 || report.Tracking.Tables[0].Table != "db.user" {
		t.Fatalf("unexpected tracking report: %+v", report.Tracking)
	}
}

func TestFormatAverageMilliseconds(t *testing.T) {
	if got := formatAverageMilliseconds(246_722_222 * time.Nanosecond); got != "246.7ms" {
		t.Fatalf("got %q, want %q", got, "246.7ms")
	}
}

func TestTrackingTotals(t *testing.T) {
	var totals trackingTotals
	totals.add([]byte("--- lapse .151s\n--- usage 3+2us 0+0io\n--- memory cmd/proc 20mb/20mb\n--- db.user\n---   total lock wait+held read/write 12ms+13ms/14ms+15ms\n--- db.change\n---   total lock wait+held read/write 16ms+17ms/18ms+19ms\n"))
	if totals.lapse != 151*time.Millisecond || totals.userCPU != 3*time.Millisecond || totals.systemCPU != 2*time.Millisecond {
		t.Fatalf("unexpected durations: %+v", totals)
	}
	if totals.memoryMB != 20 || totals.readLockHeld != 30*time.Millisecond || totals.writeLockHeld != 34*time.Millisecond {
		t.Fatalf("unexpected totals: %+v", totals)
	}
	if totals.tableLockHeld["db.user"] != (lockHeld{read: 13 * time.Millisecond, write: 15 * time.Millisecond}) {
		t.Fatalf("unexpected db.user lock hold times: %+v", totals.tableLockHeld["db.user"])
	}
}
# Change User Description Committed
#7 33821 Robert Cowham Fix change parsing
#6 33816 Robert Cowham Cache streams depot dirs
#5 33814 Robert Cowham Handle streams depots more intelligently
#4 33813 Robert Cowham Reduce memory usage for processing p4 files -a
#3 33809 Robert Cowham Track cpu/memory/table locks and summarise
#2 33806 Robert Cowham Add version
#1 33805 Robert Cowham New changestats utility