<commit_before>package payment_test

import (
	"database/sql"
	"math/rand"
	"testing"
	"time"

	"github.com/fritzpay/paymentd/pkg/testutil"

	"github.com/fritzpay/paymentd/pkg/paymentd/payment"
	"github.com/fritzpay/paymentd/pkg/paymentd/principal"
	"github.com/fritzpay/paymentd/pkg/paymentd/project"
	. "github.com/smartystreets/goconvey/convey"
)

func WithTestProject(db, prDB *sql.DB, f func(pr *project.Project)) func() {
	return func() {
		princ, err := principal.PrincipalByNameDB(prDB, "testprincipal")
		So(err, ShouldBeNil)
		So(princ.ID, ShouldNotEqual, 0)
		So(princ.Empty(), ShouldBeFalse)

		proj, err := project.ProjectByPrincipalIDNameDB(prDB, princ.ID, "testproject")
		So(err, ShouldBeNil)

		f(proj)
	}
}

func WithTestPayment(tx *sql.Tx, pr *project.Project, f func(p *payment.Payment)) func() {
	return func() {
		p := &payment.Payment{}
		err := p.SetProject(pr)
		So(err, ShouldBeNil)

		p.Amount = 1234
		p.Subunits = 2
		p.Currency = "EUR"
		p.Created = time.Unix(1234, 0)

		err = payment.InsertPaymentTx(tx, p)
		So(err, ShouldBeNil)

		f(p)
	}
}

func TestPaymentAmountDecimal(t *testing.T) {
	Convey("Given a payment", t, func() {
		p := &payment.Payment{}
		p.Amount = 1234
		p.Subunits = 2

		Convey("When retrieving the decimal amount representation", func() {
			dec := p.Decimal()

			Convey("It should be correctly represented", func() {
				So(dec.String(), ShouldEqual, "12.34")
			})
		})
	})
}

func TestPaymentID(t *testing.T) {
	Convey("Given a payment ID string", t, func() {
		idStr := "1-1234"

		Convey("When parsing the payment ID", func() {
			id, err := payment.ParsePaymentIDStr(idStr)

			Convey("It should succeed", func() {
				So(err, ShouldBeNil)
				Convey("The parts should be parsed correctly", func() {
					So(id.PaymentID, ShouldEqual, 1234)
					So(id.ProjectID, ShouldEqual, 1)

					Convey("When getting a string representation", func() {
						str := id.String()
						Convey("It should match the original string", func() {
							So(str, ShouldEqual, idStr)
						})
					})
				})
			})
		})
	})
}

func TestIDEncoderModInv(t *testing.T) {
	Convey("Given a prime int64", t, func() {
		prime := int64(982450871)
		Convey("When calculating the modinv", func() {
			m, err := payment.NewIDEncoder(prime, rand.Int63())
			So(err, ShouldBeNil)
			Convey("Given a random int64", func() {
				r := rand.Int63()
				Convey("When encoding with the prime", func() {
					rn := m.Hide(r)
					t.Logf("%d encoded: %d", r, rn)
					Convey("When decoding given the inverse", func() {
						dec := m.Show(rn)
						Convey("It should match the original random int64", func() {
							So(dec, ShouldEqual, r)
						})
					})
				})
			})
		})
	})
}

func TestTransactionListBalance(t *testing.T) {
	Convey("Given a transaction list", t, func() {
		tl := payment.PaymentTransactionList([]*payment.PaymentTransaction{
			&payment.PaymentTransaction{
				Amount:   1000,
				Subunits: 2,
				Currency: "EUR",
			},
			&payment.PaymentTransaction{
				Amount:   1000,
				Subunits: 3,
				Currency: "EUR",
			},
		})

		Convey("When there is only one currency present", func() {
			Convey("When retrieving the balance", func() {
				b := tl.Balance()

				Convey("It should have one entry", func() {
					So(len(b), ShouldEqual, 1)
				})
				Convey("It should add the entries correctly", func() {
					So(b["EUR"].String(), ShouldEqual, "11.000")
					So(b["EUR"].IntegerPart(), ShouldEqual, "11")
					So(b["EUR"].DecimalPart(), ShouldEqual, "000")
				})
			})
		})

		Convey("When there are two currencies present", func() {
			tl = append(tl, &payment.PaymentTransaction{
				Amount:   1234,
				Subunits: 3,
				Currency: "USD",
			}, &payment.PaymentTransaction{
				Amount:   -1234,
				Subunits: 2,
				Currency: "USD",
			})

			Convey("When retrieving the balance", func() {
				b := tl.Balance()

				Convey("It should have two entries", func() {
					So(len(b), ShouldEqual, 2)
				})
				Convey("The sum should be correct", func() {
					So(b["USD"].String(), ShouldEqual, "-11.106")
				})
			})
		})
	})
}

func TestPaymentSQL(t *testing.T) {
	Convey("Given a payment DB", t, testutil.WithPaymentDB(t, func(db *sql.DB) {
		Reset(func() {
			db.Close()
		})
		Convey("Given a principal DB", testutil.WithPrincipalDB(t, func(prDB *sql.DB) {
			Reset(func() {
				prDB.Close()
			})
			Convey("Given a test project", WithTestProject(db, prDB, func(proj *project.Project) {
				Convey("Given a transaction", func() {
					tx, err := db.Begin()
					So(err, ShouldBeNil)

					Reset(func() {
						err = tx.Rollback()
						So(err, ShouldBeNil)
					})

					Convey("Given a test payment", WithTestPayment(tx, proj, func(p *payment.Payment) {
						Convey("When selecting a payment by ident", func() {
							p2, err := payment.PaymentByProjectIDAndIdentTx(tx, proj.ID, p.Ident)
							Convey("It should succeed", func() {
								So(err, ShouldBeNil)
								Convey("It should match the original payment", func() {
									So(p2.ID(), ShouldEqual, p.ID())
								})
							})
						})

						Convey("Given the test payment has a transaction", func() {
							paymentTx := p.NewTransaction(payment.PaymentStatusPaid)
							paymentTx.Timestamp = time.Unix(9876, 0)
							err = payment.InsertPaymentTransactionTx(tx, paymentTx)
							So(err, ShouldBeNil)

							Convey("When selecting the payment", func() {
								p2, err := payment.PaymentByIDTx(tx, p.PaymentID())
								So(err, ShouldBeNil)

								Convey("The transaction values should be set in the payment", func() {
									So(p2.TransactionTimestamp.Unix(), ShouldEqual, 9876)
									So(p2.Status, ShouldEqual, payment.PaymentStatusPaid)
								})
							})
						})
					}))
				})
			}))
		}))
	}))
}

func TestPaymentTokenGenerationSQL(t *testing.T) {
	Convey("Given a payment DB", t, testutil.WithPaymentDB(t, func(db *sql.DB) {
		Reset(func() {
			db.Close()
		})
		Convey("Given a principal DB", testutil.WithPrincipalDB(t, func(prDB *sql.DB) {
			Reset(func() {
				prDB.Close()
			})
			Convey("Given a test project", WithTestProject(db, prDB, func(proj *project.Project) {
				Convey("Given a transaction", func() {
					tx, err := db.Begin()
					So(err, ShouldBeNil)

					Reset(func() {
						err = tx.Rollback()
						So(err, ShouldBeNil)
					})

					Convey("Given a test payment", WithTestPayment(tx, proj, func(p *payment.Payment) {
						Convey("When generating a token for the payment", func() {
							t, err := payment.NewPaymentToken(p.PaymentID())
							So(err, ShouldBeNil)
							So(t.Valid(time.Minute), ShouldBeTrue)

							Convey("When inserting the token", func() {
								err = payment.InsertPaymentTokenTx(tx, t)

								Convey("It should succeed", func() {
									So(err, ShouldBeNil)

									Convey("Given a duplicate token", func() {
										t2 := *t
										So(t.Token, ShouldEqual, t2.Token)
										Convey("When inserting a duplicate token", func() {
											err = payment.InsertPaymentTokenTx(tx, &t2)
											Convey("It should succeed", func() {
												So(err, ShouldBeNil)
											})
											Convey("It should have regenerated the token", func() {
												So(t.Token, ShouldNotEqual, t2.Token)
											})
										})
									})
								})
							})
						})
					}))
				})
			}))
		}))
	}))
}
<commit_msg>add failing test for #99<commit_after>package payment_test

import (
	"database/sql"
	"math/rand"
	"testing"
	"time"

	"github.com/fritzpay/paymentd/pkg/testutil"

	"github.com/fritzpay/paymentd/pkg/paymentd/payment"
	"github.com/fritzpay/paymentd/pkg/paymentd/principal"
	"github.com/fritzpay/paymentd/pkg/paymentd/project"
	. "github.com/smartystreets/goconvey/convey"
)

func WithTestProject(db, prDB *sql.DB, f func(pr *project.Project)) func() {
	return func() {
		princ, err := principal.PrincipalByNameDB(prDB, "testprincipal")
		So(err, ShouldBeNil)
		So(princ.ID, ShouldNotEqual, 0)
		So(princ.Empty(), ShouldBeFalse)

		proj, err := project.ProjectByPrincipalIDNameDB(prDB, princ.ID, "testproject")
		So(err, ShouldBeNil)

		f(proj)
	}
}

func WithTestPayment(tx *sql.Tx, pr *project.Project, f func(p *payment.Payment)) func() {
	return func() {
		p := &payment.Payment{}
		err := p.SetProject(pr)
		So(err, ShouldBeNil)

		p.Amount = 1234
		p.Subunits = 2
		p.Currency = "EUR"
		p.Created = time.Unix(1234, 0)

		err = payment.InsertPaymentTx(tx, p)
		So(err, ShouldBeNil)

		f(p)
	}
}

func TestPaymentAmountDecimal(t *testing.T) {
	Convey("Given a payment", t, func() {
		p := &payment.Payment{}
		p.Amount = 1234
		p.Subunits = 2

		Convey("When retrieving the decimal amount representation", func() {
			dec := p.Decimal()

			Convey("It should be correctly represented", func() {
				So(dec.String(), ShouldEqual, "12.34")
			})
		})
	})
}

func TestPaymentID(t *testing.T) {
	Convey("Given a payment ID string", t, func() {
		idStr := "1-1234"

		Convey("When parsing the payment ID", func() {
			id, err := payment.ParsePaymentIDStr(idStr)

			Convey("It should succeed", func() {
				So(err, ShouldBeNil)
				Convey("The parts should be parsed correctly", func() {
					So(id.PaymentID, ShouldEqual, 1234)
					So(id.ProjectID, ShouldEqual, 1)

					Convey("When getting a string representation", func() {
						str := id.String()
						Convey("It should match the original string", func() {
							So(str, ShouldEqual, idStr)
						})
					})
				})
			})
		})
	})
}

func TestIDEncoderModInv(t *testing.T) {
	Convey("Given a prime int64", t, func() {
		prime := int64(982450871)
		Convey("When calculating the modinv", func() {
			m, err := payment.NewIDEncoder(prime, rand.Int63())
			So(err, ShouldBeNil)
			Convey("Given a random int64", func() {
				r := rand.Int63()
				Convey("When encoding with the prime", func() {
					rn := m.Hide(r)
					t.Logf("%d encoded: %d", r, rn)
					Convey("When decoding given the inverse", func() {
						dec := m.Show(rn)
						Convey("It should match the original random int64", func() {
							So(dec, ShouldEqual, r)
						})
					})
				})
			})
		})
	})
}

