github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/test/runtimes/runner/lib/lib.go (about)

     1  // Copyright 2019 The gVisor 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 lib provides utilities for runner.
    16  package lib
    17  
    18  import (
    19  	"context"
    20  	"encoding/csv"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"sort"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/SagerNet/gvisor/pkg/log"
    30  	"github.com/SagerNet/gvisor/pkg/test/dockerutil"
    31  	"github.com/SagerNet/gvisor/pkg/test/testutil"
    32  )
    33  
    34  // RunTests is a helper that is called by main. It exists so that we can run
    35  // defered functions before exiting. It returns an exit code that should be
    36  // passed to os.Exit.
    37  func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Duration) int {
    38  	// TODO(github.com/SagerNet/issue/1624): Remove those tests from all exclude lists
    39  	// that only fail with VFS1.
    40  
    41  	// Get tests to exclude.
    42  	excludes, err := getExcludes(excludeFile)
    43  	if err != nil {
    44  		fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error())
    45  		return 1
    46  	}
    47  
    48  	// Construct the shared docker instance.
    49  	ctx := context.Background()
    50  	d := dockerutil.MakeContainer(ctx, testutil.DefaultLogger(lang))
    51  	defer d.CleanUp(ctx)
    52  
    53  	if err := testutil.TouchShardStatusFile(); err != nil {
    54  		fmt.Fprintf(os.Stderr, "error touching status shard file: %v\n", err)
    55  		return 1
    56  	}
    57  
    58  	// Get a slice of tests to run. This will also start a single Docker
    59  	// container that will be used to run each test. The final test will
    60  	// stop the Docker container.
    61  	tests, err := getTests(ctx, d, lang, image, batchSize, timeout, excludes)
    62  	if err != nil {
    63  		fmt.Fprintf(os.Stderr, "%s\n", err.Error())
    64  		return 1
    65  	}
    66  
    67  	m := testing.MainStart(testDeps{}, tests, nil, nil)
    68  	return m.Run()
    69  }
    70  
    71  // getTests executes all tests as table tests.
    72  func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) {
    73  	// Start the container.
    74  	opts := dockerutil.RunOpts{
    75  		Image: fmt.Sprintf("runtimes/%s", image),
    76  	}
    77  	d.CopyFiles(&opts, "/proctor", "test/runtimes/proctor/proctor")
    78  	if err := d.Spawn(ctx, opts, "/proctor/proctor", "--pause"); err != nil {
    79  		return nil, fmt.Errorf("docker run failed: %v", err)
    80  	}
    81  
    82  	// Get a list of all tests in the image.
    83  	list, err := d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--list")
    84  	if err != nil {
    85  		return nil, fmt.Errorf("docker exec failed: %v", err)
    86  	}
    87  
    88  	// Calculate a subset of tests.
    89  	tests := strings.Fields(list)
    90  	sort.Strings(tests)
    91  	indices, err := testutil.TestIndicesForShard(len(tests))
    92  	if err != nil {
    93  		return nil, fmt.Errorf("TestsForShard() failed: %v", err)
    94  	}
    95  
    96  	var itests []testing.InternalTest
    97  	for i := 0; i < len(indices); i += batchSize {
    98  		var tcs []string
    99  		end := i + batchSize
   100  		if end > len(indices) {
   101  			end = len(indices)
   102  		}
   103  		for _, tc := range indices[i:end] {
   104  			// Add test if not excluded.
   105  			if _, ok := excludes[tests[tc]]; ok {
   106  				log.Infof("Skipping test case %s\n", tests[tc])
   107  				continue
   108  			}
   109  			tcs = append(tcs, tests[tc])
   110  		}
   111  		if len(tcs) == 0 {
   112  			// No tests to add to this batch.
   113  			continue
   114  		}
   115  		itests = append(itests, testing.InternalTest{
   116  			Name: strings.Join(tcs, ", "),
   117  			F: func(t *testing.T) {
   118  				var (
   119  					now    = time.Now()
   120  					done   = make(chan struct{})
   121  					output string
   122  					err    error
   123  				)
   124  
   125  				state, err := d.Status(ctx)
   126  				if err != nil {
   127  					t.Fatalf("Could not find container status: %v", err)
   128  				}
   129  				if !state.Running {
   130  					t.Fatalf("container is not running: state = %s", state.Status)
   131  				}
   132  
   133  				go func() {
   134  					output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--tests", strings.Join(tcs, ","))
   135  					close(done)
   136  				}()
   137  
   138  				select {
   139  				case <-done:
   140  					if err == nil {
   141  						fmt.Printf("PASS: (%v) %d tests passed\n", time.Since(now), len(tcs))
   142  						return
   143  					}
   144  					t.Errorf("FAIL: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output)
   145  				case <-time.After(timeout):
   146  					t.Errorf("TIMEOUT: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output)
   147  				}
   148  			},
   149  		})
   150  	}
   151  
   152  	return itests, nil
   153  }
   154  
   155  // getBlacklist reads the exclude file and returns a set of test names to
   156  // exclude.
   157  func getExcludes(excludeFile string) (map[string]struct{}, error) {
   158  	excludes := make(map[string]struct{})
   159  	if excludeFile == "" {
   160  		return excludes, nil
   161  	}
   162  	f, err := os.Open(excludeFile)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	defer f.Close()
   167  
   168  	r := csv.NewReader(f)
   169  
   170  	// First line is header. Skip it.
   171  	if _, err := r.Read(); err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	for {
   176  		record, err := r.Read()
   177  		if err == io.EOF {
   178  			break
   179  		}
   180  		if err != nil {
   181  			return nil, err
   182  		}
   183  		excludes[record[0]] = struct{}{}
   184  	}
   185  	return excludes, nil
   186  }
   187  
   188  // testDeps implements testing.testDeps (an unexported interface), and is
   189  // required to use testing.MainStart.
   190  type testDeps struct{}
   191  
   192  func (f testDeps) MatchString(a, b string) (bool, error)       { return a == b, nil }
   193  func (f testDeps) StartCPUProfile(io.Writer) error             { return nil }
   194  func (f testDeps) StopCPUProfile()                             {}
   195  func (f testDeps) WriteProfileTo(string, io.Writer, int) error { return nil }
   196  func (f testDeps) ImportPath() string                          { return "" }
   197  func (f testDeps) StartTestLog(io.Writer)                      {}
   198  func (f testDeps) StopTestLog() error                          { return nil }
   199  func (f testDeps) SetPanicOnExit0(bool)                        {}