this repo has no description
at master 207 lines 7.8 kB view raw
1// Copyright 2023 The CUE Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package e2e_test 16 17import ( 18 "bytes" 19 "cmp" 20 cryptorand "crypto/rand" 21 "fmt" 22 "os" 23 "os/exec" 24 "path" 25 "path/filepath" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/rogpeppe/go-internal/testscript" 31) 32 33func TestMain(m *testing.M) { 34 cachedGobin := os.Getenv("CUE_CACHED_GOBIN") 35 if cachedGobin == "" { 36 // Install the cmd/cue version into a cached GOBIN so we can reuse it. 37 // TODO: use "go tool cue" once we can rely on Go's tool dependency tracking in go.mod. 38 // See: https://go.dev/issue/48429 39 cacheDir, err := os.UserCacheDir() 40 if err != nil { 41 panic(err) 42 } 43 cachedGobin = filepath.Join(cacheDir, "cue-e2e-gobin") 44 cmd := exec.Command("go", "install", "cuelang.org/go/cmd/cue") 45 cmd.Env = append(cmd.Environ(), "GOBIN="+cachedGobin) 46 out, err := cmd.CombinedOutput() 47 if err != nil { 48 panic(fmt.Errorf("%v: %s", err, out)) 49 } 50 os.Setenv("CUE_CACHED_GOBIN", cachedGobin) 51 } 52 53 testscript.Main(m, map[string]func(){ 54 "cue": func() { 55 // Note that we could avoid this wrapper entirely by setting PATH, 56 // since TestMain sets up a single cue binary in a GOBIN directory, 57 // but that may change at any point, or we might just switch to "go tool cue". 58 cmd := exec.Command(filepath.Join(cachedGobin, "cue"), os.Args[1:]...) 59 cmd.Stdin = os.Stdin 60 cmd.Stdout = os.Stdout 61 cmd.Stderr = os.Stderr 62 if err := cmd.Run(); err != nil { 63 if err, ok := err.(*exec.ExitError); ok { 64 os.Exit(err.ExitCode()) 65 } 66 fmt.Fprintln(os.Stderr, err) 67 os.Exit(1) 68 } 69 }, 70 }) 71} 72 73var ( 74 // githubPublicRepo is a GitHub public repository 75 // with the "cue.works authz" GitHub App installed. 76 // The repository can be entirely empty, as it's only needed for authz. 77 githubPublicRepo = cmp.Or(os.Getenv("GITHUB_PUBLIC_REPO"), "github.com/cue-labs-modules-testing/e2e-public") 78 79 // githubPublicRepo is a GitHub private repository 80 // with the "cue.works authz" GitHub App installed. 81 // The repository can be entirely empty, as it's only needed for authz. 82 githubPrivateRepo = cmp.Or(os.Getenv("GITHUB_PRIVATE_REPO"), "github.com/cue-labs-modules-testing/e2e-private") 83 84 // gcloudRegistry is an existing Google Cloud Artifact Registry repository 85 // to publish module versions to via "cue mod publish", 86 // and authenticated via gcloud's configuration in the host environment. 87 gcloudRegistry = cmp.Or(os.Getenv("GCLOUD_REGISTRY"), "europe-west1-docker.pkg.dev/project-unity-377819/modules-e2e-registry") 88) 89 90func TestScript(t *testing.T) { 91 p := testscript.Params{ 92 Dir: filepath.Join("testdata", "script"), 93 RequireExplicitExec: true, 94 RequireUniqueNames: true, 95 Setup: func(env *testscript.Env) error { 96 env.Setenv("CUE_CACHED_GOBIN", os.Getenv("CUE_CACHED_GOBIN")) 97 98 // Just like cmd/cue/cmd.TestScript, set up separate cache and config dirs per test. 99 env.Setenv("CUE_CACHE_DIR", filepath.Join(env.WorkDir, "tmp/cachedir")) 100 configDir := filepath.Join(env.WorkDir, "tmp/configdir") 101 env.Setenv("CUE_CONFIG_DIR", configDir) 102 103 // CUE_TEST_TOKEN is a secret used by the scripts publishing to registry.cue.works. 104 // When unset, those tests would fail with an auth error. 105 if token := os.Getenv("CUE_TEST_TOKEN"); token != "" { 106 cmd := exec.Command("cue", "login", "--token", token) 107 cmd.Env = env.Vars // store the token in the CUE_CONFIG_DIR we just set 108 if out, err := cmd.CombinedOutput(); err != nil { 109 return fmt.Errorf("%v: %s", err, out) 110 } 111 } 112 return nil 113 }, 114 Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ 115 // github-repo-module sets $MODULE to a unique nested module under the given repository path. 116 "github-repo-module": func(ts *testscript.TestScript, neg bool, args []string) { 117 if neg || len(args) != 1 { 118 ts.Fatalf("usage: with-github-repo <public|private>") 119 } 120 moduleName := testModuleName(ts) 121 var repo string 122 switch args[0] { 123 case "public": 124 repo = githubPublicRepo 125 case "private": 126 repo = githubPrivateRepo 127 default: 128 ts.Fatalf("usage: with-github-repo <public|private>") 129 } 130 module := path.Join(repo, moduleName) 131 ts.Setenv("MODULE", module) 132 ts.Logf("using module path %s", module) 133 }, 134 // env-fill rewrites its argument files to replace any environment variable 135 // references with their values, using the same algorithm as cmpenv. 136 "env-fill": func(ts *testscript.TestScript, neg bool, args []string) { 137 if neg || len(args) == 0 { 138 ts.Fatalf("usage: env-fill args...") 139 } 140 for _, arg := range args { 141 path := ts.MkAbs(arg) 142 data := ts.ReadFile(path) 143 data = tsExpand(ts, data) 144 ts.Check(os.WriteFile(path, []byte(data), 0o666)) 145 } 146 }, 147 // gcloud-auth-docker configures gcloud so that it uses the host's existing configuration, 148 // and sets CUE_REGISTRY and CUE_REGISTRY_HOST according to gcloudRegistry. 149 "gcloud-auth-docker": func(ts *testscript.TestScript, neg bool, args []string) { 150 if neg || len(args) > 0 { 151 ts.Fatalf("usage: gcloud-auth-docker") 152 } 153 // The test script needs to be able to run gcloud as a docker credential helper. 154 // gcloud will be accessible via $PATH without issue, but it needs to use its host config, 155 // so we pass it along as $CLOUDSDK_CONFIG to not share the host's entire $HOME. 156 // 157 // We assume that the host already has gcloud authorized to upload OCI artifacts, 158 // via either a user account (gcloud auth login) or a service account key (gcloud auth activate-service-account). 159 gcloudConfigPath, err := exec.Command("gcloud", "info", "--format=value(config.paths.global_config_dir)").Output() 160 ts.Check(err) 161 ts.Setenv("CLOUDSDK_CONFIG", string(bytes.TrimSpace(gcloudConfigPath))) 162 163 // The module path can be anything we want in this case, 164 // but we might as well make it unique and realistic. 165 ts.Setenv("MODULE", "domain.test/"+testModuleName(ts)) 166 167 ts.Setenv("CUE_REGISTRY", gcloudRegistry) 168 // TODO: reuse internal/mod/modresolve.parseRegistry, returning a Location with Host. 169 gcloudRegistryHost, _, _ := strings.Cut(gcloudRegistry, "/") 170 ts.Setenv("CUE_REGISTRY_HOST", gcloudRegistryHost) 171 }, 172 }, 173 } 174 testscript.Run(t, p) 175} 176 177func addr[T any](t T) *T { return &t } 178 179func envMust(t *testing.T, name string) string { 180 if s := os.Getenv(name); s != "" { 181 return s 182 } 183 t.Fatalf("%s must be set", name) 184 return "" 185} 186 187func tsExpand(ts *testscript.TestScript, s string) string { 188 return os.Expand(s, func(key string) string { 189 return ts.Getenv(key) 190 }) 191} 192 193// testModuleName creates a unique string without any slashes 194// which can be used as the base name for a module path to publish. 195// 196// It has three components: 197// "e2e" with the test name as a prefix, to spot which test created it, 198// a timestamp in seconds, to get an idea of when the test was run, 199// and a short random suffix to avoid timing collisions between machines. 200func testModuleName(ts *testscript.TestScript) string { 201 var randomTrailer [3]byte 202 if _, err := cryptorand.Read(randomTrailer[:]); err != nil { 203 panic(err) // should typically not happen 204 } 205 return fmt.Sprintf("%s-%s-%x", ts.Name(), 206 time.Now().UTC().Format("2006.01.02-15.04.05"), randomTrailer) 207}