...

Source file src/github.com/google/pprof/internal/driver/browser_test.go

Documentation: github.com/google/pprof/internal/driver

     1  // Copyright 2023 Google Inc. All Rights Reserved.
     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 driver
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"os/exec"
    21  	"regexp"
    22  	"runtime"
    23  	"strings"
    24  	"testing"
    25  	"time"
    26  
    27  	_ "embed"
    28  
    29  	"github.com/chromedp/chromedp"
    30  )
    31  
    32  func maybeSkipBrowserTest(t *testing.T) {
    33  	// Limit to just Linux for now since this is expensive and the
    34  	// browser interactions should be platform agnostic.  If we ever
    35  	// see a benefit from wider testing, we can relax this.
    36  	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
    37  		t.Skip("This test only works on x86-64 Linux")
    38  	}
    39  
    40  	// Check that browser is available.
    41  	if _, err := exec.LookPath("google-chrome"); err == nil {
    42  		return
    43  	}
    44  	if _, err := exec.LookPath("chrome"); err == nil {
    45  		return
    46  	}
    47  	t.Skip("chrome not available")
    48  }
    49  
    50  // browserDeadline is the deadline to use for browser tests. This is long to
    51  // reduce flakiness in CI workflows.
    52  const browserDeadline = time.Second * 60
    53  
    54  func TestTopTable(t *testing.T) {
    55  	maybeSkipBrowserTest(t)
    56  
    57  	prof := makeFakeProfile()
    58  	server := makeTestServer(t, prof)
    59  	ctx, cancel := context.WithTimeout(context.Background(), browserDeadline)
    60  	defer cancel()
    61  	ctx, cancel = chromedp.NewContext(ctx)
    62  	defer cancel()
    63  
    64  	err := chromedp.Run(ctx,
    65  		chromedp.Navigate(server.URL+"/top"),
    66  		chromedp.WaitVisible(`#toptable`, chromedp.ByID),
    67  
    68  		// Check that fake profile entries show up in the right order.
    69  		matchRegexp(t, "#node0", `200ms.*F2`),
    70  		matchInOrder(t, "#toptable", "F2", "F3", "F1"),
    71  
    72  		// Check sorting by cumulative count.
    73  		chromedp.Click(`#cumhdr1`, chromedp.ByID),
    74  		matchInOrder(t, "#toptable", "F1", "F2", "F3"),
    75  	)
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  }
    80  
    81  func TestFlameGraph(t *testing.T) {
    82  	maybeSkipBrowserTest(t)
    83  
    84  	prof := makeFakeProfile()
    85  	server := makeTestServer(t, prof)
    86  	ctx, cancel := context.WithTimeout(context.Background(), browserDeadline)
    87  	defer cancel()
    88  	ctx, cancel = chromedp.NewContext(ctx)
    89  	defer cancel()
    90  
    91  	var ignored []byte // Some chromedp.Evaluate() versions wants non-nil result argument
    92  	err := chromedp.Run(ctx,
    93  		chromedp.Navigate(server.URL+"/flamegraph"),
    94  		chromedp.Evaluate(jsTestFixture, &ignored),
    95  		eval(t, jsCheckFlame),
    96  	)
    97  	if err != nil {
    98  		t.Fatal(err)
    99  	}
   100  }
   101  
   102  //go:embed testdata/testflame.js
   103  var jsCheckFlame string
   104  
   105  func TestSource(t *testing.T) {
   106  	maybeSkipBrowserTest(t)
   107  
   108  	prof := makeFakeProfile()
   109  	server := makeTestServer(t, prof)
   110  	ctx, cancel := context.WithTimeout(context.Background(), browserDeadline)
   111  	defer cancel()
   112  	ctx, cancel = chromedp.NewContext(ctx)
   113  	defer cancel()
   114  
   115  	err := chromedp.Run(ctx,
   116  		chromedp.Navigate(server.URL+"/source?f=F3"),
   117  		chromedp.WaitVisible(`#content`, chromedp.ByID),
   118  		matchRegexp(t, "#content", `F3`),            // Header
   119  		matchRegexp(t, "#content", `Total:.*100ms`), // Total for function
   120  		matchRegexp(t, "#content", `\b22\b.*100ms`), // Line 22
   121  	)
   122  	if err != nil {
   123  		t.Fatal(err)
   124  	}
   125  }
   126  
   127  // matchRegexp is a chromedp.Action that fetches the text of the first
   128  // node that matched query and checks that the text matches regexp re.
   129  func matchRegexp(t *testing.T, query, re string) chromedp.ActionFunc {
   130  	return func(ctx context.Context) error {
   131  		var value string
   132  		err := chromedp.Text(query, &value, chromedp.ByQuery).Do(ctx)
   133  		if err != nil {
   134  			return fmt.Errorf("text %s: %v", query, err)
   135  		}
   136  		t.Logf("text %s:\n%s", query, value)
   137  		m, err := regexp.MatchString(re, value)
   138  		if err != nil {
   139  			return err
   140  		}
   141  		if !m {
   142  			return fmt.Errorf("%s: did not find %q in\n%s", query, re, value)
   143  		}
   144  		return nil
   145  	}
   146  }
   147  
   148  // matchInOrder is a chromedp.Action that fetches the text of the first
   149  // node that matched query and checks that the supplied sequence of
   150  // strings occur in order in the text.
   151  func matchInOrder(t *testing.T, query string, sequence ...string) chromedp.ActionFunc {
   152  	return func(ctx context.Context) error {
   153  		var value string
   154  		err := chromedp.Text(query, &value, chromedp.ByQuery).Do(ctx)
   155  		if err != nil {
   156  			return fmt.Errorf("text %s: %v", query, err)
   157  		}
   158  		t.Logf("text %s:\n%s", query, value)
   159  		remaining := value
   160  		for _, s := range sequence {
   161  			pos := strings.Index(remaining, s)
   162  			if pos < 0 {
   163  				return fmt.Errorf("%s: did not find %q in expected order %v  in\n%s", query, s, sequence, value)
   164  			}
   165  			remaining = remaining[pos+len(s):]
   166  		}
   167  		return nil
   168  	}
   169  }
   170  
   171  // eval runs the specified javascript in the browser. The javascript must
   172  // return an [][]any, where each of the []any starts with either "LOG" or
   173  // "ERROR" (see testdata/testfixture.js).
   174  func eval(t *testing.T, js string) chromedp.ActionFunc {
   175  	return func(ctx context.Context) error {
   176  		var result [][]any
   177  		err := chromedp.Evaluate(js, &result).Do(ctx)
   178  		if err != nil {
   179  			return err
   180  		}
   181  		for _, s := range result {
   182  			if len(s) > 0 && s[0] == "LOG" {
   183  				t.Log(s[1:]...)
   184  			} else if len(s) > 0 && s[0] == "ERROR" {
   185  				t.Error(s[1:]...)
   186  			} else {
   187  				t.Error(s...) // Treat missing prefix as an error.
   188  			}
   189  		}
   190  		return nil
   191  	}
   192  }
   193  
   194  //go:embed testdata/testfixture.js
   195  var jsTestFixture string
   196  

View as plain text