xref: /aosp_15_r20/external/swiftshader/tests/regres/shell/shell_unix.go (revision 03ce13f70fcc45d86ee91b7ee4cab1936a95046e)
1// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
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//go:build darwin || linux
16// +build darwin linux
17
18package shell
19
20import (
21	"bytes"
22	"fmt"
23	"log"
24	"os"
25	"os/exec"
26	"os/signal"
27	"strconv"
28	"syscall"
29	"time"
30)
31
32func init() {
33	// As we are going to be running a number of tests concurrently, we need to
34	// limit the amount of virtual memory each test uses, otherwise memory
35	// hungry tests can bring the whole system down into a swapping apocalypse.
36	//
37	// Linux has the setrlimit() function to limit a process (and child's)
38	// virtual memory usage - but we cannot call this from the regres process
39	// as this process may need more memory than the limit allows.
40	//
41	// Unfortunately golang has no native support for setting rlimits for child
42	// processes (https://github.com/golang/go/issues/6603), so we instead wrap
43	// the exec to the test executable with another child regres process using a
44	// special --exec mode:
45	//
46	// [regres] -> [regres --exec <test-exe N args...>] -> [test-exe]
47	//               ^^^^
48	//          (calls rlimit() with memory limit of N bytes)
49
50	if len(os.Args) > 3 && os.Args[1] == "--exec" {
51		exe := os.Args[2]
52		limit, err := strconv.ParseUint(os.Args[3], 10, 64)
53		if err != nil {
54			log.Fatalf("Expected memory limit as 3rd argument. %v\n", err)
55		}
56		if limit > 0 {
57			if err := syscall.Setrlimit(syscall.RLIMIT_AS, &syscall.Rlimit{Cur: limit, Max: limit}); err != nil {
58				log.Fatalln(fmt.Errorf("Setrlimit: %w", err))
59			}
60		}
61		cmd := exec.Command(exe, os.Args[4:]...)
62		cmd.Stdin = os.Stdin
63		cmd.Stdout = os.Stdout
64		cmd.Stderr = os.Stderr
65		if err := cmd.Start(); err != nil {
66			os.Stderr.WriteString(err.Error())
67			os.Exit(1)
68		}
69		// Forward signals to the child process
70		c := make(chan os.Signal, 1)
71		signal.Notify(c, os.Interrupt)
72		go func() {
73			for sig := range c {
74				cmd.Process.Signal(sig)
75			}
76		}()
77		cmd.Wait()
78		close(c)
79		os.Exit(cmd.ProcessState.ExitCode())
80	}
81}
82
83// Exec runs the executable exe with the given arguments, in the working
84// directory wd, with the custom environment flags.
85// If the process does not finish within timeout a errTimeout will be returned.
86func Exec(timeout time.Duration, exe, wd string, env []string, toStdin string, args ...string) ([]byte, error) {
87	stdin := &bytes.Buffer{}
88	stdin.WriteString(toStdin)
89
90	// Shell via regres: --exec N <exe> <args...>
91	// See main() for details.
92	args = append([]string{"--exec", exe, fmt.Sprintf("%v", MaxProcMemory)}, args...)
93	b := bytes.Buffer{}
94	c := exec.Command(os.Args[0], args...)
95	c.Dir = wd
96	c.Env = env
97	c.Stdin = stdin
98	c.Stdout = &b
99	c.Stderr = &b
100
101	if err := c.Start(); err != nil {
102		return nil, err
103	}
104
105	res := make(chan error)
106	go func() { res <- c.Wait() }()
107
108	select {
109	case <-time.NewTimer(timeout).C:
110		c.Process.Signal(syscall.SIGINT)
111		time.Sleep(time.Second * 3)
112		if c.ProcessState == nil || !c.ProcessState.Exited() {
113			log.Printf("Process %v still has not exited, killing\n", c.Process.Pid)
114			syscall.Kill(-c.Process.Pid, syscall.SIGKILL)
115		}
116		return b.Bytes(), ErrTimeout{exe, timeout}
117	case err := <-res:
118		return b.Bytes(), err
119	}
120}
121