| @@ -36,14 +36,36 @@ func buildGitbayd(t *testing.T) string { |
| 36 | return bin |
36 | return bin |
| 37 | } |
37 | } |
| 38 | |
38 | |
| 39 | func freePort(t *testing.T) int { |
39 | // freePorts reserves n distinct ports. A port is chosen by binding :0 and |
| |
40 | // reading back what the kernel assigned, so every listener has to stay open |
| |
41 | // until all of them are picked — closing one before picking the next lets |
| |
42 | // the kernel hand out the same port again, and the instance that asked for |
| |
43 | // three then fails to bind its second listener. |
| |
44 | // |
| |
45 | // Still a narrowing rather than a guarantee: another process can take a port |
| |
46 | // between the close here and the bind in gitbayd. Distinctness within one |
| |
47 | // instance is the part that is ours. |
| |
48 | func freePorts(t *testing.T, n int) []int { |
| 40 | t.Helper() |
49 | t.Helper() |
| 41 | ln, err := net.Listen("tcp", "127.0.0.1:0") |
50 | lns := make([]net.Listener, 0, n) |
| 42 | if err != nil { |
51 | ports := make([]int, 0, n) |
| 43 | t.Fatal(err) |
52 | for i := 0; i < n; i++ { |
| |
53 | ln, err := net.Listen("tcp", "127.0.0.1:0") |
| |
54 | if err != nil { |
| |
55 | t.Fatal(err) |
| |
56 | } |
| |
57 | lns = append(lns, ln) |
| |
58 | ports = append(ports, ln.Addr().(*net.TCPAddr).Port) |
| 44 | } |
59 | } |
| 45 | defer ln.Close() |
60 | for _, ln := range lns { |
| 46 | return ln.Addr().(*net.TCPAddr).Port |
61 | ln.Close() |
| |
62 | } |
| |
63 | return ports |
| |
64 | } |
| |
65 | |
| |
66 | func freePort(t *testing.T) int { |
| |
67 | t.Helper() |
| |
68 | return freePorts(t, 1)[0] |
| 47 | } |
69 | } |
| 48 | |
70 | |
| 49 | func startInstance(t *testing.T) *instance { |
71 | func startInstance(t *testing.T) *instance { |
| @@ -53,12 +75,13 @@ func startInstance(t *testing.T) *instance { |
| 53 | // startInstanceWith appends extra TOML to the instance config. |
75 | // startInstanceWith appends extra TOML to the instance config. |
| 54 | func startInstanceWith(t *testing.T, extra string) *instance { |
76 | func startInstanceWith(t *testing.T, extra string) *instance { |
| 55 | t.Helper() |
77 | t.Helper() |
| |
78 | ports := freePorts(t, 3) |
| 56 | inst := &instance{ |
79 | inst := &instance{ |
| 57 | gitbayd: buildGitbayd(t), |
80 | gitbayd: buildGitbayd(t), |
| 58 | root: t.TempDir(), |
81 | root: t.TempDir(), |
| 59 | port: freePort(t), |
82 | port: ports[0], |
| 60 | httpPort: freePort(t), |
83 | httpPort: ports[1], |
| 61 | gitPort: freePort(t), |
84 | gitPort: ports[2], |
| 62 | sshDir: t.TempDir(), |
85 | sshDir: t.TempDir(), |
| 63 | } |
86 | } |
| 64 | inst.config = filepath.Join(inst.root, "config.toml") |
87 | inst.config = filepath.Join(inst.root, "config.toml") |
| @@ -244,3 +267,19 @@ func TestControlPlaneOverBareSSH(t *testing.T) { |
| 244 | t.Fatalf("keys remove with spaced arg: exit %d (want 3), stderr %q", code, errOut) |
267 | t.Fatalf("keys remove with spaced arg: exit %d (want 3), stderr %q", code, errOut) |
| 245 | } |
268 | } |
| 246 | } |
269 | } |
| |
270 | |
| |
271 | // freePort used to close its listener before returning, so the kernel was |
| |
272 | // free to hand the same port to the next call. An instance asks for three in |
| |
273 | // a row and then fails to bind its second listener, which surfaces as an |
| |
274 | // unrelated test timing out on "gitbayd did not start listening". |
| |
275 | func TestFreePortsAreDistinct(t *testing.T) { |
| |
276 | for round := 0; round < 50; round++ { |
| |
277 | seen := map[int]bool{} |
| |
278 | for _, p := range freePorts(t, 8) { |
| |
279 | if seen[p] { |
| |
280 | t.Fatalf("round %d: port %d issued twice in one request", round, p) |
| |
281 | } |
| |
282 | seen[p] = true |
| |
283 | } |
| |
284 | } |
| |
285 | } |