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 }