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

testutil/compose: add unit tests #630

Merged
merged 5 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions testutil/compose/compose/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package main

import (
"context"
"encoding/json"
"fmt"
"io"
"net"
Expand All @@ -30,14 +31,14 @@ import (
)

// startAlertCollector starts a server that accepts alert webhooks until the context is closed and returns
// a channel on which the received webhooks will be sent.
func startAlertCollector(ctx context.Context, port int) (chan []byte, error) {
// a channel on which the received alert titles will be sent.
func startAlertCollector(ctx context.Context, port int) (chan string, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is going to be refactored away in the next sprint. Move from push Grafana alerts to pull prometheus alerts for the unit tests.

l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
return nil, errors.Wrap(err, "new listener")
}

bodies := make(chan []byte)
resp := make(chan string, 100)
server := http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
Expand All @@ -48,9 +49,28 @@ func startAlertCollector(ctx context.Context, port int) (chan []byte, error) {
return
}

log.Info(ctx, "Received webhook", z.Str("body", string(b)))
wrapper := struct {
Body string `json:"body"`
}{}
if err := json.Unmarshal(b, &wrapper); err != nil {
log.Error(ctx, "Unmarshal body wrapper", err, z.Str("body", string(b)))
return
}

alert := struct {
Title string `json:"title"`
}{}
if err := json.Unmarshal(b, &alert); err != nil {
log.Error(ctx, "Unmarshal alert", err, z.Str("body", string(b)))
return
} else if alert.Title == "" {
log.Error(ctx, "Alert title empty", err, z.Str("body", string(b)))
return
}

log.Info(ctx, "Received webhook", z.Str("body", string(b)), z.Str("title", alert.Title))

bodies <- b
resp <- alert.Title
}),
}

Expand All @@ -66,8 +86,8 @@ func startAlertCollector(ctx context.Context, port int) (chan []byte, error) {
if err := eg.Wait(); !errors.Is(err, context.Canceled) && !errors.Is(err, http.ErrServerClosed) {
log.Error(ctx, "Alert collector", err)
}
close(bodies)
close(resp)
}()

