platform_build_blueprint/proptools/unpack_test.go
Cole Faust 6437d4e737 Select statements
Select statements are a new blueprint feature inspired by bazel's select
statements. They are essentially alternative syntax for soong config
variables that require less boilerplate. In addition, they support
making decisions based on a module's variant, which will eliminate
the need for manual property struct manipulation, such as the arch
mutator's arch: and target: properties.

In order to support decisions based on the variant, select statements
cannot be evaluated as soon as they're parsed. Instead, they must be
stored in the property struct unevaluated. This means that individual
properties need to change their type from say, string, to
Configurable[string]. Currently, only configurable strings, bools, and
string slices are supported, but more types can be added later.
The module implementation must call my_property.Evaluate(ctx) in order
to get the final, resolved value of the select statement.

Bug: 323382414
Test: go tests
Change-Id: I62f8721d7f0ac3d1df4a06d7eaa260a5aa7fcba3
2024-03-06 15:00:39 -08:00

1423 lines
24 KiB
Go

// Copyright 2014 Google Inc. All rights reserved.
//
// 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 proptools
import (
"bytes"
"reflect"
"testing"
"github.com/google/blueprint/parser"
)
var validUnpackTestCases = []struct {
name string
input string
output []interface{}
empty []interface{}
errs []error
}{
{
name: "blank and unset",
input: `
m {
s: "abc",
blank: "",
}
`,
output: []interface{}{
&struct {
S *string
Blank *string
Unset *string
}{
S: StringPtr("abc"),
Blank: StringPtr(""),
Unset: nil,
},
},
},
{
name: "string",
input: `
m {
s: "abc",
}
`,
output: []interface{}{
&struct {
S string
}{
S: "abc",
},
},
},
{
name: "bool",
input: `
m {
isGood: true,
}
`,
output: []interface{}{
&struct {
IsGood bool
}{
IsGood: true,
},
},
},
{
name: "boolptr",
input: `
m {
isGood: true,
isBad: false,
}
`,
output: []interface{}{
&struct {
IsGood *bool
IsBad *bool
IsUgly *bool
}{
IsGood: BoolPtr(true),
IsBad: BoolPtr(false),
IsUgly: nil,
},
},
},
{
name: "slice",
input: `
m {
stuff: ["asdf", "jkl;", "qwert",
"uiop", "bnm,"],
empty: []
}
`,
output: []interface{}{
&struct {
Stuff []string
Empty []string
Nil []string
NonString []struct{ S string } `blueprint:"mutated"`
}{
Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
Empty: []string{},
Nil: nil,
NonString: nil,
},
},
},
{
name: "double nested",
input: `
m {
nested: {
nested: {
s: "abc",
},
},
}
`,
output: []interface{}{
&struct {
Nested struct {
Nested struct {
S string
}
}
}{
Nested: struct{ Nested struct{ S string } }{
Nested: struct{ S string }{
S: "abc",
},
},
},
},
},
{
name: "nested",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested struct {
S string
}
}{
Nested: struct{ S string }{
S: "abc",
},
},
},
},
{
name: "nested interface",
input: `
m {
nested: {
s: "def",
}
}
`,
output: []interface{}{
&struct {
Nested interface{}
}{
Nested: &struct{ S string }{
S: "def",
},
},
},
},
{
name: "mixed",
input: `
m {
nested: {
foo: "abc",
},
bar: false,
baz: ["def", "ghi"],
}
`,
output: []interface{}{
&struct {
Nested struct {
Foo string
}
Bar bool
Baz []string
}{
Nested: struct{ Foo string }{
Foo: "abc",
},
Bar: false,
Baz: []string{"def", "ghi"},
},
},
},
{
name: "filter",
input: `
m {
nested: {
foo: "abc",
},
bar: false,
baz: ["def", "ghi"],
}
`,
output: []interface{}{
&struct {
Nested struct {
Foo string `allowNested:"true"`
} `blueprint:"filter(allowNested:\"true\")"`
Bar bool
Baz []string
}{
Nested: struct {
Foo string `allowNested:"true"`
}{
Foo: "abc",
},
Bar: false,
Baz: []string{"def", "ghi"},
},
},
},
// List of maps
{
name: "list of structs",
input: `
m {
mapslist: [
{
foo: "abc",
bar: true,
},
{
foo: "def",
bar: false,
}
],
}
`,
output: []interface{}{
&struct {
Mapslist []struct {
Foo string
Bar bool
}
}{
Mapslist: []struct {
Foo string
Bar bool
}{
{Foo: "abc", Bar: true},
{Foo: "def", Bar: false},
},
},
},
},
// List of pointers to structs
{
name: "list of pointers to structs",
input: `
m {
mapslist: [
{
foo: "abc",
bar: true,
},
{
foo: "def",
bar: false,
}
],
}
`,
output: []interface{}{
&struct {
Mapslist []*struct {
Foo string
Bar bool
}
}{
Mapslist: []*struct {
Foo string
Bar bool
}{
{Foo: "abc", Bar: true},
{Foo: "def", Bar: false},
},
},
},
},
// List of lists
{
name: "list of lists",
input: `
m {
listoflists: [
["abc",],
["def",],
],
}
`,
output: []interface{}{
&struct {
Listoflists [][]string
}{
Listoflists: [][]string{
[]string{"abc"},
[]string{"def"},
},
},
},
},
// Multilevel
{
name: "multilevel",
input: `
m {
name: "mymodule",
flag: true,
settings: ["foo1", "foo2", "foo3",],
perarch: {
arm: "32",
arm64: "64",
},
configvars: [
{ var: "var1", values: ["1.1", "1.2", ], },
{ var: "var2", values: ["2.1", ], },
],
}
`,
output: []interface{}{
&struct {
Name string
Flag bool
Settings []string
Perarch *struct {
Arm string
Arm64 string
}
Configvars []struct {
Var string
Values []string
}
}{
Name: "mymodule",
Flag: true,
Settings: []string{"foo1", "foo2", "foo3"},
Perarch: &struct {
Arm string
Arm64 string
}{Arm: "32", Arm64: "64"},
Configvars: []struct {
Var string
Values []string
}{
{Var: "var1", Values: []string{"1.1", "1.2"}},
{Var: "var2", Values: []string{"2.1"}},
},
},
},
},
// Anonymous struct
{
name: "embedded struct",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
EmbeddedStruct
Nested struct {
EmbeddedStruct
}
}{
EmbeddedStruct: EmbeddedStruct{
S: "abc",
},
Nested: struct {
EmbeddedStruct
}{
EmbeddedStruct: EmbeddedStruct{
S: "def",
},
},
},
},
},
// Anonymous interface
{
name: "embedded interface",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
EmbeddedInterface
Nested struct {
EmbeddedInterface
}
}{
EmbeddedInterface: &struct{ S string }{
S: "abc",
},
Nested: struct {
EmbeddedInterface
}{
EmbeddedInterface: &struct{ S string }{
S: "def",
},
},
},
},
},
// Anonymous struct with name collision
{
name: "embedded name collision",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
S string
EmbeddedStruct
Nested struct {
S string
EmbeddedStruct
}
}{
S: "abc",
EmbeddedStruct: EmbeddedStruct{
S: "abc",
},
Nested: struct {
S string
EmbeddedStruct
}{
S: "def",
EmbeddedStruct: EmbeddedStruct{
S: "def",
},
},
},
},
},
// Anonymous interface with name collision
{
name: "embeded interface name collision",
input: `
m {
s: "abc",
nested: {
s: "def",
},
}
`,
output: []interface{}{
&struct {
S string
EmbeddedInterface
Nested struct {
S string
EmbeddedInterface
}
}{
S: "abc",
EmbeddedInterface: &struct{ S string }{
S: "abc",
},
Nested: struct {
S string
EmbeddedInterface
}{
S: "def",
EmbeddedInterface: &struct{ S string }{
S: "def",
},
},
},
},
},
// Variables
{
name: "variables",
input: `
list = ["abc"]
string = "def"
list_with_variable = [string]
struct_value = { name: "foo" }
m {
s: string,
list: list,
list2: list_with_variable,
structattr: struct_value,
}
`,
output: []interface{}{
&struct {
S string
List []string
List2 []string
Structattr struct {
Name string
}
}{
S: "def",
List: []string{"abc"},
List2: []string{"def"},
Structattr: struct {
Name string
}{
Name: "foo",
},
},
},
},
// Multiple property structs
{
name: "multiple",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested struct {
S string
}
}{
Nested: struct{ S string }{
S: "abc",
},
},
&struct {
Nested struct {
S string
}
}{
Nested: struct{ S string }{
S: "abc",
},
},
&struct {
}{},
},
},
// Nil pointer to struct
{
name: "nil struct pointer",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested *struct {
S string
}
}{
Nested: &struct{ S string }{
S: "abc",
},
},
},
empty: []interface{}{
&struct {
Nested *struct {
S string
}
}{},
},
},
// Interface containing nil pointer to struct
{
name: "interface nil struct pointer",
input: `
m {
nested: {
s: "abc",
}
}
`,
output: []interface{}{
&struct {
Nested interface{}
}{
Nested: &EmbeddedStruct{
S: "abc",
},
},
},
empty: []interface{}{
&struct {
Nested interface{}
}{
Nested: (*EmbeddedStruct)(nil),
},
},
},
// Factory set properties
{
name: "factory properties",
input: `
m {
string: "abc",
string_ptr: "abc",
bool: false,
bool_ptr: false,
list: ["a", "b", "c"],
}
`,
output: []interface{}{
&struct {
String string
String_ptr *string
Bool bool
Bool_ptr *bool
List []string
}{
String: "012abc",
String_ptr: StringPtr("abc"),
Bool: true,
Bool_ptr: BoolPtr(false),
List: []string{"0", "1", "2", "a", "b", "c"},
},
},
empty: []interface{}{
&struct {
String string
String_ptr *string
Bool bool
Bool_ptr *bool
List []string
}{
String: "012",
String_ptr: StringPtr("012"),
Bool: true,
Bool_ptr: BoolPtr(true),
List: []string{"0", "1", "2"},
},
},
},
// Captitalized property
{
input: `
m {
CAPITALIZED: "foo",
}
`,
output: []interface{}{
&struct {
CAPITALIZED string
}{
CAPITALIZED: "foo",
},
},
},
{
name: "String configurable property that isn't configured",
input: `
m {
foo: "bar"
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
}{
Foo: Configurable[string]{
propertyName: "foo",
typ: parser.SelectTypeUnconfigured,
cases: map[string]string{
default_select_branch_name: "bar",
},
appendWrapper: &appendWrapper[string]{},
},
},
},
},
{
name: "Bool configurable property that isn't configured",
input: `
m {
foo: true,
}
`,
output: []interface{}{
&struct {
Foo Configurable[bool]
}{
Foo: Configurable[bool]{
propertyName: "foo",
typ: parser.SelectTypeUnconfigured,
cases: map[string]bool{
default_select_branch_name: true,
},
appendWrapper: &appendWrapper[bool]{},
},
},
},
},
{
name: "String list configurable property that isn't configured",
input: `
m {
foo: ["a", "b"],
}
`,
output: []interface{}{
&struct {
Foo Configurable[[]string]
}{
Foo: Configurable[[]string]{
propertyName: "foo",
typ: parser.SelectTypeUnconfigured,
cases: map[string][]string{
default_select_branch_name: {"a", "b"},
},
appendWrapper: &appendWrapper[[]string]{},
},
},
},
},
{
name: "Configurable property",
input: `
m {
foo: select(soong_config_variable("my_namespace", "my_variable"), {
"a": "a2",
"b": "b2",
_: "c2",
})
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
}{
Foo: Configurable[string]{
propertyName: "foo",
typ: parser.SelectTypeSoongConfigVariable,
condition: "my_namespace:my_variable",
cases: map[string]string{
"a": "a2",
"b": "b2",
default_select_branch_name: "c2",
},
appendWrapper: &appendWrapper[string]{},
},
},
},
},
{
name: "Configurable property appending",
input: `
m {
foo: select(soong_config_variable("my_namespace", "my_variable"), {
"a": "a2",
"b": "b2",
_: "c2",
}) + select(soong_config_variable("my_namespace", "my_2nd_variable"), {
"d": "d2",
"e": "e2",
_: "f2",
})
}
`,
output: []interface{}{
&struct {
Foo Configurable[string]
}{
Foo: Configurable[string]{
propertyName: "foo",
typ: parser.SelectTypeSoongConfigVariable,
condition: "my_namespace:my_variable",
cases: map[string]string{
"a": "a2",
"b": "b2",
default_select_branch_name: "c2",
},
appendWrapper: &appendWrapper[string]{
append: Configurable[string]{
propertyName: "foo",
typ: parser.SelectTypeSoongConfigVariable,
condition: "my_namespace:my_2nd_variable",
cases: map[string]string{
"d": "d2",
"e": "e2",
default_select_branch_name: "f2",
},
appendWrapper: &appendWrapper[string]{},
},
},
},
},
},
},
}
func TestUnpackProperties(t *testing.T) {
for _, testCase := range validUnpackTestCases {
t.Run(testCase.name, func(t *testing.T) {
r := bytes.NewBufferString(testCase.input)
file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected parse errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
for _, def := range file.Defs {
module, ok := def.(*parser.Module)
if !ok {
continue
}
var output []interface{}
if len(testCase.empty) > 0 {
for _, p := range testCase.empty {
output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
}
} else {
for _, p := range testCase.output {
output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
}
}
_, errs = UnpackProperties(module.Properties, output...)
if len(errs) != 0 && len(testCase.errs) == 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected unpack errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
} else if !reflect.DeepEqual(errs, testCase.errs) {
t.Errorf("test case: %s", testCase.input)
t.Errorf("incorrect errors:")
t.Errorf(" expected: %+v", testCase.errs)
t.Errorf(" got: %+v", errs)
}
if len(output) != len(testCase.output) {
t.Fatalf("incorrect number of property structs, expected %d got %d",
len(testCase.output), len(output))
}
for i := range output {
got := reflect.ValueOf(output[i]).Interface()
if !reflect.DeepEqual(got, testCase.output[i]) {
t.Errorf("test case: %s", testCase.input)
t.Errorf("incorrect output:")
t.Errorf(" expected: %+v", testCase.output[i])
t.Errorf(" got: %+v", got)
}
}
}
})
}
}
func TestUnpackErrors(t *testing.T) {
testCases := []struct {
name string
input string
output []interface{}
errors []string
}{
{
name: "missing",
input: `
m {
missing: true,
}
`,
output: []interface{}{},
errors: []string{`<input>:3:13: unrecognized property "missing"`},
},
{
name: "missing nested",
input: `
m {
nested: {
missing: true,
},
}
`,
output: []interface{}{
&struct {
Nested struct{}
}{},
},
errors: []string{`<input>:4:14: unrecognized property "nested.missing"`},
},
{
name: "mutated",
input: `
m {
mutated: true,
}
`,
output: []interface{}{
&struct {
Mutated bool `blueprint:"mutated"`
}{},
},
errors: []string{`<input>:3:13: mutated field mutated cannot be set in a Blueprint file`},
},
{
name: "nested mutated",
input: `
m {
nested: {
mutated: true,
},
}
`,
output: []interface{}{
&struct {
Nested struct {
Mutated bool `blueprint:"mutated"`
}
}{},
},
errors: []string{`<input>:4:14: mutated field nested.mutated cannot be set in a Blueprint file`},
},
{
name: "duplicate",
input: `
m {
exists: true,
exists: true,
}
`,
output: []interface{}{
&struct {
Exists bool
}{},
},
errors: []string{
`<input>:4:12: property "exists" already defined`,
`<input>:3:12: <-- previous definition here`,
},
},
{
name: "nested duplicate",
input: `
m {
nested: {
exists: true,
exists: true,
},
}
`,
output: []interface{}{
&struct {
Nested struct {
Exists bool
}
}{},
},
errors: []string{
`<input>:5:13: property "nested.exists" already defined`,
`<input>:4:13: <-- previous definition here`,
},
},
{
name: "wrong type",
input: `
m {
int: "foo",
}
`,
output: []interface{}{
&struct {
Int *int64
}{},
},
errors: []string{
`<input>:3:11: can't assign string value to int64 property "int"`,
},
},
{
name: "wrong type for map",
input: `
m {
map: "foo",
}
`,
output: []interface{}{
&struct {
Map struct {
S string
}
}{},
},
errors: []string{
`<input>:3:11: can't assign string value to map property "map"`,
},
},
{
name: "wrong type for list",
input: `
m {
list: "foo",
}
`,
output: []interface{}{
&struct {
List []string
}{},
},
errors: []string{
`<input>:3:12: can't assign string value to list property "list"`,
},
},
{
name: "wrong type for list of maps",
input: `
m {
map_list: "foo",
}
`,
output: []interface{}{
&struct {
Map_list []struct {
S string
}
}{},
},
errors: []string{
`<input>:3:16: can't assign string value to list property "map_list"`,
},
},
{
name: "non-existent property",
input: `
m {
foo: {
foo_prop1: true,
foo_prop2: false,
foo_prop3: true,
},
bar: {
bar_prop: false,
},
baz: true,
exist: false,
}
`,
output: []interface{}{
&struct {
Foo struct {
Foo_prop1 bool
}
Exist bool
}{},
},
errors: []string{
`<input>:5:16: unrecognized property "foo.foo_prop2"`,
`<input>:6:16: unrecognized property "foo.foo_prop3"`,
`<input>:9:15: unrecognized property "bar.bar_prop"`,
`<input>:11:9: unrecognized property "baz"`,
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
r := bytes.NewBufferString(testCase.input)
file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected parse errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
for _, def := range file.Defs {
module, ok := def.(*parser.Module)
if !ok {
continue
}
var output []interface{}
for _, p := range testCase.output {
output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
}
_, errs = UnpackProperties(module.Properties, output...)
printErrors := false
for _, expectedErr := range testCase.errors {
foundError := false
for _, err := range errs {
if err.Error() == expectedErr {
foundError = true
}
}
if !foundError {
t.Errorf("expected error %s", expectedErr)
printErrors = true
}
}
if printErrors {
t.Errorf("got errors:")
for _, err := range errs {
t.Errorf(" %s", err.Error())
}
}
}
})
}
}
func BenchmarkUnpackProperties(b *testing.B) {
run := func(b *testing.B, props []interface{}, input string) {
b.ReportAllocs()
b.StopTimer()
r := bytes.NewBufferString(input)
file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
if len(errs) != 0 {
b.Errorf("test case: %s", input)
b.Errorf("unexpected parse errors:")
for _, err := range errs {
b.Errorf(" %s", err)
}
b.FailNow()
}
for i := 0; i < b.N; i++ {
for _, def := range file.Defs {
module, ok := def.(*parser.Module)
if !ok {
continue
}
var output []interface{}
for _, p := range props {
output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
}
b.StartTimer()
_, errs = UnpackProperties(module.Properties, output...)
b.StopTimer()
if len(errs) > 0 {
b.Errorf("unexpected unpack errors:")
for _, err := range errs {
b.Errorf(" %s", err)
}
}
}
}
}
b.Run("basic", func(b *testing.B) {
props := []interface{}{
&struct {
Nested struct {
S string
}
}{},
}
bp := `
m {
nested: {
s: "abc",
},
}
`
run(b, props, bp)
})
b.Run("interface", func(b *testing.B) {
props := []interface{}{
&struct {
Nested interface{}
}{
Nested: (*struct {
S string
})(nil),
},
}
bp := `
m {
nested: {
s: "abc",
},
}
`
run(b, props, bp)
})
b.Run("many", func(b *testing.B) {
props := []interface{}{
&struct {
A *string
B *string
C *string
D *string
E *string
F *string
G *string
H *string
I *string
J *string
}{},
}
bp := `
m {
a: "a",
b: "b",
c: "c",
d: "d",
e: "e",
f: "f",
g: "g",
h: "h",
i: "i",
j: "j",
}
`
run(b, props, bp)
})
b.Run("deep", func(b *testing.B) {
props := []interface{}{
&struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
Nested struct {
S string
}
}
}
}
}
}
}
}
}
}
}{},
}
bp := `
m {
nested: { nested: { nested: { nested: { nested: {
nested: { nested: { nested: { nested: { nested: {
s: "abc",
}, }, }, }, },
}, }, }, }, },
}
`
run(b, props, bp)
})
b.Run("mix", func(b *testing.B) {
props := []interface{}{
&struct {
Name string
Flag bool
Settings []string
Perarch *struct {
Arm string
Arm64 string
}
Configvars []struct {
Name string
Values []string
}
}{},
}
bp := `
m {
name: "mymodule",
flag: true,
settings: ["foo1", "foo2", "foo3",],
perarch: {
arm: "32",
arm64: "64",
},
configvars: [
{ name: "var1", values: ["var1:1", "var1:2", ], },
{ name: "var2", values: ["var2:1", "var2:2", ], },
],
}
`
run(b, props, bp)
})
}
func TestRemoveUnnecessaryUnusedNames(t *testing.T) {
testCases := []struct {
name string
input []string
output []string
}{
{
name: "no unused names",
input: []string{},
output: []string{},
},
{
name: "only one unused name",
input: []string{"a.b.c"},
output: []string{"a.b.c"},
},
{
name: "unused names in a chain",
input: []string{"a", "a.b", "a.b.c"},
output: []string{"a.b.c"},
},
{
name: "unused names unrelated",
input: []string{"a.b.c", "s.t", "x.y"},
output: []string{"a.b.c", "s.t", "x.y"},
},
{
name: "unused names partially related one",
input: []string{"a.b", "a.b.c", "a.b.d"},
output: []string{"a.b.c", "a.b.d"},
},
{
name: "unused names partially related two",
input: []string{"a", "a.b.c", "a.c"},
output: []string{"a.b.c", "a.c"},
},
{
name: "unused names partially related three",
input: []string{"a.b.c", "b.c", "c"},
output: []string{"a.b.c", "b.c", "c"},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
simplifiedNames := removeUnnecessaryUnusedNames(testCase.input)
if !reflect.DeepEqual(simplifiedNames, testCase.output) {
t.Errorf("test case: %s", testCase.name)
t.Errorf(" input: %s", testCase.input)
t.Errorf(" expect: %s", testCase.output)
t.Errorf(" got: %s", simplifiedNames)
}
})
}
}