func TestTransactionListBalance(t *testing.T) {
	Convey("Given a transaction list", t, func() {
		tl := payment.PaymentTransactionList([]*payment.PaymentTransaction{
			&payment.PaymentTransaction{
				Amount:   1000,
				Subunits: 2,
				Currency: "EUR",
			},
			&payment.PaymentTransaction{
				Amount:   1000,
				Subunits: 3,
				Currency: "EUR",
			},
		})

		Convey("When there is only one currency present", func() {
			Convey("When retrieving the balance", func() {
				b := tl.Balance()

				Convey("It should have one entry", func() {
					So(len(b), ShouldEqual, 1)
				})
				Convey("It should add the entries correctly", func() {
					So(b["EUR"].String(), ShouldEqual, "11.000")
					So(b["EUR"].IntegerPart(), ShouldEqual, "11")
					So(b["EUR"].DecimalPart(), ShouldEqual, "000")
				})
			})
		})

		Convey("When there are two currencies present", func() {
			tl = append(tl, &payment.PaymentTransaction{
				Amount:   1234,
				Subunits: 3,
				Currency: "USD",
			}, &payment.PaymentTransaction{
				Amount:   -1234,
				Subunits: 2,
				Currency: "USD",
			})

			Convey("When retrieving the balance", func() {
				b := tl.Balance()

				Convey("It should have two entries", func() {
					So(len(b), ShouldEqual, 2)
				})
				Convey("The sum should be correct", func() {
					So(b["USD"].String(), ShouldEqual, "-11.106")
				})
			})
		})
	})
}

func TestPaymentSQL(t *testing.T) {
	Convey("Given a payment DB", t, testutil.WithPaymentDB(t, func(db *sql.DB) {
		Reset(func() {
			db.Close()
		})
		Convey("Given a principal DB", testutil.WithPrincipalDB(t, func(prDB *sql.DB) {
			Reset(func() {
				prDB.Close()
			})
			Convey("Given a test project", WithTestProject(db, prDB, func(proj *project.Project) {
				Convey("Given a transaction", func() {
					tx, err := db.Begin()
					So(err, ShouldBeNil)

					Reset(func() {
						err = tx.Rollback()
						So(err, ShouldBeNil)
					})

					Convey("Given a test payment", WithTestPayment(tx, proj, func(p *payment.Payment) {
						Convey("When selecting a payment by ident", func() {
							p2, err := payment.PaymentByProjectIDAndIdentTx(tx, proj.ID, p.Ident)
							Convey("It should succeed", func() {
								So(err, ShouldBeNil)
								Convey("It should match the original payment", func() {
									So(p2.ID(), ShouldEqual, p.ID())
								})
							})
						})

						Convey("Given the test payment has a transaction", func() {
							paymentTx := p.NewTransaction(payment.PaymentStatusPaid)
							paymentTx.Timestamp = time.Unix(9876, 0)
							err = payment.InsertPaymentTransactionTx(tx, paymentTx)
							So(err, ShouldBeNil)

							Convey("When selecting the payment", func() {
								p2, err := payment.PaymentByIDTx(tx, p.PaymentID())
								So(err, ShouldBeNil)

								Convey("The transaction values should be set in the payment", func() {
									So(p2.TransactionTimestamp.Unix(), ShouldEqual, 9876)
									So(p2.Status, ShouldEqual, payment.PaymentStatusPaid)
								})
							})
						})
					}))
				})
			}))
		}))
	}))
}

func TestPaymentTokenGenerationSQL(t *testing.T) {
	Convey("Given a payment DB", t, testutil.WithPaymentDB(t, func(db *sql.DB) {
		Reset(func() {
			db.Close()
		})
		Convey("Given a principal DB", testutil.WithPrincipalDB(t, func(prDB *sql.DB) {
			Reset(func() {
				prDB.Close()
			})
			Convey("Given a test project", WithTestProject(db, prDB, func(proj *project.Project) {
				Convey("Given a transaction", func() {
					tx, err := db.Begin()
					So(err, ShouldBeNil)

					Reset(func() {
						err = tx.Rollback()
						So(err, ShouldBeNil)
					})

					Convey("Given a test payment", WithTestPayment(tx, proj, func(p *payment.Payment) {
						Convey("When generating a token for the payment", func() {
							t, err := payment.NewPaymentToken(p.PaymentID())
							So(err, ShouldBeNil)
							So(t.Valid(time.Minute), ShouldBeTrue)

							Convey("When inserting the token", func() {
								err = payment.InsertPaymentTokenTx(tx, t)

								Convey("It should succeed", func() {
									So(err, ShouldBeNil)

									Convey("Given a duplicate token", func() {
										t2 := *t
										So(t.Token, ShouldEqual, t2.Token)
										Convey("When inserting a duplicate token", func() {
											err = payment.InsertPaymentTokenTx(tx, &t2)
											Convey("It should succeed", func() {
												So(err, ShouldBeNil)
											})
											Convey("It should have regenerated the token", func() {
												So(t.Token, ShouldNotEqual, t2.Token)
											})
										})
									})
								})
							})
						})
					}))
				})
			}))
		}))
	}))
}

func TestPaymentMetadata(t *testing.T) {
	Convey("Given a payment DB", t, testutil.WithPaymentDB(t, func(db *sql.DB) {
		Reset(func() {
			db.Close()
		})
		Convey("Given a principal DB", testutil.WithPrincipalDB(t, func(prDB *sql.DB) {
			Reset(func() {
				prDB.Close()
			})
			Convey("Given a test project", WithTestProject(db, prDB, func(proj *project.Project) {
				Convey("Given a transaction", func() {
					tx, err := db.Begin()
					So(err, ShouldBeNil)

					Reset(func() {
						err = tx.Rollback()
						So(err, ShouldBeNil)
					})

					Convey("Given a test payment", WithTestPayment(tx, proj, func(p *payment.Payment) {
						Convey("When adding metadata", func() {
							p.Metadata = map[string]string{"metadataEntry": "metadataValue"}
							err = payment.InsertPaymentMetadataTx(tx, p)
							So(err, ShouldBeNil)

							Convey("When retrieving the payment", func() {
								pRet, err := payment.PaymentByIDTx(tx, p.PaymentID())
								So(err, ShouldBeNil)

								Convey("It should return the metadata", func() {
									So(pRet.Metadata, ShouldNotBeNil)
									So(pRet.Metadata["metadataEntry"], ShouldEqual, "metadataValue")

									Convey("When adding an additional metadata entry", func() {
										pRet.Metadata["second"] = "me"
										err = payment.InsertPaymentMetadataTx(tx, pRet)
										So(err, ShouldBeNil)

										Convey("When retrieving the payment", func() {
											p, err = payment.PaymentByIDTx(tx, pRet.PaymentID())
											So(err, ShouldBeNil)

											Convey("It should have both entries", func() {
												So(p.Metadata, ShouldNotBeNil)
												So(len(p.Metadata), ShouldEqual, 2)
												So(p.Metadata["metadataEntry"], ShouldEqual, "metadataValue")
												So(p.Metadata["second"], ShouldEqual, "me")
											})
										})
									})
								})
							})
						})
					}))
				})
			}))
		}))
	}))
}
<|endoftext|>


<commit_before>/*******************************************************************************
*
* Copyright 2018 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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.
*
*******************************************************************************/

package plugins

import (
	"encoding/json"
	"regexp"
	"strconv"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
	flavorsmodule "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/pagination"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/sapcc/go-api-declarations/limes"
	"github.com/sapcc/go-bits/logg"

	"github.com/sapcc/limes/pkg/core"
)

type capacitySapccIronicPlugin struct {
	cfg                 core.CapacitorConfiguration
	ftt                 novaFlavorTranslationTable
	reportSubcapacities bool
}

type capacitySapccIronicSerializedMetrics struct {
	UnmatchedNodeCount uint64 `json:"unmatched_nodes"`
}

func init() {
	core.RegisterCapacityPlugin(func(c core.CapacitorConfiguration, scrapeSubcapacities map[string]map[string]bool) core.CapacityPlugin {
		ftt := newNovaFlavorTranslationTable(c.SAPCCIronic.FlavorAliases)
		return &capacitySapccIronicPlugin{c, ftt, scrapeSubcapacities["compute"]["instances-baremetal"]}
	})
}

//Init implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Init(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) error {
	return nil
}

//Type implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Type() string {
	return "sapcc-ironic"
}

type ironicNode struct {
	ID                   string  `json:"uuid"`
	Name                 string  `json:"name"`
	ProvisionState       string  `json:"provision_state"`
	TargetProvisionState *string `json:"target_provision_state"`
	InstanceID           *string `json:"instance_uuid"`
	ResourceClass        *string `json:"resource_class"`
	Properties           struct {
		Cores           veryFlexibleUint64 `json:"cpus"`
		DiskGiB         veryFlexibleUint64 `json:"local_gb"`
		MemoryMiB       veryFlexibleUint64 `json:"memory_mb"`
		CPUArchitecture string             `json:"cpu_arch"`
		Capabilities    string             `json:"capabilities"` //e.g. "cpu_txt:true,cpu_aes:true"
		SerialNumber    string             `json:"serial"`
	} `json:"properties"`
}

func (n ironicNode) StableProvisionState() string {
	if n.TargetProvisionState != nil {
		return *n.TargetProvisionState
	}
	return n.ProvisionState
}

////////////////////////////////////////////////////////////////////////////////
// OpenStack is being inconsistent with itself again

//For fields that are sometimes missing, sometimes an integer, sometimes a string.
type veryFlexibleUint64 uint64

//UnmarshalJSON implements the json.Unmarshaler interface.
func (value *veryFlexibleUint64) UnmarshalJSON(buf []byte) error {
	if string(buf) == "null" {
		*value = 0
		return nil
	}

	if buf[0] == '"' {
		var str string
		err := json.Unmarshal(buf, &str)
		if err != nil {
			return err
		}
		val, err := strconv.ParseUint(str, 10, 64)
		*value = veryFlexibleUint64(val)
		return err
	}

	var val uint64
	err := json.Unmarshal(buf, &val)
	*value = veryFlexibleUint64(val)
	return err
}

//Reference:
//  Hosts are expected to be in one of the following format:
//    - "nova-compute-xxxx"
//    - "nova-compute-ironic-xxxx"
//  where "xxxx" is unique among all hosts.
var computeHostStubRx = regexp.MustCompile(`^nova-compute-(?:ironic-)?([a-zA-Z0-9]+)$`)

//Node names are expected to be in the form "nodeXXX-bmYYY" or "nodeXXX-bbYYY"
//or "nodeXXX-apYYY" or "nodeXXX-mdYYY", where the second half is the host stub
//(the match group from above).
var nodeNameRx = regexp.MustCompile(`^node\d+-((?:b[bm]|ap|md)\d+)$`)