return bodies, nil
return resp, nil
}
20 changes: 16 additions & 4 deletions testutil/compose/compose/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func newRootCmd() *cobra.Command {

root.AddCommand(newNewCmd())
root.AddCommand(newCleanCmd())
root.AddCommand(newAutoCmd())
root.AddCommand(newAutoCmd(nil))
root.AddCommand(newDockerCmd(
"define",
"Creates a docker-compose.yml that executes `charon create dkg` if keygen==dkg",
Expand Down Expand Up @@ -123,7 +123,7 @@ func newDockerCmd(use string, short string, runFunc runFunc) *cobra.Command {
return cmd
}

func newAutoCmd() *cobra.Command {
func newAutoCmd(tmplCallback func(data compose.TmplData)) *cobra.Command {
cmd := &cobra.Command{
Use: "auto",
Short: "Convenience function that runs `compose define && compose lock && compose run`",
Expand All @@ -141,15 +141,27 @@ func newAutoCmd() *cobra.Command {

rootCtx := log.WithTopic(cmd.Context(), "auto")

var lastTmpl compose.TmplData
for _, runFunc := range runFuncs {
_, err := runFunc(rootCtx)
lastTmpl, err = runFunc(rootCtx)
if err != nil {
return err
}
}

if tmplCallback != nil {
tmplCallback(lastTmpl)
err := compose.WriteDockerCompose(*dir, lastTmpl)
if err != nil {
return err
}
}

ctx := rootCtx
if *alertTimeout != 0 {
// Ensure everything is clean before we start with alert test.
_ = execDown(rootCtx, *dir)

var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(rootCtx, *alertTimeout)
defer cancel()
Expand All @@ -170,7 +182,7 @@ func newAutoCmd() *cobra.Command {

var fail bool
for alert := range alerts {
log.Error(rootCtx, "Received alert", nil, z.Str("alert", string(alert)))
log.Error(rootCtx, "Received alert", nil, z.Str("alert", alert))
fail = true
}
if fail {
Expand Down
120 changes: 120 additions & 0 deletions testutil/compose/compose/smoke_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright © 2022 Obol Labs Inc.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <http://www.gnu.org/licenses/>.

package main

import (
"context"
"flag"
"os"
"testing"

"github.com/stretchr/testify/require"

"github.com/obolnetwork/charon/testutil/compose"
)

//go:generate go test . -run=TestSmoke -integration -v
var integration = flag.Bool("integration", false, "Enable docker based integration test")

func TestSmoke(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are the unit tests

Copy link
Contributor

Choose a reason for hiding this comment

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

Unit tests of smoke tests 🙃

if !*integration {
t.Skip("Skipping smoke integration test")
}

tests := []struct {
Name string
ConfigFunc func(*compose.Config)
TmplFunc func(compose.TmplData)
}{
{
Name: "default alpha",
ConfigFunc: func(conf *compose.Config) {
conf.KeyGen = compose.KeyGenCreate
conf.FeatureSet = "alpha"
},
},
{
Name: "default beta",
ConfigFunc: func(conf *compose.Config) {
conf.KeyGen = compose.KeyGenCreate
conf.FeatureSet = "beta"
},
},
{
Name: "default stable",
ConfigFunc: func(conf *compose.Config) {
conf.KeyGen = compose.KeyGenCreate
conf.FeatureSet = "stable"
},
},
{
Name: "dkg",
ConfigFunc: func(conf *compose.Config) {
conf.KeyGen = compose.KeyGenDKG
},
},
{
Name: "very large dkg",
ConfigFunc: func(conf *compose.Config) {
conf.NumNodes = 21
conf.Threshold = 14
conf.NumValidators = 1000
conf.KeyGen = compose.KeyGenDKG
},
},
{
Name: "version matrix",
TmplFunc: func(data compose.TmplData) {
data.Nodes[0].ImageTag = "latest"
data.Nodes[1].ImageTag = "latest"
data.Nodes[2].ImageTag = "v0.5.0" // TODO(corver): Update this with new releases.
data.Nodes[3].ImageTag = "v0.5.0"
},
},
{
Name: "teku versions", // TODO(corver): Do the same for lighthouse.
ConfigFunc: func(conf *compose.Config) {
conf.VCs = []compose.VCType{compose.VCTeku}
},
TmplFunc: func(data compose.TmplData) {
data.VCs[0].Image = "consensys/teku:latest"
data.VCs[1].Image = "consensys/teku:22.5"
data.VCs[2].Image = "consensys/teku:22.4"
data.VCs[3].Image = "consensys/teku:22.3"
},
},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
dir, err := os.MkdirTemp("", "")
require.NoError(t, err)

conf := compose.NewDefaultConfig()
if test.ConfigFunc != nil {
test.ConfigFunc(&conf)
}
require.NoError(t, compose.WriteConfig(dir, conf))

cmd := newAutoCmd(test.TmplFunc)
require.NoError(t, cmd.Flags().Set("compose-dir", dir))
require.NoError(t, cmd.Flags().Set("alert-timeout", "30s"))

err = cmd.ExecuteContext(context.Background())
require.NoError(t, err)
})
}
}
10 changes: 5 additions & 5 deletions testutil/compose/compose_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,30 @@ func TestDockerCompose(t *testing.T) {
{
Name: "define dkg",
ConfFunc: func(conf *Config) {
conf.KeyGen = keyGenDKG
conf.KeyGen = KeyGenDKG
},
RunFunc: Define,
},
{
Name: "define create",
ConfFunc: func(conf *Config) {
conf.KeyGen = keyGenCreate
conf.KeyGen = KeyGenCreate
},
RunFunc: Define,
},
{
Name: "lock dkg",
ConfFunc: func(conf *Config) {
conf.Step = stepDefined
conf.KeyGen = keyGenDKG
conf.KeyGen = KeyGenDKG
},
RunFunc: Lock,
},
{
Name: "lock create",
ConfFunc: func(conf *Config) {
conf.Step = stepDefined
conf.KeyGen = keyGenCreate
conf.KeyGen = KeyGenCreate
},
RunFunc: Lock,
},
Expand Down Expand Up @@ -110,6 +110,6 @@ func TestParseTemplate(t *testing.T) {
_, err := template.New("").Parse(string(tmpl))
require.NoError(t, err)

_, err = getVC(vcTeku, 0, 1)
_, err = getVC(VCTeku, 0, 1)
require.NoError(t, err)
}
20 changes: 10 additions & 10 deletions testutil/compose/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const (
configFile = "config.json"
defaultImageTag = "latest"
defaultBeaconNode = "mock"
defaultKeyGen = keyGenCreate
defaultKeyGen = KeyGenCreate
defaultNumVals = 1
defaultNumNodes = 4
defaultThreshold = 3
Expand All @@ -35,21 +35,21 @@ const (
cmdCreateDKG = "[create,dkg]"
)

// vcType defines a validator client type.
type vcType string
// VCType defines a validator client type.
type VCType string

const (
vcMock vcType = "mock"
vcTeku vcType = "teku"
vcLighthouse vcType = "lighthouse"
VCMock VCType = "mock"
VCTeku VCType = "teku"
VCLighthouse VCType = "lighthouse"
)

// KeyGen defines a key generation process.
type KeyGen string

const (
keyGenDKG KeyGen = "dkg"
keyGenCreate KeyGen = "create"
KeyGenDKG KeyGen = "dkg"
KeyGenCreate KeyGen = "create"
)

// step defines the current completed compose step.
Expand Down Expand Up @@ -94,7 +94,7 @@ type Config struct {
BeaconNode string `json:"beacon_node"`

// VCs define the types of validator clients to use.
VCs []vcType `json:"validator_clients"`
VCs []VCType `json:"validator_clients"`

// FeatureSet defines the minimum feature set to enable.
FeatureSet string `json:"feature_set"`
Expand All @@ -117,7 +117,7 @@ func NewDefaultConfig() Config {
Threshold: defaultThreshold,
NumValidators: defaultNumVals,
ImageTag: defaultImageTag,
VCs: []vcType{vcTeku, vcLighthouse, vcMock},
VCs: []VCType{VCTeku, VCLighthouse, VCMock},
KeyGen: defaultKeyGen,
BeaconNode: defaultBeaconNode,
Step: stepNew,
Expand Down
8 changes: 4 additions & 4 deletions testutil/compose/define.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func Define(ctx context.Context, dir string, conf Config) (TmplData, error) {
}

var data TmplData
if conf.KeyGen == keyGenDKG {
if conf.KeyGen == KeyGenDKG {
log.Info(ctx, "Creating node*/p2pkey for ENRs required for charon create dkg")

// charon create dkg requires operator ENRs, so we need to create p2pkeys now.
Expand Down Expand Up @@ -172,7 +172,7 @@ func Define(ctx context.Context, dir string, conf Config) (TmplData, error) {
log.Info(ctx, "Creating config.json")

conf.Step = stepDefined
if err := writeConfig(dir, conf); err != nil {
if err := WriteConfig(dir, conf); err != nil {
return TmplData{}, err
}

Expand Down Expand Up @@ -349,8 +349,8 @@ func nodeFile(dir string, i int, file string) string {
return path.Join(dir, fmt.Sprintf("node%d", i), file)
}

// writeConfig writes the config as yaml to disk.
func writeConfig(dir string, conf Config) error {
// WriteConfig writes the config as yaml to disk.
func WriteConfig(dir string, conf Config) error {
b, err := json.MarshalIndent(conf, "", " ")
if err != nil {
return errors.Wrap(err, "marshal config")
Expand Down
6 changes: 5 additions & 1 deletion testutil/compose/docker-compose.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ services:
{{- range $i, $node := .Nodes}}
node{{$i}}:
<<: *node-base
{{if .ImageTag}}image: ghcr.io/obolnetwork/charon:{{.ImageTag}}
{{end -}}
{{- if .EnvVars}}
environment:
{{- range $node.EnvVars}}
Expand Down Expand Up @@ -72,8 +74,10 @@ services:
volumes:
- ./grafana/datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml
- ./grafana/dashboards.yml:/etc/grafana/provisioning/dashboards/datasource.yml
- ./grafana/notifiers.yml:/etc/grafana/provisioning/notifiers/notifiers.yml
- ./grafana/grafana.ini:/etc/grafana/grafana.ini:ro
- ./grafana/simnet_dash.json:/etc/dashboards/simnet_dash.json
- ./grafana/dash_simnet.json:/etc/dashboards/dash_simnet.json
- ./grafana/dash_alerts.json:/etc/dashboards/dash_alerts.json

jaeger:
image: jaegertracing/all-in-one:latest
Expand Down
Loading