go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/exec/execmock/mock_example_test.go (about)

     1  // Copyright 2023 The LUCI 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 execmock_test
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"flag"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"testing"
    25  
    26  	"go.chromium.org/luci/common/errors"
    27  	"go.chromium.org/luci/common/exec"
    28  	"go.chromium.org/luci/common/exec/execmock"
    29  )
    30  
    31  func TestMain(m *testing.M) {
    32  	execmock.Intercept()
    33  	os.Exit(m.Run())
    34  }
    35  
    36  // CustomInput is data that the TEST wants to communicate to CustomRunner.
    37  //
    38  // This should typically be things which affect the behavior of the Runner, e.g.
    39  // to simulate different outputs, errors, etc., or to change the data that the
    40  // Runner responds with.
    41  type CustomInput struct {
    42  	// OutputFile is a string that the test sends to CustomRunner to have it write
    43  	// out to the `--output` argument.
    44  	OutputFile []byte
    45  
    46  	// CollectInput indicates that the test wants the Runner to collect the data
    47  	// of the file specified by the `--input` argument.
    48  	CollectInput bool
    49  }
    50  
    51  type CustomOutput struct {
    52  	// If CollectInput was true, expect and read the input file and return the
    53  	// data here.
    54  	InputData []byte
    55  }
    56  
    57  var CustomRunner = execmock.Register(func(in *CustomInput) (*CustomOutput, int, error) {
    58  	input := flag.String("input", "", "")
    59  	_ = flag.String("random", "", "")
    60  	output := flag.String("output", "", "")
    61  	flag.Parse()
    62  
    63  	ret := &CustomOutput{}
    64  
    65  	if in.CollectInput {
    66  		if *input == "" {
    67  			return nil, 1, errors.New("input was expected")
    68  		}
    69  		data, err := os.ReadFile(*input)
    70  		if err != nil {
    71  			return nil, 1, errors.Annotate(err, "reading input file").Err()
    72  		}
    73  		ret.InputData = data
    74  	}
    75  
    76  	if in.OutputFile != nil {
    77  		if err := os.WriteFile(*output, in.OutputFile, 0777); err != nil {
    78  			return nil, 1, errors.Annotate(err, "writing output file").Err()
    79  		}
    80  	}
    81  
    82  	return ret, 0, nil
    83  })
    84  
    85  func must(err error) {
    86  	if err != nil {
    87  		panic(err)
    88  	}
    89  }
    90  
    91  func ExampleInit() {
    92  	// See this file for the definition of `CustomRunner`
    93  
    94  	// BEGIN: TEST CODE
    95  	ctx := execmock.Init(context.Background())
    96  
    97  	outputUses := CustomRunner.WithArgs("--output").Mock(ctx, &CustomInput{
    98  		OutputFile: []byte("hello I am Mx. Catopolous"),
    99  	})
   100  	allOtherUses := CustomRunner.Mock(ctx)
   101  	inputUses := CustomRunner.WithArgs("--input").Mock(ctx, &CustomInput{
   102  		CollectInput: true,
   103  	})
   104  	tmpDir, err := os.MkdirTemp("", "")
   105  	must(errors.Annotate(err, "failed to create tmpDir").Err())
   106  
   107  	defer func() {
   108  		must(os.RemoveAll(tmpDir))
   109  	}()
   110  	// END: TEST CODE
   111  
   112  	// BEGIN: APPLICATION CODE
   113  	// Your program would then get `ctx` passed to it from the test, and it would
   114  	// run commands as usual:
   115  
   116  	// this should be intercepted by `allOtherUses`
   117  	must(exec.Command(ctx, "some_prog", "--random", "argument").Run())
   118  	fmt.Println("[some_prog --random argument]: OK")
   119  
   120  	// this should be intercepted by `inputUses`
   121  	inputFile := filepath.Join(tmpDir, "input_file")
   122  	must(os.WriteFile(inputFile, []byte("hello world"), 0777))
   123  	must(exec.Command(ctx, "another_program", "--input", inputFile).Run())
   124  	fmt.Println("[another_program --input inputFile]: OK")
   125  
   126  	// this should be intercepted by `outputUses`.
   127  	outputFile := filepath.Join(tmpDir, "output_file")
   128  	outputCall := exec.Command(ctx, "another_program", "--output", outputFile)
   129  	outputCall.Stdout = os.Stdout
   130  	outputCall.Stderr = os.Stderr
   131  	must(outputCall.Run())
   132  
   133  	outputFileData, err := os.ReadFile(outputFile)
   134  	must(errors.Annotate(err, "our mock failed to write %q", outputFile).Err())
   135  	if !bytes.Equal(outputFileData, []byte("hello I am Mx. Catopolous")) {
   136  		panic(errors.New("our mock failed to write the expected data"))
   137  	}
   138  	fmt.Printf("[another_program --output outputFile]: %q\n", outputFileData)
   139  	// END: APPLICATION CODE
   140  
   141  	// BEGIN: TEST CODE
   142  	// Back in the test after running the application code. Now we can look and
   143  	// see what our mocks caught.
   144  	fmt.Printf("allOtherUses: got %d calls\n", len(allOtherUses.Snapshot()))
   145  	fmt.Printf("outputUses: got %d calls\n", len(outputUses.Snapshot()))
   146  
   147  	incallMock, _, err := inputUses.Snapshot()[0].GetOutput(ctx)
   148  	must(errors.Annotate(err, "could not get output from --input call").Err())
   149  	fmt.Printf("incallMock: saw %q written to the mock program\n", incallMock.InputData)
   150  	// END: TEST CODE
   151  
   152  	// Output:
   153  	// [some_prog --random argument]: OK
   154  	// [another_program --input inputFile]: OK
   155  	// [another_program --output outputFile]: "hello I am Mx. Catopolous"
   156  	// allOtherUses: got 1 calls
   157  	// outputUses: got 1 calls
   158  	// incallMock: saw "hello world" written to the mock program
   159  }