/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Generator for GCE compute wrapper code. You must regenerate the code after
// modifying this file:
//
//   $ go run gen/main.go > gen.go
package main

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"text/template"
	"time"

	"k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud/meta"
)

const (
	gofmt       = "gofmt"
	packageRoot = "k8s.io/kubernetes/pkg/cloudprovider/providers/gce/cloud"

	// readOnly specifies that the given resource is read-only and should not
	// have insert() or delete() methods generated for the wrapper.
	readOnly = iota
)

var flags = struct {
	gofmt bool
	mode  string
}{}

func init() {
	flag.BoolVar(&flags.gofmt, "gofmt", true, "run output through gofmt")
	flag.StringVar(&flags.mode, "mode", "src", "content to generate: src, test, dummy")
}

// gofmtContent runs "gofmt" on the given contents.
func gofmtContent(r io.Reader) string {
	cmd := exec.Command(gofmt, "-s")
	out := &bytes.Buffer{}
	cmd.Stdin = r
	cmd.Stdout = out
	cmdErr := &bytes.Buffer{}
	cmd.Stderr = cmdErr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, cmdErr.String())
		panic(err)
	}
	return out.String()
}

// genHeader generate the header for the file.
func genHeader(wr io.Writer) {
	const text = `/*
Copyright {{.Year}} The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This file was generated by "go run gen/main.go > gen.go". Do not edit
// directly.

package cloud

import (
	"context"
	"fmt"
	"net/http"
	"sync"

	"google.golang.org/api/googleapi"
	"github.com/golang/glog"

	"{{.PackageRoot}}/filter"
	"{{.PackageRoot}}/meta"

`
	tmpl := template.Must(template.New("header").Parse(text))
	values := map[string]string{
		"Year":        fmt.Sprintf("%v", time.Now().Year()),
		"PackageRoot": packageRoot,
	}
	if err := tmpl.Execute(wr, values); err != nil {
		panic(err)
	}

	var hasGA, hasAlpha, hasBeta bool
	for _, s := range meta.AllServices {
		switch s.Version() {
		case meta.VersionGA:
			hasGA = true
		case meta.VersionAlpha:
			hasAlpha = true
		case meta.VersionBeta:
			hasBeta = true
		}
	}
	if hasAlpha {
		fmt.Fprintln(wr, `	alpha "google.golang.org/api/compute/v0.alpha"`)
	}
	if hasBeta {
		fmt.Fprintln(wr, `	beta "google.golang.org/api/compute/v0.beta"`)
	}
	if hasGA {
		fmt.Fprintln(wr, `	ga "google.golang.org/api/compute/v1"`)
	}
	fmt.Fprintf(wr, ")\n\n")
}

// genStubs generates the interface and wrapper stubs.
func genStubs(wr io.Writer) {
	const text = `// Cloud is an interface for the GCE compute API.
type Cloud interface {
{{- range .All}}
	{{.WrapType}}() {{.WrapType}}
{{- end}}
}

// NewGCE returns a GCE.
func NewGCE(s *Service) *GCE {
	g := &GCE{
	{{- range .All}}
		{{.Field}}: &{{.GCEWrapType}}{s},
	{{- end}}
	}
	return g
}

// GCE implements Cloud.
var _ Cloud = (*GCE)(nil)

// GCE is the golang adapter for the compute APIs.
type GCE struct {
{{- range .All}}
	{{.Field}} *{{.GCEWrapType}}
{{- end}}
}

{{range .All}}
// {{.WrapType}} returns the interface for the {{.Version}} {{.Service}}.
func (gce *GCE) {{.WrapType}}() {{.WrapType}} {
	return gce.{{.Field}}
}
{{- end}}

// NewMockGCE returns a new mock for GCE.
func NewMockGCE(projectRouter ProjectRouter) *MockGCE {
	{{- range .Groups}}
	mock{{.Service}}Objs := map[meta.Key]*Mock{{.Service}}Obj{}
	{{- end}}

	mock := &MockGCE{
	{{- range .All}}
		{{.MockField}}: New{{.MockWrapType}}(projectRouter, mock{{.Service}}Objs),
	{{- end}}
	}
	return mock
}

// MockGCE implements Cloud.
var _ Cloud = (*MockGCE)(nil)

// MockGCE is the mock for the compute API.
type MockGCE struct {
{{- range .All}}
	{{.MockField}} *{{.MockWrapType}}
{{- end}}
}
{{range .All}}
// {{.WrapType}} returns the interface for the {{.Version}} {{.Service}}.
func (mock *MockGCE) {{.WrapType}}() {{.WrapType}} {
	return mock.{{.MockField}}
}
{{end}}

{{range .Groups}}
// Mock{{.Service}}Obj is used to store the various object versions in the shared
// map of mocked objects. This allows for multiple API versions to co-exist and
// share the same "view" of the objects in the backend.
type Mock{{.Service}}Obj struct {
	Obj interface{}
}
{{- if .HasAlpha}}
// ToAlpha retrieves the given version of the object.
func (m *Mock{{.Service}}Obj) ToAlpha() *{{.Alpha.FQObjectType}} {
	if ret, ok := m.Obj.(*{{.Alpha.FQObjectType}}); ok {
		return ret
	}
	// Convert the object via JSON copying to the type that was requested.
	ret := &{{.Alpha.FQObjectType}}{}
	if err := copyViaJSON(ret, m.Obj); err != nil {
		glog.Errorf("Could not convert %T to *{{.Alpha.FQObjectType}} via JSON: %v", m.Obj, err)
	}
	return ret
}
{{- end}}
{{- if .HasBeta}}
// ToBeta retrieves the given version of the object.
func (m *Mock{{.Service}}Obj) ToBeta() *{{.Beta.FQObjectType}} {
	if ret, ok := m.Obj.(*{{.Beta.FQObjectType}}); ok {
		return ret
	}
	// Convert the object via JSON copying to the type that was requested.
	ret := &{{.Beta.FQObjectType}}{}
	if err := copyViaJSON(ret, m.Obj); err != nil {
		glog.Errorf("Could not convert %T to *{{.Beta.FQObjectType}} via JSON: %v", m.Obj, err)
	}
	return ret
}
{{- end}}
{{- if .HasGA}}
// ToGA retrieves the given version of the object.
func (m *Mock{{.Service}}Obj) ToGA() *{{.GA.FQObjectType}} {
	if ret, ok := m.Obj.(*{{.GA.FQObjectType}}); ok {
		return ret
	}
		// Convert the object via JSON copying to the type that was requested.
	ret := &{{.GA.FQObjectType}}{}
	if err := copyViaJSON(ret, m.Obj); err != nil {
		glog.Errorf("Could not convert %T to *{{.GA.FQObjectType}} via JSON: %v", m.Obj, err)
	}
	return ret
}
{{- end}}
{{- end}}
`
	data := struct {
		All    []*meta.ServiceInfo
		Groups map[string]*meta.ServiceGroup
	}{meta.AllServices, meta.AllServicesByGroup}

	tmpl := template.Must(template.New("interface").Parse(text))
	if err := tmpl.Execute(wr, data); err != nil {
		panic(err)
	}
}

