Skip to content

Commit 4e38fc0

Browse files
authored
[QT-555] Fix panic when passing an invalid scenario filter (#89)
Fix a panic when we tried to execute scenario sub-commands against a scenario that has no variants but we've included invalid matrix filters. Signed-off-by: Ryan Cragun <[email protected]>
1 parent c1f597a commit 4e38fc0

File tree

4 files changed

+77
-8
lines changed

4 files changed

+77
-8
lines changed

internal/flightplan/matrix.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,6 @@ func (e Element) Equal(other Element) bool {
9999
return true
100100
}
101101

102-
// NewElementFromProto creates a new Element from a proto filter element
103-
func NewElementFromProto(p *pb.Scenario_Filter_Element) Element {
104-
return NewElement(p.GetKey(), p.GetValue())
105-
}
106-
107102
func NewVector() *Vector {
108103
return &Vector{}
109104
}
@@ -120,6 +115,14 @@ func (v *Vector) String() string {
120115

121116
// Equal returns true if both Vectors have Equal values and Equal value ordering
122117
func (v *Vector) Equal(other *Vector) bool {
118+
if v == nil && other == nil {
119+
return true
120+
}
121+
122+
if other == nil {
123+
return false
124+
}
125+
123126
if v.elements == nil && other.elements == nil {
124127
return true
125128
}
@@ -145,6 +148,14 @@ func (v *Vector) Equal(other *Vector) bool {
145148
// not be ordered the same. This is useful for Vectors of pairs that do not
146149
// enforce ordering.
147150
func (v *Vector) EqualUnordered(other *Vector) bool {
151+
if v == nil && other == nil {
152+
return true
153+
}
154+
155+
if other == nil {
156+
return false
157+
}
158+
148159
if v.elements == nil && other.elements == nil {
149160
return true
150161
}
@@ -172,6 +183,18 @@ func (v *Vector) EqualUnordered(other *Vector) bool {
172183
// ContainsUnordered returns a boolean which represent if vector contains the values
173184
// of another vector.
174185
func (v *Vector) ContainsUnordered(other *Vector) bool {
186+
if v == nil && other == nil {
187+
return true
188+
}
189+
190+
if other == nil {
191+
return false
192+
}
193+
194+
if len(v.elements) < 1 || len(other.elements) < 1 {
195+
return false
196+
}
197+
175198
for oi := range other.elements {
176199
match := false
177200
for vi := range v.elements {
@@ -384,6 +407,10 @@ func (m *Matrix) CartesianProduct() *Matrix {
384407
// HasVector returns whether or not a matrix has a vector that exactly matches
385408
// the elements of another that is given.
386409
func (m *Matrix) HasVector(other *Vector) bool {
410+
if other == nil {
411+
return false
412+
}
413+
387414
for _, v := range m.Vectors {
388415
if v.Equal(other) {
389416
return true
@@ -396,6 +423,10 @@ func (m *Matrix) HasVector(other *Vector) bool {
396423
// HasVectorUnordered returns whether or not a matrix has a vector whose unordered
397424
// values match exactly with another that is given.
398425
func (m *Matrix) HasVectorUnordered(other *Vector) bool {
426+
if other == nil {
427+
return false
428+
}
429+
399430
for _, v := range m.Vectors {
400431
if v.EqualUnordered(other) {
401432
return true
@@ -431,6 +462,10 @@ func (m *Matrix) UniqueValues() *Matrix {
431462

432463
// Match determines if Exclude directive matches the vector
433464
func (ex *Exclude) Match(vec *Vector) bool {
465+
if vec == nil {
466+
return false
467+
}
468+
434469
switch ex.Mode {
435470
case pb.Scenario_Filter_Exclude_MODE_EXACTLY:
436471
if vec.Equal(ex.Vector) {
@@ -460,6 +495,10 @@ func (ex *Exclude) Proto() *pb.Scenario_Filter_Exclude {
460495

461496
// FromProto unmarshals a proto Scenario_Filter_Exclude into itself
462497
func (ex *Exclude) FromProto(pfe *pb.Scenario_Filter_Exclude) {
498+
if pfe == nil {
499+
return
500+
}
501+
463502
ex.Vector = NewVectorFromProto(pfe.GetVector())
464503
ex.Mode = pfe.GetMode()
465504
}

internal/flightplan/scenario.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,21 @@ func (s *Scenario) Ref() *pb.Ref_Scenario {
7878

7979
// FromRef takes a unmarshals a scenario reference into itself
8080
func (s *Scenario) FromRef(ref *pb.Ref_Scenario) {
81+
if ref == nil {
82+
return
83+
}
84+
8185
s.Name = ref.GetId().GetName()
8286
s.Variants = NewVectorFromProto(ref.GetId().GetVariants())
8387
}
8488

8589
// Match takes a filter and determines whether or not the scenario matches
8690
// it.
8791
func (s *Scenario) Match(filter *ScenarioFilter) bool {
92+
if filter == nil {
93+
return false
94+
}
95+
8896
if filter.SelectAll {
8997
return true
9098
}
@@ -94,6 +102,18 @@ func (s *Scenario) Match(filter *ScenarioFilter) bool {
94102
return false
95103
}
96104

105+
// If our scenario doesn't have any variants make make sure we don't have
106+
// a filter with includes or excludes.
107+
if s.Variants == nil || len(s.Variants.elements) == 0 {
108+
if filter.Include != nil && len(filter.Include.elements) > 0 {
109+
return false
110+
}
111+
112+
if filter.Exclude != nil && len(filter.Exclude) > 0 {
113+
return false
114+
}
115+
}
116+
97117
// Make sure it matches any includes
98118
if filter.Include != nil && len(filter.Include.elements) > 0 {
99119
if !s.Variants.ContainsUnordered(filter.Include) {

internal/flightplan/scenario_filter_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010

1111
// Test_ScenarioFilter_WithScenarioFilterFromScenarioRef tests filtering a
1212
// scenario that was created from a scenario reference.
13-
func Test_ScenarioFilter_WithScenarioFilterFromScenarioRe(t *testing.T) {
13+
func Test_ScenarioFilter_WithScenarioFilterFromScenarioRef(t *testing.T) {
1414
ref := &pb.Ref_Scenario{
1515
Id: &pb.Scenario_ID{
1616
Name: "foo",
@@ -53,6 +53,7 @@ func Test_ScenarioFilter_ScenariosSelect(t *testing.T) {
5353
{Name: "upgrade", Variants: &Vector{elements: []Element{NewElement("backend", "raft"), NewElement("arch", "amd64")}}},
5454
{Name: "upgrade", Variants: &Vector{elements: []Element{NewElement("backend", "consul"), NewElement("arch", "arm64")}}},
5555
{Name: "upgrade", Variants: &Vector{elements: []Element{NewElement("backend", "consul"), NewElement("arch", "amd64")}}},
56+
{Name: "no-variant"},
5657
}
5758

5859
for _, test := range []struct {
@@ -65,7 +66,7 @@ func Test_ScenarioFilter_ScenariosSelect(t *testing.T) {
6566
"name only",
6667
scenarios,
6768
&ScenarioFilter{Name: "upgrade"},
68-
scenarios[4:],
69+
scenarios[4:8],
6970
},
7071
{
7172
"name no match",
@@ -111,6 +112,15 @@ func Test_ScenarioFilter_ScenariosSelect(t *testing.T) {
111112
},
112113
[]*Scenario{},
113114
},
115+
{
116+
"variant filter pass to scenario without variants",
117+
scenarios,
118+
&ScenarioFilter{
119+
Name: "no-variant",
120+
Include: &Vector{elements: []Element{NewElement("backend", "raft")}},
121+
},
122+
[]*Scenario{},
123+
},
114124
} {
115125
t.Run(test.desc, func(t *testing.T) {
116126
fp := &FlightPlan{

version/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ var (
1414
//
1515
// Version must conform to the format expected by github.com/hashicorp/go-version
1616
// for tests to work.
17-
Version = "0.0.18"
17+
Version = "0.0.19"
1818

1919
// VersionPrerelease is a pre-release marker for the version. If this is ""
2020
// (empty string) then it means that it is a final release. Otherwise, this

0 commit comments

Comments
 (0)