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  }