blob: 0317085f4d44bead01de83ac82580385fe2aac46 [file] [log] [blame]
// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lib
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
rbeclient "github.com/bazelbuild/remote-apis-sdks/go/pkg/client"
. "github.com/smartystreets/goconvey/convey"
"go.chromium.org/luci/cipd/client/cipd/ensure"
"go.chromium.org/luci/common/api/swarming/swarming/v1"
"go.chromium.org/luci/common/system/environ"
. "go.chromium.org/luci/common/testing/assertions"
)
func init() {
// So that this test works on swarming!
os.Unsetenv(ServerEnvVar)
os.Unsetenv(TaskIDEnvVar)
}
func TestReproduceParse_NoArgs(t *testing.T) {
t.Parallel()
Convey(`Make sure Parse works with no arguments.`, t, func() {
c := reproduceRun{}
c.init(&testAuthFlags{})
err := c.parse([]string(nil))
So(err, ShouldErrLike, "must provide -server")
})
}
func TestReproduceParse_NoTaskID(t *testing.T) {
t.Parallel()
Convey(`Make sure Parse works with with no task ID.`, t, func() {
c := reproduceRun{}
c.init(&testAuthFlags{})
err := c.GetFlags().Parse([]string{"-server", "http://localhost:9050"})
So(err, ShouldBeNil)
err = c.parse([]string(nil))
So(err, ShouldErrLike, "must specify exactly one task id.")
})
}
func TestPrepareTaskRequestEnvironment(t *testing.T) {
t.Parallel()
Convey(`Make sure we can create the correct Cmd from a fetched task's properties.`, t, func() {
c := reproduceRun{}
c.init(&testAuthFlags{})
var cipdSlicesByPath map[string]ensure.PackageSlice
c.cipdDownloader = func(ctx context.Context, workdir string, slicesByPath map[string]ensure.PackageSlice) error {
cipdSlicesByPath = slicesByPath
return nil
}
// Use TempDir, which creates a temp directory, to return a unique directory name
// that prepareTaskRequestEnvironment() will remove and recreate (via prepareDir()).
c.work = t.TempDir()
relativeCwd := "farm"
c.out = t.TempDir()
ctx := context.Background()
ctxBaseEnvMap := environ.System()
// Set env values to be removed or replaced in prepareTaskRequestEnvironment().
ctxBaseEnvMap.Set("removeKey", "removeValue")
ctxBaseEnvMap.Set("replaceKey", "oldValue")
ctxBaseEnvMap.Set("noChangeKey", "noChangeValue")
ctxBaseEnvMap.Set("PATH", "existingPathValue")
ctx = ctxBaseEnvMap.SetInCtx(ctx)
expectedEnvMap := ctxBaseEnvMap.Clone()
var fetchedCASFiles bool
properties := &swarming.SwarmingRpcsTaskProperties{
Command: []string{"rbd", "stream", "-test-id-prefix", "--isolated-output=${ISOLATED_OUTDIR}/chicken-output.json"},
RelativeCwd: relativeCwd,
Env: []*swarming.SwarmingRpcsStringPair{
&swarming.SwarmingRpcsStringPair{
Key: "key",
Value: "value1",
},
&swarming.SwarmingRpcsStringPair{
Key: "replaceKey",
Value: "value2",
},
&swarming.SwarmingRpcsStringPair{
Key: "removeKey",
Value: "",
},
},
EnvPrefixes: []*swarming.SwarmingRpcsStringListPair{
&swarming.SwarmingRpcsStringListPair{
Key: "PATH",
Value: []string{".task_template_packages", ".task_template_packages/zoo"},
},
&swarming.SwarmingRpcsStringListPair{
Key: "CHICKENS",
Value: []string{"egg", "rooster"},
},
},
InputsRef: &swarming.SwarmingRpcsFilesRef{
Isolatedserver: "server-url",
Namespace: "chicken-files",
},
CasInputRoot: &swarming.SwarmingRpcsCASReference{
CasInstance: "CAS-instance",
},
CipdInput: &swarming.SwarmingRpcsCipdInput{
Packages: []*swarming.SwarmingRpcsCipdPackage{
&swarming.SwarmingRpcsCipdPackage{
PackageName: "infra/tools/luci-auth/${platform}",
Path: ".task_template_packages",
Version: "git_revision:41a7e9bcbf18718dcda83dd5c6188cfc44271e70",
},
&swarming.SwarmingRpcsCipdPackage{
PackageName: "infra/tools/luci/logdog/butler/${platform}",
Path: ".",
Version: "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c",
},
&swarming.SwarmingRpcsCipdPackage{
PackageName: "infra/tools/luci/logchicken/${platform}",
Path: "",
Version: "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867d",
},
},
},
}
service := &testService{
filesFromCAS: func(_ context.Context, _ string, _ *rbeclient.Client, _ *swarming.SwarmingRpcsCASReference) ([]string, error) {
fetchedCASFiles = true
return []string{}, nil
},
}
cmd, err := c.prepareTaskRequestEnvironment(ctx, properties, service)
So(err, ShouldBeNil)
expected := exec.CommandContext(ctx, "rbd", "stream", "-test-id-prefix",
fmt.Sprintf("--isolated-output=%s", filepath.Join(c.out, "chicken-output.json")))
expected.Dir = filepath.Join(c.work, relativeCwd)
expected.Stdout = os.Stdout
expected.Stderr = os.Stderr
expectedEnvMap.Remove("removeKey")
expectedEnvMap.Set("key", "value1")
expectedEnvMap.Set("replaceKey", "value2")
paths := []string{
filepath.Join(c.work, ".task_template_packages"),
filepath.Join(c.work, ".task_template_packages/zoo"),
"existingPathValue"}
expectedEnvMap.Set("PATH", strings.Join(paths, string(os.PathListSeparator)))
chickens := []string{filepath.Join(c.work, "egg"), filepath.Join(c.work, "rooster")}
expectedEnvMap.Set("CHICKENS", strings.Join(chickens, string(os.PathListSeparator)))
expected.Env = expectedEnvMap.Sorted()
So(cmd, ShouldResemble, expected)
So(fetchedCASFiles, ShouldBeTrue)
So(cipdSlicesByPath, ShouldResemble, map[string]ensure.PackageSlice{
"": ensure.PackageSlice{
ensure.PackageDef{
PackageTemplate: "infra/tools/luci/logdog/butler/${platform}",
UnresolvedVersion: "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c",
},
ensure.PackageDef{
PackageTemplate: "infra/tools/luci/logchicken/${platform}",
UnresolvedVersion: "git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867d",
},
},
".task_template_packages": ensure.PackageSlice{
ensure.PackageDef{
PackageTemplate: "infra/tools/luci-auth/${platform}",
UnresolvedVersion: "git_revision:41a7e9bcbf18718dcda83dd5c6188cfc44271e70",
}},
})
})
}
func TestPrepareTaskRequestEnvironment_Isolate(t *testing.T) {
t.Parallel()
Convey(`Make sure we can process InputsRef for Isolate`, t, func() {
c := reproduceRun{}
c.init(&testAuthFlags{})
// Use TempDir, which creates a temp directory, to return a unique directory namme
// that prepareTaskRequestEnvironment() will remove and recreate (via prepareDir())
c.work = t.TempDir()
ctx := context.Background()
ctxBaseEnvMap := environ.System()
ctx = ctxBaseEnvMap.SetInCtx(ctx)
expectedEnvMap := ctxBaseEnvMap.Clone()
var fetchedIsolateFiles bool
properties := &swarming.SwarmingRpcsTaskProperties{
Command: []string{"rbd", "stream", "-test-id-prefix", "chicken://chicken_chicken/"},
InputsRef: &swarming.SwarmingRpcsFilesRef{
Isolated: "isolated",
},
}
service := &testService{
filesFromIsolate: func(_ context.Context, _ string, _ *swarming.SwarmingRpcsFilesRef) ([]string, error) {
fetchedIsolateFiles = true
return []string{}, nil
},
}
cmd, err := c.prepareTaskRequestEnvironment(ctx, properties, service)
So(err, ShouldBeNil)
expected := exec.CommandContext(ctx, "rbd", "stream", "-test-id-prefix", "chicken://chicken_chicken/")
expected.Dir = c.work
expected.Stdout = os.Stdout
expected.Stderr = os.Stderr
expected.Env = expectedEnvMap.Sorted()
So(cmd, ShouldResemble, expected)
So(fetchedIsolateFiles, ShouldBeTrue)
})
}
func TestPrepareTaskRequestEnvironment_IsolateAndCAS(t *testing.T) {
t.Parallel()
Convey(`Make sure we do not process both Isolate and CAS`, t, func() {
c := reproduceRun{}
c.init(&testAuthFlags{})
// Use TempDir, which creates a temp directory, to return a unique directory namme
// that prepareTaskRequestEnvironment() will remove and recreate (via prepareDir())
c.work = t.TempDir()
service := &testService{}
properties := &swarming.SwarmingRpcsTaskProperties{
Command: []string{"rbd", "stream", "-test-id-prefix", "chicken://chicken_chicken/"},
InputsRef: &swarming.SwarmingRpcsFilesRef{
Isolated: "isolated",
},
CasInputRoot: &swarming.SwarmingRpcsCASReference{
CasInstance: "CAS-instance",
},
}
_, err := c.prepareTaskRequestEnvironment(context.Background(), properties, service)
So(err, ShouldBeError, "fetched TaskRequest has files from Isolate and RBE-CAS")
})
}
func TestReproduceTaskRequestCommand(t *testing.T) {
t.Parallel()
Convey(`Make sure we can execute commands.`, t, func() {
c := reproduceRun{}
c.init(&testAuthFlags{})
ctx := context.Background()
var cmd *exec.Cmd
if runtime.GOOS == "windows" {
cmd = exec.CommandContext(ctx, "cmd", "/c", "echo", "chicken")
} else {
cmd = exec.CommandContext(ctx, "echo", "chicken")
}
var stdout bytes.Buffer
cmd.Stdout = &stdout
err := c.executeTaskRequestCommand(ctx, &swarming.SwarmingRpcsTaskRequest{}, cmd)
So(err, ShouldBeNil)
if runtime.GOOS == "windows" {
So(stdout.String(), ShouldEqual, "chicken\r\n")
} else {
So(stdout.String(), ShouldEqual, "chicken\n")
}
})
}