// genTypes generates the type wrappers.
func genTypes(wr io.Writer) {
	const text = `// {{.WrapType}} is an interface that allows for mocking of {{.Service}}.
type {{.WrapType}} interface {
{{- if .GenerateCustomOps}}
	// {{.WrapTypeOps}} is an interface with additional non-CRUD type methods.
	// This interface is expected to be implemented by hand (non-autogenerated).
	{{.WrapTypeOps}}
{{- end}}
{{- if .GenerateGet}}
	Get(ctx context.Context, key *meta.Key) (*{{.FQObjectType}}, error)
{{- end -}}
{{- if .GenerateList}}
{{- if .KeyIsGlobal}}
	List(ctx context.Context, fl *filter.F) ([]*{{.FQObjectType}}, error)
{{- end -}}
{{- if .KeyIsRegional}}
	List(ctx context.Context, region string, fl *filter.F) ([]*{{.FQObjectType}}, error)
{{- end -}}
{{- if .KeyIsZonal}}
	List(ctx context.Context, zone string, fl *filter.F) ([]*{{.FQObjectType}}, error)
{{- end -}}
{{- end -}}
{{- if .GenerateInsert}}
	Insert(ctx context.Context, key *meta.Key, obj *{{.FQObjectType}}) error
{{- end -}}
{{- if .GenerateDelete}}
	Delete(ctx context.Context, key *meta.Key) error
{{- end -}}
{{- if .AggregatedList}}
	AggregatedList(ctx context.Context, fl *filter.F) (map[string][]*{{.FQObjectType}}, error)
{{- end}}
{{- with .Methods -}}
{{- range .}}
	{{.InterfaceFunc}}
{{- end -}}
{{- end}}
}

// New{{.MockWrapType}} returns a new mock for {{.Service}}.
func New{{.MockWrapType}}(pr ProjectRouter, objs map[meta.Key]*Mock{{.Service}}Obj) *{{.MockWrapType}} {
	mock := &{{.MockWrapType}}{
		ProjectRouter: pr,

		Objects: objs,
		{{- if .GenerateGet}}
		GetError:    map[meta.Key]error{},
		{{- end -}}
		{{- if .GenerateInsert}}
		InsertError: map[meta.Key]error{},
		{{- end -}}
		{{- if .GenerateDelete}}
		DeleteError: map[meta.Key]error{},
		{{- end}}
	}
	return mock
}

// {{.MockWrapType}} is the mock for {{.Service}}.
type {{.MockWrapType}} struct {
	Lock sync.Mutex

	ProjectRouter ProjectRouter

	// Objects maintained by the mock.
	Objects map[meta.Key]*Mock{{.Service}}Obj

	// If an entry exists for the given key and operation, then the error
	// will be returned instead of the operation.
	{{- if .GenerateGet}}
	GetError map[meta.Key]error
	{{- end -}}
	{{- if .GenerateList}}
	ListError *error
	{{- end -}}
	{{- if .GenerateInsert}}
	InsertError map[meta.Key]error
	{{- end -}}
	{{- if .GenerateDelete}}
	DeleteError map[meta.Key]error
	{{- end -}}
	{{- if .AggregatedList}}
	AggregatedListError *error
	{{- end}}

	// xxxHook allow you to intercept the standard processing of the mock in
	// order to add your own logic. Return (true, _, _) to prevent the normal
	// execution flow of the mock. Return (false, nil, nil) to continue with
	// normal mock behavior/ after the hook function executes.
	{{- if .GenerateGet}}
	GetHook    func(ctx context.Context, key *meta.Key, m *{{.MockWrapType}}) (bool, *{{.FQObjectType}}, error)
	{{- end -}}
	{{- if .GenerateList}}
	{{- if .KeyIsGlobal}}
	ListHook   func(ctx context.Context, fl *filter.F, m *{{.MockWrapType}}) (bool, []*{{.FQObjectType}}, error)
	{{- end -}}
	{{- if .KeyIsRegional}}
	ListHook   func(ctx context.Context, region string, fl *filter.F, m *{{.MockWrapType}}) (bool, []*{{.FQObjectType}}, error)
	{{- end -}}
	{{- if .KeyIsZonal}}
	ListHook   func(ctx context.Context, zone string, fl *filter.F, m *{{.MockWrapType}}) (bool, []*{{.FQObjectType}}, error)
	{{- end}}
	{{- end -}}
	{{- if .GenerateInsert}}
	InsertHook func(ctx context.Context, key *meta.Key, obj *{{.FQObjectType}}, m *{{.MockWrapType}}) (bool, error)
	{{- end -}}
	{{- if .GenerateDelete}}
	DeleteHook func(ctx context.Context, key *meta.Key, m *{{.MockWrapType}}) (bool, error)
	{{- end -}}
	{{- if .AggregatedList}}
	AggregatedListHook func(ctx context.Context, fl *filter.F, m *{{.MockWrapType}}) (bool, map[string][]*{{.FQObjectType}}, error)
	{{- end}}

{{- with .Methods -}}
{{- range .}}
	{{.MockHook}}
{{- end -}}
{{- end}}

	// X is extra state that can be used as part of the mock. Generated code
	// will not use this field.
	X interface{}
}

{{- if .GenerateGet}}
// Get returns the object from the mock.
func (m *{{.MockWrapType}}) Get(ctx context.Context, key *meta.Key) (*{{.FQObjectType}}, error) {
	if m.GetHook != nil {
		if intercept, obj, err := m.GetHook(ctx, key, m);  intercept {
			glog.V(5).Infof("{{.MockWrapType}}.Get(%v, %s) = %+v, %v", ctx, key, obj ,err)
			return obj, err
		}
	}
	if !key.Valid() {
		return nil, fmt.Errorf("invalid GCE key (%+v)", key)
	}

	m.Lock.Lock()
	defer m.Lock.Unlock()

	if err, ok := m.GetError[*key]; ok {
		glog.V(5).Infof("{{.MockWrapType}}.Get(%v, %s) = nil, %v", ctx, key, err)
		return nil, err
	}
	if obj, ok := m.Objects[*key]; ok {
		typedObj := obj.To{{.VersionTitle}}()
		glog.V(5).Infof("{{.MockWrapType}}.Get(%v, %s) = %+v, nil", ctx, key, typedObj)
		return typedObj, nil
	}

	err := &googleapi.Error{
		Code: http.StatusNotFound,
		Message: fmt.Sprintf("{{.MockWrapType}} %v not found", key),
	}
	glog.V(5).Infof("{{.MockWrapType}}.Get(%v, %s) = nil, %v", ctx, key, err)
	return nil, err
}
{{- end}}

{{- if .GenerateList}}
{{if .KeyIsGlobal -}}
// List all of the objects in the mock.
func (m *{{.MockWrapType}}) List(ctx context.Context, fl *filter.F) ([]*{{.FQObjectType}}, error) {
{{- end -}}
{{- if .KeyIsRegional -}}
// List all of the objects in the mock in the given region.
func (m *{{.MockWrapType}}) List(ctx context.Context, region string, fl *filter.F) ([]*{{.FQObjectType}}, error) {
{{- end -}}
{{- if .KeyIsZonal -}}
// List all of the objects in the mock in the given zone.
func (m *{{.MockWrapType}}) List(ctx context.Context, zone string, fl *filter.F) ([]*{{.FQObjectType}}, error) {
{{- end}}
	if m.ListHook != nil {
		{{if .KeyIsGlobal -}}
		if intercept, objs, err := m.ListHook(ctx, fl, m);  intercept {
			glog.V(5).Infof("{{.MockWrapType}}.List(%v, %v) = [%v items], %v", ctx, fl, len(objs), err)
		{{- end -}}
		{{- if .KeyIsRegional -}}
		if intercept, objs, err := m.ListHook(ctx, region, fl, m);  intercept {
			glog.V(5).Infof("{{.MockWrapType}}.List(%v, %q, %v) = [%v items], %v", ctx, region, fl, len(objs), err)
		{{- end -}}
		{{- if .KeyIsZonal -}}
		if intercept, objs, err := m.ListHook(ctx, zone, fl, m);  intercept {
			glog.V(5).Infof("{{.MockWrapType}}.List(%v, %q, %v) = [%v items], %v", ctx, zone, fl, len(objs), err)
		{{- end}}
			return objs, err
		}
	}

	m.Lock.Lock()
	defer m.Lock.Unlock()

	if m.ListError != nil {
		err := *m.ListError
		{{if .KeyIsGlobal -}}
		glog.V(5).Infof("{{.MockWrapType}}.List(%v, %v) = nil, %v", ctx, fl, err)
		{{- end -}}
		{{- if .KeyIsRegional -}}
		glog.V(5).Infof("{{.MockWrapType}}.List(%v, %q, %v) = nil, %v", ctx, region, fl, err)
		{{- end -}}
		{{- if .KeyIsZonal -}}
		glog.V(5).Infof("{{.MockWrapType}}.List(%v, %q, %v) = nil, %v", ctx, zone, fl, err)
		{{- end}}

		return nil, *m.ListError
	}

	var objs []*{{.FQObjectType}}
{{- if .KeyIsGlobal}}
	for _, obj := range m.Objects {
{{- else}}
	for key, obj := range m.Objects {
{{- end -}}
{{- if .KeyIsRegional}}
		if key.Region != region {
			continue
		}
{{- end -}}
{{- if .KeyIsZonal}}
		if key.Zone != zone {
			continue
		}
{{- end}}
		if !fl.Match(obj.To{{.VersionTitle}}()) {
			continue
		}
		objs = append(objs, obj.To{{.VersionTitle}}())
	}

	{{if .KeyIsGlobal -}}
		glog.V(5).Infof("{{.MockWrapType}}.List(%v, %v) = [%v items], nil", ctx, fl, len(objs))
	{{- end -}}
	{{- if .KeyIsRegional -}}
		glog.V(5).Infof("{{.MockWrapType}}.List(%v, %q, %v) = [%v items], nil", ctx, region, fl, len(objs))
	{{- end -}}
	{{- if .KeyIsZonal -}}
		glog.V(5).Infof("{{.MockWrapType}}.List(%v, %q, %v) = [%v items], nil", ctx, zone, fl, len(objs))
	{{- end}}
	return objs, nil
}
{{- end}}

{{- if .GenerateInsert}}
// Insert is a mock for inserting/creating a new object.
func (m *{{.MockWrapType}}) Insert(ctx context.Context, key *meta.Key, obj *{{.FQObjectType}}) error {
	if m.InsertHook != nil {
		if intercept, err := m.InsertHook(ctx, key, obj, m);  intercept {
			glog.V(5).Infof("{{.MockWrapType}}.Insert(%v, %v, %+v) = %v", ctx, key, obj, err)
			return err
		}
	}
	if !key.Valid() {
		return fmt.Errorf("invalid GCE key (%+v)", key)
	}

	m.Lock.Lock()
	defer m.Lock.Unlock()

	if err, ok := m.InsertError[*key]; ok {
		glog.V(5).Infof("{{.MockWrapType}}.Insert(%v, %v, %+v) = %v", ctx, key, obj, err)
		return err
	}
	if _, ok := m.Objects[*key]; ok {
		err := &googleapi.Error{
			Code: http.StatusConflict,
			Message: fmt.Sprintf("{{.MockWrapType}} %v exists", key),
		}
		glog.V(5).Infof("{{.MockWrapType}}.Insert(%v, %v, %+v) = %v", ctx, key, obj, err)
		return err
	}

	obj.Name = key.Name
	projectID := m.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Resource}}")
	obj.SelfLink = SelfLink(meta.Version{{.VersionTitle}}, projectID, "{{.Resource}}", key)

	m.Objects[*key] = &Mock{{.Service}}Obj{obj}
	glog.V(5).Infof("{{.MockWrapType}}.Insert(%v, %v, %+v) = nil", ctx, key, obj)
	return nil
}
{{- end}}

{{- if .GenerateDelete}}
// Delete is a mock for deleting the object.
func (m *{{.MockWrapType}}) Delete(ctx context.Context, key *meta.Key) error {
	if m.DeleteHook != nil {
		if intercept, err := m.DeleteHook(ctx, key, m);  intercept {
			glog.V(5).Infof("{{.MockWrapType}}.Delete(%v, %v) = %v", ctx, key, err)
			return err
		}
	}
	if !key.Valid() {
		return fmt.Errorf("invalid GCE key (%+v)", key)
	}

	m.Lock.Lock()
	defer m.Lock.Unlock()

	if err, ok := m.DeleteError[*key]; ok {
		glog.V(5).Infof("{{.MockWrapType}}.Delete(%v, %v) = %v", ctx, key, err)
		return err
	}
	if _, ok := m.Objects[*key]; !ok {
		err := &googleapi.Error{
			Code: http.StatusNotFound,
			Message: fmt.Sprintf("{{.MockWrapType}} %v not found", key),
		}
		glog.V(5).Infof("{{.MockWrapType}}.Delete(%v, %v) = %v", ctx, key, err)
		return err
	}

	delete(m.Objects, *key)
	glog.V(5).Infof("{{.MockWrapType}}.Delete(%v, %v) = nil", ctx, key)
	return nil
}
{{- end}}

{{- if .AggregatedList}}
// AggregatedList is a mock for AggregatedList.
func (m *{{.MockWrapType}}) AggregatedList(ctx context.Context, fl *filter.F) (map[string][]*{{.FQObjectType}}, error) {
	if m.AggregatedListHook != nil {
		if intercept, objs, err := m.AggregatedListHook(ctx, fl, m); intercept {
			glog.V(5).Infof("{{.MockWrapType}}.AggregatedList(%v, %v) = [%v items], %v", ctx, fl, len(objs), err)
			return objs, err
		}
	}

	m.Lock.Lock()
	defer m.Lock.Unlock()

	if m.AggregatedListError != nil {
		err := *m.AggregatedListError
		glog.V(5).Infof("{{.MockWrapType}}.AggregatedList(%v, %v) = nil, %v", ctx, fl, err)
		return nil, err
	}

	objs := map[string][]*{{.FQObjectType}}{}
	for _, obj := range m.Objects {
		res, err := ParseResourceURL(obj.To{{.VersionTitle}}().SelfLink)
		{{- if .KeyIsRegional}}
		location := res.Key.Region
		{{- end -}}
		{{- if .KeyIsZonal}}
		location := res.Key.Zone
		{{- end}}
		if err != nil {
			glog.V(5).Infof("{{.MockWrapType}}.AggregatedList(%v, %v) = nil, %v", ctx, fl, err)
			return nil, err
		}
		if !fl.Match(obj.To{{.VersionTitle}}()) {
			continue
		}
		objs[location] = append(objs[location], obj.To{{.VersionTitle}}())
	}
	glog.V(5).Infof("{{.MockWrapType}}.AggregatedList(%v, %v) = [%v items], nil", ctx, fl, len(objs))
	return objs, nil
}
{{- end}}

// Obj wraps the object for use in the mock.
func (m *{{.MockWrapType}}) Obj(o *{{.FQObjectType}}) *Mock{{.Service}}Obj {
	return &Mock{{.Service}}Obj{o}
}

{{with .Methods -}}
{{- range .}}
// {{.Name}} is a mock for the corresponding method.
func (m *{{.MockWrapType}}) {{.FcnArgs}} {
{{- if .IsOperation }}
	if m.{{.MockHookName}} != nil {
		return m.{{.MockHookName}}(ctx, key {{.CallArgs}}, m)
	}
	return nil
{{- else if .IsGet}}
	if m.{{.MockHookName}} != nil {
		return m.{{.MockHookName}}(ctx, key {{.CallArgs}}, m)
	}
	return nil, fmt.Errorf("{{.MockHookName}} must be set")
{{- else if .IsPaged}}
	if m.{{.MockHookName}} != nil {
		return m.{{.MockHookName}}(ctx, key {{.CallArgs}}, fl, m)
	}
	return nil, nil
{{- end}}
}
{{end -}}
{{- end}}
// {{.GCEWrapType}} is a simplifying adapter for the GCE {{.Service}}.
type {{.GCEWrapType}} struct {
	s *Service
}

{{- if .GenerateGet}}
// Get the {{.Object}} named by key.
func (g *{{.GCEWrapType}}) Get(ctx context.Context, key *meta.Key) (*{{.FQObjectType}}, error) {
	glog.V(5).Infof("{{.GCEWrapType}}.Get(%v, %v): called", ctx, key)

	if !key.Valid() {
		glog.V(2).Infof("{{.GCEWrapType}}.Get(%v, %v): key is invalid (%#v)", ctx, key, key)
		return nil, fmt.Errorf("invalid GCE key (%#v)", key)
	}
	projectID := g.s.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Service}}")
	rk := &RateLimitKey{
		ProjectID: projectID,
		Operation: "Get",
		Version: meta.Version("{{.Version}}"),
		Service: "{{.Service}}",
	}
	glog.V(5).Infof("{{.GCEWrapType}}.Get(%v, %v): projectID = %v, rk = %+v", ctx, key, projectID, rk)
	if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.Get(%v, %v): RateLimiter error: %v", ctx, key, err)
		return nil, err
	}
{{- if .KeyIsGlobal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Get(projectID, key.Name)
{{- end -}}
{{- if .KeyIsRegional}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Get(projectID, key.Region, key.Name)
{{- end -}}
{{- if .KeyIsZonal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Get(projectID, key.Zone, key.Name)
{{- end}}
	call.Context(ctx)
	v, err := call.Do()
	glog.V(4).Infof("{{.GCEWrapType}}.Get(%v, %v) = %+v, %v", ctx, key, v, err)
	return v, err
}
{{- end}}

{{- if .GenerateList}}
// List all {{.Object}} objects.
{{- if .KeyIsGlobal}}
func (g *{{.GCEWrapType}}) List(ctx context.Context, fl *filter.F) ([]*{{.FQObjectType}}, error) {
	glog.V(5).Infof("{{.GCEWrapType}}.List(%v, %v) called", ctx, fl)
{{- end -}}
{{- if .KeyIsRegional}}
func (g *{{.GCEWrapType}}) List(ctx context.Context, region string, fl *filter.F) ([]*{{.FQObjectType}}, error) {
	glog.V(5).Infof("{{.GCEWrapType}}.List(%v, %v, %v) called", ctx, region, fl)
{{- end -}}
{{- if .KeyIsZonal}}
func (g *{{.GCEWrapType}}) List(ctx context.Context, zone string, fl *filter.F) ([]*{{.FQObjectType}}, error) {
	glog.V(5).Infof("{{.GCEWrapType}}.List(%v, %v, %v) called", ctx, zone, fl)
{{- end}}
	projectID := g.s.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Service}}")
	rk := &RateLimitKey{
		ProjectID: projectID,
		Operation: "List",
		Version: meta.Version("{{.Version}}"),
		Service: "{{.Service}}",
	}
	if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
		return nil, err
	}
{{- if .KeyIsGlobal}}
	glog.V(5).Infof("{{.GCEWrapType}}.List(%v, %v): projectID = %v, rk = %+v", ctx, fl, projectID, rk)
	call := g.s.{{.VersionTitle}}.{{.Service}}.List(projectID)
{{- end -}}
{{- if .KeyIsRegional}}
	glog.V(5).Infof("{{.GCEWrapType}}.List(%v, %v, %v): projectID = %v, rk = %+v", ctx, region, fl, projectID, rk)
	call := g.s.{{.VersionTitle}}.{{.Service}}.List(projectID, region)
{{- end -}}
{{- if .KeyIsZonal}}
	glog.V(5).Infof("{{.GCEWrapType}}.List(%v, %v, %v): projectID = %v, rk = %+v", ctx, zone, fl, projectID, rk)
	call := g.s.{{.VersionTitle}}.{{.Service}}.List(projectID, zone)
{{- end}}
	if fl != filter.None {
		call.Filter(fl.String())
	}
	var all []*{{.FQObjectType}}
	f := func(l *{{.ObjectListType}}) error {
		glog.V(5).Infof("{{.GCEWrapType}}.List(%v, ..., %v): page %+v", ctx, fl, l)
		all = append(all, l.Items...)
		return nil
	}
	if err := call.Pages(ctx, f); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.List(%v, ..., %v) = %v, %v", ctx, fl, nil, err)
		return nil, err
	}

	if glog.V(4) {
		glog.V(4).Infof("{{.GCEWrapType}}.List(%v, ..., %v) = [%v items], %v", ctx, fl, len(all), nil)
	} else if glog.V(5) {
		var asStr []string
		for _, o := range all {
			asStr = append(asStr, fmt.Sprintf("%+v", o))
		}
		glog.V(5).Infof("{{.GCEWrapType}}.List(%v, ..., %v) = %v, %v", ctx, fl, asStr, nil)
	}

	return all, nil
}
{{- end}}

{{- if .GenerateInsert}}
// Insert {{.Object}} with key of value obj.
func (g *{{.GCEWrapType}}) Insert(ctx context.Context, key *meta.Key, obj *{{.FQObjectType}}) error {
	glog.V(5).Infof("{{.GCEWrapType}}.Insert(%v, %v, %+v): called", ctx, key, obj)
	if !key.Valid() {
		glog.V(2).Infof("{{.GCEWrapType}}.Insert(%v, %v, ...): key is invalid (%#v)", ctx, key, key)
		return fmt.Errorf("invalid GCE key (%+v)", key)
	}
	projectID := g.s.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Service}}")
	rk := &RateLimitKey{
		ProjectID: projectID,
		Operation: "Insert",
		Version: meta.Version("{{.Version}}"),
		Service: "{{.Service}}",
	}
	glog.V(5).Infof("{{.GCEWrapType}}.Insert(%v, %v, ...): projectID = %v, rk = %+v", ctx, key, projectID, rk)
	if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.Insert(%v, %v, ...): RateLimiter error: %v", ctx, key, err)
		return err
	}
	obj.Name = key.Name
{{- if .KeyIsGlobal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Insert(projectID, obj)
{{- end -}}
{{- if .KeyIsRegional}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Insert(projectID, key.Region, obj)
{{- end -}}
{{- if .KeyIsZonal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Insert(projectID, key.Zone, obj)
{{- end}}
	call.Context(ctx)

	op, err := call.Do()
	if err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.Insert(%v, %v, ...) = %+v", ctx, key, err)
		return err
	}

	err = g.s.WaitForCompletion(ctx, op)
	glog.V(4).Infof("{{.GCEWrapType}}.Insert(%v, %v, %+v) = %+v", ctx, key, obj, err)
	return err
}
{{- end}}

{{- if .GenerateDelete}}
// Delete the {{.Object}} referenced by key.
func (g *{{.GCEWrapType}}) Delete(ctx context.Context, key *meta.Key) error {
	glog.V(5).Infof("{{.GCEWrapType}}.Delete(%v, %v): called", ctx, key)
	if !key.Valid() {
		glog.V(2).Infof("{{.GCEWrapType}}.Delete(%v, %v): key is invalid (%#v)", ctx, key, key)
		return fmt.Errorf("invalid GCE key (%+v)", key)
	}
	projectID := g.s.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Service}}")
	rk := &RateLimitKey{
		ProjectID: projectID,
		Operation: "Delete",
		Version: meta.Version("{{.Version}}"),
		Service: "{{.Service}}",
	}
	glog.V(5).Infof("{{.GCEWrapType}}.Delete(%v, %v): projectID = %v, rk = %+v", ctx, key, projectID, rk)
	if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.Delete(%v, %v): RateLimiter error: %v", ctx, key, err)
		return err
	}
{{- if .KeyIsGlobal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Delete(projectID, key.Name)
{{end -}}
{{- if .KeyIsRegional}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Delete(projectID, key.Region, key.Name)
{{- end -}}
{{- if .KeyIsZonal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.Delete(projectID, key.Zone, key.Name)
{{- end}}
	call.Context(ctx)

	op, err := call.Do()
	if err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.Delete(%v, %v) = %v", ctx, key, err)
		return err
	}

	err = g.s.WaitForCompletion(ctx, op)
	glog.V(4).Infof("{{.GCEWrapType}}.Delete(%v, %v) = %v", ctx, key, err)
	return err
}
{{end -}}

{{- if .AggregatedList}}
// AggregatedList lists all resources of the given type across all locations.
func (g *{{.GCEWrapType}}) AggregatedList(ctx context.Context, fl *filter.F) (map[string][]*{{.FQObjectType}}, error) {
	glog.V(5).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v) called", ctx, fl)

	projectID := g.s.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Service}}")
	rk := &RateLimitKey{
		ProjectID: projectID,
		Operation: "AggregatedList",
		Version: meta.Version("{{.Version}}"),
		Service: "{{.Service}}",
	}

	glog.V(5).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v): projectID = %v, rk = %+v", ctx, fl, projectID, rk)
	if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
		glog.V(5).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v): RateLimiter error: %v", ctx, fl, err)
		return nil, err
	}

	call := g.s.{{.VersionTitle}}.{{.Service}}.AggregatedList(projectID)
	call.Context(ctx)
	if fl != filter.None {
		call.Filter(fl.String())
	}

	all := map[string][]*{{.FQObjectType}}{}
	f := func(l *{{.ObjectAggregatedListType}}) error {
		for k, v := range l.Items {
			glog.V(5).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v): page[%v]%+v", ctx, fl, k, v)
			all[k] = append(all[k], v.{{.AggregatedListField}}...)
		}
		return nil
	}
	if err := call.Pages(ctx, f); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v) = %v, %v", ctx, fl, nil, err)
		return nil, err
	}
	if glog.V(4) {
		glog.V(4).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v) = [%v items], %v", ctx, fl, len(all), nil)
	} else if glog.V(5) {
		var asStr []string
		for _, o := range all {
			asStr = append(asStr, fmt.Sprintf("%+v", o))
		}
		glog.V(5).Infof("{{.GCEWrapType}}.AggregatedList(%v, %v) = %v, %v", ctx, fl, asStr, nil)
	}
	return all, nil
}
{{- end}}

{{- with .Methods -}}
{{- range .}}
// {{.Name}} is a method on {{.GCEWrapType}}.
func (g *{{.GCEWrapType}}) {{.FcnArgs}} {
	glog.V(5).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...): called", ctx, key)

	if !key.Valid() {
		glog.V(2).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...): key is invalid (%#v)", ctx, key, key)
{{- if .IsOperation}}
		return fmt.Errorf("invalid GCE key (%+v)", key)
{{- else if .IsGet}}
		return nil, fmt.Errorf("invalid GCE key (%+v)", key)
{{- else if .IsPaged}}
		return nil, fmt.Errorf("invalid GCE key (%+v)", key)
{{- end}}
	}
	projectID := g.s.ProjectRouter.ProjectID(ctx, "{{.Version}}", "{{.Service}}")
	rk := &RateLimitKey{
		ProjectID: projectID,
		Operation: "{{.Name}}",
		Version: meta.Version("{{.Version}}"),
		Service: "{{.Service}}",
	}
	glog.V(5).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...): projectID = %v, rk = %+v", ctx, key, projectID, rk)

	if err := g.s.RateLimiter.Accept(ctx, rk); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...): RateLimiter error: %v", ctx, key, err)
	{{- if .IsOperation}}
		return err
	{{- else}}
		return nil, err
	{{- end}}
	}
{{- if .KeyIsGlobal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.{{.Name}}(projectID, key.Name {{.CallArgs}})
{{- end -}}
{{- if .KeyIsRegional}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.{{.Name}}(projectID, key.Region, key.Name {{.CallArgs}})
{{- end -}}
{{- if .KeyIsZonal}}
	call := g.s.{{.VersionTitle}}.{{.Service}}.{{.Name}}(projectID, key.Zone, key.Name {{.CallArgs}})
{{- end}}
{{- if .IsOperation}}
	call.Context(ctx)
	op, err := call.Do()
	if err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...) = %+v", ctx, key, err)
		return err
	}
	err = g.s.WaitForCompletion(ctx, op)
	glog.V(4).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...) = %+v", ctx, key, err)
	return err
{{- else if .IsGet}}
	call.Context(ctx)
	v, err := call.Do()
	glog.V(4).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...) = %+v, %v", ctx, key, v, err)
	return v, err
{{- else if .IsPaged}}
	var all []*{{.Version}}.{{.ItemType}}
	f := func(l *{{.Version}}.{{.ReturnType}}) error {
		glog.V(5).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...): page %+v", ctx, key, l)
		all = append(all, l.Items...)
		return nil
	}
	if err := call.Pages(ctx, f); err != nil {
		glog.V(4).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...) = %v, %v", ctx, key, nil, err)
		return nil, err
	}
	if glog.V(4) {
		glog.V(4).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...) = [%v items], %v", ctx, key, len(all), nil)
	} else if glog.V(5) {
		var asStr []string
		for _, o := range all {
			asStr = append(asStr, fmt.Sprintf("%+v", o))
		}
		glog.V(5).Infof("{{.GCEWrapType}}.{{.Name}}(%v, %v, ...) = %v, %v", ctx, key, asStr, nil)
	}
	return all, nil
{{- end}}
}
{{end -}}
{{- end}}
`
	tmpl := template.Must(template.New("interface").Parse(text))
	for _, s := range meta.AllServices {
		if err := tmpl.Execute(wr, s); err != nil {
			panic(err)
		}
	}
}

