Skip to content

Commit

Permalink
Initial commit of pool and unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Kenneth Shaw committed Feb 9, 2017
1 parent c112899 commit 42c6cca
Show file tree
Hide file tree
Showing 5 changed files with 305 additions and 31 deletions.
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: go
go:
- 1.7
- tip
before_install:
- mkdir -p /headless_shell
- wget -O /headless_shell/headless_shell.tar.bz2 https://storage.googleapis.com/docker-chrome-headless/headless_shell.tar.bz2
- tar -jxf /headless_shell/headless_shell.tar.bz2 -C /headless_shell
- go get github.com/mattn/goveralls
script:
- CHROMEDP_NO_SANDBOX=true go test -v -coverprofile=coverage.out
- goveralls -service=travis-ci -coverprofile=coverage.out
55 changes: 24 additions & 31 deletions chromedp.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,6 @@ func New(ctxt context.Context, opts ...Option) (*CDP, error) {
}
}

// setup context
if ctxt == nil {
var cancel func()
ctxt, cancel = context.WithCancel(context.Background())
defer cancel()
}

// check for supplied runner, if none then create one
if c.r == nil && c.watch == nil {
c.r, err = runner.Run(ctxt, c.opts...)
Expand Down Expand Up @@ -312,30 +305,30 @@ func (c *CDP) NewTarget(id *string, opts ...client.Option) Action {

// NewTargetWithURL creates a new Chrome target, sets it as the active target,
// and then navigates to the specified url.
func (c *CDP) NewTargetWithURL(urlstr string, id *string, opts ...client.Option) Action {
return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
n, err := c.newTarget(ctxt, opts...)
if err != nil {
return err
}

l := c.GetHandlerByID(n)
if l == nil {
return errors.New("could not retrieve newly created target")
}

/*err = Navigate(l, urlstr).Do(ctxt)
if err != nil {
return err
}
if id != nil {
*id = n
}*/

return nil
})
}
//func (c *CDP) NewTargetWithURL(urlstr string, id *string, opts ...client.Option) Action {
// return ActionFunc(func(ctxt context.Context, h cdp.FrameHandler) error {
// n, err := c.newTarget(ctxt, opts...)
// if err != nil {
// return err
// }
//
// l := c.GetHandlerByID(n)
// if l == nil {
// return errors.New("could not retrieve newly created target")
// }
//
// /*err = Navigate(l, urlstr).Do(ctxt)
// if err != nil {
// return err
// }
//
// if id != nil {
// *id = n
// }*/
//
// return nil
// })
//}

