mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 12:51:55 +00:00
sg: create a test command to run e2e tests locally (#34627)
This commit is contained in:
parent
a2d1fd474d
commit
284aba024b
@ -2,12 +2,16 @@ package run
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
secretmanager "cloud.google.com/go/secretmanager/apiv1"
|
||||
"golang.org/x/sync/errgroup"
|
||||
secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/dev/sg/internal/stdout"
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
"github.com/sourcegraph/sourcegraph/lib/output"
|
||||
"github.com/sourcegraph/sourcegraph/lib/process"
|
||||
)
|
||||
@ -23,11 +27,20 @@ type Command struct {
|
||||
IgnoreStderr bool `yaml:"ignoreStderr"`
|
||||
DefaultArgs string `yaml:"defaultArgs"`
|
||||
ContinueWatchOnExit bool `yaml:"continueWatchOnExit"`
|
||||
Secrets map[string]Secret `yaml:"secrets"`
|
||||
// Preamble is a short and visible message, displayed when the command is launched.
|
||||
Preamble string `yaml:"preamble"`
|
||||
|
||||
// ATTENTION: If you add a new field here, be sure to also handle that
|
||||
// field in `Merge` (below).
|
||||
}
|
||||
|
||||
type Secret struct {
|
||||
Provider string `yaml:"provider"`
|
||||
Project string `yaml:"project"`
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
func (c Command) Merge(other Command) Command {
|
||||
merged := c
|
||||
|
||||
@ -49,12 +62,19 @@ func (c Command) Merge(other Command) Command {
|
||||
if other.DefaultArgs != merged.DefaultArgs && other.DefaultArgs != "" {
|
||||
merged.DefaultArgs = other.DefaultArgs
|
||||
}
|
||||
if other.Preamble != merged.Preamble && other.Preamble != "" {
|
||||
merged.Preamble = other.Preamble
|
||||
}
|
||||
merged.ContinueWatchOnExit = other.ContinueWatchOnExit || merged.ContinueWatchOnExit
|
||||
|
||||
for k, v := range other.Env {
|
||||
merged.Env[k] = v
|
||||
}
|
||||
|
||||
for k, v := range other.Secrets {
|
||||
merged.Secrets[k] = v
|
||||
}
|
||||
|
||||
if !equal(merged.Watch, other.Watch) && len(other.Watch) != 0 {
|
||||
merged.Watch = other.Watch
|
||||
}
|
||||
@ -110,7 +130,31 @@ func (sc *startedCmd) CapturedStderr() string {
|
||||
return string(sc.stderrBuf.Bytes())
|
||||
}
|
||||
|
||||
func startCmd(ctx context.Context, dir string, cmd Command, globalEnv map[string]string) (*startedCmd, error) {
|
||||
func getSecrets(ctx context.Context, cmd Command) (map[string]string, error) {
|
||||
secretsEnv := map[string]string{}
|
||||
|
||||
client, err := secretmanager.NewClient(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to create secretmanager client: %v", err)
|
||||
}
|
||||
for envName, secret := range cmd.Secrets {
|
||||
if secret.Provider != "gcloud" {
|
||||
errors.Newf("Unknown secrets provider %s", secret.Provider)
|
||||
}
|
||||
path := fmt.Sprintf("projects/%s/secrets/%s/versions/latest", secret.Project, secret.Name)
|
||||
req := &secretmanagerpb.AccessSecretVersionRequest{
|
||||
Name: path,
|
||||
}
|
||||
result, err := client.AccessSecretVersion(ctx, req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to access secret %s from %s", secret.Name, secret.Project)
|
||||
}
|
||||
secretsEnv[envName] = string(result.Payload.Data)
|
||||
}
|
||||
return secretsEnv, nil
|
||||
}
|
||||
|
||||
func startCmd(ctx context.Context, dir string, cmd Command, parentEnv map[string]string) (*startedCmd, error) {
|
||||
sc := &startedCmd{
|
||||
stdoutBuf: &prefixSuffixSaver{N: 32 << 10},
|
||||
stderrBuf: &prefixSuffixSaver{N: 32 << 10},
|
||||
@ -121,7 +165,13 @@ func startCmd(ctx context.Context, dir string, cmd Command, globalEnv map[string
|
||||
|
||||
sc.Cmd = exec.CommandContext(commandCtx, "bash", "-c", cmd.Cmd)
|
||||
sc.Cmd.Dir = dir
|
||||
sc.Cmd.Env = makeEnv(globalEnv, cmd.Env)
|
||||
|
||||
secretsEnv, err := getSecrets(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot fetch secrets")
|
||||
}
|
||||
|
||||
sc.Cmd.Env = makeEnv(cmd.Env, secretsEnv, parentEnv)
|
||||
|
||||
var stdoutWriter, stderrWriter io.Writer
|
||||
logger := newCmdLogger(commandCtx, cmd.Name, stdout.Out)
|
||||
@ -138,6 +188,9 @@ func startCmd(ctx context.Context, dir string, cmd Command, globalEnv map[string
|
||||
stderrWriter = io.MultiWriter(logger, sc.stderrBuf)
|
||||
}
|
||||
|
||||
if cmd.Preamble != "" {
|
||||
stdout.Out.WriteLine(output.Linef("", output.StyleOrange, "[%s] %s %s", cmd.Name, output.EmojiInfo, cmd.Preamble))
|
||||
}
|
||||
eg, err := process.PipeOutputUnbuffered(ctx, sc.Cmd, stdoutWriter, stderrWriter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -21,7 +21,7 @@ import (
|
||||
"github.com/sourcegraph/sourcegraph/lib/output"
|
||||
)
|
||||
|
||||
func Commands(ctx context.Context, globalEnv map[string]string, verbose bool, cmds ...Command) error {
|
||||
func Commands(ctx context.Context, parentEnv map[string]string, verbose bool, cmds ...Command) error {
|
||||
chs := make([]<-chan struct{}, 0, len(cmds))
|
||||
monitor := &changeMonitor{}
|
||||
for _, cmd := range cmds {
|
||||
@ -58,7 +58,7 @@ func Commands(ctx context.Context, globalEnv map[string]string, verbose bool, cm
|
||||
defer wg.Done()
|
||||
var err error
|
||||
for first := true; cmd.ContinueWatchOnExit || first; first = false {
|
||||
if err = runWatch(ctx, cmd, root, globalEnv, ch, verbose, installed, okayToStart); err != nil {
|
||||
if err = runWatch(ctx, cmd, root, parentEnv, ch, verbose, installed, okayToStart); err != nil {
|
||||
if errors.Is(err, ctx.Err()) { // if error caused by context, terminate
|
||||
return
|
||||
}
|
||||
@ -275,7 +275,7 @@ func runWatch(
|
||||
ctx context.Context,
|
||||
cmd Command,
|
||||
root string,
|
||||
globalEnv map[string]string,
|
||||
parentEnv map[string]string,
|
||||
reload <-chan struct{},
|
||||
verbose bool,
|
||||
installDone chan string,
|
||||
@ -312,7 +312,7 @@ func runWatch(
|
||||
stdout.Out.WriteLine(output.Linef("", output.StylePending, "Installing %s...", cmd.Name))
|
||||
}
|
||||
|
||||
cmdOut, err := BashInRoot(ctx, cmd.Install, makeEnv(globalEnv, cmd.Env))
|
||||
cmdOut, err := BashInRoot(ctx, cmd.Install, makeEnv(cmd.Env, parentEnv))
|
||||
if err != nil {
|
||||
if !startedOnce {
|
||||
return installErr{cmdName: cmd.Name, output: cmdOut, originalErr: err}
|
||||
@ -363,7 +363,7 @@ func runWatch(
|
||||
// Run it
|
||||
stdout.Out.WriteLine(output.Linef("", output.StylePending, "Running %s...", cmd.Name))
|
||||
|
||||
sc, err := startCmd(ctx, root, cmd, globalEnv)
|
||||
sc, err := startCmd(ctx, root, cmd, parentEnv)
|
||||
defer sc.cancel()
|
||||
|
||||
if err != nil {
|
||||
@ -418,6 +418,9 @@ func runWatch(
|
||||
}
|
||||
}
|
||||
|
||||
// makeEnv merges environments starting from the left, meaning the first environment will be overriden by the second one, skipping
|
||||
// any key that has been explicitly defined in the current environment of this process. This enables users to manually overrides
|
||||
// environment variables explictly, i.e FOO=1 sg start will have FOO=1 set even if a command or commandset sets FOO.
|
||||
func makeEnv(envs ...map[string]string) []string {
|
||||
combined := os.Environ()
|
||||
expandedEnv := map[string]string{}
|
||||
@ -561,7 +564,7 @@ func watch() (<-chan string, error) {
|
||||
return paths, nil
|
||||
}
|
||||
|
||||
func Test(ctx context.Context, cmd Command, args []string, globalEnv map[string]string) error {
|
||||
func Test(ctx context.Context, cmd Command, args []string, parentEnv map[string]string) error {
|
||||
root, err := root.RepositoryRoot()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -581,9 +584,18 @@ func Test(ctx context.Context, cmd Command, args []string, globalEnv map[string]
|
||||
cmdArgs = append(cmdArgs, cmd.DefaultArgs)
|
||||
}
|
||||
|
||||
secretsEnv, err := getSecrets(ctx, cmd)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot fetch secrets")
|
||||
}
|
||||
|
||||
if cmd.Preamble != "" {
|
||||
stdout.Out.WriteLine(output.Linef("", output.StyleOrange, "[%s] %s %s", cmd.Name, output.EmojiInfo, cmd.Preamble))
|
||||
}
|
||||
|
||||
c := exec.CommandContext(commandCtx, "bash", "-c", strings.Join(cmdArgs, " "))
|
||||
c.Dir = root
|
||||
c.Env = makeEnv(globalEnv, cmd.Env)
|
||||
c.Env = makeEnv(parentEnv, secretsEnv, cmd.Env)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
|
||||
|
||||
3
go.mod
3
go.mod
@ -7,6 +7,7 @@ require (
|
||||
cloud.google.com/go/monitoring v1.2.0
|
||||
cloud.google.com/go/profiler v0.2.0
|
||||
cloud.google.com/go/pubsub v1.17.1
|
||||
cloud.google.com/go/secretmanager v1.4.0
|
||||
cloud.google.com/go/storage v1.19.0
|
||||
github.com/BurntSushi/toml v0.4.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
@ -185,7 +186,7 @@ require (
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.5.0 // indirect
|
||||
cloud.google.com/go/iam v0.1.1 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.35.1 // indirect
|
||||
github.com/DataDog/datadog-go v4.8.3+incompatible // indirect
|
||||
github.com/DataDog/datadog-go/v5 v5.1.0 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@ -51,8 +51,9 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/iam v0.1.1 h1:4CapQyNFjiksks1/x7jsvsygFPhihslYk5GptIrlX68=
|
||||
cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw=
|
||||
cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
|
||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
||||
cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0=
|
||||
cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM=
|
||||
cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI=
|
||||
@ -67,6 +68,8 @@ cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjp
|
||||
cloud.google.com/go/pubsub v1.4.0/go.mod h1:LFrqilwgdw4X2cJS9ALgzYmMu+ULyrUN6IHV3CPK4TM=
|
||||
cloud.google.com/go/pubsub v1.17.1 h1:s2UGTTphpnUQ0Wppkp2OprR4pS3nlBpPvyL2GV9cqdc=
|
||||
cloud.google.com/go/pubsub v1.17.1/go.mod h1:4qDxMr1WsM9+aQAz36ltDwCIM+R0QdlseyFjBuNvnss=
|
||||
cloud.google.com/go/secretmanager v1.4.0 h1:Cl+kDYvKHjPQ1l2DZDr2FG/cXUzNGCZkh05BARgddo8=
|
||||
cloud.google.com/go/secretmanager v1.4.0/go.mod h1:h2VZz7Svt1W9/YVl7mfcX9LddvS6SOLOvMoOXBhYT1k=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
@ -3211,6 +3214,7 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2
|
||||
google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
|
||||
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac h1:qSNTkEN+L2mvWcLgJOR+8bdHX9rN/IdU3A1Ghpfb1Rg=
|
||||
google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo=
|
||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
|
||||
@ -749,6 +749,13 @@ commandsets:
|
||||
- zoekt-webserver-0
|
||||
- zoekt-webserver-1
|
||||
|
||||
enterprise-e2e:
|
||||
<<: *enterprise_set
|
||||
env:
|
||||
# EXTSVC_CONFIG_FILE being set prevents the e2e test suite to add
|
||||
# additional connections.
|
||||
EXTSVC_CONFIG_FILE: ""
|
||||
|
||||
dotcom:
|
||||
<<: *enterprise_set
|
||||
env:
|
||||
@ -960,7 +967,23 @@ tests:
|
||||
cmd: yarn run jest --testPathIgnorePatterns end-to-end regression integration storybook
|
||||
|
||||
frontend-e2e:
|
||||
cmd: yarn run test-e2e
|
||||
preamble: |
|
||||
sg start enterprise-e2e must be already running for these tests to work.
|
||||
|
||||
If you run into authentication issues, you can run the following commands to fix them:
|
||||
sg db reset-pg && sg db add-user -name test@sourcegraph.com -password supersecurepassword
|
||||
cmd: |
|
||||
yarn run test-e2e
|
||||
env:
|
||||
TEST_USER_EMAIL: test@sourcegraph.com
|
||||
TEST_USER_PASSWORD: supersecurepassword
|
||||
SOURCEGRAPH_BASE_URL: https://sourcegraph.test:3443
|
||||
BROWSER: chrome
|
||||
secrets:
|
||||
GH_TOKEN:
|
||||
provider: "gcloud"
|
||||
project: "sourcegraph-ci"
|
||||
name: "BUILDKITE_GITHUBDOTCOM_TOKEN"
|
||||
|
||||
docsite:
|
||||
cmd: .bin/docsite_${DOCSITE_VERSION} check ./doc
|
||||
|
||||
Loading…
Reference in New Issue
Block a user