summaryrefslogtreecommitdiffstats
path: root/vendor/google.golang.org/appengine/aetest/instance_vm.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/google.golang.org/appengine/aetest/instance_vm.go')
-rw-r--r--vendor/google.golang.org/appengine/aetest/instance_vm.go282
1 files changed, 282 insertions, 0 deletions
diff --git a/vendor/google.golang.org/appengine/aetest/instance_vm.go b/vendor/google.golang.org/appengine/aetest/instance_vm.go
new file mode 100644
index 000000000..829979000
--- /dev/null
+++ b/vendor/google.golang.org/appengine/aetest/instance_vm.go
@@ -0,0 +1,282 @@
+// +build !appengine
+
+package aetest
+
+import (
+ "bufio"
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "regexp"
+ "time"
+
+ "golang.org/x/net/context"
+ "google.golang.org/appengine/internal"
+)
+
+// NewInstance launches a running instance of api_server.py which can be used
+// for multiple test Contexts that delegate all App Engine API calls to that
+// instance.
+// If opts is nil the default values are used.
+func NewInstance(opts *Options) (Instance, error) {
+ i := &instance{
+ opts: opts,
+ appID: "testapp",
+ startupTimeout: 15 * time.Second,
+ }
+ if opts != nil {
+ if opts.AppID != "" {
+ i.appID = opts.AppID
+ }
+ if opts.StartupTimeout > 0 {
+ i.startupTimeout = opts.StartupTimeout
+ }
+ }
+ if err := i.startChild(); err != nil {
+ return nil, err
+ }
+ return i, nil
+}
+
+func newSessionID() string {
+ var buf [16]byte
+ io.ReadFull(rand.Reader, buf[:])
+ return fmt.Sprintf("%x", buf[:])
+}
+
+// instance implements the Instance interface.
+type instance struct {
+ opts *Options
+ child *exec.Cmd
+ apiURL *url.URL // base URL of API HTTP server
+ adminURL string // base URL of admin HTTP server
+ appDir string
+ appID string
+ startupTimeout time.Duration
+ relFuncs []func() // funcs to release any associated contexts
+}
+
+// NewRequest returns an *http.Request associated with this instance.
+func (i *instance) NewRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
+ req, err := http.NewRequest(method, urlStr, body)
+ if err != nil {
+ return nil, err
+ }
+
+ // Associate this request.
+ req, release := internal.RegisterTestRequest(req, i.apiURL, func(ctx context.Context) context.Context {
+ ctx = internal.WithAppIDOverride(ctx, "dev~"+i.appID)
+ return ctx
+ })
+ i.relFuncs = append(i.relFuncs, release)
+
+ return req, nil
+}
+
+// Close kills the child api_server.py process, releasing its resources.
+func (i *instance) Close() (err error) {
+ for _, rel := range i.relFuncs {
+ rel()
+ }
+ i.relFuncs = nil
+ child := i.child
+ if child == nil {
+ return nil
+ }
+ defer func() {
+ i.child = nil
+ err1 := os.RemoveAll(i.appDir)
+ if err == nil {
+ err = err1
+ }
+ }()
+
+ if p := child.Process; p != nil {
+ errc := make(chan error, 1)
+ go func() {
+ errc <- child.Wait()
+ }()
+
+ // Call the quit handler on the admin server.
+ res, err := http.Get(i.adminURL + "/quit")
+ if err != nil {
+ p.Kill()
+ return fmt.Errorf("unable to call /quit handler: %v", err)
+ }
+ res.Body.Close()
+ select {
+ case <-time.After(15 * time.Second):
+ p.Kill()
+ return errors.New("timeout killing child process")
+ case err = <-errc:
+ // Do nothing.
+ }
+ }
+ return
+}
+
+func fileExists(path string) bool {
+ _, err := os.Stat(path)
+ return err == nil
+}
+
+func findPython() (path string, err error) {
+ for _, name := range []string{"python2.7", "python"} {
+ path, err = exec.LookPath(name)
+ if err == nil {
+ return
+ }
+ }
+ return
+}
+
+func findDevAppserver() (string, error) {
+ if p := os.Getenv("APPENGINE_DEV_APPSERVER"); p != "" {
+ if fileExists(p) {
+ return p, nil
+ }
+ return "", fmt.Errorf("invalid APPENGINE_DEV_APPSERVER environment variable; path %q doesn't exist", p)
+ }
+ return exec.LookPath("dev_appserver.py")
+}
+
+var apiServerAddrRE = regexp.MustCompile(`Starting API server at: (\S+)`)
+var adminServerAddrRE = regexp.MustCompile(`Starting admin server at: (\S+)`)
+
+func (i *instance) startChild() (err error) {
+ if PrepareDevAppserver != nil {
+ if err := PrepareDevAppserver(); err != nil {
+ return err
+ }
+ }
+ python, err := findPython()
+ if err != nil {
+ return fmt.Errorf("Could not find python interpreter: %v", err)
+ }
+ devAppserver, err := findDevAppserver()
+ if err != nil {
+ return fmt.Errorf("Could not find dev_appserver.py: %v", err)
+ }
+
+ i.appDir, err = ioutil.TempDir("", "appengine-aetest")
+ if err != nil {
+ return err
+ }
+ defer func() {
+ if err != nil {
+ os.RemoveAll(i.appDir)
+ }
+ }()
+ err = os.Mkdir(filepath.Join(i.appDir, "app"), 0755)
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "app.yaml"), []byte(i.appYAML()), 0644)
+ if err != nil {
+ return err
+ }
+ err = ioutil.WriteFile(filepath.Join(i.appDir, "app", "stubapp.go"), []byte(appSource), 0644)
+ if err != nil {
+ return err
+ }
+
+ appserverArgs := []string{
+ devAppserver,
+ "--port=0",
+ "--api_port=0",
+ "--admin_port=0",
+ "--automatic_restart=false",
+ "--skip_sdk_update_check=true",
+ "--clear_datastore=true",
+ "--clear_search_indexes=true",
+ "--datastore_path", filepath.Join(i.appDir, "datastore"),
+ }
+ if i.opts != nil && i.opts.StronglyConsistentDatastore {
+ appserverArgs = append(appserverArgs, "--datastore_consistency_policy=consistent")
+ }
+ appserverArgs = append(appserverArgs, filepath.Join(i.appDir, "app"))
+
+ i.child = exec.Command(python,
+ appserverArgs...,
+ )
+ i.child.Stdout = os.Stdout
+ var stderr io.Reader
+ stderr, err = i.child.StderrPipe()
+ if err != nil {
+ return err
+ }
+ stderr = io.TeeReader(stderr, os.Stderr)
+ if err = i.child.Start(); err != nil {
+ return err
+ }
+
+ // Read stderr until we have read the URLs of the API server and admin interface.
+ errc := make(chan error, 1)
+ go func() {
+ s := bufio.NewScanner(stderr)
+ for s.Scan() {
+ if match := apiServerAddrRE.FindStringSubmatch(s.Text()); match != nil {
+ u, err := url.Parse(match[1])
+ if err != nil {
+ errc <- fmt.Errorf("failed to parse API URL %q: %v", match[1], err)
+ return
+ }
+ i.apiURL = u
+ }
+ if match := adminServerAddrRE.FindStringSubmatch(s.Text()); match != nil {
+ i.adminURL = match[1]
+ }
+ if i.adminURL != "" && i.apiURL != nil {
+ break
+ }
+ }
+ errc <- s.Err()
+ }()
+
+ select {
+ case <-time.After(i.startupTimeout):
+ if p := i.child.Process; p != nil {
+ p.Kill()
+ }
+ return errors.New("timeout starting child process")
+ case err := <-errc:
+ if err != nil {
+ return fmt.Errorf("error reading child process stderr: %v", err)
+ }
+ }
+ if i.adminURL == "" {
+ return errors.New("unable to find admin server URL")
+ }
+ if i.apiURL == nil {
+ return errors.New("unable to find API server URL")
+ }
+ return nil
+}
+
+func (i *instance) appYAML() string {
+ return fmt.Sprintf(appYAMLTemplate, i.appID)
+}
+
+const appYAMLTemplate = `
+application: %s
+version: 1
+runtime: go
+api_version: go1
+
+handlers:
+- url: /.*
+ script: _go_app
+`
+
+const appSource = `
+package main
+import "google.golang.org/appengine"
+func main() { appengine.Main() }
+`