Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Run integration tests inside docker, dont depend on local platform #844

Merged
merged 16 commits into from
Oct 8, 2022
Merged
32 changes: 28 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,40 @@ test:
test_integration: test_integration_cli test_integration_derp test_integration_oidc test_integration_general

test_integration_cli:
go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./...
docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_cli,integration -timeout 30m -count=1 ./...

test_integration_derp:
go test -failfast -tags integration_derp,integration -timeout 30m -count=1 ./...
docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_derp,integration -timeout 30m -count=1 ./...

test_integration_general:
go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./...
docker network rm $$(docker network ls --filter name=headscale --quiet) || true
docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_general,integration -timeout 30m -count=1 ./...

test_integration_oidc:
go test -failfast -tags integration_oidc,integration -timeout 30m -count=1 ./...
docker network rm $$(docker network ls --filter name=headscale --quiet) || true
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one

docker network create headscale-test || true
docker run -t --rm \
--network headscale-test \
-v $$PWD:$$PWD -w $$PWD \
-v /var/run/docker.sock:/var/run/docker.sock golang:1 \
go test -failfast -tags integration_oidc,integration -timeout 30m -count=1 ./...

coverprofile_func:
go tool cover -func=coverage.out
Expand Down
6 changes: 5 additions & 1 deletion cmd/headscale/cli/mockoidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func mockOIDC() error {
if clientSecret == "" {
return errMockOidcClientSecretNotDefined
}
addrStr := os.Getenv("MOCKOIDC_ADDR")
if addrStr == "" {
return errMockOidcPortNotDefined
}
portStr := os.Getenv("MOCKOIDC_PORT")
if portStr == "" {
return errMockOidcPortNotDefined
Expand All @@ -61,7 +65,7 @@ func mockOIDC() error {
return err
}

listener, err := net.Listen("tcp", fmt.Sprintf("mockoidc:%d", port))
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", addrStr, port))
if err != nil {
return err
}
Expand Down
8 changes: 7 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,13 @@
};
in rec {
# `nix develop`
devShell = pkgs.mkShell {buildInputs = devDeps;};
devShell = pkgs.mkShell {
buildInputs = devDeps;

shellHook = ''
export GOFLAGS=-tags="integration,integration_general,integration_oidc,integration_cli,integration_derp"
'';
};

# `nix build`
packages = with pkgs; {
Expand Down
21 changes: 14 additions & 7 deletions integration_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)
Expand Down Expand Up @@ -42,11 +43,11 @@ func (s *IntegrationCLITestSuite) SetupTest() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
}

if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil {
s.network = *pnetwork
} else {
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
if err != nil {
s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
}
s.network = network

headscaleBuildOptions := &dockertest.BuildOptions{
Dockerfile: "Dockerfile",
Expand All @@ -63,8 +64,12 @@ func (s *IntegrationCLITestSuite) SetupTest() {
Mounts: []string{
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
},
Networks: []*dockertest.Network{&s.network},
Cmd: []string{"headscale", "serve"},
Cmd: []string{"headscale", "serve"},
Networks: []*dockertest.Network{&s.network},
ExposedPorts: []string{"8080/tcp"},
PortBindings: map[docker.Port][]docker.PortBinding{
"8080/tcp": {{HostPort: "8080"}},
},
}

err = s.pool.RemoveContainerByName(headscaleHostname)
Expand All @@ -87,7 +92,9 @@ func (s *IntegrationCLITestSuite) SetupTest() {
fmt.Println("Created headscale container for CLI tests")

fmt.Println("Waiting for headscale to be ready for CLI tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
hostEndpoint := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8080/tcp"))

if err := s.pool.Retry(func() error {
url := fmt.Sprintf("http://%s/health", hostEndpoint)
Expand Down
34 changes: 30 additions & 4 deletions integration_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import (
)

const (
headscaleHostname = "headscale-derp"
headscaleNetwork = "headscale-test"
headscaleHostname = "headscale"
DOCKER_EXECUTE_TIMEOUT = 10 * time.Second
)

Expand All @@ -32,7 +33,7 @@ var (
tailscaleVersions = []string{
// "head",
// "unstable",
"1.30.0",
"1.30.2",
"1.28.0",
"1.26.2",
"1.24.2",
Expand Down Expand Up @@ -115,13 +116,19 @@ func ExecuteCommand(
fmt.Println("stdout: ", stdout.String())
fmt.Println("stderr: ", stderr.String())

return stdout.String(), stderr.String(), fmt.Errorf("command failed with: %s", stderr.String())
return stdout.String(), stderr.String(), fmt.Errorf(
"command failed with: %s",
stderr.String(),
)
}

return stdout.String(), stderr.String(), nil
case <-time.After(execConfig.timeout):

return stdout.String(), stderr.String(), fmt.Errorf("command timed out after %s", execConfig.timeout)
return stdout.String(), stderr.String(), fmt.Errorf(
"command timed out after %s",
execConfig.timeout,
)
}
}

Expand Down Expand Up @@ -316,3 +323,22 @@ func GetEnvBool(key string) (bool, error) {

return v, nil
}

func GetFirstOrCreateNetwork(pool *dockertest.Pool, name string) (dockertest.Network, error) {
networks, err := pool.NetworksByName(name)
if err != nil || len(networks) == 0 {

if _, err := pool.CreateNetwork(name); err == nil {
// Create does not give us an updated version of the resource, so we need to
// get it again.
networks, err := pool.NetworksByName(name)
if err != nil {
return dockertest.Network{}, err
}

return networks[0], nil
}
}

return networks[0], nil
}
44 changes: 29 additions & 15 deletions integration_embedded_derp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,20 @@ import (
)

const (
namespaceName = "derpnamespace"
totalContainers = 3
headscaleDerpHostname = "headscale-derp"
namespaceName = "derpnamespace"
totalContainers = 3
)

type IntegrationDERPTestSuite struct {
suite.Suite
stats *suite.SuiteInformation

pool dockertest.Pool
networks map[int]dockertest.Network // so we keep the containers isolated
headscale dockertest.Resource
saveLogs bool
pool dockertest.Pool
network dockertest.Network
containerNetworks map[int]dockertest.Network // so we keep the containers isolated
headscale dockertest.Resource
saveLogs bool

tailscales map[string]dockertest.Resource
joinWaitGroup sync.WaitGroup
Expand All @@ -53,7 +55,7 @@ func TestDERPIntegrationTestSuite(t *testing.T) {
s := new(IntegrationDERPTestSuite)

s.tailscales = make(map[string]dockertest.Resource)
s.networks = make(map[int]dockertest.Network)
s.containerNetworks = make(map[int]dockertest.Network)
s.saveLogs = saveLogs

suite.Run(t, s)
Expand All @@ -78,7 +80,7 @@ func TestDERPIntegrationTestSuite(t *testing.T) {
log.Printf("Could not purge resource: %s\n", err)
}

for _, network := range s.networks {
for _, network := range s.containerNetworks {
if err := network.Close(); err != nil {
log.Printf("Could not close network: %s\n", err)
}
Expand All @@ -93,9 +95,15 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
}

network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
if err != nil {
s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
}
s.network = network

for i := 0; i < totalContainers; i++ {
if pnetwork, err := s.pool.CreateNetwork(fmt.Sprintf("headscale-derp-%d", i)); err == nil {
s.networks[i] = *pnetwork
s.containerNetworks[i] = *pnetwork
} else {
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
}
Expand All @@ -112,22 +120,24 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
}

headscaleOptions := &dockertest.RunOptions{
Name: headscaleHostname,

Name: headscaleDerpHostname,
Mounts: []string{
fmt.Sprintf(
"%s/integration_test/etc_embedded_derp:/etc/headscale",
currentPath,
),
},
Cmd: []string{"headscale", "serve"},
Networks: []*dockertest.Network{&s.network},
ExposedPorts: []string{"8443/tcp", "3478/udp"},
PortBindings: map[docker.Port][]docker.PortBinding{
"8443/tcp": {{HostPort: "8443"}},
"3478/udp": {{HostPort: "3478"}},
},
}

err = s.pool.RemoveContainerByName(headscaleHostname)
err = s.pool.RemoveContainerByName(headscaleDerpHostname)
if err != nil {
s.FailNow(
fmt.Sprintf(
Expand All @@ -153,13 +163,15 @@ func (s *IntegrationDERPTestSuite) SetupSuite() {
hostname, container := s.tailscaleContainer(
fmt.Sprint(i),
version,
s.networks[i],
s.containerNetworks[i],
)
s.tailscales[hostname] = *container
}

log.Println("Waiting for headscale to be ready for embedded DERP tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8443/tcp"))
hostEndpoint := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8443/tcp"))

if err := s.pool.Retry(func() error {
url := fmt.Sprintf("https://%s/health", hostEndpoint)
Expand Down Expand Up @@ -320,7 +332,7 @@ func (s *IntegrationDERPTestSuite) TearDownSuite() {
log.Printf("Could not purge resource: %s\n", err)
}

for _, network := range s.networks {
for _, network := range s.containerNetworks {
if err := network.Close(); err != nil {
log.Printf("Could not close network: %s\n", err)
}
Expand Down Expand Up @@ -428,7 +440,9 @@ func (s *IntegrationDERPTestSuite) TestPingAllPeersByHostname() {
}

func (s *IntegrationDERPTestSuite) TestDERPSTUN() {
headscaleSTUNAddr := fmt.Sprintf("localhost:%s", s.headscale.GetPort("3478/udp"))
headscaleSTUNAddr := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("3478/udp"))
client := stun.NewClient()
client.SetVerbose(true)
client.SetVVerbose(true)
Expand Down
29 changes: 23 additions & 6 deletions integration_general_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ func (s *IntegrationTestSuite) tailscaleContainer(
},
}

err := s.pool.RemoveContainerByName(hostname)
if err != nil {
s.FailNow(
fmt.Sprintf(
"Could not remove existing container before building test: %s",
err,
),
"",
)
}

pts, err := s.pool.BuildAndRunWithBuildOptions(
tailscaleBuildOptions,
tailscaleOptions,
Expand Down Expand Up @@ -219,11 +230,11 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.FailNow(fmt.Sprintf("Could not connect to docker: %s", err), "")
}

if pnetwork, err := s.pool.CreateNetwork("headscale-test"); err == nil {
s.network = *pnetwork
} else {
s.FailNow(fmt.Sprintf("Could not create network: %s", err), "")
network, err := GetFirstOrCreateNetwork(&s.pool, headscaleNetwork)
if err != nil {
s.FailNow(fmt.Sprintf("Failed to create or get network: %s", err), "")
}
s.network = network

headscaleBuildOptions := &dockertest.BuildOptions{
Dockerfile: "Dockerfile",
Expand All @@ -236,10 +247,14 @@ func (s *IntegrationTestSuite) SetupSuite() {
}

headscaleOptions := &dockertest.RunOptions{
Name: "headscale",
Name: headscaleHostname,
Mounts: []string{
fmt.Sprintf("%s/integration_test/etc:/etc/headscale", currentPath),
},
ExposedPorts: []string{"8080/tcp"},
PortBindings: map[docker.Port][]docker.PortBinding{
"8080/tcp": {{HostPort: "8080"}},
},
Networks: []*dockertest.Network{&s.network},
Cmd: []string{"headscale", "serve"},
}
Expand Down Expand Up @@ -278,7 +293,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
}

log.Println("Waiting for headscale to be ready for core integration tests")
hostEndpoint := fmt.Sprintf("localhost:%s", s.headscale.GetPort("8080/tcp"))
hostEndpoint := fmt.Sprintf("%s:%s",
s.headscale.GetIPInNetwork(&s.network),
s.headscale.GetPort("8080/tcp"))

if err := s.pool.Retry(func() error {
url := fmt.Sprintf("http://%s/health", hostEndpoint)
Expand Down
Loading