github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/test/test_factory.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package test
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"io"
    24  
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/config"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    27  	eventV2 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/event/v2"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/logfile"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/custom"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/test/structure"
    34  )
    35  
    36  type Config interface {
    37  	docker.Config
    38  
    39  	TestCases() []*latest.TestCase
    40  	Muted() config.Muted
    41  }
    42  
    43  // NewTester parses the provided test cases from the Skaffold config,
    44  // and returns a Tester instance with all the necessary test runners
    45  // to run all specified tests.
    46  func NewTester(ctx context.Context, cfg Config, imagesAreLocal func(imageName string) (bool, error)) (Tester, error) {
    47  	testers, err := getImageTesters(ctx, cfg, imagesAreLocal, cfg.TestCases())
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	return FullTester{
    53  		Testers: testers,
    54  		muted:   cfg.Muted(),
    55  	}, nil
    56  }
    57  
    58  // TestDependencies returns the watch dependencies for the target artifact to the runner.
    59  func (t FullTester) TestDependencies(ctx context.Context, artifact *latest.Artifact) ([]string, error) {
    60  	var deps []string
    61  	for _, tester := range t.Testers[artifact.ImageName] {
    62  		result, err := tester.TestDependencies(ctx)
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		deps = append(deps, result...)
    67  	}
    68  	return deps, nil
    69  }
    70  
    71  // Test is the top level testing execution call. It serves as the
    72  // entrypoint to all individual tests.
    73  func (t FullTester) Test(ctx context.Context, out io.Writer, bRes []graph.Artifact) error {
    74  	if len(t.Testers) == 0 {
    75  		return nil
    76  	}
    77  
    78  	output.Default.Fprintln(out, "Testing images...")
    79  
    80  	if t.muted.MuteTest() {
    81  		file, err := logfile.Create("test.log")
    82  		if err != nil {
    83  			return fmt.Errorf("unable to create log file for tests: %w", err)
    84  		}
    85  		fmt.Fprintln(out, " - writing logs to", file.Name())
    86  
    87  		// Print logs to a memory buffer and to a file.
    88  		var buf bytes.Buffer
    89  		w := io.MultiWriter(file, &buf)
    90  
    91  		// Run the tests.
    92  		err = t.runTests(ctx, w, bRes)
    93  
    94  		// After the test finish, close the log file. If the tests failed, print the full log to the console.
    95  		file.Close()
    96  		if err != nil {
    97  			buf.WriteTo(out)
    98  		}
    99  
   100  		return err
   101  	}
   102  
   103  	if err := t.runTests(ctx, out, bRes); err != nil {
   104  		return err
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func (t FullTester) runTests(ctx context.Context, out io.Writer, bRes []graph.Artifact) error {
   111  	testerID := 0
   112  	for _, b := range bRes {
   113  		for _, tester := range t.Testers[b.ImageName] {
   114  			eventV2.TesterInProgress(testerID)
   115  			if err := tester.Test(ctx, out, b.Tag); err != nil {
   116  				eventV2.TesterFailed(testerID, err)
   117  				return fmt.Errorf("running tests: %w", err)
   118  			}
   119  			eventV2.TesterSucceeded(testerID)
   120  			testerID++
   121  		}
   122  	}
   123  	return nil
   124  }
   125  
   126  func getImageTesters(ctx context.Context, cfg docker.Config, imagesAreLocal func(imageName string) (bool, error), tcs []*latest.TestCase) (ImageTesters, error) {
   127  	runners := make(map[string][]ImageTester)
   128  	for _, tc := range tcs {
   129  		isLocal, err := imagesAreLocal(tc.ImageName)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  
   134  		if len(tc.StructureTests) != 0 {
   135  			structureRunner, err := structure.New(ctx, cfg, tc, isLocal)
   136  			if err != nil {
   137  				return nil, err
   138  			}
   139  			runners[tc.ImageName] = append(runners[tc.ImageName], structureRunner)
   140  		}
   141  
   142  		for _, customTest := range tc.CustomTests {
   143  			customRunner, err := custom.New(cfg, tc.ImageName, tc.Workspace, customTest)
   144  			if err != nil {
   145  				return nil, err
   146  			}
   147  			runners[tc.ImageName] = append(runners[tc.ImageName], customRunner)
   148  		}
   149  	}
   150  	return runners, nil
   151  }