github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/profile/profile_test.go (about)

     1  package profile
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  )
    14  
    15  type checkFn func(t *testing.T, stdout, stderr []byte, err error)
    16  
    17  func TestProfile(t *testing.T) {
    18  	f, err := ioutil.TempFile("", "profile_test")
    19  	if err != nil {
    20  		t.Fatal(err)
    21  	}
    22  	defer os.Remove(f.Name())
    23  
    24  	var profileTests = []struct {
    25  		name   string
    26  		code   string
    27  		checks []checkFn
    28  	}{{
    29  		name: "default profile (cpu)",
    30  		code: `
    31  package main
    32  
    33  import "github.com/angenalZZZ/gofunc/profile"
    34  
    35  func main() {
    36  	defer profile.Start().Stop()
    37  }	
    38  `,
    39  		checks: []checkFn{
    40  			NoStdout,
    41  			Stderr("profile: cpu profiling enabled"),
    42  			NoErr,
    43  		},
    44  	}, {
    45  		name: "memory profile",
    46  		code: `
    47  package main
    48  
    49  import "github.com/angenalZZZ/gofunc/profile"
    50  
    51  func main() {
    52  	defer profile.Start(profile.MemProfile).Stop()
    53  }	
    54  `,
    55  		checks: []checkFn{
    56  			NoStdout,
    57  			Stderr("profile: memory profiling enabled"),
    58  			NoErr,
    59  		},
    60  	}, {
    61  		name: "memory profile (rate 2048)",
    62  		code: `
    63  package main
    64  
    65  import "github.com/angenalZZZ/gofunc/profile"
    66  
    67  func main() {
    68  	defer profile.Start(profile.MemProfileRate(2048)).Stop()
    69  }	
    70  `,
    71  		checks: []checkFn{
    72  			NoStdout,
    73  			Stderr("profile: memory profiling enabled (rate 2048)"),
    74  			NoErr,
    75  		},
    76  	}, {
    77  		name: "double start",
    78  		code: `
    79  package main
    80  
    81  import "github.com/angenalZZZ/gofunc/profile"
    82  
    83  func main() {
    84  	profile.Start()
    85  	profile.Start()
    86  }	
    87  `,
    88  		checks: []checkFn{
    89  			NoStdout,
    90  			Stderr("cpu profiling enabled", "profile: Start() already called"),
    91  			Err,
    92  		},
    93  	}, {
    94  		name: "block profile",
    95  		code: `
    96  package main
    97  
    98  import "github.com/angenalZZZ/gofunc/profile"
    99  
   100  func main() {
   101  	defer profile.Start(profile.BlockProfile).Stop()
   102  }	
   103  `,
   104  		checks: []checkFn{
   105  			NoStdout,
   106  			Stderr("profile: block profiling enabled"),
   107  			NoErr,
   108  		},
   109  	}, {
   110  		name: "mutex profile",
   111  		code: `
   112  package main
   113  
   114  import "github.com/angenalZZZ/gofunc/profile"
   115  
   116  func main() {
   117  	defer profile.Start(profile.MutexProfile).Stop()
   118  }
   119  `,
   120  		checks: []checkFn{
   121  			NoStdout,
   122  			Stderr("profile: mutex profiling enabled"),
   123  			NoErr,
   124  		},
   125  	}, {
   126  		name: "profile path",
   127  		code: `
   128  package main
   129  
   130  import "github.com/angenalZZZ/gofunc/profile"
   131  
   132  func main() {
   133  	defer profile.Start(profile.ProfilePath(".")).Stop()
   134  }	
   135  `,
   136  		checks: []checkFn{
   137  			NoStdout,
   138  			Stderr("profile: cpu profiling enabled, cpu.pprof"),
   139  			NoErr,
   140  		},
   141  	}, {
   142  		name: "profile path error",
   143  		code: `
   144  package main
   145  
   146  import "github.com/angenalZZZ/gofunc/profile"
   147  
   148  func main() {
   149  		defer profile.Start(profile.ProfilePath("` + f.Name() + `")).Stop()
   150  }	
   151  `,
   152  		checks: []checkFn{
   153  			NoStdout,
   154  			Stderr("could not create initial output"),
   155  			Err,
   156  		},
   157  	}, {
   158  		name: "multiple profile sessions",
   159  		code: `
   160  package main
   161  
   162  import "github.com/angenalZZZ/gofunc/profile"
   163  
   164  func main() {
   165  	profile.Start(profile.CPUProfile).Stop()
   166  	profile.Start(profile.MemProfile).Stop()
   167  	profile.Start(profile.BlockProfile).Stop()
   168  	profile.Start(profile.CPUProfile).Stop()
   169  	profile.Start(profile.MutexProfile).Stop()
   170  }
   171  `,
   172  		checks: []checkFn{
   173  			NoStdout,
   174  			Stderr("profile: cpu profiling enabled",
   175  				"profile: cpu profiling disabled",
   176  				"profile: memory profiling enabled",
   177  				"profile: memory profiling disabled",
   178  				"profile: block profiling enabled",
   179  				"profile: block profiling disabled",
   180  				"profile: cpu profiling enabled",
   181  				"profile: cpu profiling disabled",
   182  				"profile: mutex profiling enabled",
   183  				"profile: mutex profiling disabled"),
   184  			NoErr,
   185  		},
   186  	}, {
   187  		name: "profile quiet",
   188  		code: `
   189  package main
   190  
   191  import "github.com/angenalZZZ/gofunc/profile"
   192  
   193  func main() {
   194          defer profile.Start(profile.Quiet).Stop()
   195  }       
   196  `,
   197  		checks: []checkFn{NoStdout, NoStderr, NoErr},
   198  	}}
   199  	for _, tt := range profileTests {
   200  		t.Log(tt.name)
   201  		stdout, stderr, err := runTest(t, tt.code)
   202  		for _, f := range tt.checks {
   203  			f(t, stdout, stderr, err)
   204  		}
   205  	}
   206  }
   207  
   208  // NoStdout checks that stdout was blank.
   209  func NoStdout(t *testing.T, stdout, _ []byte, _ error) {
   210  	if len := len(stdout); len > 0 {
   211  		t.Errorf("stdout: wanted 0 bytes, got %d", len)
   212  	}
   213  }
   214  
   215  // Stderr verifies that the given lines match the output from stderr
   216  func Stderr(lines ...string) checkFn {
   217  	return func(t *testing.T, _, stderr []byte, _ error) {
   218  		r := bytes.NewReader(stderr)
   219  		if !validateOutput(r, lines) {
   220  			t.Errorf("stderr: wanted '%s', got '%s'", lines, stderr)
   221  		}
   222  	}
   223  }
   224  
   225  // NoStderr checks that stderr was blank.
   226  func NoStderr(t *testing.T, _, stderr []byte, _ error) {
   227  	if len := len(stderr); len > 0 {
   228  		t.Errorf("stderr: wanted 0 bytes, got %d", len)
   229  	}
   230  }
   231  
   232  // Err checks that there was an error returned
   233  func Err(t *testing.T, _, _ []byte, err error) {
   234  	if err == nil {
   235  		t.Errorf("expected error")
   236  	}
   237  }
   238  
   239  // NoErr checks that err was nil
   240  func NoErr(t *testing.T, _, _ []byte, err error) {
   241  	if err != nil {
   242  		t.Errorf("error: expected nil, got %v", err)
   243  	}
   244  }
   245  
   246  // validatedOutput validates the given slice of lines against data from the given reader.
   247  func validateOutput(r io.Reader, want []string) bool {
   248  	s := bufio.NewScanner(r)
   249  	for _, line := range want {
   250  		if !s.Scan() || !strings.Contains(s.Text(), line) {
   251  			return false
   252  		}
   253  	}
   254  	return true
   255  }
   256  
   257  var validateOutputTests = []struct {
   258  	input string
   259  	lines []string
   260  	want  bool
   261  }{{
   262  	input: "",
   263  	want:  true,
   264  }, {
   265  	input: `profile: yes
   266  `,
   267  	want: true,
   268  }, {
   269  	input: `profile: yes
   270  `,
   271  	lines: []string{"profile: yes"},
   272  	want:  true,
   273  }, {
   274  	input: `profile: yes
   275  profile: no
   276  `,
   277  	lines: []string{"profile: yes"},
   278  	want:  true,
   279  }, {
   280  	input: `profile: yes
   281  profile: no
   282  `,
   283  	lines: []string{"profile: yes", "profile: no"},
   284  	want:  true,
   285  }, {
   286  	input: `profile: yes
   287  profile: no
   288  `,
   289  	lines: []string{"profile: no"},
   290  	want:  false,
   291  }}
   292  
   293  func TestValidateOutput(t *testing.T) {
   294  	for _, tt := range validateOutputTests {
   295  		r := strings.NewReader(tt.input)
   296  		got := validateOutput(r, tt.lines)
   297  		if tt.want != got {
   298  			t.Errorf("validateOutput(%q, %q), want %v, got %v", tt.input, tt.lines, tt.want, got)
   299  		}
   300  	}
   301  }
   302  
   303  // runTest executes the go program supplied and returns the contents of stdout,
   304  // stderr, and an error which may contain status information about the result
   305  // of the program.
   306  func runTest(t *testing.T, code string) ([]byte, []byte, error) {
   307  	chk := func(err error) {
   308  		if err != nil {
   309  			t.Fatal(err)
   310  		}
   311  	}
   312  	gopath, err := ioutil.TempDir("", "profile-gopath")
   313  	chk(err)
   314  	defer os.RemoveAll(gopath)
   315  
   316  	srcdir := filepath.Join(gopath, "src")
   317  	err = os.Mkdir(srcdir, 0755)
   318  	chk(err)
   319  	src := filepath.Join(srcdir, "main.go")
   320  	err = ioutil.WriteFile(src, []byte(code), 0644)
   321  	chk(err)
   322  
   323  	cmd := exec.Command("go", "run", src)
   324  
   325  	var stdout, stderr bytes.Buffer
   326  	cmd.Stdout = &stdout
   327  	cmd.Stderr = &stderr
   328  	err = cmd.Run()
   329  	return stdout.Bytes(), stderr.Bytes(), err
   330  }