carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "bytes"
5 "os"
6 "strings"
7 "testing"
8)
9
10// pipeFile is a real *os.File that is NOT a terminal — the shape every CI
11// run, every `| less` and every e2e test's exec'd binary sees.
12func pipeFile(t *testing.T) *os.File {
13 t.Helper()
14 r, w, err := os.Pipe()
15 if err != nil {
16 t.Fatal(err)
17 }
18 t.Cleanup(func() { r.Close(); w.Close() })
19 return w
20}
21
22func TestColorEnabledPrecedence(t *testing.T) {
23 f := pipeFile(t)
24 cases := []struct {
25 name string
26 env map[string]string
27 want bool
28 }{
29 {"pipe with a terminal set", map[string]string{"TERM": "xterm-256color"}, false},
30 {"CARLOS_COLOR=always beats the pipe", map[string]string{"TERM": "xterm", "CARLOS_COLOR": "always"}, true},
31 {"CARLOS_COLOR=always beats NO_COLOR", map[string]string{"TERM": "xterm", "CARLOS_COLOR": "always", "NO_COLOR": "1"}, true},
32 {"CARLOS_COLOR=never", map[string]string{"TERM": "xterm", "CARLOS_COLOR": "never"}, false},
33 {"NO_COLOR", map[string]string{"TERM": "xterm", "NO_COLOR": "1"}, false},
34 {"TERM=dumb", map[string]string{"TERM": "dumb", "CARLOS_COLOR": ""}, false},
35 }
36 for _, tc := range cases {
37 t.Run(tc.name, func(t *testing.T) {
38 for _, k := range []string{"TERM", "NO_COLOR", "CARLOS_COLOR"} {
39 t.Setenv(k, tc.env[k])
40 }
41 if got := colorEnabled(f); got != tc.want {
42 t.Fatalf("colorEnabled = %v, want %v", got, tc.want)
43 }
44 })
45 }
46}
47
48// A buffer is what the CLI's print seam holds under test. It must never be
49// coloured, whatever the environment says — including CARLOS_COLOR=always,
50// because an operator with that set in their shell would otherwise see every
51// in-process test fail on escape codes.
52func TestColorForNonFileIsAlwaysPlain(t *testing.T) {
53 t.Setenv("CARLOS_COLOR", "always")
54 t.Setenv("TERM", "xterm-256color")
55 var buf bytes.Buffer
56 if c := colorFor(&buf); c.on {
57 t.Fatal("a bytes.Buffer was given a colour palette")
58 }
59 mutedf(&buf, "using %s as %s", "https://console.example", "vicky@keymail.dev")
60 if got, want := buf.String(), "using https://console.example as vicky@keymail.dev\n"; got != want {
61 t.Fatalf("mutedf wrote %q, want %q", got, want)
62 }
63}
64
65func TestPaletteWrapsOnlyWhenOn(t *testing.T) {
66 on := colors{on: true}
67 if got, want := on.ok("shipped"), ansiGreen+"shipped"+ansiReset; got != want {
68 t.Fatalf("ok = %q, want %q", got, want)
69 }
70 if got := on.muted(""); got != "" {
71 t.Fatalf("an empty string got wrapped: %q", got)
72 }
73 off := colors{}
74 for name, got := range map[string]string{
75 "muted": off.muted("x"), "ok": off.ok("x"), "warn": off.warn("x"),
76 "bad": off.bad("x"), "head": off.head("x"), "link": off.link("x"),
77 } {
78 if got != "x" {
79 t.Fatalf("%s with colour off = %q, want %q", name, got, "x")
80 }
81 }
82}
83
84// The point of table's buffering: escape codes on the header must not be
85// counted as column content. If they were, every row below would be padded
86// to the header's inflated width.
87func TestTableHeaderStylingDoesNotMoveColumns(t *testing.T) {
88 rows := func(w *table) {
89 w.Write([]byte("CHANNEL\tVERSION\tPROMOTED\n"))
90 w.Write([]byte("canary/keymail\t1.2.3\t2026-08-07\n"))
91 w.Write([]byte("stable\t1.0.0\t2026-08-01\n"))
92 }
93
94 var plain bytes.Buffer
95 pt := newTable(&plain)
96 rows(pt)
97 if err := pt.Flush(); err != nil {
98 t.Fatal(err)
99 }
100
101 var styled bytes.Buffer
102 st := newTable(&styled)
103 st.pal = colors{on: true} // a buffer never gets colour on its own
104 rows(st)
105 if err := st.Flush(); err != nil {
106 t.Fatal(err)
107 }
108
109 got := styled.String()
110 if !strings.HasPrefix(got, ansiBold+"CHANNEL") {
111 t.Fatalf("header not styled: %q", got[:40])
112 }
113 // Strip the codes back out and the two renderings must be identical:
114 // the columns did not move.
115 stripped := strings.NewReplacer(ansiBold, "", ansiReset, "").Replace(got)
116 if stripped != plain.String() {
117 t.Fatalf("styling changed the layout:\n plain: %q\nstyled: %q", plain.String(), stripped)
118 }
119}
120
121// Colour off, the banner is byte-for-byte the line the API-mode tests pin.
122func TestPrintUsingIsUnchangedWithoutColor(t *testing.T) {
123 var buf bytes.Buffer
124 printUsing(&buf, &apiSession{console: "https://console.example", address: "vicky@keymail.dev"})
125 if got, want := buf.String(), "using https://console.example as vicky@keymail.dev\n"; got != want {
126 t.Fatalf("printUsing wrote %q, want %q", got, want)
127 }
128}
129