v2.30.0-pre 7341e0e

readiness_test.go RawHistoryFolder

1package container
2
3import (
4	"context"
5	"net"
6	"net/http"
7	"net/http/httptest"
8	"sync/atomic"
9	"syscall"
10	"testing"
11	"time"
12
13	"github.com/stretchr/testify/assert"
14	"github.com/stretchr/testify/require"
15)
16
17// --- TCP Probe Tests ---
18
19func TestTCPProbe_Success(t *testing.T) {
20	ln, err := net.Listen("tcp", "127.0.0.1:0")
21	require.NoError(t, err)
22	defer ln.Close()
23
24	// Accept connections in background so the probe succeeds
25	go func() {
26		for {
27			conn, err := ln.Accept()
28			if err != nil {
29				return
30			}
31			conn.Close()
32		}
33	}()
34
35	ctx := testContext()
36	err = tcpProbe(ctx, ln.Addr().String(), 5*time.Second)
37	assert.NoError(t, err)
38}
39
40func TestTCPProbe_Timeout(t *testing.T) {
41	// Reserve an ephemeral port and close it immediately to get a deterministic
42	// unreachable target, avoiding assumptions about privileged ports.
43	ln, err := net.Listen("tcp", "127.0.0.1:0")
44	require.NoError(t, err)
45	addr := ln.Addr().String()
46	ln.Close()
47
48	ctx := testContext()
49	err = tcpProbe(ctx, addr, 200*time.Millisecond)
50	assert.Error(t, err)
51	assert.Contains(t, err.Error(), "TCP probe timeout")
52}
53
54func TestTCPProbe_ContextCancelled(t *testing.T) {
55	ctx, cancel := context.WithCancel(testContext())
56	cancel()
57	err := tcpProbe(ctx, "127.0.0.1:1", 5*time.Second)
58	assert.Error(t, err)
59}
60
61func TestTCPProbe_DelayedListener(t *testing.T) {
62	ln, err := net.Listen("tcp", "127.0.0.1:0")
63	require.NoError(t, err)
64	// Close initially so probe fails at first
65	addr := ln.Addr().String()
66	ln.Close()
67
68	// Re-open after 1 second
69	go func() {
70		time.Sleep(time.Second)
71		lc := net.ListenConfig{
72			Control: func(network, address string, c syscall.RawConn) error {
73				return c.Control(func(fd uintptr) {
74					_ = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
75				})
76			},
77		}
78		newLn, err := lc.Listen(context.Background(), "tcp", addr)
79		if err != nil {
80			t.Logf("delayed listener failed to bind: %v", err)
81			return
82		}
83		defer newLn.Close()
84		for {
85			conn, err := newLn.Accept()
86			if err != nil {
87				return
88			}
89			conn.Close()
90		}
91	}()
92
93	ctx := testContext()
94	err = tcpProbe(ctx, addr, 5*time.Second)
95	assert.NoError(t, err)
96}
97
98// --- HTTP Probe Tests ---
99
100func TestHTTPProbe_Success(t *testing.T) {
101	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102		if r.URL.Path == "/healthz" {
103			w.WriteHeader(200)
104			return
105		}
106		w.WriteHeader(404)
107	}))
108	defer srv.Close()
109
110	ctx := testContext()
111	err := httpProbe(ctx, srv.URL+"/healthz", 5*time.Second)
112	assert.NoError(t, err)
113}
114
115func TestHTTPProbe_ServerError_ThenSuccess(t *testing.T) {
116	var calls int32
117	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
118		n := atomic.AddInt32(&calls, 1)
119		if n < 3 {
120			w.WriteHeader(503)
121			return
122		}
123		w.WriteHeader(200)
124	}))
125	defer srv.Close()
126
127	ctx := testContext()
128	err := httpProbe(ctx, srv.URL+"/healthz", 10*time.Second)
129	assert.NoError(t, err)
130	assert.GreaterOrEqual(t, atomic.LoadInt32(&calls), int32(3))
131}
132
133func TestHTTPProbe_Timeout(t *testing.T) {
134	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
135		w.WriteHeader(503)
136	}))
137	defer srv.Close()
138
139	ctx := testContext()
140	err := httpProbe(ctx, srv.URL+"/healthz", 500*time.Millisecond)
141	assert.Error(t, err)
142	assert.Contains(t, err.Error(), "HTTP probe timeout")
143}
144
145func TestHTTPProbe_ContextCancelled(t *testing.T) {
146	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
147		w.WriteHeader(503)
148	}))
149	defer srv.Close()
150
151	ctx, cancel := context.WithCancel(testContext())
152	cancel()
153	err := httpProbe(ctx, srv.URL+"/healthz", 5*time.Second)
154	assert.Error(t, err)
155}
156
157func TestHTTPProbe_RedirectIsSuccess(t *testing.T) {
158	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
159		http.Redirect(w, r, "/other", http.StatusFound)
160	}))
161	defer srv.Close()
162
163	ctx := testContext()
164	// The probe should treat 3xx as success without following the redirect
165	err := httpProbe(ctx, srv.URL+"/healthz", 5*time.Second)
166	assert.NoError(t, err)
167}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close