// genTypes generates the type wrappers.
func genResourceIDs(wr io.Writer) {
	const text = `
// New{{.Service}}ResourceID creates a ResourceID for the {{.Service}} resource.
{{- if .KeyIsProject}}
func New{{.Service}}ResourceID(project string) *ResourceID {
	var key *meta.Key
{{- else}}
{{- if .KeyIsGlobal}}
func New{{.Service}}ResourceID(project, name string) *ResourceID {
	key := meta.GlobalKey(name)
{{- end}}
{{- if .KeyIsRegional}}
func New{{.Service}}ResourceID(project, region, name string) *ResourceID {
	key := meta.RegionalKey(name, region)
{{- end}}
{{- if .KeyIsZonal}}
func New{{.Service}}ResourceID(project, zone, name string) *ResourceID {
	key := meta.ZonalKey(name, zone)
{{- end -}}
{{end}}
	return &ResourceID{project, "{{.Resource}}", key}
}
`
	tmpl := template.Must(template.New("resourceIDs").Parse(text))
	for _, sg := range meta.SortedServicesGroups {
		if err := tmpl.Execute(wr, sg.ServiceInfo()); err != nil {
			panic(err)
		}
	}
}

func genUnitTestHeader(wr io.Writer) {
	const text = `/*
Copyright {{.Year}} The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// This file was generated by "go run gen/main.go -mode test > gen_test.go". Do not edit
// directly.

package cloud

import (
	"context"
	"reflect"
	"testing"

	alpha "google.golang.org/api/compute/v0.alpha"
	beta "google.golang.org/api/compute/v0.beta"
	ga "google.golang.org/api/compute/v1"

	"{{.PackageRoot}}/filter"
	"{{.PackageRoot}}/meta"
)

const location = "location"
`
	tmpl := template.Must(template.New("header").Parse(text))
	values := map[string]string{
		"Year":        fmt.Sprintf("%v", time.Now().Year()),
		"PackageRoot": packageRoot,
	}
	if err := tmpl.Execute(wr, values); err != nil {
		panic(err)
	}
}

