github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/pkg/test/runner/config.go (about) 1 // Copyright 2021 Google LLC 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 // AllowWasm determines if `fn render` needs to be invoked with `--allow-alpha-wasm` flag 85 AllowWasm bool `json:"allowWasm,omitempty" yaml:"allowWasm,omitempty"` 86 87 // Skip means should this test case be skipped. Default: false 88 Skip bool `json:"skip,omitempty" yaml:"skip,omitempty"` 89 90 // Debug means will the debug behavior be enabled. Default: false 91 // Debug behavior: 92 // 1. Keep the temporary directory used to run the test cases 93 // after test. 94 Debug bool `json:"debug,omitempty" yaml:"debug,omitempty"` 95 96 // TestType is the type of the test case. Possible value: ['render', 'eval'] 97 // Default: 'render' 98 TestType string `json:"testType,omitempty" yaml:"testType,omitempty"` 99 100 // DisableOutputTruncate indicates should error output be truncated 101 DisableOutputTruncate bool `json:"disableOutputTruncate,omitempty" yaml:"disableOutputTruncate,omitempty"` 102 103 // EvalConfig is the configs for eval tests 104 EvalConfig *EvalTestCaseConfig `json:",inline" yaml:",inline"` 105 106 // Environment variables to be set for the test case. 107 Env map[string]string `json:"env,omitempty" yaml:"env,omitempty"` 108 } 109 110 func (c *TestCaseConfig) RunCount() int { 111 return 2 112 } 113 114 func newTestCaseConfig(path string) (TestCaseConfig, error) { 115 configPath := filepath.Join(path, expectedDir, expectedConfigFile) 116 b, err := os.ReadFile(configPath) 117 if os.IsNotExist(err) { 118 // return default config 119 return TestCaseConfig{ 120 TestType: CommandFnRender, 121 }, nil 122 } 123 if err != nil { 124 return TestCaseConfig{}, fmt.Errorf("filed to read test config file: %w", err) 125 } 126 127 var config TestCaseConfig 128 err = yaml.Unmarshal(b, &config) 129 if err != nil { 130 return config, fmt.Errorf("failed to unmarshal config file: %s\n: %w", string(b), err) 131 } 132 if config.TestType == "" { 133 // by default we test pipeline 134 config.TestType = CommandFnRender 135 } 136 if config.EvalConfig != nil { 137 config.EvalConfig.fnConfigUniquePath, err = fromSlashPath(filepath.Join(path, expectedDir), config.EvalConfig.FnConfig) 138 if err != nil { 139 return config, fmt.Errorf("failed to get UniquePath from slash path %s: %w", 140 config.EvalConfig.FnConfig, err) 141 } 142 config.EvalConfig.execUniquePath, err = fromSlashPath(filepath.Join(path, expectedDir), config.EvalConfig.ExecPath) 143 if err != nil { 144 return config, fmt.Errorf("failed to get UniquePath from slash path %s: %w", 145 config.EvalConfig.ExecPath, err) 146 } 147 } 148 return config, nil 149 } 150 151 // TestCase contains the information needed to run a test. Each test case 152 // run by this driver is described by a `TestCase`. 153 type TestCase struct { 154 Path string 155 Config TestCaseConfig 156 } 157 158 // TestCases contains a list of TestCase. 159 type TestCases []TestCase 160 161 func isTestCase(path string, info os.FileInfo) bool { 162 if !info.IsDir() { 163 return false 164 } 165 166 expectedPath := filepath.Join(path, expectedDir) 167 expectedInfo, err := os.Stat(expectedPath) 168 if err != nil { 169 return false 170 } 171 if !expectedInfo.IsDir() { 172 return false 173 } 174 return true 175 } 176 177 // ScanTestCases will recursively scan the directory `path` and return 178 // a list of TestConfig found 179 func ScanTestCases(path string) (*TestCases, error) { 180 var cases TestCases 181 err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 182 if err != nil { 183 return err 184 } 185 if !isTestCase(path, info) { 186 return nil 187 } 188 189 config, err := newTestCaseConfig(path) 190 if err != nil { 191 return err 192 } 193 194 cases = append(cases, TestCase{ 195 Path: path, 196 Config: config, 197 }) 198 199 return nil 200 }) 201 if err != nil { 202 return nil, fmt.Errorf("failed to scan test cases in %s", path) 203 } 204 return &cases, nil 205 } 206 207 // fromSlashPath returns a UniquePath according to the input slash 'path'. 208 // 'base' should be an OS-specific base path which will be joined with 'path' 209 // if 'path' is not absolute. 210 func fromSlashPath(base, path string) (types.UniquePath, error) { 211 if path == "" { 212 return types.UniquePath(""), nil 213 } 214 path = filepath.FromSlash(path) 215 if filepath.IsAbs(path) { 216 return types.UniquePath(path), nil 217 } 218 p, err := filepath.Abs(filepath.Join(base, path)) 219 if err != nil { 220 return "", err 221 } 222 return types.UniquePath(p), nil 223 }