lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/tracing/cmd/gotrace/gotrace_test.go (about)

     1  // Copyright (C) 2017-2021  Nexedi SA and Contributors.
     2  //                          Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  package main
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"go/build"
    26  	"io/ioutil"
    27  	"os"
    28  	"os/exec"
    29  	"path/filepath"
    30  	"runtime"
    31  	"strings"
    32  	"syscall"
    33  	"testing"
    34  
    35  	"lab.nexedi.com/kirr/go123/exc"
    36  )
    37  
    38  type TreePrepareMode int
    39  const (
    40  	TreePrepareGolden TreePrepareMode = iota // prepare golden tree - how `gotrace gen` result should look like
    41  	TreePrepareWork                          // prepare work tree - inital state for `gotrace gen` to run
    42  )
    43  
    44  // prepareTestTree copies files from src to dst recursively processing *.ok and *.rm depending on mode.
    45  //
    46  // dst should not initially exist
    47  func prepareTestTree(src, dst string, mode TreePrepareMode) error {
    48  	err := os.MkdirAll(dst, 0777)
    49  	if err != nil {
    50  		return err
    51  	}
    52  
    53  	return filepath.Walk(src, func(srcpath string, info os.FileInfo, err error) error {
    54  		if srcpath == src /* skip root */ || err != nil {
    55  			return err
    56  		}
    57  
    58  		dstpath := dst + strings.TrimPrefix(srcpath, src)
    59  		if info.IsDir() {
    60  			err := os.Mkdir(dstpath, 0777)
    61  			return err
    62  		}
    63  
    64  		// NOTE since files are walked in lexical order <f>.ok or
    65  		// <f>.rm is always guaranteed to go after <f>.
    66  
    67  		var isOk, isRm bool
    68  		if strings.HasSuffix(srcpath, ".ok") {
    69  			isOk = true
    70  			dstpath = strings.TrimSuffix(dstpath, ".ok")
    71  		}
    72  		if strings.HasSuffix(srcpath, ".rm") {
    73  			isRm = true
    74  			dstpath = strings.TrimSuffix(dstpath, ".rm")
    75  		}
    76  
    77  		data, err := ioutil.ReadFile(srcpath)
    78  		if err != nil {
    79  			return err
    80  		}
    81  
    82  		switch mode {
    83  		case TreePrepareGolden:
    84  			// ok files are written as is
    85  
    86  			// no removed files
    87  			if isRm {
    88  				return nil
    89  			}
    90  
    91  		case TreePrepareWork:
    92  			// no ok files initially
    93  			if isOk {
    94  				return nil
    95  			}
    96  			// files to remove - prepopulate with magic
    97  			if isRm {
    98  				data = []byte(magic)
    99  			}
   100  		}
   101  
   102  		err = ioutil.WriteFile(dstpath, data, info.Mode())
   103  		return err
   104  	})
   105  }
   106  
   107  func xprepareTree(src, dst string, mode TreePrepareMode) {
   108  	err := prepareTestTree(src, dst, mode)
   109  	exc.Raiseif(err)
   110  }
   111  
   112  // diffR compares two directories recursively
   113  func diffR(patha, pathb string) (diff string, err error) {
   114  	cmd := exec.Command("diff", "-urN", patha, pathb)
   115  	out, err := cmd.Output()
   116  	if e, ok := err.(*exec.ExitError); ok {
   117  		if e.Sys().(syscall.WaitStatus).ExitStatus() == 1 {
   118  			err = nil // diff signals with 1 just a difference - problem exit code is 2
   119  		} else {
   120  			err = fmt.Errorf("diff %s %s:\n%s", patha, pathb, e.Stderr)
   121  		}
   122  	}
   123  
   124  	return string(out), err
   125  }
   126  
   127  // haveReleaseTag returns whether current compiler has specified tag in its default build environment.
   128  func haveReleaseTag(tag string) bool {
   129  	for _, rtag := range build.Default.ReleaseTags {
   130  		if tag == rtag {
   131  			return true
   132  		}
   133  	}
   134  	return false
   135  }
   136  
   137  func TestGoTrace(t *testing.T) {
   138  	tmp, err := ioutil.TempDir("", "t-gotrace")
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	defer os.RemoveAll(tmp)
   143  
   144  	good := tmp + "/good"
   145  	work := tmp + "/work"
   146  
   147  	xprepareTree("testdata", good, TreePrepareGolden)
   148  	xprepareTree("testdata", work, TreePrepareWork)
   149  
   150  	// test build context with GOPATH set to work tree
   151  	var tBuildCtx = &build.Context{
   152  		GOARCH:     "amd64",
   153  		GOOS:       "linux",
   154  		GOROOT:     runtime.GOROOT(),
   155  		GOPATH:     work,
   156  		CgoEnabled: true,
   157  		Compiler:   runtime.Compiler,
   158  	}
   159  
   160  	// XXX autodetect (go list ?)
   161  	testv := []string{"a/pkg1", "b/pkg2", "c/pkg3", "d/pkg4"}
   162  	// cgo parsing works starting with Go 1.10
   163  	if haveReleaseTag("go1.10") {
   164  		testv = append(testv, "a/pkg1_cgo")
   165  	}
   166  
   167  	for _, tpkg := range testv {
   168  		// verify `gotrace gen`
   169  		err = tracegen(tpkg, tBuildCtx, "" /* = local imorts disabled */)
   170  		if err != nil {
   171  			t.Errorf("%v: %v", tpkg, err)
   172  		}
   173  
   174  		diff, err := diffR(good+"/src/"+tpkg, work+"/src/"+tpkg)
   175  		if err != nil {
   176  			t.Fatalf("%v: %v", tpkg, err)
   177  		}
   178  
   179  		if diff != "" {
   180  			t.Errorf("%v: gold & work differ:\n%s", tpkg, diff)
   181  		}
   182  
   183  		// verify `gotrace list`
   184  		var tlistBuf bytes.Buffer
   185  		err = tracelist(&tlistBuf, tpkg, tBuildCtx, "" /* = local imports disabled */)
   186  		if err != nil {
   187  			t.Fatalf("%v: %v", tpkg, err)
   188  		}
   189  
   190  		tlistOk, err := ioutil.ReadFile(work + "/src/" + tpkg + "/tracelist.txt")
   191  		if err != nil {
   192  			t.Fatalf("%v: %v", tpkg, err)
   193  		}
   194  
   195  		tlist := tlistBuf.Bytes()
   196  		if !bytes.Equal(tlist, tlistOk) {
   197  			t.Errorf("%v: tracelist differ:\nhave:\n%s\nwant:\n%s", tpkg, tlist, tlistOk)
   198  		}
   199  	}
   200  }