//As a special case, nodes in the control plane do not belong to any
//user-accessible Nova aggregates, so we cannot establish an AZ association.
//However, we don't really need the AZ association anyway: AZ capacities are
//presented to the customer as a sort of manual scheduling hint, but CP nodes
//are earmarked for internal use and thus are not relevant there.
var cpNodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-(cp\d+)$`)

//Scrape implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Scrape(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (map[string]map[string]core.CapacityData, string, error) {
	//collect info about flavors with separate instance quota
	novaClient, err := openstack.NewComputeV2(provider, eo)
	if err != nil {
		return nil, "", err
	}
	flavors, err := p.getIronicFlavorNames(novaClient)
	if err != nil {
		return nil, "", err
	}

	//we are going to report capacity for all per-flavor instance quotas
	result := make(map[string]*core.CapacityData)
	for _, flavor := range flavors {
		result[p.ftt.LimesResourceNameForFlavor(flavor)] = &core.CapacityData{
			Capacity:      0,
			CapacityPerAZ: map[string]*core.CapacityDataForAZ{},
		}
	}

	//count Ironic nodes
	ironicClient, err := openstack.NewBareMetalV1(provider, eo)
	if err != nil {
		return nil, "", err
	}
	ironicClient.Microversion = "1.22"
	allPages, err := nodes.ListDetail(ironicClient, nil).AllPages()
	if err != nil {
		return nil, "", err
	}
	var allNodes []ironicNode
	err = nodes.ExtractNodesInto(allPages, &allNodes)
	if err != nil {
		return nil, "", err
	}

	//Ironic bPods are expected to be listed as compute hosts assigned to
	//host aggregates in the format: "nova-compute-ironic-xxxx".
	azs, _, err := getAggregates(novaClient)
	if err != nil {
		return nil, "", err
	}
	azForHostStub := make(map[string]string)
	for azName, az := range azs {
		for host := range az.ContainsComputeHost {
			if host == "nova-compute-ironic" {
				azForHostStub["bpod001"] = azName //hardcoded for the few nodes using legacy naming convention
			} else {
				match := computeHostStubRx.FindStringSubmatch(host)
				if match == nil {
					logg.Error(`compute host %q does not match the "nova-compute-(ironic-)xxxx" naming convention`, host)
				} else {
					azForHostStub[match[1]] = azName
				}
			}
		}
	}

	unmatchedCounter := uint64(0)
	for _, node := range allNodes {
		//do not consider nodes that have not been made available for provisioning yet
		if !isAvailableProvisionState[node.StableProvisionState()] {
			continue
		}

		matched := false
		for _, flavor := range flavors {
			if node.Matches(flavor) {
				logg.Debug("Ironic node %q (%s) matches flavor %s", node.Name, node.ID, flavor)

				data := result[p.ftt.LimesResourceNameForFlavor(flavor)]
				data.Capacity++

				var nodeAZ string
				if match := cpNodeNameRx.FindStringSubmatch(node.Name); match != nil {
					//special case as explained above (near definition of `cpNodeNameRx`)
					nodeAZ = match[1]
				} else if match := nodeNameRx.FindStringSubmatch(node.Name); match != nil {
					nodeAZ = azForHostStub[match[1]]
					if nodeAZ == "" {
						logg.Info("Ironic node %q (%s) does not match any compute host from host aggregates", node.Name, node.ID)
						nodeAZ = "unknown"
					}
				} else {
					logg.Error(`Ironic node %q (%s) does not match the "nodeXXX-{bm,bb,ap,md}YYY" naming convention`, node.Name, node.ID)
				}

				if _, ok := data.CapacityPerAZ[nodeAZ]; !ok {
					data.CapacityPerAZ[nodeAZ] = &core.CapacityDataForAZ{}
				}
				data.CapacityPerAZ[nodeAZ].Capacity++
				if node.StableProvisionState() == "active" {
					data.CapacityPerAZ[nodeAZ].Usage++
				}

				if p.reportSubcapacities {
					sub := map[string]interface{}{
						"id":              node.ID,
						"name":            node.Name,
						"provision_state": node.ProvisionState,
					}
					if node.TargetProvisionState != nil && *node.TargetProvisionState != "" {
						sub["target_provision_state"] = *node.TargetProvisionState
					}
					if nodeAZ != "" {
						sub["availability_zone"] = nodeAZ
					}
					if node.Properties.MemoryMiB > 0 {
						sub["ram"] = limes.ValueWithUnit{Unit: limes.UnitMebibytes, Value: uint64(node.Properties.MemoryMiB)}
					}
					if node.Properties.DiskGiB > 0 {
						sub["disk"] = limes.ValueWithUnit{Unit: limes.UnitGibibytes, Value: uint64(node.Properties.DiskGiB)}
					}
					if node.Properties.Cores > 0 {
						sub["cores"] = uint64(node.Properties.Cores)
					}
					if node.Properties.SerialNumber != "" {
						sub["serial"] = node.Properties.SerialNumber
					}
					if node.InstanceID != nil && *node.InstanceID != "" {
						sub["instance_id"] = *node.InstanceID
					}
					data.Subcapacities = append(data.Subcapacities, sub)
				}

				matched = true
				break
			}
		}
		if !matched {
			logg.Error("Ironic node %q (%s) does not match any baremetal flavor", node.Name, node.ID)
			unmatchedCounter++
		}
	}

	//remove pointers from `result`
	result2 := make(map[string]core.CapacityData, len(result))
	for resourceName, data := range result {
		result2[resourceName] = *data
	}

	serializedMetrics, err := json.Marshal(capacitySapccIronicSerializedMetrics{
		UnmatchedNodeCount: unmatchedCounter,
	})
	if err != nil {
		return nil, "", err
	}

	return map[string]map[string]core.CapacityData{"compute": result2}, string(serializedMetrics), nil
}

var ironicUnmatchedNodesGauge = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Name: "limes_unmatched_ironic_nodes",
		Help: "Number of available/active Ironic nodes without matching flavor.",
	},
	[]string{"os_cluster"},
)

//DescribeMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) DescribeMetrics(ch chan<- *prometheus.Desc) {
	ironicUnmatchedNodesGauge.Describe(ch)
}

//CollectMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) CollectMetrics(ch chan<- prometheus.Metric, clusterID, serializedMetrics string) error {
	if serializedMetrics == "" {
		return nil
	}
	var metrics capacitySapccIronicSerializedMetrics
	err := json.Unmarshal([]byte(serializedMetrics), &metrics)
	if err != nil {
		return err
	}

	descCh := make(chan *prometheus.Desc, 1)
	ironicUnmatchedNodesGauge.Describe(descCh)
	ironicUnmatchedNodesDesc := <-descCh

	ch <- prometheus.MustNewConstMetric(
		ironicUnmatchedNodesDesc,
		prometheus.GaugeValue, float64(metrics.UnmatchedNodeCount),
		clusterID,
	)
	return nil
}

func (p *capacitySapccIronicPlugin) getIronicFlavorNames(novaClient *gophercloud.ServiceClient) ([]string, error) {
	//which flavors have separate instance quota?
	flavorNames, err := p.ftt.ListFlavorsWithSeparateInstanceQuota(novaClient)
	if err != nil {
		return nil, err
	}
	isRelevantFlavorName := make(map[string]bool, len(flavorNames))
	for _, flavorName := range flavorNames {
		isRelevantFlavorName[flavorName] = true
	}

	//check flavor relevancy
	var result []string
	err = flavorsmodule.ListDetail(novaClient, nil).EachPage(func(page pagination.Page) (bool, error) {
		flavors, err := flavorsmodule.ExtractFlavors(page)
		if err != nil {
			return false, err
		}

		for _, flavor := range flavors {
			if isRelevantFlavorName[flavor.Name] {
				result = append(result, flavor.Name)
			}
		}
		return true, nil
	})
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (n ironicNode) Matches(flavorName string) bool {
	if n.ResourceClass != nil {
		return *n.ResourceClass == flavorName
	}
	return false
}
<commit_msg>Ironic capacity plugin<commit_after>/*******************************************************************************
*
* Copyright 2018 SAP SE
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You should have received a copy of the License along with this
* program. If not, 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.
*
*******************************************************************************/

package plugins

import (
	"encoding/json"
	"regexp"
	"strconv"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack"
	"github.com/gophercloud/gophercloud/openstack/baremetal/v1/nodes"
	flavorsmodule "github.com/gophercloud/gophercloud/openstack/compute/v2/flavors"
	"github.com/gophercloud/gophercloud/pagination"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/sapcc/go-api-declarations/limes"
	"github.com/sapcc/go-bits/logg"

	"github.com/sapcc/limes/pkg/core"
)

type capacitySapccIronicPlugin struct {
	cfg                 core.CapacitorConfiguration
	ftt                 novaFlavorTranslationTable
	reportSubcapacities bool
}

type capacitySapccIronicSerializedMetrics struct {
	UnmatchedNodeCount uint64 `json:"unmatched_nodes"`
}

func init() {
	core.RegisterCapacityPlugin(func(c core.CapacitorConfiguration, scrapeSubcapacities map[string]map[string]bool) core.CapacityPlugin {
		ftt := newNovaFlavorTranslationTable(c.SAPCCIronic.FlavorAliases)
		return &capacitySapccIronicPlugin{c, ftt, scrapeSubcapacities["compute"]["instances-baremetal"]}
	})
}

//Init implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Init(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) error {
	return nil
}

//Type implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Type() string {
	return "sapcc-ironic"
}

type ironicNode struct {
	ID                   string  `json:"uuid"`
	Name                 string  `json:"name"`
	ProvisionState       string  `json:"provision_state"`
	TargetProvisionState *string `json:"target_provision_state"`
	InstanceID           *string `json:"instance_uuid"`
	ResourceClass        *string `json:"resource_class"`
	Properties           struct {
		Cores           veryFlexibleUint64 `json:"cpus"`
		DiskGiB         veryFlexibleUint64 `json:"local_gb"`
		MemoryMiB       veryFlexibleUint64 `json:"memory_mb"`
		CPUArchitecture string             `json:"cpu_arch"`
		Capabilities    string             `json:"capabilities"` //e.g. "cpu_txt:true,cpu_aes:true"
		SerialNumber    string             `json:"serial"`
	} `json:"properties"`
}

func (n ironicNode) StableProvisionState() string {
	if n.TargetProvisionState != nil {
		return *n.TargetProvisionState
	}
	return n.ProvisionState
}

////////////////////////////////////////////////////////////////////////////////
// OpenStack is being inconsistent with itself again

//For fields that are sometimes missing, sometimes an integer, sometimes a string.
type veryFlexibleUint64 uint64

//UnmarshalJSON implements the json.Unmarshaler interface.
func (value *veryFlexibleUint64) UnmarshalJSON(buf []byte) error {
	if string(buf) == "null" {
		*value = 0
		return nil
	}

	if buf[0] == '"' {
		var str string
		err := json.Unmarshal(buf, &str)
		if err != nil {
			return err
		}
		val, err := strconv.ParseUint(str, 10, 64)
		*value = veryFlexibleUint64(val)
		return err
	}

	var val uint64
	err := json.Unmarshal(buf, &val)
	*value = veryFlexibleUint64(val)
	return err
}

//Reference:
//  Hosts are expected to be in one of the following format:
//    - "nova-compute-xxxx"
//    - "nova-compute-ironic-xxxx"
//  where "xxxx" is unique among all hosts.
var computeHostStubRx = regexp.MustCompile(`^nova-compute-(?:ironic-)?([a-zA-Z0-9]+)$`)

//Node names are expected to be in the form "nodeXXX-bmYYY" or "nodeXXX-bbYYY"
//or "nodeXXX-apYYY" or "nodeXXX-mdYYY", where the second half is the host stub
//(the match group from above).
var nodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-((?:b[bm]|ap|md|st)\d+)$`)

//As a special case, nodes in the control plane do not belong to any
//user-accessible Nova aggregates, so we cannot establish an AZ association.
//However, we don't really need the AZ association anyway: AZ capacities are
//presented to the customer as a sort of manual scheduling hint, but CP nodes
//are earmarked for internal use and thus are not relevant there.
var cpNodeNameRx = regexp.MustCompile(`^node(?:swift)?\d+-(cp\d+)$`)