func genUnitTestServices(wr io.Writer) {
	const text = `
func Test{{.Service}}Group(t *testing.T) {
	t.Parallel()

	ctx := context.Background()
	pr := &SingleProjectRouter{"mock-project"}
	mock := NewMockGCE(pr)

	var key *meta.Key
{{- if .HasAlpha}}
	keyAlpha := meta.{{.Alpha.MakeKey "key-alpha" "location"}}
	key = keyAlpha
{{- end}}
{{- if .HasBeta}}
	keyBeta := meta.{{.Beta.MakeKey "key-beta" "location"}}
	key = keyBeta
{{- end}}
{{- if .HasGA}}
	keyGA := meta.{{.GA.MakeKey "key-ga" "location"}}
	key = keyGA
{{- end}}
	// Ignore unused variables.
	_, _, _ = ctx, mock, key

	// Get not found.
{{- if .HasAlpha}}{{- if .Alpha.GenerateGet}}
	if _, err := mock.Alpha{{.Service}}().Get(ctx, key); err == nil {
		t.Errorf("Alpha{{.Service}}().Get(%v, %v) = _, nil; want error", ctx, key)
	}
{{- end}}{{- end}}
{{- if .HasBeta}}{{- if .Beta.GenerateGet}}
	if _, err := mock.Beta{{.Service}}().Get(ctx, key); err == nil {
		t.Errorf("Beta{{.Service}}().Get(%v, %v) = _, nil; want error", ctx, key)
	}
{{- end}}{{- end}}
{{- if .HasGA}}{{- if .GA.GenerateGet}}
	if _, err := mock.{{.Service}}().Get(ctx, key); err == nil {
		t.Errorf("{{.Service}}().Get(%v, %v) = _, nil; want error", ctx, key)
	}
{{- end}}{{- end}}

	// Insert.
{{- if .HasAlpha}}{{- if .Alpha.GenerateInsert}}
	{
		obj := &alpha.{{.Alpha.Object}}{}
		if err := mock.Alpha{{.Service}}().Insert(ctx, keyAlpha, obj); err != nil {
			t.Errorf("Alpha{{.Service}}().Insert(%v, %v, %v) = %v; want nil", ctx, keyAlpha, obj, err)
		}
	}
{{- end}}{{- end}}
{{- if .HasBeta}}{{- if .Beta.GenerateInsert}}
	{
		obj := &beta.{{.Beta.Object}}{}
		if err := mock.Beta{{.Service}}().Insert(ctx, keyBeta, obj); err != nil {
			t.Errorf("Beta{{.Service}}().Insert(%v, %v, %v) = %v; want nil", ctx, keyBeta, obj, err)
		}
	}
{{- end}}{{- end}}
{{- if .HasGA}}{{- if .GA.GenerateInsert}}
	{
		obj := &ga.{{.GA.Object}}{}
		if err := mock.{{.Service}}().Insert(ctx, keyGA, obj); err != nil {
			t.Errorf("{{.Service}}().Insert(%v, %v, %v) = %v; want nil", ctx, keyGA, obj, err)
		}
	}
{{- end}}{{- end}}

	// Get across versions.
{{- if .HasAlpha}}{{- if .Alpha.GenerateInsert}}
	if obj, err := mock.Alpha{{.Service}}().Get(ctx, key); err != nil {
		t.Errorf("Alpha{{.Service}}().Get(%v, %v) = %v, %v; want nil", ctx, key, obj, err)
	}
{{- end}}{{- end}}
{{- if .HasBeta}}{{- if .Beta.GenerateInsert}}
	if obj, err := mock.Beta{{.Service}}().Get(ctx, key); err != nil {
		t.Errorf("Beta{{.Service}}().Get(%v, %v) = %v, %v; want nil", ctx, key, obj, err)
	}
{{- end}}{{- end}}
{{- if .HasGA}}{{- if .GA.GenerateInsert}}
	if obj, err := mock.{{.Service}}().Get(ctx, key); err != nil {
		t.Errorf("{{.Service}}().Get(%v, %v) = %v, %v; want nil", ctx, key, obj, err)
	}
{{- end}}{{- end}}

	// List.
{{- if .HasAlpha}}
	mock.MockAlpha{{.Service}}.Objects[*keyAlpha] =  mock.MockAlpha{{.Service}}.Obj(&alpha.{{.Alpha.Object}}{Name: keyAlpha.Name})
{{- end}}
{{- if .HasBeta}}
	mock.MockBeta{{.Service}}.Objects[*keyBeta] =  mock.MockBeta{{.Service}}.Obj(&beta.{{.Beta.Object}}{Name: keyBeta.Name})
{{- end}}
{{- if .HasGA}}
	mock.Mock{{.Service}}.Objects[*keyGA] =  mock.Mock{{.Service}}.Obj(&ga.{{.GA.Object}}{Name: keyGA.Name})
{{- end}}
	want := map[string]bool{
{{- if .HasAlpha}}
		"key-alpha": true,
{{- end}}
{{- if .HasBeta}}
		"key-beta": true,
{{- end}}
{{- if .HasGA}}
		"key-ga": true,
{{- end}}
	}
	_ = want // ignore unused variables.

{{- if .HasAlpha}}{{- if .Alpha.GenerateList}}
	{
	{{- if .Alpha.KeyIsGlobal }}
		objs, err := mock.Alpha{{.Service}}().List(ctx, filter.None)
	{{- else}}
		objs, err := mock.Alpha{{.Service}}().List(ctx, location, filter.None)
	{{- end}}
		if err != nil {
			t.Errorf("Alpha{{.Service}}().List(%v, %v, %v) = %v, %v; want _, nil", ctx, location, filter.None, objs, err)
		} else {
			got := map[string]bool{}
			for _, obj := range objs {
				got[obj.Name] = true
			}
			if !reflect.DeepEqual(got, want) {
				t.Errorf("Alpha{{.Service}}().List(); got %+v, want %+v", got, want)
			}
		}
	}
{{- end}}{{- end}}
{{- if .HasBeta}}{{- if .Beta.GenerateList}}
	{
	{{- if .Beta.KeyIsGlobal }}
		objs, err := mock.Beta{{.Service}}().List(ctx, filter.None)
	{{- else}}
		objs, err := mock.Beta{{.Service}}().List(ctx, location, filter.None)
	{{- end}}
		if err != nil {
			t.Errorf("Beta{{.Service}}().List(%v, %v, %v) = %v, %v; want _, nil", ctx, location, filter.None, objs, err)
		} else {
			got := map[string]bool{}
			for _, obj := range objs {
				got[obj.Name] = true
			}
			if !reflect.DeepEqual(got, want) {
				t.Errorf("Alpha{{.Service}}().List(); got %+v, want %+v", got, want)
			}
		}
	}
{{- end}}{{- end}}
{{- if .HasGA}}{{- if .GA.GenerateList}}
	{
	{{- if .GA.KeyIsGlobal }}
		objs, err := mock.{{.Service}}().List(ctx, filter.None)
	{{- else}}
		objs, err := mock.{{.Service}}().List(ctx, location, filter.None)
	{{- end}}
		if err != nil {
			t.Errorf("{{.Service}}().List(%v, %v, %v) = %v, %v; want _, nil", ctx, location, filter.None, objs, err)
		} else {
			got := map[string]bool{}
			for _, obj := range objs {
				got[obj.Name] = true
			}
			if !reflect.DeepEqual(got, want) {
				t.Errorf("Alpha{{.Service}}().List(); got %+v, want %+v", got, want)
			}
		}
	}
{{- end}}{{- end}}

	// Delete across versions.
{{- if .HasAlpha}}{{- if .Alpha.GenerateDelete}}
	if err := mock.Alpha{{.Service}}().Delete(ctx, keyAlpha); err != nil {
		t.Errorf("Alpha{{.Service}}().Delete(%v, %v) = %v; want nil", ctx, keyAlpha, err)
	}
{{- end}}{{- end}}
{{- if .HasBeta}}{{- if .Beta.GenerateDelete}}
	if err := mock.Beta{{.Service}}().Delete(ctx, keyBeta); err != nil {
		t.Errorf("Beta{{.Service}}().Delete(%v, %v) = %v; want nil", ctx, keyBeta, err)
	}
{{- end}}{{- end}}
{{- if .HasGA}}{{- if .GA.GenerateDelete}}
	if err := mock.{{.Service}}().Delete(ctx, keyGA); err != nil {
		t.Errorf("{{.Service}}().Delete(%v, %v) = %v; want nil", ctx, keyGA, err)
	}
{{- end}}{{- end}}

	// Delete not found.
{{- if .HasAlpha}}{{- if .Alpha.GenerateDelete}}
	if err := mock.Alpha{{.Service}}().Delete(ctx, keyAlpha); err == nil {
		t.Errorf("Alpha{{.Service}}().Delete(%v, %v) = nil; want error", ctx, keyAlpha)
	}
{{- end}}{{- end}}
{{- if .HasBeta}}{{- if .Beta.GenerateDelete}}
	if err := mock.Beta{{.Service}}().Delete(ctx, keyBeta); err == nil {
		t.Errorf("Beta{{.Service}}().Delete(%v, %v) = nil; want error", ctx, keyBeta)
	}
{{- end}}{{- end}}
{{- if .HasGA}}{{- if .GA.GenerateDelete}}
	if err := mock.{{.Service}}().Delete(ctx, keyGA); err == nil {
		t.Errorf("{{.Service}}().Delete(%v, %v) = nil; want error", ctx, keyGA)
	}
{{- end}}{{- end}}
}
`
	tmpl := template.Must(template.New("unittest").Parse(text))
	for _, sg := range meta.SortedServicesGroups {
		if err := tmpl.Execute(wr, sg); err != nil {
			panic(err)
		}
	}
}

