Skip to content

Commit

Permalink
support parallel execution of unit tests
Browse files Browse the repository at this point in the history
resolve go-rod#236
  • Loading branch information
ysmood committed Oct 6, 2020
1 parent 4b4bc8b commit c695a63
Show file tree
Hide file tree
Showing 63 changed files with 5,497 additions and 5,384 deletions.
9 changes: 8 additions & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,14 @@ There are several helper functions in the [setup_test.go](../setup_test.go) for

1. `docker run --rm -v rod:/root -v $(pwd):/t rod go test -v -run /Click`

### Convention of the git commit message
### Parallel execution of tests

Because we check goroutine leak on each test, parallel execution will pollution the global goroutine stack.
You can't have your cake and eat it. By default, we trade speed for safety.

To enable parallel execution, you can use the `-short` flag, for example: `go test -short`. When it's enabled, the goroutine leak detection for each test will be disabled, but the detection for the whole test program will still work as well.

## Convention of the git commit message

The commit message follows the rules [here](https://github.com/torvalds/subsurface-for-dirk/blame/a48494d2fbed58c751e9b7e8fbff88582f9b2d02/README#L88). We don't use rules like [Conventional Commits](https://www.conventionalcommits.org/) because it's hard for beginners to write correct commit messages. It will encourage reviewers to spend more time on high-level problems, not the details. We also want to reduce the overhead when reading the git-blame, for example, `fix: correct minor typos in code` is the same as `fix minor typos in code`, there's no need to repeat content in the title line.

Expand Down
13 changes: 4 additions & 9 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,15 @@ assignees: ''

[Don't rush to claim that you have found a bug.](http://www.catb.org/~esr/faqs/smart-questions.html#idm368)

## To Reproduce
## Steps to reproduce

Check before you submit:

- Is this issue general enough to benefit others?
- Can I provide code to reproduce the issue?
- Can I reduce the unrelated code to make the example minimal?
- Is the example code standalone?
- What did you do?
- What have you tried to solve the problem?

## Expected behavior

What you expected to get.
What do you expect to get?

## Actual result

What you actually got.
What did you actually get?
6 changes: 4 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ jobs:

- run: go generate

- run: godev -m 100
- run: go test -short -cover -coverprofile=coverage.txt ./...

- run: go run ./lib/utils/check-cov

- uses: codecov/codecov-action@v1
if: ${{ always() }}
Expand All @@ -44,7 +46,7 @@ jobs:
- uses: actions/checkout@v2

- name: test
run: go test -v -race -run Test
run: go test -race -short -run /

docker:
runs-on: ubuntu-latest
Expand Down
47 changes: 23 additions & 24 deletions browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ package rod

import (
"context"
"encoding/json"
"reflect"
"sync"
"time"
Expand All @@ -23,8 +22,9 @@ import (
"github.com/ysmood/goob"
)

// Browser implements the proto.Caller interface
var _ proto.Caller = &Browser{}
// Browser implements these interfaces
var _ proto.Client = &Browser{}
var _ proto.Contextable = &Browser{}

// Browser represents the browser.
// It doesn't depends on file system, it should work with remote browser seamlessly.
Expand All @@ -38,14 +38,16 @@ type Browser struct {
// BrowserContextID is the id for incognito window
BrowserContextID proto.BrowserBrowserContextID

logger utils.Logger

slowmotion time.Duration // see defaults.slow
trace bool // see defaults.Trace
traceLog TraceLog
headless bool
monitor string

defaultViewport *proto.EmulationSetDeviceMetricsOverride

client Client
client CDPClient
event *goob.Observable // all the browser events from cdp client
targetsLock *sync.Mutex

Expand All @@ -62,7 +64,8 @@ func New() *Browser {
sleeper: DefaultSleeper,
slowmotion: defaults.Slow,
trace: defaults.Trace,
traceLog: defaultTraceLog,
monitor: defaults.Monitor,
logger: DefaultLogger,
defaultViewport: devices.LaptopWithMDPIScreen.Metrics(true),
targetsLock: &sync.Mutex{},
states: &sync.Map{},
Expand Down Expand Up @@ -100,18 +103,20 @@ func (b *Browser) Trace(enable bool) *Browser {
return b
}

// TraceLog overrides the default log functions for tracing
func (b *Browser) TraceLog(l TraceLog) *Browser {
if l == nil {
b.traceLog = defaultTraceLog
} else {
b.traceLog = l
}
// Monitor address to listen if not empty. Shortcut for Browser.ServeMonitor
func (b *Browser) Monitor(url string) *Browser {
b.monitor = url
return b
}

// Logger overrides the default log functions for tracing
func (b *Browser) Logger(l utils.Logger) *Browser {
b.logger = l
return b
}

// Client set the cdp client
func (b *Browser) Client(c Client) *Browser {
func (b *Browser) Client(c CDPClient) *Browser {
b.client = c
return b
}
Expand Down Expand Up @@ -141,8 +146,8 @@ func (b *Browser) Connect() error {

b.initEvents()

if defaults.Monitor != "" {
launcher.NewBrowser().Open(b.ServeMonitor(defaults.Monitor))
if b.monitor != "" {
launcher.NewBrowser().Open(b.ServeMonitor(b.monitor))
}

return b.setHeadless()
Expand Down Expand Up @@ -234,7 +239,7 @@ func (b *Browser) eachEvent(
// Only enabled domains will emit events to cdp client.
// We enable the domains for the event types if it's not enabled.
// We recover the domains to their previous states after the wait ends.
domain, _ := proto.ParseMethodName(reflect.New(eType).Interface().(proto.Payload).MethodName())
domain, _ := proto.ParseMethodName(reflect.New(eType).Interface().(proto.Payload).ProtoName())
var enable proto.Payload
if domain == "Target" { // only Target domain is special
enable = proto.TargetSetDiscoverTargets{Discover: true}
Expand Down Expand Up @@ -300,22 +305,16 @@ func (b *Browser) waitEvent(ctx context.Context, sessionID proto.TargetSessionID
}

// Call raw cdp interface directly
func (b *Browser) Call(ctx context.Context, sessionID, methodName string, params json.RawMessage) (res []byte, err error) {
func (b *Browser) Call(ctx context.Context, sessionID, methodName string, params interface{}) (res []byte, err error) {
res, err = b.client.Call(ctx, sessionID, methodName, params)
if err != nil {
return nil, err
}

b.set(proto.TargetSessionID(sessionID), methodName, params)

return
}

// CallContext parameters for proto
func (b *Browser) CallContext() (context.Context, proto.Client, string) {
return b.ctx, b, ""
}

// PageFromTarget creates a Page instance from a targetID
func (b *Browser) PageFromTarget(targetID proto.TargetTargetID) (*Page, error) {
b.targetsLock.Lock()
Expand Down
47 changes: 25 additions & 22 deletions browser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rod_test
import (
"context"
"errors"
"io/ioutil"
"net/http"
"os"
"os/exec"
Expand All @@ -17,6 +18,7 @@ import (
"github.com/go-rod/rod/lib/launcher"
"github.com/go-rod/rod/lib/proto"
"github.com/go-rod/rod/lib/utils"
"github.com/ysmood/gson"
)

func (c C) Incognito() {
Expand All @@ -28,8 +30,8 @@ func (c C) Incognito() {
defer page.MustClose()
page.MustEval(`k => localStorage[k] = 1`, k)

c.Nil(c.page.MustNavigate(file).MustEval(`k => localStorage[k]`, k).Value())
c.Eq(1, page.MustEval(`k => localStorage[k]`, k).Int())
c.True(c.page.MustNavigate(file).MustEval(`k => localStorage[k]`, k).Nil())
c.Eq(page.MustEval(`k => localStorage[k]`, k).Str(), "1") // localStorage can only store string
}

func (c C) PageErr() {
Expand Down Expand Up @@ -61,9 +63,9 @@ func (c C) BrowserPages() {
c.Len(pages, 2)

{
c.mc.stub(1, proto.TargetGetTargets{}, func(send StubSend) (proto.JSON, error) {
c.mc.stub(1, proto.TargetGetTargets{}, func(send StubSend) (gson.JSON, error) {
d, _ := send()
return d.Set("targetInfos.0.type", "iframe")
return *d.Set("targetInfos.0.type", "iframe"), nil
})
pages := c.browser.MustPages()
c.Len(pages, 1)
Expand Down Expand Up @@ -141,33 +143,32 @@ func (c C) Monitor() {

res, err := http.Get(host + "/screenshot")
c.E(err)
c.Gt(len(utils.MustReadBytes(res.Body)), 10)
img, err := ioutil.ReadAll(res.Body)
c.E(err)
c.Gt(len(img), 10)

res, err = http.Get(host + "/api/page/test")
c.E(err)
c.Eq(400, res.StatusCode)
c.Eq(-32602, utils.MustReadJSON(res.Body).Get("code").Int())
c.Eq(-32602, gson.New(res.Body).Get("code").Int())
}

func (c C) MonitorErr() {
defaults.Monitor = "abc"
defer defaults.ResetWithEnv("")

l := launcher.New()
u := l.MustLaunch()
defer l.Kill()

c.Panic(func() {
rod.New().ControlURL(u).MustConnect()
rod.New().Monitor("abc").ControlURL(u).MustConnect()
})
}

func (c C) Trace() {
var msg *rod.TraceMsg
c.browser.TraceLog(func(m *rod.TraceMsg) { msg = m })
c.browser.Logger(utils.Log(func(list ...interface{}) { msg = list[0].(*rod.TraceMsg) }))
c.browser.Trace(true).Slowmotion(time.Microsecond)
defer func() {
c.browser.TraceLog(nil)
c.browser.Logger(rod.DefaultLogger)
c.browser.Trace(defaults.Trace).Slowmotion(defaults.Slow)
}()

Expand All @@ -184,8 +185,10 @@ func (c C) Trace() {
}

func (c C) TraceLogs() {
c.browser.Logger(utils.LoggerQuiet)
c.browser.Trace(true)
defer func() {
c.browser.Logger(rod.DefaultLogger)
c.browser.Trace(defaults.Trace)
}()

Expand All @@ -199,9 +202,9 @@ func (c C) TraceLogs() {

func (c C) ConcurrentOperations() {
p := c.page.MustNavigate(srcFile("fixtures/click.html"))
list := []int64{}
list := []int{}
lock := sync.Mutex{}
add := func(item int64) {
add := func(item int) {
lock.Lock()
defer lock.Unlock()
list = append(list, item)
Expand All @@ -213,7 +216,7 @@ func (c C) ConcurrentOperations() {
add(p.MustEval(`1`).Int())
})()

c.Eq([]int64{1, 2}, list)
c.Eq([]int{1, 2}, list)
}

func (c C) PromiseLeak() {
Expand Down Expand Up @@ -348,12 +351,12 @@ func (c C) BinarySize() {
stat, err := os.Stat("tmp/translator")
c.E(err)

c.Lt(float64(stat.Size())/1024/1024, 8.65) // mb
c.Lte(float64(stat.Size())/1024/1024, 8.23) // mb
}

func (c C) BrowserConnectErr() {
c.Panic(func() {
c := newMockClient(c.Testable.(*testing.T), nil)
c := newMockClient(nil)
c.connect = func() error { return errors.New("err") }
rod.New().Client(c).MustConnect()
})
Expand All @@ -362,7 +365,7 @@ func (c C) BrowserConnectErr() {
ch := make(chan *cdp.Event)
defer close(ch)

c := newMockClient(c.Testable.(*testing.T), nil)
c := newMockClient(nil)
c.connect = func() error { return nil }
c.event = ch
c.stubErr(1, proto.BrowserGetBrowserCommandLine{})
Expand All @@ -373,8 +376,8 @@ func (c C) BrowserConnectErr() {
func (c C) StreamReader() {
r := rod.NewStreamReader(c.page, "")

c.mc.stub(1, proto.IORead{}, func(send StubSend) (proto.JSON, error) {
return proto.NewJSON(proto.IOReadResult{
c.mc.stub(1, proto.IORead{}, func(send StubSend) (gson.JSON, error) {
return gson.New(proto.IOReadResult{
Data: "test",
}), nil
})
Expand All @@ -386,8 +389,8 @@ func (c C) StreamReader() {
_, err := r.Read(nil)
c.Err(err)

c.mc.stub(1, proto.IORead{}, func(send StubSend) (proto.JSON, error) {
return proto.NewJSON(proto.IOReadResult{
c.mc.stub(1, proto.IORead{}, func(send StubSend) (gson.JSON, error) {
return gson.New(proto.IOReadResult{
Base64Encoded: true,
Data: "@",
}), nil
Expand Down
15 changes: 15 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ func (b *Browser) Context(ctx context.Context) *Browser {
return &newObj
}

// GetContext interface
func (b *Browser) GetContext() context.Context {
return b.ctx
}

// Timeout for chained sub-operations
func (b *Browser) Timeout(d time.Duration) *Browser {
ctx, cancel := context.WithTimeout(b.ctx, d)
Expand Down Expand Up @@ -53,6 +58,11 @@ func (p *Page) Context(ctx context.Context) *Page {
return &newObj
}

// GetContext interface
func (p *Page) GetContext() context.Context {
return p.ctx
}

// Timeout for chained sub-operations
func (p *Page) Timeout(d time.Duration) *Page {
ctx, cancel := context.WithTimeout(p.ctx, d)
Expand Down Expand Up @@ -86,6 +96,11 @@ func (el *Element) Context(ctx context.Context) *Element {
return &newObj
}

// GetContext interface
func (el *Element) GetContext() context.Context {
return el.ctx
}

// Timeout for chained sub-operations
func (el *Element) Timeout(d time.Duration) *Element {
ctx, cancel := context.WithTimeout(el.ctx, d)
Expand Down
Loading

0 comments on commit c695a63

Please sign in to comment.