//Scrape implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) Scrape(provider *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) (map[string]map[string]core.CapacityData, string, error) {
	//collect info about flavors with separate instance quota
	novaClient, err := openstack.NewComputeV2(provider, eo)
	if err != nil {
		return nil, "", err
	}
	flavors, err := p.getIronicFlavorNames(novaClient)
	if err != nil {
		return nil, "", err
	}

	//we are going to report capacity for all per-flavor instance quotas
	result := make(map[string]*core.CapacityData)
	for _, flavor := range flavors {
		result[p.ftt.LimesResourceNameForFlavor(flavor)] = &core.CapacityData{
			Capacity:      0,
			CapacityPerAZ: map[string]*core.CapacityDataForAZ{},
		}
	}

	//count Ironic nodes
	ironicClient, err := openstack.NewBareMetalV1(provider, eo)
	if err != nil {
		return nil, "", err
	}
	ironicClient.Microversion = "1.22"
	allPages, err := nodes.ListDetail(ironicClient, nil).AllPages()
	if err != nil {
		return nil, "", err
	}
	var allNodes []ironicNode
	err = nodes.ExtractNodesInto(allPages, &allNodes)
	if err != nil {
		return nil, "", err
	}

	//Ironic bPods are expected to be listed as compute hosts assigned to
	//host aggregates in the format: "nova-compute-ironic-xxxx".
	azs, _, err := getAggregates(novaClient)
	if err != nil {
		return nil, "", err
	}
	azForHostStub := make(map[string]string)
	for azName, az := range azs {
		for host := range az.ContainsComputeHost {
			if host == "nova-compute-ironic" {
				azForHostStub["bpod001"] = azName //hardcoded for the few nodes using legacy naming convention
			} else {
				match := computeHostStubRx.FindStringSubmatch(host)
				if match == nil {
					logg.Error(`compute host %q does not match the "nova-compute-(ironic-)xxxx" naming convention`, host)
				} else {
					azForHostStub[match[1]] = azName
				}
			}
		}
	}

	unmatchedCounter := uint64(0)
	for _, node := range allNodes {
		//do not consider nodes that have not been made available for provisioning yet
		if !isAvailableProvisionState[node.StableProvisionState()] {
			continue
		}

		matched := false
		for _, flavor := range flavors {
			if node.Matches(flavor) {
				logg.Debug("Ironic node %q (%s) matches flavor %s", node.Name, node.ID, flavor)

				data := result[p.ftt.LimesResourceNameForFlavor(flavor)]
				data.Capacity++

				var nodeAZ string
				if match := cpNodeNameRx.FindStringSubmatch(node.Name); match != nil {
					//special case as explained above (near definition of `cpNodeNameRx`)
					nodeAZ = match[1]
				} else if match := nodeNameRx.FindStringSubmatch(node.Name); match != nil {
					nodeAZ = azForHostStub[match[1]]
					if nodeAZ == "" {
						logg.Info("Ironic node %q (%s) does not match any compute host from host aggregates", node.Name, node.ID)
						nodeAZ = "unknown"
					}
				} else {
					logg.Error(`Ironic node %q (%s) does not match the "nodeXXX-{bm,bb,ap,md}YYY" naming convention`, node.Name, node.ID)
				}

				if _, ok := data.CapacityPerAZ[nodeAZ]; !ok {
					data.CapacityPerAZ[nodeAZ] = &core.CapacityDataForAZ{}
				}
				data.CapacityPerAZ[nodeAZ].Capacity++
				if node.StableProvisionState() == "active" {
					data.CapacityPerAZ[nodeAZ].Usage++
				}

				if p.reportSubcapacities {
					sub := map[string]interface{}{
						"id":              node.ID,
						"name":            node.Name,
						"provision_state": node.ProvisionState,
					}
					if node.TargetProvisionState != nil && *node.TargetProvisionState != "" {
						sub["target_provision_state"] = *node.TargetProvisionState
					}
					if nodeAZ != "" {
						sub["availability_zone"] = nodeAZ
					}
					if node.Properties.MemoryMiB > 0 {
						sub["ram"] = limes.ValueWithUnit{Unit: limes.UnitMebibytes, Value: uint64(node.Properties.MemoryMiB)}
					}
					if node.Properties.DiskGiB > 0 {
						sub["disk"] = limes.ValueWithUnit{Unit: limes.UnitGibibytes, Value: uint64(node.Properties.DiskGiB)}
					}
					if node.Properties.Cores > 0 {
						sub["cores"] = uint64(node.Properties.Cores)
					}
					if node.Properties.SerialNumber != "" {
						sub["serial"] = node.Properties.SerialNumber
					}
					if node.InstanceID != nil && *node.InstanceID != "" {
						sub["instance_id"] = *node.InstanceID
					}
					data.Subcapacities = append(data.Subcapacities, sub)
				}

				matched = true
				break
			}
		}
		if !matched {
			logg.Error("Ironic node %q (%s) does not match any baremetal flavor", node.Name, node.ID)
			unmatchedCounter++
		}
	}

	//remove pointers from `result`
	result2 := make(map[string]core.CapacityData, len(result))
	for resourceName, data := range result {
		result2[resourceName] = *data
	}

	serializedMetrics, err := json.Marshal(capacitySapccIronicSerializedMetrics{
		UnmatchedNodeCount: unmatchedCounter,
	})
	if err != nil {
		return nil, "", err
	}

	return map[string]map[string]core.CapacityData{"compute": result2}, string(serializedMetrics), nil
}

var ironicUnmatchedNodesGauge = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Name: "limes_unmatched_ironic_nodes",
		Help: "Number of available/active Ironic nodes without matching flavor.",
	},
	[]string{"os_cluster"},
)

//DescribeMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) DescribeMetrics(ch chan<- *prometheus.Desc) {
	ironicUnmatchedNodesGauge.Describe(ch)
}

//CollectMetrics implements the core.CapacityPlugin interface.
func (p *capacitySapccIronicPlugin) CollectMetrics(ch chan<- prometheus.Metric, clusterID, serializedMetrics string) error {
	if serializedMetrics == "" {
		return nil
	}
	var metrics capacitySapccIronicSerializedMetrics
	err := json.Unmarshal([]byte(serializedMetrics), &metrics)
	if err != nil {
		return err
	}

	descCh := make(chan *prometheus.Desc, 1)
	ironicUnmatchedNodesGauge.Describe(descCh)
	ironicUnmatchedNodesDesc := <-descCh

	ch <- prometheus.MustNewConstMetric(
		ironicUnmatchedNodesDesc,
		prometheus.GaugeValue, float64(metrics.UnmatchedNodeCount),
		clusterID,
	)
	return nil
}

func (p *capacitySapccIronicPlugin) getIronicFlavorNames(novaClient *gophercloud.ServiceClient) ([]string, error) {
	//which flavors have separate instance quota?
	flavorNames, err := p.ftt.ListFlavorsWithSeparateInstanceQuota(novaClient)
	if err != nil {
		return nil, err
	}
	isRelevantFlavorName := make(map[string]bool, len(flavorNames))
	for _, flavorName := range flavorNames {
		isRelevantFlavorName[flavorName] = true
	}

	//check flavor relevancy
	var result []string
	err = flavorsmodule.ListDetail(novaClient, nil).EachPage(func(page pagination.Page) (bool, error) {
		flavors, err := flavorsmodule.ExtractFlavors(page)
		if err != nil {
			return false, err
		}

		for _, flavor := range flavors {
			if isRelevantFlavorName[flavor.Name] {
				result = append(result, flavor.Name)
			}
		}
		return true, nil
	})
	if err != nil {
		return nil, err
	}

	return result, nil
}

func (n ironicNode) Matches(flavorName string) bool {
	if n.ResourceClass != nil {
		return *n.ResourceClass == flavorName
	}
	return false
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

package blob

import "github.com/jacobsa/comeback/util"

// Create a blob store that wraps another, responding to calls as follows:
//
//  *  Flush will panic; it is assumed that any buffering and early returning
//     is in front of this store, and that the wrapped store responds to Store
//     only when the blob is durable.
//
//  *  Contains will be responded to directly by this store based on the
//     contents of existingScores. It is assumed that existingScores initially
//     contains only scores that are durable in the wrapped store.
//
//  *  Store will be forwarded to the wrapped store. When it succeeds,
//     existingScores will be updated.
//
//  *  Load will be forwarded to the wrapped store.
//
func NewExistingScoresStore(
	existingScores util.StringSet,
	wrapped Store) (store Store) {
	store = &existingScoresStore{
		scores:  existingScores,
		wrapped: wrapped,
	}

	return
}

type existingScoresStore struct {
	scores  util.StringSet
	wrapped Store
}

func (bs *existingScoresStore) Store(blob []byte) (s Score, err error) {
	s, err = bs.wrapped.Store(blob)
	if err == nil {
		bs.scores.Add(s.Hex())
	}

	return
}

func (bs *existingScoresStore) Flush() (err error) {
	panic("We expect buffering to happen outside of here")
}

func (bs *existingScoresStore) Contains(score Score) (b bool) {
	b = bs.scores.Contains(score.Hex())
	return
}

func (bs *existingScoresStore) Load(s Score) (blob []byte, err error) {
	blob, err = bs.wrapped.Load(s)
	return
}
<commit_msg>Added a clarifying comment.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

package blob

import "github.com/jacobsa/comeback/util"

// Create a blob store that wraps another, responding to calls as follows:
//
//  *  Flush will panic; it is assumed that any buffering and early returning
//     is in front of this store, and that the wrapped store responds to Store
//     only when the blob is durable.
//
//  *  Contains will be responded to directly by this store based on the
//     contents of existingScores. It is assumed that existingScores initially
//     contains only scores that are durable in the wrapped store.
//
//  *  Store will be forwarded to the wrapped store. When it succeeds,
//     existingScores will be updated.
//
//  *  Load will be forwarded to the wrapped store.
//
// In other words, assuming that wrapped.Store returns successfully only when
// durable, this store maintains the invariant that existingScores contains
// only durable scores.
func NewExistingScoresStore(
	existingScores util.StringSet,
	wrapped Store) (store Store) {
	store = &existingScoresStore{
		scores:  existingScores,
		wrapped: wrapped,
	}

	return
}

type existingScoresStore struct {
	scores  util.StringSet
	wrapped Store
}

func (bs *existingScoresStore) Store(blob []byte) (s Score, err error) {
	s, err = bs.wrapped.Store(blob)
	if err == nil {
		bs.scores.Add(s.Hex())
	}

	return
}

func (bs *existingScoresStore) Flush() (err error) {
	panic("We expect buffering to happen outside of here")
}

func (bs *existingScoresStore) Contains(score Score) (b bool) {
	b = bs.scores.Contains(score.Hex())
	return
}

func (bs *existingScoresStore) Load(s Score) (blob []byte, err error) {
	blob, err = bs.wrapped.Load(s)
	return
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"fmt"
	"os/exec"
	"strings"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
)

type TestCmdRecorder struct {
	cmds []string
	err  error
}

func (t *TestCmdRecorder) RunCmd(cmd *exec.Cmd) error {
	if t.err != nil {
		return t.err
	}
	t.cmds = append(t.cmds, strings.Join(cmd.Args, " "))
	return nil
}

func (t *TestCmdRecorder) RunCmdOut(cmd *exec.Cmd) ([]byte, error) {
	return nil, t.RunCmd(cmd)
}

func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd {
	return exec.Command("copy", src, dst)
}

var pod = &v1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name: "podname",
	},
	Status: v1.PodStatus{
		Phase: v1.PodRunning,
	},
	Spec: v1.PodSpec{
		Containers: []v1.Container{
			{
				Name:  "container_name",
				Image: "gcr.io/k8s-skaffold:123",
			},
		},
	},
}

func TestPerform(t *testing.T) {
	var tests = []struct {
		description string
		image       string
		files       map[string]string
		cmdFn       func(v1.Pod, v1.Container, string, string) *exec.Cmd
		cmdErr      error
		clientErr   error
		expected    []string
		shouldErr   bool
	}{
		{
			description: "no error",
			image:       "gcr.io/k8s-skaffold:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
			expected:    []string{"copy test.go /test.go"},
		},
		{
			description: "cmd error",
			image:       "gcr.io/k8s-skaffold:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
			cmdErr:      fmt.Errorf(""),
			shouldErr:   true,
		},
		{
			description: "client error",
			image:       "gcr.io/k8s-skaffold:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
			clientErr:   fmt.Errorf(""),
			shouldErr:   true,
		},
		{
			description: "no copy",
			image:       "gcr.io/different-pod:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			cmdRecord := &TestCmdRecorder{err: test.cmdErr}
			defer func(c util.Command) { util.DefaultExecCommand = c }(util.DefaultExecCommand)
			util.DefaultExecCommand = cmdRecord

			defer func(c func() (kubernetes.Interface, error)) { Client = c }(GetClientset)
			Client = func() (kubernetes.Interface, error) {
				return fake.NewSimpleClientset(pod), test.clientErr
			}

			util.DefaultExecCommand = cmdRecord
			err := perform(test.image, test.files, test.cmdFn)
			testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, cmdRecord.cmds)
		})
	}
}
<commit_msg>[linter] add boilerplate to sync_test.go<commit_after>/*
Copyright 2018 The Skaffold 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.
*/

package kubernetes