// CloseByIndex closes the Chrome target with specified index i.
func (c *CDP) CloseByIndex(i int) Action {
Expand Down
61 changes: 61 additions & 0 deletions chromedp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package chromedp

import (
"context"
"log"
"os"
"strings"
"testing"
)

var pool *Pool

var defaultContext = context.Background()

func TestMain(m *testing.M) {
var err error

pool, err = NewPool()
if err != nil {
log.Fatal(err)
}

code := m.Run()

err = pool.Shutdown()
if err != nil {
log.Fatal(err)
}

os.Exit(code)
}

func TestNavigate(t *testing.T) {
var err error

c, err := pool.Allocate(defaultContext)
if err != nil {
t.Fatal(err)
}
defer c.Release()

err = c.Run(defaultContext, Navigate("https://www.google.com/"))
if err != nil {
t.Fatal(err)
}

err = c.Run(defaultContext, WaitVisible(`#hplogo`, ByID))
if err != nil {
t.Fatal(err)
}

var urlstr string
err = c.Run(defaultContext, Location(&urlstr))
if err != nil {
t.Fatal(err)
}

if !strings.HasPrefix(urlstr, "https://www.google.") {
t.Errorf("expected to be on google, got: %v", urlstr)
}
}
183 changes: 183 additions & 0 deletions pool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package chromedp

import (
"context"
"fmt"
"sync"

"github.com/knq/chromedp/runner"
)

const (
// DefaultStartPort is the default start port number.
DefaultStartPort = 9000

// DefaultEndPort is the default end port number.
DefaultEndPort = 10000
)

// Pool provides a pool of running Chrome processes.
type Pool struct {
// start is the start port.
start int

// end is the end port.
end int

// res are the running chrome resources.
res map[int]*Res

rw sync.RWMutex
}

// NewPool creates a new Chrome runner pool.
func NewPool(opts ...PoolOption) (*Pool, error) {
var err error

p := &Pool{
start: DefaultStartPort,
end: DefaultEndPort,
res: make(map[int]*Res),
}

// apply opts
for _, o := range opts {
err = o(p)
if err != nil {
return nil, err
}
}

return p, err
}

// Shutdown releases all the pool resources.
func (p *Pool) Shutdown() error {
p.rw.Lock()
defer p.rw.Unlock()

for _, r := range p.res {
r.cancel()
}

return nil
}

// Allocate creates a new process runner and returns it.
func (p *Pool) Allocate(ctxt context.Context, opts ...runner.CommandLineOption) (*Res, error) {
var err error

ctxt, cancel := context.WithCancel(ctxt)

r := &Res{
p: p,
ctxt: ctxt,
cancel: cancel,
port: p.next(),
}

// create runner
r.r, err = runner.New(append([]runner.CommandLineOption{
runner.Headless("", r.port),
}, opts...)...)
if err != nil {
cancel()
return nil, err
}

// start runner
err = r.r.Start(ctxt)
if err != nil {
cancel()
return nil, err
}

// setup cdp
r.c, err = New(ctxt, WithRunner(r.r))
if err != nil {
cancel()
return nil, err
}

p.rw.Lock()
defer p.rw.Unlock()

p.res[r.port] = r

return r, nil
}

// next returns the next available port number.
func (p *Pool) next() int {
p.rw.Lock()
defer p.rw.Unlock()

var found bool
var i int
for i = p.start; i < p.end; i++ {
if _, ok := p.res[i]; !ok {
found = true
break
}
}

if !found {
panic("no ports available")
}

return i
}

// Res is a pool resource.
type Res struct {
p *Pool
ctxt context.Context
cancel func()
port int
r *runner.Runner
c *CDP
}

// Release releases the pool resource.
func (r *Res) Release() error {
r.cancel()

r.p.rw.Lock()
defer r.p.rw.Unlock()

delete(r.p.res, r.port)

return nil
}

// Port returns the allocated port for the pool resource.
func (r *Res) Port() int {
return r.port
}

// URL returns a formatted URL for the pool resource.
func (r *Res) URL() string {
return fmt.Sprintf("http://localhost:%d/json", r.port)
}

// CDP returns the actual CDP instance.
func (r *Res) CDP() *CDP {
return r.c
}

// Run runs an action.
func (r *Res) Run(ctxt context.Context, a Action) error {
return r.c.Run(ctxt, a)
}

// PoolOption is a pool option.
type PoolOption func(*Pool) error

// PortRange is a pool option to set the port range to use.
func PortRange(start, end int) PoolOption {
return func(p *Pool) error {
p.start = start
p.end = end
return nil
}
}
25 changes: 25 additions & 0 deletions runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,26 @@ func Path(path string) CommandLineOption {
}
}

// Headless is the Chrome command line option to set the default settings for
// running the headless_shell executable. If path is empty, then an attempt
// will be made to find headless_shell on the path.
func Headless(path string, port int) CommandLineOption {
if path == "" {
path, _ = exec.LookPath("headless_shell")
}

return func(m map[string]interface{}) error {
m["exec-path"] = path
m["remote-debugging-port"] = port

if os.Getenv("CHROMEDP_NO_SANDBOX") != "" {
m["no-sandbox"] = true
}

return nil
}
}

// ExecPath is a Chrome command line option to set the exec path.
func ExecPath(path string) CommandLineOption {
return Flag("exec-path", path)
Expand Down Expand Up @@ -370,6 +390,11 @@ func UserAgent(userAgent string) CommandLineOption {
return Flag("user-agent", userAgent)
}

// NoSandbox is the Chrome comamnd line option to disable the sandbox.
func NoSandbox(m map[string]interface{}) error {
return Flag("no-sandbox", true)(m)
}

// CmdOpt is a Chrome command line option to modify the underlying exec.Cmd
// prior to invocation.
func CmdOpt(o func(*exec.Cmd) error) CommandLineOption {
Expand Down

0 comments on commit 42c6cca

Please sign in to comment.