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  }