import (
	"fmt"
	"os/exec"
	"strings"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes/fake"

	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
	"github.com/GoogleContainerTools/skaffold/testutil"
	v1 "k8s.io/api/core/v1"
	"k8s.io/client-go/kubernetes"
)

type TestCmdRecorder struct {
	cmds []string
	err  error
}

func (t *TestCmdRecorder) RunCmd(cmd *exec.Cmd) error {
	if t.err != nil {
		return t.err
	}
	t.cmds = append(t.cmds, strings.Join(cmd.Args, " "))
	return nil
}

func (t *TestCmdRecorder) RunCmdOut(cmd *exec.Cmd) ([]byte, error) {
	return nil, t.RunCmd(cmd)
}

func fakeCmd(p v1.Pod, c v1.Container, src, dst string) *exec.Cmd {
	return exec.Command("copy", src, dst)
}

var pod = &v1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name: "podname",
	},
	Status: v1.PodStatus{
		Phase: v1.PodRunning,
	},
	Spec: v1.PodSpec{
		Containers: []v1.Container{
			{
				Name:  "container_name",
				Image: "gcr.io/k8s-skaffold:123",
			},
		},
	},
}

func TestPerform(t *testing.T) {
	var tests = []struct {
		description string
		image       string
		files       map[string]string
		cmdFn       func(v1.Pod, v1.Container, string, string) *exec.Cmd
		cmdErr      error
		clientErr   error
		expected    []string
		shouldErr   bool
	}{
		{
			description: "no error",
			image:       "gcr.io/k8s-skaffold:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
			expected:    []string{"copy test.go /test.go"},
		},
		{
			description: "cmd error",
			image:       "gcr.io/k8s-skaffold:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
			cmdErr:      fmt.Errorf(""),
			shouldErr:   true,
		},
		{
			description: "client error",
			image:       "gcr.io/k8s-skaffold:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
			clientErr:   fmt.Errorf(""),
			shouldErr:   true,
		},
		{
			description: "no copy",
			image:       "gcr.io/different-pod:123",
			files:       map[string]string{"test.go": "/test.go"},
			cmdFn:       fakeCmd,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			cmdRecord := &TestCmdRecorder{err: test.cmdErr}
			defer func(c util.Command) { util.DefaultExecCommand = c }(util.DefaultExecCommand)
			util.DefaultExecCommand = cmdRecord

			defer func(c func() (kubernetes.Interface, error)) { Client = c }(GetClientset)
			Client = func() (kubernetes.Interface, error) {
				return fake.NewSimpleClientset(pod), test.clientErr
			}

			util.DefaultExecCommand = cmdRecord
			err := perform(test.image, test.files, test.cmdFn)
			testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, cmdRecord.cmds)
		})
	}
}
<|endoftext|>


<commit_before>package bitbucket

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"

	"github.com/hashicorp/terraform/helper/schema"
)

type CloneUrl struct {
	Href string `json:"href,omitempty"`
	Name string `json:"name,omitempty"`
}

type Repository struct {
	SCM         string `json:"scm,omitempty"`
	HasWiki     bool   `json:"has_wiki,omitempty"`
	HasIssues   bool   `json:"has_issues,omitempty"`
	Website     string `json:"website,omitempty"`
	IsPrivate   bool   `json:"is_private,omitempty"`
	ForkPolicy  string `json:"fork_policy,omitempty"`
	Language    string `json:"language,omitempty"`
	Description string `json:"description,omitempty"`
	Name        string `json:"name,omitempty"`
	UUID        string `json:"uuid,omitempty"`
	Project     struct {
		Key string `json:"key,omitempty"`
	} `json:"project,omitempty"`
	Links struct {
		Clone []CloneUrl `json:"clone,omitempty"`
	} `json:"links,omitempty"`
}

type PipelinesConfig struct {
	Pipelines bool `json:"enabled"`
}

type PipelinesVariable struct {
	Key     string `json:"key,omitempty"`
	Value   string `json:"value,omitempty"`
	Secured bool   `json:"secured"`
	// UUID    string `json:"uuid,omitempty"`
}

func resourceRepository() *schema.Resource {
	return &schema.Resource{
		Create: resourceRepositoryCreate,
		Update: resourceRepositoryUpdate,
		Read:   resourceRepositoryRead,
		Delete: resourceRepositoryDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"scm": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "git",
			},
			"has_wiki": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"has_issues": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"website": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"clone_ssh": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"clone_https": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"project_key": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"is_private": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"fork_policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "allow_forks",
			},
			"language": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"owner": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"pipelines": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"pipelines_variable": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"key": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"value": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"secured": &schema.Schema{
							Type:     schema.TypeBool,
							Optional: true,
							Default:  false,
						},
						// "uuid": &schema.Schema{
						//	Type:     schema.TypeString,
						//	Computed: true,
						//},
					},
				},
			},
		},
	}
}

func newRepositoryFromResource(d *schema.ResourceData) *Repository {
	repo := &Repository{
		Name:        d.Get("name").(string),
		Language:    d.Get("language").(string),
		IsPrivate:   d.Get("is_private").(bool),
		Description: d.Get("description").(string),
		ForkPolicy:  d.Get("fork_policy").(string),
		HasWiki:     d.Get("has_wiki").(bool),
		HasIssues:   d.Get("has_issues").(bool),
		SCM:         d.Get("scm").(string),
		Website:     d.Get("website").(string),
	}

	repo.Project.Key = d.Get("project_key").(string)
	return repo
}

func enablePipelinesFromResource(d *schema.ResourceData) *PipelinesConfig {
	//return pipelines
	pipeline := &PipelinesConfig{
		Pipelines: d.Get("pipelines").(bool),
	}
	return pipeline
}

func newPipelinesVarFromResource(d *schema.ResourceData) *PipelinesVariable {
	pipelines_var := &PipelinesVariable{
		Key:     d.Get("key").(string),
		Value:   d.Get("value").(string),
		Secured: d.Get("secured").(bool),
	}
	return pipelines_var
}

func resourceRepositoryUpdate(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketClient)
	repository := newRepositoryFromResource(d)

	var jsonbuffer []byte

	jsonpayload := bytes.NewBuffer(jsonbuffer)
	enc := json.NewEncoder(jsonpayload)
	enc.Encode(repository)

	_, err := client.Put(fmt.Sprintf("2.0/repositories/%s/%s",
		d.Get("owner").(string),
		d.Get("name").(string),
	), jsonpayload)

	if err != nil {
		return err
	}

	pipelines := enablePipelinesFromResource(d)
	pipelinesbytedata, err := json.Marshal(pipelines)

	// do a put against pipelines endpoint
	_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config",
		d.Get("owner").(string),
		d.Get("name").(string),
	), bytes.NewBuffer(pipelinesbytedata))

	if err != nil {
		return err
	}

	// pipelines_var := newPipelinesVarFromResource(d)
	//pipelinesvarbytedata, err := json.Marshal(pipelines_var)

	// todo: get uuid of variable

	// do a put against variables endpoint
	// _, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/variables/%s",
	//	d.Get("owner").(string),
	//	d.Get("name").(string),
	// d.Get("uuid").(string),
	//), bytes.NewBuffer(pipelinesvarbytedata))

	//if err != nil {
	//	return err
	//}

	return resourceRepositoryRead(d, m)
}

func resourceRepositoryCreate(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketClient)
	repo := newRepositoryFromResource(d)

	bytedata, err := json.Marshal(repo)

	if err != nil {
		return err
	}

	_, err = client.Post(fmt.Sprintf("2.0/repositories/%s/%s",
		d.Get("owner").(string),
		d.Get("name").(string),
	), bytes.NewBuffer(bytedata))

	if err != nil {
		return err
	}

	d.SetId(string(fmt.Sprintf("%s/%s", d.Get("owner").(string), d.Get("name").(string))))

	if d.Get("pipelines") != nil {
		pipelines := enablePipelinesFromResource(d)
		pipelinesbytedata, err := json.Marshal(pipelines)

		_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config",
			d.Get("owner").(string),
			d.Get("name").(string),
		), bytes.NewBuffer(pipelinesbytedata))

		if err != nil {
			return err
		}
	}

	//fmt.Printf("%+v\n", pvar)
	// if d.Get("pipelines_variable") != nil {
	//	pipelines_var := newPipelinesVarFromResource(d)
	//	pipelinesvarbytedata, err := json.Marshal(pipelines_var)

	//	_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/variables",
	//		d.Get("owner").(string),
	//		d.Get("name").(string),
	//	), bytes.NewBuffer(pipelinesvarbytedata))

	//	if err != nil {
	//		return err
	//	}
	//}

	return resourceRepositoryRead(d, m)
}

func resourceRepositoryRead(d *schema.ResourceData, m interface{}) error {

	client := m.(*BitbucketClient)
	repo_req, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/%s",
		d.Get("owner").(string),
		d.Get("name").(string),
	))

	if repo_req.StatusCode == 200 {

		var repo Repository

		body, readerr := ioutil.ReadAll(repo_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &repo)
		if decodeerr != nil {
			return decodeerr
		}

		d.Set("scm", repo.SCM)
		d.Set("is_private", repo.IsPrivate)
		d.Set("has_wiki", repo.HasWiki)
		d.Set("has_issues", repo.HasIssues)
		d.Set("name", repo.Name)
		d.Set("language", repo.Language)
		d.Set("fork_policy", repo.ForkPolicy)
		d.Set("website", repo.Website)
		d.Set("description", repo.Description)
		d.Set("project_key", repo.Project.Key)

		for _, clone_url := range repo.Links.Clone {
			if clone_url.Name == "https" {
				d.Set("clone_https", clone_url.Href)
			} else {
				d.Set("clone_ssh", clone_url.Href)
			}
		}
	}

	// do a get against api to get pipelines config
	pipelines_req, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config",
		d.Get("owner").(string),
		d.Get("name").(string),
	))

	// unmarshal it into my struct
	if pipelines_req.StatusCode == 200 {

		var pipeline PipelinesConfig

		body, readerr := ioutil.ReadAll(pipelines_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &pipeline)
		if decodeerr != nil {
			return decodeerr
		}

		// set the resource data
		d.Set("pipelines", pipeline.Pipelines)
	}

	// GET for pipelines variables
	pipelines_var_req, _ := client.Get(
		fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/variables/",
			d.Get("owner").(string),
			d.Get("name").(string),
		))

	// unmarshal it into my struct
	if pipelines_var_req.StatusCode == 200 {

		var pipeline_var PipelinesVariable
		body, readerr := ioutil.ReadAll(pipelines_var_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &pipeline_var)
		if decodeerr != nil {
			return decodeerr
		}

		//fmt.Printf("%T\n", pipeline_var)
		// set the resource data
		d.Set("pipelines_variable.key", pipeline_var.Key)
		d.Set("value", pipeline_var.Value)
		d.Set("secured", pipeline_var.Secured)
		// d.Set("uuid", pipeline_var.UUID)
	}
	return nil
}

func resourceRepositoryDelete(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketClient)
	_, err := client.Delete(
		fmt.Sprintf("2.0/repositories/%s/%s",
			d.Get("owner").(string),
			d.Get("name").(string),
		))

	// DELETE for pipelines variables
	//pipelines_var_req, _ := client.Delete(
	//	fmt.Sprintf("/1.0/repositories/%s/%s/pipelines_config/variables/%s",
	//		d.Get("owner").(string),
	//		d.Get("name").(string),
	//		d.Get("variable_uuid").(string),
	//	))

	return err
}
<commit_msg>Progress<commit_after>package bitbucket

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/hashicorp/terraform/helper/schema"
	"io/ioutil"
	"log"
)

type CloneUrl struct {
	Href string `json:"href,omitempty"`
	Name string `json:"name,omitempty"`
}

