blob: 437170d404fa64b2719f7c92107662fe11bfc54e [file] [log] [blame]
// Copyright 2023 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 pbutil
import (
"strings"
"testing"
. "github.com/smartystreets/goconvey/convey"
. "go.chromium.org/luci/common/testing/assertions"
pb "go.chromium.org/luci/resultdb/proto/v1"
)
func TestValidate(t *testing.T) {
t.Parallel()
Convey(`ValidateGitilesCommit`, t, func() {
commit := &pb.GitilesCommit{
Host: "chromium.googlesource.com",
Project: "chromium/src",
Ref: "refs/heads/branch",
CommitHash: "123456789012345678901234567890abcdefabcd",
Position: 1,
}
Convey(`Valid`, func() {
So(ValidateGitilesCommit(commit), ShouldBeNil)
})
Convey(`Nil`, func() {
So(ValidateGitilesCommit(nil), ShouldErrLike, `unspecified`)
})
Convey(`Host`, func() {
Convey(`Missing`, func() {
commit.Host = ""
So(ValidateGitilesCommit(commit), ShouldErrLike, `host: unspecified`)
})
Convey(`Invalid format`, func() {
commit.Host = "https://somehost.com"
So(ValidateGitilesCommit(commit), ShouldErrLike, `host: does not match`)
})
Convey(`Too long`, func() {
commit.Host = strings.Repeat("a", hostnameMaxLength+1)
So(ValidateGitilesCommit(commit), ShouldErrLike, `host: exceeds `, ` characters`)
})
})
Convey(`Project`, func() {
Convey(`Missing`, func() {
commit.Project = ""
So(ValidateGitilesCommit(commit), ShouldErrLike, `project: unspecified`)
})
Convey(`Too long`, func() {
commit.Project = strings.Repeat("a", 256)
So(ValidateGitilesCommit(commit), ShouldErrLike, `project: exceeds 255 characters`)
})
})
Convey(`Refs`, func() {
Convey(`Missing`, func() {
commit.Ref = ""
So(ValidateGitilesCommit(commit), ShouldErrLike, `ref: unspecified`)
})
Convey(`Invalid`, func() {
commit.Ref = "main"
So(ValidateGitilesCommit(commit), ShouldErrLike, `ref: does not match refs/.*`)
})
Convey(`Too long`, func() {
commit.Ref = "refs/" + strings.Repeat("a", 252)
So(ValidateGitilesCommit(commit), ShouldErrLike, `ref: exceeds 255 characters`)
})
})
Convey(`Commit Hash`, func() {
Convey(`Missing`, func() {
commit.CommitHash = ""
So(ValidateGitilesCommit(commit), ShouldErrLike, `commit_hash: unspecified`)
})
Convey(`Invalid (too long)`, func() {
commit.CommitHash = strings.Repeat("a", 41)
So(ValidateGitilesCommit(commit), ShouldErrLike, `commit_hash: does not match "^[a-f0-9]{40}$"`)
})
Convey(`Invalid (too short)`, func() {
commit.CommitHash = strings.Repeat("a", 39)
So(ValidateGitilesCommit(commit), ShouldErrLike, `commit_hash: does not match "^[a-f0-9]{40}$"`)
})
Convey(`Invalid (upper case)`, func() {
commit.CommitHash = "123456789012345678901234567890ABCDEFABCD"
So(ValidateGitilesCommit(commit), ShouldErrLike, `commit_hash: does not match "^[a-f0-9]{40}$"`)
})
})
Convey(`Position`, func() {
Convey(`Missing`, func() {
commit.Position = 0
So(ValidateGitilesCommit(commit), ShouldErrLike, `position: unspecified`)
})
Convey(`Negative`, func() {
commit.Position = -1
So(ValidateGitilesCommit(commit), ShouldErrLike, `position: cannot be negative`)
})
})
})
Convey(`ValidateGerritChange`, t, func() {
change := &pb.GerritChange{
Host: "chromium-review.googlesource.com",
Project: "chromium/src",
Change: 12345,
Patchset: 1,
}
Convey(`Valid`, func() {
So(ValidateGerritChange(change), ShouldBeNil)
})
Convey(`Nil`, func() {
So(ValidateGerritChange(nil), ShouldErrLike, `unspecified`)
})
Convey(`Host`, func() {
Convey(`Missing`, func() {
change.Host = ""
So(ValidateGerritChange(change), ShouldErrLike, `host: unspecified`)
})
Convey(`Invalid format`, func() {
change.Host = "https://somehost.com"
So(ValidateGerritChange(change), ShouldErrLike, `host: does not match`)
})
Convey(`Too long`, func() {
change.Host = strings.Repeat("a", hostnameMaxLength+1)
So(ValidateGerritChange(change), ShouldErrLike, `host: exceeds `, ` characters`)
})
})
Convey(`Project`, func() {
Convey(`Missing`, func() {
change.Project = ""
So(ValidateGerritChange(change), ShouldErrLike, `project: unspecified`)
})
Convey(`Too long`, func() {
change.Project = strings.Repeat("a", 256)
So(ValidateGerritChange(change), ShouldErrLike, `project: exceeds 255 characters`)
})
})
Convey(`Change`, func() {
Convey(`Missing`, func() {
change.Change = 0
So(ValidateGerritChange(change), ShouldErrLike, `change: unspecified`)
})
Convey(`Invalid`, func() {
change.Change = -1
So(ValidateGerritChange(change), ShouldErrLike, `change: cannot be negative`)
})
})
Convey(`Patchset`, func() {
Convey(`Missing`, func() {
change.Patchset = 0
So(ValidateGerritChange(change), ShouldErrLike, `patchset: unspecified`)
})
Convey(`Invalid`, func() {
change.Patchset = -1
So(ValidateGerritChange(change), ShouldErrLike, `patchset: cannot be negative`)
})
})
})
Convey("ValidateInstruction", t, func() {
stepInstructions := &pb.Instructions{
Instructions: []*pb.Instruction{
{
Id: "instruction1",
TargetedInstructions: []*pb.TargetedInstruction{
{
Targets: []pb.InstructionTarget{
pb.InstructionTarget_LOCAL,
},
Content: "content1",
},
},
},
{
Id: "instruction2",
TargetedInstructions: []*pb.TargetedInstruction{
{
Targets: []pb.InstructionTarget{
pb.InstructionTarget_LOCAL,
},
Content: "content2",
},
},
},
},
}
Convey("Valid Step Instructions", func() {
So(ValidateStepInstructions(stepInstructions), ShouldBeNil)
})
Convey("Step Instruction can be nil", func() {
So(ValidateStepInstructions(nil), ShouldBeNil)
})
Convey("Step Instruction too big", func() {
stepInstructions.Instructions[0].Id = stringWithLength(2 * 1024 * 1024)
So(ValidateStepInstructions(stepInstructions), ShouldErrLike, "step instructions: bigger than 1048576 bytes")
})
Convey("No ID", func() {
stepInstructions.Instructions[0].Id = ""
So(ValidateStepInstructions(stepInstructions), ShouldErrLike, "step instructions: unspecified id")
})
Convey("Duplicate ID", func() {
stepInstructions.Instructions[0].Id = "instruction2"
So(ValidateStepInstructions(stepInstructions), ShouldErrLike, `step instructions: ID "instruction2" is re-used at index 0 and 1`)
})
instruction := &pb.Instruction{
TargetedInstructions: []*pb.TargetedInstruction{
{
Targets: []pb.InstructionTarget{
pb.InstructionTarget_LOCAL,
},
Content: "content1",
},
{
Targets: []pb.InstructionTarget{
pb.InstructionTarget_REMOTE,
},
Content: "content2",
Dependency: []*pb.InstructionDependency{
{
BuildId: "8000",
StepName: "compile",
},
},
},
},
}
Convey("Valid Test Instruction", func() {
So(ValidateTestInstruction(instruction), ShouldBeNil)
})
Convey("Test instruction can be nil", func() {
So(ValidateTestInstruction(nil), ShouldBeNil)
})
Convey("Empty target", func() {
instruction.TargetedInstructions[0].Targets = []pb.InstructionTarget{}
So(ValidateInstruction(instruction), ShouldErrLike, "target: empty")
})
Convey("Unspecified target", func() {
instruction.TargetedInstructions[0].Targets = []pb.InstructionTarget{
pb.InstructionTarget_INSTRUCTION_TARGET_UNSPECIFIED,
}
So(ValidateInstruction(instruction), ShouldErrLike, "target: unspecified")
})
Convey("Duplicated target", func() {
instruction.TargetedInstructions[0].Targets = []pb.InstructionTarget{
pb.InstructionTarget_REMOTE,
}
So(ValidateInstruction(instruction), ShouldErrLike, `target: duplicated "REMOTE"`)
})
Convey("Content exceeds size limit", func() {
content := ""
for i := 0; i < 120000; i++ {
content += "a"
}
instruction.TargetedInstructions[0].Content = content
So(ValidateInstruction(instruction), ShouldErrLike, "content: longer than 10240 bytes")
})
Convey("More than 1 dependency", func() {
instruction.TargetedInstructions[0].Dependency = []*pb.InstructionDependency{
{
BuildId: "8000",
StepName: "dep1",
},
{
BuildId: "8000",
StepName: "dep2",
},
}
So(ValidateInstruction(instruction), ShouldErrLike, "dependency: more than 1")
})
Convey("Dependency buildID exceeds limit", func() {
instruction.TargetedInstructions[0].Dependency = []*pb.InstructionDependency{
{
BuildId: stringWithLength(101),
StepName: "dep",
},
}
So(ValidateInstruction(instruction), ShouldErrLike, "dependencies[0]: build_id: longer than 100 bytes")
})
Convey("Dependency empty step name", func() {
instruction.TargetedInstructions[0].Dependency = []*pb.InstructionDependency{
{
BuildId: "8000",
},
}
So(ValidateInstruction(instruction), ShouldErrLike, "dependencies[0]: step_name: empty")
})
Convey("Dependency step name exceeds limit", func() {
instruction.TargetedInstructions[0].Dependency = []*pb.InstructionDependency{
{
BuildId: "8000",
StepName: stringWithLength(1025),
},
}
So(ValidateInstruction(instruction), ShouldErrLike, "dependencies[0]: step_name: longer than 1024 bytes")
})
Convey("Dependency step tag key exceeds limit", func() {
instruction.TargetedInstructions[0].Dependency = []*pb.InstructionDependency{
{
BuildId: "8000",
StepName: "step",
StepTag: &pb.StringPair{
Key: stringWithLength(257),
Value: "val",
},
},
}
So(ValidateInstruction(instruction), ShouldErrLike, "dependencies[0]: step_tag_key: longer than 256 bytes")
})
Convey("Dependency step tag value exceeds limit", func() {
instruction.TargetedInstructions[0].Dependency = []*pb.InstructionDependency{
{
BuildId: "8000",
StepName: "step",
StepTag: &pb.StringPair{
Key: "key",
Value: stringWithLength(1025),
},
},
}
So(ValidateInstruction(instruction), ShouldErrLike, "dependencies[0]: step_tag_val: longer than 1024 bytes")
})
})
}
func stringWithLength(l int) string {
return strings.Repeat("a", l)
}