func genUnitTestResourceIDConversion(wr io.Writer) {
	const text = `
func TestResourceIDConversion(t *testing.T) {
	t.Parallel()

	for _, id := range []*ResourceID{
		{{- range .Groups}}
		{{- with .ServiceInfo}}
		{{- if .KeyIsProject}}
		New{{.Service}}ResourceID("my-{{.Resource}}-resource"),
		{{- else}}
		{{- if .KeyIsGlobal}}
		New{{.Service}}ResourceID("some-project", "my-{{.Resource}}-resource"),
		{{- end}}
		{{- if .KeyIsRegional}}
		New{{.Service}}ResourceID("some-project", "us-central1", "my-{{.Resource}}-resource"),
		{{- end}}
		{{- if .KeyIsZonal}}
		New{{.Service}}ResourceID("some-project", "us-east1-b", "my-{{.Resource}}-resource"),
		{{- end -}}
		{{end -}}
		{{end -}}
		{{end}}
	} {
		t.Run(id.Resource, func(t *testing.T) {
			// Test conversion to and from full URL.
			fullURL := id.SelfLink(meta.VersionGA)
			parsedID, err := ParseResourceURL(fullURL)
			if err != nil {
				t.Errorf("ParseResourceURL(%s) = _, %v, want nil", fullURL, err)
			}
			if !reflect.DeepEqual(id, parsedID) {
				t.Errorf("SelfLink(%+v) -> ParseResourceURL(%s) = %+v, want original ID", id, fullURL, parsedID)
			}

			// Test conversion to and from relative resource name.
			relativeName := id.RelativeResourceName()
			parsedID, err = ParseResourceURL(relativeName)
			if err != nil {
				t.Errorf("ParseResourceURL(%s) = _, %v, want nil", relativeName, err)
			}
			if !reflect.DeepEqual(id, parsedID) {
				t.Errorf("RelativeResourceName(%+v) -> ParseResourceURL(%s) = %+v, want original ID", id, relativeName, parsedID)
			}

			// Do not test ResourcePath for projects.
			if id.Resource == "projects" {
				return
			}

			// Test conversion to and from resource path.
			resourcePath := id.ResourcePath()
			parsedID, err = ParseResourceURL(resourcePath)
			if err != nil {
				t.Errorf("ParseResourceURL(%s) = _, %v, want nil", resourcePath, err)
			}
			id.ProjectID = ""
			if !reflect.DeepEqual(id, parsedID) {
				t.Errorf("ResourcePath(%+v) -> ParseResourceURL(%s) = %+v, want %+v", id, resourcePath, parsedID, id)
			}
		})
	}
}
`
	data := struct {
		Groups []*meta.ServiceGroup
	}{meta.SortedServicesGroups}
	tmpl := template.Must(template.New("unittest-resourceIDs").Parse(text))
	if err := tmpl.Execute(wr, data); err != nil {
		panic(err)
	}
}

func main() {
	flag.Parse()

	out := &bytes.Buffer{}

	switch flags.mode {
	case "src":
		genHeader(out)
		genStubs(out)
		genTypes(out)
		genResourceIDs(out)
	case "test":
		genUnitTestHeader(out)
		genUnitTestServices(out)
		genUnitTestResourceIDConversion(out)
	default:
		log.Fatalf("Invalid -mode: %q", flags.mode)
	}

	if flags.gofmt {
		fmt.Print(gofmtContent(out))
	} else {
		fmt.Print(out.String())
	}
}