type Repository struct {
	SCM         string `json:"scm,omitempty"`
	HasWiki     bool   `json:"has_wiki,omitempty"`
	HasIssues   bool   `json:"has_issues,omitempty"`
	Website     string `json:"website,omitempty"`
	IsPrivate   bool   `json:"is_private,omitempty"`
	ForkPolicy  string `json:"fork_policy,omitempty"`
	Language    string `json:"language,omitempty"`
	Description string `json:"description,omitempty"`
	Name        string `json:"name,omitempty"`
	UUID        string `json:"uuid,omitempty"`
	Project     struct {
		Key string `json:"key,omitempty"`
	} `json:"project,omitempty"`
	Links struct {
		Clone []CloneUrl `json:"clone,omitempty"`
	} `json:"links,omitempty"`
}

type PipelinesConfig struct {
	Pipelines bool `json:"enabled"`
}

type PipelinesVariable struct {
	Key     string `json:"key,omitempty"`
	Value   string `json:"value,omitempty"`
	Secured bool   `json:"secured"`
	// UUID    string `json:"uuid,omitempty"`
}

type PipelinesSshKey struct {
	PrivateKey string `json:"private_key,omitempty"`
	PublicKey  string `json:"public_key,omitempty"`
}

func resourceRepository() *schema.Resource {
	return &schema.Resource{
		Create: resourceRepositoryCreate,
		Update: resourceRepositoryUpdate,
		Read:   resourceRepositoryRead,
		Delete: resourceRepositoryDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"scm": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "git",
			},
			"has_wiki": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"has_issues": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"website": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"clone_ssh": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"clone_https": &schema.Schema{
				Type:     schema.TypeString,
				Computed: true,
			},
			"project_key": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"is_private": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  true,
			},
			"fork_policy": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "allow_forks",
			},
			"language": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"description": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
			},
			"owner": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
			"pipelines": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
			"pipelines_variable": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"key": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"value": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"secured": &schema.Schema{
							Type:     schema.TypeBool,
							Optional: true,
							Default:  false,
						},
						// "uuid": &schema.Schema{
						//	Type:     schema.TypeString,
						//	Computed: true,
						//},
					},
				},
			},
			"pipelines_ssh_key": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"private_key": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
						"public_key": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},
		},
	}
}

func newRepositoryFromResource(d *schema.ResourceData) *Repository {
	repo := &Repository{
		Name:        d.Get("name").(string),
		Language:    d.Get("language").(string),
		IsPrivate:   d.Get("is_private").(bool),
		Description: d.Get("description").(string),
		ForkPolicy:  d.Get("fork_policy").(string),
		HasWiki:     d.Get("has_wiki").(bool),
		HasIssues:   d.Get("has_issues").(bool),
		SCM:         d.Get("scm").(string),
		Website:     d.Get("website").(string),
	}

	repo.Project.Key = d.Get("project_key").(string)
	return repo
}

func enablePipelinesFromResource(d *schema.ResourceData) *PipelinesConfig {
	//return pipelines
	pipeline := &PipelinesConfig{
		Pipelines: d.Get("pipelines").(bool),
	}
	return pipeline
}

func newPipelinesVarFromResource(d *schema.ResourceData) *PipelinesVariable {
	pipelines_var := &PipelinesVariable{
		Key:     d.Get("key").(string),
		Value:   d.Get("value").(string),
		Secured: d.Get("secured").(bool),
	}
	return pipelines_var
}

func newPipelinesSshKeyFromResource(d *schema.ResourceData) *PipelinesSshKey {
	pipelines_ssh_key := &PipelinesSshKey{
		PrivateKey: d.Get("private_key").(string),
		PublicKey:  d.Get("public_key").(string),
	}
	return pipelines_ssh_key
}

func resourceRepositoryUpdate(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketClient)
	repository := newRepositoryFromResource(d)

	var jsonbuffer []byte

	jsonpayload := bytes.NewBuffer(jsonbuffer)
	enc := json.NewEncoder(jsonpayload)
	enc.Encode(repository)

	_, err := client.Put(fmt.Sprintf("2.0/repositories/%s/%s",
		d.Get("owner").(string),
		d.Get("name").(string),
	), jsonpayload)

	if err != nil {
		return err
	}

	pipelines := enablePipelinesFromResource(d)
	pipelinesbytedata, err := json.Marshal(pipelines)

	// do a put against pipelines endpoint
	_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config",
		d.Get("owner").(string),
		d.Get("name").(string),
	), bytes.NewBuffer(pipelinesbytedata))

	if err != nil {
		return err
	}

	// begin pipelines ssh keys
	pipelines_ssh_key := newPipelinesSshKeyFromResource(d)
	pipelinessshkeybytedata, err := json.Marshal(pipelines_ssh_key)

	_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/ssh/key_pair",
		d.Get("owner").(string),
		d.Get("name").(string),
	), bytes.NewBuffer(pipelinessshkeybytedata))

	if err != nil {
		return err
	}

	// pipelines_var := newPipelinesVarFromResource(d)
	//pipelinesvarbytedata, err := json.Marshal(pipelines_var)

	// todo: get uuid of variable

	// do a put against variables endpoint
	// _, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/variables/%s",
	//	d.Get("owner").(string),
	//	d.Get("name").(string),
	// d.Get("uuid").(string),
	//), bytes.NewBuffer(pipelinesvarbytedata))

	//if err != nil {
	//	return err
	//}

	return resourceRepositoryRead(d, m)
}

func resourceRepositoryCreate(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketClient)
	repo := newRepositoryFromResource(d)

	bytedata, err := json.Marshal(repo)

	if err != nil {
		return err
	}

	_, err = client.Post(fmt.Sprintf("2.0/repositories/%s/%s",
		d.Get("owner").(string),
		d.Get("name").(string),
	), bytes.NewBuffer(bytedata))

	if err != nil {
		return err
	}

	d.SetId(string(fmt.Sprintf("%s/%s", d.Get("owner").(string), d.Get("name").(string))))

	if d.Get("pipelines") != nil {
		pipelines := enablePipelinesFromResource(d)
		pipelinesbytedata, err := json.Marshal(pipelines)

		_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config",
			d.Get("owner").(string),
			d.Get("name").(string),
		), bytes.NewBuffer(pipelinesbytedata))

		if err != nil {
			return err
		}
	}

	if d.Get("pipelines_ssh_key") != nil {

		// this might work
		//pipelineskey := d.Get("pipelines_ssh_key").(*schema.Set)

		pipelines_ssh_key := newPipelinesSshKeyFromResource(d)
		pipelinessshkeybytedata, err := json.Marshal(pipelines_ssh_key)

		_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/ssh/key_pair",
			d.Get("owner").(string),
			d.Get("name").(string),
		), bytes.NewBuffer(pipelinessshkeybytedata))

		if err != nil {
			return err
		}
	}

	// if d.Get("pipelines_variable") != nil {
	//	pipelines_var := newPipelinesVarFromResource(d)
	//	pipelinesvarbytedata, err := json.Marshal(pipelines_var)

	//	_, err = client.Put(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/variables",
	//		d.Get("owner").(string),
	//		d.Get("name").(string),
	//	), bytes.NewBuffer(pipelinesvarbytedata))

	//	if err != nil {
	//		return err
	//	}
	//}

	return resourceRepositoryRead(d, m)
}

func resourceRepositoryRead(d *schema.ResourceData, m interface{}) error {

	client := m.(*BitbucketClient)
	repo_req, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/%s",
		d.Get("owner").(string),
		d.Get("name").(string),
	))

	if repo_req.StatusCode == 200 {

		var repo Repository

		body, readerr := ioutil.ReadAll(repo_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &repo)
		if decodeerr != nil {
			return decodeerr
		}

		d.Set("scm", repo.SCM)
		d.Set("is_private", repo.IsPrivate)
		d.Set("has_wiki", repo.HasWiki)
		d.Set("has_issues", repo.HasIssues)
		d.Set("name", repo.Name)
		d.Set("language", repo.Language)
		d.Set("fork_policy", repo.ForkPolicy)
		d.Set("website", repo.Website)
		d.Set("description", repo.Description)
		d.Set("project_key", repo.Project.Key)

		for _, clone_url := range repo.Links.Clone {
			if clone_url.Name == "https" {
				d.Set("clone_https", clone_url.Href)
			} else {
				d.Set("clone_ssh", clone_url.Href)
			}
		}
	}

	// do a get against api to get pipelines config
	pipelines_req, _ := client.Get(fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config",
		d.Get("owner").(string),
		d.Get("name").(string),
	))

	// unmarshal it into my struct
	if pipelines_req.StatusCode == 200 {

		var pipeline PipelinesConfig

		body, readerr := ioutil.ReadAll(pipelines_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &pipeline)
		if decodeerr != nil {
			return decodeerr
		}

		// set the resource data
		d.Set("pipelines", pipeline.Pipelines)
	}

	// GET for pipelines variables
	pipelines_var_req, _ := client.Get(
		fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/variables/",
			d.Get("owner").(string),
			d.Get("name").(string),
		))

	// unmarshal it into my struct
	// I think this is where my logic is wrong for variables
	if pipelines_var_req.StatusCode == 200 {

		var pipeline_var PipelinesVariable
		body, readerr := ioutil.ReadAll(pipelines_var_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &pipeline_var)
		if decodeerr != nil {
			return decodeerr
		}

		//fmt.Printf("%T\n", pipeline_var)
		// set the resource data
		d.Set("key", pipeline_var.Key)
		d.Set("value", pipeline_var.Value)
		d.Set("secured", pipeline_var.Secured)
		// d.Set("uuid", pipeline_var.UUID)
	}

	// GET for ssh keys
	pipelines_sshkey_req, _ := client.Get(
		fmt.Sprintf("2.0/repositories/%s/%s/pipelines_config/ssh/key_pair",
			d.Get("owner").(string),
			d.Get("name").(string),
		))

	// 200 = already exists
	// 404 = doesn't exist
	if pipelines_sshkey_req.StatusCode == 200 {

		var pipeline_ssh_key PipelinesSshKey
		body, readerr := ioutil.ReadAll(pipelines_sshkey_req.Body)
		if readerr != nil {
			return readerr
		}

		decodeerr := json.Unmarshal(body, &pipeline_ssh_key)
		if decodeerr != nil {
			return decodeerr
		}

		// set the resource data
		d.Set("private_key", pipeline_ssh_key.PrivateKey)
		d.Set("public_key", pipeline_ssh_key.PublicKey)
	}
	return nil
}

func resourceRepositoryDelete(d *schema.ResourceData, m interface{}) error {
	client := m.(*BitbucketClient)
	_, err := client.Delete(
		fmt.Sprintf("2.0/repositories/%s/%s",
			d.Get("owner").(string),
			d.Get("name").(string),
		))

	// DELETE for pipelines variables
	//pipelines_var_req, _ := client.Delete(
	//	fmt.Sprintf("/1.0/repositories/%s/%s/pipelines_config/variables/%s",
	//		d.Get("owner").(string),
	//		d.Get("name").(string),
	//		d.Get("variable_uuid").(string),
	//	))

	return err
}
<|endoftext|>


<commit_before>package descriptor

import (
	"fmt"
	"strings"

	"github.com/golang/glog"
	"github.com/golang/protobuf/proto"
	descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule"
	options "google.golang.org/genproto/googleapis/api/annotations"
)

// loadServices registers services and their methods from "targetFile" to "r".
// It must be called after loadFile is called for all files so that loadServices
// can resolve names of message types and their fields.
func (r *Registry) loadServices(file *File) error {
	glog.V(1).Infof("Loading services from %s", file.GetName())
	var svcs []*Service
	for _, sd := range file.GetService() {
		glog.V(2).Infof("Registering %s", sd.GetName())
		svc := &Service{
			File: file,
			ServiceDescriptorProto: sd,
		}
		for _, md := range sd.GetMethod() {
			glog.V(2).Infof("Processing %s.%s", sd.GetName(), md.GetName())
			opts, err := extractAPIOptions(md)
			if err != nil {
				glog.Errorf("Failed to extract ApiMethodOptions from %s.%s: %v", svc.GetName(), md.GetName(), err)
				return err
			}
			if opts == nil {
				glog.V(1).Infof("Found non-target method: %s.%s", svc.GetName(), md.GetName())
			}
			meth, err := r.newMethod(svc, md, opts)
			if err != nil {
				return err
			}
			svc.Methods = append(svc.Methods, meth)
		}
		if len(svc.Methods) == 0 {
			continue
		}
		glog.V(2).Infof("Registered %s with %d method(s)", svc.GetName(), len(svc.Methods))
		svcs = append(svcs, svc)
	}
	file.Services = svcs
	return nil
}

