blob: 185f2c7bc995e6922cd5ed8dc0219f7c6778f386 [file] [log] [blame]
package command
import (
"errors"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"google.golang.org/protobuf/proto"
repb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
)
func TestStableId_SameCommands(t *testing.T) {
t.Parallel()
testcases := []struct {
label string
A, B *Command
}{
{
label: "platform",
A: &Command{
Platform: map[string]string{"a": "1", "b": "2", "c": "3"},
},
B: &Command{
Platform: map[string]string{"c": "3", "b": "2", "a": "1"},
},
},
{
label: "inputs",
A: &Command{
InputSpec: &InputSpec{
Inputs: []string{"a", "b", "c"},
},
},
B: &Command{
InputSpec: &InputSpec{
Inputs: []string{"c", "b", "a"},
},
},
},
{
label: "output files",
A: &Command{
OutputFiles: []string{"a", "b", "c"},
},
B: &Command{
OutputFiles: []string{"c", "b", "a"},
},
},
{
label: "output directories",
A: &Command{
OutputDirs: []string{"a", "b", "c"},
},
B: &Command{
OutputDirs: []string{"c", "b", "a"},
},
},
{
label: "environment",
A: &Command{
InputSpec: &InputSpec{
EnvironmentVariables: map[string]string{"a": "1", "b": "2", "c": "3"},
},
},
B: &Command{
InputSpec: &InputSpec{
EnvironmentVariables: map[string]string{"c": "3", "b": "2", "a": "1"},
},
},
},
{
label: "exclusions",
A: &Command{
InputSpec: &InputSpec{
InputExclusions: []*InputExclusion{
{Regex: "a", Type: FileInputType},
{Regex: "b", Type: DirectoryInputType},
{Regex: "c", Type: UnspecifiedInputType},
},
},
},
B: &Command{
InputSpec: &InputSpec{
InputExclusions: []*InputExclusion{
{Regex: "b", Type: DirectoryInputType},
{Regex: "c", Type: UnspecifiedInputType},
{Regex: "a", Type: FileInputType},
},
},
},
},
}
for _, tc := range testcases {
aID := tc.A.stableID()
bID := tc.B.stableID()
if aID != bID {
t.Errorf("%s: stableID of %v = %s different from %v = %s", tc.label, tc.A, aID, tc.B, bID)
}
}
}
func TestStableId_DifferentCommands(t *testing.T) {
t.Parallel()
testcases := []struct {
label string
A, B *Command
}{
{
label: "args",
A: &Command{Args: []string{"a", "b"}},
B: &Command{Args: []string{"b", "a"}},
},
{
label: "exec root",
A: &Command{ExecRoot: "a"},
B: &Command{ExecRoot: "b"},
},
{
label: "working dir",
A: &Command{WorkingDir: "a"},
B: &Command{WorkingDir: "b"},
},
{
label: "output files",
A: &Command{OutputFiles: []string{"a", "b", "c"}},
B: &Command{OutputFiles: []string{"c", "b", "c"}},
},
{
label: "output dirs",
A: &Command{OutputDirs: []string{"a", "b", "c"}},
B: &Command{OutputDirs: []string{"c", "b", "c"}},
},
{
label: "platform",
A: &Command{Platform: map[string]string{"a": "1", "b": "2", "c": "3"}},
B: &Command{Platform: map[string]string{"c": "3", "b": "2", "a": "10"}},
},
{
label: "inputs",
A: &Command{
InputSpec: &InputSpec{
Inputs: []string{"a", "b", "c"},
},
},
B: &Command{
InputSpec: &InputSpec{
Inputs: []string{"c", "b", "a1"},
},
},
},
{
label: "environment",
A: &Command{
InputSpec: &InputSpec{
EnvironmentVariables: map[string]string{"a": "1", "b": "2", "c": "3"},
},
},
B: &Command{
InputSpec: &InputSpec{
EnvironmentVariables: map[string]string{"c": "3", "b": "2", "a": "10"},
},
},
},
{
label: "exclusions",
A: &Command{
InputSpec: &InputSpec{
InputExclusions: []*InputExclusion{
{Regex: "a", Type: FileInputType},
{Regex: "b", Type: DirectoryInputType},
{Regex: "c", Type: UnspecifiedInputType},
},
},
},
B: &Command{
InputSpec: &InputSpec{
InputExclusions: []*InputExclusion{
{Regex: "b", Type: UnspecifiedInputType},
{Regex: "c", Type: UnspecifiedInputType},
{Regex: "a", Type: FileInputType},
},
},
},
},
}
for _, tc := range testcases {
aID := tc.A.stableID()
bID := tc.B.stableID()
if aID == bID {
t.Errorf("%s: stableID of %v = %s is same as %v", tc.label, tc.A, aID, tc.B)
}
}
}
func TestFillDefaultFieldValues_Empty(t *testing.T) {
t.Parallel()
c := &Command{}
c.FillDefaultFieldValues()
if c.Identifiers == nil {
t.Fatal("{}.Identifiers = nil, expected filled")
}
if c.Identifiers.CommandID == "" {
t.Errorf("did not fill command id for empty command")
}
if c.Identifiers.ToolName == "" {
t.Errorf("did not fill tool name for empty command, expected \"remote-client\"")
}
if c.Identifiers.InvocationID == "" {
t.Errorf("did not generate invocation id for empty command")
}
if c.InputSpec == nil {
t.Errorf("did not generate input spec for empty command")
}
}
func TestFillDefaultFieldValues_PreserveExisting(t *testing.T) {
t.Parallel()
ids := &Identifiers{
CommandID: "bla",
ToolName: "foo",
InvocationID: "bar",
}
inputSpec := &InputSpec{}
c := &Command{InputSpec: inputSpec, Identifiers: ids}
c.FillDefaultFieldValues()
if c.Identifiers != ids {
t.Fatal("command.Identifiers address not preserved")
}
if c.Identifiers.CommandID != "bla" {
t.Errorf("did not preserve CommandID: got %s, expected bla", c.Identifiers.CommandID)
}
if c.Identifiers.ToolName != "foo" {
t.Errorf("did not preserve CommandID: got %s, expected foo", c.Identifiers.ToolName)
}
if c.Identifiers.InvocationID != "bar" {
t.Errorf("did not preserve CommandID: got %s, expected bar", c.Identifiers.InvocationID)
}
if c.InputSpec != inputSpec {
t.Fatal("command.InputSpec address not preserved")
}
}
func TestValidate_Errors(t *testing.T) {
t.Parallel()
testcases := []struct {
label string
Command *Command
}{
{
label: "missing args",
Command: &Command{
Identifiers: &Identifiers{},
ExecRoot: "a",
InputSpec: &InputSpec{},
},
},
{
label: "missing input spec",
Command: &Command{
Identifiers: &Identifiers{},
Args: []string{"a"},
ExecRoot: "a",
},
},
{
label: "missing exec root",
Command: &Command{
Identifiers: &Identifiers{},
Args: []string{"a"},
InputSpec: &InputSpec{},
},
},
{
label: "missing identifiers",
Command: &Command{
Args: []string{"a"},
InputSpec: &InputSpec{},
ExecRoot: "a",
},
},
{
label: "mismatch between local and remote working dir depth",
Command: &Command{
Identifiers: &Identifiers{},
Args: []string{"a"},
ExecRoot: "a",
InputSpec: &InputSpec{},
WorkingDir: "foo",
RemoteWorkingDir: "bar/baz",
},
},
}
for _, tc := range testcases {
if err := tc.Command.Validate(); err == nil {
t.Errorf("%s: expected Validate of %v to error, got nil", tc.label, tc.Command)
}
}
}
func TestValidate_NilSuccess(t *testing.T) {
t.Parallel()
var c *Command
if err := c.Validate(); err != nil {
t.Errorf("expected Validate of nil = nil, got %v", err)
}
}
func TestValidate_Success(t *testing.T) {
t.Parallel()
c := &Command{
Identifiers: &Identifiers{},
Args: []string{"a"},
ExecRoot: "a",
InputSpec: &InputSpec{},
}
if err := c.Validate(); err != nil {
t.Errorf("expected Validate of %v = nil, got %v", c, err)
}
}
func TestToREProto(t *testing.T) {
tests := []struct {
name string
cmd *Command
wantCmd *repb.Command
}{
{
name: "pass args",
cmd: &Command{Args: []string{"foo", "bar"}},
wantCmd: &repb.Command{Arguments: []string{"foo", "bar"}},
},
{
name: "pass working directory",
cmd: &Command{WorkingDir: "a/b"},
wantCmd: &repb.Command{WorkingDirectory: "a/b"},
},
{
name: "sort output files",
cmd: &Command{OutputFiles: []string{"foo", "bar", "abc"}},
wantCmd: &repb.Command{OutputFiles: []string{"abc", "bar", "foo"}},
},
{
name: "sort output directories",
cmd: &Command{OutputDirs: []string{"foo", "bar", "abc"}},
wantCmd: &repb.Command{OutputDirectories: []string{"abc", "bar", "foo"}},
},
{
name: "sort environment variables",
cmd: &Command{
InputSpec: &InputSpec{
EnvironmentVariables: map[string]string{"b": "3", "a": "2", "c": "1"},
},
},
wantCmd: &repb.Command{
EnvironmentVariables: []*repb.Command_EnvironmentVariable{
&repb.Command_EnvironmentVariable{Name: "a", Value: "2"},
&repb.Command_EnvironmentVariable{Name: "b", Value: "3"},
&repb.Command_EnvironmentVariable{Name: "c", Value: "1"},
},
},
},
{
name: "sort platform",
cmd: &Command{Platform: map[string]string{"b": "3", "a": "2", "c": "1"}},
wantCmd: &repb.Command{
Platform: &repb.Platform{
Properties: []*repb.Platform_Property{
&repb.Platform_Property{Name: "a", Value: "2"},
&repb.Platform_Property{Name: "b", Value: "3"},
&repb.Platform_Property{Name: "c", Value: "1"},
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.cmd.FillDefaultFieldValues()
gotCmd := tc.cmd.ToREProto(false)
if diff := cmp.Diff(tc.wantCmd, gotCmd, cmpopts.EquateEmpty(), cmp.Comparer(proto.Equal)); diff != "" {
t.Errorf("%s: buildCommand gave result diff (-want +got):\n%s", tc.name, diff)
}
})
}
}
func TestToREProtoWithOutputPaths(t *testing.T) {
tests := []struct {
name string
cmd *Command
wantCmd *repb.Command
}{
{
name: "pass args",
cmd: &Command{Args: []string{"foo", "bar"}},
wantCmd: &repb.Command{Arguments: []string{"foo", "bar"}},
},
{
name: "pass working directory",
cmd: &Command{WorkingDir: "a/b"},
wantCmd: &repb.Command{WorkingDirectory: "a/b"},
},
{
name: "sort output files",
cmd: &Command{OutputFiles: []string{"foo", "bar", "abc"}},
wantCmd: &repb.Command{OutputPaths: []string{"abc", "bar", "foo"}},
},
{
name: "sort output directories",
cmd: &Command{OutputDirs: []string{"foo", "bar", "abc"}},
wantCmd: &repb.Command{OutputPaths: []string{"abc", "bar", "foo"}},
},
{
name: "sort environment variables",
cmd: &Command{
InputSpec: &InputSpec{
EnvironmentVariables: map[string]string{"b": "3", "a": "2", "c": "1"},
},
},
wantCmd: &repb.Command{
EnvironmentVariables: []*repb.Command_EnvironmentVariable{
&repb.Command_EnvironmentVariable{Name: "a", Value: "2"},
&repb.Command_EnvironmentVariable{Name: "b", Value: "3"},
&repb.Command_EnvironmentVariable{Name: "c", Value: "1"},
},
},
},
{
name: "sort platform",
cmd: &Command{Platform: map[string]string{"b": "3", "a": "2", "c": "1"}},
wantCmd: &repb.Command{
Platform: &repb.Platform{
Properties: []*repb.Platform_Property{
&repb.Platform_Property{Name: "a", Value: "2"},
&repb.Platform_Property{Name: "b", Value: "3"},
&repb.Platform_Property{Name: "c", Value: "1"},
},
},
},
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.cmd.FillDefaultFieldValues()
gotCmd := tc.cmd.ToREProto(true)
if diff := cmp.Diff(tc.wantCmd, gotCmd, cmpopts.EquateEmpty(), cmp.Comparer(proto.Equal)); diff != "" {
t.Errorf("%s: buildCommand gave result diff (-want +got):\n%s", tc.name, diff)
}
})
}
}
func TestToFromProto(t *testing.T) {
cmd := &Command{
Identifiers: &Identifiers{
CommandID: "a",
InvocationID: "b",
ToolName: "c",
},
Args: []string{"a", "b", "c"},
ExecRoot: "/exec/root",
InputSpec: &InputSpec{
Inputs: []string{"foo.h", "bar.h"},
VirtualInputs: []*VirtualInput{
&VirtualInput{
Path: "empty_file",
IsExecutable: true,
},
&VirtualInput{
Path: "foo/empty_dir",
IsEmptyDirectory: true,
},
&VirtualInput{
Path: "foo/bar",
Contents: []byte("bar-contents"),
},
},
InputExclusions: []*InputExclusion{
&InputExclusion{
Regex: "*.bla",
Type: DirectoryInputType,
},
&InputExclusion{
Regex: "*.blo",
Type: FileInputType,
},
},
EnvironmentVariables: map[string]string{
"k": "v",
"k1": "v1",
},
SymlinkBehavior: ResolveSymlink,
},
OutputFiles: []string{"a/b/out"},
}
gotCmd := FromProto(ToProto(cmd))
if diff := cmp.Diff(cmd, gotCmd, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("FromProto(ToProto()) returned diff in result: (-want +got)\n%s", diff)
}
}
func TestResultToFromProto(t *testing.T) {
res := &Result{
Status: CacheHitResultStatus,
ExitCode: 42,
Err: errors.New("message"),
}
gotRes := ResultFromProto(ResultToProto(res))
if diff := cmp.Diff(res, gotRes, cmpopts.IgnoreFields(Result{}, "Err")); diff != "" {
t.Errorf("ResultFromProto(ResultToProto()) returned diff in result: (-want +got)\n%s", diff)
}
if res.Err.Error() != gotRes.Err.Error() {
t.Errorf("ResultFromProto(ResultToProto()) returned diff in error: want %v, got %v", res.Err, gotRes.Err)
}
}
func TestTimeIntervalToFromProto(t *testing.T) {
ti := &TimeInterval{
From: time.Now(),
To: time.Now(),
}
gotTi := TimeIntervalFromProto(TimeIntervalToProto(ti))
if diff := cmp.Diff(ti, gotTi); diff != "" {
t.Errorf("TimeIntervalFromProto(TimeIntervalToProto()) returned diff in result: (-want +got)\n%s", diff)
}
ti = &TimeInterval{From: time.Now()}
gotTi = TimeIntervalFromProto(TimeIntervalToProto(ti))
if diff := cmp.Diff(ti, gotTi); diff != "" {
t.Errorf("TimeIntervalFromProto(TimeIntervalToProto()) returned diff in result: (-want +got)\n%s", diff)
}
ti = &TimeInterval{To: time.Now()}
gotTi = TimeIntervalFromProto(TimeIntervalToProto(ti))
if diff := cmp.Diff(ti, gotTi); diff != "" {
t.Errorf("TimeIntervalFromProto(TimeIntervalToProto()) returned diff in result: (-want +got)\n%s", diff)
}
ti = &TimeInterval{}
gotTi = TimeIntervalFromProto(TimeIntervalToProto(ti))
if diff := cmp.Diff(ti, gotTi); diff != "" {
t.Errorf("TimeIntervalFromProto(TimeIntervalToProto()) returned diff in result: (-want +got)\n%s", diff)
}
gotTi = TimeIntervalFromProto(TimeIntervalToProto(nil))
if gotTi != nil {
t.Errorf("TimeIntervalFromProto(TimeIntervalToProto()) returned %v, wanted nil", gotTi)
}
}