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  }