| // Copyright 2024 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| package task | |
| import ( | |
| "archive/zip" | |
| "bytes" | |
| "context" | |
| "fmt" | |
| "io" | |
| "path/filepath" | |
| "regexp" | |
| "strings" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/google/go-cmp/cmp/cmpopts" | |
| "golang.org/x/build/internal/workflowtest" | |
| ) | |
| func TestVSCodeGoReleaseTask_buildVSCGO(t *testing.T) { | |
| mustHaveShell(t) | |
| tests := []struct { | |
| name string | |
| revision string | |
| wantBuildError bool | |
| }{ | |
| { | |
| name: "success", | |
| revision: "release", | |
| }, | |
| { | |
| name: "broken build", | |
| revision: "master", // broken | |
| wantBuildError: true, | |
| }, | |
| } | |
| for _, test := range tests { | |
| workflowtest.Subtest(t, test.name, func(t *testing.T) { | |
| repo := NewFakeRepo(t, "vscode-go") | |
| t1 := repo.Commit(map[string]string{ | |
| "go.mod": "module github.com/golang/vscode-go\n", | |
| "go.sum": "\n", | |
| "vscgo/main.go": "package main\nfunc main() { println(\"v1\") }\n", | |
| }) | |
| repo.Branch("release", t1) | |
| _ = repo.Commit(map[string]string{ | |
| "vscgo/broken.go": "package broken\n", // broken at master | |
| }) | |
| gerrit := NewFakeGerrit(t, repo) | |
| scratchDir := t.TempDir() | |
| // fakeGo will record the invocation command in the fake output. | |
| const fakeGo = `#!/bin/bash -eu | |
| function find_output_value() { | |
| # returns -o flag value. | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -o) | |
| if [[ $# -gt 1 ]]; then | |
| echo "$2" | |
| shift 2 | |
| else | |
| echo "Error: No argument provided after '-o'" | |
| exit 1 | |
| fi | |
| ;; | |
| *) | |
| shift | |
| ;; | |
| esac | |
| done | |
| } | |
| case "$1" in | |
| "build") | |
| if [[ -f "vscgo/broken.go" ]]; then | |
| echo build broken | |
| exit 1 | |
| fi | |
| out=$(find_output_value "$@") | |
| echo "GOOS=${GOOS} GOARCH=${GOARCH} $0 $@" > "${out}" | |
| exit 0 | |
| ;; | |
| *) | |
| echo unexpected command $@ | |
| ;; | |
| esac | |
| ` | |
| releaseTask := &VSCodeGoReleaseTask{ | |
| CloudBuild: NewFakeCloudBuild(t, gerrit, "vscode-go", nil, FakeBinary{Name: "go", Implementation: fakeGo}), | |
| ScratchFS: &ScratchFS{BaseURL: "file://" + scratchDir}, | |
| Revision: test.revision, | |
| } | |
| wd := releaseTask.NewDefinition() | |
| w := workflowtest.Start(t, wd, map[string]any{ | |
| vscgoVersionParam.Name: "v0.0.0", | |
| }) | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| defer cancel() | |
| outputs, err := w.Run(ctx, &workflowtest.VerboseListener{T: t, OnStall: func() error { cancel(); return nil }}) | |
| if err != nil { | |
| if test.wantBuildError { | |
| return // expected build error. | |
| } | |
| t.Fatal(err) | |
| } | |
| buildArtifacts, ok := outputs["build artifacts"].([]goBuildArtifact) | |
| if !ok { | |
| t.Fatalf("unexpected build artifacts: %T (%+v): ", outputs, outputs) | |
| } | |
| var want []goBuildArtifact | |
| for _, p := range vscgoPlatforms { | |
| want = append(want, goBuildArtifact{ | |
| Platform: p.Platform, | |
| }) | |
| } | |
| sortSlice := cmpopts.SortSlices( | |
| func(x, y goBuildArtifact) bool { return x.Platform < y.Platform }, | |
| ) | |
| ignoreFilename := cmpopts.IgnoreFields(goBuildArtifact{}, "Filename") | |
| if diff := cmp.Diff(buildArtifacts, want, sortSlice, ignoreFilename); diff != "" { | |
| t.Fatal(diff) | |
| } | |
| // HACK: Reading directly from the scratch filesystem. | |
| envMap := make(map[string][]string) | |
| for _, p := range vscgoPlatforms { | |
| envMap[p.Platform] = p.Env | |
| } | |
| re := regexp.MustCompile(`-([a-z0-9]+-[a-z0-9]+)-vscgo\.zip$`) | |
| for _, a := range buildArtifacts { | |
| m := re.FindStringSubmatch(a.Filename) | |
| if m == nil { | |
| t.Errorf( | |
| "artifact file with unexpected file name: %q, want <platform>-<arch>-vscgo.zip", | |
| a.Filename, | |
| ) | |
| continue | |
| } | |
| platform := m[1] | |
| if platform != a.Platform { | |
| t.Errorf( | |
| "artifact file with unexpected platform: %q, want %q", | |
| a.Platform, | |
| platform, | |
| ) | |
| continue | |
| } | |
| // The output files are zip files. | |
| executable := "vscgo" | |
| if strings.HasPrefix(platform, "win32") { | |
| executable = "vscgo.exe" | |
| } | |
| data, err := extractFileZip(t, filepath.Join(scratchDir, w.ID.String(), a.Filename), executable) | |
| if err != nil { | |
| t.Errorf("%v: %v", a.Platform, err) | |
| } | |
| envs := envMap[a.Platform] | |
| if !bytes.Contains(data, []byte(strings.Join(envs, " "))) { | |
| t.Errorf("%v: unexpected contents: %s", a.Platform, data) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| func extractFileZip(t *testing.T, zipfile, fileToExtract string) ([]byte, error) { | |
| r, err := zip.OpenReader(zipfile) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer r.Close() | |
| // Iterate through the files in the archive, | |
| // return the contents of the requested file. | |
| for _, f := range r.File { | |
| if f.Name != fileToExtract { | |
| t.Errorf("unexpected file in zip: %q", f.Name) | |
| continue | |
| } | |
| rc, err := f.Open() | |
| if err != nil { | |
| return nil, err | |
| } | |
| return io.ReadAll(rc) | |
| } | |
| return nil, fmt.Errorf("file %q not found", fileToExtract) | |
| } |