Skip to content

Commit 2b90ff5

Browse files
committed
undent: move the testutil.Undent func to its own file for clarity
1 parent 52f018d commit 2b90ff5

File tree

3 files changed

+241
-61
lines changed

3 files changed

+241
-61
lines changed

pkg/testutil/testutil.go renamed to pkg/testutil/envtest.go

Lines changed: 35 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package testutil
22

33
import (
4+
"context"
5+
"crypto/tls"
46
"crypto/x509"
57
"io"
68
"net/http"
@@ -10,6 +12,7 @@ import (
1012
"sync"
1113
"testing"
1214

15+
"github.com/jetstack/preflight/pkg/client"
1316
"github.com/jetstack/venafi-connection-lib/api/v1alpha1"
1417
"github.com/stretchr/testify/require"
1518
corev1 "k8s.io/api/core/v1"
@@ -99,71 +102,42 @@ func WithKubeconfig(t testing.TB, restCfg *rest.Config) string {
99102
return kubeconfig.Name()
100103
}
101104

102-
// Undent removes leading indentation/white-space from given string and returns
103-
// it as a string. Useful for inlining YAML manifests in Go code. Inline YAML
104-
// manifests in the Go test files makes it easier to read the test case as
105-
// opposed to reading verbose-y Go structs.
106-
//
107-
// This was copied from https://github.com/jimeh/Undent/blob/main/Undent.go, all
108-
// credit goes to the author, Jim Myhrberg.
109-
func Undent(s string) string {
110-
const (
111-
tab = 9
112-
lf = 10
113-
spc = 32
114-
)
115-
116-
if len(s) == 0 {
117-
return ""
118-
}
119-
120-
// find smallest indent relative to each line-feed
121-
min := 99999999999
122-
count := 0
123-
124-
lfs := make([]int, 0, strings.Count(s, "\n"))
125-
if s[0] != lf {
126-
lfs = append(lfs, -1)
127-
}
105+
// Tests calling to VenConnClient.PostDataReadingsWithOptions must call this
106+
// function to start the VenafiConnection watcher. If you don't call this, the
107+
// test will stall.
108+
func VenConnStartWatching(t *testing.T, cl client.Client) {
109+
t.Helper()
128110

129-
indent := 0
130-
for i := 0; i < len(s); i++ {
131-
if s[i] == lf {
132-
lfs = append(lfs, i)
133-
indent = 0
134-
} else if indent < min {
135-
switch s[i] {
136-
case spc, tab:
137-
indent++
138-
default:
139-
if indent > 0 {
140-
count++
141-
}
142-
if indent < min {
143-
min = indent
144-
}
145-
}
146-
}
147-
}
111+
require.IsType(t, &client.VenConnClient{}, cl)
112+
113+
// This `cancel` is important because the below func `Start(ctx)` needs to
114+
// be stopped before the apiserver is stopped. Otherwise, the test fail with
115+
// the message "timeout waiting for process kube-apiserver to stop". See:
116+
// https://github.com/jetstack/venafi-connection-lib/pull/158#issuecomment-1949002322
117+
// https://github.com/kubernetes-sigs/controller-runtime/issues/1571#issuecomment-945535598
118+
ctx, cancel := context.WithCancel(context.Background())
119+
go func() {
120+
err := cl.(*client.VenConnClient).Start(ctx)
121+
require.NoError(t, err)
122+
}()
123+
t.Cleanup(cancel)
124+
}
148125

149-
// extract each line without indentation
150-
out := make([]byte, 0, len(s)-(min*count))
126+
func VenConnTrustCA(t *testing.T, cl client.Client, cert *x509.Certificate) {
127+
t.Helper()
128+
require.IsType(t, &client.VenConnClient{}, cl)
129+
vcCl := cl.(*client.VenConnClient)
151130

152-
for i := 0; i < len(lfs); i++ {
153-
offset := lfs[i] + 1
154-
end := len(s)
155-
if i+1 < len(lfs) {
156-
end = lfs[i+1] + 1
157-
}
131+
pool := x509.NewCertPool()
132+
pool.AddCert(cert)
158133

159-
if offset+min <= end {
160-
out = append(out, s[offset+min:end]...)
161-
} else if offset < end {
162-
out = append(out, s[offset:end]...)
163-
}
134+
if vcCl.Client.Transport == nil {
135+
vcCl.Client.Transport = http.DefaultTransport
164136
}
165-
166-
return string(out)
137+
if vcCl.Client.Transport.(*http.Transport).TLSClientConfig == nil {
138+
vcCl.Client.Transport.(*http.Transport).TLSClientConfig = &tls.Config{}
139+
}
140+
vcCl.Client.Transport.(*http.Transport).TLSClientConfig.RootCAs = pool
167141
}
168142

169143
// Parses the YAML manifest. Useful for inlining YAML manifests in Go test
@@ -215,7 +189,7 @@ func FakeVenafiCloud(t *testing.T) (_ *httptest.Server, _ *x509.Certificate, set
215189
if r.URL.Path == "/v1/tlspk/upload/clusterdata/no" {
216190
if r.URL.Query().Get("name") != "test cluster name" {
217191
w.WriteHeader(http.StatusBadRequest)
218-
_, _ = w.Write([]byte(`{"error":"unexpected name query param in the test server: ` + r.URL.Query().Get("name") + `"}`))
192+
_, _ = w.Write([]byte(`{"error":"unexpected name query param in the test server: ` + r.URL.Query().Get("name") + `, expected: 'test cluster name'"}`))
219193
return
220194
}
221195
_, _ = w.Write([]byte(`{"status":"ok","organization":"756db001-280e-11ee-84fb-991f3177e2d0"}`))

pkg/testutil/undent.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package testutil
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// Undent removes leading indentation/white-space from given string and returns
8+
// it as a string. Useful for inlining YAML manifests in Go code. Inline YAML
9+
// manifests in the Go test files makes it easier to read the test case as
10+
// opposed to reading verbose-y Go structs.
11+
//
12+
// This was copied from https://github.com/jimeh/Undent/blob/main/Undent.go, all
13+
// credit goes to the author, Jim Myhrberg.
14+
//
15+
// For code readability purposes, it is possible to start the literal string
16+
// with "\n", in which case, the first line is ignored. For example, in the
17+
// following example, name and labels have the same indentation level but aren't
18+
// aligned due to the leading '`':
19+
//
20+
// Undent(
21+
// ` name: foo
22+
// labels:
23+
// foo: bar`)
24+
//
25+
// Instead, you can write a well-aligned text like this:
26+
//
27+
// Undent(`
28+
// name: foo
29+
// labels:
30+
// foo: bar`)
31+
//
32+
// For code readability purposes, it is also possible to not have the correct
33+
// number of indentations in the last line. For example:
34+
//
35+
// Undent(`
36+
// foo
37+
// bar
38+
// `)
39+
//
40+
// For code readability purposes, you can also omit the indentations for empty
41+
// lines. For example:
42+
//
43+
// Undent(`
44+
// foo <---- 4 spaces
45+
// <---- no indentation here
46+
// bar <---- 4 spaces
47+
// `)
48+
func Undent(s string) string {
49+
if len(s) == 0 {
50+
return ""
51+
}
52+
53+
// indentsPerLine is the minimal indent level that we have found up to now.
54+
// For example, "\t\t" corresponds to an indentation of 2, and " " an
55+
// indentation of 3.
56+
indentsPerLine := 99999999999
57+
indentedLinesCnt := 0
58+
59+
// lineOffsets tells you where the beginning of each line is in terms of
60+
// offset. Example:
61+
// "\tfoo\n\tbar\n" -> [0, 5]
62+
// 0 5
63+
var lineOffsets []int
64+
65+
// For code readability purposes, users can leave the first line empty.
66+
if s[0] != '\n' {
67+
lineOffsets = append(lineOffsets, 0)
68+
}
69+
70+
curLineIndent := 0 // Number of tabs or spaces in the current line.
71+
for pos := 0; pos < len(s); pos++ {
72+
if s[pos] == '\n' {
73+
if pos+1 < len(s) {
74+
lineOffsets = append(lineOffsets, pos+1)
75+
}
76+
curLineIndent = 0
77+
continue
78+
}
79+
80+
// Skip to the next line if we are already beyond the minimal indent
81+
// level that we have found so far. The rest of this line will be kept
82+
// as-is.
83+
if curLineIndent >= indentsPerLine {
84+
continue
85+
}
86+
87+
// The minimal indent level that we have found so far in previous lines
88+
// might not be the smallest indent level. Once we hit the first
89+
// non-indent char, let's check whether it is the new minimal indent
90+
// level.
91+
if s[pos] != ' ' && s[pos] != '\t' {
92+
if curLineIndent != 0 {
93+
indentedLinesCnt++
94+
}
95+
indentsPerLine = curLineIndent
96+
continue
97+
}
98+
99+
curLineIndent++
100+
}
101+
102+
// Extract each line without indentation.
103+
out := make([]byte, 0, len(s)-(indentsPerLine*indentedLinesCnt))
104+
105+
for line := 0; line < len(lineOffsets); line++ {
106+
first := lineOffsets[line]
107+
108+
// Index of the last character of the line. It is often the '\n'
109+
// character, except for the last line.
110+
var last int
111+
if line == len(lineOffsets)-1 {
112+
last = len(s) - 1
113+
} else {
114+
last = lineOffsets[line+1] - 1
115+
}
116+
117+
var lineStr string
118+
switch {
119+
// Case 0: if the first line is empty, let's skip it.
120+
case line == 0 && first == last:
121+
lineStr = ""
122+
123+
// Case 1: we want the user to be able to omit some tabs or spaces in
124+
// the last line for readability purposes.
125+
case line == len(lineOffsets)-1 && s[last] != '\n' && isIndent(s[first:last]):
126+
lineStr = ""
127+
128+
// Case 2: we want the user to be able to omit the indentations for
129+
// empty lines for readability purposes.
130+
case first == last:
131+
lineStr = "\n"
132+
133+
// Case 3: error when a line doesn't contain the correct indentation
134+
// level.
135+
case first+indentsPerLine > last:
136+
panic(fmt.Sprintf("line %d has an incorrect indent level: %q", line, s[first:last]))
137+
138+
// Case 4: at this point, the indent level is correct, so let's remove
139+
// the indentation and keep the rest.
140+
case first+indentsPerLine <= last:
141+
lineStr = s[first+indentsPerLine : last+1]
142+
143+
default:
144+
panic(fmt.Sprintf("unexpected case: first: %d, last: %d, indentsPerLine: %d, line: %q", first, last, indentsPerLine, s[first:last]))
145+
}
146+
out = append(out, lineStr...)
147+
}
148+
149+
return string(out)
150+
}
151+
152+
// isIndent returns true if the given string is only made of spaces or a
153+
// tabs.
154+
func isIndent(s string) bool {
155+
for _, r := range s {
156+
if r != ' ' && r != '\t' {
157+
return false
158+
}
159+
}
160+
return true
161+
}

pkg/testutil/undent_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package testutil
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
// This is a test for the testing func "Undent". I wasn't confident with
10+
// Undent's behavior, so I wrote this test to verify it.
11+
func Test_Undent(t *testing.T) {
12+
t.Run("empty string", runTest_Undent(``, ``))
13+
14+
t.Run("if last line has the same indent as other lines and, it is ignored", runTest_Undent(`
15+
foo
16+
bar
17+
`, "foo\nbar\n"))
18+
19+
t.Run("you can un-indent the last line to make the Go code more readable", runTest_Undent(`
20+
foo
21+
bar
22+
`, "foo\nbar\n"))
23+
24+
t.Run("last line may not be an empty line", runTest_Undent(`
25+
foo
26+
bar`, "foo\nbar"))
27+
28+
t.Run("1 empty line is preserved", runTest_Undent("\t\tfoo\n\t\t\n\t\tbar\n", "foo\n\nbar\n"))
29+
30+
t.Run("2 empty lines are preserved", runTest_Undent("\t\tfoo\n\t\t\n\t\t\n\t\tbar\n", "foo\n\n\nbar\n"))
31+
32+
t.Run("you can also omit the tabs or spaces for empty lines", runTest_Undent(`
33+
foo
34+
35+
bar
36+
`, "foo\n\nbar\n"))
37+
}
38+
39+
func runTest_Undent(given, expected string) func(t *testing.T) {
40+
return func(t *testing.T) {
41+
t.Helper()
42+
got := Undent(given)
43+
assert.Equal(t, expected, got)
44+
}
45+
}

0 commit comments

Comments
 (0)