blob: 1c0844749ba97f675c9fcc2f833240abe9580712 [file] [log] [blame]
// Copyright 2016 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 (
"encoding/base64"
"io/ioutil"
"os"
"path/filepath"
"testing"
swarming "go.chromium.org/luci/common/api/swarming/swarming/v1"
"go.chromium.org/luci/common/flag/stringlistflag"
"go.chromium.org/luci/common/flag/stringmapflag"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
)
func init() {
// So that this test works on swarming!
os.Unsetenv(ServerEnvVar)
os.Unsetenv(TaskIDEnvVar)
}
// Make sure that stringmapflag.Value are returned as sorted arrays.
func TestMapToArray(t *testing.T) {
Convey(`Make sure that stringmapflag.Value are returned as sorted arrays.`, t, func() {
type item struct {
m stringmapflag.Value
a []*swarming.SwarmingRpcsStringPair
}
data := []item{
{
m: stringmapflag.Value{},
a: []*swarming.SwarmingRpcsStringPair{},
},
{
m: stringmapflag.Value{
"foo": "bar",
},
a: []*swarming.SwarmingRpcsStringPair{
{Key: "foo", Value: "bar"},
},
},
{
m: stringmapflag.Value{
"foo": "bar",
"toto": "fifi",
},
a: []*swarming.SwarmingRpcsStringPair{
{Key: "foo", Value: "bar"},
{Key: "toto", Value: "fifi"},
},
},
{
m: stringmapflag.Value{
"toto": "fifi",
"foo": "bar",
},
a: []*swarming.SwarmingRpcsStringPair{
{Key: "foo", Value: "bar"},
{Key: "toto", Value: "fifi"},
},
},
}
for _, item := range data {
a := mapToArray(item.m)
So(len(a), ShouldResemble, len(item.m))
So(a, ShouldResemble, item.a)
}
})
}
func TestOptionalDimension(t *testing.T) {
Convey(`Make sure that stringmapflag.Value are returned as sorted arrays.`, t, func() {
type item struct {
s string
d *optionalDimension
}
data := []item{
{
s: "foo",
},
{
s: "foo=123",
},
{
s: "foo=123:abc",
},
{
s: "foo=123=abc",
},
{
s: "foo=123:321",
d: &optionalDimension{
kv: &swarming.SwarmingRpcsStringPair{
Key: "foo",
Value: "123",
},
expiration: 321,
},
},
{
s: "foo=123:abc:321",
d: &optionalDimension{
kv: &swarming.SwarmingRpcsStringPair{
Key: "foo",
Value: "123:abc",
},
expiration: 321,
},
},
}
for _, item := range data {
f := optionalDimension{}
err := f.Set(item.s)
if item.d == nil {
So(err, ShouldNotBeNil)
} else {
So(err, ShouldBeNil)
So(f, ShouldResemble, *item.d)
}
}
})
}
func TestListToStringListPairArray(t *testing.T) {
Convey(`TestListToStringListPairArray`, t, func() {
input := stringlistflag.Flag{
"x=a",
"y=c",
"x=b",
}
expected := []*swarming.SwarmingRpcsStringListPair{
{Key: "x", Value: []string{"a", "b"}},
{Key: "y", Value: []string{"c"}},
}
So(listToStringListPairArray(input), ShouldResemble, expected)
})
}
func TestNamePartFromDimensions(t *testing.T) {
Convey(`Make sure that a string name can be constructed from dimensions.`, t, func() {
type item struct {
m stringmapflag.Value
part string
}
data := []item{
{
m: stringmapflag.Value{},
part: "",
},
{
m: stringmapflag.Value{
"foo": "bar",
},
part: "foo=bar",
},
{
m: stringmapflag.Value{
"foo": "bar",
"toto": "fifi",
},
part: "foo=bar_toto=fifi",
},
{
m: stringmapflag.Value{
"toto": "fifi",
"foo": "bar",
},
part: "foo=bar_toto=fifi",
},
}
for _, item := range data {
part := namePartFromDimensions(item.m)
So(part, ShouldResemble, item.part)
}
})
}
func TestTriggerParse_NoArgs(t *testing.T) {
Convey(`Make sure that Parse works with no arguments.`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
err := c.Parse([]string(nil))
So(err, ShouldErrLike, "must provide -server")
})
}
func TestTriggerParse_NoDimension(t *testing.T) {
Convey(`Make sure that Parse fails with no dimensions.`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
err := c.GetFlags().Parse([]string{"-server", "http://localhost:9050"})
So(err, ShouldBeNil)
err = c.Parse([]string(nil))
So(err, ShouldErrLike, "dimension")
})
}
func TestTriggerParse_NoIsolated(t *testing.T) {
Convey(`Make sure that Parse handles a missing isolated flag.`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
err := c.GetFlags().Parse([]string{
"-server", "http://localhost:9050",
"-dimension", "os=Ubuntu",
})
So(err, ShouldBeNil)
err = c.Parse([]string(nil))
So(err, ShouldErrLike, "please specify command after '--'")
})
}
func TestTriggerParse_RawNoArgs(t *testing.T) {
Convey(`Make sure that Parse handles missing raw-cmd arguments.`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
err := c.GetFlags().Parse([]string{
"-server", "http://localhost:9050",
"-dimension", "os=Ubuntu",
"-isolated", "0123456789012345678901234567890123456789",
})
So(err, ShouldBeNil)
err = c.Parse([]string(nil))
So(err, ShouldErrLike, "please specify command after '--'")
})
}
func TestTriggerParse_RawArgs(t *testing.T) {
Convey(`Make sure that Parse allows both raw-cmd and -isolated`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
err := c.GetFlags().Parse([]string{
"-server", "http://localhost:9050",
"-dimension", "os=Ubuntu",
"-isolated", "0123456789012345678901234567890123456789",
})
So(err, ShouldBeNil)
err = c.Parse([]string{"arg1", "arg2"})
So(err, ShouldBeNil)
})
}
func TestProcessTriggerOptions_WithRawArgs(t *testing.T) {
Convey(`Make sure that processing trigger options handles raw-args.`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
c.commonFlags.serverURL = "http://localhost:9050"
c.isolateServer = "http://localhost:10050"
result, err := c.processTriggerOptions([]string{"arg1", "arg2"}, nil)
So(err, ShouldBeNil)
// Setting properties directly on the task is deprecated.
So(result.Properties, ShouldBeNil)
So(result.TaskSlices, ShouldHaveLength, 1)
properties := result.TaskSlices[0].Properties
So(properties.Command, ShouldResemble, []string{"arg1", "arg2"})
So(properties.ExtraArgs, ShouldResemble, ([]string)(nil))
So(properties.InputsRef, ShouldBeNil)
})
}
func TestProcessTriggerOptions_CipdPackages(t *testing.T) {
Convey(`Make sure that processing trigger options handles cipd packages.`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
c.cipdPackage = map[string]string{
"path:name": "version",
}
result, err := c.processTriggerOptions([]string(nil), nil)
So(err, ShouldBeNil)
// Setting properties directly on the task is deprecated.
So(result.Properties, ShouldBeNil)
So(result.TaskSlices, ShouldHaveLength, 1)
properties := result.TaskSlices[0].Properties
So(properties.CipdInput, ShouldResemble, &swarming.SwarmingRpcsCipdInput{
Packages: []*swarming.SwarmingRpcsCipdPackage{{
PackageName: "name",
Path: "path",
Version: "version",
}},
})
})
}
func TestProcessTriggerOptions_CAS(t *testing.T) {
t.Parallel()
Convey(`Make sure that processing trigger options handles cas digest.`, t, func() {
c := triggerRun{}
c.digest = "1d1e14a2d0da6348f3f37312ef524a2cea1db4ead9ebc6c335f9948ad634cbfd/10430"
c.commonFlags.serverURL = "https://cas.appspot.com"
result, err := c.processTriggerOptions([]string(nil), nil)
So(err, ShouldBeNil)
// Setting properties directly on the task is deprecated.
So(result.Properties, ShouldBeNil)
So(result.TaskSlices, ShouldHaveLength, 1)
properties := result.TaskSlices[0].Properties
So(properties.CasInputRoot, ShouldResemble,
&swarming.SwarmingRpcsCASReference{
CasInstance: "projects/cas/instances/default_instance",
Digest: &swarming.SwarmingRpcsDigest{
Hash: "1d1e14a2d0da6348f3f37312ef524a2cea1db4ead9ebc6c335f9948ad634cbfd",
SizeBytes: 10430,
ForceSendFields: []string{"SizeBytes"},
},
})
})
}
func TestProcessTriggerOptions_OptionalDimension(t *testing.T) {
t.Parallel()
Convey(`Basic`, t, func() {
c := triggerRun{}
c.Init(&testAuthFlags{})
So(c.dimensions.Set("foo=abc"), ShouldBeNil)
So(c.optionalDimension.Set("bar=def:60"), ShouldBeNil)
optDimExp := int64(60)
totalExp := int64(660)
c.expiration = totalExp
result, err := c.processTriggerOptions([]string(nil), nil)
So(err, ShouldBeNil)
So(result.Properties, ShouldBeNil)
So(result.TaskSlices, ShouldHaveLength, 2)
slice := result.TaskSlices[0]
So(slice.Properties.Dimensions, ShouldResemble,
[]*swarming.SwarmingRpcsStringPair{
{
Key: "foo",
Value: "abc",
},
{
Key: "bar",
Value: "def",
},
})
So(slice.ExpirationSecs, ShouldResemble, optDimExp)
slice = result.TaskSlices[1]
So(slice.Properties.Dimensions, ShouldResemble,
[]*swarming.SwarmingRpcsStringPair{
{
Key: "foo",
Value: "abc",
},
})
So(slice.ExpirationSecs, ShouldResemble, totalExp-optDimExp)
})
}
func TestProcessTriggerOptions_SecretBytesPath(t *testing.T) {
Convey(`Read secret bytes from the file, and set the base64 encoded string.`, t, func() {
// prepare secret bytes file.
dir := t.TempDir()
secretBytes := []byte("this is secret!")
secretBytesPath := filepath.Join(dir, "secret_bytes")
err := ioutil.WriteFile(secretBytesPath, secretBytes, 0600)
So(err, ShouldBeEmpty)
c := triggerRun{}
c.Init(&testAuthFlags{})
c.secretBytesPath = secretBytesPath
result, err := c.processTriggerOptions(nil, nil)
So(err, ShouldBeNil)
So(result.Properties, ShouldBeNil)
So(result.TaskSlices, ShouldHaveLength, 1)
slice := result.TaskSlices[0]
So(slice.Properties.SecretBytes, ShouldEqual, base64.StdEncoding.EncodeToString(secretBytes))
})
}