func (r *Registry) newMethod(svc *Service, md *descriptor.MethodDescriptorProto, opts *options.HttpRule) (*Method, error) {
	requestType, err := r.LookupMsg(svc.File.GetPackage(), md.GetInputType())
	if err != nil {
		return nil, err
	}
	responseType, err := r.LookupMsg(svc.File.GetPackage(), md.GetOutputType())
	if err != nil {
		return nil, err
	}
	meth := &Method{
		Service:               svc,
		MethodDescriptorProto: md,
		RequestType:           requestType,
		ResponseType:          responseType,
	}

	newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
		var (
			httpMethod   string
			pathTemplate string
		)
		switch {
		case opts.GetGet() != "":
			httpMethod = "GET"
			pathTemplate = opts.GetGet()
			if opts.Body != "" {
				return nil, fmt.Errorf("needs request body even though http method is GET: %s", md.GetName())
			}

		case opts.GetPut() != "":
			httpMethod = "PUT"
			pathTemplate = opts.GetPut()

		case opts.GetPost() != "":
			httpMethod = "POST"
			pathTemplate = opts.GetPost()

		case opts.GetDelete() != "":
			httpMethod = "DELETE"
			pathTemplate = opts.GetDelete()
			if opts.Body != "" && !r.allowDeleteBody {
				return nil, fmt.Errorf("needs request body even though http method is DELETE: %s", md.GetName())
			}

		case opts.GetPatch() != "":
			httpMethod = "PATCH"
			pathTemplate = opts.GetPatch()

		case opts.GetCustom() != nil:
			custom := opts.GetCustom()
			httpMethod = custom.Kind
			pathTemplate = custom.Path

		default:
			glog.V(1).Infof("No pattern specified in google.api.HttpRule: %s", md.GetName())
			return nil, nil
		}

		parsed, err := httprule.Parse(pathTemplate)
		if err != nil {
			return nil, err
		}
		tmpl := parsed.Compile()

		if md.GetClientStreaming() && len(tmpl.Fields) > 0 {
			return nil, fmt.Errorf("cannot use path parameter in client streaming")
		}

		b := &Binding{
			Method:     meth,
			Index:      idx,
			PathTmpl:   tmpl,
			HTTPMethod: httpMethod,
		}

		for _, f := range tmpl.Fields {
			param, err := r.newParam(meth, f)
			if err != nil {
				return nil, err
			}
			b.PathParams = append(b.PathParams, param)
		}

		// TODO(yugui) Handle query params

		b.Body, err = r.newBody(meth, opts.Body)
		if err != nil {
			return nil, err
		}

		return b, nil
	}
	b, err := newBinding(opts, 0)
	if err != nil {
		return nil, err
	}

	if b != nil {
		meth.Bindings = append(meth.Bindings, b)
	}
	for i, additional := range opts.GetAdditionalBindings() {
		if len(additional.AdditionalBindings) > 0 {
			return nil, fmt.Errorf("additional_binding in additional_binding not allowed: %s.%s", svc.GetName(), meth.GetName())
		}
		b, err := newBinding(additional, i+1)
		if err != nil {
			return nil, err
		}
		meth.Bindings = append(meth.Bindings, b)
	}

	return meth, nil
}

func extractAPIOptions(meth *descriptor.MethodDescriptorProto) (*options.HttpRule, error) {
	if meth.Options == nil {
		return nil, nil
	}
	if !proto.HasExtension(meth.Options, options.E_Http) {
		return nil, nil
	}
	ext, err := proto.GetExtension(meth.Options, options.E_Http)
	if err != nil {
		return nil, err
	}
	opts, ok := ext.(*options.HttpRule)
	if !ok {
		return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
	}
	return opts, nil
}

func (r *Registry) newParam(meth *Method, path string) (Parameter, error) {
	msg := meth.RequestType
	fields, err := r.resolveFiledPath(msg, path)
	if err != nil {
		return Parameter{}, err
	}
	l := len(fields)
	if l == 0 {
		return Parameter{}, fmt.Errorf("invalid field access list for %s", path)
	}
	target := fields[l-1].Target
	switch target.GetType() {
	case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP:
		glog.V(2).Infoln("found aggregate type:", target, target.TypeName)
		if IsWellKnownType(*target.TypeName) {
			glog.V(2).Infoln("found well known aggregate type:", target)
		} else {
			return Parameter{}, fmt.Errorf("aggregate type %s in parameter of %s.%s: %s", target.Type, meth.Service.GetName(), meth.GetName(), path)
		}
	}
	return Parameter{
		FieldPath: FieldPath(fields),
		Method:    meth,
		Target:    fields[l-1].Target,
	}, nil
}

func (r *Registry) newBody(meth *Method, path string) (*Body, error) {
	msg := meth.RequestType
	switch path {
	case "":
		return nil, nil
	case "*":
		return &Body{FieldPath: nil}, nil
	}
	fields, err := r.resolveFiledPath(msg, path)
	if err != nil {
		return nil, err
	}
	return &Body{FieldPath: FieldPath(fields)}, nil
}

// lookupField looks up a field named "name" within "msg".
// It returns nil if no such field found.
func lookupField(msg *Message, name string) *Field {
	for _, f := range msg.Fields {
		if f.GetName() == name {
			return f
		}
	}
	return nil
}

// resolveFieldPath resolves "path" into a list of fieldDescriptor, starting from "msg".
func (r *Registry) resolveFiledPath(msg *Message, path string) ([]FieldPathComponent, error) {
	if path == "" {
		return nil, nil
	}

	root := msg
	var result []FieldPathComponent
	for i, c := range strings.Split(path, ".") {
		if i > 0 {
			f := result[i-1].Target
			switch f.GetType() {
			case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP:
				var err error
				msg, err = r.LookupMsg(msg.FQMN(), f.GetTypeName())
				if err != nil {
					return nil, err
				}
			default:
				return nil, fmt.Errorf("not an aggregate type: %s in %s", f.GetName(), path)
			}
		}

		glog.V(2).Infof("Lookup %s in %s", c, msg.FQMN())
		f := lookupField(msg, c)
		if f == nil {
			return nil, fmt.Errorf("no field %q found in %s", path, root.GetName())
		}
		if f.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED {
			return nil, fmt.Errorf("repeated field not allowed in field path: %s in %s", f.GetName(), path)
		}
		result = append(result, FieldPathComponent{Name: c, Target: f})
	}
	return result, nil
}
<commit_msg>Change error message in protoc-gen-grpc-gateway<commit_after>package descriptor

import (
	"fmt"
	"strings"

	"github.com/golang/glog"
	"github.com/golang/protobuf/proto"
	descriptor "github.com/golang/protobuf/protoc-gen-go/descriptor"
	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/httprule"
	options "google.golang.org/genproto/googleapis/api/annotations"
)

// loadServices registers services and their methods from "targetFile" to "r".
// It must be called after loadFile is called for all files so that loadServices
// can resolve names of message types and their fields.
func (r *Registry) loadServices(file *File) error {
	glog.V(1).Infof("Loading services from %s", file.GetName())
	var svcs []*Service
	for _, sd := range file.GetService() {
		glog.V(2).Infof("Registering %s", sd.GetName())
		svc := &Service{
			File: file,
			ServiceDescriptorProto: sd,
		}
		for _, md := range sd.GetMethod() {
			glog.V(2).Infof("Processing %s.%s", sd.GetName(), md.GetName())
			opts, err := extractAPIOptions(md)
			if err != nil {
				glog.Errorf("Failed to extract ApiMethodOptions from %s.%s: %v", svc.GetName(), md.GetName(), err)
				return err
			}
			if opts == nil {
				glog.V(1).Infof("Found non-target method: %s.%s", svc.GetName(), md.GetName())
			}
			meth, err := r.newMethod(svc, md, opts)
			if err != nil {
				return err
			}
			svc.Methods = append(svc.Methods, meth)
		}
		if len(svc.Methods) == 0 {
			continue
		}
		glog.V(2).Infof("Registered %s with %d method(s)", svc.GetName(), len(svc.Methods))
		svcs = append(svcs, svc)
	}
	file.Services = svcs
	return nil
}

func (r *Registry) newMethod(svc *Service, md *descriptor.MethodDescriptorProto, opts *options.HttpRule) (*Method, error) {
	requestType, err := r.LookupMsg(svc.File.GetPackage(), md.GetInputType())
	if err != nil {
		return nil, err
	}
	responseType, err := r.LookupMsg(svc.File.GetPackage(), md.GetOutputType())
	if err != nil {
		return nil, err
	}
	meth := &Method{
		Service:               svc,
		MethodDescriptorProto: md,
		RequestType:           requestType,
		ResponseType:          responseType,
	}

	newBinding := func(opts *options.HttpRule, idx int) (*Binding, error) {
		var (
			httpMethod   string
			pathTemplate string
		)
		switch {
		case opts.GetGet() != "":
			httpMethod = "GET"
			pathTemplate = opts.GetGet()
			if opts.Body != "" {
				return nil, fmt.Errorf("must not set request body when http method is GET: %s", md.GetName())
			}

		case opts.GetPut() != "":
			httpMethod = "PUT"
			pathTemplate = opts.GetPut()

		case opts.GetPost() != "":
			httpMethod = "POST"
			pathTemplate = opts.GetPost()

		case opts.GetDelete() != "":
			httpMethod = "DELETE"
			pathTemplate = opts.GetDelete()
			if opts.Body != "" && !r.allowDeleteBody {
				return nil, fmt.Errorf("must not set request body when http method is DELETE except allow_delete_body option is true: %s", md.GetName())
			}

		case opts.GetPatch() != "":
			httpMethod = "PATCH"
			pathTemplate = opts.GetPatch()

		case opts.GetCustom() != nil:
			custom := opts.GetCustom()
			httpMethod = custom.Kind
			pathTemplate = custom.Path

		default:
			glog.V(1).Infof("No pattern specified in google.api.HttpRule: %s", md.GetName())
			return nil, nil
		}

		parsed, err := httprule.Parse(pathTemplate)
		if err != nil {
			return nil, err
		}
		tmpl := parsed.Compile()

		if md.GetClientStreaming() && len(tmpl.Fields) > 0 {
			return nil, fmt.Errorf("cannot use path parameter in client streaming")
		}

		b := &Binding{
			Method:     meth,
			Index:      idx,
			PathTmpl:   tmpl,
			HTTPMethod: httpMethod,
		}

		for _, f := range tmpl.Fields {
			param, err := r.newParam(meth, f)
			if err != nil {
				return nil, err
			}
			b.PathParams = append(b.PathParams, param)
		}

		// TODO(yugui) Handle query params

		b.Body, err = r.newBody(meth, opts.Body)
		if err != nil {
			return nil, err
		}

		return b, nil
	}
	b, err := newBinding(opts, 0)
	if err != nil {
		return nil, err
	}

	if b != nil {
		meth.Bindings = append(meth.Bindings, b)
	}
	for i, additional := range opts.GetAdditionalBindings() {
		if len(additional.AdditionalBindings) > 0 {
			return nil, fmt.Errorf("additional_binding in additional_binding not allowed: %s.%s", svc.GetName(), meth.GetName())
		}
		b, err := newBinding(additional, i+1)
		if err != nil {
			return nil, err
		}
		meth.Bindings = append(meth.Bindings, b)
	}

	return meth, nil
}

