github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/pkg/test/runner/config.go (about) 1 // Copyright 2021 The kpt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package runner 16 17 import ( 18 "fmt" 19 "os" 20 "path/filepath" 21 22 "github.com/GoogleContainerTools/kpt/internal/types" 23 "sigs.k8s.io/kustomize/kyaml/yaml" 24 ) 25 26 // EvalTestCaseConfig contains the config only for imperative 27 // function run 28 type EvalTestCaseConfig struct { 29 // ExecPath is a path to the executable file that will be run as function 30 // Mutually exclusive with Image. 31 // The path should be separated by slash '/' 32 ExecPath string `json:"execPath,omitempty" yaml:"execPath,omitempty"` 33 // execUniquePath is an absolute, OS-specific path to exec file. 34 execUniquePath types.UniquePath 35 // Image is the image name for the function 36 Image string `json:"image,omitempty" yaml:"image,omitempty"` 37 // Args are the arguments that will be passed into function. 38 // Args will be passed as 'key=value' format after the '--' in command. 39 Args map[string]string `json:"args,omitempty" yaml:"args,omitempty"` 40 // Network indicates is network accessible from the function container. Default: false 41 Network bool `json:"network,omitempty" yaml:"network,omitempty"` 42 // IncludeMetaResources enables including meta resources, like Kptfile, 43 // in the function input. Default: false 44 IncludeMetaResources bool `json:"includeMetaResources,omitempty" yaml:"includeMetaResources,omitempty"` 45 // FnConfig is the path to the function config file. 46 // The path should be separated by slash '/' 47 FnConfig string `json:"fnConfig,omitempty" yaml:"fnConfig,omitempty"` 48 // fnConfigUniquePath is an absolute, OS-specific path to function config file. 49 fnConfigUniquePath types.UniquePath 50 } 51 52 // TestCaseConfig contains the config information for the test case 53 type TestCaseConfig struct { 54 // ExitCode is the expected exit code from the kpt commands. Default: 0 55 ExitCode int `json:"exitCode,omitempty" yaml:"exitCode,omitempty"` 56 57 // StdErr is the expected standard error output and should be checked 58 // when a nonzero exit code is expected. Default: "" 59 StdErr string `json:"stdErr,omitempty" yaml:"stdErr,omitempty"` 60 // StdErrRegEx is the regular expression to match standard error output and should be checked 61 // when a nonzero exit code is expected. Default: "" 62 StdErrRegEx string `json:"stdErrRegEx,omitempty" yaml:"stdErrRegEx,omitempty"` 63 64 // StdOut is the expected standard output from running the command. 65 // Default: "" 66 StdOut string `json:"stdOut,omitempty" yaml:"stdOut,omitempty"` 67 68 // Sequential means should this test case be run sequentially. Default: false 69 Sequential bool `json:"sequential,omitempty" yaml:"sequential,omitempty"` 70 71 // ImagePullPolicy controls the image pulling behavior. It can be set to one 72 // of `Always`, `IfNotPresent` and `Never`. If unspecified, the default will 73 // be the same as the CLI flag. 74 ImagePullPolicy string `json:"imagePullPolicy,omitempty" yaml:"imagePullPolicy,omitempty"` 75 76 // Runtimes controls if a test case should be skipped. If the current runtime doesn't match 77 // any of the desired runtimes here, the test case will be skipped. Valid values are `docker` 78 // and `podman`. If unspecified, it will match any runtime. 79 Runtimes []string `json:"runtimes,omitempty" yaml:"runtimes,omitempty"` 80 81 // AllowExec determines if `fn render` needs to be invoked with `--allow-exec` flag 82 AllowExec bool `json:"allowExec,omitempty" yaml:"allowExec,omitempty"` 83 84 // AllowExec determines if `fn render` needs to be invoked with `--allow-network` flag 85 AllowNetwork bool `json:"allowNetwork,omitempty" yaml:"allowNetwork,omitempty"` 86 87 // AllowWasm determines if `fn render` needs to be invoked with `--allow-alpha-wasm` flag 88 AllowWasm bool `json:"allowWasm,omitempty" yaml:"allowWasm,omitempty"` 89 90 // Skip means should this test case be skipped. Default: false 91 Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"` 92 93 // Debug means will the debug behavior be enabled. Default: false 94 // Debug behavior: 95 // 1. Keep the temporary directory used to run the test cases 96 // after test. 97 Debug bool `json:"debug,omitempty" yaml:"debug,omitempty"` 98 99 // TestType is the type of the test case. Possible value: ['render', 'eval'] 100 // Default: 'render' 101 TestType string `json:"testType,omitempty" yaml:"testType,omitempty"` 102 103 // DisableOutputTruncate indicates should error output be truncated 104 DisableOutputTruncate bool `json:"disableOutputTruncate,omitempty" yaml:"disableOutputTruncate,omitempty"` 105 106 // EvalConfig is the configs for eval tests 107 EvalConfig *EvalTestCaseConfig `json:",inline" yaml:",inline"` 108 109 // Environment variables to be set for the test case. 110 Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` 111 } 112 113 func (c *TestCaseConfig) RunCount() int { 114 return 2 115 } 116 117 func newTestCaseConfig(path string) (TestCaseConfig, error) { 118 configPath := filepath.Join(path, expectedDir, expectedConfigFile) 119 b, err := os.ReadFile(configPath) 120 if os.IsNotExist(err) { 121 // return default config 122 return TestCaseConfig{ 123 TestType: CommandFnRender, 124 }, nil 125 } 126 if err != nil { 127 return TestCaseConfig{}, fmt.Errorf("filed to read test config file: %w", err) 128 } 129 130 var config TestCaseConfig 131 err = yaml.Unmarshal(b, &config) 132 if err != nil { 133 return config, fmt.Errorf("failed to unmarshal config file: %s\n: %w", string(b), err) 134 } 135 if config.TestType == "" { 136 // by default we test pipeline 137 config.TestType = CommandFnRender 138 } 139 if config.EvalConfig != nil { 140 config.EvalConfig.fnConfigUniquePath, err = fromSlashPath(filepath.Join(path, expectedDir), config.EvalConfig.FnConfig) 141 if err != nil { 142 return config, fmt.Errorf("failed to get UniquePath from slash path %s: %w", 143 config.EvalConfig.FnConfig, err) 144 } 145 config.EvalConfig.execUniquePath, err = fromSlashPath(filepath.Join(path, expectedDir), config.EvalConfig.ExecPath) 146 if err != nil { 147 return config, fmt.Errorf("failed to get UniquePath from slash path %s: %w", 148 config.EvalConfig.ExecPath, err) 149 } 150 } 151 return config, nil 152 } 153 154 // TestCase contains the information needed to run a test. Each test case 155 // run by this driver is described by a `TestCase`. 156 type TestCase struct { 157 Path string 158 Config TestCaseConfig 159 } 160 161 // TestCases contains a list of TestCase. 162 type TestCases []TestCase 163 164 func isTestCase(path string, info os.FileInfo) bool { 165 if !info.IsDir() { 166 return false 167 } 168 169 expectedPath := filepath.Join(path, expectedDir) 170 expectedInfo, err := os.Stat(expectedPath) 171 if err != nil { 172 return false 173 } 174 if !expectedInfo.IsDir() { 175 return false 176 } 177 return true 178 } 179 180 // ScanTestCases will recursively scan the directory `path` and return 181 // a list of TestConfig found 182 func ScanTestCases(path string) (*TestCases, error) { 183 var cases TestCases 184 err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 185 if err != nil { 186 return err 187 } 188 if !isTestCase(path, info) { 189 return nil 190 } 191 192 config, err := newTestCaseConfig(path) 193 if err != nil { 194 return err 195 } 196 197 cases = append(cases, TestCase{ 198 Path: path, 199 Config: config, 200 }) 201 202 return nil 203 }) 204 if err != nil { 205 return nil, fmt.Errorf("failed to scan test cases in %s", path) 206 } 207 return &cases, nil 208 } 209 210 // fromSlashPath returns a UniquePath according to the input slash 'path'. 211 // 'base' should be an OS-specific base path which will be joined with 'path' 212 // if 'path' is not absolute. 213 func fromSlashPath(base, path string) (types.UniquePath, error) { 214 if path == "" { 215 return types.UniquePath(""), nil 216 } 217 path = filepath.FromSlash(path) 218 if filepath.IsAbs(path) { 219 return types.UniquePath(path), nil 220 } 221 p, err := filepath.Abs(filepath.Join(base, path)) 222 if err != nil { 223 return "", err 224 } 225 return types.UniquePath(p), nil 226 }