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 }