func extractAPIOptions(meth *descriptor.MethodDescriptorProto) (*options.HttpRule, error) {
	if meth.Options == nil {
		return nil, nil
	}
	if !proto.HasExtension(meth.Options, options.E_Http) {
		return nil, nil
	}
	ext, err := proto.GetExtension(meth.Options, options.E_Http)
	if err != nil {
		return nil, err
	}
	opts, ok := ext.(*options.HttpRule)
	if !ok {
		return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
	}
	return opts, nil
}

func (r *Registry) newParam(meth *Method, path string) (Parameter, error) {
	msg := meth.RequestType
	fields, err := r.resolveFiledPath(msg, path)
	if err != nil {
		return Parameter{}, err
	}
	l := len(fields)
	if l == 0 {
		return Parameter{}, fmt.Errorf("invalid field access list for %s", path)
	}
	target := fields[l-1].Target
	switch target.GetType() {
	case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP:
		glog.V(2).Infoln("found aggregate type:", target, target.TypeName)
		if IsWellKnownType(*target.TypeName) {
			glog.V(2).Infoln("found well known aggregate type:", target)
		} else {
			return Parameter{}, fmt.Errorf("aggregate type %s in parameter of %s.%s: %s", target.Type, meth.Service.GetName(), meth.GetName(), path)
		}
	}
	return Parameter{
		FieldPath: FieldPath(fields),
		Method:    meth,
		Target:    fields[l-1].Target,
	}, nil
}

func (r *Registry) newBody(meth *Method, path string) (*Body, error) {
	msg := meth.RequestType
	switch path {
	case "":
		return nil, nil
	case "*":
		return &Body{FieldPath: nil}, nil
	}
	fields, err := r.resolveFiledPath(msg, path)
	if err != nil {
		return nil, err
	}
	return &Body{FieldPath: FieldPath(fields)}, nil
}

// lookupField looks up a field named "name" within "msg".
// It returns nil if no such field found.
func lookupField(msg *Message, name string) *Field {
	for _, f := range msg.Fields {
		if f.GetName() == name {
			return f
		}
	}
	return nil
}

// resolveFieldPath resolves "path" into a list of fieldDescriptor, starting from "msg".
func (r *Registry) resolveFiledPath(msg *Message, path string) ([]FieldPathComponent, error) {
	if path == "" {
		return nil, nil
	}

	root := msg
	var result []FieldPathComponent
	for i, c := range strings.Split(path, ".") {
		if i > 0 {
			f := result[i-1].Target
			switch f.GetType() {
			case descriptor.FieldDescriptorProto_TYPE_MESSAGE, descriptor.FieldDescriptorProto_TYPE_GROUP:
				var err error
				msg, err = r.LookupMsg(msg.FQMN(), f.GetTypeName())
				if err != nil {
					return nil, err
				}
			default:
				return nil, fmt.Errorf("not an aggregate type: %s in %s", f.GetName(), path)
			}
		}

		glog.V(2).Infof("Lookup %s in %s", c, msg.FQMN())
		f := lookupField(msg, c)
		if f == nil {
			return nil, fmt.Errorf("no field %q found in %s", path, root.GetName())
		}
		if f.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED {
			return nil, fmt.Errorf("repeated field not allowed in field path: %s in %s", f.GetName(), path)
		}
		result = append(result, FieldPathComponent{Name: c, Target: f})
	}
	return result, nil
}
<|endoftext|>


<commit_before>package xml

import "testing"
import "fmt"

func TestAddChild(t *testing.T) {

	docAssertion := func (doc *XmlDocument) (string, string, string) {
		expectedDocAfterAdd :=
		`<?xml version="1.0" encoding="utf-8"?>
<foo>
  <bar/>
</foo>
`
		doc.Root().AddChild("<bar></bar>")

		return doc.String(), expectedDocAfterAdd, "output of the xml doc after AddChild does not match"
	}

	nodeAssertion := func (doc *XmlDocument) (string, string, string) {
		expectedNodeAfterAdd :=
		`<foo>
  <bar/>
</foo>`

		return doc.Root().String(), expectedNodeAfterAdd, "the output of the xml root after AddChild does not match"
	}

	RunTest(t, "node", "add_child", nil, docAssertion, nodeAssertion)

}

func addChildBenchLogic(b *testing.B, doc *XmlDocument) {
	root := doc.Root()

	for i := 0; i < b.N; i++ {
		root.AddChild("<bar></bar>")
	}
}

func BenchmarkAddChild(b *testing.B) {
	RunBenchmark(b, "document", "big_un", addChildBenchLogic) // Run against big doc
}

func BenchmarkAddChildBigDoc(b *testing.B) {
	RunBenchmark(b, "node", "add_child", addChildBenchLogic)
}

func TestAddPreviousSibling(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		err := doc.Root().AddPreviousSibling("<bar></bar><cat></cat>")

		if err != nil {
			t.Errorf("Error adding previous sibling:\n%v\n", err.String())
		}
	}

	RunTest(t, "node", "add_previous_sibling", testLogic)
}

func TestAddNextSibling(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		doc.Root().AddNextSibling("<bar></bar><baz></baz>")
	}

	RunTest(t, "node", "add_next_sibling", testLogic)
}

func TestSetContent(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		root := doc.Root()
		root.SetContent("<fun></fun>")
	}

	RunTest(t, "node", "set_content", testLogic)
}

func BenchmarkSetContent(b *testing.B) {

	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {
			root := doc.Root()
			for i := 0; i < b.N; i++ {
				root.SetContent("<fun></fun>")
			}
	}

	RunBenchmark(b, "node", "set_content", benchmarkLogic)
}

func TestSetChildren(t *testing.T) {
	testLogic := func(t *testing.T, doc *XmlDocument) {
		root := doc.Root()
		root.SetChildren("<fun></fun>")
	}

	RunTest(t, "node", "set_children", testLogic)
}

func _BenchmarkSetChildren(b *testing.B) {
	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {
			println("a")
		root := doc.Root()
			println("b")
//		for i := 0; i < b.N; i++ {
//		for i := 0; i < 1000; i++ {
//		for i := 0; i < 200; i++ {
		for i := 0; i < 2; i++ {
			root.SetChildren("<fun></fun>")
		}
			println("c")
	}

	RunBenchmark(b, "node", "set_children", benchmarkLogic)
}

func TestReplace(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		root := doc.Root()
		root.Replace("<fun></fun><cool/>")
	}

	rootAssertion := func(doc *XmlDocument) (string, string, string) {
		root := doc.Root()
		return root.String(), "<fun/>", "the output of the xml root does not match"
	}

	RunTest(t, "node", "replace", testLogic, rootAssertion)
}

func _BenchmarkReplace(b *testing.B) {

	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {
		root := doc.Root()
//		for i := 0; i < b.N; i++ {
		for i := 0; i < 2; i++ {
			root.Replace("<fun></fun><cool/>")
		}
	}

	RunBenchmark(b, "node", "replace", benchmarkLogic)
}

func TestAttributes(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {

		root := doc.Root()
		attributes := root.Attributes()

		if len(attributes) != 2 || attributes["myname"].String() != "ff" {
			fmt.Printf("%v, %q\n", attributes, attributes["myname"].String())
			t.Error("root's attributes do not match")
		}

		child := root.FirstChild()
		childAttributes := child.Attributes()

		if len(childAttributes) != 1 || childAttributes["class"].String() != "shine" {
			t.Error("child's attributes do not match")
		}
	}

	RunTest(t, "node", "attributes", testLogic)

}

func BenchmarkAttributes(b *testing.B) {
	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {

		root := doc.Root()

		for i := 0; i < b.N; i++ {
			root.SetAttr("garfield", "spaghetti")
		}
	}

	RunBenchmark(b, "node", "attributes", benchmarkLogic)
}
<commit_msg>restore the tests; change the replace test because it needs to get the new node once being replaced<commit_after>package xml

import "testing"
import "fmt"

func TestAddChild(t *testing.T) {

	docAssertion := func (doc *XmlDocument) (string, string, string) {
		expectedDocAfterAdd :=
		`<?xml version="1.0" encoding="utf-8"?>
<foo>
  <bar/>
</foo>
`
		doc.Root().AddChild("<bar></bar>")

		return doc.String(), expectedDocAfterAdd, "output of the xml doc after AddChild does not match"
	}

	nodeAssertion := func (doc *XmlDocument) (string, string, string) {
		expectedNodeAfterAdd :=
		`<foo>
  <bar/>
</foo>`

		return doc.Root().String(), expectedNodeAfterAdd, "the output of the xml root after AddChild does not match"
	}

	RunTest(t, "node", "add_child", nil, docAssertion, nodeAssertion)

}

func addChildBenchLogic(b *testing.B, doc *XmlDocument) {
	root := doc.Root()

	for i := 0; i < b.N; i++ {
		root.AddChild("<bar></bar>")
	}
}

func BenchmarkAddChild(b *testing.B) {
	RunBenchmark(b, "document", "big_un", addChildBenchLogic) // Run against big doc
}

func BenchmarkAddChildBigDoc(b *testing.B) {
	RunBenchmark(b, "node", "add_child", addChildBenchLogic)
}

func TestAddPreviousSibling(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		err := doc.Root().AddPreviousSibling("<bar></bar><cat></cat>")

		if err != nil {
			t.Errorf("Error adding previous sibling:\n%v\n", err.String())
		}
	}

	RunTest(t, "node", "add_previous_sibling", testLogic)
}

func TestAddNextSibling(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		doc.Root().AddNextSibling("<bar></bar><baz></baz>")
	}

	RunTest(t, "node", "add_next_sibling", testLogic)
}

func TestSetContent(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		root := doc.Root()
		root.SetContent("<fun></fun>")
	}

	RunTest(t, "node", "set_content", testLogic)
}

func BenchmarkSetContent(b *testing.B) {

	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {
			root := doc.Root()
			for i := 0; i < b.N; i++ {
				root.SetContent("<fun></fun>")
			}
	}

	RunBenchmark(b, "node", "set_content", benchmarkLogic)
}

func TestSetChildren(t *testing.T) {
	testLogic := func(t *testing.T, doc *XmlDocument) {
		root := doc.Root()
		root.SetChildren("<fun></fun>")
	}

	RunTest(t, "node", "set_children", testLogic)
}

func BenchmarkSetChildren(b *testing.B) {
	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {
		root := doc.Root()
		for i := 0; i < b.N; i++ {
			root.SetChildren("<fun></fun>")
		}
	}

	RunBenchmark(b, "node", "set_children", benchmarkLogic)
}

func TestReplace(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {
		root := doc.Root()
		root.Replace("<fun></fun><cool/>")
	}

	rootAssertion := func(doc *XmlDocument) (string, string, string) {
		root := doc.Root()
		return root.String(), "<fun/>", "the output of the xml root does not match"
	}

	RunTest(t, "node", "replace", testLogic, rootAssertion)
}

func BenchmarkReplace(b *testing.B) {

	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {
		root := doc.Root()
		for i := 0; i < b.N; i++ {
			root.Replace("<fun></fun>")
			root = doc.Root() //once the node has been replaced, we need to get a new node
		}
	}

	RunBenchmark(b, "node", "replace", benchmarkLogic)
}

func TestAttributes(t *testing.T) {

	testLogic := func(t *testing.T, doc *XmlDocument) {

		root := doc.Root()
		attributes := root.Attributes()

		if len(attributes) != 2 || attributes["myname"].String() != "ff" {
			fmt.Printf("%v, %q\n", attributes, attributes["myname"].String())
			t.Error("root's attributes do not match")
		}

		child := root.FirstChild()
		childAttributes := child.Attributes()

		if len(childAttributes) != 1 || childAttributes["class"].String() != "shine" {
			t.Error("child's attributes do not match")
		}
	}

	RunTest(t, "node", "attributes", testLogic)

}

func BenchmarkAttributes(b *testing.B) {
	benchmarkLogic := func(b *testing.B, doc *XmlDocument) {

		root := doc.Root()

		for i := 0; i < b.N; i++ {
			root.SetAttr("garfield", "spaghetti")
		}
	}

	RunBenchmark(b, "node", "attributes", benchmarkLogic)
}
<|endoftext|>