From 66dd8719e3889c10848a90399e8be8a5765b565d Mon Sep 17 00:00:00 2001 From: bin Date: Wed, 23 Jun 2021 11:48:29 +0800 Subject: [PATCH] runtime: refact virtcontainers/pkg/oci Use common functions wrapping logic of getting values from annotations, parsing bool/uint32/uint64 and setting to struct fields. Fixes: #2082 Signed-off-by: bin --- src/runtime/virtcontainers/pkg/oci/utils.go | 308 ++++++++---------- .../virtcontainers/pkg/oci/utils_test.go | 142 ++++++++ 2 files changed, 275 insertions(+), 175 deletions(-) diff --git a/src/runtime/virtcontainers/pkg/oci/utils.go b/src/runtime/virtcontainers/pkg/oci/utils.go index b007f3ae31..4db73a1655 100644 --- a/src/runtime/virtcontainers/pkg/oci/utils.go +++ b/src/runtime/virtcontainers/pkg/oci/utils.go @@ -463,30 +463,22 @@ func addHypervisorConfigOverrides(ocispec specs.Spec, config *vc.SandboxConfig, } } - if value, ok := ocispec.Annotations[vcAnnotations.DisableImageNvdimm]; ok { - disableNvdimm, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for use_nvdimm: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DisableImageNvdimm).setBool(func(disableNvdimm bool) { config.HypervisorConfig.DisableImageNvdimm = disableNvdimm + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.HotplugVFIOOnRootBus]; ok { - hotplugVFIOOnRootBus, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for hotplug_vfio_on_root_bus: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.HotplugVFIOOnRootBus).setBool(func(hotplugVFIOOnRootBus bool) { config.HypervisorConfig.HotplugVFIOOnRootBus = hotplugVFIOOnRootBus + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.PCIeRootPort]; ok { - pcieRootPort, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error parsing annotation for pcie_root_port: %v, Please specify an integer greater than or equal to 0", err) - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.PCIeRootPort).setUint(func(pcieRootPort uint64) { config.HypervisorConfig.PCIeRootPort = uint32(pcieRootPort) + }); err != nil { + return err } if value, ok := ocispec.Annotations[vcAnnotations.EntropySource]; ok { @@ -551,57 +543,43 @@ func addHypervisorPathOverrides(ocispec specs.Spec, config *vc.SandboxConfig, ru } func addHypervisorMemoryOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig, runtime RuntimeConfig) error { - if value, ok := ocispec.Annotations[vcAnnotations.DefaultMemory]; ok { - memorySz, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error encountered parsing annotation for default_memory: %v, please specify positive numeric value greater than 8", err) - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DefaultMemory).setUintWithCheck(func(memorySz uint64) error { if memorySz < vc.MinHypervisorMemory { return fmt.Errorf("Memory specified in annotation %s is less than minimum required %d, please specify a larger value", vcAnnotations.DefaultMemory, vc.MinHypervisorMemory) } - sbConfig.HypervisorConfig.MemorySize = uint32(memorySz) + return nil + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.MemSlots]; ok { - mslots, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error parsing annotation for memory_slots: %v, please specify positive numeric value", err) - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.MemSlots).setUint(func(mslots uint64) { if mslots > 0 { sbConfig.HypervisorConfig.MemSlots = uint32(mslots) } + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.MemOffset]; ok { - moffset, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return fmt.Errorf("Error parsing annotation for memory_offset: %v, please specify positive numeric value", err) - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.MemOffset).setUint(func(moffset uint64) { if moffset > 0 { sbConfig.HypervisorConfig.MemOffset = moffset } + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.VirtioMem]; ok { - virtioMem, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for enable_virtio_mem: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.VirtioMem).setBool(func(virtioMem bool) { sbConfig.HypervisorConfig.VirtioMem = virtioMem + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.MemPrealloc]; ok { - memPrealloc, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for enable_mem_prealloc: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.MemPrealloc).setBool(func(memPrealloc bool) { sbConfig.HypervisorConfig.MemPrealloc = memPrealloc + }); err != nil { + return err } if value, ok := ocispec.Annotations[vcAnnotations.EnableSwap]; ok { @@ -620,58 +598,41 @@ func addHypervisorMemoryOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig sbConfig.HypervisorConfig.FileBackedMemRootDir = value } - if value, ok := ocispec.Annotations[vcAnnotations.HugePages]; ok { - hugePages, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for enable_hugepages: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.HugePages).setBool(func(hugePages bool) { sbConfig.HypervisorConfig.HugePages = hugePages + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.IOMMU]; ok { - iommu, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for iommu: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.IOMMU).setBool(func(iommu bool) { sbConfig.HypervisorConfig.IOMMU = iommu + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.IOMMUPlatform]; ok { - deviceIOMMU, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for enable_iommu_platform: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.IOMMUPlatform).setBool(func(deviceIOMMU bool) { sbConfig.HypervisorConfig.IOMMUPlatform = deviceIOMMU + }); err != nil { + return err } + return nil } func addHypervisorCPUOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig) error { - if value, ok := ocispec.Annotations[vcAnnotations.DefaultVCPUs]; ok { - vcpus, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error encountered parsing annotation default_vcpus: %v, please specify numeric value", err) - } - - numCPUs := goruntime.NumCPU() + numCPUs := goruntime.NumCPU() + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DefaultVCPUs).setUintWithCheck(func(vcpus uint64) error { if uint32(vcpus) > uint32(numCPUs) { return fmt.Errorf("Number of cpus %d specified in annotation default_vcpus is greater than the number of CPUs %d on the system", vcpus, numCPUs) } - sbConfig.HypervisorConfig.NumVCPUs = uint32(vcpus) + return nil + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.DefaultMaxVCPUs]; ok { - maxVCPUs, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error encountered parsing annotation for default_maxvcpus: %v, please specify positive numeric value", err) - } - - numCPUs := goruntime.NumCPU() + return newAnnotationConfiguration(ocispec, vcAnnotations.DefaultMaxVCPUs).setUintWithCheck(func(maxVCPUs uint64) error { max := uint32(maxVCPUs) if max > uint32(numCPUs) { @@ -681,11 +642,9 @@ func addHypervisorCPUOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig) e if sbConfig.HypervisorType == vc.QemuHypervisor && max > vc.MaxQemuVCPUs() { return fmt.Errorf("Number of cpus %d in annotation default_maxvcpus is greater than max no of CPUs %d supported for qemu", max, vc.MaxQemuVCPUs()) } - sbConfig.HypervisorConfig.DefaultMaxVCPUs = max - } - - return nil + return nil + }) } func addHypervisorBlockOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig) error { @@ -705,52 +664,33 @@ func addHypervisorBlockOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig) } } - if value, ok := ocispec.Annotations[vcAnnotations.DisableBlockDeviceUse]; ok { - disableBlockDeviceUse, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for disable_block_device_use: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DisableBlockDeviceUse).setBool(func(disableBlockDeviceUse bool) { sbConfig.HypervisorConfig.DisableBlockDeviceUse = disableBlockDeviceUse + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.EnableIOThreads]; ok { - enableIOThreads, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for enable_iothreads: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.EnableIOThreads).setBool(func(enableIOThreads bool) { sbConfig.HypervisorConfig.EnableIOThreads = enableIOThreads + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.BlockDeviceCacheSet]; ok { - blockDeviceCacheSet, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for block_device_cache_set: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.BlockDeviceCacheSet).setBool(func(blockDeviceCacheSet bool) { sbConfig.HypervisorConfig.BlockDeviceCacheSet = blockDeviceCacheSet + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.BlockDeviceCacheDirect]; ok { - blockDeviceCacheDirect, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for block_device_cache_direct: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.BlockDeviceCacheDirect).setBool(func(blockDeviceCacheDirect bool) { sbConfig.HypervisorConfig.BlockDeviceCacheDirect = blockDeviceCacheDirect + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.BlockDeviceCacheNoflush]; ok { - blockDeviceCacheNoflush, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for block_device_cache_noflush: Please specify boolean value 'true|false'") - } - + return newAnnotationConfiguration(ocispec, vcAnnotations.BlockDeviceCacheNoflush).setBool(func(blockDeviceCacheNoflush bool) { sbConfig.HypervisorConfig.BlockDeviceCacheNoflush = blockDeviceCacheNoflush - } - - return nil + }) } func addHypervisorVirtioFsOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig, runtime RuntimeConfig) error { @@ -793,25 +733,19 @@ func addHypervisorVirtioFsOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConf sbConfig.HypervisorConfig.VirtioFSCache = value } - if value, ok := ocispec.Annotations[vcAnnotations.VirtioFSCacheSize]; ok { - cacheSize, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error parsing annotation for virtio_fs_cache_size: %v, please specify positive numeric value", err) - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.VirtioFSCacheSize).setUint(func(cacheSize uint64) { sbConfig.HypervisorConfig.VirtioFSCacheSize = uint32(cacheSize) + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.Msize9p]; ok { - msize9p, err := strconv.ParseUint(value, 10, 32) - if err != nil || msize9p == 0 { + return newAnnotationConfiguration(ocispec, vcAnnotations.Msize9p).setUintWithCheck(func(msize9p uint64) error { + if msize9p == 0 { return fmt.Errorf("Error parsing annotation for msize_9p, please specify positive numeric value") } - sbConfig.HypervisorConfig.Msize9p = uint32(msize9p) - } - - return nil + return nil + }) } func addHypervisporNetworkOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig) error { @@ -821,51 +755,35 @@ func addHypervisporNetworkOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConf } } - if value, ok := ocispec.Annotations[vcAnnotations.DisableVhostNet]; ok { - disableVhostNet, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for disable_vhost_net: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DisableVhostNet).setBool(func(disableVhostNet bool) { sbConfig.HypervisorConfig.DisableVhostNet = disableVhostNet + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.RxRateLimiterMaxRate]; ok { - rxRateLimiterMaxRate, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return fmt.Errorf("Error parsing annotation for rx_rate_limiter_max_rate: %v, Please specify an integer greater than or equal to 0", err) - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.RxRateLimiterMaxRate).setUint(func(rxRateLimiterMaxRate uint64) { sbConfig.HypervisorConfig.RxRateLimiterMaxRate = rxRateLimiterMaxRate + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.TxRateLimiterMaxRate]; ok { - txRateLimiterMaxRate, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return fmt.Errorf("Error parsing annotation for tx_rate_limiter_max_rate: %v, Please specify an integer greater than or equal to 0", err) - } + return newAnnotationConfiguration(ocispec, vcAnnotations.TxRateLimiterMaxRate).setUint(func(txRateLimiterMaxRate uint64) { sbConfig.HypervisorConfig.TxRateLimiterMaxRate = txRateLimiterMaxRate - } - - return nil + }) } func addRuntimeConfigOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig, runtime RuntimeConfig) error { - if value, ok := ocispec.Annotations[vcAnnotations.DisableGuestSeccomp]; ok { - disableGuestSeccomp, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for disable_guest_seccomp: Please specify boolean value 'true|false'") - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DisableGuestSeccomp).setBool(func(disableGuestSeccomp bool) { sbConfig.DisableGuestSeccomp = disableGuestSeccomp + }); err != nil { + return err } - if value, ok := ocispec.Annotations[vcAnnotations.SandboxCgroupOnly]; ok { - sandboxCgroupOnly, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for sandbox_cgroup_only: Please specify boolean value 'true|false'") - } - + if err := newAnnotationConfiguration(ocispec, vcAnnotations.SandboxCgroupOnly).setBool(func(sandboxCgroupOnly bool) { sbConfig.SandboxCgroupOnly = sandboxCgroupOnly + }); err != nil { + return err } if value, ok := ocispec.Annotations[vcAnnotations.Experimental]; ok { @@ -881,12 +799,10 @@ func addRuntimeConfigOverrides(ocispec specs.Spec, sbConfig *vc.SandboxConfig, r } } - if value, ok := ocispec.Annotations[vcAnnotations.DisableNewNetNs]; ok { - disableNewNetNs, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for experimental: Please specify boolean value 'true|false'") - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.DisableNewNetNs).setBool(func(disableNewNetNs bool) { sbConfig.NetworkConfig.DisableNewNetNs = disableNewNetNs + }); err != nil { + return err } if value, ok := ocispec.Annotations[vcAnnotations.InterNetworkModel]; ok { @@ -910,12 +826,10 @@ func addAgentConfigOverrides(ocispec specs.Spec, config *vc.SandboxConfig) error config.AgentConfig = c } - if value, ok := ocispec.Annotations[vcAnnotations.AgentTrace]; ok { - trace, err := strconv.ParseBool(value) - if err != nil { - return fmt.Errorf("Error parsing annotation for agent.trace: Please specify boolean value 'true|false'") - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.AgentTrace).setBool(func(trace bool) { c.Trace = trace + }); err != nil { + return err } if value, ok := ocispec.Annotations[vcAnnotations.AgentTraceMode]; ok { @@ -926,12 +840,10 @@ func addAgentConfigOverrides(ocispec specs.Spec, config *vc.SandboxConfig) error c.TraceType = value } - if value, ok := ocispec.Annotations[vcAnnotations.AgentContainerPipeSize]; ok { - containerPipeSize, err := strconv.ParseUint(value, 10, 32) - if err != nil { - return fmt.Errorf("Error parsing annotation for %s: Please specify uint32 value", vcAnnotations.AgentContainerPipeSize) - } + if err := newAnnotationConfiguration(ocispec, vcAnnotations.AgentContainerPipeSize).setUint(func(containerPipeSize uint64) { c.ContainerPipeSize = uint32(containerPipeSize) + }); err != nil { + return err } config.AgentConfig = c @@ -1166,3 +1078,49 @@ func IsCRIOContainerManager(spec *specs.Spec) bool { } return false } + +const ( + errAnnotationPositiveNumericKey = "Error parsing annotation for %s: Please specify positive numeric value" + errAnnotationBoolKey = "Error parsing annotation for %s: Please specify boolean value 'true|false'" +) + +type annotationConfiguration struct { + ocispec specs.Spec + key string +} + +func newAnnotationConfiguration(ocispec specs.Spec, key string) *annotationConfiguration { + return &annotationConfiguration{ + ocispec: ocispec, + key: key, + } +} + +func (a *annotationConfiguration) setBool(f func(bool)) error { + if value, ok := a.ocispec.Annotations[a.key]; ok { + boolValue, err := strconv.ParseBool(value) + if err != nil { + return fmt.Errorf(errAnnotationBoolKey, a.key) + } + f(boolValue) + } + return nil +} + +func (a *annotationConfiguration) setUint(f func(uint64)) error { + return a.setUintWithCheck(func(v uint64) error { + f(v) + return nil + }) +} + +func (a *annotationConfiguration) setUintWithCheck(f func(uint64) error) error { + if value, ok := a.ocispec.Annotations[a.key]; ok { + uintValue, err := strconv.ParseUint(value, 10, 64) + if err != nil { + return fmt.Errorf(errAnnotationPositiveNumericKey, a.key) + } + return f(uintValue) + } + return nil +} diff --git a/src/runtime/virtcontainers/pkg/oci/utils_test.go b/src/runtime/virtcontainers/pkg/oci/utils_test.go index 8e8a5a3ed3..71f9c9ea28 100644 --- a/src/runtime/virtcontainers/pkg/oci/utils_test.go +++ b/src/runtime/virtcontainers/pkg/oci/utils_test.go @@ -1103,3 +1103,145 @@ func TestIsCRIOContainerManager(t *testing.T) { assert.Equal(tc.result, result, "test case %d", (i + 1)) } } + +func TestParseAnnotationUintConfiguration(t *testing.T) { + assert := assert.New(t) + + const key = "my_key" + + validErr := fmt.Errorf("invalid value range: must between [10-1000]") + validFunc := func(v uint64) error { + if v < 10 || v > 1000 { + return validErr + } + return nil + } + + testCases := []struct { + annotations map[string]string + expected uint64 + err error + validFunc func(uint64) error + }{ + { + annotations: map[string]string{key: ""}, + expected: 0, + err: fmt.Errorf(errAnnotationPositiveNumericKey, key), + validFunc: nil, + }, + { + annotations: map[string]string{key: "a"}, + expected: 0, + err: fmt.Errorf(errAnnotationPositiveNumericKey, key), + validFunc: nil, + }, + { + annotations: map[string]string{key: "16"}, + expected: 16, + err: nil, + validFunc: nil, + }, + { + annotations: map[string]string{key: "16"}, + expected: 16, + err: nil, + validFunc: validFunc, + }, + { + annotations: map[string]string{key: "8"}, + expected: 0, + err: validErr, + validFunc: validFunc, + }, + { + annotations: map[string]string{key: "0"}, + expected: 0, + err: nil, + validFunc: nil, + }, + { + annotations: map[string]string{key: "-1"}, + expected: 0, + err: fmt.Errorf(errAnnotationPositiveNumericKey, key), + validFunc: nil, + }, + } + + for i := range testCases { + tc := testCases[i] + ocispec := specs.Spec{ + Annotations: tc.annotations, + } + var val uint64 = 0 + + err := newAnnotationConfiguration(ocispec, key).setUintWithCheck(func(v uint64) error { + if tc.validFunc != nil { + if err := tc.validFunc(v); err != nil { + return err + } + } + val = v + return nil + }) + + assert.Equal(tc.err, err, "test case %d check error", (i + 1)) + if tc.err == nil { + assert.Equal(tc.expected, val, "test case %d check parsed result", (i + 1)) + } + } +} + +func TestParseAnnotationBoolConfiguration(t *testing.T) { + assert := assert.New(t) + + const ( + u32Key = "u32_key" + u64Key = "u64_key" + boolKey = "bool_key" + ) + + testCases := []struct { + annotationKey string + annotationValueList []string + expected bool + err error + }{ + { + annotationKey: boolKey, + annotationValueList: []string{"1", "t", "T", "true", "TRUE", "True"}, + expected: true, + err: nil, + }, + { + annotationKey: boolKey, + annotationValueList: []string{"0", "f", "F", "false", "FALSE", "False"}, + expected: false, + err: nil, + }, + { + annotationKey: boolKey, + annotationValueList: []string{"a", "FalSE", "Fal", "TRue", "TRU", "falsE"}, + expected: false, + err: fmt.Errorf(errAnnotationBoolKey, boolKey), + }, + } + + for i := range testCases { + tc := testCases[i] + for _, annotaionValue := range tc.annotationValueList { + ocispec := specs.Spec{ + Annotations: map[string]string{tc.annotationKey: annotaionValue}, + } + var val bool = false + + err := newAnnotationConfiguration(ocispec, tc.annotationKey).setBool(func(v bool) { + val = v + }) + + assert.Equal(tc.err, err, "test case %d check error", (i + 1)) + if tc.err == nil { + assert.Equal(tc.expected, val, "test case %d check parsed result", (i + 1)) + } + } + } +}