github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/tests/runtime_test.go (about)

     1  //go:build js && gopherjs
     2  
     3  package tests
     4  
     5  import (
     6  	"fmt"
     7  	"runtime"
     8  	"testing"
     9  
    10  	"github.com/google/go-cmp/cmp"
    11  	"github.com/gopherjs/gopherjs/js"
    12  )
    13  
    14  func Test_parseCallFrame(t *testing.T) {
    15  	tests := []struct {
    16  		name  string
    17  		input string
    18  		want  string
    19  	}{
    20  		{
    21  			name:  "Chrome 96.0.4664.110 on Linux #1",
    22  			input: "at foo (eval at $b (https://gopherjs.github.io/playground/playground.js:102:11836), <anonymous>:25887:60)",
    23  			want:  "foo https://gopherjs.github.io/playground/playground.js 102 11836",
    24  		},
    25  		{
    26  			name:  "Chrome 96, anonymous eval",
    27  			input: "	at eval (<anonymous>)",
    28  			want:  "eval <anonymous> 0 0",
    29  		},
    30  		{
    31  			name:  "Chrome 96, anonymous Array.forEach",
    32  			input: "	at Array.forEach (<anonymous>)",
    33  			want:  "Array.forEach <anonymous> 0 0",
    34  		},
    35  		{
    36  			name:  "Chrome 96, file location only",
    37  			input: "at https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js:31:225",
    38  			want:  "<none> https://ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js 31 225",
    39  		},
    40  		{
    41  			name:  "Chrome 96, aliased function",
    42  			input: "at k.e.$externalizeWrapper.e.$externalizeWrapper [as run] (https://gopherjs.github.io/playground/playground.js:5:30547)",
    43  			want:  "run https://gopherjs.github.io/playground/playground.js 5 30547",
    44  		},
    45  		{
    46  			name:  "Node.js v12.22.5",
    47  			input: "    at Script.runInThisContext (vm.js:120:18)",
    48  			want:  "Script.runInThisContext vm.js 120 18",
    49  		},
    50  		{
    51  			name:  "Node.js v12.22.5, aliased function",
    52  			input: "at REPLServer.runBound [as eval] (domain.js:440:12)",
    53  			want:  "eval domain.js 440 12",
    54  		},
    55  		{
    56  			name:  "Firefox 78.15.0esr Linux",
    57  			input: "getEvalResult@resource://devtools/server/actors/webconsole/eval-with-debugger.js:231:24",
    58  			want:  "getEvalResult resource://devtools/server/actors/webconsole/eval-with-debugger.js 231 24",
    59  		},
    60  	}
    61  
    62  	for _, tt := range tests {
    63  		t.Run(tt.name, func(t *testing.T) {
    64  			lines := js.Global.Get("String").New(tt.input)
    65  			frame := runtime.ParseCallFrame(lines)
    66  			got := fmt.Sprintf("%v %v %v %v", frame.FuncName, frame.File, frame.Line, frame.Col)
    67  			if tt.want != got {
    68  				t.Errorf("Unexpected result: %s", got)
    69  			}
    70  		})
    71  	}
    72  }
    73  
    74  func TestBuildPlatform(t *testing.T) {
    75  	if runtime.GOOS != "js" {
    76  		t.Errorf("Got runtime.GOOS=%q. Want: %q.", runtime.GOOS, "js")
    77  	}
    78  	if runtime.GOARCH != "ecmascript" {
    79  		t.Errorf("Got runtime.GOARCH=%q. Want: %q.", runtime.GOARCH, "ecmascript")
    80  	}
    81  }
    82  
    83  type funcName string
    84  
    85  func masked(_ funcName) funcName { return "<MASKED>" }
    86  
    87  type callStack []funcName
    88  
    89  func (c *callStack) capture() {
    90  	*c = nil
    91  	pc := [100]uintptr{}
    92  	depth := runtime.Callers(0, pc[:])
    93  	frames := runtime.CallersFrames(pc[:depth])
    94  	for true {
    95  		frame, more := frames.Next()
    96  		*c = append(*c, funcName(frame.Function))
    97  		if !more {
    98  			break
    99  		}
   100  	}
   101  }
   102  
   103  func TestCallers(t *testing.T) {
   104  	got := callStack{}
   105  
   106  	// Some of the GopherJS function names don't match upstream Go, or even the
   107  	// function names in the Go source when minified.
   108  	// Until https://github.com/gopherjs/gopherjs/issues/1085 is resolved, the
   109  	// mismatch is difficult to avoid, but we can at least use "masked" frames to
   110  	// make sure the number of frames matches expected.
   111  	want := callStack{
   112  		masked("runtime.Callers"),
   113  		masked("github.com/gopherjs/gopherjs/tests.(*callerNames).capture"),
   114  		masked("github.com/gopherjs/gopherjs/tests.TestCallers.func{1,2}"),
   115  		masked("testing.tRunner"),
   116  		"runtime.goexit",
   117  	}
   118  
   119  	opts := cmp.Comparer(func(a, b funcName) bool {
   120  		if a == masked("") || b == masked("") {
   121  			return true
   122  		}
   123  		return a == b
   124  	})
   125  
   126  	t.Run("Normal", func(t *testing.T) {
   127  		got.capture()
   128  		if diff := cmp.Diff(want, got, opts); diff != "" {
   129  			t.Errorf("runtime.Callers() returned a diff (-want,+got):\n%s", diff)
   130  		}
   131  	})
   132  
   133  	t.Run("Deferred", func(t *testing.T) {
   134  		defer func() {
   135  			if diff := cmp.Diff(want, got, opts); diff != "" {
   136  				t.Errorf("runtime.Callers() returned a diff (-want,+got):\n%s", diff)
   137  			}
   138  		}()
   139  		defer got.capture()
   140  	})
   141  
   142  	t.Run("Recover", func(t *testing.T) {
   143  		defer func() {
   144  			recover()
   145  			got.capture()
   146  
   147  			want := callStack{
   148  				masked("runtime.Callers"),
   149  				masked("github.com/gopherjs/gopherjs/tests.(*callerNames).capture"),
   150  				masked("github.com/gopherjs/gopherjs/tests.TestCallers.func3.1"),
   151  				"runtime.gopanic",
   152  				masked("github.com/gopherjs/gopherjs/tests.TestCallers.func{1,2}"),
   153  				masked("testing.tRunner"),
   154  				"runtime.goexit",
   155  			}
   156  			if diff := cmp.Diff(want, got, opts); diff != "" {
   157  				t.Errorf("runtime.Callers() returned a diff (-want,+got):\n%s", diff)
   158  			}
   159  		}()
   160  		panic("panic")
   161  	})
   162  }