github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/fakeexec/auxmain.go (about) 1 // Copyright 2021 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package fakeexec 6 7 import ( 8 "encoding/json" 9 "fmt" 10 "os" 11 "reflect" 12 ) 13 14 const ( 15 // auxMainNameEnv is the name of an environment variable that specifies 16 // a name of an auxiliary main function to run. 17 auxMainNameEnv = "AUX_MAIN_NAME" 18 19 // auxMainValueEnv is the name of an environment variable that carries 20 // an extra value passed to an auxiliary main function. 21 auxMainValueEnv = "AUX_MAIN_VALUE" 22 ) 23 24 // AuxMain represents a auxiliary main function. 25 type AuxMain struct { 26 name string 27 } 28 29 // Params creates AuxMainParams that contains information necessary to execute 30 // the auxiliary main function. 31 // v should be an arbitrary JSON-serializable value. It is passed to the 32 // auxiliary main function. 33 func (a *AuxMain) Params(v interface{}) (*AuxMainParams, error) { 34 exe, err := os.Executable() 35 if err != nil { 36 return nil, err 37 } 38 p, err := json.Marshal(v) 39 if err != nil { 40 return nil, err 41 } 42 return &AuxMainParams{ 43 executable: exe, 44 name: a.name, 45 param: string(p), 46 }, nil 47 } 48 49 // AuxMainParams contains information necessary to execute an auxiliary main 50 // function. 51 type AuxMainParams struct { 52 executable string 53 name string 54 param string 55 } 56 57 // Executable returns a path to the current executable. It is similar to 58 // os.Executable, but it is precomputed and never fails. 59 func (a *AuxMainParams) Executable() string { 60 return a.executable 61 } 62 63 // Envs returns environment variables to be set to execute the auxiliary main 64 // function. Elements are in the form of "key=value" so that they can be 65 // appended to os/exec.Cmd.Env. 66 func (a *AuxMainParams) Envs() []string { 67 return []string{ 68 fmt.Sprintf("%s=%s", auxMainNameEnv, a.name), 69 fmt.Sprintf("%s=%s", auxMainValueEnv, a.param), 70 } 71 } 72 73 // SetEnvs modifies the current process' environment variables with os.Setenv 74 // so that the auxiliary main function is called on executing the self 75 // executable as a subprocess. 76 // 77 // It returns a closure to restore the original environment variable. It panics 78 // if the environment variable is already set. 79 func (a *AuxMainParams) SetEnvs() (restore func()) { 80 if val := os.Getenv(auxMainNameEnv); val != "" { 81 panic(fmt.Sprintf("fakeexec.AuxMain.SetEnv: Environment variable %s already set to non-empty value %q", auxMainNameEnv, val)) 82 } 83 os.Setenv(auxMainNameEnv, a.name) 84 os.Setenv(auxMainValueEnv, a.param) 85 return func() { 86 os.Unsetenv(auxMainNameEnv) 87 os.Unsetenv(auxMainValueEnv) 88 } 89 } 90 91 var knownNames = map[string]struct{}{} 92 93 // NewAuxMain registers a new auxiliary main function. 94 // 95 // name identifies an auxiliary main function. It must be unique within the 96 // current executable; otherwise this function will panic. 97 // 98 // f must be a function having a signature func(T) where T is a JSON 99 // serializable type. 100 // 101 // NewAuxMain must be called in a top-level variable initialization like: 102 // 103 // type fooParams struct { ... } 104 // 105 // var fooMain = fakeexec.NewAuxMain("foo", func(p fooParams) { 106 // // Another main function here... 107 // }) 108 // 109 // If the current process is executed for the auxiliary main, NewAuxMain 110 // immediately calls f and exits. Otherwise *AuxMain is returned, which you can 111 // use to start a subprocess running the auxiliary main. 112 // 113 // p := fooMain.Params(fooParams{ ... }) 114 // 115 // cmd := exec.Command(p.Name()) 116 // cmd.Env = append(os.Environ(), p.Envs()...) 117 // 118 // if err := cmd.Run(); err != nil { ... } 119 // 120 // Prefer Loopback if subprocesses don't need to call system calls. Loopback 121 // subprocesses virtually run within the current unit test process, which is 122 // usually more convenient than auxiliary main functions that run as separate 123 // processes. 124 func NewAuxMain(name string, f interface{}) *AuxMain { 125 if _, found := knownNames[name]; found { 126 panic(fmt.Sprintf("fakeexec.NewAuxMain: Multiple registrations for %q", name)) 127 } 128 knownNames[name] = struct{}{} 129 130 tf := reflect.TypeOf(f) 131 if ni, no := tf.NumIn(), tf.NumOut(); ni != 1 || no != 0 { 132 panic(fmt.Sprintf("fakeexec.NewAuxMain: f has wrong signature: must be func(T)")) 133 } 134 tp := tf.In(0) 135 136 if os.Getenv(auxMainNameEnv) != name { 137 return &AuxMain{name: name} 138 } 139 140 // Run the auxiliary main function. 141 vp := reflect.New(tp) 142 if err := json.Unmarshal([]byte(os.Getenv(auxMainValueEnv)), vp.Interface()); err != nil { 143 panic(fmt.Sprintf("fakeexec.AuxMain: %s: failed to unmarshal parameter: %v", name, err)) 144 } 145 reflect.ValueOf(f).Call([]reflect.Value{vp.Elem()}) 146 os.Exit(0) 147 panic("unreachable") 148 }