-
Notifications
You must be signed in to change notification settings - Fork 2.4k
/
testutil.go
135 lines (119 loc) · 4.72 KB
/
testutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package testutil // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/common/testutil"
import (
"net"
"os/exec"
"runtime"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/featuregate"
)
type portpair struct {
first string
last string
}
// GetAvailableLocalAddress finds an available local port on tcp network and returns an endpoint
// describing it. The port is available for opening when this function returns
// provided that there is no race by some other code to grab the same port
// immediately.
func GetAvailableLocalAddress(t testing.TB) string {
return GetAvailableLocalNetworkAddress(t, "tcp")
}
// GetAvailableLocalNetworkAddress finds an available local port on specified network and returns an endpoint
// describing it. The port is available for opening when this function returns
// provided that there is no race by some other code to grab the same port
// immediately.
func GetAvailableLocalNetworkAddress(t testing.TB, network string) string {
// Retry has been added for windows as net.Listen can return a port that is not actually available. Details can be
// found in https://github.com/docker/for-win/issues/3171 but to summarize Hyper-V will reserve ranges of ports
// which do not show up under the "netstat -ano" but can only be found by
// "netsh interface ipv4 show excludedportrange protocol=tcp". We'll use []exclusions to hold those ranges and
// retry if the port returned by GetAvailableLocalAddress falls in one of those them.
var exclusions []portpair
portFound := false
if runtime.GOOS == "windows" {
exclusions = getExclusionsList(t)
}
var endpoint string
for !portFound {
endpoint = findAvailableAddress(t, network)
_, port, err := net.SplitHostPort(endpoint)
require.NoError(t, err)
portFound = true
if runtime.GOOS == "windows" {
for _, pair := range exclusions {
if port >= pair.first && port <= pair.last {
portFound = false
break
}
}
}
}
return endpoint
}
func findAvailableAddress(t testing.TB, network string) string {
switch network {
// net.Listen supported network strings
case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
ln, err := net.Listen(network, "localhost:0")
require.NoError(t, err, "Failed to get a free local port")
// There is a possible race if something else takes this same port before
// the test uses it, however, that is unlikely in practice.
defer func() {
assert.NoError(t, ln.Close())
}()
return ln.Addr().String()
// net.ListenPacket supported network strings
case "udp", "udp4", "udp6", "unixgram":
ln, err := net.ListenPacket(network, "localhost:0")
require.NoError(t, err, "Failed to get a free local port")
// There is a possible race if something else takes this same port before
// the test uses it, however, that is unlikely in practice.
defer func() {
assert.NoError(t, ln.Close())
}()
return ln.LocalAddr().String()
}
return ""
}
// Get excluded ports on Windows from the command: netsh interface ipv4 show excludedportrange protocol=tcp
func getExclusionsList(t testing.TB) []portpair {
cmdTCP := exec.Command("netsh", "interface", "ipv4", "show", "excludedportrange", "protocol=tcp")
outputTCP, errTCP := cmdTCP.CombinedOutput()
require.NoError(t, errTCP)
exclusions := createExclusionsList(t, string(outputTCP))
cmdUDP := exec.Command("netsh", "interface", "ipv4", "show", "excludedportrange", "protocol=udp")
outputUDP, errUDP := cmdUDP.CombinedOutput()
require.NoError(t, errUDP)
exclusions = append(exclusions, createExclusionsList(t, string(outputUDP))...)
return exclusions
}
func createExclusionsList(t testing.TB, exclusionsText string) []portpair {
var exclusions []portpair
parts := strings.Split(exclusionsText, "--------")
require.Equal(t, len(parts), 3)
portsText := strings.Split(parts[2], "*")
require.Greater(t, len(portsText), 1) // original text may have a suffix like " - Administered port exclusions."
lines := strings.Split(portsText[0], "\n")
for _, line := range lines {
if strings.TrimSpace(line) != "" {
entries := strings.Fields(strings.TrimSpace(line))
require.Equal(t, len(entries), 2)
pair := portpair{entries[0], entries[1]}
exclusions = append(exclusions, pair)
}
}
return exclusions
}
// Force the state of feature gate for a test
// usage: defer SetFeatureGateForTest("gateName", true)()
func SetFeatureGateForTest(t testing.TB, gate *featuregate.Gate, enabled bool) func() {
originalValue := gate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), enabled))
return func() {
require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), originalValue))
}
}