| // 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/workflow" |
| ) |
| |
| 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 { |
| t.Run(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, err := workflow.Start(wd, map[string]interface{}{ |
| vscgoVersionParam.Name: "v0.0.0", |
| }) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| outputs, err := w.Run(ctx, &verboseListener{t: t, onStall: cancel}) |
| 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) |
| } |