gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/runtimes/proctor/lib/python.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
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"os/exec"
    21  	"sort"
    22  	"strings"
    23  )
    24  
    25  // exclude holds test cases that fail in gVisor for various reasons and should
    26  // be excluded. The format of this map is [test library] -> [test cases...].
    27  // We need to handle test exclusion differently for python. Running python
    28  // tests on a test-case basis using `./python -m test --fromfile FILE` is too
    29  // slow. For example, it took 0.065s seconds to run
    30  // `./python -m test test_grammar` but it took 9.5 seconds to run the same set
    31  // of tests using `--fromfile`. The fast way of running a test library while
    32  // excluding a few test cases is to run `./python -m {lib} {test cases...}`. We
    33  // don't `--list-cases` + the CSV exclude file mechanism because tests are
    34  // selected from the list based on sharding/partition. We end up getting a
    35  // small list of tests from various test libraries. Tests from same library
    36  // are not grouped well together. So instead, we use `--list-tests` (which
    37  // lists test libraries). When running a test library, using this map we
    38  // generate a command to run all un-excluded tests from that library together.
    39  var exclude = map[string][]string{
    40  	// TODO(b/271473320): Un-exclude once this bug is fixed. Fails with overlay.
    41  	"test_os": []string{"TestScandir.test_attributes"},
    42  	// Broken test. Fails with runc too.
    43  	"test_asyncio.test_base_events": []string{
    44  		"BaseEventLoopWithSelectorTests.test_create_connection_service_name",
    45  	},
    46  	// TODO(b/271950879): Un-exclude once this bug is fixed.
    47  	"test_asyncio.test_events": []string{
    48  		"EPollEventLoopTests.test_bidirectional_pty",
    49  		"PollEventLoopTests.test_bidirectional_pty",
    50  		"SelectEventLoopTests.test_bidirectional_pty",
    51  	},
    52  	// TODO(b/162973328): Un-exclude once this bug is fixed.
    53  	"test_asyncore": []string{
    54  		"TestAPI_UseIPv4Poll.test_handle_expt",
    55  		"TestAPI_UseIPv4Select.test_handle_expt",
    56  	},
    57  	// TODO(b/162978767): Un-exclude once this bug is fixed.
    58  	"test_fcntl": []string{"TestFcntl.test_fcntl_64_bit"},
    59  	// TODO(b/76174079): Un-exclude once this bug is fixed.
    60  	"test_posix": []string{
    61  		"PosixTester.test_sched_priority",
    62  		"PosixTester.test_sched_rr_get_interval",
    63  		"PosixTester.test_get_and_set_scheduler_and_param", // sched_setparam(2) is not supported.
    64  		"TestPosixSpawn.test_setscheduler_only_param",
    65  		"TestPosixSpawnP.test_setscheduler_only_param",
    66  	},
    67  	// TODO(b/162979921): Un-exclude once this bug is fixed.
    68  	"test_pty": []string{
    69  		"PtyTest.test_fork",
    70  		"PtyTest.test_master_read",
    71  		"PtyTest.test_spawn_doesnt_hang",
    72  	},
    73  	// TODO(b/162980389): Un-exclude once this bug is fixed.
    74  	"test_readline": []string{"TestReadline.*"},
    75  	// TODO(b/76174079): Un-exclude once this bug is fixed.
    76  	"test_resource": []string{"ResourceTest.test_prlimit"},
    77  	// TODO(b/271949964): Un-exclude test cases as they are fixed.
    78  	"test_socket": []string{
    79  		"BasicUDPLITETest.testRecvFrom",
    80  		"BasicUDPLITETest.testRecvFromNegative",
    81  		"BasicUDPLITETest.testSendtoAndRecv",
    82  		"GeneralModuleTests.testGetServBy",
    83  		"GeneralModuleTests.testGetaddrinfo",
    84  		"RecvmsgIntoUDPLITETest.testRecvmsg",
    85  		"RecvmsgIntoUDPLITETest.testRecvmsgAfterClose",
    86  		"RecvmsgIntoUDPLITETest.testRecvmsgExplicitDefaults",
    87  		"RecvmsgIntoUDPLITETest.testRecvmsgFromSendmsg",
    88  		"RecvmsgIntoUDPLITETest.testRecvmsgIntoArray",
    89  		"RecvmsgIntoUDPLITETest.testRecvmsgIntoBadArgs",
    90  		"RecvmsgIntoUDPLITETest.testRecvmsgIntoGenerator",
    91  		"RecvmsgIntoUDPLITETest.testRecvmsgIntoScatter",
    92  		"RecvmsgIntoUDPLITETest.testRecvmsgLongAncillaryBuf",
    93  		"RecvmsgIntoUDPLITETest.testRecvmsgPeek",
    94  		"RecvmsgIntoUDPLITETest.testRecvmsgShortAncillaryBuf",
    95  		"RecvmsgIntoUDPLITETest.testRecvmsgShorter",
    96  		"RecvmsgIntoUDPLITETest.testRecvmsgTimeout",
    97  		"RecvmsgIntoUDPLITETest.testRecvmsgTrunc",
    98  		"RecvmsgUDPLITETest.testRecvmsg",
    99  		"RecvmsgUDPLITETest.testRecvmsgAfterClose",
   100  		"RecvmsgUDPLITETest.testRecvmsgBadArgs",
   101  		"RecvmsgUDPLITETest.testRecvmsgExplicitDefaults",
   102  		"RecvmsgUDPLITETest.testRecvmsgFromSendmsg",
   103  		"RecvmsgUDPLITETest.testRecvmsgLongAncillaryBuf",
   104  		"RecvmsgUDPLITETest.testRecvmsgPeek",
   105  		"RecvmsgUDPLITETest.testRecvmsgShortAncillaryBuf",
   106  		"RecvmsgUDPLITETest.testRecvmsgShorter",
   107  		"RecvmsgUDPLITETest.testRecvmsgTimeout",
   108  		"RecvmsgUDPLITETest.testRecvmsgTrunc",
   109  		"SendmsgUDPLITETest.testSendmsg",
   110  		"SendmsgUDPLITETest.testSendmsgAfterClose",
   111  		"SendmsgUDPLITETest.testSendmsgAncillaryGenerator",
   112  		"SendmsgUDPLITETest.testSendmsgArray",
   113  		"SendmsgUDPLITETest.testSendmsgBadArgs",
   114  		"SendmsgUDPLITETest.testSendmsgBadCmsg",
   115  		"SendmsgUDPLITETest.testSendmsgBadMultiCmsg",
   116  		"SendmsgUDPLITETest.testSendmsgDataGenerator",
   117  		"SendmsgUDPLITETest.testSendmsgExcessCmsgReject",
   118  		"SendmsgUDPLITETest.testSendmsgGather",
   119  		"SendmsgUDPLITETest.testSendmsgNoDestAddr",
   120  		"UDPLITETimeoutTest.testTimeoutZero",
   121  		"UDPLITETimeoutTest.testUDPLITETimeout",
   122  	},
   123  	// TODO(b/274167897): Un-exclude test cases once this is patched upstream.
   124  	// The test is broken: https://github.com/python/cpython/issues/102795
   125  	"test_epoll": []string{"TestEPoll.test_control_and_wait"},
   126  }
   127  
   128  // Some python test libraries contain other test libraries that have test cases
   129  // that need to be excluded. We need to expand such libraries so that the test
   130  // case exclusion can work correctly.
   131  var expand = []string{
   132  	"test_asyncio",
   133  }
   134  
   135  // pythonRunner implements TestRunner for Python.
   136  type pythonRunner struct{}
   137  
   138  var _ TestRunner = pythonRunner{}
   139  
   140  // ListTests implements TestRunner.ListTests.
   141  func (pythonRunner) ListTests() ([]string, error) {
   142  	args := []string{"-m", "test", "--list-tests"}
   143  	cmd := exec.Command("./python", args...)
   144  	cmd.Stderr = os.Stderr
   145  	out, err := cmd.Output()
   146  	if err != nil {
   147  		return nil, fmt.Errorf("failed to list: %v", err)
   148  	}
   149  	testLibs := make(map[string]struct{})
   150  	for _, testLib := range strings.Split(string(out), "\n") {
   151  		if len(testLib) == 0 {
   152  			continue
   153  		}
   154  		testLibs[testLib] = struct{}{}
   155  	}
   156  	for _, libToExpand := range expand {
   157  		if _, ok := testLibs[libToExpand]; !ok {
   158  			return nil, fmt.Errorf("%s test library was not listed", libToExpand)
   159  		}
   160  		delete(testLibs, libToExpand)
   161  		subLibs, err := subTestLibs(libToExpand)
   162  		if err != nil {
   163  			return nil, err
   164  		}
   165  		for subLib := range subLibs {
   166  			testLibs[fmt.Sprintf("%s.%s", libToExpand, subLib)] = struct{}{}
   167  		}
   168  	}
   169  	res := make([]string, 0, len(testLibs))
   170  	for lib := range testLibs {
   171  		res = append(res, lib)
   172  	}
   173  	// Sort to have deterministic results across shards.
   174  	sort.Strings(res)
   175  	return res, nil
   176  }
   177  
   178  func subTestLibs(testLib string) (map[string]struct{}, error) {
   179  	// --list-{tests/cases} is only implemented by the 'test' library.
   180  	// Running './python -m test {X} --list-tests' does not list libraries inside
   181  	// X library. We need to list all test cases and extract sub libraries.
   182  	args := []string{"-m", "test", testLib, "--list-cases"}
   183  	cmd := exec.Command("./python", args...)
   184  	cmd.Stderr = os.Stderr
   185  	out, err := cmd.Output()
   186  	if err != nil {
   187  		return nil, fmt.Errorf("failed to list: %v", err)
   188  	}
   189  	subLibs := make(map[string]struct{})
   190  	for _, tc := range strings.Split(string(out), "\n") {
   191  		if len(tc) == 0 {
   192  			continue
   193  		}
   194  		idx := strings.Index(tc, testLib)
   195  		if idx < 0 {
   196  			return nil, fmt.Errorf("could not find %q library in test case %q", testLib, tc)
   197  		}
   198  		subLibs[strings.Split(tc[idx:], ".")[1]] = struct{}{}
   199  	}
   200  	return subLibs, nil
   201  }
   202  
   203  // TestCmds implements TestRunner.TestCmds.
   204  func (pythonRunner) TestCmds(tests []string) []*exec.Cmd {
   205  	var cmds []*exec.Cmd
   206  	var full []string
   207  	for _, testModule := range tests {
   208  		if excludeTCs, ok := exclude[testModule]; ok {
   209  			cmds = append(cmds, testCmdWithExclTCs(testModule, excludeTCs))
   210  		} else {
   211  			full = append(full, testModule)
   212  		}
   213  	}
   214  	if len(full) > 0 {
   215  		// Run all test modules (that have no excludes) together for speed.
   216  		// Running them individually with a new exec.Cmd takes longer.
   217  		args := append([]string{"-m", "test"}, full...)
   218  		cmds = append(cmds, exec.Command("./python", args...))
   219  	}
   220  	return cmds
   221  }
   222  
   223  func testCmdWithExclTCs(testModule string, excludeTCs []string) *exec.Cmd {
   224  	shellPipeline := []string{
   225  		// List all test cases in this module.
   226  		fmt.Sprintf("./python -m test %s --list-cases", testModule),
   227  		// Exclude the test cases. Note that '$' was added after each excluded test
   228  		// case to be exact about which test case to exclude.
   229  		fmt.Sprintf("grep -v \"%s$\"", strings.Join(excludeTCs, "$\\|")),
   230  		// Remove the "test.{testModule}." prefix.
   231  		fmt.Sprintf("sed -e \"s/^test.%s.//\"", testModule),
   232  		// Run all un-excluded test cases in one command.
   233  		fmt.Sprintf("xargs ./python -m test.%s", testModule),
   234  	}
   235  	args := []string{"-c", strings.Join(shellPipeline, " | ")}
   236  	return exec.Command("sh", args...)
   237  }