Skip to content

Commit

Permalink
Deterministic template slice functions (#30)
Browse files Browse the repository at this point in the history
* Deterministic order of TmplObjectArray function

Template function objectArray did not order values deterministic.
This changes ensures this by ordering after all values are parsed.

The test assertion is changed to `assert.Equal` that matches the values
directly instead of `assert.ElementsMatch` that does not take order into
account.
With this change the "nil input" test case did not match expectatations.
This is due to `assert.ElementsMatch` accepting a nil value to match an
empty slice.
This means the output of the function have not changed, but our
assertion has gotten more strict.

Related to #29

* Deterministic order of TmplGetFiles function

Template function 'getFiles' did not order files deterministic.
This change ensures this by ordering all files by name.

Related to #29

* Deterministic order of TmplArray function

Template function array did not order values deterministic.
This change ensures this for string arrays.
We have no trivial way of ordering unknown types so this is omitted.

I removed some code handling objects in the input.
The function is not intended to handle these cases.
TmplObjectArray should be used instead.

Related to #29
  • Loading branch information
Crevil authored and kaspernissen committed Feb 12, 2019
1 parent da3d85b commit 465dfed
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 24 deletions.
56 changes: 34 additions & 22 deletions pkg/templates/funcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"text/template"

Expand Down Expand Up @@ -84,26 +85,34 @@ func TmplInt(path string, input interface{}) int {
return value.(int)
}

// TODO: Add description
// TmplArray parses a YAML path in the input parameter aas an arraay.
//
// The array can be of any type but order is only deterministic for strings.
func TmplArray(path string, input interface{}) []interface{} {
value := TmplGet(path, input)
switch value.(type) {
case map[interface{}]interface{}:
values := []interface{}{}
for _, v := range input.(map[interface{}]interface{}) {
values = append(values, v)
}
return values
case map[string]interface{}:
values := []interface{}{}
for _, v := range input.(map[string]interface{}) {
values = append(values, v)
rawValue := TmplGet(path, input)
rawValues, ok := rawValue.([]interface{})
if !ok {
return nil
}

var stringValues []string
var values []interface{}
for _, v := range rawValues {
s, ok := v.(string)
if ok {
stringValues = append(stringValues, s)
continue
}
return values
case []interface{}:
return value.([]interface{})
values = append(values, v)
}
return nil
sort.Slice(stringValues, func(i, j int) bool {
return stringValues[i] < stringValues[j]
})
for _, v := range stringValues {
values = append(values, v)
}

return values
}

// TODO: Add description
Expand All @@ -112,21 +121,21 @@ func TmplObjectArray(path string, input interface{}) []KeyValuePair {
return nil
}
value := TmplGet(path, input)
values := []KeyValuePair{}
switch value.(type) {
case map[interface{}]interface{}:
values := []KeyValuePair{}
for k, v := range value.(map[interface{}]interface{}) {
values = append(values, KeyValuePair{Key: k.(string), Value: v})
}
return values
case map[string]interface{}:
values := []KeyValuePair{}
for k, v := range value.(map[string]interface{}) {
values = append(values, KeyValuePair{Key: k, Value: v})
}
return values
}
return nil
sort.Slice(values, func(i, j int) bool {
return values[i].Key < values[j].Key
})
return values
}

func TmplIs(a interface{}, b interface{}) bool {
Expand Down Expand Up @@ -189,6 +198,9 @@ func TmplGetFiles(directoryPath string) []os.FileInfo {
if err != nil {
return []os.FileInfo{} // TODO: Print error to shuttle output
}
sort.Slice(files, func(i, j int) bool {
return files[i].Name() < files[j].Name()
})
return files
}

Expand Down
172 changes: 170 additions & 2 deletions pkg/templates/funcs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,128 @@ func TestTmplInt(t *testing.T) {
}
}

func TestTmplArray(t *testing.T) {
type input struct {
path string
data interface{}
}
tt := []struct {
name string
input input
output []interface{}
}{
{
name: "nil input",
input: input{
path: "b",
data: nil,
},
output: nil,
},
{
name: "empty input",
input: input{
path: "b",
data: fromYaml(""),
},
output: nil,
},
{
name: "empty array",
input: input{
path: "b",
data: fromYaml(`
b:
`),
},
output: nil,
},
{
name: "single value string array",
input: input{
path: "b",
data: fromYaml(`
b:
- 'a'
`),
},
output: []interface{}{
"a",
},
},
{
name: "single value object array",
input: input{
path: "b",
data: fromYaml(`
b:
- name: 'a'
field: 'b'
`),
},
output: []interface{}{
map[interface{}]interface{}{
"name": "a",
"field": "b",
},
},
},
{
name: "large string array testing for deterministic order",
input: input{
path: "a",
data: fromYaml(`a:
- 'b'
- 'd'
- 'e'
- 'g'
- 'h'
- 'f'
- 'i'
- 's'
- 'k'
- 'c'
- 'l'
- 'm'
- 'n'
- 'o'
- 'r'
- 'j'
- 'p'
- 'q'
`),
},
output: []interface{}{
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
"j",
"k",
"l",
"m",
"n",
"o",
"p",
"q",
"r",
"s",
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
output := TmplArray(tc.input.path, tc.input.data)

assert.Equal(t, tc.output, output, "output does not match the expected")
})
}
}

func TestTmpObjArray(t *testing.T) {
type input struct {
path string
Expand All @@ -73,7 +195,7 @@ func TestTmpObjArray(t *testing.T) {
path: "b",
data: nil,
},
output: []KeyValuePair{},
output: nil,
},
{
name: "empty input",
Expand All @@ -98,6 +220,52 @@ b:
{Key: "h", Value: "ewff"},
},
},
{
name: "large set testing for deterministic order",
input: input{
path: "a",
data: fromYaml(`a:
'b': 2
'd': 4
'e': 5
'g': 7
'h': 8
'f': 6
'i': 9
's': 19
'k': 11
'c': 3
'l': 12
'm': 13
'n': 14
'o': 15
'r': 18
'j': 10
'p': 16
'q': 17
`),
},
output: []KeyValuePair{
{Key: "b", Value: 2},
{Key: "c", Value: 3},
{Key: "d", Value: 4},
{Key: "e", Value: 5},
{Key: "f", Value: 6},
{Key: "g", Value: 7},
{Key: "h", Value: 8},
{Key: "i", Value: 9},
{Key: "j", Value: 10},
{Key: "k", Value: 11},
{Key: "l", Value: 12},
{Key: "m", Value: 13},
{Key: "n", Value: 14},
{Key: "o", Value: 15},
{Key: "p", Value: 16},
{Key: "q", Value: 17},
{Key: "r", Value: 18},
{Key: "s", Value: 19},
},
},
{
name: "non object value in path",
input: input{
Expand All @@ -111,7 +279,7 @@ b:
t.Run(tc.name, func(t *testing.T) {
output := TmplObjectArray(tc.input.path, tc.input.data)

assert.ElementsMatch(t, tc.output, output, "output does not match the expected")
assert.Equal(t, tc.output, output, "output does not match the expected")
})
}
}
Expand Down

0 comments on commit 465dfed

Please sign in to comment.